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
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue