From df687f71808b0cf5dbd712f829a0b52b57307433 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Wed, 4 May 2022 15:03:08 +0200 Subject: [PATCH] Clixon SNMP frontend Added two new config options to clixon-config.yang: `CLICON_HTTP_DATA_ROOT` Added new files: apps/snmp/snmp_mib_yang.[ch] for generic MIB/YANG handling Test: killall quiet Test: added specific MIB for generic code: NET-SNMP-EXAMPLES-MIB --- CHANGELOG.md | 3 +- apps/cli/cli_show.c | 8 +- apps/snmp/Makefile.in | 1 + apps/snmp/snmp_main.c | 238 +----------- apps/snmp/snmp_mib_yang.c | 453 ++++++++++++++++++++++ apps/snmp/snmp_mib_yang.h | 53 +++ lib/src/clixon_options.c | 18 +- lib/src/clixon_yang.c | 6 +- test/lib.sh | 4 +- test/test_snmp.sh | 5 +- test/test_snmp_table.sh | 5 +- yang/clixon/clixon-config@2022-03-21.yang | 10 + 12 files changed, 561 insertions(+), 243 deletions(-) create mode 100644 apps/snmp/snmp_mib_yang.c create mode 100644 apps/snmp/snmp_mib_yang.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c8e9a5b..eb62f7ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,8 +41,9 @@ * 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 option: + * Added options: * `CLICON_SNMP_AGENT_SOCK` + * `CLICON_SNMP_MIB` * New configure options: * `--enable-netsnmp` * `--with-mib-generated-yang-dir=DIR` 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 index 9ceb6caf..a5a0cdab 100644 --- a/apps/snmp/Makefile.in +++ b/apps/snmp/Makefile.in @@ -84,6 +84,7 @@ APPL = clixon_snmp # Common source - not accessible from plugin - independent of restconf package (fcgi|native) APPSRC = APPSRC += snmp_main.c +APPSRC += snmp_mib_yang.c APPOBJ = $(APPSRC:.c=.o) diff --git a/apps/snmp/snmp_main.c b/apps/snmp/snmp_main.c index bb06fd8a..ee921319 100644 --- a/apps/snmp/snmp_main.c +++ b/apps/snmp/snmp_main.c @@ -55,229 +55,11 @@ /* clicon */ #include +#include "snmp_mib_yang.h" + /* Command line options to be passed to getopt(3) */ #define SNMP_OPTS "hD:f:l:o:" -#if 1 /* scalar example */ - -#define TESTHANDLER_SET_NAME "my_test" -int -my_test_instance_handler(netsnmp_mib_handler *handler, - netsnmp_handler_registration *reginfo, - netsnmp_agent_request_info *reqinfo, - netsnmp_request_info *requests) -{ - - static int accesses = 42; - u_long *accesses_cache = NULL; - - clicon_debug(1, "%s", __FUNCTION__); - - switch (reqinfo->mode) { - case MODE_GET: - snmp_set_var_typed_value(requests->requestvb, ASN_INTEGER, - (u_char *) & accesses, sizeof(accesses)); - break; - case MODE_SET_RESERVE1: - if (requests->requestvb->type != ASN_INTEGER) - netsnmp_set_request_error(reqinfo, requests, - SNMP_ERR_WRONGTYPE); - break; - - case MODE_SET_RESERVE2: - /* - * store old info for undo later - */ - accesses_cache = netsnmp_memdup(&accesses, sizeof(accesses)); - if (accesses_cache == NULL) { - netsnmp_set_request_error(reqinfo, requests, - SNMP_ERR_RESOURCEUNAVAILABLE); - return SNMP_ERR_NOERROR; - } - netsnmp_request_add_list_data(requests, - netsnmp_create_data_list - (TESTHANDLER_SET_NAME, - accesses_cache, free)); - break; - - case MODE_SET_ACTION: - /* - * update current - */ - accesses = *(requests->requestvb->val.integer); - DEBUGMSGTL(("testhandler", "updated accesses -> %d\n", accesses)); - break; - - case MODE_SET_UNDO: - accesses = - *((u_long *) netsnmp_request_get_list_data(requests, - TESTHANDLER_SET_NAME)); - break; - - case MODE_SET_COMMIT: - case MODE_SET_FREE: - /* - * nothing to do - */ - break; - } - - return SNMP_ERR_NOERROR; -} - -static void -init_testscalar(void) -{ - oid my_oid[] = { 1, 3, 6, 1, 4, 1, 8072, 2, 1, 1 }; - - clicon_debug(1, "%s", __FUNCTION__); - - /* - * instance handler test - */ - - netsnmp_register_instance(netsnmp_create_handler_registration - ("netSnmpExampleInteger", my_test_instance_handler, - my_oid, OID_LENGTH(my_oid), - HANDLER_CAN_RWRITE)); -} - -#endif - -#if 1 /* table example */ -static netsnmp_table_data_set *table_set; - -/* - * https://net-snmp.sourceforge.io/dev/agent/data_set_8c-example.html#_a0 - */ -static void -init_testtable(void) -{ - netsnmp_table_row *row; - - /* - * the OID we want to register our integer at. This should be the - * * OID node for the entire table. In our case this is the - * * netSnmpIETFWGTable oid definition - */ - oid my_oid[] = { 1, 3, 6, 1, 4, 1, 8072, 2, 2, 1 }; - - /* - * a debugging statement. Run the agent with -Dexample_data_set to see - * * the output of this debugging statement. - */ - DEBUGMSGTL(("example_data_set", - "Initalizing example dataset table\n")); - - /* - * It's going to be the "working group chairs" table, since I'm - * * sitting at an IETF convention while I'm writing this. - * * - * * column 1 = index = string = WG name - * * column 2 = string = chair #1 - * * column 3 = string = chair #2 (most WGs have 2 chairs now) - */ - - table_set = netsnmp_create_table_data_set("netSnmpIETFWGTable"); - - /* - * allow the creation of new rows via SNMP SETs - */ - table_set->allow_creation = 1; - - /* - * set up what a row "should" look like, starting with the index - */ - netsnmp_table_dataset_add_index(table_set, ASN_OCTET_STR); - - /* - * define what the columns should look like. both are octet strings here - */ - netsnmp_table_set_multi_add_default_row(table_set, - /* - * column 2 = OCTET STRING, - * writable = 1, - * default value = NULL, - * default value len = 0 - */ - 2, ASN_OCTET_STR, 1, NULL, 0, - /* - * similar - */ - 3, ASN_OCTET_STR, 1, NULL, 0, - 0 /* done */ ); - - /* - * register the table - */ - /* - * if we wanted to handle specific data in a specific way, or note - * * when requests came in we could change the NULL below to a valid - * * handler method in which we could over ride the default - * * behaviour of the table_dataset helper - */ - netsnmp_register_table_data_set(netsnmp_create_handler_registration - ("netSnmpIETFWGTable", NULL, - my_oid, OID_LENGTH(my_oid), - HANDLER_CAN_RWRITE), table_set, NULL); - - - /* - * create the a row for the table, and add the data - */ - row = netsnmp_create_table_data_row(); - /* - * set the index to the IETF WG name "snmpv3" - */ - netsnmp_table_row_add_index(row, ASN_OCTET_STR, "snmpv3", - strlen("snmpv3")); - - - /* - * set column 2 to be the WG chair name "Russ Mundy" - */ - netsnmp_set_row_column(row, 2, ASN_OCTET_STR, - "Russ Mundy", strlen("Russ Mundy")); - netsnmp_mark_row_column_writable(row, 2, 1); /* make writable via SETs */ - - /* - * set column 3 to be the WG chair name "David Harrington" - */ - netsnmp_set_row_column(row, 3, ASN_OCTET_STR, "David Harrington", - strlen("David Harrington")); - netsnmp_mark_row_column_writable(row, 3, 1); /* make writable via SETs */ - - /* - * add the row to the table - */ - netsnmp_table_dataset_add_row(table_set, row); - - /* - * add the data, for the second row - */ - row = netsnmp_create_table_data_row(); - netsnmp_table_row_add_index(row, ASN_OCTET_STR, "snmpconf", - strlen("snmpconf")); - netsnmp_set_row_column(row, 2, ASN_OCTET_STR, "David Partain", - strlen("David Partain")); - netsnmp_mark_row_column_writable(row, 2, 1); /* make writable */ - netsnmp_set_row_column(row, 3, ASN_OCTET_STR, "Jon Saperia", - strlen("Jon Saperia")); - netsnmp_mark_row_column_writable(row, 3, 1); /* make writable */ - netsnmp_table_dataset_add_row(table_set, row); - - /* - * Finally, this actually allows the "add_row" token it the - * * snmpd.conf file to add rows to this table. - * * Example snmpd.conf line: - * * add_row netSnmpIETFWGTable eos "Glenn Waters" "Dale Francisco" - */ - netsnmp_register_auto_data_table(table_set, NULL); - - DEBUGMSGTL(("example_data_set", "Done initializing.\n")); -} -#endif /* table example */ - /*! Signal terminates process * Just set exit flag for proper exit in event loop */ @@ -357,8 +139,8 @@ clixon_snmp_fdset_register(clicon_handle h) * @see snmp_terminate */ static int -clixon_snmp_init(clicon_handle h, - int logdst) +clixon_snmp_subagent(clicon_handle h, + int logdst) { int retval = -1; char *sockpath = NULL; @@ -378,13 +160,10 @@ clixon_snmp_init(clicon_handle h, /* XXX: This should be configurable. */ netsnmp_ds_set_string(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_X_SOCKET, sockpath); + /* initialize the agent library */ init_agent(__PROGRAM__); - /* XXX Hardcoded, replace this with generic MIB */ - init_testscalar(); - init_testtable(); - /* example-demon will be used to read example-demon.conf files. */ init_snmp(__PROGRAM__); @@ -397,7 +176,7 @@ clixon_snmp_init(clicon_handle h, goto done; } if (set_signal(SIGPIPE, SIG_IGN, NULL) < 0){ - clicon_err(OE_UNIX, errno, "Setting DIGPIPE signal"); + clicon_err(OE_UNIX, errno, "Setting SIGPIPE signal"); goto done; } /* Workaround for netsnmps API use of fdset:s instead of sockets */ @@ -633,7 +412,10 @@ main(int argc, clicon_session_id_set(h, id); /* Init snmp as subagent */ - if (clixon_snmp_init(h, logdst) < 0) + if (clixon_snmp_subagent(h, logdst) < 0) + goto done; + /* Init mib-translated yangs and register callbacks */ + if (clixon_snmp_mib_yangs(h) < 0) goto done; if (dbg) diff --git a/apps/snmp/snmp_mib_yang.c b/apps/snmp/snmp_mib_yang.c new file mode 100644 index 00000000..30056e4a --- /dev/null +++ b/apps/snmp/snmp_mib_yang.c @@ -0,0 +1,453 @@ +/* + * + ***** 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 ***** + * 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 + +/* net-snmp */ +#include +#include +#include + +/* cligen */ +#include + +/* clicon */ +#include + +#include "snmp_mib_yang.h" + +#define IETF_YANG_SMIV2_NS "urn:ietf:params:xml:ns:yang:ietf-yang-smiv2" + +/* + * 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 acc_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} +}; + +#if 1 /* table example */ +static netsnmp_table_data_set *table_set; + +/* + * https://net-snmp.sourceforge.io/dev/agent/data_set_8c-example.html#_a0 + */ +static void +init_testtable(void) +{ + netsnmp_table_row *row; + + /* + * the OID we want to register our integer at. This should be the + * * OID node for the entire table. In our case this is the + * * netSnmpIETFWGTable oid definition + */ + oid my_oid[] = { 1, 3, 6, 1, 4, 1, 8072, 2, 2, 1 }; + + /* + * a debugging statement. Run the agent with -Dexample_data_set to see + * * the output of this debugging statement. + */ + DEBUGMSGTL(("example_data_set", + "Initalizing example dataset table\n")); + + /* + * It's going to be the "working group chairs" table, since I'm + * * sitting at an IETF convention while I'm writing this. + * * + * * column 1 = index = string = WG name + * * column 2 = string = chair #1 + * * column 3 = string = chair #2 (most WGs have 2 chairs now) + */ + + table_set = netsnmp_create_table_data_set("netSnmpIETFWGTable"); + + /* + * allow the creation of new rows via SNMP SETs + */ + table_set->allow_creation = 1; + + /* + * set up what a row "should" look like, starting with the index + */ + netsnmp_table_dataset_add_index(table_set, ASN_OCTET_STR); + + /* + * define what the columns should look like. both are octet strings here + */ + netsnmp_table_set_multi_add_default_row(table_set, + /* + * column 2 = OCTET STRING, + * writable = 1, + * default value = NULL, + * default value len = 0 + */ + 2, ASN_OCTET_STR, 1, NULL, 0, + /* + * similar + */ + 3, ASN_OCTET_STR, 1, NULL, 0, + 0 /* done */ ); + + /* + * register the table + */ + /* + * if we wanted to handle specific data in a specific way, or note + * * when requests came in we could change the NULL below to a valid + * * handler method in which we could over ride the default + * * behaviour of the table_dataset helper + */ + netsnmp_register_table_data_set(netsnmp_create_handler_registration + ("netSnmpIETFWGTable", NULL, + my_oid, OID_LENGTH(my_oid), + HANDLER_CAN_RWRITE), table_set, NULL); + + + /* + * create the a row for the table, and add the data + */ + row = netsnmp_create_table_data_row(); + /* + * set the index to the IETF WG name "snmpv3" + */ + netsnmp_table_row_add_index(row, ASN_OCTET_STR, "snmpv3", + strlen("snmpv3")); + + + /* + * set column 2 to be the WG chair name "Russ Mundy" + */ + netsnmp_set_row_column(row, 2, ASN_OCTET_STR, + "Russ Mundy", strlen("Russ Mundy")); + netsnmp_mark_row_column_writable(row, 2, 1); /* make writable via SETs */ + + /* + * set column 3 to be the WG chair name "David Harrington" + */ + netsnmp_set_row_column(row, 3, ASN_OCTET_STR, "David Harrington", + strlen("David Harrington")); + netsnmp_mark_row_column_writable(row, 3, 1); /* make writable via SETs */ + + /* + * add the row to the table + */ + netsnmp_table_dataset_add_row(table_set, row); + + /* + * add the data, for the second row + */ + row = netsnmp_create_table_data_row(); + netsnmp_table_row_add_index(row, ASN_OCTET_STR, "snmpconf", + strlen("snmpconf")); + netsnmp_set_row_column(row, 2, ASN_OCTET_STR, "David Partain", + strlen("David Partain")); + netsnmp_mark_row_column_writable(row, 2, 1); /* make writable */ + netsnmp_set_row_column(row, 3, ASN_OCTET_STR, "Jon Saperia", + strlen("Jon Saperia")); + netsnmp_mark_row_column_writable(row, 3, 1); /* make writable */ + netsnmp_table_dataset_add_row(table_set, row); + + /* + * Finally, this actually allows the "add_row" token it the + * * snmpd.conf file to add rows to this table. + * * Example snmpd.conf line: + * * add_row netSnmpIETFWGTable eos "Glenn Waters" "Dale Francisco" + */ + netsnmp_register_auto_data_table(table_set, NULL); + + DEBUGMSGTL(("example_data_set", "Done initializing.\n")); +} +#endif /* table example */ + +#if 1 /* scalar example */ + + +#define TESTHANDLER_SET_NAME "my_test" +int +my_test_instance_handler(netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests) +{ + + static int accesses = 42; + u_long *accesses_cache = NULL; + // clicon_handle h = (clicon_handle)handler->myvoid; + + clicon_debug(1, "%s", __FUNCTION__); + clicon_debug(1, "%s %p", __FUNCTION__, reginfo); + + switch (reqinfo->mode) { + case MODE_GET: + snmp_set_var_typed_value(requests->requestvb, ASN_INTEGER, + (u_char *) & accesses, sizeof(accesses)); + break; + case MODE_SET_RESERVE1: + if (requests->requestvb->type != ASN_INTEGER) + netsnmp_set_request_error(reqinfo, requests, + SNMP_ERR_WRONGTYPE); + break; + + case MODE_SET_RESERVE2: + /* + * store old info for undo later + */ + accesses_cache = netsnmp_memdup(&accesses, sizeof(accesses)); + if (accesses_cache == NULL) { + netsnmp_set_request_error(reqinfo, requests, + SNMP_ERR_RESOURCEUNAVAILABLE); + return SNMP_ERR_NOERROR; + } + netsnmp_request_add_list_data(requests, + netsnmp_create_data_list + (TESTHANDLER_SET_NAME, + accesses_cache, free)); + break; + + case MODE_SET_ACTION: + /* + * update current + */ + accesses = *(requests->requestvb->val.integer); + DEBUGMSGTL(("testhandler", "updated accesses -> %d\n", accesses)); + break; + + case MODE_SET_UNDO: + accesses = + *((u_long *) netsnmp_request_get_list_data(requests, + TESTHANDLER_SET_NAME)); + break; + + case MODE_SET_COMMIT: + case MODE_SET_FREE: + /* + * nothing to do + */ + break; + } + + return SNMP_ERR_NOERROR; +} +#endif + +/*! Parse smiv2 extensions for YANG leaf + * Typical leaf: + * smiv2:oid "1.3.6.1.4.1.8072.2.1.1"; + * smiv2:max-access "read-write"; + * smiv2:defval "42"; (optional) + */ +static int +mib_yang_leaf(clicon_handle h, + yang_stmt *ys) +{ + int retval = -1; + netsnmp_handler_registration *nh = NULL; + netsnmp_mib_handler *handler; + int ret; + char *oidstr = NULL; + char *modes_str = NULL; + oid oid1[MAX_OID_LEN] = {0,}; + size_t sz1 = MAX_OID_LEN; + int modes; + yang_stmt *yrestype; /* resolved type */ + // char *restype; /* resolved type */ + char *origtype=NULL; /* original type */ + char *name; + + + clicon_debug(1, "%s %s", __FUNCTION__, oidstr); + if (yang_extension_value(ys, "oid", IETF_YANG_SMIV2_NS, NULL, &oidstr) < 0) + goto done; + if (oidstr == NULL) + goto ok; + if (snmp_parse_oid(oidstr, oid1, &sz1) == NULL){ + clicon_err(OE_SNMP, 0, "snmp_parse_oid"); + goto done; + } + if (yang_extension_value(ys, "max-access", IETF_YANG_SMIV2_NS, NULL, &modes_str) < 0) + goto done; + + /* read-only, read-write, not-accessible, oaccessible-for-notify + */ + if (modes_str == NULL) + goto ok; + modes = clicon_str2int(acc_map, modes_str); + + /* Default value, XXX How is this different from yang defaults? + */ + // if (yang_extension_value(ys, "defval", IETF_YANG_SMIV2_NS, NULL, &modes_str) < 0) + // goto done; + + /* get yang type of leaf */ + if (yang_type_get(ys, &origtype, &yrestype, NULL, NULL, NULL, NULL, NULL) < 0) + goto done; + // restype = yrestype?yang_argument_get(yrestype):NULL; + name = yang_argument_get(ys); + if ((handler = netsnmp_create_handler(name, my_test_instance_handler)) == NULL){ + clicon_err(OE_SNMP, errno, "netsnmp_create_handler"); + goto done; + } + // handler->myvoid = h; + handler->myvoid =(void*)99; + if ((nh = netsnmp_handler_registration_create(name, + handler, + oid1, + sz1, + modes)) == NULL){ + clicon_err(OE_SNMP, errno, "netsnmp_handler_registration_create"); + netsnmp_handler_free(handler); + goto done; + } + clicon_debug(1, "%s %p", __FUNCTION__, nh); + if ((ret = netsnmp_register_instance(nh)) < 0){ + /* XXX Failures are MIB_REGISTRATION_FAILED and MIB_DUPLICATE_REGISTRATION. */ + clicon_err(OE_SNMP, ret, "netsnmp_register_instance"); + goto done; + } + ok: + retval = 0; + done: + return retval; +} + +/*! Check smiv2 extensions for + * + * Called for each node in a mib-yang + * Algorithm is to find smiv2:oid, then its associated parent type (eg leaf, container, list) + * and then register callbacks. + * @param[in] ys Yang node, of type unknown + * @param[in] arg Argument, in effect clicon_handle + * @retval -1 Error + * @retval 0 OK + * @see yang_extension_value + * @see ys_populate_unknown + */ +static int +mib_yang_extension(yang_stmt *ys, + void *arg) +{ + int retval = -1; + clicon_handle h = (clicon_handle)arg; + + switch(yang_keyword_get(ys)){ + case Y_LEAF: + if (mib_yang_leaf(h, ys) < 0) + goto done; + break; + case Y_CONTAINER: // XXX + break; + case Y_LIST: // XXX + break; + default: + break; + } + retval = 0; +done: + return retval; +} + +int +clixon_snmp_mib_yangs(clicon_handle h) +{ + int retval = -1; + char *modname; + cxobj *x; + yang_stmt *yspec; + yang_stmt *ymod; + + /* XXX Hardcoded, replace this with generic MIB */ + init_testtable(); // XXX + + if ((yspec = clicon_dbspec_yang(h)) == NULL){ + clicon_err(OE_FATAL, 0, "No DB_SPEC"); + goto done; + } + 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; + if ((modname = xml_body(x)) == NULL) + continue; + clicon_debug(1, "%s %s: \"%s\"", __FUNCTION__, xml_name(x), modname); + /* Note, here we assume the Yang is loaded by some other mechanism and + * error if it not found. + * Alternatively, that YANG could be loaded. + * Problem is, if clixon_snmp has not loaded it, has backend done it? + * What happens if backend has not loaded it? + */ + if ((ymod = yang_find(yspec, Y_MODULE, modname)) == NULL){ + clicon_err(OE_YANG, 0, "Mib-translated-yang %s not loaded", modname); + goto done; + } + /* Recursively traverse the mib-yang to find extensions */ + if (yang_apply(ymod, -1, mib_yang_extension, 1, h) < 0) + goto done; + } + retval = 0; + done: + return retval; +} diff --git a/apps/snmp/snmp_mib_yang.h b/apps/snmp/snmp_mib_yang.h new file mode 100644 index 00000000..6befc7f0 --- /dev/null +++ b/apps/snmp/snmp_mib_yang.h @@ -0,0 +1,53 @@ +/* + * + ***** 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 ***** + + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _SNMP_MIB_YANG_H_ +#define _SNMP_MIB_YANG_H_ + +/* + * Prototypes + */ +int clixon_snmp_mib_yangs(clicon_handle h); + +#endif /* _SNMP_MIB_YANG_H_ */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index 89df5fa8..7276599f 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -133,7 +133,7 @@ static const map_str2int yang_regexp_map[] = { * @param[in] dbglevel Debug level * @retval 0 OK * @retval -1 Error - * @note CLICON_FEATURE and CLICON_YANG_DIR are treated specially since they are lists + * @note CLICON_FEATURE, CLICON_YANG_DIR and CLICON_SNMP_MIB are treated specially since they are lists */ int clicon_option_dump(clicon_handle h, @@ -161,7 +161,7 @@ clicon_option_dump(clicon_handle h, else clicon_debug(dbglevel, "%s = NULL", keys[i]); } - /* Next print CLICON_FEATURE and CLICON_YANG_DIR from config tree + /* Next print CLICON_FEATURE, CLICON_YANG_DIR and CLICON_SNMP_DIR from config tree * Since they are lists they are placed in the config tree. */ x = NULL; @@ -176,6 +176,12 @@ clicon_option_dump(clicon_handle h, continue; clicon_debug(dbglevel, "%s =\t \"%s\"", 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; + clicon_debug(dbglevel, "%s =\t \"%s\"", xml_name(x), xml_body(x)); + } retval = 0; done: if (keys) @@ -333,7 +339,8 @@ parse_configfile(clicon_handle h, continue; /* List options for configure options that are lists or leaf-lists: append to main */ if (strcmp(name,"CLICON_FEATURE")==0 || - strcmp(name,"CLICON_YANG_DIR")==0){ + strcmp(name,"CLICON_YANG_DIR")==0 || + strcmp(name,"CLICON_SNMP_MIB")==0){ if (xml_addsub(xt, xec) < 0) goto done; continue; @@ -381,6 +388,8 @@ parse_configfile(clicon_handle h, continue; if (strcmp(name,"CLICON_YANG_DIR")==0) continue; + if (strcmp(name,"CLICON_SNMP_MIB")==0) + continue; if (clicon_hash_add(copt, name, body, @@ -424,7 +433,8 @@ clicon_option_add(clicon_handle h, cxobj *x; if (strcmp(name, "CLICON_FEATURE")==0 || - strcmp(name, "CLICON_YANG_DIR")==0){ + strcmp(name, "CLICON_YANG_DIR")==0 || + strcmp(name, "CLICON_SNMP_MIB")==0){ if ((x = clicon_conf_xml(h)) == NULL){ clicon_err(OE_UNIX, ENOENT, "option %s not found (clicon_conf_xml_set has not been called?)", name); goto done; diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index e8f6d5c9..8c926fc1 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -1364,7 +1364,7 @@ yang_find_prefix_by_namespace(yang_stmt *ys, goto done; } -/*! Given a yang statement and local prefi valid in module , find namespace +/*! Given a yang statement and local prefix valid in module, find namespace * * @param[in] ys Yang statement in module tree (or module itself) * @param[in] prefix Local prefix to access module with (direct pointer) @@ -3661,7 +3661,7 @@ yang_anydata_add(yang_stmt *yp, /*! Find extension argument and return if extension exists and its argument value * - * @param[in] ys Yang statement where unknown statement may occur referncing to extension + * @param[in] ys Yang statement where unknown statement may occur referencing to extension * @param[in] name Name of the extension * @param[in] ns The namespace of the module where the extension is defined * @param[out] exist The extension exists. @@ -3678,7 +3678,7 @@ yang_anydata_add(yang_stmt *yp, * // use extension value * } * @endcode - * @see ys_populate_unknown Called when parsing YANGo + * @see ys_populate_unknown Called when parsing YANG */ int yang_extension_value(yang_stmt *ys, diff --git a/test/lib.sh b/test/lib.sh index 0e161f6b..10cd8e7c 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -440,7 +440,7 @@ function chunked_framing() function start_snmp(){ cfg=$1 - $clixon_snmp -f $cfg -D $DBG -l s & + $clixon_snmp -f $cfg -D $DBG & if [ $? -ne 0 ]; then err @@ -456,7 +456,7 @@ function stop_snmp(){ if [ $valgrindtest != 0 ]; then kill `ps aux | grep [v]algrind | awk '{print $2}' | tail -n1` else - killall clixon_snmp + killall -q clixon_snmp fi } diff --git a/test/test_snmp.sh b/test/test_snmp.sh index 84990629..2c14e98f 100755 --- a/test/test_snmp.sh +++ b/test/test_snmp.sh @@ -31,6 +31,7 @@ cat < $cfg /var/tmp/$APPNAME.pidfile $dir unix:$SOCK + NET-SNMP-EXAMPLES-MIB EOF @@ -61,7 +62,7 @@ function testinit(){ # Kill old clixon_snmp, if any new "Terminating any old clixon_snmp processes" - sudo killall clixon_snmp + sudo killall -q clixon_snmp new "Starting clixon_snmp" start_snmp $cfg & @@ -71,7 +72,7 @@ function testinit(){ } function testexit(){ - sudo killall clixon_snmp + stop_snmp } new "SNMP tests" diff --git a/test/test_snmp_table.sh b/test/test_snmp_table.sh index 34acfc5f..dc8b2757 100755 --- a/test/test_snmp_table.sh +++ b/test/test_snmp_table.sh @@ -41,6 +41,7 @@ cat < $cfg /var/tmp/$APPNAME.pidfile $dir unix:$SOCK + NET-SNMP-EXAMPLES-MIB EOF @@ -71,7 +72,7 @@ function testinit(){ # Kill old clixon_snmp, if any new "Terminating any old clixon_snmp processes" - sudo killall clixon_snmp + sudo killall -q clixon_snmp new "Starting clixon_snmp" start_snmp $cfg & @@ -81,7 +82,7 @@ function testinit(){ } function testexit(){ - sudo killall clixon_snmp + stop_snmp } new "SNMP table tests" diff --git a/yang/clixon/clixon-config@2022-03-21.yang b/yang/clixon/clixon-config@2022-03-21.yang index 7becc7e2..3a6e3d91 100644 --- a/yang/clixon/clixon-config@2022-03-21.yang +++ b/yang/clixon/clixon-config@2022-03-21.yang @@ -1132,6 +1132,16 @@ module clixon-config { 0 means no limit"; } + leaf-list CLICON_SNMP_MIB { + description + "Names of MIBs that are used by clixon_snmp. + For each MIB M, a YANG file M.yang is expected to be found. + If not found, an error is genereated. + The YANG file M.yang is typically generated from the source MIB but can also + be handcrafted. An example of such a script is scripts/mib_to_yang.sh. + A list of these options should be in the configuration."; + type string; + } leaf CLICON_SNMP_AGENT_SOCK { type string; default "unix:/tmp/clixon_snmp.sock";