Unified netconf input function

First for external use, later internal
This commit is contained in:
Olof hagsand 2023-03-06 10:48:46 +01:00
parent 04d5f52d90
commit e7c9f3d075
12 changed files with 534 additions and 32 deletions

View file

@ -73,6 +73,10 @@ Developers may need to change their code
### Minor features ### Minor features
* Unified netconf input function
* Three different implementations were used in external, internal and controller code
* The new clixon_netconf_input API unifies all three uses
* Code still experimental controlled by `NEW_NETCONF_INPUT`
* RFC 8528 YANG schema mount * RFC 8528 YANG schema mount
* Made cli/autocli mount-point-aware * Made cli/autocli mount-point-aware
* Internal NETCONF (client <-> backend) * Internal NETCONF (client <-> backend)

View file

@ -77,10 +77,13 @@
/* clixon-data value to save buffer between invocations. /* clixon-data value to save buffer between invocations.
* Saving data may be necessary if socket buffer contains partial netconf messages, such as: * Saving data may be necessary if socket buffer contains partial netconf messages, such as:
* <foo/> ..wait 1min ]]>]]> * <foo/> ..wait 1min ]]>]]>
* XXX move to data
*/ */
#define NETCONF_HASH_BUF "netconf_input_cbuf" /* Unfinished frame */
#define NETCONF_FRAME_STATE "netconf_input_frame_state" #define NETCONF_FRAME_MSG "netconf-frame-msg"
#define NETCONF_FRAME_SIZE "netconf_input_frame_size"
#define NETCONF_FRAME_STATE "netconf-input-frame-state"
#define NETCONF_FRAME_SIZE "netconf-input-frame-size"
/*! Ignore errors on packet errors: continue */ /*! Ignore errors on packet errors: continue */
static int ignore_packet_errors = 1; static int ignore_packet_errors = 1;
@ -149,6 +152,7 @@ netconf_hello_msg(clicon_handle h,
int foundbase_11 = 0; int foundbase_11 = 0;
char *body; char *body;
clicon_debug(1, "%s", __FUNCTION__);
_netconf_hello_nr++; _netconf_hello_nr++;
if (xml_find_type(xn, NULL, "session-id", CX_ELMNT) != NULL) { if (xml_find_type(xn, NULL, "session-id", CX_ELMNT) != NULL) {
clicon_err(OE_XML, errno, "Server received hello with session-id from client, terminating (see RFC 6241 Sec 8.1"); clicon_err(OE_XML, errno, "Server received hello with session-id from client, terminating (see RFC 6241 Sec 8.1");
@ -166,12 +170,15 @@ netconf_hello_msg(clicon_handle h,
continue; continue;
/* When comparing protocol version capability URIs, only the base part is used, in the /* When comparing protocol version capability URIs, only the base part is used, in the
* event any parameters are encoded at the end of the URI string. */ * event any parameters are encoded at the end of the URI string. */
if (strncmp(body, NETCONF_BASE_CAPABILITY_1_0, strlen(NETCONF_BASE_CAPABILITY_1_0)) == 0) /* RFC 4741 */ if (strncmp(body, NETCONF_BASE_CAPABILITY_1_0, strlen(NETCONF_BASE_CAPABILITY_1_0)) == 0){ /* RFC 4741 */
foundbase_10++; foundbase_10++;
clicon_debug(1, "%s foundbase10", __FUNCTION__);
}
else if (strncmp(body, NETCONF_BASE_CAPABILITY_1_1, strlen(NETCONF_BASE_CAPABILITY_1_1)) == 0 && else if (strncmp(body, NETCONF_BASE_CAPABILITY_1_1, strlen(NETCONF_BASE_CAPABILITY_1_1)) == 0 &&
clicon_option_int(h, "CLICON_NETCONF_BASE_CAPABILITY") > 0){ /* RFC 6241 */ clicon_option_int(h, "CLICON_NETCONF_BASE_CAPABILITY") > 0){ /* RFC 6241 */
foundbase_11++; foundbase_11++;
clicon_data_int_set(h, "netconf-framing", NETCONF_SSH_CHUNKED); /* enable chunked enc */ clicon_debug(1, "%s foundbase11", __FUNCTION__);
clicon_data_int_set(h, NETCONF_FRAMING_TYPE, NETCONF_SSH_CHUNKED); /* enable chunked enc */
} }
} }
} }
@ -208,7 +215,7 @@ netconf_rpc_message(clicon_handle h,
cxobj *xc; cxobj *xc;
netconf_framing_type framing; netconf_framing_type framing;
framing = clicon_data_int_get(h, "netconf-framing"); framing = clicon_data_int_get(h, NETCONF_FRAMING_TYPE);
if (_netconf_hello_nr == 0 && if (_netconf_hello_nr == 0 &&
clicon_option_bool(h, "CLICON_NETCONF_HELLO_OPTIONAL") == 0){ clicon_option_bool(h, "CLICON_NETCONF_HELLO_OPTIONAL") == 0){
if (netconf_operation_failed_xml(&xret, "rpc", "Client must send an hello element before any RPC")< 0) if (netconf_operation_failed_xml(&xret, "rpc", "Client must send an hello element before any RPC")< 0)
@ -319,9 +326,10 @@ netconf_input_packet(clicon_handle h,
netconf_framing_type framing; netconf_framing_type framing;
clicon_debug(1, "%s", __FUNCTION__); clicon_debug(1, "%s", __FUNCTION__);
clicon_debug_xml(1, xreq, "%s", __FUNCTION__);
rpcname = xml_name(xreq); rpcname = xml_name(xreq);
rpcprefix = xml_prefix(xreq); rpcprefix = xml_prefix(xreq);
framing = clicon_data_int_get(h, "netconf-framing"); framing = clicon_data_int_get(h, NETCONF_FRAMING_TYPE);
if (xml2ns(xreq, rpcprefix, &namespace) < 0) if (xml2ns(xreq, rpcprefix, &namespace) < 0)
goto done; goto done;
if (strcmp(rpcname, "rpc") == 0){ if (strcmp(rpcname, "rpc") == 0){
@ -365,11 +373,14 @@ netconf_input_packet(clicon_handle h,
ok: ok:
retval = 0; retval = 0;
done: done:
if (xret)
xml_free(xret);
if (cbret) if (cbret)
cbuf_free(cbret); cbuf_free(cbret);
return retval; return retval;
} }
#ifndef NETCONF_INPUT_UNIFIED_EXTERN
/*! Process incoming frame, ie a char message framed by ]]>]]> /*! Process incoming frame, ie a char message framed by ]]>]]>
* Parse string to xml, check only one netconf message within a frame * Parse string to xml, check only one netconf message within a frame
* @param[in] h Clixon handle * @param[in] h Clixon handle
@ -390,7 +401,7 @@ netconf_input_packet(clicon_handle h,
* - RPC messages: send rpc-error * - RPC messages: send rpc-error
*/ */
static int static int
netconf_input_frame(clicon_handle h, netconf_input_frame1(clicon_handle h,
cbuf *cb, cbuf *cb,
int *eof) int *eof)
{ {
@ -406,7 +417,7 @@ netconf_input_frame(clicon_handle h,
clicon_debug(CLIXON_DBG_DETAIL, "%s", __FUNCTION__); clicon_debug(CLIXON_DBG_DETAIL, "%s", __FUNCTION__);
clicon_debug(CLIXON_DBG_MSG, "Recv ext: %s", cbuf_get(cb)); clicon_debug(CLIXON_DBG_MSG, "Recv ext: %s", cbuf_get(cb));
framing = clicon_data_int_get(h, "netconf-framing"); framing = clicon_data_int_get(h, NETCONF_FRAMING_TYPE);
yspec = clicon_dbspec_yang(h); yspec = clicon_dbspec_yang(h);
if ((str = strdup(cbuf_get(cb))) == NULL){ if ((str = strdup(cbuf_get(cb))) == NULL){
clicon_err(OE_UNIX, errno, "strdup"); clicon_err(OE_UNIX, errno, "strdup");
@ -504,6 +515,7 @@ netconf_input_frame(clicon_handle h,
cbuf_free(cbret); cbuf_free(cbret);
return retval; return retval;
} }
#endif /* NETCONF_INPUT_UNIFIED_EXTERN */
/*! Get netconf message: detect end-of-msg /*! Get netconf message: detect end-of-msg
* @param[in] s Socket where input arrived. read from this. * @param[in] s Socket where input arrived. read from this.
@ -511,11 +523,144 @@ netconf_input_frame(clicon_handle h,
* This routine continuously reads until no more data on s. There could * This routine continuously reads until no more data on s. There could
* be risk of starvation, but the netconf client does little else than * be risk of starvation, but the netconf client does little else than
* read data so I do not see a danger of true starvation here. * read data so I do not see a danger of true starvation here.
* @note data is saved in clicon-handle at NETCONF_HASH_BUF since there is a potential issue if data * @note data is saved in clicon-handle at NETCONF_FRAME_MSG since there is a potential issue if data
* is not completely present on the s, ie if eg: * is not completely present on the s, ie if eg:
* <a>foo ..pause.. </a>]]>]]> * <a>foo ..pause.. </a>]]>]]>
* then only "</a>" would be delivered to netconf_input_frame(). * then only "</a>" would be delivered to netconf_input_frame().
*/ */
#ifdef NETCONF_INPUT_UNIFIED_EXTERN
static int
netconf_input_cb(int s,
void *arg)
{
int retval = -1;
clicon_handle h = arg;
cbuf *cbmsg=NULL;
cbuf *cberr = NULL;
void *ptr;
yang_stmt *yspec;
clicon_hash_t *cdat = clicon_data(h); /* Save cbuf between calls if not done */
size_t cdatlen = 0;
int frame_state;
size_t frame_size;
int i32;
int eom = 0;
int eof = 0;
int framing_type;
cxobj *xtop = NULL;
cxobj *xreq;
cxobj *xerr = NULL;
int ret;
unsigned char buf[BUFSIZ]; /* from stdio.h, typically 8K */
ssize_t buflen = sizeof(buf);
unsigned char *p = buf;
ssize_t len;
size_t plen;
yspec = clicon_dbspec_yang(h);
/* Get unfinished frame */
if ((ptr = clicon_hash_value(cdat, NETCONF_FRAME_MSG, &cdatlen)) != NULL){
if (cdatlen != sizeof(cbmsg)){
clicon_err(OE_XML, errno, "size mismatch %lu %lu",
(unsigned long)cdatlen, (unsigned long)sizeof(cbmsg));
goto done;
}
cbmsg = *(cbuf**)ptr;
clicon_hash_del(cdat, NETCONF_FRAME_MSG);
}
else{
if ((cbmsg = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
}
if ((frame_state = clicon_data_int_get(h, NETCONF_FRAME_STATE)) < 0)
frame_state = 0;
if ((i32 = clicon_data_int_get(h, NETCONF_FRAME_SIZE)) < 0)
frame_size = 0;
else
frame_size = i32;
/* Read input data from socket and append to cbbuf */
if ((len = netconf_input_read2(s, buf, buflen, &eof)) < 0)
goto done;
p = buf;
plen = len;
while (!eof && plen > 0){
framing_type = clicon_data_int_get(h, NETCONF_FRAMING_TYPE); /* Can be set in frame handler */
if (netconf_input_msg2(&p, &plen,
cbmsg,
framing_type,
&frame_state,
&frame_size,
&eom) < 0)
goto done;
if (eom == 0){ /* frame not complete */
clicon_debug(CLIXON_DBG_DETAIL, "%s: frame: %lu", __FUNCTION__, cbuf_len(cbmsg));
/* Extra data to read, save data and continue on next round */
if (clicon_hash_add(cdat, NETCONF_FRAME_MSG, &cbmsg, sizeof(cbmsg)) == NULL)
goto done;
cbmsg = NULL;
break;
}
clicon_debug(CLIXON_DBG_MSG, "Recv ext: %s", cbuf_get(cbmsg));
if ((ret = netconf_input_frame2(cbmsg, Y_RPC, yspec, &xtop, &xerr)) < 0)
goto done;
cbuf_reset(cbmsg);
if (ret == 0){ /* Invalid frame, parse error, etc */
if ((cberr = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
if (clixon_xml2cbuf(cberr, xerr, 0, 0, NULL, -1, 0) < 0)
goto done;
if (xerr){
xml_free(xerr);
xerr = NULL;
}
if (netconf_output_encap(framing_type, cberr) < 0)
goto done;
if (netconf_output(1, cberr, "rpc-error") < 0)
goto done;
}
else {
if ((xreq = xml_child_i_type(xtop, 0, CX_ELMNT)) == NULL){
clicon_err(OE_XML, EFAULT, "No xml req (shouldnt happen)");
goto done;
}
if (netconf_input_packet(h, xreq, yspec, &eof) < 0){
goto done;
}
if (xtop){
xml_free(xtop);
xtop = NULL;
}
}
}
if (eof){ /* socket closed / read returns 0 */
clicon_debug(1, "%s len==0, closing", __FUNCTION__);
clixon_event_unreg_fd(s, netconf_input_cb);
close(s);
clixon_exit_set(1);
}
else {
clicon_data_int_set(h, NETCONF_FRAME_STATE, frame_state);
clicon_data_int_set(h, NETCONF_FRAME_SIZE, frame_size);
}
retval = 0;
done:
if (cbmsg)
cbuf_free(cbmsg);
if (cberr)
cbuf_free(cberr);
if (xtop)
xml_free(xtop);
if (xerr)
xml_free(xerr);
return retval;
}
#else /* NETCONF_INPUT_UNIFIED_EXTERN */
static int static int
netconf_input_cb(int s, netconf_input_cb(int s,
void *arg) void *arg)
@ -547,14 +692,14 @@ netconf_input_cb(int s,
goto done; goto done;
frame_size = (size_t)ret; frame_size = (size_t)ret;
} }
if ((ptr = clicon_hash_value(cdat, NETCONF_HASH_BUF, &cdatlen)) != NULL){ if ((ptr = clicon_hash_value(cdat, NETCONF_FRAME_MSG, &cdatlen)) != NULL){
if (cdatlen != sizeof(cb)){ if (cdatlen != sizeof(cb)){
clicon_err(OE_XML, errno, "size mismatch %lu %lu", clicon_err(OE_XML, errno, "size mismatch %lu %lu",
(unsigned long)cdatlen, (unsigned long)sizeof(cb)); (unsigned long)cdatlen, (unsigned long)sizeof(cb));
goto done; goto done;
} }
cb = *(cbuf**)ptr; cb = *(cbuf**)ptr;
clicon_hash_del(cdat, NETCONF_HASH_BUF); clicon_hash_del(cdat, NETCONF_FRAME_MSG);
} }
else{ else{
if ((cb = cbuf_new()) == NULL){ if ((cb = cbuf_new()) == NULL){
@ -582,7 +727,7 @@ netconf_input_cb(int s,
for (i=0; i<len; i++){ for (i=0; i<len; i++){
if (buf[i] == 0) if (buf[i] == 0)
continue; /* Skip NULL chars (eg from terminals) */ continue; /* Skip NULL chars (eg from terminals) */
if (clicon_data_int_get(h, "netconf-framing") == NETCONF_SSH_CHUNKED){ if (clicon_data_int_get(h, NETCONF_FRAMING_TYPE) == NETCONF_SSH_CHUNKED){
/* Track chunked framing defined in RFC6242 */ /* Track chunked framing defined in RFC6242 */
if ((ret = netconf_input_chunked_framing(buf[i], &frame_state, &frame_size)) < 0) if ((ret = netconf_input_chunked_framing(buf[i], &frame_state, &frame_size)) < 0)
goto done; goto done;
@ -594,7 +739,7 @@ netconf_input_cb(int s,
/* Somewhat complex error-handling: /* Somewhat complex error-handling:
* Ignore packet errors, UNLESS an explicit termination request (eof) * Ignore packet errors, UNLESS an explicit termination request (eof)
*/ */
if (netconf_input_frame(h, cb, &eof) < 0 && if (netconf_input_frame1(h, cb, &eof) < 0 &&
!ignore_packet_errors) !ignore_packet_errors)
goto done; goto done;
if (eof) if (eof)
@ -612,7 +757,7 @@ netconf_input_cb(int s,
/* OK, we have an xml string from a client */ /* OK, we have an xml string from a client */
/* Remove trailer */ /* Remove trailer */
*(((char*)cbuf_get(cb)) + cbuf_len(cb) - strlen("]]>]]>")) = '\0'; *(((char*)cbuf_get(cb)) + cbuf_len(cb) - strlen("]]>]]>")) = '\0';
if (netconf_input_frame(h, cb, &eof) < 0 && if (netconf_input_frame1(h, cb, &eof) < 0 &&
!ignore_packet_errors) // default is to ignore errors !ignore_packet_errors) // default is to ignore errors
goto done; goto done;
if (eof) if (eof)
@ -627,7 +772,7 @@ netconf_input_cb(int s,
if (poll == 0){ if (poll == 0){
/* No data to read, save data and continue on next round */ /* No data to read, save data and continue on next round */
if (cbuf_len(cb) != 0){ if (cbuf_len(cb) != 0){
if (clicon_hash_add(cdat, NETCONF_HASH_BUF, &cb, sizeof(cb)) == NULL) if (clicon_hash_add(cdat, NETCONF_FRAME_MSG, &cb, sizeof(cb)) == NULL)
goto done; goto done;
cb = NULL; cb = NULL;
} }
@ -643,6 +788,7 @@ netconf_input_cb(int s,
cbuf_free(cb); cbuf_free(cb);
return retval; return retval;
} }
#endif /* NETCONF_INPUT_UNIFIED_EXTERN */
/*! Send netconf hello message /*! Send netconf hello message
* @param[in] h Clixon handle * @param[in] h Clixon handle
@ -663,7 +809,7 @@ send_hello(clicon_handle h,
} }
if (netconf_hello_server(h, cb, id) < 0) if (netconf_hello_server(h, cb, id) < 0)
goto done; goto done;
framing = clicon_data_int_get(h, "netconf-framing"); framing = clicon_data_int_get(h, NETCONF_FRAMING_TYPE);
if (netconf_output_encap(framing, cb) < 0) if (netconf_output_encap(framing, cb) < 0)
goto done; goto done;
if (netconf_output(s, cb, "hello") < 0) if (netconf_output(s, cb, "hello") < 0)
@ -1021,7 +1167,6 @@ main(int argc,
if (clixon_event_reg_timeout(t, timeout_fn, NULL, "timeout") < 0) if (clixon_event_reg_timeout(t, timeout_fn, NULL, "timeout") < 0)
goto done; goto done;
} }
if (clixon_event_loop(h) < 0) if (clixon_event_loop(h) < 0)
goto done; goto done;
retval = 0; retval = 0;

View file

@ -482,7 +482,7 @@ netconf_notification_cb(int s,
if (clixon_xml2cbuf(cb, xn, 0, 0, NULL, -1, 0) < 0) if (clixon_xml2cbuf(cb, xn, 0, 0, NULL, -1, 0) < 0)
goto done; goto done;
/* Send it to listening client on stdout */ /* Send it to listening client on stdout */
if (netconf_output_encap(clicon_option_int(h, "netconf-framing"), cb) < 0){ if (netconf_output_encap(clicon_data_int_get(h, NETCONF_FRAMING_TYPE), cb) < 0){
goto done; goto done;
} }
if (netconf_output(1, cb, "notification") < 0){ if (netconf_output(1, cb, "notification") < 0){

View file

@ -201,3 +201,22 @@
* Introduced in 6.1, remove in 6.2 * Introduced in 6.1, remove in 6.2
*/ */
#define AUTOCLI_DEPRECATED_HIDE #define AUTOCLI_DEPRECATED_HIDE
/*! Unified netconf input function
* Replace external, internal and controller netconf eventually
* New file: clixon_netconf_input.c with functions:
* - netconf_input_read2
* - netconf_input_msg2
* - netconf_input_frame2
* The following code should use this:
* X - netconf_main.c external netconf
* - netconf_proto.c internal netconf
* - controller device control
*/
/*! Use unified netconf input function for external use
*/
#define NETCONF_INPUT_UNIFIED_EXTERN
/*! Use unified netconf input function for internal use
*/
#undef NETCONF_INPUT_UNIFIED_INTERNAL

View file

@ -89,6 +89,7 @@ extern "C" {
#include <clixon/clixon_stream.h> #include <clixon/clixon_stream.h>
#include <clixon/clixon_proto.h> #include <clixon/clixon_proto.h>
#include <clixon/clixon_netconf_lib.h> #include <clixon/clixon_netconf_lib.h>
#include <clixon/clixon_netconf_input.h>
#include <clixon/clixon_proto_client.h> #include <clixon/clixon_proto_client.h>
#include <clixon/clixon_plugin.h> #include <clixon/clixon_plugin.h>
#include <clixon/clixon_options.h> #include <clixon/clixon_options.h>

View file

@ -0,0 +1,61 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate)
This file is part of CLIXON.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the "GPL"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2, indicate
your decision by deleting the provisions above and replace them with the
notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
* Netconf input routines
*/
#ifndef _CLIXON_NETCONF_INPUT_H
#define _CLIXON_NETCONF_INPUT_H
/* What kind of framing? NETCONF_SSH_EOM or NETCONF_SSH_CHUNKED
* Global setting used with clicon_data_int_get()
*/
#define NETCONF_FRAMING_TYPE "netconf-framing-type"
/*
* Prototypes
*/
#ifdef __cplusplus
extern "C" {
#endif
ssize_t netconf_input_read2(int s, unsigned char *buf, ssize_t buflen, int *eof);
int netconf_input_msg2(unsigned char **bufp, size_t *lenp, cbuf *cbmsg,
netconf_framing_type framing, int *frame_state, size_t *frame_size,
int *eom);
int netconf_input_frame2(cbuf *cb, yang_bind yb, yang_stmt *yspec, cxobj **xrecv, cxobj **xerr);
#ifdef __cplusplus
}
#endif
#endif /* _CLIXON_NETCONF_INPUT_H */

View file

@ -92,7 +92,8 @@ SRC = clixon_sig.c clixon_uid.c clixon_log.c clixon_err.c clixon_event.c \
clixon_xpath.c clixon_xpath_ctx.c clixon_xpath_eval.c clixon_xpath_function.c \ clixon_xpath.c clixon_xpath_ctx.c clixon_xpath_eval.c clixon_xpath_function.c \
clixon_xpath_optimize.c clixon_xpath_yang.c \ clixon_xpath_optimize.c clixon_xpath_yang.c \
clixon_datastore.c clixon_datastore_write.c clixon_datastore_read.c \ clixon_datastore.c clixon_datastore_write.c clixon_datastore_read.c \
clixon_netconf_lib.c clixon_stream.c clixon_nacm.c clixon_client.c clixon_netns.c \ clixon_netconf_lib.c clixon_netconf_input.c clixon_stream.c \
clixon_nacm.c clixon_client.c clixon_netns.c \
clixon_dispatcher.c clixon_text_syntax.c clixon_dispatcher.c clixon_text_syntax.c
YACCOBJS = lex.clixon_xml_parse.o clixon_xml_parse.tab.o \ YACCOBJS = lex.clixon_xml_parse.o clixon_xml_parse.tab.o \

View file

@ -0,0 +1,270 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2023 Olof Hagsand
This file is part of CLIXON.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
***** END LICENSE BLOCK *****
* Unified netconf input routines
*
* Usage:
if ((len = netconf_input_read2(s, buf, buflen, &eof)) < 0)
goto done;
p = buf; plen = len;
while (!eof){
if (netconf_input_msg2(&p, &plen, cbmsg, framing_type, &frame_state, &frame_size, &eom) < 0)
goto done;
if (!eom)
break;
if ((ret = netconf_input_frame2(cbmsg, Y_RPC, yspec, &cbret, &xtop)) < 0)
goto done;
// process incoming packet xtop
}
if (eom == 0)
// frame not complete
if ((ret = netconf_input_frame(cb, yspec, &xtop)) < 0)
goto done;
if (ret == 0)
// invalid
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <syslog.h>
#include <fcntl.h>
#include <fnmatch.h>
#include <sys/stat.h>
#include <sys/time.h>
/* clicon */
#include <cligen/cligen.h>
/* Local includes */
#include "clixon_queue.h"
#include "clixon_hash.h"
#include "clixon_string.h"
#include "clixon_err.h"
#include "clixon_handle.h"
#include "clixon_yang.h"
#include "clixon_log.h"
#include "clixon_xml.h"
#include "clixon_xml_io.h"
#include "clixon_proto.h"
#include "clixon_netconf_lib.h"
#include "clixon_netconf_input.h"
/*! Read from socket and append to cbuf
*
* @param[in] s Socket where input arrives. Read from this.
* @param[in] buf Packet buffer
* @param[in] buflen Length of packet buffer
* @param[out] eof Socket closed / eof?
* @retval 0 OK
* @retval -1 Error
*/
ssize_t
netconf_input_read2(int s,
unsigned char *buf,
ssize_t buflen,
int *eof)
{
int retval = -1;
ssize_t len;
memset(buf, 0, buflen);
if ((len = read(s, buf, buflen)) < 0){
if (errno == ECONNRESET)
len = 0; /* emulate EOF */
else{
clicon_log(LOG_ERR, "%s: read: %s", __FUNCTION__, strerror(errno));
goto done;
}
} /* read */
clicon_debug(CLIXON_DBG_DETAIL, "%s len:%ld", __FUNCTION__, len);
if (len == 0){ /* EOF */
clicon_debug(CLIXON_DBG_DETAIL, "%s len==0, closing", __FUNCTION__);
*eof = 1;
}
retval = len;
done:
clicon_debug(CLIXON_DBG_DETAIL, "%s retval:%d", __FUNCTION__, retval);
return retval;
}
/*! Get netconf message using NETCONF framing
*
* @param[in,out] bufp Input data, incremented as read
* @param[in,out] lenp Data len, decremented as read
* @param[in,out] cbmsg Completed frame (if eom), may contain data on entry
* @param[in] framing_type EOM or chunked framing
* @param[in,out] frame_state Framing state depending on type
* @param[in,out] frame_size Chunked framing size parameter
* @param[out] eom If frame found in cb?
* @retval 0 OK
* @retval -1 Error
* The routine should be called continuously with more data from input socket in buf
* State of previous reads is saved in:
* - bufp/lenp
* - cbmsg
* - frame_state/frame_size
*/
int
netconf_input_msg2(unsigned char **bufp,
size_t *lenp,
cbuf *cbmsg,
netconf_framing_type framing_type,
int *frame_state,
size_t *frame_size,
int *eom)
{
int retval = -1;
int i;
int ret;
int found = 0;
size_t len;
char ch;
clicon_debug(CLIXON_DBG_DETAIL, "%s", __FUNCTION__);
len = *lenp;
for (i=0; i<len; i++){
if ((ch = (*bufp)[i]) == 0)
continue; /* Skip NULL chars (eg from terminals) */
if (framing_type == NETCONF_SSH_CHUNKED){
/* Track chunked framing defined in RFC6242 */
if ((ret = netconf_input_chunked_framing(ch, frame_state, frame_size)) < 0)
goto done;
switch (ret){
case 1: /* chunk-data */
cprintf(cbmsg, "%c", ch);
break;
case 2: /* end-of-data */
/* Somewhat complex error-handling:
* Ignore packet errors, UNLESS an explicit termination request (eof)
*/
found++;
break;
default:
break;
}
}
else{
cprintf(cbmsg, "%c", ch);
if (detect_endtag("]]>]]>", ch, frame_state)){
*frame_state = 0;
/* OK, we have an xml string from a client */
/* Remove trailer */
*(((char*)cbuf_get(cbmsg)) + cbuf_len(cbmsg) - strlen("]]>]]>")) = '\0';
found++;
}
}
if (found){
i++;
break;
}
} /* for */
*bufp += i;
*lenp -= i;
*eom = found;
retval = 0;
done:
clicon_debug(CLIXON_DBG_DETAIL, "%s retval:%d", __FUNCTION__, retval);
return retval;
}
/*! Process incoming frame, ie a char message framed by ]]>]]>
*
* Parse string to xml, check only one netconf message within a frame
* @param[in] cb Packet buffer
* @param[in] yb Yang binding: Y_RPC for server-side, Y_NONE for client-side (for now)
* @param[in] yspec Yang spec
* @param[out] xrecv XML packet
* @param[out] xerr XML error, (if ret = 0)
* @retval 1 OK
* @retval 0 Invalid, parse error, etc, xerr points to netconf error message
* @retval -1 Fatal error
*/
int
netconf_input_frame2(cbuf *cb,
yang_bind yb,
yang_stmt *yspec,
cxobj **xrecv,
cxobj **xerr)
{
int retval = -1;
char *str = NULL;
cxobj *xtop = NULL; /* Request (in) */
int ret;
clicon_debug(CLIXON_DBG_DETAIL, "%s", __FUNCTION__);
if (xrecv == NULL){
clicon_err(OE_PLUGIN, EINVAL, "xrecv is NULL");
goto done;
}
str = cbuf_get(cb);
/* Special case: empty XML */
if (strlen(str) == 0){
if (netconf_operation_failed_xml(xerr, "rpc", "Empty XML")< 0)
goto done;
goto failed;
}
/* Fix to distinguish RPC and REPLIES */
if ((ret = clixon_xml_parse_string(str, yb, yspec, &xtop, xerr)) < 0){
/* XXX possibly should quit on -1? */
if (netconf_operation_failed_xml(xerr, "rpc", clicon_err_reason)< 0)
goto done;
goto failed;
}
if (ret == 0){
/* Note: xtop can be "hello" in which case one (maybe) should drop the session and log
* However, its not until netconf_input_packet that rpc vs hello vs other identification is
* actually made.
* Actually, there are no error replies to hello messages according to any RFC, so
* rpc error reply here is non-standard, but may be useful.
*/
goto failed;
}
/* Check for empty frame (no messages), return empty message, not clear from RFC what to do */
if (xml_child_nr_type(xtop, CX_ELMNT) == 0){
if (netconf_operation_failed_xml(xerr, "rpc", "Truncated XML")< 0)
goto done;
goto failed;
}
if (xml_child_nr_type(xtop, CX_ELMNT) != 1){
if (netconf_malformed_message_xml(xerr, "More than one message in netconf rpc frame")< 0)
goto done;
goto failed;
}
*xrecv = xtop;
xtop = NULL;
retval = 1;
done:
if (xtop)
xml_free(xtop);
return retval;
failed:
retval = 0;
goto done;
}

View file

@ -74,8 +74,8 @@
#include "clixon_yang_module.h" #include "clixon_yang_module.h"
#include "clixon_yang_parse_lib.h" #include "clixon_yang_parse_lib.h"
#include "clixon_plugin.h" #include "clixon_plugin.h"
#include "clixon_netconf_lib.h" #include "clixon_netconf_lib.h"
#include "clixon_netconf_input.h"
/* Mapping between RFC6243 withdefaults strings <--> ints /* Mapping between RFC6243 withdefaults strings <--> ints
*/ */
@ -465,6 +465,7 @@ netconf_unknown_attribute(cbuf *cb,
} }
/*! Common Netconf element XML tree according to RFC 6241 App A /*! Common Netconf element XML tree according to RFC 6241 App A
*
* @param[out] xret Error XML tree. Free with xml_free after use * @param[out] xret Error XML tree. Free with xml_free after use
* @param[in] type Error type: "application" or "protocol" * @param[in] type Error type: "application" or "protocol"
* @param[in] tag Error tag * @param[in] tag Error tag
@ -495,7 +496,6 @@ netconf_common_xml(cxobj **xret,
} }
else if (xml_name_set(*xret, "rpc-reply") < 0) else if (xml_name_set(*xret, "rpc-reply") < 0)
goto done; goto done;
if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL) if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL)
goto done; goto done;
if (clixon_xml_parse_va(YB_NONE, NULL, &xerr, NULL, "<error-type>%s</error-type>" if (clixon_xml_parse_va(YB_NONE, NULL, &xerr, NULL, "<error-type>%s</error-type>"
@ -1631,10 +1631,10 @@ netconf_module_load(clicon_handle h)
* But start with default: RFC 4741 EOM ]]>]]> * But start with default: RFC 4741 EOM ]]>]]>
* For now this only applies to external protocol * For now this only applies to external protocol
*/ */
clicon_data_int_set(h, "netconf-framing", NETCONF_SSH_EOM); clicon_data_int_set(h, NETCONF_FRAMING_TYPE, NETCONF_SSH_EOM);
if (clicon_option_bool(h, "CLICON_NETCONF_HELLO_OPTIONAL")){ if (clicon_option_bool(h, "CLICON_NETCONF_HELLO_OPTIONAL")){
if (clicon_option_int(h, "CLICON_NETCONF_BASE_CAPABILITY") > 0) /* RFC 6241 */ if (clicon_option_int(h, "CLICON_NETCONF_BASE_CAPABILITY") > 0) /* RFC 6241 */
clicon_data_int_set(h, "netconf-framing", NETCONF_SSH_CHUNKED); clicon_data_int_set(h, NETCONF_FRAMING_TYPE, NETCONF_SSH_CHUNKED);
} }
retval = 0; retval = 0;
done: done:
@ -1870,7 +1870,7 @@ netconf_hello_server(clicon_handle h,
return retval; return retval;
} }
/*! Generate textual error log from Netconf error message /*! Generate and print textual error log from Netconf error message
* *
* Get a text error message from netconf error message and generate error on the form: * Get a text error message from netconf error message and generate error on the form:
* <msg>: "<arg>": <netconf-error> or <msg>: <netconf-error> * <msg>: "<arg>": <netconf-error> or <msg>: <netconf-error>

View file

@ -494,7 +494,7 @@ function chunked_equal()
fi fi
} }
# Given a string, add RFC6242 chunked franing around it # Given a string, add RFC6242 chunked framing around it
# Args: # Args:
# 0: string # 0: string
function chunked_framing() function chunked_framing()
@ -818,6 +818,7 @@ function expecteof(){
retval=$2 retval=$2
input=$3 input=$3
expect=$4 expect=$4
if [ $# -gt 4 ]; then if [ $# -gt 4 ]; then
errfile=$(mktemp) errfile=$(mktemp)
expecterr=$5 expecterr=$5

View file

@ -174,7 +174,7 @@ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "<rpc $DEFAULTONLY><ge
#<capability>urn:ietf:params:netconf:base:1.1</capability> #<capability>urn:ietf:params:netconf:base:1.1</capability>
new "netconf rcv hello, disable RFC7895/ietf-yang-library" new "netconf rcv hello, disable RFC7895/ietf-yang-library"
expecteof_netconf "$clixon_netconf -f $cfg -o CLICON_YANG_LIBRARY=0" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><get-config><source><candidate/></source></get-config></rpc>" "" "<rpc-reply $DEFAULTNS><data/></rpc-reply>" expecteof_netconf "$clixon_netconf -qD 7 -lf/tmp/netconf0.log -f $cfg -o CLICON_YANG_LIBRARY=0" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><get-config><source><candidate/></source></get-config></rpc>" "" "<rpc-reply $DEFAULTNS><data/></rpc-reply>"
new "netconf get-config nc prefix" new "netconf get-config nc prefix"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<nc:rpc xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\" nc:message-id=\"42\"><nc:get-config><nc:source><nc:candidate/></nc:source></nc:get-config></nc:rpc>" "" "<rpc-reply xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\" nc:message-id=\"42\"><data/></rpc-reply>" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<nc:rpc xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\" nc:message-id=\"42\"><nc:get-config><nc:source><nc:candidate/></nc:source></nc:get-config></nc:rpc>" "" "<rpc-reply xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\" nc:message-id=\"42\"><data/></rpc-reply>"

View file

@ -118,14 +118,14 @@ expecteof "$clixon_netconf -f $cfg" 0 "<?xml version=\"1.0\" encoding=\"UTF-8\"?
# Actually non-standard to reply on wrong hello with rpc-error, but may be useful # Actually non-standard to reply on wrong hello with rpc-error, but may be useful
new "Netconf snd hello with extra element" new "Netconf snd hello with extra element"
expecteof "$clixon_netconf -qef $cfg" 0 "<hello $DEFAULTONLY><capabilities><extra-element/><capability>urn:ietf:params:netconf:base:1.1</capability></capabilities></hello>]]>]]>" '^<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><rpc-error><error-type>protocol</error-type><error-tag>unknown-element</error-tag><error-info><bad-element>extra-element</bad-element></error-info><error-severity>error</error-severity><error-message>Unrecognized hello/capabilities element</error-message></rpc-error></rpc-reply>]]>]]>$' '^$' expecteof "$clixon_netconf -qf $cfg" 0 "<hello $DEFAULTONLY><capabilities><extra-element/><capability>urn:ietf:params:netconf:base:1.1</capability></capabilities></hello>]]>]]>" '^<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><rpc-error><error-type>protocol</error-type><error-tag>unknown-element</error-tag><error-info><bad-element>extra-element</bad-element></error-info><error-severity>error</error-severity><error-message>Unrecognized hello/capabilities element</error-message></rpc-error></rpc-reply>]]>]]>$' '^$'
new "Netconf send rpc without hello error" new "Netconf send rpc without hello error"
expecteof "$clixon_netconf -qef $cfg" 255 "<rpc $DEFAULTNS><get-config><source><candidate/></source></get-config></rpc>]]>]]>" "<rpc-reply $DEFAULTNS><rpc-error><error-type>rpc</error-type><error-tag>operation-failed</error-tag><error-severity>error</error-severity><error-message>Client must send an hello element before any RPC</error-message></rpc-error></rpc-reply>]]>]]>" '^$' expecteof "$clixon_netconf -qf $cfg" 0 "<rpc $DEFAULTNS><get-config><source><candidate/></source></get-config></rpc>]]>]]>" "<rpc-reply $DEFAULTNS><rpc-error><error-type>rpc</error-type><error-tag>operation-failed</error-tag><error-severity>error</error-severity><error-message>Client must send an hello element before any RPC</error-message></rpc-error></rpc-reply>]]>]]>" '^$'
# same as -H # same as -H
new "Netconf send rpc without hello w CLICON_NETCONF_HELLO_OPTIONAL" new "Netconf send rpc without hello w CLICON_NETCONF_HELLO_OPTIONAL"
expecteof_netconf "$clixon_netconf -qef $cfg -o CLICON_NETCONF_HELLO_OPTIONAL=true" 0 "" "<rpc $DEFAULTNS><get-config><source><candidate/></source></get-config></rpc>" "" "<rpc-reply $DEFAULTNS><data/></rpc-reply>" expecteof_netconf "$clixon_netconf -qf $cfg -o CLICON_NETCONF_HELLO_OPTIONAL=true" 0 "" "<rpc $DEFAULTNS><get-config><source><candidate/></source></get-config></rpc>" "" "<rpc-reply $DEFAULTNS><data/></rpc-reply>"
if [ $BE -ne 0 ]; then if [ $BE -ne 0 ]; then
new "Kill backend" new "Kill backend"