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 \"