diff --git a/apps/snmp/snmp_handler.c b/apps/snmp/snmp_handler.c index 1809a3fd..094f800c 100644 --- a/apps/snmp/snmp_handler.c +++ b/apps/snmp/snmp_handler.c @@ -853,13 +853,13 @@ snmp_table_get(clixon_handle h, yang_stmt *ys; yang_stmt *yk; char *xpath = NULL; - cvec *cvk_orig; cvec *cvk_val = NULL; int i; cg_var *cv; char *defaultval = NULL; int inext; int ret; + clixon_debug(1, "%s", __FUNCTION__); /* Get yang of leaf from first part of OID */ inext = 0; @@ -885,18 +885,14 @@ snmp_table_get(clixon_handle h, */ if (yang_extension_value_opt(ys, "smiv2:defval", 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){ - clixon_err(OE_YANG, 0, "No keys"); - goto done; - } - if ((cvk_val = cvec_dup(cvk_orig)) == NULL){ - clixon_err(OE_UNIX, errno, "cvec_dup"); + if (clixon_snmp_ylist_keys(yt, &cvk_val) < 0) { + clixon_err(OE_XML, errno, "clixon_snmp_ylist_keys"); goto done; } + /* read through keys and create cvk */ oidilen = oidslen-(oidtlen+1); oidi = oids+oidtlen+1; @@ -971,7 +967,6 @@ snmp_table_set(clixon_handle h, yang_stmt *yk; yang_stmt *yrestype = NULL; char *xpath = NULL; - cvec *cvk_orig; cvec *cvk_val = NULL; int i; cg_var *cv; @@ -1054,12 +1049,9 @@ snmp_table_set(clixon_handle h, /* Create xpath with right keys from later part of OID * Inverse of snmp_str2oid */ - if ((cvk_orig = yang_cvec_get(yt)) == NULL){ - clixon_err(OE_YANG, 0, "No keys"); - goto done; - } - if ((cvk_val = cvec_dup(cvk_orig)) == NULL){ - clixon_err(OE_UNIX, errno, "cvec_dup"); + + if (clixon_snmp_ylist_keys(yt, &cvk_val) < 0) { + clixon_err(OE_XML, errno, "clixon_snmp_ylist_keys"); goto done; } /* read through keys and create cvk */ @@ -1258,7 +1250,7 @@ snmp_table_getnext(clixon_handle h, yang_stmt *ycol; yang_stmt *ys; int ret; - cvec *cvk_name; + cvec *cvk_name = NULL; oid oidc[MAX_OID_LEN] = {0,}; /* Table / list oid */ size_t oidclen = MAX_OID_LEN; oid oidk[MAX_OID_LEN] = {0,}; /* Key oid */ @@ -1285,7 +1277,7 @@ snmp_table_getnext(clixon_handle h, goto done; if ((xtable = xpath_first(xt, nsc, "%s", xpath)) != NULL) { /* Make a clone of key-list, but replace names with values */ - if ((cvk_name = yang_cvec_get(ylist)) == NULL){ + if (clixon_snmp_ylist_keys(ylist, &cvk_name) < 0){ clixon_err(OE_YANG, 0, "No keys"); goto done; } @@ -1333,6 +1325,10 @@ snmp_table_getnext(clixon_handle h, } retval = found; done: + if (cvk_name) + cvec_free(cvk_name); + if (cb) + cbuf_free(cb); if (xpath) free(xpath); if (cb) diff --git a/apps/snmp/snmp_lib.c b/apps/snmp/snmp_lib.c index 9ddbf27f..dc358f43 100644 --- a/apps/snmp/snmp_lib.c +++ b/apps/snmp/snmp_lib.c @@ -1069,7 +1069,7 @@ snmp_yang2xpath_cb(yang_stmt *ys, } switch (yang_keyword_get(ys)){ case Y_LIST: - if ((cvk = yang_cvec_get(ys)) == NULL) /* Use Y_LIST cache, see ys_populate_list() */ + if (clixon_snmp_ylist_keys(ys, &cvk) < 0) break; /* Iterate over individual keys */ assert(keyvec && cvec_len(cvk) == cvec_len(keyvec)); @@ -1081,6 +1081,7 @@ snmp_yang2xpath_cb(yang_stmt *ys, cv_string_get(cvec_i(cvk, i)), cv_string_get(cvec_i(keyvec, i))); } + cvec_free(cvk); break; case Y_LEAF_LIST: assert(0); // NYI @@ -1428,3 +1429,41 @@ clixon_snmp_api_oid_find(oid *oid0, // done: return retval; } + +/*! Get an SNMP key list from a ylist. + * @param[in] ylist ylist + * @param[out] ylist_keys Vector of key names (strings). + * @retval -1 Error + * @retval 0 OK + * Note: ylist_keys need to be freed. + */ +int clixon_snmp_ylist_keys(yang_stmt *ylist, cvec **ylist_keys) { + int retval = -1; + cvec *cvk = NULL; + char *smi_key_name = NULL; + if (yang_extension_value(ylist, "table-key", CLIXON_SNMP_NS, NULL, &smi_key_name) < 0){ + clixon_err(OE_XML, errno, "could not lookup table-key"); + goto done; + } + + if (!smi_key_name){ + /* Keys, go through keys */ + if ((cvk = yang_cvec_get(ylist)) == NULL){ + clixon_err(OE_YANG, 0, "No keys"); + goto done; + } + + cvk = cvec_dup(cvk); + } else { + cvk = cvec_new(0); + if (cvec_add_string(cvk, NULL, smi_key_name) < 0){ + clixon_err(OE_UNIX, 0, "Could not add key"); + goto done; + } + } + + *ylist_keys = cvk; + retval = 0; +done: + return retval; +} diff --git a/apps/snmp/snmp_lib.h b/apps/snmp/snmp_lib.h index b4154f3b..5ed87636 100644 --- a/apps/snmp/snmp_lib.h +++ b/apps/snmp/snmp_lib.h @@ -48,6 +48,7 @@ extern "C" { #define CLIXON_ERR_SNMP_MIB 0x1000 #define IETF_YANG_SMIV2_NS "urn:ietf:params:xml:ns:yang:ietf-yang-smiv2" +#define CLIXON_SNMP_NS "http://clicon.org/snmp" /* Special case/extended Clixon ASN1 types * Set in type_yang2asn1() if extended is true @@ -108,6 +109,7 @@ 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); +int clixon_snmp_ylist_keys(yang_stmt *ylist, cvec **ylist_keys); /*========== libnetsnmp-specific code =============== */ int clixon_snmp_api_agent_check(void); diff --git a/apps/snmp/snmp_register.c b/apps/snmp/snmp_register.c index 98fb9141..8b3e3438 100644 --- a/apps/snmp/snmp_register.c +++ b/apps/snmp/snmp_register.c @@ -286,11 +286,12 @@ mibyang_table_register(clixon_handle h, clixon_err(OE_UNIX, errno, "SNMP_MALLOC_TYPEDEF"); goto done; } - /* Keys, go through keys */ - if ((cvk = yang_cvec_get(ylist)) == NULL){ - clixon_err(OE_YANG, 0, "No keys"); + + if (clixon_snmp_ylist_keys(ylist, &cvk) < 0) { + clixon_err(OE_XML, errno, "clixon_snmp_ylist_keys"); goto done; } + cvi = NULL; /* Iterate over individual keys */ while ((cvi = cvec_each(cvk, cvi)) != NULL) { @@ -332,6 +333,9 @@ mibyang_table_register(clixon_handle h, ok: retval = 0; done: + if (cvk) + cvec_free(cvk); + return retval; } @@ -497,7 +501,8 @@ mibyang_table_poll(clixon_handle h, } if ((xtable = xpath_first(xt, nsc, "%s", xpath)) != NULL) { /* Make a clone of key-list, but replace names with values */ - if ((cvk_name = yang_cvec_get(ylist)) == NULL){ + + if (clixon_snmp_ylist_keys(ylist, &cvk_name) < 0){ clixon_err(OE_YANG, 0, "No keys"); goto done; } @@ -518,6 +523,8 @@ mibyang_table_poll(clixon_handle h, } retval = 0; done: + if (cvk_name) + cvec_free(cvk_name); if (xpath) free(xpath); if (cvk_val) diff --git a/test/test_snmp_custom.sh b/test/test_snmp_custom.sh new file mode 100755 index 00000000..a7ad841f --- /dev/null +++ b/test/test_snmp_custom.sh @@ -0,0 +1,206 @@ +#!/usr/bin/env bash +# SNMP test for custom clixon extensions. + +# Magic line must be first in script (see README.md) +s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi + +# Re-use main example backend state callbacks +APPNAME=example + +if [ ${ENABLE_NETSNMP} != "yes" ]; then + echo "Skipping test, Net-SNMP support not enabled." + rm -rf $dir + if [ "$s" = $0 ]; then exit 0; else return 0; fi +fi + +cfg=$dir/conf_startup.xml +fyang=$dir/clixon-example.yang +fstate=$dir/state.xml + +# AgentX unix socket +SOCK=/var/run/snmp.sock + +# Relies on example_backend.so for $fstate file handling + +cat < $cfg + + $cfg + ${YANG_INSTALLDIR} + ${YANG_STANDARD_DIR} + ${MIB_GENERATED_YANG_DIR} + $fyang + $dir/$APPNAME.sock + /usr/local/lib/$APPNAME/backend + /var/tmp/$APPNAME.pidfile + $dir + unix:$SOCK + clixon-example + true + +EOF + +cat < $fyang +module clixon-example{ + yang-version 1.1; + namespace "urn:example:clixon"; + prefix ex; + import clixon-snmp { + prefix cx-snmp; + } + import ietf-yang-smiv2 { + prefix smiv2; + } + /* Generic config data */ + container table{ + smiv2:oid "1.3.6.1.2.1.47.1.1.1"; + list parameter{ + smiv2:oid "1.3.6.1.2.1.47.1.1.1.1"; + key name; + cx-snmp:table-key "value"; + leaf name{ + type int32; + smiv2:oid "1.3.6.1.2.1.47.1.1.1.1.1"; + } + leaf value{ + type int32; + smiv2:oid "1.3.6.1.2.1.47.1.1.1.1.2"; + } + leaf hidden{ + type string; + } + leaf stat{ + description "Inline state data for example application"; + config false; + type int32; + smiv2:oid "1.3.6.1.2.1.47.1.1.1.1.3"; + } + } + } +} +EOF + +# This is state data written to file that backend reads from (on request) +# integer and string have values, sleeper does not and uses default (=1) + +cat < $fstate + + + 1 + 2 + 3 + 4 + + + 11 + 12 + 13 + 14 + +
+EOF + +function testinit(){ + new "test params: -s init -f $cfg -- -sS $fstate" + if [ $BE -ne 0 ]; then + # Kill old backend and start a new one + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err "Failed to start backend" + fi + + sudo pkill -f clixon_backend + + new "Starting backend" + start_backend -s init -f $cfg -- -sS $fstate + fi + + new "wait backend" + wait_backend + + if [ $SN -ne 0 ]; then + # Kill old clixon_snmp, if any + new "Terminating any old clixon_snmp processes" + sudo killall -q clixon_snmp + + new "Starting clixon_snmp" + start_snmp $cfg & + fi + + new "wait snmp" + wait_snmp +} + +function testexit(){ + stop_snmp + + if [ $BE -ne 0 ]; then + new "Kill backend" + # Check if premature kill + pid=$(pgrep -u root -f clixon_backend) + if [ -z "$pid" ]; then + err "backend already dead" + fi + # kill backend + stop_backend -f $cfg + fi +} + +ENTITY_OID=".1.3.6.1.2.1.47.1.1.1" + +# name, value=2 +OID1="${ENTITY_OID}.1.1.2" +# name, value=12 +OID2="${ENTITY_OID}.1.1.12" +# value, value=2 +OID3="${ENTITY_OID}.1.2.2" +# value, value=12 +OID4="${ENTITY_OID}.1.2.12" +# stat, value=2 +OIDX="${ENTITY_OID}.1.3.2" +# stat, value=12 +OIDY="${ENTITY_OID}.1.3.12" + + +new "SNMP system tests" +testinit + +new "Get index, $OID1" +validate_oid $OID1 $OID1 "INTEGER" "1" + +new "Get next $OID1" +validate_oid $OID1 $OID2 "INTEGER" "11" + +new "Get index, $OID2" +validate_oid $OID2 $OID2 "INTEGER" "11" +new "Get next $OID2" +validate_oid $OID2 $OID3 "INTEGER" "2" + +new "Get index, $OID3" +validate_oid $OID3 $OID3 "INTEGER" "2" + +new "Get next $OID4" +validate_oid $OID3 $OID4 "INTEGER" "12" + +new "Get index, $OID4" +validate_oid $OID4 $OID4 "INTEGER" "12" + +new "Get next $OID4" +validate_oid $OID4 $OIDX "INTEGER" "4" + +new "Get index, $OIDX" +validate_oid $OIDX $OIDX "INTEGER" "4" + +new "Get next $OIDX" +validate_oid $OIDX $OIDY "INTEGER" "14" + +new "Get index, $OIDY" +validate_oid $OIDY $OIDY "INTEGER" "14" + +new "Cleaning up" +testexit + +rm -rf $dir + +new "endtest" +endtest diff --git a/yang/clixon/Makefile.in b/yang/clixon/Makefile.in index ad588ff7..49bdd772 100644 --- a/yang/clixon/Makefile.in +++ b/yang/clixon/Makefile.in @@ -47,6 +47,7 @@ YANGSPECS += clixon-lib@2024-11-01.yang # 7.3 YANGSPECS += clixon-rfc5277@2008-07-01.yang YANGSPECS += clixon-xml-changelog@2019-03-21.yang YANGSPECS += clixon-restconf@2022-08-01.yang # 5.9 +YANGSPECS += clixon-snmp@2023-01-17.yang YANGSPECS += clixon-autocli@2024-08-01.yang # 7.2 all: diff --git a/yang/clixon/clixon-snmp@2023-01-17.yang b/yang/clixon/clixon-snmp@2023-01-17.yang new file mode 100644 index 00000000..c491300c --- /dev/null +++ b/yang/clixon/clixon-snmp@2023-01-17.yang @@ -0,0 +1,55 @@ +module clixon-snmp{ + yang-version 1.1; + namespace "http://clicon.org/snmp"; + prefix cx-snmp; + + organization + "Clicon / Clixon"; + + contact + "Olof Hagsand "; + + description + "Clixon SNMP-specific extensions. + Design inspired by ietf-netconf-acm.yang + + ***** BEGIN LICENSE BLOCK ***** + Copyright (C) 2020-2021 Olof Hagsand and Rubicon Communications, LLC(Netgate) + + 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 *****"; + + + revision 2023-01-17 { + description + "Initial version"; + } + extension table-key { + argument key-name; + + description + "Selects an alternative key for an SNMP table represented by the YANG list."; + } +}