From e7c9f3d0754a80e83673d0529f0528117b0c0d06 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 6 Mar 2023 10:48:46 +0100 Subject: [PATCH] Unified netconf input function First for external use, later internal --- CHANGELOG.md | 4 + apps/netconf/netconf_main.c | 185 +++++++++++++++++--- apps/netconf/netconf_rpc.c | 2 +- include/clixon_custom.h | 19 +++ lib/clixon/clixon.h.in | 1 + lib/clixon/clixon_netconf_input.h | 61 +++++++ lib/src/Makefile.in | 3 +- lib/src/clixon_netconf_input.c | 270 ++++++++++++++++++++++++++++++ lib/src/clixon_netconf_lib.c | 10 +- test/lib.sh | 3 +- test/test_netconf.sh | 2 +- test/test_netconf_hello.sh | 6 +- 12 files changed, 534 insertions(+), 32 deletions(-) create mode 100644 lib/clixon/clixon_netconf_input.h create mode 100644 lib/src/clixon_netconf_input.c diff --git a/CHANGELOG.md b/CHANGELOG.md index ec4577b1..848e0937 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,10 @@ Developers may need to change their code ### 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 * Made cli/autocli mount-point-aware * Internal NETCONF (client <-> backend) diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index ed45fe8a..894c6dc8 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -77,10 +77,13 @@ /* clixon-data value to save buffer between invocations. * Saving data may be necessary if socket buffer contains partial netconf messages, such as: * ..wait 1min ]]>]]> + * XXX move to data */ -#define NETCONF_HASH_BUF "netconf_input_cbuf" -#define NETCONF_FRAME_STATE "netconf_input_frame_state" -#define NETCONF_FRAME_SIZE "netconf_input_frame_size" +/* Unfinished frame */ +#define NETCONF_FRAME_MSG "netconf-frame-msg" + +#define NETCONF_FRAME_STATE "netconf-input-frame-state" +#define NETCONF_FRAME_SIZE "netconf-input-frame-size" /*! Ignore errors on packet errors: continue */ static int ignore_packet_errors = 1; @@ -149,6 +152,7 @@ netconf_hello_msg(clicon_handle h, int foundbase_11 = 0; char *body; + clicon_debug(1, "%s", __FUNCTION__); _netconf_hello_nr++; 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"); @@ -166,12 +170,15 @@ netconf_hello_msg(clicon_handle h, continue; /* 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. */ - 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++; + clicon_debug(1, "%s foundbase10", __FUNCTION__); + } 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 */ 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; 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 && 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) @@ -319,9 +326,10 @@ netconf_input_packet(clicon_handle h, netconf_framing_type framing; clicon_debug(1, "%s", __FUNCTION__); + clicon_debug_xml(1, xreq, "%s", __FUNCTION__); rpcname = xml_name(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) goto done; if (strcmp(rpcname, "rpc") == 0){ @@ -365,11 +373,14 @@ netconf_input_packet(clicon_handle h, ok: retval = 0; done: + if (xret) + xml_free(xret); if (cbret) cbuf_free(cbret); return retval; } +#ifndef NETCONF_INPUT_UNIFIED_EXTERN /*! Process incoming frame, ie a char message framed by ]]>]]> * Parse string to xml, check only one netconf message within a frame * @param[in] h Clixon handle @@ -390,9 +401,9 @@ netconf_input_packet(clicon_handle h, * - RPC messages: send rpc-error */ static int -netconf_input_frame(clicon_handle h, - cbuf *cb, - int *eof) +netconf_input_frame1(clicon_handle h, + cbuf *cb, + int *eof) { int retval = -1; char *str = NULL; @@ -406,7 +417,7 @@ netconf_input_frame(clicon_handle h, clicon_debug(CLIXON_DBG_DETAIL, "%s", __FUNCTION__); 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); if ((str = strdup(cbuf_get(cb))) == NULL){ clicon_err(OE_UNIX, errno, "strdup"); @@ -504,6 +515,7 @@ netconf_input_frame(clicon_handle h, cbuf_free(cbret); return retval; } +#endif /* NETCONF_INPUT_UNIFIED_EXTERN */ /*! Get netconf message: detect end-of-msg * @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 * 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. - * @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: * foo ..pause.. ]]>]]> * then only "" 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 netconf_input_cb(int s, void *arg) @@ -547,14 +692,14 @@ netconf_input_cb(int s, goto done; 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)){ clicon_err(OE_XML, errno, "size mismatch %lu %lu", (unsigned long)cdatlen, (unsigned long)sizeof(cb)); goto done; } cb = *(cbuf**)ptr; - clicon_hash_del(cdat, NETCONF_HASH_BUF); + clicon_hash_del(cdat, NETCONF_FRAME_MSG); } else{ if ((cb = cbuf_new()) == NULL){ @@ -582,7 +727,7 @@ netconf_input_cb(int s, for (i=0; i]]>")) = '\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 goto done; if (eof) @@ -627,7 +772,7 @@ netconf_input_cb(int s, if (poll == 0){ /* No data to read, save data and continue on next round */ 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; cb = NULL; } @@ -643,6 +788,7 @@ netconf_input_cb(int s, cbuf_free(cb); return retval; } +#endif /* NETCONF_INPUT_UNIFIED_EXTERN */ /*! Send netconf hello message * @param[in] h Clixon handle @@ -663,7 +809,7 @@ send_hello(clicon_handle h, } if (netconf_hello_server(h, cb, id) < 0) 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) goto done; 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) goto done; } - if (clixon_event_loop(h) < 0) goto done; retval = 0; diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index 1d03c23b..2ded279e 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -482,7 +482,7 @@ netconf_notification_cb(int s, if (clixon_xml2cbuf(cb, xn, 0, 0, NULL, -1, 0) < 0) goto done; /* 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; } if (netconf_output(1, cb, "notification") < 0){ diff --git a/include/clixon_custom.h b/include/clixon_custom.h index b51b3216..0b7f1617 100644 --- a/include/clixon_custom.h +++ b/include/clixon_custom.h @@ -201,3 +201,22 @@ * Introduced in 6.1, remove in 6.2 */ #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 diff --git a/lib/clixon/clixon.h.in b/lib/clixon/clixon.h.in index cbb8c7f7..ab9df9d4 100644 --- a/lib/clixon/clixon.h.in +++ b/lib/clixon/clixon.h.in @@ -89,6 +89,7 @@ extern "C" { #include #include #include +#include #include #include #include diff --git a/lib/clixon/clixon_netconf_input.h b/lib/clixon/clixon_netconf_input.h new file mode 100644 index 00000000..518b12e1 --- /dev/null +++ b/lib/clixon/clixon_netconf_input.h @@ -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 */ diff --git a/lib/src/Makefile.in b/lib/src/Makefile.in index edbbffaf..f1dc2962 100644 --- a/lib/src/Makefile.in +++ b/lib/src/Makefile.in @@ -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_optimize.c clixon_xpath_yang.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 YACCOBJS = lex.clixon_xml_parse.o clixon_xml_parse.tab.o \ diff --git a/lib/src/clixon_netconf_input.c b/lib/src/clixon_netconf_input.c new file mode 100644 index 00000000..b7fa233e --- /dev/null +++ b/lib/src/clixon_netconf_input.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* clicon */ +#include + +/* 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]]>", 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; +} + diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c index 3b9e00f2..54a816e9 100644 --- a/lib/src/clixon_netconf_lib.c +++ b/lib/src/clixon_netconf_lib.c @@ -74,8 +74,8 @@ #include "clixon_yang_module.h" #include "clixon_yang_parse_lib.h" #include "clixon_plugin.h" - #include "clixon_netconf_lib.h" +#include "clixon_netconf_input.h" /* 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 + * * @param[out] xret Error XML tree. Free with xml_free after use * @param[in] type Error type: "application" or "protocol" * @param[in] tag Error tag @@ -495,7 +496,6 @@ netconf_common_xml(cxobj **xret, } else if (xml_name_set(*xret, "rpc-reply") < 0) goto done; - if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL) goto done; if (clixon_xml_parse_va(YB_NONE, NULL, &xerr, NULL, "%s" @@ -1631,10 +1631,10 @@ netconf_module_load(clicon_handle h) * But start with default: RFC 4741 EOM ]]>]]> * 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_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; done: @@ -1870,7 +1870,7 @@ netconf_hello_server(clicon_handle h, 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: * : "": or : diff --git a/test/lib.sh b/test/lib.sh index 1538793a..12f56ca6 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -494,7 +494,7 @@ function chunked_equal() fi } -# Given a string, add RFC6242 chunked franing around it +# Given a string, add RFC6242 chunked framing around it # Args: # 0: string function chunked_framing() @@ -818,6 +818,7 @@ function expecteof(){ retval=$2 input=$3 expect=$4 + if [ $# -gt 4 ]; then errfile=$(mktemp) expecterr=$5 diff --git a/test/test_netconf.sh b/test/test_netconf.sh index f8b7f485..99df8829 100755 --- a/test/test_netconf.sh +++ b/test/test_netconf.sh @@ -174,7 +174,7 @@ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "urn:ietf:params:netconf:base:1.1 new "netconf rcv hello, disable RFC7895/ietf-yang-library" -expecteof_netconf "$clixon_netconf -f $cfg -o CLICON_YANG_LIBRARY=0" 0 "$DEFAULTHELLO" "" "" "" +expecteof_netconf "$clixon_netconf -qD 7 -lf/tmp/netconf0.log -f $cfg -o CLICON_YANG_LIBRARY=0" 0 "$DEFAULTHELLO" "" "" "" new "netconf get-config nc prefix" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" diff --git a/test/test_netconf_hello.sh b/test/test_netconf_hello.sh index 545eba4b..b055aad7 100755 --- a/test/test_netconf_hello.sh +++ b/test/test_netconf_hello.sh @@ -118,14 +118,14 @@ expecteof "$clixon_netconf -f $cfg" 0 "urn:ietf:params:netconf:base:1.1]]>]]>" '^protocolunknown-elementextra-elementerrorUnrecognized hello/capabilities element]]>]]>$' '^$' +expecteof "$clixon_netconf -qf $cfg" 0 "urn:ietf:params:netconf:base:1.1]]>]]>" '^protocolunknown-elementextra-elementerrorUnrecognized hello/capabilities element]]>]]>$' '^$' new "Netconf send rpc without hello error" -expecteof "$clixon_netconf -qef $cfg" 255 "]]>]]>" "rpcoperation-failederrorClient must send an hello element before any RPC]]>]]>" '^$' +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "rpcoperation-failederrorClient must send an hello element before any RPC]]>]]>" '^$' # same as -H new "Netconf send rpc without hello w CLICON_NETCONF_HELLO_OPTIONAL" -expecteof_netconf "$clixon_netconf -qef $cfg -o CLICON_NETCONF_HELLO_OPTIONAL=true" 0 "" "" "" "" +expecteof_netconf "$clixon_netconf -qf $cfg -o CLICON_NETCONF_HELLO_OPTIONAL=true" 0 "" "" "" "" if [ $BE -ne 0 ]; then new "Kill backend"