Unified netconf input function
First for external use, later internal
This commit is contained in:
parent
04d5f52d90
commit
e7c9f3d075
12 changed files with 534 additions and 32 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
* <foo/> ..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:
|
||||
* <a>foo ..pause.. </a>]]>]]>
|
||||
* 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
|
||||
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<len; i++){
|
||||
if (buf[i] == 0)
|
||||
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 */
|
||||
if ((ret = netconf_input_chunked_framing(buf[i], &frame_state, &frame_size)) < 0)
|
||||
goto done;
|
||||
|
|
@ -594,7 +739,7 @@ netconf_input_cb(int s,
|
|||
/* Somewhat complex error-handling:
|
||||
* 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)
|
||||
goto done;
|
||||
if (eof)
|
||||
|
|
@ -612,7 +757,7 @@ netconf_input_cb(int s,
|
|||
/* OK, we have an xml string from a client */
|
||||
/* Remove trailer */
|
||||
*(((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
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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){
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ extern "C" {
|
|||
#include <clixon/clixon_stream.h>
|
||||
#include <clixon/clixon_proto.h>
|
||||
#include <clixon/clixon_netconf_lib.h>
|
||||
#include <clixon/clixon_netconf_input.h>
|
||||
#include <clixon/clixon_proto_client.h>
|
||||
#include <clixon/clixon_plugin.h>
|
||||
#include <clixon/clixon_options.h>
|
||||
|
|
|
|||
61
lib/clixon/clixon_netconf_input.h
Normal file
61
lib/clixon/clixon_netconf_input.h
Normal 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 */
|
||||
|
|
@ -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 \
|
||||
|
|
|
|||
270
lib/src/clixon_netconf_input.c
Normal file
270
lib/src/clixon_netconf_input.c
Normal 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;
|
||||
}
|
||||
|
||||
|
|
@ -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, "<error-type>%s</error-type>"
|
||||
|
|
@ -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:
|
||||
* <msg>: "<arg>": <netconf-error> or <msg>: <netconf-error>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "<rpc $DEFAULTONLY><ge
|
|||
|
||||
#<capability>urn:ietf:params:netconf:base:1.1</capability>
|
||||
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"
|
||||
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>"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
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"
|
||||
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
|
||||
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
|
||||
new "Kill backend"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue