diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5aeea93e..8a9bd2fc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: Clixon CI on: push: - branches: [ master, http-data ] + branches: [ master, snmp ] pull_request: - branches: [ master, http-data ] + branches: [ master, snmp ] jobs: build: diff --git a/CHANGELOG.md b/CHANGELOG.md index 01b9f230..a0f1dc05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,20 @@ * [3.3.2](#332) Aug 27 2017 * [3.3.1](#331) June 7 2017 +## SNMP branch + +* Clixon SNMP frontend + * Support of SNMP for retreiving and setting values via net-snmp using a MIB-YANG mapping defined in RFC6643. + * For details, see [SNMP section of user manual](https://clixon-docs.readthedocs.io/en/latest/snmp.html) + * YANG `clixon-config@2022-03-21.yang` changes: + * Added options: + * `CLICON_SNMP_AGENT_SOCK` + * `CLICON_SNMP_MIB` + * New configure options: + * `--enable-netsnmp` + * `--with-mib-generated-yang-dir=DIR` + * Thanks to Siklu Communications LTD for sponsoring this work + ## 5.8.0 Planned: July 2022 @@ -121,8 +135,6 @@ Developers may need to change their code The Clixon 5.7 release introduces (long overdue) NETCONF chunked framing as defined in RFC 6242. It also introduces a limited http data service and lots of bugfixes. -### New features - * Implementation of "chunked framing" according to RFC6242 for Netconf 1.1. * First hello is 1.0 EOM framing, then successing rpc is chunked framing * See diff --git a/apps/Makefile.in b/apps/Makefile.in index 4bf422c6..1e85dc81 100644 --- a/apps/Makefile.in +++ b/apps/Makefile.in @@ -36,6 +36,7 @@ VPATH = @srcdir@ srcdir = @srcdir@ top_srcdir = @top_srcdir@ with_restconf = @with_restconf@ +enable_netsnmp = @enable_netsnmp@ SHELL = /bin/sh @@ -47,6 +48,11 @@ SUBDIRS += netconf ifdef with_restconf SUBDIRS += restconf endif +ifdef enable_netsnmp +ifeq ($(enable_netsnmp),yes) +SUBDIRS += snmp +endif +endif .PHONY: all clean depend install $(SUBDIRS) diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index 7895fb45..ac8bc69d 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -453,7 +453,7 @@ usage(clicon_handle h, "\t-p \tAdd Yang directory path (see CLICON_YANG_DIR)\n" "\t-b \tSpecify datastore directory\n" "\t-F\t\tRun in foreground, do not run as daemon\n" - "\t-z\t\tKill other config daemon and exit\n" + "\t-z\t\tKill other backend daemon and exit\n" "\t-a UNIX|IPv4|IPv6 Internal backend socket family\n" "\t-u \tInternal socket domain path or IP addr (see -a)(default: %s)\n" "\t-P \tPid filename (default: %s)\n" @@ -1004,7 +1004,7 @@ main(int argc, goto done; /* Write pid-file */ - if ((pid = pidfile_write(pidfile)) < 0) + if (pidfile_write(pidfile) < 0) goto done; if (set_signal(SIGTERM, backend_sig_term, NULL) < 0){ diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index 92de4dfe..d9e4b503 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -861,7 +861,7 @@ cli_show_options(clicon_handle h, else fprintf(stdout, "%s: NULL\n", keys[i]); } - /* Next print CLICON_FEATURE and CLICON_YANG_DIR from config tree + /* Next print CLICON_FEATURE, CLICON_YANG_DIR and CLICON_SNMP_MIB from config tree * Since they are lists they are placed in the config tree. */ x = NULL; @@ -876,6 +876,12 @@ cli_show_options(clicon_handle h, continue; fprintf(stdout, "%s: \"%s\"\n", xml_name(x), xml_body(x)); } + x = NULL; + while ((x = xml_child_each(clicon_conf_xml(h), x, CX_ELMNT)) != NULL) { + if (strcmp(xml_name(x), "CLICON_SNMP_MIB") != 0) + continue; + fprintf(stdout, "%s: \"%s\"\n", xml_name(x), xml_body(x)); + } retval = 0; done: if (keys) diff --git a/apps/snmp/Makefile.in b/apps/snmp/Makefile.in new file mode 100644 index 00000000..b39fe187 --- /dev/null +++ b/apps/snmp/Makefile.in @@ -0,0 +1,134 @@ +# +# ***** BEGIN LICENSE BLOCK ***** +# +# Copyright (C) 2022 Olof Hagsand and Kristofer Hallin +# +# 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 ***** +# +# +VPATH = @srcdir@ +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +CC = @CC@ +CFLAGS = @CFLAGS@ +LINKAGE = @LINKAGE@ +INSTALLFLAGS = @INSTALLFLAGS@ +LDFLAGS = @LDFLAGS@ + +prefix = @prefix@ +datarootdir = @datarootdir@ +exec_prefix = @exec_prefix@ +bindir = @bindir@ +libdir = @libdir@ +sbindir = @sbindir@ +mandir = @mandir@ +libexecdir = @libexecdir@ +localstatedir = @localstatedir@ +sysconfdir = @sysconfdir@ +includedir = @includedir@ +HOST_VENDOR = @host_vendor@ + +SH_SUFFIX = @SH_SUFFIX@ +LIBSTATIC_SUFFIX = @LIBSTATIC_SUFFIX@ +CLIXON_MAJOR = @CLIXON_VERSION_MAJOR@ +CLIXON_MINOR = @CLIXON_VERSION_MINOR@ + +# Use this clixon lib for linking +ifeq ($(LINKAGE),dynamic) + CLIXON_LIB = libclixon$(SH_SUFFIX).$(CLIXON_MAJOR).$(CLIXON_MINOR) +else + CLIXON_LIB = libclixon$(LIBSTATIC_SUFFIX) +endif + +# For dependency +LIBDEPS = $(top_srcdir)/lib/src/$(CLIXON_LIB) + +LIBS = -L$(top_srcdir)/lib/src $(top_srcdir)/lib/src/$(CLIXON_LIB) @LIBS@ + +CPPFLAGS = @CPPFLAGS@ + +ifeq ($(LINKAGE),dynamic) +CPPFLAGS += -fPIC +endif + +INCLUDES = -I. -I$(top_srcdir)/lib/src -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@ + +# Application +APPL = clixon_snmp + +# Common source - not accessible from plugin - independent of restconf package (fcgi|native) +APPSRC = +APPSRC += snmp_main.c +APPSRC += snmp_register.c +APPSRC += snmp_handler.c +APPSRC += snmp_lib.c + +APPOBJ = $(APPSRC:.c=.o) + +all: $(APPL) + +# Dependency of clixon library (LIBDEPS) +$(top_srcdir)/lib/src/$(CLIXON_LIB): + (cd $(top_srcdir)/lib/src && $(MAKE) $(MFLAGS) $(CLIXON_LIB)) + +clean: + rm -f *.core $(APPL) $(APPOBJ) *.o + rm -f *.gcda *.gcno *.gcov # coverage + +distclean: clean + rm -f Makefile *~ .depend + +# Put daemon in bin +# Put other executables in libexec/ +install: $(APPL) + install -d -m 0755 $(DESTDIR)$(sbindir) + install -m 0755 $(INSTALLFLAGS) $(APPL) $(DESTDIR)$(sbindir) + +install-include: + +uninstall: + rm -f $(DESTDIR)$(sbindir)/$(APPL) + +.SUFFIXES: +.SUFFIXES: .c .o + +.c.o: + $(CC) $(INCLUDES) -D__PROGRAM__=\"clixon_snmp\" $(CPPFLAGS) $(CFLAGS) -c $< + +$(APPL) : $(APPOBJ) $(LIBDEPS) + echo $(APPOBJ) + echo $(LIBDEPS) + $(CC) $(LDFLAGS) $^ $(LIBS) -o $@ + +TAGS: + find . -name '*.[chyl]' -print | etags - + +depend: + $(CC) $(DEPENDFLAGS) @DEFS@ $(INCLUDES) $(CFLAGS) -MM $(APPFCGI) $(APPSRC) > .depend + +#include .depend diff --git a/apps/snmp/snmp_handler.c b/apps/snmp/snmp_handler.c new file mode 100644 index 00000000..69f950e5 --- /dev/null +++ b/apps/snmp/snmp_handler.c @@ -0,0 +1,1360 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2022 Olof Hagsand and Kristofer Hallin + Sponsored by Siklu Communications LTD + + 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 ***** + */ + +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +/* net-snmp */ +#include +#include +#include + +/* cligen */ +#include + +/* clicon */ +#include + +#include "snmp_lib.h" +#include "snmp_register.h" +#include "snmp_handler.h" + +/*! Common code for handling incoming SNMP request + * + * Get clixon handle from snmp request, print debug data + * @param[in] handler Registered MIB handler structure + * @param[in] nhreg Root registration info. + * @param[in] reqinfo Agent transaction request structure + * @param[in] request The netsnmp request info structure. + * @param[in] tablehandler 0:scalar, 1:table for debug + * @param[out] shp Clixon snmp handle + * @retval 0 OK + * @retval -1 Error + */ +static int +snmp_common_handler(netsnmp_mib_handler *handler, + netsnmp_handler_registration *nhreg, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *request, + int tablehandler, + clixon_snmp_handle **shp) +{ + int retval = -1; + netsnmp_variable_list *requestvb; /* sub of request */ + cbuf *cb; + + if (request == NULL || shp == NULL){ + clicon_err(OE_XML, EINVAL, "request or shp is null"); + goto done; + } + requestvb = request->requestvb; + if ((*shp = (clixon_snmp_handle*)handler->myvoid) == NULL){ + clicon_err(OE_XML, 0, "No myvoid handler"); + goto done; + } + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + oid_cbuf(cb, (*shp)->sh_oid, (*shp)->sh_oidlen); + if (oid_eq(requestvb->name, requestvb->name_length, + (*shp)->sh_oid, (*shp)->sh_oidlen) == 0){ /* equal */ + clicon_debug(1, "%s \"%s\" %s inclusive:%d %s", __FUNCTION__, + cbuf_get(cb), + snmp_msg_int2str(reqinfo->mode), + request->inclusive, tablehandler?"table":"scalar"); + } + else{ /* not equal */ + cprintf(cb, " ("); + oid_cbuf(cb, requestvb->name, requestvb->name_length); + cprintf(cb, ")"); + // nhreg->rootoid same as shp + clicon_debug(1, "%s \"%s\" %s inclusive:%d %s", __FUNCTION__, + cbuf_get(cb), + snmp_msg_int2str(reqinfo->mode), + request->inclusive, tablehandler?"table":"scalar"); + } + retval = 0; + done: + if (cb) + cbuf_free(cb); + return retval; +} + +/*! + * @param[in] reqinfo Agent transaction request structure + * @param[in] request The netsnmp request info structure. + * @retval 0 OK + * @retval -1 Error + */ +static int +snmp_scalar_return(cxobj *xs, + yang_stmt *ys, + oid *oidc, + size_t oidclen, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *request) +{ + int retval = -1; + int asn1type; + char *xmlstr = NULL; + char *defaultval = NULL; + u_char *snmpval = NULL; + size_t snmplen = 0; + char *reason = NULL; + netsnmp_variable_list *requestvb = request->requestvb; + int ret; + + /* SMI default value, How is this different from yang defaults? + */ + if (yang_extension_value(ys, "defval", IETF_YANG_SMIV2_NS, NULL, &defaultval) < 0) + goto done; + if (xs != NULL){ + if ((ret = type_xml2snmp_pre(xml_body(xs), ys, &xmlstr)) < 0) + goto done; + if (ret == 0){ + if ((ret = netsnmp_request_set_error(request, SNMP_ERR_WRONGVALUE)) != SNMPERR_SUCCESS){ + clicon_err(OE_SNMP, ret, "netsnmp_request_set_error"); + goto done; + } + goto ok; + } + } + else if (defaultval != NULL){ + if ((xmlstr = strdup(defaultval)) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } + } + else{ + if ((ret = netsnmp_request_set_error(request, SNMP_NOSUCHINSTANCE)) != SNMPERR_SUCCESS){ + clicon_err(OE_SNMP, ret, "netsnmp_request_set_error"); + goto done; + } + goto ok; + } + if (type_yang2asn1(ys, &asn1type, 1) < 0) + goto done; + if ((ret = type_xml2snmp(xmlstr, &asn1type, &snmpval, &snmplen, &reason)) < 0) + goto done; + if (ret == 0){ + clicon_debug(1, "%s %s", __FUNCTION__, reason); + if ((ret = netsnmp_request_set_error(request, SNMP_ERR_WRONGTYPE)) != SNMPERR_SUCCESS){ + clicon_err(OE_SNMP, ret, "netsnmp_request_set_error"); + goto done; + } + goto ok; + } + /* see snmplib/snmp_client. somewhat indirect + */ + if ((ret = snmp_set_var_typed_value(requestvb, asn1type, snmpval, snmplen)) != SNMPERR_SUCCESS){ + clicon_err(OE_SNMP, ret, "snmp_set_var_typed_value"); + goto done; + } + if ((ret = snmp_set_var_objid(requestvb, oidc, oidclen)) != SNMPERR_SUCCESS){ + clicon_err(OE_SNMP, ret, "snmp_set_var_objid"); + goto done; + } + ok: + retval = 0; + done: + if (xmlstr) + free(xmlstr); + if (snmpval) + free(snmpval); + if (reason) + free(reason); + return retval; +} + +/*! Scalar handler, set a value to clixon + * get xpath: see yang2api_path_fmt / api_path2xpath + * @param[in] h Clixon handle + * @param[in] ys Yang node + * @param[in] cvk Vector of index/Key variables, if any + * @param[in] defaultval Default value + * @param[in] reqinfo Agent transaction request structure + * @param[in] request The netsnmp request info structure. + * @retval 0 OK + * @retval -1 Error + */ +static int +snmp_scalar_get(clicon_handle h, + yang_stmt *ys, + cvec *cvk, + char *defaultval, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *request) +{ + int retval = -1; + cvec *nsc = NULL; + char *xpath = NULL; + cxobj *xt = NULL; + cxobj *xerr; + cxobj *x = NULL; + char *xmlstr = NULL; + u_char *snmpval = NULL; + size_t snmplen = 0; + int ret; + int asn1type; + char *reason = NULL; + netsnmp_variable_list *requestvb = request->requestvb; + cxobj *xcache = NULL; + + clicon_debug(1, "%s", __FUNCTION__); + /* Prepare backend call by constructing namespace context */ + if (xml_nsctx_yang(ys, &nsc) < 0) + goto done; + /* Create xpath from yang */ + if (snmp_yang2xpath(ys, cvk, &xpath) < 0) + goto done; + if (type_yang2asn1(ys, &asn1type, 1) < 0) + goto done; + /* First try cache */ + clicon_ptr_get(h, "snmp-rowstatus-tree", (void**)&xcache); + if (xcache==NULL || (x = xpath_first(xcache, nsc, "%s", xpath)) == NULL){ + /* If not found do the backend call */ + if (clicon_rpc_get(h, xpath, nsc, CONTENT_ALL, -1, &xt) < 0) + goto done; + /* Detect error XXX Error handling could improve */ + if ((xerr = xpath_first(xt, NULL, "/rpc-error")) != NULL){ + clixon_netconf_error(xerr, "clicon_rpc_get", NULL); + goto done; + } + x = xpath_first(xt, nsc, "%s", xpath); + } + /* + * The xml to snmp value conversion is done in two steps: + * 1. From XML to SNMP string, there is a special case for enumeration, and for default value + * 2. From SNMP string to SNMP binary value which invloves parsing + */ + if (x != NULL){ + assert(xml_spec(x) == ys); + if ((ret = type_xml2snmp_pre(xml_body(x), ys, &xmlstr)) < 0) + goto done; + if (ret == 0){ + if ((ret = netsnmp_request_set_error(request, SNMP_ERR_WRONGVALUE)) != SNMPERR_SUCCESS){ + clicon_err(OE_SNMP, ret, "netsnmp_request_set_error"); + goto done; + } + goto ok; + } + } + else if (defaultval != NULL){ + if ((xmlstr = strdup(defaultval)) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } + } + else{ + if ((ret = netsnmp_request_set_error(request, SNMP_NOSUCHINSTANCE)) != SNMPERR_SUCCESS){ + clicon_err(OE_SNMP, ret, "netsnmp_request_set_error"); + goto done; + } + goto ok; + } + if ((ret = type_xml2snmp(xmlstr, &asn1type, &snmpval, &snmplen, &reason)) < 0) + goto done; + if (ret == 0){ + clicon_debug(1, "%s %s", __FUNCTION__, reason); + if ((ret = netsnmp_request_set_error(request, SNMP_ERR_WRONGTYPE)) != SNMPERR_SUCCESS){ + clicon_err(OE_SNMP, ret, "netsnmp_request_set_error"); + goto done; + } + goto ok; + } + /* see snmplib/snmp_client. somewhat indirect + */ + if ((ret = snmp_set_var_typed_value(requestvb, asn1type, snmpval, snmplen)) != SNMPERR_SUCCESS){ + clicon_err(OE_SNMP, ret, "snmp_set_var_typed_value"); + goto done; + } + ok: + retval = 0; + done: + if (reason) + free(reason); + if (xmlstr) + free(xmlstr); + if (snmpval) + free(snmpval); + if (xt) + xml_free(xt); + if (xpath) + free(xpath); + if (nsc) + xml_nsctx_free(nsc); + return retval; +} + +/*! Yang 2 xml via api-path lib functions + */ +int +snmp_yang2xml(cxobj *xtop, + yang_stmt *ys, + cvec *cvk, + cxobj **xbot) +{ + int retval = -1; + cvec *cvk1 = NULL; + char *api_path = NULL; + char *api_path_fmt = NULL; + int i; + int ret; + yang_stmt *yspec; + + yspec = ys_spec(ys); + if (yang2api_path_fmt(ys, 0, &api_path_fmt) < 0) + goto done; + /* Need to prepend an element to fit api_path_fmt2api_path cvv parameter */ + if ((cvk1 = cvec_new(1)) == NULL){ + clicon_err(OE_UNIX, errno, "cvec_new"); + goto done; + } + for (i=0; irequestvb; + int asn1_type; + enum operation_type op = OP_MERGE; + + clicon_debug(1, "%s", __FUNCTION__); + if ((xtop = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL) + goto done; + if (snmp_yang2xml(xtop, ys, cvk, &xbot) < 0) + goto done; + if ((xb = xml_new("body", xbot, CX_BODY)) == NULL) + goto done; + /* Extended */ + if (type_yang2asn1(ys, &asn1_type, 1) < 0) + goto done; + if (valstr0){ + if (xml_value_set(xb, valstr0) < 0) + goto done; + } + else { + if ((ret = type_snmp2xml(ys, &asn1_type, requestvb, reqinfo, request, &valstr)) < 0) + goto done; + if (ret == 0) + goto ok; + if (xml_value_set(xb, valstr) < 0) + goto done; + } + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + if (clixon_xml2cbuf(cb, xtop, 0, 0, -1, 0) < 0) + goto done; + if (clicon_rpc_edit_config(h, "candidate", op, cbuf_get(cb)) < 0) + goto done; + ok: + retval = 0; + done: + if (cb) + cbuf_free(cb); + if (xtop) + xml_free(xtop); + if (valstr) + free(valstr); + return retval; +} + +/* Make cache row operation: move to backend, remove altogether + * + * Remove row from cache, then make merge or delete operation on backend. + * @param[in] h Clixon handle + * @param[in] yp Yang statement of list / parent of leaf + * @param[in] cvk Vector of index/Key variables + * @param[in] opstr Operation on row: merge or delete + * @param[in] rpc If 0: do not make backend RPC, 1: do make backend RPC + * @retval 0 OK + * @retval -1 Error + */ +static int +snmp_cache_row_op(clicon_handle h, + yang_stmt *yp, + cvec *cvk, + char *opstr, + int rpc) +{ + int retval = -1; + char *xpath; + cxobj *xrow = NULL; + cvec *nsc = NULL; + cbuf *cb = NULL; + cxobj *xtop = NULL; + cxobj *xbot = NULL; + cxobj *xa; + cxobj *xcache; + + if (xml_nsctx_yang(yp, &nsc) < 0) + goto done; + /* Create xpath from yang */ + if (snmp_yang2xpath(yp, cvk, &xpath) < 0) + goto done; + clicon_ptr_get(h, "snmp-rowstatus-tree", (void**)&xcache); + if (xcache && (xrow = xpath_first(xcache, nsc, "%s", xpath)) != NULL){ + if (xml_rm(xrow) < 0) + goto done; + if (rpc){ + if ((xtop = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL) + goto done; + if (snmp_yang2xml(xtop, yang_parent_get(yp), cvk, &xbot) < 0) + goto done; + if ((xa = xml_new("operation", xrow, CX_ATTR)) == NULL) + goto done; + if (xml_value_set(xa, opstr) < 0) + goto done; + if (xml_namespace_change(xa, NETCONF_BASE_NAMESPACE, NETCONF_BASE_PREFIX) < 0) + goto done; + if (xml_addsub(xbot, xrow) < 0) + goto done; + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + if (clixon_xml2cbuf(cb, xtop, 0, 0, -1, 0) < 0) + goto done; + if (clicon_rpc_edit_config(h, "candidate", OP_NONE, cbuf_get(cb)) < 0) + goto done; + } + else + xml_free(xrow); + } + retval = 0; + done: + if (nsc) + xml_nsctx_free(nsc); + if (xtop) + xml_free(xtop); + if (cb) + cbuf_free(cb); + if (xpath) + free(xpath); + return retval; +} + +/*! Set internal cache instead of RPC to backend + * + * Always applies to rowstatus settings, as well as rows with notInService(2) status + * @param[in] h Clixon handle + * @param[in] yp Yang statement of list / parent of leaf + * @param[in] cvk Vector of index/Key variables + * @param[in] rowstatus Existing value of rowstatus + * @param[in] reqinfo Agent transaction request structure + * @param[in] request The netsnmp request info structure. + * @retval 0 OK + * @retval -1 Error + */ +static int +snmp_cache_set(clicon_handle h, + yang_stmt *ys, + cvec *cvk, + int rowstatus, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *request) +{ + int retval = -1; + cxobj *xtop = NULL; + cxobj *xbot = NULL; + cxobj *xb; + int ret; + char *valstr = NULL; + netsnmp_variable_list *requestvb = request->requestvb; + int asn1_type; + yang_stmt *yspec; + int isrowstatus = 0; + cxobj *xcache = NULL; + + clicon_debug(1, "%s", __FUNCTION__); + if ((yspec = clicon_dbspec_yang(h)) == NULL){ + clicon_err(OE_FATAL, 0, "No DB_SPEC"); + goto done; + } + if ((xtop = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL) + goto done; + if (snmp_yang2xml(xtop, ys, cvk, &xbot) < 0) + goto done; + if ((xb = xml_new("body", xbot, CX_BODY)) == NULL) + goto done; + /* Extended */ + if (type_yang2asn1(ys, &asn1_type, 1) < 0) + goto done; + if (asn1_type == CLIXON_ASN_ROWSTATUS) + isrowstatus++; + if ((ret = type_snmp2xml(ys, &asn1_type, requestvb, reqinfo, request, &valstr)) < 0) + goto done; + if (ret == 0) + goto ok; + if (isrowstatus){ + /* Special case translation of rowstatus values: + * createAndGo -> active, createAndWait -> notInService + */ + if (strcmp(valstr, "createAndGo") == 0){ + free(valstr); + if ((valstr = strdup("active")) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } + } + else if (strcmp(valstr, "createAndWait") == 0){ + free(valstr); + if ((valstr = strdup("notInService")) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } + } + else if (strcmp(valstr, "active") == 0){ + if (rowstatus == 5) /* createAndWait */ + if (snmp_cache_row_op(h, yang_parent_get(ys), cvk, "merge", 1) < 0) + goto done; + } + else if (strcmp(valstr, "destroy") == 0){ + clicon_debug(1, "%s %d", __FUNCTION__, rowstatus); + /* Dont send delete to backend if notInService(2) */ + if (snmp_cache_row_op(h, yang_parent_get(ys), cvk, "delete", rowstatus!=2) < 0) + goto done; + } + } + if (xml_value_set(xb, valstr) < 0) + goto done; + clicon_ptr_get(h, "snmp-rowstatus-tree", (void**)&xcache); + if (xcache == NULL){ + if ((xcache = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL) + goto done; + clicon_ptr_set(h, "snmp-rowstatus-tree", xcache); + } + if (!isrowstatus || strcmp(valstr, "destroy") != 0) + if (xml_merge(xcache, xtop, yspec, NULL) < 0) + goto done; + /* Special case: push active to clixon */ + if (isrowstatus && strcmp(valstr, "active") == 0){ + if (snmp_scalar_set(h, ys, + cvk, + valstr, + reqinfo, + request) < 0) + goto done; + } + ok: + retval = 0; + done: + if (xtop) + xml_free(xtop); + if (valstr) + free(valstr); + return retval; +} + +/*! Specialized get-config to get value of row-status, if any + * + * @param[in] h Clixon handle + * @param[in] ys Yang node + * @param[in] cvk Vector of index/Key variables, if any + * @param[out] rowstatus Enmu rowstatus: 0 invalid, 1 active, etc + * @retval 0 OK + * @retval -1 Error + */ +static int +snmp_table_rowstatus_get(clicon_handle h, + yang_stmt *ys, + yang_stmt *yrestype, + cvec *cvk, + int32_t *rowstatus) +{ + int retval = -1; + cvec *nsc = NULL; + cxobj *xt = NULL; + char *xpath = NULL; + cxobj *xr; + int ret; + char *body; + char *intstr; + char *reason = NULL; + + clicon_debug(1, "%s", __FUNCTION__); + /* Prepare backend call by constructing namespace context */ + if (xml_nsctx_yang(ys, &nsc) < 0) + goto done; + /* Create xpath from yang */ + if (snmp_yang2xpath(ys, cvk, &xpath) < 0) + goto done; + clicon_ptr_get(h, "snmp-rowstatus-tree", (void**)&xt); + if (xt && (xr = xpath_first(xt, nsc, "%s", xpath)) != NULL && + (body = xml_body(xr)) != NULL) { + if ((ret = yang_enum2valstr(yrestype, body, &intstr)) < 0) + goto done; + if (ret == 0){ + clicon_debug(1, "%s %s invalid or not found", __FUNCTION__, body); + *rowstatus = 0; + } + else { + if ((ret = parse_int32(intstr, rowstatus, &reason)) < 0) + goto done; + if (ret == 0){ + clicon_debug(1, "%s parse_int32: %s", __FUNCTION__, reason); + *rowstatus = 0; + } + } + } + else + *rowstatus = 0; + retval = 0; + done: + if (xpath) + free(xpath); + if (nsc) + xml_nsctx_free(nsc); + if (reason) + free(reason); + return retval; +} + +/*! Single SNMP Scalar operation handler + * + * @param[in] handler Registered MIB handler structure + * @param[in] nhreg Root registration info. + * @param[in] reqinfo Agent transaction request structure + * @param[in] request The netsnmp request info structure. + * @retval 0 OK + * @retval -1 Error + */ +static int +clixon_snmp_scalar_handler1(netsnmp_mib_handler *handler, + netsnmp_handler_registration *nhreg, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *request) +{ + int retval = -1; + clixon_snmp_handle *sh = NULL; + int asn1_type; + netsnmp_variable_list *requestvb = request->requestvb; + int ret; + + clicon_debug(2, "%s", __FUNCTION__); + if (snmp_common_handler(handler, nhreg, reqinfo, request, 0, &sh) < 0) + goto done; + /* see net-snmp/agent/snmp_agent.h / net-snmp/library/snmp.h */ + switch (reqinfo->mode) { + case MODE_GET: /* 160 */ + if (snmp_scalar_get(sh->sh_h, sh->sh_ys, sh->sh_cvk_orig, + sh->sh_default, reqinfo, request) < 0) + goto done; + break; + case MODE_GETNEXT: /* 161 */ + assert(0); // Not seen? + break; + case MODE_SET_RESERVE1: /* 0 */ + /* Translate from YANG ys leaf type to SNMP asn1.1 type ids (not value), also cvtype */ + if (type_yang2asn1(sh->sh_ys, &asn1_type, 0) < 0) + goto done; + if (requestvb->type != asn1_type){ + clicon_debug(1, "%s Expected type:%d, got: %d", __FUNCTION__, requestvb->type, asn1_type); + if ((ret = netsnmp_request_set_error(request, SNMP_ERR_WRONGTYPE)) != SNMPERR_SUCCESS){ + clicon_err(OE_SNMP, ret, "netsnmp_request_set_error"); + goto ok; + } + } + break; + case MODE_SET_RESERVE2: /* 1 */ + break; + case MODE_SET_ACTION: /* 2 */ + if (snmp_scalar_set(sh->sh_h, sh->sh_ys, NULL, NULL, reqinfo, request) < 0) + goto done; + break; + case MODE_SET_COMMIT: /* 3 */ + if (clicon_rpc_commit(sh->sh_h) < 0) + goto done; + break; + case MODE_SET_FREE: /* 4 */ + break; + case MODE_SET_UNDO: /* 5 */ + if (clicon_rpc_discard_changes(sh->sh_h) < 0) + goto done; + break; + } + ok: + retval = SNMP_ERR_NOERROR; + done: + return retval; +} + +/*! Top level scalar request handler, loop over individual request + * + * @param[in] handler Registered MIB handler structure + * @param[in] nhreg Root registration info. + * @param[in] reqinfo Agent transaction request structure + * @param[in] requests The netsnmp request info structure. + * @see clixon_snmp_table_handler + */ +int +clixon_snmp_scalar_handler(netsnmp_mib_handler *handler, + netsnmp_handler_registration *nhreg, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests) +{ + int retval = -1; + netsnmp_request_info *req; + int ret; + + clicon_debug(1, "%s", __FUNCTION__); + for (req = requests; req; req = req->next){ + ret = clixon_snmp_scalar_handler1(handler, nhreg, reqinfo, req); + if (ret != SNMP_ERR_NOERROR){ + retval = ret; + goto done; + break; + } + } + retval = SNMP_ERR_NOERROR; + done: + return retval; +} + +/*! Create xpath from YANG table OID + 1 + n + cvk/key = requestvb->name + * + * Get yang of leaf from first part of OID + * Create xpath with right keys from later part of OID + * Query clixon if object exists, if so return value + * @param[in] h Clixon handle + * @param[in] yt Yang of table (of list type) + * @param[in] oids OID of ultimate scalar value + * @param[in] oidslen OID length of scalar + * @param[in] reqinfo Agent transaction request structure + * @param[in] request The netsnmp request info structure. + * @retval -1 Error + * @retval 0 Object not found + * @retval 1 OK + */ +static int +snmp_table_get(clicon_handle h, + yang_stmt *yt, + oid *oids, + size_t oidslen, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *request) +{ + int retval = -1; + oid oidt[MAX_OID_LEN] = {0,}; /* Table / list oid */ + size_t oidtlen = MAX_OID_LEN; + oid oidleaf[MAX_OID_LEN] = {0,}; /* Leaf */ + size_t oidleaflen = MAX_OID_LEN; + oid *oidi; + size_t oidilen; + yang_stmt *ys; + yang_stmt *yk; + char *xpath = NULL; + cvec *cvk_orig; + cvec *cvk_val; + int i; + cg_var *cv; + char *defaultval = NULL; + int ret; + + /* Get OID from table /list */ + if ((ret = yangext_oid_get(yt, oidt, &oidtlen, NULL)) < 0) + goto done; + if (ret == 0) + goto done; + /* Get yang of leaf from first part of OID */ + ys = NULL; + while ((ys = yn_each(yt, ys)) != NULL) { + if (yang_keyword_get(ys) != Y_LEAF) + continue; + /* reset oid */ + oidleaflen = MAX_OID_LEN; + if ((ret = yangext_oid_get(ys, oidleaf, &oidleaflen, NULL)) < 0) + goto done; + if (ret == 0) + goto done; + if (oidtlen + 1 != oidleaflen) /* Indexes may be from other OID scope, skip those */ + continue; + if (oids[oidleaflen-1] == oidleaf[oidleaflen-1]) + break; + } + if (ys == NULL){ + /* No leaf with matching OID */ + goto fail; + } + /* SMI default value, How is this different from yang defaults? + */ + if (yang_extension_value(ys, "defval", IETF_YANG_SMIV2_NS, NULL, &defaultval) < 0) + goto done; + + /* Create xpath with right keys from later part of OID + * Inverse of snmp_str2oid + */ + if ((cvk_orig = yang_cvec_get(yt)) == NULL){ + clicon_err(OE_YANG, 0, "No keys"); + goto done; + } + if ((cvk_val = cvec_dup(cvk_orig)) == NULL){ + clicon_err(OE_UNIX, errno, "cvec_dup"); + goto done; + } + /* read through keys and create cvk */ + oidilen = oidslen-(oidtlen+1); + oidi = oids+oidtlen+1; + /* Add keys */ + for (i=0; irequestvb; + if (requestvb->type != asn1_type){ + clicon_debug(1, "%s Expected type:%d, got: %d", __FUNCTION__, requestvb->type, asn1_type); + if ((ret = netsnmp_request_set_error(request, SNMP_ERR_WRONGTYPE)) != SNMPERR_SUCCESS){ + clicon_err(OE_SNMP, ret, "netsnmp_request_set_error"); + goto ok; + } + } + /* Create xpath with right keys from later part of OID + * Inverse of snmp_str2oid + */ + if ((cvk_orig = yang_cvec_get(yt)) == NULL){ + clicon_err(OE_YANG, 0, "No keys"); + goto done; + } + if ((cvk_val = cvec_dup(cvk_orig)) == NULL){ + clicon_err(OE_UNIX, errno, "cvec_dup"); + goto done; + } + /* read through keys and create cvk */ + oidilen = oidslen-(oidtlen+1); + oidi = oids+oidtlen+1; + /* Add keys */ + for (i=0; i 0 && + oid_eq(oidc, oidclen, oidnext, oidnextlen) < 0){ + memcpy(oidnext, oidc, oidclen*sizeof(*oidnext)); + oidnextlen = oidclen; + xnext = xcol; + ynext = ycol; + found++; + } + } /* while xcol */ + } /* while xrow */ + } + if (found){ + if (snmp_scalar_return(xnext, ynext, oidnext, oidnextlen, reqinfo, request) < 0) + goto done; + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + oid_cbuf(cb, oidnext, oidnextlen); + clicon_debug(1, "%s next: %s", __FUNCTION__, cbuf_get(cb)); + } + retval = found; + done: + if (cb) + cbuf_free(cb); + if (xpath) + free(xpath); + if (xt) + xml_free(xt); + if (nsc) + xml_nsctx_free(nsc); + return retval; +} + +/*! SNMP table operation handler + * + * @param[in] handler Registered MIB handler structure + * @param[in] nhreg Root registration info. + * @param[in] reqinfo Agent transaction request structure + * @param[in] request The netsnmp request info structure. + */ +static int +clixon_snmp_table_handler1(netsnmp_mib_handler *handler, + netsnmp_handler_registration *nhreg, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *request) +{ + int retval = -1; + clixon_snmp_handle *sh = NULL; + cvec *nsc = NULL; + cxobj *xt = NULL; + cbuf *cb = NULL; + int ret; + netsnmp_variable_list *requestvb; + + clicon_debug(2, "%s", __FUNCTION__); + if ((ret = snmp_common_handler(handler, nhreg, reqinfo, request, 1, &sh)) < 0) + goto done; + if (sh->sh_ys == NULL){ + clicon_debug(1, "%s Error table not registered", __FUNCTION__); + goto ok; + } + requestvb = request->requestvb; + switch(reqinfo->mode){ + case MODE_GET: // 160 + /* Create xpath from YANG table OID + 1 + n + cvk/key = requestvb->name + */ + if ((ret = snmp_table_get(sh->sh_h, sh->sh_ys, + requestvb->name, requestvb->name_length, + reqinfo, request)) < 0) + goto done; + if (ret == 0){ + if ((ret = netsnmp_request_set_error(request, SNMP_NOSUCHINSTANCE)) != SNMPERR_SUCCESS){ + clicon_err(OE_SNMP, ret, "netsnmp_request_set_error"); + goto done; + } + clicon_debug(1, "%s Nosuchinstance", __FUNCTION__); + } + break; + case MODE_GETNEXT: // 161 + /* Register table sub-oid:s of existing entries in clixon */ + if ((ret = snmp_table_getnext(sh->sh_h, sh->sh_ys, + requestvb->name, requestvb->name_length, + reqinfo, request)) < 0) + goto done; + if (ret == 0){ + // if ((ret = netsnmp_request_set_error(request, SNMP_NOSUCHOBJECT)) != SNMPERR_SUCCESS){ + if ((ret = netsnmp_request_set_error(request, SNMP_NOSUCHOBJECT)) != SNMPERR_SUCCESS){ + + clicon_err(OE_SNMP, ret, "netsnmp_request_set_error"); + goto done; + } + clicon_debug(1, "%s No such object", __FUNCTION__); + } + break; + case MODE_SET_RESERVE1: // 0 + // Check types: compare type in requestvb to yang type (or do later) + break; + case MODE_SET_RESERVE2: // 1 + break; + case MODE_SET_ACTION: // 2 + if ((ret = snmp_table_set(sh->sh_h, sh->sh_ys, + requestvb->name, requestvb->name_length, + reqinfo, request)) < 0) + goto done; + if (ret == 0){ + if ((ret = netsnmp_request_set_error(request, SNMP_NOSUCHINSTANCE)) != SNMPERR_SUCCESS){ + clicon_err(OE_SNMP, ret, "netsnmp_request_set_error"); + goto done; + } + clicon_debug(1, "%s Nosuchinstance", __FUNCTION__); + } + break; + case MODE_SET_COMMIT: // 3 + if (clicon_rpc_commit(sh->sh_h) < 0) + goto done; + break; + case MODE_SET_FREE: // 4 + break; + case MODE_SET_UNDO : // 5 + if (clicon_rpc_discard_changes(sh->sh_h) < 0) + goto done; + break; + } + ok: + retval = SNMP_ERR_NOERROR; + done: + if (xt) + xml_free(xt); + if (cb) + cbuf_free(cb); + if (nsc) + xml_nsctx_free(nsc); + return retval; +} + +/*! Top level table request handler, loop over individual requests + * + * @param[in] handler Registered MIB handler structure + * @param[in] nhreg Root registration info. + * @param[in] reqinfo Agent transaction request structure + * @param[in] requests The netsnmp request info structure. + * @see clixon_snmp_scalar_handler + */ +int +clixon_snmp_table_handler(netsnmp_mib_handler *handler, + netsnmp_handler_registration *nhreg, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests) +{ + int retval = -1; + netsnmp_request_info *req; + int ret; + + clicon_debug(1, "%s", __FUNCTION__); + for (req = requests; req; req = req->next){ + ret = clixon_snmp_table_handler1(handler, nhreg, reqinfo, req); + if (ret != SNMP_ERR_NOERROR){ + retval = ret; + goto done; + break; + } + } + retval = SNMP_ERR_NOERROR; + done: + return retval; +} diff --git a/apps/snmp/snmp_handler.h b/apps/snmp/snmp_handler.h new file mode 100644 index 00000000..69c8ae30 --- /dev/null +++ b/apps/snmp/snmp_handler.h @@ -0,0 +1,61 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2022 Olof Hagsand and Kristofer Hallin + Sponsored by Siklu Communications LTD + + 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 ***** + + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _SNMP_HANDLER_H_ +#define _SNMP_HANDLER_H_ + +/* + * Prototypes + */ +int clixon_snmp_table_handler(netsnmp_mib_handler *handler, + netsnmp_handler_registration *nhreg, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests); +int clixon_snmp_scalar_handler(netsnmp_mib_handler *handler, + netsnmp_handler_registration *nhreg, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests); + +#endif /* _SNMP_HANDLER_H_ */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + diff --git a/apps/snmp/snmp_lib.c b/apps/snmp/snmp_lib.c new file mode 100644 index 00000000..cbd2eabc --- /dev/null +++ b/apps/snmp/snmp_lib.c @@ -0,0 +1,1208 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2022 Olof Hagsand and Kristofer Hallin + Sponsored by Siklu Communications LTD + + 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 ***** + * See RFC 6643 + * Extensions are grouped in some categories, the one I have seen are, example: + * 1. leaf + * smiv2:max-access "read-write"; + * smiv2:oid "1.3.6.1.4.1.8072.2.1.1"; + * smiv2:defval "42"; (not always) + * 2. container, list + * smiv2:oid "1.3.6.1.4.1.8072.2.1"; + * 3. module level + * smiv2:alias "netSnmpExamples" { + * smiv2:oid "1.3.6.1.4.1.8072.2"; + * + + */ + +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* inet_addr */ +#include +#include /* ether_aton */ + +/* net-snmp */ +#include +#include +#include + +/* cligen */ +#include + +/* clicon */ +#include + +#include "snmp_lib.h" + +/* + * Local variables + */ +/* Mapping between yang keyword string <--> clixon constants + * Here is also the place where doc on some types store variables (cv) + */ +/* Mapping between smiv2 yang extension access string string <--> netsnmp handler codes (agent_handler.h) + * Here is also the place where doc on some types store variables (cv) + * see netsnmp_handler_registration_create() + */ +static const map_str2int snmp_access_map[] = { + {"read-only", HANDLER_CAN_RONLY}, /* HANDLER_CAN_GETANDGETNEXT */ + {"read-write", HANDLER_CAN_RWRITE}, /* HANDLER_CAN_GETANDGETNEXT | HANDLER_CAN_SET */ + {"not-accessible", 0}, // XXX + {"accessible-for-notify", 0}, // XXX + {NULL, -1} +}; + +/* Map between clixon and ASN.1 types. + * @see net-snmp/library/asn1.h + * @see union netsnmp_vardata in net-snmp/types.h + */ +static const map_str2int snmp_type_map[] = { + {"int32", ASN_INTEGER}, // 2 + {"string", ASN_OCTET_STR}, // 4 + {"enumeration", ASN_INTEGER}, // 2 special case + {"uint32", ASN_GAUGE}, // 0x42 / 66 + {"uint32", ASN_COUNTER}, // 0x41 / 65 + {"uint32", ASN_TIMETICKS}, // 0x43 / 67 + {"uint64", ASN_COUNTER64}, // 0x46 / 70 + {"boolean", ASN_INTEGER}, // 2 special case -> enumeration + {"string", ASN_IPADDRESS}, // 64 + {NULL, -1} +}; + + +/* Map between clixon "orig" resolved type and ASN.1 types. + */ +static const map_str2int snmp_orig_map[] = { + {"counter32", ASN_COUNTER}, // 0x41 / 65 + {"object-identifier-128", ASN_OBJECT_ID}, // 6 + {"AutonomousType", ASN_OBJECT_ID}, // 6 + {"DateAndTime", ASN_OCTET_STR}, // 4 + {"UUIDorZero", ASN_OCTET_STR}, // 4 + {"binary", ASN_OCTET_STR}, // 4 + {"timeticks", ASN_TIMETICKS}, // 0x43 / 67 + {"timestamp", ASN_TIMETICKS}, // 0x43 / 67 + {"InetAddress", ASN_IPADDRESS}, // 0x40 / 64 (Dont see this being used) + {"ipv4-address", ASN_IPADDRESS}, // 0x40 / 64 (This is used instead) + {"RowStatus", CLIXON_ASN_ROWSTATUS}, // 0x40 / 64 (This is used instead) + {"phys-address", CLIXON_ASN_PHYS_ADDR}, /* Clixon extended string type */ + {NULL, -1} +}; + +/* Map between SNMP message / mode str and int form + */ +static const map_str2int snmp_msg_map[] = { + {"MODE_SET_RESERVE1", MODE_SET_RESERVE1}, // 0 + {"MODE_SET_RESERVE2", MODE_SET_RESERVE2}, // 1 + {"MODE_SET_ACTION", MODE_SET_ACTION}, // 2 + {"MODE_SET_COMMIT", MODE_SET_COMMIT}, // 3 + {"MODE_SET_FREE", MODE_SET_FREE}, // 4 + {"MODE_GET", MODE_GET}, // 160 + {"MODE_GETNEXT", MODE_GETNEXT}, // 161 + {NULL, -1} +}; + +/*! Translate from snmp string to int representation + * @note Internal snmpd, maybe find something in netsnmpd? + */ +int +snmp_access_str2int(char *modes_str) +{ + return clicon_str2int(snmp_access_map, modes_str); +} + +const char * +snmp_msg_int2str(int msg) +{ + return clicon_int2str(snmp_msg_map, msg); +} + +/*! Check equality of two OIDs + * + * @param[in] objid0 First OID vector + * @param[in] objid0len Length of first OID vector + * @param[in] objid1 Second OID vector + * @param[in] objid1len Length of second OID vector + * @retval 0 Equal + * @retval !=0 Not equal, see man memcmp + * Should really be netsnmp lib function, but cant find any? + */ +int +oid_eq(const oid *objid0, + size_t objid0len, + const oid *objid1, + size_t objid1len) +{ + size_t min; + int ret; + + if (objid0len < objid1len) + min = objid0len; + else + min = objid1len; + /* First compare common prefix */ + ret = memcmp(objid0, objid1, min*sizeof(*objid0)); + if (ret != 0) + return ret; + /* If equal, check lengths */ + if (objid0len < objid1len) + return -1; + else if (objid0len > objid1len) + return 1; + else + return 0; +} + +/*! Append a second OID to a first + * @param[in,out] objid0 First OID vector + * @param[in,out] objid0len Length of first OID vector + * @param[in] objid1 Second OID vector + * @param[in] objid1len Length of second OID vector + * @retval 0 OK + * @retval -1 Error + * Assume objid0 is allocated with MAX_OID_LEN > oid0len+oid1len + */ +int +oid_append(const oid *objid0, + size_t *objid0len, + const oid *objid1, + size_t objid1len) +{ + void *dst; + + dst = (void*)objid0; + dst += (*objid0len)*sizeof(*objid0); + if (memcpy(dst, objid1, objid1len*sizeof(*objid0)) < 0){ + clicon_err(OE_UNIX, errno, "memcpy"); + return -1; + } + *objid0len += objid1len; + return 0; +} + +/*! Print objid to file + * @see fprint_objid but prints symbolic + */ +int +oid_cbuf(cbuf *cb, + const oid *objid, + size_t objidlen) +{ + size_t i; + + for (i=0; i oidlen + * @param[out] objidlen Length of OID vector on return + * @param[out] objidstrp Pointer to string (direct not malloced) optional + * @retval 1 OK + * @retval 0 Invalid, not found + * @retval -1 Error + */ +int +yangext_oid_get(yang_stmt *yn, + oid *objid, + size_t *objidlen, + char **objidstrp) +{ + int retval = -1; + int exist = 0; + char *oidstr = NULL; + yang_stmt *yref = NULL; + + if (yang_keyword_get(yn) == Y_LEAF){ + if (snmp_yang_type_get(yn, &yref, NULL, NULL, NULL) < 0) + goto done; + } + else + yref = yn; + /* Get OID from table /list */ + if (yang_extension_value(yref, "oid", IETF_YANG_SMIV2_NS, &exist, &oidstr) < 0) + goto done; + if (exist == 0 || oidstr == NULL){ + clicon_debug(1, "OID not found as SMIv2 yang extension of %s", yang_argument_get(yref)); + goto fail; + } + if (snmp_parse_oid(oidstr, objid, objidlen) == NULL){ + clicon_err(OE_XML, errno, "snmp_parse_oid"); + goto done; + } + if (objidstrp) + *objidstrp = oidstr; + retval = 1; + done: + return retval; + fail: + retval = 0; + goto done; +} + +/*! Duplicate clixon snmp handler struct + * Use signature of libnetsnmp data_clone field of netsnmp_mib_handler in agent_handler.h + * @param[in] arg + */ +void* +snmp_handle_clone(void *arg) +{ + clixon_snmp_handle *sh0 = (clixon_snmp_handle *)arg; + clixon_snmp_handle *sh1 = NULL; + + if (sh0 == NULL) + return NULL; + if ((sh1 = malloc(sizeof(*sh1))) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + return NULL; + } + memset(sh1, 0, sizeof(*sh1)); + if (sh0->sh_cvk_orig && + (sh1->sh_cvk_orig = cvec_dup(sh0->sh_cvk_orig)) == NULL){ + clicon_err(OE_UNIX, errno, "cvec_dup"); + return NULL; + } + return (void*)sh1; +} + +/*! Free clixon snmp handler struct + * Use signature of libnetsnmp data_free field of netsnmp_mib_handler in agent_handler.h + * @param[in] arg + */ +void +snmp_handle_free(void *arg) +{ + clixon_snmp_handle *sh = (clixon_snmp_handle *)arg; + + if (sh != NULL){ + if (sh->sh_cvk_orig) + cvec_free(sh->sh_cvk_orig); + if (sh->sh_table_info){ + if (sh->sh_table_info->indexes){ + snmp_free_varbind(sh->sh_table_info->indexes); + } + free(sh->sh_table_info); + } + free(sh); + } +} + +/*! Translate from YANG to SNMP asn1.1 type ids (not value) + * + * @param[in] ys YANG leaf node + * @param[out] asn1_type ASN.1 type id + * @param[in] extended Special case clixon extended types used in xml<->asn1 data conversions + * @retval 0 OK + * @retval -1 Error + * @see type_yang2snmp, yang only + * @note there are some special cases where extended clixon asn1-types are used to convey info + * to type_snmpstr2val, these types are prefixed with CLIXON_ASN_ + */ +int +type_yang2asn1(yang_stmt *ys, + int *asn1_type, + int extended) +{ + int retval = -1; + char *restype; /* resolved type */ + char *origtype = NULL; /* original type */ + int at; + yang_stmt *yrestype = NULL; + + /* Get yang type of leaf and translate to ASN.1 */ + if (snmp_yang_type_get(ys, NULL, &origtype, &yrestype, &restype) < 0) + goto done; + /* Translate to asn.1 + * First try original type, first type + */ + if ((at = clicon_str2int(snmp_orig_map, origtype)) >= 0 && + (extended || (at < CLIXON_ASN_EXTRAS))){ + ; + } + /* Then try fully resolved type */ + else if ((at = clicon_str2int(snmp_type_map, restype)) < 0){ + clicon_err(OE_YANG, 0, "No snmp translation for YANG %s type:%s", + yang_argument_get(ys), restype); + goto done; + } + if (extended && at == ASN_OCTET_STR && yrestype){ + yang_stmt *yrp; + char *display_hint = NULL; + yrp = yang_parent_get(yrestype); + if (yang_extension_value(yrp, "display-hint", IETF_YANG_SMIV2_NS, NULL, &display_hint) < 0) + goto done; + /* RFC2578/2579 but maybe all strings with display-hint should use this, eg exist>0? */ + if (display_hint && strcmp(display_hint, "255t")==0) + at = CLIXON_ASN_FIXED_STRING; + } + if (asn1_type) + *asn1_type = at; + retval = 0; + done: + if (origtype) + free(origtype); + return retval; +} + +/*! Translate from yang/xml/clixon to SNMP/ASN.1 + * + * @param[in] snmpval Malloc:ed snmp type + * @param[in] snmplen Length of snmp type + * @param[in] reqinfo Agent transaction request structure + * @param[in] request The netsnmp request info structure. + * @param[out] valstr Clixon/yang/xml string value, free after use) + * @retval 1 OK, and valstr set + * @retval 0 Invalid value or type + * @retval -1 Error + * @see type_xml2snmp for snmpget + */ +int +type_snmp2xml(yang_stmt *ys, + int *asn1type, + netsnmp_variable_list *requestvb, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *request, + char **valstr) +{ + int retval = -1; + char *cvstr; + enum cv_type cvtype; + cg_var *cv = NULL; + char *restype = NULL; /* resolved type */ + char *origtype = NULL; /* original type */ + yang_stmt *yrestype = NULL; + int ret; + + clicon_debug(1, "%s", __FUNCTION__); + if (valstr == NULL){ + clicon_err(OE_UNIX, EINVAL, "valstr is NULL"); + goto done; + } + if ((cvstr = (char*)clicon_int2str(snmp_type_map, requestvb->type)) == NULL){ + clicon_err(OE_XML, 0, "No mapping for snmp type %d", requestvb->type); + goto done; + } + /* Get yang type of leaf and trasnslate to ASN.1 */ + if (snmp_yang_type_get(ys, NULL, &origtype, &yrestype, &restype) < 0) + goto done; + /* special case for enum */ + if (strcmp(cvstr, "int32")==0 && strcmp(restype, "enumeration") == 0) + cvstr = "string"; + else if (strcmp(cvstr, "int32")==0 && strcmp(restype, "boolean") == 0) + cvstr = "string"; + cvtype = cv_str2type(cvstr); + if ((cv = cv_new(cvtype)) == NULL){ + clicon_err(OE_UNIX, errno, "cv_new"); + goto done; + } + switch (*asn1type){ + case CLIXON_ASN_ROWSTATUS: + *asn1type = ASN_INTEGER; + /* fall through */ + case ASN_TIMETICKS: // 67 + case ASN_INTEGER: // 2 + if (cvtype == CGV_STRING){ /* special case for enum */ + char *xmlstr; + cbuf *cb = NULL; + + if (strcmp(restype, "enumeration") == 0){ + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cb, "%ld", *requestvb->val.integer); + if (yang_valstr2enum(yrestype, cbuf_get(cb), &xmlstr) < 0) + goto done; + cbuf_free(cb); + } + else if (strcmp(restype, "boolean") == 0){ + if (*requestvb->val.integer == 1) + xmlstr = "true"; + else + xmlstr = "false"; + } + cv_string_set(cv, xmlstr); + } + else + cv_int32_set(cv, *requestvb->val.integer); + break; + case ASN_GAUGE: // 0x42 + cv_uint32_set(cv, *requestvb->val.integer); + break; + case ASN_IPADDRESS:{ + struct in_addr addr; + memcpy(&addr.s_addr, requestvb->val.string, 4); + cv_string_set(cv, inet_ntoa(addr)); + break; + } + case CLIXON_ASN_FIXED_STRING: + cv_string_set(cv, (char*)requestvb->val.string); + *asn1type = ASN_OCTET_STR; + break; + case CLIXON_ASN_PHYS_ADDR: + cv_string_set(cv, ether_ntoa((const struct ether_addr *)requestvb->val.string)); + *asn1type = ASN_OCTET_STR; + break; + case ASN_OCTET_STR: // 4 + cv_string_set(cv, (char*)requestvb->val.string); + break; + case ASN_COUNTER64:{ // 0x46 / 70 + uint64_t u64; + struct counter64 *c64; + c64 = requestvb->val.counter64; + u64 = c64->low; + u64 += c64->high*0x100000000; + cv_uint64_set(cv, u64); + break; + } + default: + assert(0); // XXX + clicon_debug(1, "%s %s not supported", __FUNCTION__, cv_type2str(cvtype)); + if ((ret = netsnmp_request_set_error(request, SNMP_ERR_WRONGTYPE)) != SNMPERR_SUCCESS){ + clicon_err(OE_SNMP, ret, "netsnmp_request_set_error"); + goto done; + } + goto fail; + break; + } + if ((*valstr = cv2str_dup(cv)) == NULL){ + clicon_err(OE_UNIX, errno, "cv2str_dup"); + goto done; + } + retval = 1; + done: + clicon_debug(2, "%s %d", __FUNCTION__, retval); + if (origtype) + free(origtype); + if (cv) + cv_free(cv); + return retval; + fail: + retval = 0; + goto done; +} + +/*! Given xml value and YANG,m return corresponding malloced snmp string + * + * For special cases to prepare for proper xml2snmp translation. This includes translating + * from string values to numeric values for enumeration and boolean. + * @param[in] xmlstr0 XML string pre + * @param[in] ys Yang node + * @param[out] xmlstr1 XML string ready for translation + * @retval 1 OK + * @retval 0 Invalid type + * @retval -1 Error + * @see type_snmp2xml for snmpset + */ +int +type_xml2snmp_pre(char *xmlstr0, + yang_stmt *ys, + char **xmlstr1) + +{ + int retval = -1; + yang_stmt *yrestype; /* resolved type */ + char *restype = NULL; /* resolved type */ + char *str = NULL; + int ret; + + if (xmlstr1 == NULL){ + clicon_err(OE_UNIX, EINVAL, "xmlstr1"); + goto done; + } + /* Get yang type of leaf and trasnslate to ASN.1 */ + if (snmp_yang_type_get(ys, NULL, NULL, &yrestype, &restype) < 0) // XXX yrestype + goto done; + if (strcmp(restype, "enumeration") == 0){ /* special case for enum */ + if ((ret = yang_enum2valstr(yrestype, xmlstr0, &str)) < 0) + goto done; + if (ret == 0){ + clicon_debug(1, "Invalid enum valstr %s", xmlstr0); + goto fail; + } + } + /* special case for bool: although smidump translates TruthValue to boolean + * and there is an ASN_BOOLEAN constant: + * 1) there is no code for ASN_BOOLEAN and + * 2) Truthvalue actually translates to enum true(1)/false(0) + */ + else if (strcmp(restype, "boolean") == 0){ + if (strcmp(xmlstr0, "false")==0) + str = "0"; + else + str = "1"; + } + else{ + str = xmlstr0; + } + if ((*xmlstr1 = strdup(str)) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } + retval = 1; + done: + clicon_debug(2, "%s %d", __FUNCTION__, retval); + return retval; + fail: + retval = 0; + goto done; +} + +/*! Given snmp string value (as translated frm XML) parse into snmp value + * + * @param[in] snmpstr SNMP type string + * @param[in,out] asn1type ASN.1 type id + * @param[out] snmpval Malloc:ed snmp type + * @param[out] snmplen Length of snmp type + * @param[out] reason Error reason if retval is 0 + * @retval 1 OK + * @retval 0 Invalid + * @retval -1 Error + * @note asn1type can be rewritten from CLIXON_ASN_ to ASN_ + * @see type_xml2snmp_pre for some pre-condition XML special cases (eg enums and bool) + */ +int +type_xml2snmp(char *snmpstr, + int *asn1type, + u_char **snmpval, + size_t *snmplen, + char **reason) +{ + int retval = -1; + int ret; + + if (snmpval == NULL || snmplen == NULL){ + clicon_err(OE_UNIX, EINVAL, "snmpval or snmplen is NULL"); + goto done; + } + switch (*asn1type){ + case CLIXON_ASN_ROWSTATUS: + *asn1type = ASN_INTEGER; + /* fall through */ + case ASN_INTEGER: // 2 + *snmplen = 4; + if ((*snmpval = malloc(*snmplen)) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + if ((ret = parse_int32(snmpstr, (int32_t*)*snmpval, reason)) < 0) + goto done; + if (ret == 0) + goto fail; + break; + case ASN_TIMETICKS: + case ASN_COUNTER: // 0x41 + case ASN_GAUGE: // 0x42 + *snmplen = 4; + if ((*snmpval = malloc(*snmplen)) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + if ((ret = parse_uint32(snmpstr, (uint32_t*)*snmpval, reason)) < 0) + goto done; + if (ret == 0) + goto fail; + + break; + case ASN_OBJECT_ID:{ // 6 + oid oid1[MAX_OID_LEN] = {0,}; + size_t sz1 = MAX_OID_LEN; + if (snmp_parse_oid(snmpstr, oid1, &sz1) == NULL){ + clicon_debug(1, "Failed to parse OID %s", snmpstr); + goto fail; + } + *snmplen = sizeof(oid)*sz1; + if ((*snmpval = malloc(*snmplen)) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memcpy(*snmpval, oid1, *snmplen); + break; + } + case ASN_OCTET_STR: // 4 + *snmplen = strlen(snmpstr)+1; + if ((*snmpval = (u_char*)strdup((snmpstr))) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } + break; + case ASN_COUNTER64:{ // 0x46 / 70 + uint64_t u64; + struct counter64 *c64; + *snmplen = sizeof(struct counter64); // 16! + if ((*snmpval = malloc(*snmplen)) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(*snmpval, 0, *snmplen); + if ((ret = parse_uint64(snmpstr, &u64, reason)) < 0) + goto done; + c64 = (struct counter64 *)*snmpval; + c64->low = u64&0xffffffff; + c64->high = u64/0x100000000; + if (ret == 0) + goto fail; + } + break; + case ASN_IPADDRESS:{ + in_addr_t saddr; + *snmplen = 4; + if ((*snmpval = malloc(*snmplen)) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + saddr = (int32_t)inet_addr(snmpstr); + memcpy(*snmpval, &saddr, 4); + break; + } + case CLIXON_ASN_PHYS_ADDR:{ + struct ether_addr *eaddr; + *snmplen = sizeof(*eaddr); + if ((*snmpval = malloc(*snmplen + 1)) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(*snmpval, 0, *snmplen + 1); + if ((eaddr = ether_aton(snmpstr)) == NULL){ + clicon_debug(1, "ether_aton(%s)", snmpstr); + goto fail; + } + memcpy(*snmpval, eaddr, sizeof(*eaddr)); + *asn1type = ASN_OCTET_STR; + break; + } + case CLIXON_ASN_FIXED_STRING: /* OCTET-STRING with decrement length */ + *snmplen = strlen(snmpstr); + if ((*snmpval = (u_char*)strdup((snmpstr))) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } + *asn1type = ASN_OCTET_STR; + break; + default: + assert(0); + } + retval = 1; + done: + clicon_debug(2, "%s %d", __FUNCTION__, retval); + return retval; + fail: + retval = 0; + goto done; +} + +/*! Construct an xpath from yang statement, internal fn using cb + * Recursively construct it to the top. + * @param[in] ys Yang statement + * @param[in] keyvec Cvec of key values + * @param[out] cb xpath as cbuf + * @retval 0 OK + * @retval -1 Error + * @see yang2xpath + */ +static int +snmp_yang2xpath_cb(yang_stmt *ys, + cvec *keyvec, + cbuf *cb) +{ + yang_stmt *yp; /* parent */ + int i; + cvec *cvk = NULL; /* vector of index keys */ + int retval = -1; + char *prefix = NULL; + + if ((yp = yang_parent_get(ys)) == NULL){ + clicon_err(OE_YANG, EINVAL, "yang expected parent %s", yang_argument_get(ys)); + goto done; + } + if (yp != NULL && /* XXX rm */ + yang_keyword_get(yp) != Y_MODULE && + yang_keyword_get(yp) != Y_SUBMODULE){ + if (snmp_yang2xpath_cb(yp, keyvec, cb) < 0) /* recursive call */ + goto done; + if (yang_keyword_get(yp) != Y_CHOICE && yang_keyword_get(yp) != Y_CASE){ + cprintf(cb, "/"); + } + } + prefix = yang_find_myprefix(ys); + if (yang_keyword_get(ys) != Y_CHOICE && yang_keyword_get(ys) != Y_CASE){ + if (prefix) + cprintf(cb, "%s:", prefix); + cprintf(cb, "%s", yang_argument_get(ys)); + } + switch (yang_keyword_get(ys)){ + case Y_LIST: + cvk = yang_cvec_get(ys); /* Use Y_LIST cache, see ys_populate_list() */ + /* Iterate over individual keys */ + assert(keyvec && cvec_len(cvk) == cvec_len(keyvec)); + for (i=0; i 42 + * But for eg strings this is more complex, eg foo -> 3.6.22.22 (or something,...) + * @param[in] str XML body string + * @param[in] yi Yang statement + * @param[out] objid OID vector + * @param[out] objidlen Length of OID vector + */ +int +snmp_str2oid(char *str, + yang_stmt *yi, + oid *objid, + size_t *objidlen) +{ + int retval = -1; + int asn1_type; + int i; + int j = 0; + + if (type_yang2asn1(yi, &asn1_type, 0) < 0) + goto done; + switch (asn1_type){ + case ASN_INTEGER: + case ASN_GAUGE: + case ASN_TIMETICKS: + case ASN_COUNTER64: + case ASN_COUNTER: + case ASN_IPADDRESS: + objid[j++] = atoi(str); + break; + case ASN_OCTET_STR:{ /* encode to N.c.c.c.c */ + objid[j++] = strlen(str); + for (i=0; i 42 + * But for eg strings this is more complex, eg foo -> 3.6.22.22 (or something,...) + * @param[in,out] oidi ObjID vector + * @param[in,out] oidilen Length of ObjID vector + * @param[in] yk Yang statement of key + * @param[out] cv CLIgen variable string notation as "x.y.z" + * @see rfc2578 Section 7.7 + */ +int +snmp_oid2str(oid **oidi, + size_t *oidilen, + yang_stmt *yk, + cg_var *cv) +{ + int retval = -1; + int asn1_type; + int i = 0; + cbuf *enc = NULL; + size_t len; + + if (type_yang2asn1(yk, &asn1_type, 1) < 0) + goto done; + if ((enc = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + switch (asn1_type){ + case ASN_INTEGER: + case ASN_GAUGE: + case ASN_TIMETICKS: + case ASN_COUNTER64: + case ASN_COUNTER: + case ASN_IPADDRESS: + case CLIXON_ASN_ROWSTATUS: + cprintf(enc, "%lu", (*oidi)[i++]); + break; + case CLIXON_ASN_PHYS_ADDR: /* XXX may need special mapping: ether_aton() ? */ + case ASN_OCTET_STR: /* decode from N.c.c.c.c */ + len = (*oidi)[i++]; + for (; i1a... + * where i and j are list keys (table indexes) + * Return two vectors: + * - cvk_val: A vector of key values: [1,a] + * - cvk_oid: A vector of OIDs: [1, 1.97] + * + * @param[in] xentry XML list entry + * @param[in] cvk_name Vector of list keys + * @param[out] cvk_val Vector of XML key values + * @param[out] objidk OID key part, to be appended to node OID + * @retval -1 Error + * @retval 0 Invalid (not all indexes present) + * @retval 1 OK + * Both cvk_val and cvk_oid can be re-used in successive calls but need to be freed w cvec_free after use + */ +int +snmp_xmlkey2val_oid(cxobj *xentry, + cvec *cvk_name, + cvec **cvk_val, + oid *objidk, + size_t *objidklen) +{ + int retval = -1; + cxobj *xi; + int i; + cg_var *cv; + cg_var *cv0; + oid objid[MAX_OID_LEN] = {0,}; + size_t objidlen = MAX_OID_LEN; + + *objidklen = 0; + if (cvk_val){ + if (*cvk_val){ + cvec_free(*cvk_val); + if ((*cvk_val = cvec_dup(cvk_name)) == NULL){ + clicon_err(OE_UNIX, errno, "cvec_dup"); + goto done; + } + } + else if ((*cvk_val = cvec_dup(cvk_name)) == NULL){ + clicon_err(OE_UNIX, errno, "cvec_dup"); + goto done; + } + } + for (i=0; iname_a, tree1->namelen)==0){ + retval = 1; + } + else + retval = 0; + // done: + return retval; +} + diff --git a/apps/snmp/snmp_lib.h b/apps/snmp/snmp_lib.h new file mode 100644 index 00000000..5a96938c --- /dev/null +++ b/apps/snmp/snmp_lib.h @@ -0,0 +1,117 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2022 Olof Hagsand and Kristofer Hallin + Sponsored by Siklu Communications LTD + + 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 ***** + + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _SNMP_LIB_H_ +#define _SNMP_LIB_H_ + +/* + * Constants + */ +/* Need some way to multiplex SNMP_ and MIB errors on OE_SNMP error handler */ +#define CLIXON_ERR_SNMP_MIB 0x1000 + +#define IETF_YANG_SMIV2_NS "urn:ietf:params:xml:ns:yang:ietf-yang-smiv2" + +/* Special case/extended Clixon ASN1 types + * Set in type_yang2asn1() if extended is true + * Must be back to proper net-snmp ASN_ types in type_snmp2xml and type_xml2snmp + * before calling netsnmp API +*/ +#define CLIXON_ASN_EXTRAS 253 /* Special case clixon address >= this */ +#define CLIXON_ASN_PHYS_ADDR 253 /* Special case phy-address */ +#define CLIXON_ASN_FIXED_STRING 254 /* RFC2578 Sec 7.7: String-valued, fixed-length */ +#define CLIXON_ASN_ROWSTATUS 255 + +/* + * Types + */ +/* Userdata to pass around in netsmp callbacks + */ +struct clixon_snmp_handle { + clicon_handle sh_h; + yang_stmt *sh_ys; /* Leaf for scalar, list for table */ + oid sh_oid[MAX_OID_LEN]; /* OID for debug, may be removed? */ + size_t sh_oidlen; + char *sh_default; /* MIB default value leaf only */ + cvec *sh_cvk_orig; /* Index/Key variable values (original) */ + netsnmp_table_registration_info *sh_table_info; /* To mimic table-handler in libnetsnmp code + * save only to free properly */ +}; +typedef struct clixon_snmp_handle clixon_snmp_handle; + +/* + * Prototypes + */ +int oid_eq(const oid * objid0, size_t objid0len, const oid * objid1, size_t objid1len); +int oid_append(const oid *objid0, size_t *objid0len, const oid *objid1, size_t objid1len); +int oid_cbuf(cbuf *cb, const oid *objid, size_t objidlen); +int oid_print(FILE *f, const oid *objid, size_t objidlen); +int snmp_yang_type_get(yang_stmt *ys, yang_stmt **yrefp, char **origtypep, yang_stmt **yrestypep, char **restypep); +int yangext_oid_get(yang_stmt *yn, oid *objid, size_t *objidlen, char **objidstr); +int snmp_access_str2int(char *modes_str); +const char *snmp_msg_int2str(int msg); +void *snmp_handle_clone(void *arg); +void snmp_handle_free(void *arg); +int type_yang2asn1(yang_stmt *ys, int *asn1_type, int extended); +int type_snmp2xml(yang_stmt *ys, + int *asn1type, + netsnmp_variable_list *requestvb, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests, + char **valstr); +int type_xml2snmp_pre(char *xmlstr, yang_stmt *ys, char **snmpstr); +int type_xml2snmp(char *snmpstr, int *asn1type, u_char **snmpval, size_t *snmplen, char **reason); +int snmp_yang2xpath(yang_stmt *ys, cvec *keyvec, char **xpath); +int snmp_str2oid(char *str, yang_stmt *yi, oid *objid, size_t *objidlen); +int snmp_oid2str(oid **oidi, size_t *oidilen, yang_stmt *yi, cg_var *cv); +int clixon_snmp_err_cb(void *handle, int suberr, cbuf *cb); +int snmp_xmlkey2val_oid(cxobj *xrow, cvec *cvk_name, cvec **cvk_orig, oid *objidk, size_t *objidklen); + +/*========== libnetsnmp-specific code =============== */ +int clixon_snmp_api_agent_check(void); +int clixon_snmp_api_agent_cleanup(void); +int clixon_snmp_api_oid_find(oid *oid1, size_t oidlen); + +#endif /* _SNMP_LIB_H_ */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + diff --git a/apps/snmp/snmp_main.c b/apps/snmp/snmp_main.c new file mode 100644 index 00000000..28fb9321 --- /dev/null +++ b/apps/snmp/snmp_main.c @@ -0,0 +1,550 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2022 Olof Hagsand and Kristofer Hallin + Sponsored by Siklu Communications LTD + + 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 ***** + * This is the clixon_snmp daemon + * It assumes a netsnmp damon is running. + * - If netsnmp does not run, clixon_snmp will not start + * - If netsnmp dies, clixon_snmp will exit + * - If netsnmp is restarted, clixon_snmp should also be restarted + * It is possible to be more resilient, such as setting a timer and trying again, in fact, libnetsnmp + * has some such mechanisms but these are NOT implemented + * @see RFC 6643 Translation of Structure of Management Information Version 2 (SMIv2) + * MIB Modules to YANG Modules + */ + +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +/* net-snmp */ +#include +#include +#include + +/* cligen */ +#include + +/* clicon */ +#include + +#include "snmp_lib.h" +#include "snmp_register.h" + +/* Command line options to be passed to getopt(3) */ +#define SNMP_OPTS "hD:f:l:o:z" + +/* Forward */ +static int clixon_snmp_input_cb(int s, void *arg); + +/*! Return (hardcoded) pid file + */ +static char* +clicon_snmp_pidfile(clicon_handle h) +{ + return "/var/tmp/clixon_snmp.pid"; +} + +/*! Signal terminates process + * Just set exit flag for proper exit in event loop + */ +static void +clixon_snmp_sig_term(int arg) +{ + clicon_log(LOG_NOTICE, "%s: %s: pid: %u Signal %d", + __PROGRAM__, __FUNCTION__, getpid(), arg); + /* This should ensure no more accepts or incoming packets are processed because next time eventloop + * is entered, it will terminate. + * However there may be a case of sockets closing rather abruptly for clients + */ + clixon_exit_set(1); +} + +/*! Clean and close all state of netconf process (but dont exit). + * Cannot use h after this + * @param[in] h Clixon handle + */ +static int +snmp_terminate(clicon_handle h) +{ + yang_stmt *yspec; + cvec *nsctx; + cxobj *x = NULL; + char *pidfile = clicon_snmp_pidfile(h); + + snmp_shutdown(__FUNCTION__); + shutdown_agent(); + clixon_snmp_api_agent_cleanup(); + if (clicon_ptr_get(h, "snmp-rowstatus-tree", (void**)&x) == 0 && x){ + xml_free(x); + x = NULL; + } + clicon_rpc_close_session(h); + if ((yspec = clicon_dbspec_yang(h)) != NULL) + ys_free(yspec); + if ((yspec = clicon_config_yang(h)) != NULL) + ys_free(yspec); + if ((nsctx = clicon_nsctx_global_get(h)) != NULL) + cvec_free(nsctx); + if ((x = clicon_conf_xml(h)) != NULL) + xml_free(x); + xpath_optimize_exit(); + clixon_event_exit(); + clicon_handle_exit(h); + clixon_err_exit(); + clicon_log_exit(); + if (pidfile) + unlink(pidfile); + return 0; +} + +/*! Get which sockets are used from SNMP API, the register single sockets into clixon event system + * + * @param[in] h Clixon handle + * @param[in] register if 1 register snmp sockets with event handler. If 0 close and unregister + * This is a workaround for netsnmps API usiing fdset:s, instead an fdset is created before calling + * the snmp api + * if you use select(), see snmp_select_info() in snmp_api(3) + * snmp_select_info(int *numfds, fd_set *fdset, struct timeval *timeout, int *block) + * @see clixon_snmp_input_cb + */ +static int +clixon_snmp_fdset_register(clicon_handle h, + int regfd) +{ + int retval = -1; + int numfds = 0; + fd_set readfds; + struct timeval timeout = { LONG_MAX, 0 }; + int block = 0; + int nr; + int s; + + FD_ZERO(&readfds); + if ((nr = snmp_sess_select_info(NULL, &numfds, &readfds, &timeout, &block)) < 0){ + clicon_err(OE_XML, errno, "snmp_select_error"); + goto done; + } + /* eg 4, 6, 8 */ + for (s=0; s\tDebug level\n" + "\t-f \tConfiguration file (mandatory)\n" + "\t-l (e|o|s|f) Log on std(e)rr, std(o)ut, (s)yslog(default), (f)ile\n" + "\t-z\t\tKill other %s daemon and exit\n" + "\t-o \"