Netconf monitoring RFC 6022 Sessions (https://github.com/clicon/clixon/issues/370)

- statistics and transport/source-host parameters
  - extended internal NETCONF hello with transport and source-host attributes
clixon-lib,yang
  - Moved all extended internal NETCONF attributes to the clicon-lib namespace
C-API:
  - wrapped most attribute creation into new fn xml_add_attr()
This commit is contained in:
Olof hagsand 2023-01-14 11:25:39 +01:00
parent 7558d40faa
commit 3916fa919c
36 changed files with 883 additions and 402 deletions

View file

@ -78,7 +78,7 @@
*/
static struct client_entry *
ce_find_byid(struct client_entry *ce_list,
uint32_t id)
uint32_t id)
{
struct client_entry *ce;
@ -154,6 +154,81 @@ release_all_dbs(clicon_handle h,
return retval;
}
/*! Get backend-specific client netconf monitoring state
*
* Backend-specific netconf monitoring state is:
* sessions
* @param[in] h Clicon handle
* @param[in] yspec Yang spec
* @param[in] xpath XML Xpath
* @param[in] nsc XML Namespace context for xpath
* @param[in,out] xret Existing XML tree, merge x into this
* @param[out] xerr XML error tree, if retval = 0
* @retval -1 Error (fatal)
* @retval 0 Statedata callback failed, error in xerr
* @retval 1 OK
* @see RFC 6022
*/
int
backend_monitoring_state_get(clicon_handle h,
yang_stmt *yspec,
char *xpath,
cvec *nsc,
cxobj **xret,
cxobj **xerr)
{
int retval = -1;
cbuf *cb = NULL;
struct client_entry *ce;
char timestr[28];
int ret;
if ((cb = cbuf_new()) ==NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
cprintf(cb, "<netconf-state xmlns=\"%s\">", NETCONF_MONITORING_NAMESPACE);
cprintf(cb, "<sessions>");
for (ce = backend_client_list(h); ce; ce = ce->ce_next){
cprintf(cb, "<session>");
cprintf(cb, "<session-id>%u</session-id>", ce->ce_id);
if (ce->ce_transport)
cprintf(cb, "<transport xmlns:%s=\"%s\">%s</transport>",
CLIXON_LIB_PREFIX, CLIXON_LIB_NS,
ce->ce_transport);
cprintf(cb, "<username>%s</username>", ce->ce_username);
if (ce->ce_source_host)
cprintf(cb, "<source-host>%s</source-host>", ce->ce_source_host);
if (ce->ce_time.tv_sec != 0){
if (time2str(ce->ce_time, timestr, sizeof(timestr)) < 0){
clicon_err(OE_UNIX, errno, "time2str");
goto done;
}
cprintf(cb, "<login-time>%s</login-time>", timestr);
}
cprintf(cb, "<in-rpcs>%u</in-rpcs>", ce->ce_in_rpcs);
cprintf(cb, "<in-bad-rpcs>%u</in-bad-rpcs>", ce->ce_in_bad_rpcs);
cprintf(cb, "<out-rpc-errors>%u</out-rpc-errors>", ce->ce_out_rpc_errors);
cprintf(cb, "<out-notifications>%u</out-notifications>", 0);
cprintf(cb, "</session>");
}
cprintf(cb, "</sessions>");
cprintf(cb, "</netconf-state>");
if ((ret = clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, xret, xerr)) < 0)
goto done;
if (ret == 0)
goto fail;
retval = 1;
done:
clicon_debug(1, "%s %d", __FUNCTION__, retval);
if (cb)
cbuf_free(cb);
return retval;
fail:
retval = 0;
goto done;
}
/*! Remove client entry state
* Close down everything wrt clients (eg sockets, subscriptions)
* Finally actually remove client struct in handle
@ -451,7 +526,7 @@ from_client_edit_config(clicon_handle h,
xmldb_modified_set(h, target, 1); /* mark as dirty */
/* Clixon extension: autocommit */
if ((attr = xml_find_value(xn, "autocommit")) != NULL &&
strcmp(attr,"true")==0)
strcmp(attr,"true") == 0)
autocommit = 1;
/* If autocommit option is set or requested by client */
if (clicon_autocommit(h) || autocommit) {
@ -513,7 +588,9 @@ from_client_edit_config(clicon_handle h,
}
cprintf(cbret, "<rpc-reply xmlns=\"%s\"><ok", NETCONF_BASE_NAMESPACE);
if (clicon_data_get(h, "objectexisted", &val) == 0)
cprintf(cbret, " objectexisted=\"%s\"", val);
cprintf(cbret, " %s:objectexisted=\"%s\" xmlns:%s=\"%s\"",
CLIXON_LIB_PREFIX, val,
CLIXON_LIB_PREFIX, CLIXON_LIB_NS);
cprintf(cbret, "/></rpc-reply>");
ok:
retval = 0;
@ -1375,6 +1452,11 @@ from_client_process_control(clicon_handle h,
}
/*! Clixon hello to check liveness
*
* @param[in] h Clixon handle
* @param[in] x Incoming XML of hello request
* @param[in] ce Client entry (from)
* @param[out] cbret Hello reply
* @retval 0 OK
* @retval -1 Error
*/
@ -1386,11 +1468,24 @@ from_client_hello(clicon_handle h,
{
int retval = -1;
uint32_t id;
char *val;
if (clicon_session_id_get(h, &id) < 0){
clicon_err(OE_NETCONF, ENOENT, "session_id not set");
goto done;
}
if ((val = xml_find_type_value(x, "cl", "transport", CX_ATTR)) != NULL){
if ((ce->ce_transport = strdup(val)) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
}
if ((val = xml_find_type_value(x, "cl", "source-host", CX_ATTR)) != NULL){
if ((ce->ce_source_host = strdup(val)) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
}
id++;
clicon_session_id_set(h, id);
cprintf(cbret, "<hello xmlns=\"%s\"><session-id>%u</session-id></hello>",
@ -1498,7 +1593,7 @@ from_client_msg(clicon_handle h,
}
if (strcmp(rpcname, "rpc") == 0){
; /* continue below */
ce->ce_in_rpcs++; /* Track all RPCs */
}
else if (strcmp(rpcname, "hello") == 0){
if ((ret = from_client_hello(h, x, ce, cbret)) <0)
@ -1508,9 +1603,12 @@ from_client_msg(clicon_handle h,
else{
if (netconf_unknown_element(cbret, "protocol", rpcname, "Unrecognized netconf operation")< 0)
goto done;
ce->ce_in_bad_rpcs++;
ce->ce_out_rpc_errors++; /* Number of <rpc-reply> messages sent that contained an <rpc-error> */
goto reply;
}
ce->ce_id = id;
/* As a side-effect, this expands xt with default values according to "report-all"
* This may not be correct, the RFC does not mention expanding default values for
* input RPC
@ -1520,6 +1618,8 @@ from_client_msg(clicon_handle h,
if (ret == 0){
if (clixon_xml2cbuf(cbret, xret, 0, 0, -1, 0) < 0)
goto done;
ce->ce_in_bad_rpcs++;
ce->ce_in_rpcs--; /* Track all RPCs */
goto reply;
}
xe = NULL;
@ -1531,6 +1631,7 @@ from_client_msg(clicon_handle h,
if ((ye = xml_spec(xe)) == NULL){
if (netconf_operation_not_supported(cbret, "protocol", rpc) < 0)
goto done;
ce->ce_out_rpc_errors++;
goto reply;
}
if ((ymod = ys_module(ye)) == NULL){
@ -1557,26 +1658,34 @@ from_client_msg(clicon_handle h,
creds = clicon_nacm_credentials(h);
if ((ret = verify_nacm_user(h, creds, ce->ce_username, username, cbret)) < 0)
goto done;
if (ret == 0) /* credentials fail */
if (ret == 0){ /* credentials fail */
ce->ce_out_rpc_errors++;
goto reply;
}
/* NACM rpc operation exec validation */
if ((ret = nacm_rpc(rpc, module, username, xnacm, cbret)) < 0)
goto done;
if (ret == 0) /* Not permitted and cbret set */
if (ret == 0){ /* Not permitted and cbret set */
ce->ce_out_rpc_errors++;
goto reply;
}
}
clicon_err_reset();
if ((ret = rpc_callback_call(h, xe, ce, &nr, cbret)) < 0){
if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0)
goto done;
clicon_log(LOG_NOTICE, "%s Error in rpc_callback_call:%s", __FUNCTION__, xml_name(xe));
ce->ce_out_rpc_errors++;
goto reply; /* Dont quit here on user callbacks */
}
if (ret == 0)
if (ret == 0){
ce->ce_out_rpc_errors++;
goto reply;
}
if (nr == 0){ /* not handled by callback */
if (netconf_operation_not_supported(cbret, "application", "RPC operation not supported")< 0)
goto done;
ce->ce_out_rpc_errors++;
goto reply;
}
if (xnacm){

View file

@ -41,24 +41,33 @@
* Types
*/
/*
* Client entry.
* Backend client entry.
* Keep state about every connected client.
* References from RFC 6022, ietf-netconf-monitoring.yang sessions container
*/
struct client_entry{
struct client_entry *ce_next; /* The clients linked list */
struct sockaddr ce_addr; /* The clients (UNIX domain) address */
int ce_s; /* stream socket to client */
int ce_nr; /* Client number (for dbg/tracing) */
int ce_stat_in; /* Nr of received msgs from client */
int ce_stat_out;/* Nr of sent msgs to client */
uint32_t ce_id; /* Session id, accessor functions: clicon_session_id_get/set */
char *ce_username;/* Translated from peer user cred */
clicon_handle ce_handle; /* clicon config handle (all clients have same?) */
char *ce_transport; /* Identifies the transport for each session.
Clixon-lib.yang extends these values by prefixing with
"cl:", where cl is ensured to be declared ie by
netconf-monitoring state */
char *ce_source_host; /* Host identifier of the NETCONF client */
struct timeval ce_time; /* Time at the server at which the session was established. */
uint32_t ce_in_rpcs ; /* Number of correct <rpc> messages received. */
uint32_t ce_in_bad_rpcs; /* Not correct <rpc> messages */
uint32_t ce_out_rpc_errors; /* <rpc-error> messages*/
};
/*
* Prototypes
*/
int backend_monitoring_state_get(clicon_handle h, yang_stmt *yspec, char *xpath, cvec *nsc, cxobj **xret, cxobj **xerr);
int backend_client_rm(clicon_handle h, struct client_entry *ce);
int from_client(int fd, void *arg);
int backend_rpc_init(clicon_handle h);

View file

@ -181,10 +181,22 @@ client_get_streams(clicon_handle h,
* @param[in] xpath XPath selection, may be used to filter early
* @param[in] nsc XML Namespace context for xpath
* @param[in] wdef With-defaults parameter, see RFC 6243
* @param[in,out] xret Existing XML tree, merge x into this
* @param[in,out] xret Existing XML tree, merge x into this, or rpc-error
* @retval -1 Error (fatal)
* @retval 0 Statedata callback failed (clicon_err called)
* @retval 0 Statedata callback failed (error in xret)
* @retval 1 OK
* @note This code in general does not look at xpath, needs to be filtered in retrospect
* @note Awkward error handling. Even if most of this is during development phase, except for plugin
* state callbacks.
* Present behavior:
* - Present behavior: should be returned in xret with retval 0(error) or 1(ok)
* - On error, previous content of xret is not freed
* - xret is in turn translated to cbuf in calling function
* Instead, I think there should be a second out argument **xerr with the error message, see code
* for CLICON_NETCONF_MONITORING which is transformed in calling function(?) to an internal error
* message. But this needs to be explored in all sub-functions
*
*
*/
static int
get_client_statedata(clicon_handle h,
@ -196,9 +208,11 @@ get_client_statedata(clicon_handle h,
int retval = -1;
yang_stmt *yspec;
yang_stmt *ymod;
cxobj *x1 = NULL;
int ret;
char *namespace;
cbuf *cb = NULL;
cxobj *xerr = NULL;
clicon_debug(1, "%s", __FUNCTION__);
if ((yspec = clicon_dbspec_yang(h)) == NULL){
@ -236,7 +250,6 @@ get_client_statedata(clicon_handle h,
goto done;
}
cbuf_reset(cb);
/* XXX This code does not filter state data with xpath */
cprintf(cb, "<restconf-state xmlns=\"%s\"/>", namespace);
if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, xret, NULL) < 0)
goto done;
@ -254,7 +267,32 @@ get_client_statedata(clicon_handle h,
goto fail;
}
if (clicon_option_bool(h, "CLICON_NETCONF_MONITORING")){
if ((ret = netconf_monitoring_state_get(h, yspec, xpath, nsc, 0, xret)) < 0)
if ((ret = netconf_monitoring_state_get(h, yspec, xpath, nsc, xret, &xerr)) < 0)
goto done;
if (ret == 0){
if (clixon_netconf_internal_error(xerr, " . Internal error, netconf_monitoring_state returned invalid XML", NULL) < 0)
goto done;
if (*xret)
xml_free(*xret);
*xret = xerr;
xerr = NULL;
goto fail;
}
/* Some state, client state, is avaliable in backend only, not in lib
* Needs merge since same subtree as previous lib state
*/
if ((ret = backend_monitoring_state_get(h, yspec, xpath, nsc, &x1, &xerr)) < 0)
goto done;
if (ret == 0){
if (clixon_netconf_internal_error(xerr, " . Internal error, baenckend_monitoring_state_get returned invalid XML", NULL) < 0)
goto done;
if (*xret)
xml_free(*xret);
*xret = xerr;
xerr = NULL;
goto fail;
}
if ((ret = netconf_trymerge(x1, yspec, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
@ -320,6 +358,10 @@ get_client_statedata(clicon_handle h,
retval = 1; /* OK */
done:
clicon_debug(1, "%s %d", __FUNCTION__, retval);
if (xerr)
xml_free(xerr);
if (x1)
xml_free(x1);
if (cb)
cbuf_free(cb);
return retval;
@ -734,19 +776,13 @@ get_list_pagination(clicon_handle h,
cbuf *cba = NULL;
/* Add remaining attribute */
if ((xa = xml_new("remaining", x1, CX_ATTR)) == NULL)
goto done;
if ((cba = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cba, "%u", remaining);
if (xml_value_set(xa, cbuf_get(cba)) < 0)
goto done;
if (xml_prefix_set(xa, "cp") < 0)
goto done;
if (xmlns_set(x1, "cp", "http://clicon.org/clixon-netconf-list-pagination") < 0)
goto done;
if (xml_add_attr(x1, "remaining", cbuf_get(cba), "cp", "http://clicon.org/clixon-netconf-list-pagination") < 0)
goto done;
if (cba)
cbuf_free(cba);
}

View file

@ -249,7 +249,6 @@ backend_accept_client(int fd,
}
if ((ce = backend_client_add(h, &from)) == NULL)
goto done;
ce->ce_handle = h;
/*
* Get credentials of connected peer - only for unix socket

View file

@ -145,6 +145,8 @@ backend_client_add(clicon_handle h,
ce->ce_nr = bh->bh_ce_nr++; /* Session-id ? */
memcpy(&ce->ce_addr, addr, sizeof(*addr));
ce->ce_next = bh->bh_ce_list;
ce->ce_handle = h;
gettimeofday(&ce->ce_time, NULL);
bh->bh_ce_list = ce;
return ce;
}
@ -180,6 +182,10 @@ backend_client_delete(clicon_handle h,
*ce_prev = c->ce_next;
if (ce->ce_username)
free(ce->ce_username);
if (ce->ce_transport)
free(ce->ce_transport);
if (ce->ce_source_host)
free(ce->ce_source_host);
free(ce);
break;
}
@ -203,8 +209,9 @@ backend_client_print(clicon_handle h,
fprintf(f, "Client: %d\n", ce->ce_nr);
fprintf(f, " Session: %d\n", ce->ce_id);
fprintf(f, " Socket: %d\n", ce->ce_s);
fprintf(f, " Msgs in: %d\n", ce->ce_stat_in);
fprintf(f, " Msgs out: %d\n", ce->ce_stat_out);
fprintf(f, " RPCs in: %u\n", ce->ce_in_rpcs);
fprintf(f, " Bad RPCs in: %u\n", ce->ce_in_bad_rpcs);
fprintf(f, " Err RPCs out: %u\n", ce->ce_out_rpc_errors);
fprintf(f, " Username: %s\n", ce->ce_username);
}
return 0;