From ba45d6d2a13220e7c1b1d27ada564c10ae97c576 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Wed, 23 Jun 2021 10:50:47 +0200 Subject: [PATCH 01/34] Fixed memory leak in new candidate_validate function Added error returns for clixon-lib process rpc empty returns --- apps/backend/backend_commit.c | 15 +++-- apps/backend/backend_plugin_restconf.c | 6 +- lib/src/clixon_proc.c | 81 ++++++++++++++------------ 3 files changed, 57 insertions(+), 45 deletions(-) diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index 1bda0f73..03e318cf 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -612,20 +612,27 @@ candidate_validate(clicon_handle h, * use clicon_err. */ if (xret && clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 0) goto done; - plugin_transaction_abort_all(h, td); if (!cbuf_len(cbret) && netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) goto done; goto fail; } if (xmldb_get0_clear(h, td->td_src) < 0 || - xmldb_get0_clear(h, td->td_target) < 0){ - plugin_transaction_abort_all(h, td); + xmldb_get0_clear(h, td->td_target) < 0) goto done; - } + plugin_transaction_end_all(h, td); retval = 1; done: + if (xret) + xml_free(xret); + if (td){ + if (retval < 1) + plugin_transaction_abort_all(h, td); + xmldb_get0_free(h, &td->td_target); + xmldb_get0_free(h, &td->td_src); + transaction_free(td); + } return retval; fail: retval = 0; diff --git a/apps/backend/backend_plugin_restconf.c b/apps/backend/backend_plugin_restconf.c index 084d6c9c..6b82417d 100644 --- a/apps/backend/backend_plugin_restconf.c +++ b/apps/backend/backend_plugin_restconf.c @@ -270,11 +270,11 @@ restconf_pseudo_process_control(clicon_handle h) * see restconf_pseudo_set_log which sets flag when process starts */ argv[i++] = "-D"; - argv[i++] = strdup("0"); + argv[i++] = "0"; argv[i++] = "-l"; - argv[i++] = strdup("s"); /* There is also log-destination in clixon-restconf.yang */ + argv[i++] = "s"; /* There is also log-destination in clixon-restconf.yang */ argv[i++] = "-R"; - argv[i++] = strdup(""); + argv[i++] = ""; argv[i++] = NULL; assert(i==nr); if (clixon_process_register(h, RESTCONF_PROCESS, diff --git a/lib/src/clixon_proc.c b/lib/src/clixon_proc.c index 29dd9320..b96fc877 100644 --- a/lib/src/clixon_proc.c +++ b/lib/src/clixon_proc.c @@ -682,48 +682,53 @@ clixon_process_status(clicon_handle h, int run; int i; char timestr[28]; + int match = 0; - if (_proc_entry_list == NULL) - goto ok; - pe = _proc_entry_list; - do { - if (strcmp(pe->pe_name, name) == 0){ - /* Check if running */ - run = 0; - if (pe->pe_pid && proc_op_run(pe->pe_pid, &run) < 0) - goto done; - cprintf(cbret, "%s", - NETCONF_BASE_NAMESPACE, CLIXON_LIB_NS, run?"true":"false"); - if (pe->pe_description) - cprintf(cbret, "%s", CLIXON_LIB_NS, pe->pe_description); - cprintf(cbret, "", CLIXON_LIB_NS); - /* the command may include any data, including XML (such as restconf -R command) and - therefore needs CDATA encoding */ - cprintf(cbret, "pe_argc-1; i++){ - if (i) - cprintf(cbret, " "); - cprintf(cbret, "%s", pe->pe_argv[i]); - } - cprintf(cbret, "]]>"); - cprintf(cbret, ""); - cprintf(cbret, "%s", CLIXON_LIB_NS, - clicon_int2str(proc_state_map, pe->pe_state)); - if (timerisset(&pe->pe_starttime)){ - if (time2str(pe->pe_starttime, timestr, sizeof(timestr)) < 0){ - clicon_err(OE_UNIX, errno, "time2str"); + if (_proc_entry_list != NULL){ + pe = _proc_entry_list; + do { + if (strcmp(pe->pe_name, name) == 0){ + /* Check if running */ + run = 0; + if (pe->pe_pid && proc_op_run(pe->pe_pid, &run) < 0) goto done; + cprintf(cbret, "%s", + NETCONF_BASE_NAMESPACE, CLIXON_LIB_NS, run?"true":"false"); + if (pe->pe_description) + cprintf(cbret, "%s", CLIXON_LIB_NS, pe->pe_description); + cprintf(cbret, "", CLIXON_LIB_NS); + /* the command may include any data, including XML (such as restconf -R command) and + therefore needs CDATA encoding */ + cprintf(cbret, "pe_argc-1; i++){ + if (i) + cprintf(cbret, " "); + cprintf(cbret, "%s", pe->pe_argv[i]); } - cprintf(cbret, "%s", CLIXON_LIB_NS, timestr); + cprintf(cbret, "]]>"); + cprintf(cbret, ""); + cprintf(cbret, "%s", CLIXON_LIB_NS, + clicon_int2str(proc_state_map, pe->pe_state)); + if (timerisset(&pe->pe_starttime)){ + if (time2str(pe->pe_starttime, timestr, sizeof(timestr)) < 0){ + clicon_err(OE_UNIX, errno, "time2str"); + goto done; + } + cprintf(cbret, "%s", CLIXON_LIB_NS, timestr); + } + if (pe->pe_pid) + cprintf(cbret, "%u", CLIXON_LIB_NS, pe->pe_pid); + cprintf(cbret, ""); + match++; + break; /* hit break here */ } - if (pe->pe_pid) - cprintf(cbret, "%u", CLIXON_LIB_NS, pe->pe_pid); - cprintf(cbret, ""); - break; /* hit break here */ - } - pe = NEXTQ(process_entry_t *, pe); - } while (pe != _proc_entry_list); - ok: + pe = NEXTQ(process_entry_t *, pe); + } while (pe != _proc_entry_list); + } + if (!match){ /* No match, return error */ + if (netconf_unknown_element(cbret, "application", (char*)name, "Process service is not known") < 0) + goto done; + } retval = 0; done: clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); From d6c175e76ea9bf0c488746e9473c1458912be0f4 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Wed, 23 Jun 2021 10:52:19 +0200 Subject: [PATCH 02/34] - Improved debugs for native restconf config reads - Docker: restconf native added nghttp2 --- apps/restconf/restconf_main_native.c | 4 +++- docker/main/Dockerfile.native | 17 +++++++++++------ example/main/example.xml | 2 +- lib/src/clixon_datastore.c | 2 +- test/test_type.sh | 2 +- 5 files changed, 17 insertions(+), 10 deletions(-) diff --git a/apps/restconf/restconf_main_native.c b/apps/restconf/restconf_main_native.c index 61e27897..acc889d0 100644 --- a/apps/restconf/restconf_main_native.c +++ b/apps/restconf/restconf_main_native.c @@ -1733,7 +1733,7 @@ restconf_clixon_init(clicon_handle h, if (clicon_nsctx_global_set(h, nsctx_global) < 0) goto done; if (inline_config != NULL && strlen(inline_config)){ - clicon_debug(1, "%s using restconf inline config", __FUNCTION__); + clicon_debug(1, "%s reading from inline config", __FUNCTION__); if ((ret = clixon_xml_parse_string(inline_config, YB_MODULE, yspec, &xrestconf, &xerr)) < 0) goto done; if (ret == 0){ @@ -1755,6 +1755,7 @@ restconf_clixon_init(clicon_handle h, goto done; } else if (clicon_option_bool(h, "CLICON_BACKEND_RESTCONF_PROCESS") == 0){ + clicon_debug(1, "%s reading from clixon config", __FUNCTION__); /* If not read from backend, try to get restconf config from local config-file */ if ((xrestconf = clicon_conf_restconf(h)) != NULL){ /*! Basic config init, set auth-type, pretty, etc ret 0 means disabled */ @@ -1772,6 +1773,7 @@ restconf_clixon_init(clicon_handle h, /* If no local config, or it is disabled, try to query backend of config. */ else { + clicon_debug(1, "%s reading from backend datastore config", __FUNCTION__); if ((ret = restconf_clixon_backend(h, xrestconfp)) < 0) goto done; if (ret == 0) diff --git a/docker/main/Dockerfile.native b/docker/main/Dockerfile.native index f8f1344a..37bce4e6 100644 --- a/docker/main/Dockerfile.native +++ b/docker/main/Dockerfile.native @@ -41,10 +41,12 @@ RUN apk add --update git make build-base gcc flex bison curl-dev # Create a directory to hold source-code, dependencies etc RUN mkdir /clixon -# evhtp -# dependencies +# evhtp dependencies RUN apk add --update libevent libevent-dev +# nghttp2 dependencies +RUN apk add --update nghttp2 + # clone libevhtp WORKDIR /clixon @@ -68,14 +70,14 @@ RUN ./configure --prefix=/clixon/build RUN make RUN make install +# Need to add www user manually +RUN adduser -D -H -G www-data www-data + # Copy Clixon from local dir RUN mkdir /clixon/clixon WORKDIR /clixon/clixon COPY clixon . -# Need to add www user manually -RUN adduser -D -H -G www-data www-data - # Configure, build and install clixon RUN ./configure --prefix=/clixon/build --with-cligen=/clixon/build --enable-optyangs --with-restconf=native --enable-nghttp2 --enable-evhtp @@ -117,6 +119,9 @@ RUN adduser -D -H -G www-data www-data # for libevtp RUN apk add --update openssl libevent +# nghttp2 dependencies +RUN apk add --update nghttp2 + # Test-specific (for test scripts) RUN apk add --update sudo curl procps grep make bash # iproute2 # contains ip - but CAP_SYS_ADMIN isssue @@ -135,5 +140,5 @@ RUN mkdir /www-data RUN chown clicon /www-data RUN chgrp clicon /www-data -# Log to stderr. +# Start the backend and restconf deamons CMD /usr/local/bin/startsystem.sh diff --git a/example/main/example.xml b/example/main/example.xml index 63272346..d745a759 100644 --- a/example/main/example.xml +++ b/example/main/example.xml @@ -14,9 +14,9 @@ 1 VARS clixon-restconf - /usr/local/var/example 0 0 + /usr/local/var/example init disabled true diff --git a/lib/src/clixon_datastore.c b/lib/src/clixon_datastore.c index 60dd9f74..583c3206 100644 --- a/lib/src/clixon_datastore.c +++ b/lib/src/clixon_datastore.c @@ -539,7 +539,7 @@ xmldb_empty_get(clicon_handle h, return de->de_empty; } -/*! Get modified flag from datastore +/*! Set modified flag from datastore * @param[in] h Clicon handle * @param[in] db Database name * @param[in] value 0 or 1 diff --git a/test/test_type.sh b/test/test_type.sh index 81660f35..77259fed 100755 --- a/test/test_type.sh +++ b/test/test_type.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Advanced union types and generated code # and enum w values -# The test is run twice, first with dbcache turned on, then turned off. +# The test is run three times, with dbcache turned on, cache off and zero-copy # It is the only test with dbcache off. # Magic line must be first in script (see README.md) From c5da97e2a46a548f678e154bb5b7d415dce5b7fd Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Thu, 24 Jun 2021 13:09:18 +0200 Subject: [PATCH 03/34] restconf memleaks --- apps/restconf/restconf_main_native.c | 18 +----------------- apps/restconf/restconf_native.c | 8 ++++++++ apps/restconf/restconf_nghttp2.c | 6 ++++-- test/all.sh | 1 + test/lib.sh | 7 +++++++ test/mem.sh | 1 + test/test_restconf_nmap.sh | 7 +------ 7 files changed, 23 insertions(+), 25 deletions(-) diff --git a/apps/restconf/restconf_main_native.c b/apps/restconf/restconf_main_native.c index acc889d0..e257ac34 100644 --- a/apps/restconf/restconf_main_native.c +++ b/apps/restconf/restconf_main_native.c @@ -591,15 +591,7 @@ restconf_close_ssl_socket(restconf_conn *rc, { int retval = -1; int ret; -#ifdef HAVE_LIBEVHTP - evhtp_connection_t *evconn; - if ((evconn = rc->rc_evconn) != NULL){ - clicon_debug(1, "%s evconn-free (%p)", __FUNCTION__, evconn); - if (evconn) - evhtp_connection_free(evconn); /* evhtp */ - } -#endif /* HAVE_LIBEVHTP */ if (rc->rc_ssl != NULL){ if (shutdown && (ret = SSL_shutdown(rc->rc_ssl)) < 0){ #if 0 @@ -762,7 +754,6 @@ restconf_connection(int s, clixon_event_unreg_fd(rc->rc_s, restconf_connection); clicon_debug(1, "%s evconn-free (%p) 2", __FUNCTION__, evconn); restconf_conn_free(rc); - evhtp_connection_free(evconn); goto ok; } /* connection_parse_nobev */ clicon_debug(1, "%s connection_parse OK", __FUNCTION__); @@ -998,15 +989,7 @@ ssl_alpn_check(clicon_handle h, /* XXX Sending badrequest here gives a segv in SSL_shutdown() later or a SIGPIPE here */ clicon_log(LOG_NOTICE, "Warning: ALPN: No protocol selected"); } - restconf_conn_free(rc); -#ifdef HAVE_LIBEVHTP - { - evhtp_connection_t *evconn; - if ((evconn = rc->rc_evconn) != NULL) - evhtp_connection_free(evconn); /* evhtp */ - } -#endif /* HAVE_LIBEVHTP */ if (rc->rc_ssl){ /* nmap ssl-known-key SEGV at s->method->ssl_shutdown(s); * OR OpenSSL error: : SSL_shutdown, err: SSL_ERROR_SYSCALL(5) @@ -1024,6 +1007,7 @@ ssl_alpn_check(clicon_handle h, } SSL_free(rc->rc_ssl); } + restconf_conn_free(rc); } retval = 0; /* ALPN not OK */ done: diff --git a/apps/restconf/restconf_native.c b/apps/restconf/restconf_native.c index 9bd1ff86..92cb3a3c 100644 --- a/apps/restconf/restconf_native.c +++ b/apps/restconf/restconf_native.c @@ -177,6 +177,14 @@ restconf_conn_free(restconf_conn *rc) clicon_err(OE_RESTCONF, EINVAL, "rc is NULL"); return -1; } +#ifdef HAVE_LIBNGHTTP2 + if (rc->rc_ngsession) + nghttp2_session_del(rc->rc_ngsession); +#endif +#ifdef HAVE_LIBEVHTP + if (rc->rc_evconn) + evhtp_connection_free(rc->rc_evconn); /* evhtp */ +#endif /* Free all streams */ while ((sd = rc->rc_streams) != NULL) { DELQ(sd, rc->rc_streams, restconf_stream_data *); diff --git a/apps/restconf/restconf_nghttp2.c b/apps/restconf/restconf_nghttp2.c index d990b592..9360de89 100644 --- a/apps/restconf/restconf_nghttp2.c +++ b/apps/restconf/restconf_nghttp2.c @@ -397,7 +397,7 @@ restconf_submit_response(nghttp2_session *session, nghttp2_data_provider data_prd; nghttp2_error ngerr; cg_var *cv; - nghttp2_nv *hdrs; + nghttp2_nv *hdrs = NULL; nghttp2_nv *hdr; int i = 0; char valstr[16]; @@ -437,6 +437,8 @@ restconf_submit_response(nghttp2_session *session, retval = 0; done: clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + if (hdrs) + free(hdrs); return retval; } @@ -933,7 +935,7 @@ http2_session_init(restconf_conn *rc) nghttp2_session_callbacks_set_error_callback2(callbacks, error_callback2); /* Create session for server use, register callbacks */ - if ((ngerr = nghttp2_session_server_new(&session, callbacks, rc)) < 0){ + if ((ngerr = nghttp2_session_server_new3(&session, callbacks, rc, NULL, NULL)) < 0){ clicon_err(OE_NGHTTP2, ngerr, "nghttp2_session_server_new"); goto done; } diff --git a/test/all.sh b/test/all.sh index 15785356..4580b734 100755 --- a/test/all.sh +++ b/test/all.sh @@ -32,6 +32,7 @@ for test in $pattern; do testfile=$test . ./$test errcode=$? + endsuite if [ $errcode -ne 0 ]; then allerr=1 echo -e "\e[31mError in $test errcode=$errcode" diff --git a/test/lib.sh b/test/lib.sh index a2c35fc8..258d5cd5 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -432,6 +432,7 @@ function wait_restconf_stopped(){ } # End of test, final tests before normal exit of test +# Note this is a single test started by new, not a total test suite function endtest() { if [ $valgrindtest -eq 1 ]; then @@ -448,6 +449,12 @@ function new(){ >&2 echo "Test $testi($testnr) [$1]" } +# End of complete test-suite, eg a test file +function endsuite() +{ + unset CURLOPTS +} + # Evaluate and return # Example: expectpart $(fn arg) 0 "my return" -- "foo" # - evaluated expression diff --git a/test/mem.sh b/test/mem.sh index 30648a09..42e2667e 100755 --- a/test/mem.sh +++ b/test/mem.sh @@ -58,6 +58,7 @@ function memonce(){ testfile=$test . ./$test errcode=$? + endsuite if [ $errcode -ne 0 ]; then memerr=1 echo -e "\e[31mError in $test errcode=$errcode" diff --git a/test/test_restconf_nmap.sh b/test/test_restconf_nmap.sh index 6aff68ea..bdea1c59 100755 --- a/test/test_restconf_nmap.sh +++ b/test/test_restconf_nmap.sh @@ -116,12 +116,7 @@ fi new "wait backend" wait_backend -#new "netconf edit config" -#expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO$RESTCONFIG]]>]]>" "^]]>]]>$" - -#new "netconf commit" -#expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" - +# Explicit start of restconf for easier debugging if [ $RC -ne 0 ]; then new "kill old restconf daemon" stop_restconf_pre From 2d7d3c9782fe771839f1cc98220ad5a879946983 Mon Sep 17 00:00:00 2001 From: Olof Hagsand Date: Thu, 24 Jun 2021 13:01:19 +0000 Subject: [PATCH 04/34] test restconf native: generete server cert if not exists --- test/lib.sh | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/test/lib.sh b/test/lib.sh index 258d5cd5..cb6ec4a5 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -221,6 +221,7 @@ fi # 1: auth-type (one of none, client-cert, user) # 2: pretty (if true pretty-print restconf return values) # Note, if AUTH=none then FEATURE clixon-restconf:allow-auth-none must be enabled +# Note if https, check if server cert/key exists, if not generate them function restconf_config() { AUTH=$1 @@ -229,11 +230,21 @@ function restconf_config() if [ $RCPROTO = http ]; then echo "true$AUTH$PRETTY$DBGdefault
0.0.0.0
80false
" else - echo "true$AUTH$PRETTY/etc/ssl/certs/clixon-server-crt.pem/etc/ssl/private/clixon-server-key.pem/etc/ssl/certs/clixon-ca-crt.pem$DBGdefault
0.0.0.0
443true
" + certdir=$dir/certs + if [ ! -f ${dir}/clixon-server-crt.pem ]; then + certdir=$dir/certs + test -d $certdir || mkdir $certdir + srvcert=${certdir}/clixon-server-crt.pem + srvkey=${certdir}/clixon-server-key.pem + cacert=${certdir}/clixon-ca-crt.pem + cakey=${certdir}/clixon-ca-key.pem + cacerts $cakey $cacert + servercerts $cakey $cacert $srvkey $srvcert + fi + echo "true$AUTH$PRETTY${certdir}/clixon-server-crt.pem${certdir}/clixon-server-key.pem${certdir}/clixon-ca-crt.pem$DBGdefault
0.0.0.0
443true
" fi } - # Some tests may set owner of testdir to something strange and quit, need # to reset to me if [ ! -G $dir ]; then From e3e36aba50777b03ed64a1c00702392a8bfeb8b3 Mon Sep 17 00:00:00 2001 From: Olof Hagsand Date: Thu, 24 Jun 2021 14:31:59 +0000 Subject: [PATCH 05/34] test restconf: restconf proto should be default https only for native, not fcgi --- test/lib.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/lib.sh b/test/lib.sh index cb6ec4a5..67b7e82c 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -127,7 +127,12 @@ DEMSLEEP=.2 let DEMLOOP=5*DEMWAIT # RESTCONF protocol, eg http or https -: ${RCPROTO:=https} + +if [ "${WITH_RESTCONF}" = "fcgi" ]; then + : ${RCPROTO:=http} +else + : ${RCPROTO:=https} +fi # www user (on linux typically www-data, freebsd www) # Start restconf user, can be root which is dropped to wwwuser From caf396cbda5a0fdd13109ecc792d9705738f1829 Mon Sep 17 00:00:00 2001 From: Olof Hagsand Date: Thu, 24 Jun 2021 17:41:46 +0000 Subject: [PATCH 06/34] - disable util_validate - test vagrant: add libnghttp2 dependency --- test/test_restconf_ssl_certs.sh | 2 +- test/vagrant/vagrant.sh | 6 +++++- util/Makefile.in | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/test/test_restconf_ssl_certs.sh b/test/test_restconf_ssl_certs.sh index dec1b1b3..18a363b7 100755 --- a/test/test_restconf_ssl_certs.sh +++ b/test/test_restconf_ssl_certs.sh @@ -288,7 +288,7 @@ EOF new "limited invalid cert" - expectpart "$(curl $CURLOPTS --key $certdir/limited.key --cert $certdir/limited.crt -X GET $RCPROTO://localhost/restconf/data/example:x 2>&1)" "35 55 56" # 55 "certificate expired" + expectpart "$(curl $CURLOPTS --key $certdir/limited.key --cert $certdir/limited.crt -X GET $RCPROTO://localhost/restconf/data/example:x 2>&1)" "16 35 55 56" # 55 "certificate expired" new "too weak cert (sign w md5)" expectpart "$(curl $CURLOPTS --key $certdir/mymd5.key --cert $certdir/mymd5.crt -X GET $RCPROTO://localhost/restconf/data/example:x 2>&1)" 58 "md too weak" diff --git a/test/vagrant/vagrant.sh b/test/vagrant/vagrant.sh index 03fc22ad..efadf216 100755 --- a/test/vagrant/vagrant.sh +++ b/test/vagrant/vagrant.sh @@ -169,6 +169,8 @@ case $release in native) $sshcmd sudo yum install -y libevent openssl $sshcmd sudo yum install -y libevent-devel openssl-devel + $sshcmd sudo dnf config-manager --set-enabled powertools + $sshcmd sudo yum install -y libnghttp2-devel ;; esac ;; @@ -213,7 +215,9 @@ case $release in ;; native) # $sshcmd sudo apt install -y libevent-2.1 - $sshcmd sudo apt install -y libevent-dev libssl-dev + $sshcmd sudo apt install -y libssl-dev + $sshcmd sudo apt install -y libevent-dev # evhtp + $sshcmd sudo apt install -y libnghttp2-dev # nghttp2 ;; esac ;; diff --git a/util/Makefile.in b/util/Makefile.in index 33da0c6d..f857033e 100644 --- a/util/Makefile.in +++ b/util/Makefile.in @@ -91,7 +91,7 @@ APPSRC += clixon_util_path.c APPSRC += clixon_util_datastore.c APPSRC += clixon_util_regexp.c APPSRC += clixon_util_socket.c -APPSRC += clixon_util_validate.c +# APPSRC += clixon_util_validate.c APPSRC += clixon_netconf_ssh_callhome.c APPSRC += clixon_netconf_ssh_callhome_client.c ifdef with_restconf From 940f4d4fb95b1fa83a8239175f1560ce1905bfde Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Thu, 24 Jun 2021 21:56:36 +0200 Subject: [PATCH 07/34] * Added ordering sanity check for YANG modules and sub-modules * If YANG sub-statements are placed in wrong order, clixon fails on error. --- CHANGELOG.md | 4 +- lib/src/clixon_yang_cardinality.c | 724 ++++++++++++++++-------------- test/test_augment.sh | 4 +- test/test_augment_state.sh | 4 +- test/test_augment_trans.sh | 6 +- test/test_datastore_repair.sh | 4 +- test/test_nacm_ext.sh | 2 +- test/test_type.sh | 2 +- test/test_union.sh | 2 +- test/test_upgrade_checkold.sh | 4 +- test/test_upgrade_failsafe.sh | 8 +- test/test_upgrade_module.sh | 4 +- test/test_upgrade_repair.sh | 4 +- test/test_yang_load.sh | 6 +- 14 files changed, 404 insertions(+), 374 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ba31513..6e81c6d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,7 +50,9 @@ Expected: June 2021 * See [yang uses's substatement when has no effect #218](https://github.com/clicon/clixon/issues/218) * YANG deviation [deviation statement not yet support #211](https://github.com/clicon/clixon/issues/211) * See RFC7950 Sec 5.6.3 - +* Added ordering sanity check for YANG modules and sub-modules + * If YANG sub-statements are placed in wrong order, clixon fails on error. + ### API changes on existing protocol/config features Users may have to change how they access the system diff --git a/lib/src/clixon_yang_cardinality.c b/lib/src/clixon_yang_cardinality.c index 99e39a2b..b783dcd4 100644 --- a/lib/src/clixon_yang_cardinality.c +++ b/lib/src/clixon_yang_cardinality.c @@ -33,6 +33,7 @@ ***** END LICENSE BLOCK ***** * Yang cardinality functions according to RFC 7950 + * That is, how many children any yang node has */ #ifdef HAVE_CONFIG_H @@ -69,28 +70,36 @@ /* * Types */ -/* Encode cardinality according to RFC 7950 +/* Encode cardinality and ordering according to RFC 7950 * Example: - * 7.20.3.1. The deviation's Substatements - * - * +--------------+----------+-------------+ - * | substatement | section | cardinality | - * +--------------+----------+-------------+ - * | description | 7.21.3 | 0..1 | - * | deviate | 7.20.3.2 | 1..n | - * | reference | 7.21.4 | 0..1 | - * +--------------+----------+-------------+ + * 7.1.1. The module's Substatements (subset) + * +--------------+----------+-------------+----------+ + * | substatement | section | cardinality | ordering | + * +--------------+----------+-------------+----------+ + * | contact | 7.1.8 | 0..1 | 2 | + * | import | 7.1.5 | 0..n | 1 | + * | include | 7.1.6 | 0..n | 1 | + * | namespace | 7.1.3 | 1 | 0 | + * | organization | 7.1.7 | 0..1 | 2 | + * | yang-version | 7.1.2 | 1 | 0 | + * +--------------+----------+-------------+----------+ * The cardinalities are (and how many time they occur) * 0..1 149 See ycardmap_01 * 1..n, 1 * 0..n 176 (no restrictions) * 1 10 + * + * Ordering means in which order the statements may occur. If same ordering the may occur + * in any order. In the example above, namespace and yang-version occurs before + * import and include which in turn preceedes contact and organization + * Note: order is only relevant for modules and sub-modules */ struct ycard{ enum rfc_6020 yc_parent; enum rfc_6020 yc_child; int yc_min; int yc_max; + int yc_order; }; /* @@ -107,341 +116,341 @@ struct ycard{ */ #define NMAX 1000000 /* Just a large number */ static const struct ycard yclist[] = { - {Y_ACTION, Y_DESCRIPTION, 0, 1}, - {Y_ACTION, Y_GROUPING, 0, NMAX}, - {Y_ACTION, Y_IF_FEATURE, 0, NMAX}, - {Y_ACTION, Y_INPUT, 0, 1}, - {Y_ACTION, Y_OUTPUT, 0, 1}, - {Y_ACTION, Y_REFERENCE, 0, 1}, - {Y_ACTION, Y_STATUS, 0, 1}, - {Y_ACTION, Y_TYPEDEF, 0, NMAX}, - {Y_ANYDATA, Y_CONFIG, 0, 1}, - {Y_ANYDATA, Y_DESCRIPTION, 0, 1}, - {Y_ANYDATA, Y_IF_FEATURE, 0, NMAX}, - {Y_ANYDATA, Y_MANDATORY, 0, 1}, - {Y_ANYDATA, Y_MUST, 0, NMAX}, - {Y_ANYDATA, Y_REFERENCE, 0, 1}, - {Y_ANYDATA, Y_STATUS, 0, 1}, - {Y_ANYDATA, Y_WHEN, 0, 1}, - {Y_ANYXML, Y_CONFIG, 0, 1}, - {Y_ANYXML, Y_DESCRIPTION, 0, 1}, - {Y_ANYXML, Y_IF_FEATURE, 0, NMAX}, - {Y_ANYXML, Y_MANDATORY, 0, 1}, - {Y_ANYXML, Y_MUST, 0, NMAX}, - {Y_ANYXML, Y_REFERENCE, 0, 1}, - {Y_ANYXML, Y_STATUS, 0, 1}, - {Y_ANYXML, Y_WHEN, 0, 1}, - {Y_ARGUMENT, Y_YIN_ELEMENT, 0, 1}, - {Y_AUGMENT, Y_ACTION, 0, NMAX}, - {Y_AUGMENT, Y_ANYDATA, 0, NMAX}, - {Y_AUGMENT, Y_ANYXML, 0, NMAX}, - {Y_AUGMENT, Y_CASE, 0, NMAX}, - {Y_AUGMENT, Y_CHOICE, 0, NMAX}, - {Y_AUGMENT, Y_CONTAINER, 0, NMAX}, - {Y_AUGMENT, Y_DESCRIPTION, 0, 1}, - {Y_AUGMENT, Y_IF_FEATURE, 0, NMAX}, - {Y_AUGMENT, Y_LEAF, 0, NMAX}, - {Y_AUGMENT, Y_LEAF_LIST, 0, NMAX}, - {Y_AUGMENT, Y_LIST, 0, NMAX}, - {Y_AUGMENT, Y_NOTIFICATION, 0, NMAX}, - {Y_AUGMENT, Y_REFERENCE, 0, 1}, - {Y_AUGMENT, Y_STATUS, 0, 1}, - {Y_AUGMENT, Y_USES, 0, NMAX}, - {Y_AUGMENT, Y_WHEN, 0, 1}, - {Y_BELONGS_TO, Y_PREFIX, 1, 1}, - {Y_BIT, Y_DESCRIPTION, 0, 1}, - {Y_BIT, Y_IF_FEATURE, 0, NMAX}, - {Y_BIT, Y_POSITION, 0, 1}, - {Y_BIT, Y_REFERENCE, 0, 1}, - {Y_BIT, Y_STATUS, 0, 1}, - {Y_CASE, Y_ANYDATA, 0, NMAX}, - {Y_CASE, Y_ANYXML, 0, NMAX}, - {Y_CASE, Y_CHOICE, 0, NMAX}, - {Y_CASE, Y_CONTAINER, 0, NMAX}, - {Y_CASE, Y_DESCRIPTION, 0, 1}, - {Y_CASE, Y_IF_FEATURE, 0, NMAX}, - {Y_CASE, Y_LEAF, 0, NMAX}, - {Y_CASE, Y_LEAF_LIST, 0, NMAX}, - {Y_CASE, Y_LIST, 0, NMAX}, - {Y_CASE, Y_REFERENCE, 0, 1}, - {Y_CASE, Y_STATUS, 0, 1}, - {Y_CASE, Y_USES, 0, NMAX}, - {Y_CASE, Y_WHEN, 0, 1}, - {Y_CHOICE, Y_ANYXML, 0, NMAX}, - {Y_CHOICE, Y_CASE, 0, NMAX}, - {Y_CHOICE, Y_CHOICE, 0, NMAX}, - {Y_CHOICE, Y_CONFIG, 0, 1}, - {Y_CHOICE, Y_CONTAINER, 0, NMAX}, - {Y_CHOICE, Y_DEFAULT, 0, 1}, - {Y_CHOICE, Y_DESCRIPTION, 0, 1}, - {Y_CHOICE, Y_IF_FEATURE, 0, NMAX}, - {Y_CHOICE, Y_LEAF, 0, NMAX}, - {Y_CHOICE, Y_LEAF_LIST, 0, NMAX}, - {Y_CHOICE, Y_LIST, 0, NMAX}, - {Y_CHOICE, Y_MANDATORY, 0, 1}, - {Y_CHOICE, Y_REFERENCE, 0, 1}, - {Y_CHOICE, Y_STATUS, 0, 1}, - {Y_CHOICE, Y_WHEN, 0, 1}, - {Y_CHOICE, Y_ANYDATA, 0, NMAX}, - {Y_CONTAINER, Y_ACTION, 0, NMAX}, - {Y_CONTAINER, Y_ANYDATA, 0, NMAX}, - {Y_CONTAINER, Y_ANYXML, 0, NMAX}, - {Y_CONTAINER, Y_CHOICE, 0, NMAX}, - {Y_CONTAINER, Y_CONFIG, 0, 1}, - {Y_CONTAINER, Y_CONTAINER, 0, NMAX}, - {Y_CONTAINER, Y_DESCRIPTION, 0, 1}, - {Y_CONTAINER, Y_GROUPING, 0, NMAX}, - {Y_CONTAINER, Y_IF_FEATURE, 0, NMAX}, - {Y_CONTAINER, Y_LEAF, 0, NMAX}, - {Y_CONTAINER, Y_LEAF_LIST, 0, NMAX}, - {Y_CONTAINER, Y_LIST, 0, NMAX}, - {Y_CONTAINER, Y_MUST, 0, NMAX}, - {Y_CONTAINER, Y_NOTIFICATION, 0, NMAX}, - {Y_CONTAINER, Y_PRESENCE, 0, 1}, - {Y_CONTAINER, Y_REFERENCE, 0, 1}, - {Y_CONTAINER, Y_STATUS, 0, 1}, - {Y_CONTAINER, Y_TYPEDEF, 0, NMAX}, - {Y_CONTAINER, Y_USES, 0, NMAX}, - {Y_CONTAINER, Y_WHEN, 0, 1}, - {Y_DEVIATE, Y_CONFIG, 0, 1}, - {Y_DEVIATE, Y_DEFAULT, 0, NMAX}, - {Y_DEVIATE, Y_MANDATORY, 0, 1}, - {Y_DEVIATE, Y_MAX_ELEMENTS, 0, 1}, - {Y_DEVIATE, Y_MIN_ELEMENTS, 0, 1}, - {Y_DEVIATE, Y_MUST, 0, NMAX}, - {Y_DEVIATE, Y_TYPE, 0, 1}, - {Y_DEVIATE, Y_UNIQUE, 0, NMAX}, - {Y_DEVIATE, Y_UNITS, 0, 1}, - {Y_DEVIATION, Y_DESCRIPTION, 0, 1}, - {Y_DEVIATION, Y_DEVIATE, 1, NMAX}, - {Y_DEVIATION, Y_REFERENCE, 0, 1}, - {Y_ENUM, Y_DESCRIPTION, 0, 1}, - {Y_ENUM, Y_IF_FEATURE, 0, NMAX}, - {Y_ENUM, Y_REFERENCE, 0, 1}, - {Y_ENUM, Y_STATUS, 0, 1}, - {Y_ENUM, Y_VALUE, 0, 1}, - {Y_EXTENSION, Y_ARGUMENT, 0, 1}, - {Y_EXTENSION, Y_DESCRIPTION, 0, 1}, - {Y_EXTENSION, Y_REFERENCE, 0, 1}, - {Y_EXTENSION, Y_STATUS, 0, 1}, - {Y_FEATURE, Y_DESCRIPTION, 0, 1}, - {Y_FEATURE, Y_IF_FEATURE, 0, NMAX}, - {Y_FEATURE, Y_REFERENCE, 0, 1}, - {Y_FEATURE, Y_STATUS, 0, 1}, - {Y_GROUPING, Y_ACTION, 0, NMAX}, - {Y_GROUPING, Y_ANYDATA, 0, NMAX}, - {Y_GROUPING, Y_ANYXML, 0, NMAX}, - {Y_GROUPING, Y_CHOICE, 0, NMAX}, - {Y_GROUPING, Y_CONTAINER, 0, NMAX}, - {Y_GROUPING, Y_DESCRIPTION, 0, 1}, - {Y_GROUPING, Y_GROUPING, 0, NMAX}, - {Y_GROUPING, Y_LEAF, 0, NMAX}, - {Y_GROUPING, Y_LEAF_LIST, 0, NMAX}, - {Y_GROUPING, Y_LIST, 0, NMAX}, - {Y_GROUPING, Y_NOTIFICATION, 0, NMAX}, - {Y_GROUPING, Y_REFERENCE, 0, 1}, - {Y_GROUPING, Y_STATUS, 0, 1}, - {Y_GROUPING, Y_TYPEDEF, 0, NMAX}, - {Y_GROUPING, Y_USES, 0, NMAX}, - {Y_IDENTITY, Y_BASE, 0, NMAX}, - {Y_IDENTITY, Y_DESCRIPTION, 0, 1}, - {Y_IDENTITY, Y_IF_FEATURE, 0, NMAX}, - {Y_IDENTITY, Y_REFERENCE, 0, 1}, - {Y_IDENTITY, Y_STATUS, 0, 1}, - {Y_IMPORT, Y_DESCRIPTION, 0, 1}, - {Y_IMPORT, Y_PREFIX, 1, 1}, - {Y_IMPORT, Y_REFERENCE, 0, 1}, - {Y_IMPORT, Y_REVISION_DATE,0, 1}, - {Y_INCLUDE, Y_DESCRIPTION, 0, 1}, - {Y_INCLUDE, Y_REFERENCE, 0, 1}, - {Y_INCLUDE, Y_REVISION_DATE,0, 1}, - {Y_INPUT, Y_ANYDATA, 0, NMAX}, - {Y_INPUT, Y_ANYXML, 0, NMAX}, - {Y_INPUT, Y_CHOICE, 0, NMAX}, - {Y_INPUT, Y_CONTAINER, 0, NMAX}, - {Y_INPUT, Y_GROUPING, 0, NMAX}, - {Y_INPUT, Y_LEAF, 0, NMAX}, - {Y_INPUT, Y_LEAF_LIST, 0, NMAX}, - {Y_INPUT, Y_LIST, 0, NMAX}, - {Y_INPUT, Y_MUST, 0, NMAX}, - {Y_INPUT, Y_TYPEDEF, 0, NMAX}, - {Y_INPUT, Y_USES, 0, NMAX}, - {Y_LEAF, Y_CONFIG, 0, 1}, - {Y_LEAF, Y_DEFAULT, 0, 1}, - {Y_LEAF, Y_DESCRIPTION, 0, 1}, - {Y_LEAF, Y_IF_FEATURE, 0, NMAX}, - {Y_LEAF, Y_MANDATORY, 0, 1}, - {Y_LEAF, Y_MUST, 0, NMAX}, - {Y_LEAF, Y_REFERENCE, 0, 1}, - {Y_LEAF, Y_STATUS, 0, 1}, - {Y_LEAF, Y_TYPE, 1, 1}, - {Y_LEAF, Y_UNITS, 0, 1}, - {Y_LEAF, Y_WHEN, 0, 1}, - {Y_LEAF_LIST, Y_CONFIG, 0, 1}, - {Y_LEAF_LIST, Y_DEFAULT, 0, NMAX}, - {Y_LEAF_LIST, Y_DESCRIPTION, 0, 1}, - {Y_LEAF_LIST, Y_IF_FEATURE, 0, NMAX}, - {Y_LEAF_LIST, Y_MAX_ELEMENTS, 0, 1}, - {Y_LEAF_LIST, Y_MIN_ELEMENTS, 0, 1}, - {Y_LEAF_LIST, Y_MUST, 0, NMAX}, - {Y_LEAF_LIST, Y_ORDERED_BY, 0, 1}, - {Y_LEAF_LIST, Y_REFERENCE, 0, 1}, - {Y_LEAF_LIST, Y_STATUS, 0, 1}, - {Y_LEAF_LIST, Y_TYPE, 1, 1}, - {Y_LEAF_LIST, Y_UNITS, 0, 1}, - {Y_LEAF_LIST, Y_WHEN, 0, 1}, - {Y_LENGTH, Y_DESCRIPTION, 0, 1}, - {Y_LENGTH, Y_ERROR_APP_TAG, 0, 1}, - {Y_LENGTH, Y_ERROR_MESSAGE, 0, 1}, - {Y_LENGTH, Y_REFERENCE, 0, 1}, - {Y_LIST, Y_ACTION, 0, NMAX}, - {Y_LIST, Y_ANYDATA, 0, NMAX}, - {Y_LIST, Y_ANYXML, 0, NMAX}, - {Y_LIST, Y_CHOICE, 0, NMAX}, - {Y_LIST, Y_CONFIG, 0, 1}, - {Y_LIST, Y_CONTAINER, 0, NMAX}, - {Y_LIST, Y_DESCRIPTION, 0, 1}, - {Y_LIST, Y_GROUPING, 0, NMAX}, - {Y_LIST, Y_IF_FEATURE, 0, NMAX}, - {Y_LIST, Y_KEY, 0, 1}, - {Y_LIST, Y_LEAF, 0, NMAX}, - {Y_LIST, Y_LEAF_LIST, 0, NMAX}, - {Y_LIST, Y_LIST, 0, NMAX}, - {Y_LIST, Y_MAX_ELEMENTS, 0, 1}, - {Y_LIST, Y_MIN_ELEMENTS, 0, 1}, - {Y_LIST, Y_MUST, 0, NMAX}, - {Y_LIST, Y_NOTIFICATION, 0, NMAX}, - {Y_LIST, Y_ORDERED_BY, 0, 1}, - {Y_LIST, Y_REFERENCE, 0, 1}, - {Y_LIST, Y_STATUS, 0, 1}, - {Y_LIST, Y_TYPEDEF, 0, NMAX}, - {Y_LIST, Y_UNIQUE, 0, NMAX}, - {Y_LIST, Y_USES, 0, NMAX}, - {Y_LIST, Y_WHEN, 0,1}, - {Y_MODULE, Y_ANYDATA, 0, NMAX}, - {Y_MODULE, Y_ANYXML, 0, NMAX}, - {Y_MODULE, Y_AUGMENT, 0, NMAX}, - {Y_MODULE, Y_CHOICE, 0, NMAX}, - {Y_MODULE, Y_CONTACT, 0, 1}, - {Y_MODULE, Y_CONTAINER, 0, NMAX}, - {Y_MODULE, Y_DESCRIPTION, 0, 1}, - {Y_MODULE, Y_DEVIATION, 0, NMAX}, - {Y_MODULE, Y_EXTENSION, 0, NMAX}, - {Y_MODULE, Y_FEATURE, 0, NMAX}, - {Y_MODULE, Y_GROUPING, 0, NMAX}, - {Y_MODULE, Y_IDENTITY, 0, NMAX}, - {Y_MODULE, Y_IMPORT, 0, NMAX}, - {Y_MODULE, Y_INCLUDE, 0, NMAX}, - {Y_MODULE, Y_LEAF, 0, NMAX}, - {Y_MODULE, Y_LEAF_LIST, 0, NMAX}, - {Y_MODULE, Y_LIST, 0, NMAX}, - {Y_MODULE, Y_NAMESPACE, 1, 1}, - {Y_MODULE, Y_NOTIFICATION, 0, NMAX}, - {Y_MODULE, Y_ORGANIZATION, 0, 1}, - {Y_MODULE, Y_PREFIX, 1, 1}, - {Y_MODULE, Y_REFERENCE, 0, 1}, - {Y_MODULE, Y_REVISION, 0, NMAX}, - {Y_MODULE, Y_RPC, 0, NMAX}, - {Y_MODULE, Y_TYPEDEF, 0, NMAX}, - {Y_MODULE, Y_USES, 0, NMAX}, - {Y_MODULE, Y_YANG_VERSION, 0, 1}, - {Y_MUST, Y_DESCRIPTION, 0, 1}, - {Y_MUST, Y_ERROR_APP_TAG, 0, 1}, - {Y_MUST, Y_ERROR_MESSAGE, 0, 1}, - {Y_MUST, Y_REFERENCE, 0, 1}, - {Y_NOTIFICATION, Y_ANYDATA, 0, NMAX}, - {Y_NOTIFICATION, Y_ANYXML, 0, NMAX}, - {Y_NOTIFICATION, Y_CHOICE, 0, NMAX}, - {Y_NOTIFICATION, Y_CONTAINER, 0, NMAX}, - {Y_NOTIFICATION, Y_DESCRIPTION, 0, 1}, - {Y_NOTIFICATION, Y_GROUPING, 0, NMAX}, - {Y_NOTIFICATION, Y_IF_FEATURE, 0, NMAX}, - {Y_NOTIFICATION, Y_LEAF, 0, NMAX}, - {Y_NOTIFICATION, Y_LEAF_LIST, 0, NMAX}, - {Y_NOTIFICATION, Y_LIST, 0, NMAX}, - {Y_NOTIFICATION, Y_MUST, 0, NMAX}, - {Y_NOTIFICATION, Y_REFERENCE, 0, 1}, - {Y_NOTIFICATION, Y_STATUS, 0, 1}, - {Y_NOTIFICATION, Y_TYPEDEF, 0, NMAX}, - {Y_NOTIFICATION, Y_USES, 0, NMAX}, - {Y_OUTPUT, Y_ANYDATA, 0, NMAX}, - {Y_OUTPUT, Y_ANYXML, 0, NMAX}, - {Y_OUTPUT, Y_CHOICE, 0, NMAX}, - {Y_OUTPUT, Y_CONTAINER, 0, NMAX}, - {Y_OUTPUT, Y_GROUPING, 0, NMAX}, - {Y_OUTPUT, Y_LEAF, 0, NMAX}, - {Y_OUTPUT, Y_LEAF_LIST, 0, NMAX}, - {Y_OUTPUT, Y_LIST, 0, NMAX}, - {Y_OUTPUT, Y_MUST, 0, NMAX}, - {Y_OUTPUT, Y_TYPEDEF, 0, NMAX}, - {Y_OUTPUT, Y_USES, 0, NMAX}, - {Y_PATTERN, Y_DESCRIPTION, 0, 1}, - {Y_PATTERN, Y_ERROR_APP_TAG, 0, 1}, - {Y_PATTERN, Y_ERROR_MESSAGE, 0, 1}, - {Y_PATTERN, Y_MODIFIER, 0, 1}, - {Y_PATTERN, Y_REFERENCE, 0, 1}, - {Y_RANGE, Y_DESCRIPTION, 0, 1}, - {Y_RANGE, Y_ERROR_APP_TAG, 0, 1}, - {Y_RANGE, Y_ERROR_MESSAGE, 0, 1}, - {Y_RANGE, Y_REFERENCE, 0, 1}, - {Y_REVISION, Y_DESCRIPTION, 0, 1}, - {Y_REVISION, Y_REFERENCE, 0, 1}, - {Y_RPC, Y_DESCRIPTION, 0, 1}, - {Y_RPC, Y_GROUPING, 0, NMAX}, - {Y_RPC, Y_IF_FEATURE, 0, NMAX}, - {Y_RPC, Y_INPUT, 0, 1}, - {Y_RPC, Y_OUTPUT, 0, 1}, - {Y_RPC, Y_REFERENCE, 0, 1}, - {Y_RPC, Y_STATUS, 0, 1}, - {Y_RPC, Y_TYPEDEF, 0, NMAX}, - {Y_SUBMODULE, Y_ANYDATA, 0, NMAX}, - {Y_SUBMODULE, Y_AUGMENT, 0, NMAX}, - {Y_SUBMODULE, Y_BELONGS_TO, 1, 1}, - {Y_SUBMODULE, Y_CHOICE, 0, NMAX}, - {Y_SUBMODULE, Y_CONTACT, 0, 1}, - {Y_SUBMODULE, Y_CONTAINER, 0, NMAX}, - {Y_SUBMODULE, Y_DESCRIPTION,0, 1}, - {Y_SUBMODULE, Y_DEVIATION, 0, NMAX}, - {Y_SUBMODULE, Y_EXTENSION, 0, NMAX}, - {Y_SUBMODULE, Y_FEATURE, 0, NMAX}, - {Y_SUBMODULE, Y_GROUPING, 0, NMAX}, - {Y_SUBMODULE, Y_IDENTITY, 0, NMAX}, - {Y_SUBMODULE, Y_IMPORT, 0, NMAX}, - {Y_SUBMODULE, Y_INCLUDE, 0, NMAX}, - {Y_SUBMODULE, Y_LEAF, 0, NMAX}, - {Y_SUBMODULE, Y_LEAF_LIST, 0, NMAX}, - {Y_SUBMODULE, Y_LIST, 0, NMAX}, - {Y_SUBMODULE, Y_NOTIFICATION,0, NMAX}, - {Y_SUBMODULE, Y_ORGANIZATION,0, 1}, - {Y_SUBMODULE, Y_REFERENCE, 0, 1}, - {Y_SUBMODULE, Y_REVISION, 0, NMAX}, - {Y_SUBMODULE, Y_RPC, 0, NMAX}, - {Y_SUBMODULE, Y_TYPEDEF, 0, NMAX}, - {Y_SUBMODULE, Y_USES, 0, NMAX}, - {Y_SUBMODULE, Y_YANG_VERSION,0, 1}, /* "yang-version" statement is mandatory in YANG version "1.1". */ - {Y_TYPE, Y_BASE, 0, NMAX}, - {Y_TYPE, Y_BIT, 0, NMAX}, - {Y_TYPE, Y_ENUM, 0, NMAX}, - {Y_TYPE, Y_FRACTION_DIGITS, 0, 1}, - {Y_TYPE, Y_LENGTH, 0, 1}, - {Y_TYPE, Y_PATH, 0, 1}, - {Y_TYPE, Y_PATTERN, 0, NMAX}, - {Y_TYPE, Y_RANGE, 0, 1}, - {Y_TYPE, Y_REQUIRE_INSTANCE, 0, 1}, - {Y_TYPE, Y_TYPE, 0, NMAX}, - {Y_TYPEDEF, Y_DEFAULT, 0, 1}, - {Y_TYPEDEF, Y_DESCRIPTION,0, 1}, - {Y_TYPEDEF, Y_REFERENCE, 0, 1}, - {Y_TYPEDEF, Y_STATUS, 0, 1}, - {Y_TYPEDEF, Y_TYPE, 1, 1}, - {Y_TYPEDEF, Y_UNITS, 0, 1}, - {Y_USES, Y_AUGMENT, 0, NMAX}, - {Y_USES, Y_DESCRIPTION, 0, 1}, - {Y_USES, Y_IF_FEATURE, 0, NMAX}, - {Y_USES, Y_REFERENCE, 0, 1}, - {Y_USES, Y_REFINE, 0, NMAX}, - {Y_USES, Y_STATUS, 0, 1}, - {Y_USES, Y_WHEN, 0, 1}, + {Y_ACTION, Y_DESCRIPTION, 0, 1, 0}, + {Y_ACTION, Y_GROUPING, 0, NMAX, 0}, + {Y_ACTION, Y_IF_FEATURE, 0, NMAX, 0}, + {Y_ACTION, Y_INPUT, 0, 1, 0}, + {Y_ACTION, Y_OUTPUT, 0, 1, 0}, + {Y_ACTION, Y_REFERENCE, 0, 1, 0}, + {Y_ACTION, Y_STATUS, 0, 1, 0}, + {Y_ACTION, Y_TYPEDEF, 0, NMAX, 0}, + {Y_ANYDATA, Y_CONFIG, 0, 1, 0}, + {Y_ANYDATA, Y_DESCRIPTION, 0, 1, 0}, + {Y_ANYDATA, Y_IF_FEATURE, 0, NMAX, 0}, + {Y_ANYDATA, Y_MANDATORY, 0, 1, 0}, + {Y_ANYDATA, Y_MUST, 0, NMAX, 0}, + {Y_ANYDATA, Y_REFERENCE, 0, 1, 0}, + {Y_ANYDATA, Y_STATUS, 0, 1, 0}, + {Y_ANYDATA, Y_WHEN, 0, 1, 0}, + {Y_ANYXML, Y_CONFIG, 0, 1, 0}, + {Y_ANYXML, Y_DESCRIPTION, 0, 1, 0}, + {Y_ANYXML, Y_IF_FEATURE, 0, NMAX, 0}, + {Y_ANYXML, Y_MANDATORY, 0, 1, 0}, + {Y_ANYXML, Y_MUST, 0, NMAX, 0}, + {Y_ANYXML, Y_REFERENCE, 0, 1, 0}, + {Y_ANYXML, Y_STATUS, 0, 1, 0}, + {Y_ANYXML, Y_WHEN, 0, 1, 0}, + {Y_ARGUMENT, Y_YIN_ELEMENT, 0, 1, 0}, + {Y_AUGMENT, Y_ACTION, 0, NMAX, 0}, + {Y_AUGMENT, Y_ANYDATA, 0, NMAX, 0}, + {Y_AUGMENT, Y_ANYXML, 0, NMAX, 0}, + {Y_AUGMENT, Y_CASE, 0, NMAX, 0}, + {Y_AUGMENT, Y_CHOICE, 0, NMAX, 0}, + {Y_AUGMENT, Y_CONTAINER, 0, NMAX, 0}, + {Y_AUGMENT, Y_DESCRIPTION, 0, 1, 0}, + {Y_AUGMENT, Y_IF_FEATURE, 0, NMAX, 0}, + {Y_AUGMENT, Y_LEAF, 0, NMAX, 0}, + {Y_AUGMENT, Y_LEAF_LIST, 0, NMAX, 0}, + {Y_AUGMENT, Y_LIST, 0, NMAX, 0}, + {Y_AUGMENT, Y_NOTIFICATION, 0, NMAX, 0}, + {Y_AUGMENT, Y_REFERENCE, 0, 1, 0}, + {Y_AUGMENT, Y_STATUS, 0, 1, 0}, + {Y_AUGMENT, Y_USES, 0, NMAX, 0}, + {Y_AUGMENT, Y_WHEN, 0, 1, 0}, + {Y_BELONGS_TO, Y_PREFIX, 1, 1, 0}, + {Y_BIT, Y_DESCRIPTION, 0, 1, 0}, + {Y_BIT, Y_IF_FEATURE, 0, NMAX, 0}, + {Y_BIT, Y_POSITION, 0, 1, 0}, + {Y_BIT, Y_REFERENCE, 0, 1, 0}, + {Y_BIT, Y_STATUS, 0, 1, 0}, + {Y_CASE, Y_ANYDATA, 0, NMAX, 0}, + {Y_CASE, Y_ANYXML, 0, NMAX, 0}, + {Y_CASE, Y_CHOICE, 0, NMAX, 0}, + {Y_CASE, Y_CONTAINER, 0, NMAX, 0}, + {Y_CASE, Y_DESCRIPTION, 0, 1, 0}, + {Y_CASE, Y_IF_FEATURE, 0, NMAX, 0}, + {Y_CASE, Y_LEAF, 0, NMAX, 0}, + {Y_CASE, Y_LEAF_LIST, 0, NMAX, 0}, + {Y_CASE, Y_LIST, 0, NMAX, 0}, + {Y_CASE, Y_REFERENCE, 0, 1, 0}, + {Y_CASE, Y_STATUS, 0, 1, 0}, + {Y_CASE, Y_USES, 0, NMAX, 0}, + {Y_CASE, Y_WHEN, 0, 1, 0}, + {Y_CHOICE, Y_ANYXML, 0, NMAX, 0}, + {Y_CHOICE, Y_CASE, 0, NMAX, 0}, + {Y_CHOICE, Y_CHOICE, 0, NMAX, 0}, + {Y_CHOICE, Y_CONFIG, 0, 1, 0}, + {Y_CHOICE, Y_CONTAINER, 0, NMAX, 0}, + {Y_CHOICE, Y_DEFAULT, 0, 1, 0}, + {Y_CHOICE, Y_DESCRIPTION, 0, 1, 0}, + {Y_CHOICE, Y_IF_FEATURE, 0, NMAX, 0}, + {Y_CHOICE, Y_LEAF, 0, NMAX, 0}, + {Y_CHOICE, Y_LEAF_LIST, 0, NMAX, 0}, + {Y_CHOICE, Y_LIST, 0, NMAX, 0}, + {Y_CHOICE, Y_MANDATORY, 0, 1, 0}, + {Y_CHOICE, Y_REFERENCE, 0, 1, 0}, + {Y_CHOICE, Y_STATUS, 0, 1, 0}, + {Y_CHOICE, Y_WHEN, 0, 1, 0}, + {Y_CHOICE, Y_ANYDATA, 0, NMAX, 0}, + {Y_CONTAINER, Y_ACTION, 0, NMAX, 0}, + {Y_CONTAINER, Y_ANYDATA, 0, NMAX, 0}, + {Y_CONTAINER, Y_ANYXML, 0, NMAX, 0}, + {Y_CONTAINER, Y_CHOICE, 0, NMAX, 0}, + {Y_CONTAINER, Y_CONFIG, 0, 1, 0}, + {Y_CONTAINER, Y_CONTAINER, 0, NMAX, 0}, + {Y_CONTAINER, Y_DESCRIPTION, 0, 1, 0}, + {Y_CONTAINER, Y_GROUPING, 0, NMAX, 0}, + {Y_CONTAINER, Y_IF_FEATURE, 0, NMAX, 0}, + {Y_CONTAINER, Y_LEAF, 0, NMAX, 0}, + {Y_CONTAINER, Y_LEAF_LIST, 0, NMAX, 0}, + {Y_CONTAINER, Y_LIST, 0, NMAX, 0}, + {Y_CONTAINER, Y_MUST, 0, NMAX, 0}, + {Y_CONTAINER, Y_NOTIFICATION, 0, NMAX, 0}, + {Y_CONTAINER, Y_PRESENCE, 0, 1, 0}, + {Y_CONTAINER, Y_REFERENCE, 0, 1, 0}, + {Y_CONTAINER, Y_STATUS, 0, 1, 0}, + {Y_CONTAINER, Y_TYPEDEF, 0, NMAX, 0}, + {Y_CONTAINER, Y_USES, 0, NMAX, 0}, + {Y_CONTAINER, Y_WHEN, 0, 1, 0}, + {Y_DEVIATE, Y_CONFIG, 0, 1, 0}, + {Y_DEVIATE, Y_DEFAULT, 0, NMAX, 0}, + {Y_DEVIATE, Y_MANDATORY, 0, 1, 0}, + {Y_DEVIATE, Y_MAX_ELEMENTS, 0, 1, 0}, + {Y_DEVIATE, Y_MIN_ELEMENTS, 0, 1, 0}, + {Y_DEVIATE, Y_MUST, 0, NMAX, 0}, + {Y_DEVIATE, Y_TYPE, 0, 1, 0}, + {Y_DEVIATE, Y_UNIQUE, 0, NMAX, 0}, + {Y_DEVIATE, Y_UNITS, 0, 1, 0}, + {Y_DEVIATION, Y_DESCRIPTION, 0, 1, 0}, + {Y_DEVIATION, Y_DEVIATE, 1, NMAX, 0}, + {Y_DEVIATION, Y_REFERENCE, 0, 1, 0}, + {Y_ENUM, Y_DESCRIPTION, 0, 1, 0}, + {Y_ENUM, Y_IF_FEATURE, 0, NMAX, 0}, + {Y_ENUM, Y_REFERENCE, 0, 1, 0}, + {Y_ENUM, Y_STATUS, 0, 1, 0}, + {Y_ENUM, Y_VALUE, 0, 1, 0}, + {Y_EXTENSION, Y_ARGUMENT, 0, 1, 0}, + {Y_EXTENSION, Y_DESCRIPTION, 0, 1, 0}, + {Y_EXTENSION, Y_REFERENCE, 0, 1, 0}, + {Y_EXTENSION, Y_STATUS, 0, 1, 0}, + {Y_FEATURE, Y_DESCRIPTION, 0, 1, 0}, + {Y_FEATURE, Y_IF_FEATURE, 0, NMAX, 0}, + {Y_FEATURE, Y_REFERENCE, 0, 1, 0}, + {Y_FEATURE, Y_STATUS, 0, 1, 0}, + {Y_GROUPING, Y_ACTION, 0, NMAX, 0}, + {Y_GROUPING, Y_ANYDATA, 0, NMAX, 0}, + {Y_GROUPING, Y_ANYXML, 0, NMAX, 0}, + {Y_GROUPING, Y_CHOICE, 0, NMAX, 0}, + {Y_GROUPING, Y_CONTAINER, 0, NMAX, 0}, + {Y_GROUPING, Y_DESCRIPTION, 0, 1, 0}, + {Y_GROUPING, Y_GROUPING, 0, NMAX, 0}, + {Y_GROUPING, Y_LEAF, 0, NMAX, 0}, + {Y_GROUPING, Y_LEAF_LIST, 0, NMAX, 0}, + {Y_GROUPING, Y_LIST, 0, NMAX, 0}, + {Y_GROUPING, Y_NOTIFICATION, 0, NMAX, 0}, + {Y_GROUPING, Y_REFERENCE, 0, 1, 0}, + {Y_GROUPING, Y_STATUS, 0, 1, 0}, + {Y_GROUPING, Y_TYPEDEF, 0, NMAX, 0}, + {Y_GROUPING, Y_USES, 0, NMAX, 0}, + {Y_IDENTITY, Y_BASE, 0, NMAX, 0}, + {Y_IDENTITY, Y_DESCRIPTION, 0, 1, 0}, + {Y_IDENTITY, Y_IF_FEATURE, 0, NMAX, 0}, + {Y_IDENTITY, Y_REFERENCE, 0, 1, 0}, + {Y_IDENTITY, Y_STATUS, 0, 1, 0}, + {Y_IMPORT, Y_DESCRIPTION, 0, 1, 0}, + {Y_IMPORT, Y_PREFIX, 1, 1, 0}, + {Y_IMPORT, Y_REFERENCE, 0, 1, 0}, + {Y_IMPORT, Y_REVISION_DATE,0, 1, 0}, + {Y_INCLUDE, Y_DESCRIPTION, 0, 1, 0}, + {Y_INCLUDE, Y_REFERENCE, 0, 1, 0}, + {Y_INCLUDE, Y_REVISION_DATE,0, 1, 0}, + {Y_INPUT, Y_ANYDATA, 0, NMAX, 0}, + {Y_INPUT, Y_ANYXML, 0, NMAX, 0}, + {Y_INPUT, Y_CHOICE, 0, NMAX, 0}, + {Y_INPUT, Y_CONTAINER, 0, NMAX, 0}, + {Y_INPUT, Y_GROUPING, 0, NMAX, 0}, + {Y_INPUT, Y_LEAF, 0, NMAX, 0}, + {Y_INPUT, Y_LEAF_LIST, 0, NMAX, 0}, + {Y_INPUT, Y_LIST, 0, NMAX, 0}, + {Y_INPUT, Y_MUST, 0, NMAX, 0}, + {Y_INPUT, Y_TYPEDEF, 0, NMAX, 0}, + {Y_INPUT, Y_USES, 0, NMAX, 0}, + {Y_LEAF, Y_CONFIG, 0, 1, 0}, + {Y_LEAF, Y_DEFAULT, 0, 1, 0}, + {Y_LEAF, Y_DESCRIPTION, 0, 1, 0}, + {Y_LEAF, Y_IF_FEATURE, 0, NMAX, 0}, + {Y_LEAF, Y_MANDATORY, 0, 1, 0}, + {Y_LEAF, Y_MUST, 0, NMAX, 0}, + {Y_LEAF, Y_REFERENCE, 0, 1, 0}, + {Y_LEAF, Y_STATUS, 0, 1, 0}, + {Y_LEAF, Y_TYPE, 1, 1, 0}, + {Y_LEAF, Y_UNITS, 0, 1, 0}, + {Y_LEAF, Y_WHEN, 0, 1, 0}, + {Y_LEAF_LIST, Y_CONFIG, 0, 1, 0}, + {Y_LEAF_LIST, Y_DEFAULT, 0, NMAX, 0}, + {Y_LEAF_LIST, Y_DESCRIPTION, 0, 1, 0}, + {Y_LEAF_LIST, Y_IF_FEATURE, 0, NMAX, 0}, + {Y_LEAF_LIST, Y_MAX_ELEMENTS, 0, 1, 0}, + {Y_LEAF_LIST, Y_MIN_ELEMENTS, 0, 1, 0}, + {Y_LEAF_LIST, Y_MUST, 0, NMAX, 0}, + {Y_LEAF_LIST, Y_ORDERED_BY, 0, 1, 0}, + {Y_LEAF_LIST, Y_REFERENCE, 0, 1, 0}, + {Y_LEAF_LIST, Y_STATUS, 0, 1, 0}, + {Y_LEAF_LIST, Y_TYPE, 1, 1, 0}, + {Y_LEAF_LIST, Y_UNITS, 0, 1, 0}, + {Y_LEAF_LIST, Y_WHEN, 0, 1, 0}, + {Y_LENGTH, Y_DESCRIPTION, 0, 1, 0}, + {Y_LENGTH, Y_ERROR_APP_TAG, 0, 1, 0}, + {Y_LENGTH, Y_ERROR_MESSAGE, 0, 1, 0}, + {Y_LENGTH, Y_REFERENCE, 0, 1, 0}, + {Y_LIST, Y_ACTION, 0, NMAX, 0}, + {Y_LIST, Y_ANYDATA, 0, NMAX, 0}, + {Y_LIST, Y_ANYXML, 0, NMAX, 0}, + {Y_LIST, Y_CHOICE, 0, NMAX, 0}, + {Y_LIST, Y_CONFIG, 0, 1, 0}, + {Y_LIST, Y_CONTAINER, 0, NMAX, 0}, + {Y_LIST, Y_DESCRIPTION, 0, 1, 0}, + {Y_LIST, Y_GROUPING, 0, NMAX, 0}, + {Y_LIST, Y_IF_FEATURE, 0, NMAX, 0}, + {Y_LIST, Y_KEY, 0, 1, 0}, + {Y_LIST, Y_LEAF, 0, NMAX, 0}, + {Y_LIST, Y_LEAF_LIST, 0, NMAX, 0}, + {Y_LIST, Y_LIST, 0, NMAX, 0}, + {Y_LIST, Y_MAX_ELEMENTS, 0, 1, 0}, + {Y_LIST, Y_MIN_ELEMENTS, 0, 1, 0}, + {Y_LIST, Y_MUST, 0, NMAX, 0}, + {Y_LIST, Y_NOTIFICATION, 0, NMAX, 0}, + {Y_LIST, Y_ORDERED_BY, 0, 1, 0}, + {Y_LIST, Y_REFERENCE, 0, 1, 0}, + {Y_LIST, Y_STATUS, 0, 1, 0}, + {Y_LIST, Y_TYPEDEF, 0, NMAX, 0}, + {Y_LIST, Y_UNIQUE, 0, NMAX, 0}, + {Y_LIST, Y_USES, 0, NMAX, 0}, + {Y_LIST, Y_WHEN, 0,1, 0}, + {Y_MODULE, Y_ANYDATA, 0, NMAX, 4}, + {Y_MODULE, Y_ANYXML, 0, NMAX, 4}, + {Y_MODULE, Y_AUGMENT, 0, NMAX, 4}, + {Y_MODULE, Y_CHOICE, 0, NMAX, 4}, + {Y_MODULE, Y_CONTACT, 0, 1, 2}, + {Y_MODULE, Y_CONTAINER, 0, NMAX, 4}, + {Y_MODULE, Y_DESCRIPTION, 0, 1, 2}, + {Y_MODULE, Y_DEVIATION, 0, NMAX, 4}, + {Y_MODULE, Y_EXTENSION, 0, NMAX, 4}, + {Y_MODULE, Y_FEATURE, 0, NMAX, 4}, + {Y_MODULE, Y_GROUPING, 0, NMAX, 4}, + {Y_MODULE, Y_IDENTITY, 0, NMAX, 4}, + {Y_MODULE, Y_IMPORT, 0, NMAX, 1}, + {Y_MODULE, Y_INCLUDE, 0, NMAX, 1}, + {Y_MODULE, Y_LEAF, 0, NMAX, 4}, + {Y_MODULE, Y_LEAF_LIST, 0, NMAX, 4}, + {Y_MODULE, Y_LIST, 0, NMAX, 4}, + {Y_MODULE, Y_NAMESPACE, 1, 1, 0}, + {Y_MODULE, Y_NOTIFICATION, 0, NMAX, 4}, + {Y_MODULE, Y_ORGANIZATION, 0, 1, 2}, + {Y_MODULE, Y_PREFIX, 1, 1, 0}, + {Y_MODULE, Y_REFERENCE, 0, 1, 2}, + {Y_MODULE, Y_REVISION, 0, NMAX, 3}, + {Y_MODULE, Y_RPC, 0, NMAX, 4}, + {Y_MODULE, Y_TYPEDEF, 0, NMAX, 4}, + {Y_MODULE, Y_USES, 0, NMAX, 4}, + {Y_MODULE, Y_YANG_VERSION, 0, 1, 0}, + {Y_MUST, Y_DESCRIPTION, 0, 1, 0}, + {Y_MUST, Y_ERROR_APP_TAG, 0, 1, 0}, + {Y_MUST, Y_ERROR_MESSAGE, 0, 1, 0}, + {Y_MUST, Y_REFERENCE, 0, 1, 0}, + {Y_NOTIFICATION, Y_ANYDATA, 0, NMAX, 0}, + {Y_NOTIFICATION, Y_ANYXML, 0, NMAX, 0}, + {Y_NOTIFICATION, Y_CHOICE, 0, NMAX, 0}, + {Y_NOTIFICATION, Y_CONTAINER, 0, NMAX, 0}, + {Y_NOTIFICATION, Y_DESCRIPTION, 0, 1, 0}, + {Y_NOTIFICATION, Y_GROUPING, 0, NMAX, 0}, + {Y_NOTIFICATION, Y_IF_FEATURE, 0, NMAX, 0}, + {Y_NOTIFICATION, Y_LEAF, 0, NMAX, 0}, + {Y_NOTIFICATION, Y_LEAF_LIST, 0, NMAX, 0}, + {Y_NOTIFICATION, Y_LIST, 0, NMAX, 0}, + {Y_NOTIFICATION, Y_MUST, 0, NMAX, 0}, + {Y_NOTIFICATION, Y_REFERENCE, 0, 1, 0}, + {Y_NOTIFICATION, Y_STATUS, 0, 1, 0}, + {Y_NOTIFICATION, Y_TYPEDEF, 0, NMAX, 0}, + {Y_NOTIFICATION, Y_USES, 0, NMAX, 0}, + {Y_OUTPUT, Y_ANYDATA, 0, NMAX, 0}, + {Y_OUTPUT, Y_ANYXML, 0, NMAX, 0}, + {Y_OUTPUT, Y_CHOICE, 0, NMAX, 0}, + {Y_OUTPUT, Y_CONTAINER, 0, NMAX, 0}, + {Y_OUTPUT, Y_GROUPING, 0, NMAX, 0}, + {Y_OUTPUT, Y_LEAF, 0, NMAX, 0}, + {Y_OUTPUT, Y_LEAF_LIST, 0, NMAX, 0}, + {Y_OUTPUT, Y_LIST, 0, NMAX, 0}, + {Y_OUTPUT, Y_MUST, 0, NMAX, 0}, + {Y_OUTPUT, Y_TYPEDEF, 0, NMAX, 0}, + {Y_OUTPUT, Y_USES, 0, NMAX, 0}, + {Y_PATTERN, Y_DESCRIPTION, 0, 1, 0}, + {Y_PATTERN, Y_ERROR_APP_TAG, 0, 1, 0}, + {Y_PATTERN, Y_ERROR_MESSAGE, 0, 1, 0}, + {Y_PATTERN, Y_MODIFIER, 0, 1, 0}, + {Y_PATTERN, Y_REFERENCE, 0, 1, 0}, + {Y_RANGE, Y_DESCRIPTION, 0, 1, 0}, + {Y_RANGE, Y_ERROR_APP_TAG, 0, 1, 0}, + {Y_RANGE, Y_ERROR_MESSAGE, 0, 1, 0}, + {Y_RANGE, Y_REFERENCE, 0, 1, 0}, + {Y_REVISION, Y_DESCRIPTION, 0, 1, 0}, + {Y_REVISION, Y_REFERENCE, 0, 1, 0}, + {Y_RPC, Y_DESCRIPTION, 0, 1, 0}, + {Y_RPC, Y_GROUPING, 0, NMAX, 0}, + {Y_RPC, Y_IF_FEATURE, 0, NMAX, 0}, + {Y_RPC, Y_INPUT, 0, 1, 0}, + {Y_RPC, Y_OUTPUT, 0, 1, 0}, + {Y_RPC, Y_REFERENCE, 0, 1, 0}, + {Y_RPC, Y_STATUS, 0, 1, 0}, + {Y_RPC, Y_TYPEDEF, 0, NMAX, 0}, + {Y_SUBMODULE, Y_ANYDATA, 0, NMAX, 4}, + {Y_SUBMODULE, Y_AUGMENT, 0, NMAX, 4}, + {Y_SUBMODULE, Y_BELONGS_TO, 1, 1, 0}, + {Y_SUBMODULE, Y_CHOICE, 0, NMAX, 4}, + {Y_SUBMODULE, Y_CONTACT, 0, 1, 2}, + {Y_SUBMODULE, Y_CONTAINER, 0, NMAX, 4}, + {Y_SUBMODULE, Y_DESCRIPTION,0, 1, 2}, + {Y_SUBMODULE, Y_DEVIATION, 0, NMAX, 4}, + {Y_SUBMODULE, Y_EXTENSION, 0, NMAX, 4}, + {Y_SUBMODULE, Y_FEATURE, 0, NMAX, 4}, + {Y_SUBMODULE, Y_GROUPING, 0, NMAX, 4}, + {Y_SUBMODULE, Y_IDENTITY, 0, NMAX, 4}, + {Y_SUBMODULE, Y_IMPORT, 0, NMAX, 1}, + {Y_SUBMODULE, Y_INCLUDE, 0, NMAX, 1}, + {Y_SUBMODULE, Y_LEAF, 0, NMAX, 4}, + {Y_SUBMODULE, Y_LEAF_LIST, 0, NMAX, 4}, + {Y_SUBMODULE, Y_LIST, 0, NMAX, 4}, + {Y_SUBMODULE, Y_NOTIFICATION,0, NMAX, 4}, + {Y_SUBMODULE, Y_ORGANIZATION,0, 1, 2}, + {Y_SUBMODULE, Y_REFERENCE, 0, 1, 2}, + {Y_SUBMODULE, Y_REVISION, 0, NMAX, 3}, + {Y_SUBMODULE, Y_RPC, 0, NMAX, 4}, + {Y_SUBMODULE, Y_TYPEDEF, 0, NMAX, 4}, + {Y_SUBMODULE, Y_USES, 0, NMAX, 4}, + {Y_SUBMODULE, Y_YANG_VERSION,0, 1, 0}, /* "yang-version" statement is mandatory in YANG version "1.1". */ + {Y_TYPE, Y_BASE, 0, NMAX, 0}, + {Y_TYPE, Y_BIT, 0, NMAX, 0}, + {Y_TYPE, Y_ENUM, 0, NMAX, 0}, + {Y_TYPE, Y_FRACTION_DIGITS, 0, 1, 0}, + {Y_TYPE, Y_LENGTH, 0, 1, 0}, + {Y_TYPE, Y_PATH, 0, 1, 0}, + {Y_TYPE, Y_PATTERN, 0, NMAX, 0}, + {Y_TYPE, Y_RANGE, 0, 1, 0}, + {Y_TYPE, Y_REQUIRE_INSTANCE, 0, 1, 0}, + {Y_TYPE, Y_TYPE, 0, NMAX, 0}, + {Y_TYPEDEF, Y_DEFAULT, 0, 1, 0}, + {Y_TYPEDEF, Y_DESCRIPTION,0, 1, 0}, + {Y_TYPEDEF, Y_REFERENCE, 0, 1, 0}, + {Y_TYPEDEF, Y_STATUS, 0, 1, 0}, + {Y_TYPEDEF, Y_TYPE, 1, 1, 0}, + {Y_TYPEDEF, Y_UNITS, 0, 1, 0}, + {Y_USES, Y_AUGMENT, 0, NMAX, 0}, + {Y_USES, Y_DESCRIPTION, 0, 1, 0}, + {Y_USES, Y_IF_FEATURE, 0, NMAX, 0}, + {Y_USES, Y_REFERENCE, 0, 1, 0}, + {Y_USES, Y_REFINE, 0, NMAX, 0}, + {Y_USES, Y_STATUS, 0, 1, 0}, + {Y_USES, Y_WHEN, 0, 1, 0}, {0,} }; @@ -497,12 +506,17 @@ yang_cardinality(clicon_handle h, int nr; const struct ycard *ycplist; /* ycard parent table*/ const struct ycard *yc; + int order; + yang_stmt *yprev = NULL; pk = yang_keyword_get(yt); /* 0) Find parent sub-parts of cardinality vector */ if ((ycplist = ycard_find(pk, 0, yclist, 0)) == NULL) goto ok; /* skip */ - /* 1) For all children, if neither in 0..n, 0..1, 1 or 1..n ->ERROR */ + /* 1) For all children, if neither in 0..n, 0..1, 1 or 1..n ->ERROR + * Also: check monotonically increasing order + */ + order = 0; ys = NULL; while ((ys = yn_each(yt, ys)) != NULL) { ck = yang_keyword_get(ys); @@ -518,7 +532,21 @@ yang_cardinality(clicon_handle h, yang_argument_get(yt)); goto done; } - } + if (order > yc->yc_order){ + clicon_err(OE_YANG, 0, "%s: yang node \"%s\"(%s) which is child of \"%s\"(%s) is not in correct order (should not be after \"%s\"(%s))", + modname, + yang_key2str(ck), + yang_argument_get(ys), + yang_key2str(pk), + yang_argument_get(yt), + yang_key2str(yang_keyword_get(yprev)), + yang_argument_get(yprev)); + goto done; + } + if (order < yc->yc_order) + order = yc->yc_order; + yprev = ys; + } /* 2) For all in 1 and 1..n list, if 0 such children ->ERROR */ for (yc = &ycplist[0]; (int)yc->yc_parent == pk; yc++){ if (yc->yc_min && @@ -541,7 +569,7 @@ yang_cardinality(clicon_handle h, goto done; } } - + /* 4) Recurse */ i = 0; while (i< yang_len_get(yt)){ /* Note, children may be removed cant use yn_each */ diff --git a/test/test_augment.sh b/test/test_augment.sh index 360039d8..a25eaede 100755 --- a/test/test_augment.sh +++ b/test/test_augment.sh @@ -52,8 +52,8 @@ cat < $fyang2 module ietf-interfaces { yang-version 1.1; namespace "urn:ietf:params:xml:ns:yang:ietf-interfaces"; - revision "2019-03-04"; prefix if; + revision "2019-03-04"; identity interface-type { description "Base identity from which specific interface types are @@ -110,10 +110,10 @@ module example-augment { yang-version 1.1; namespace "urn:example:augment"; prefix mymod; - revision "2019-03-04"; import ietf-interfaces { prefix if; } + revision "2019-03-04"; identity some-new-iftype { base if:interface-type; } diff --git a/test/test_augment_state.sh b/test/test_augment_state.sh index 2774cfe2..29e52c4a 100755 --- a/test/test_augment_state.sh +++ b/test/test_augment_state.sh @@ -35,8 +35,8 @@ cat < $fyang2 module example-lib { yang-version 1.1; namespace "urn:example:lib"; - revision "2019-03-04"; prefix lib; + revision "2019-03-04"; container global-state { config false; leaf gbds{ @@ -81,10 +81,10 @@ module example-augment { yang-version 1.1; namespace "urn:example:augment"; prefix aug; - revision "2020-09-25"; import example-lib { prefix lib; } + revision "2020-09-25"; /* Augments global state */ augment "/lib:global-state" { leaf gads{ diff --git a/test/test_augment_trans.sh b/test/test_augment_trans.sh index 07327b5b..0d497527 100755 --- a/test/test_augment_trans.sh +++ b/test/test_augment_trans.sh @@ -38,8 +38,8 @@ cat < $fyang module example-lib { yang-version 1.1; namespace "urn:example:lib"; - revision "2019-03-04"; prefix lib; + revision "2019-03-04"; container base-config { } /* No prefix */ @@ -61,10 +61,10 @@ module example-augment1 { yang-version 1.1; namespace "urn:example:augment1"; prefix aug1; - revision "2020-09-25"; import example-lib { prefix lib; } + revision "2020-09-25"; /* Augments config */ augment "/lib:base-config/lib:parameter" { container aug1{ @@ -80,13 +80,13 @@ module example-augment2 { yang-version 1.1; namespace "urn:example:augment2"; prefix aug2; - revision "2020-09-25"; import example-lib { prefix lib; } import example-augment1 { prefix aug1; } + revision "2020-09-25"; /* Augments config */ augment "/lib:base-config/lib:parameter/aug1:aug1" { /* when 'lib:name="foobar" and aug:aug1="foobar"'; */ diff --git a/test/test_datastore_repair.sh b/test/test_datastore_repair.sh index 4dee548f..d91751fe 100755 --- a/test/test_datastore_repair.sh +++ b/test/test_datastore_repair.sh @@ -37,8 +37,8 @@ EOF cat < $fyangA module A{ prefix a; - revision 2020-02-11; namespace "urn:example:a"; + revision 2020-02-11; container x { container y { } @@ -56,11 +56,11 @@ EOF cat < $fyangB module B{ prefix b; - revision 2020-02-11; namespace "urn:example:b"; import A { prefix "a"; } + revision 2020-02-11; augment "/a:x/a:y" { container z { leaf w { diff --git a/test/test_nacm_ext.sh b/test/test_nacm_ext.sh index 156e6cbf..99767820 100755 --- a/test/test_nacm_ext.sh +++ b/test/test_nacm_ext.sh @@ -46,10 +46,10 @@ cat < $fyang module nacm-example{ yang-version 1.1; namespace "urn:example:nacm"; + prefix nacm; import clixon-example { prefix ex; } - prefix nacm; container authentication { presence "To keep this from auto-expanding"; description "Example code for enabling www basic auth and some example diff --git a/test/test_type.sh b/test/test_type.sh index 77259fed..f6bb5082 100755 --- a/test/test_type.sh +++ b/test/test_type.sh @@ -47,9 +47,9 @@ module example3{ EOF cat < $fyang2 module example2{ - import example3 { prefix ex3; } namespace "urn:example:example2"; prefix ex2; + import example3 { prefix ex3; } grouping gr2 { leaf talle{ type ex3:t; diff --git a/test/test_union.sh b/test/test_union.sh index 61ca59b5..431d4c96 100755 --- a/test/test_union.sh +++ b/test/test_union.sh @@ -50,9 +50,9 @@ module example3{ EOF cat < $fyang2 module example2{ - import example3 { prefix ex3; } namespace "urn:example:example2"; prefix ex2; + import example3 { prefix ex3; } grouping gr2 { leaf talle{ type ex3:t; diff --git a/test/test_upgrade_checkold.sh b/test/test_upgrade_checkold.sh index b9289f7a..ac549169 100755 --- a/test/test_upgrade_checkold.sh +++ b/test/test_upgrade_checkold.sh @@ -44,8 +44,8 @@ EOF cat < $fyang2 module A{ prefix a; - revision 2021-01-01; namespace "urn:example:a"; + revision 2021-01-01; container upgraded{ } } @@ -95,8 +95,8 @@ for oldyang in true false; do cat < $fyang1 module A{ prefix a; - revision 2016-01-01; namespace "urn:example:a"; + revision 2016-01-01; container old{ } } diff --git a/test/test_upgrade_failsafe.sh b/test/test_upgrade_failsafe.sh index 35892d44..2a664627 100755 --- a/test/test_upgrade_failsafe.sh +++ b/test/test_upgrade_failsafe.sh @@ -38,8 +38,8 @@ fyangB=$dir/B@2019-01-01.yang cat < $fyangA0 module A{ prefix a; - revision 0814-01-28; namespace "urn:example:a"; + revision 0814-01-28; leaf a0{ type string; } @@ -53,9 +53,9 @@ EOF cat < $fyangA1 module A{ prefix a; + namespace "urn:example:a"; revision 2019-01-01; revision 0814-01-28; - namespace "urn:example:a"; /* leaf a0 has been removed */ leaf a1{ description "exists in both versions"; @@ -72,8 +72,8 @@ EOF cat < $fyangB module B{ prefix b; - revision 2019-01-01; namespace "urn:example:b"; + revision 2019-01-01; leaf b{ type string; } @@ -84,8 +84,8 @@ EOF cat < /dev/null module C{ prefix c; - revision 2019-01-01; namespace "urn:example:c"; + revision 2019-01-01; leaf c{ type string; } diff --git a/test/test_upgrade_module.sh b/test/test_upgrade_module.sh index 38d19c07..24ab59fa 100755 --- a/test/test_upgrade_module.sh +++ b/test/test_upgrade_module.sh @@ -46,8 +46,8 @@ touch $log cat < $fyangb module B{ prefix b; - revision 2016-01-01; namespace "urn:example:b"; + revision 2016-01-01; container dummy{ } } @@ -258,8 +258,8 @@ function testall() cat < $fyang module A{ prefix a; - revision 2016-01-01; namespace "urn:example:interfaces"; + revision 2016-01-01; container dummy{ } } diff --git a/test/test_upgrade_repair.sh b/test/test_upgrade_repair.sh index 4d3e6e32..728578eb 100755 --- a/test/test_upgrade_repair.sh +++ b/test/test_upgrade_repair.sh @@ -23,8 +23,8 @@ NEWXML='new version' cat < $fyangA0 module A{ prefix a; - revision 0814-01-28; namespace "urn:example:a"; + revision 0814-01-28; leaf a0{ type string; } @@ -38,9 +38,9 @@ EOF cat < $fyangA1 module A{ prefix a; + namespace "urn:example:a"; revision 2019-01-01; revision 0814-01-28; - namespace "urn:example:a"; /* leaf a0 has been removed */ leaf a1{ description "exists in both versions"; diff --git a/test/test_yang_load.sh b/test/test_yang_load.sh index e6465e73..d0f11062 100755 --- a/test/test_yang_load.sh +++ b/test/test_yang_load.sh @@ -22,9 +22,9 @@ fyang3=$dir/other.yang cat < $fyang1 module example{ prefix ex; + namespace "urn:example:clixon"; revision $NEWDATE; revision $OLDDATE; - namespace "urn:example:clixon"; leaf newex{ type string; } @@ -35,8 +35,8 @@ EOF cat < $fyang2 module example{ prefix ex; - revision $OLDDATE; namespace "urn:example:clixon"; + revision $OLDDATE; leaf oldex{ type string; } @@ -47,8 +47,8 @@ EOF cat < $fyang3 module other{ prefix oth; - revision $NEWDATE; namespace "urn:example:clixon2"; + revision $NEWDATE; leaf other{ type string; } From b31107f646566d305fac3608576ba5c45b412ae6 Mon Sep 17 00:00:00 2001 From: Olof Hagsand Date: Fri, 25 Jun 2021 10:46:53 +0000 Subject: [PATCH 08/34] - Restconf native: Fixed ssl/non-ssl read/write behaviour for data that is different in freebsd than in linux - test: removed sed -i in tests since it is not portable between linux and bsd --- apps/restconf/restconf_main_native.c | 38 ++++++++++++++++++++++++---- apps/restconf/restconf_nghttp2.c | 16 ++++++++++-- test/test_debug.sh | 5 +++- test/test_perf_restconf.sh | 20 ++++++++++----- 4 files changed, 64 insertions(+), 15 deletions(-) diff --git a/apps/restconf/restconf_main_native.c b/apps/restconf/restconf_main_native.c index e257ac34..5e7551f2 100644 --- a/apps/restconf/restconf_main_native.c +++ b/apps/restconf/restconf_main_native.c @@ -679,6 +679,8 @@ restconf_connection(int s, ssize_t n; char buf[BUFSIZ]; /* from stdio.h, typically 8K XXX: reduce for test */ int readmore = 1; + int sslerr; + #ifdef HAVE_LIBEVHTP clicon_handle h; evhtp_connection_t *evconn = NULL; @@ -700,21 +702,47 @@ restconf_connection(int s, curl -Ssik --key /var/tmp/./test_restconf_ssl_certs.sh/certs/limited.key --cert /var/tmp/./test_restconf_ssl_certs.sh/certs/limited.crt -X GET https://localhost/restconf/data/example:x */ if ((n = SSL_read(rc->rc_ssl, buf, sizeof(buf))) < 0){ - clicon_err(OE_XML, errno, "SSL_read"); - goto done; + sslerr = SSL_get_error(rc->rc_ssl, n); + clicon_debug(1, "%s SSL_read() n:%ld errno:%d sslerr:%d", __FUNCTION__, n, errno, sslerr); + switch (sslerr){ + case SSL_ERROR_WANT_READ: /* 2 */ + /* SSL_ERROR_WANT_READ is returned when the last operation was a read operation + * from a nonblocking BIO. + * That is, it can happen if restconf_socket_init() below is called + * with SOCK_NONBLOCK + */ + clicon_debug(1, "%s SSL_read SSL_ERROR_WANT_READ", __FUNCTION__); + usleep(1000); + readmore = 1; + break; + default: + clicon_err(OE_XML, errno, "SSL_read"); + goto done; + } /* switch */ + continue; /* readmore */ } } else{ if ((n = read(rc->rc_s, buf, sizeof(buf))) < 0){ /* XXX atomicio ? */ - if (errno == ECONNRESET) {/* Connection reset by peer */ + switch(errno){ + case ECONNRESET:/* Connection reset by peer */ clicon_debug(1, "%s %d Connection reset by peer", __FUNCTION__, rc->rc_s); clixon_event_unreg_fd(rc->rc_s, restconf_connection); close(rc->rc_s); restconf_conn_free(rc); goto ok; /* Close socket and ssl */ + break; + case EAGAIN: + clicon_debug(1, "%s read EAGAIN", __FUNCTION__); + usleep(1000); + readmore = 1; + break; + default:; + clicon_err(OE_XML, errno, "read"); + goto done; + break; } - clicon_err(OE_XML, errno, "read"); - goto done; + continue; } } clicon_debug(1, "%s read:%ld", __FUNCTION__, n); diff --git a/apps/restconf/restconf_nghttp2.c b/apps/restconf/restconf_nghttp2.c index 9360de89..7f963c51 100644 --- a/apps/restconf/restconf_nghttp2.c +++ b/apps/restconf/restconf_nghttp2.c @@ -182,6 +182,7 @@ session_send_callback(nghttp2_session *session, ssize_t totlen = 0; int s; SSL *ssl; + int sslerr; clicon_debug(1, "%s buflen:%lu", __FUNCTION__, buflen); s = rc->rc_s; @@ -190,7 +191,14 @@ session_send_callback(nghttp2_session *session, if (ssl){ if ((len = SSL_write(ssl, buf+totlen, buflen-totlen)) <= 0){ er = errno; - switch (SSL_get_error(ssl, len)){ + sslerr = SSL_get_error(ssl, len); + clicon_debug(1, "%s errno:;%d sslerr:%d", __FUNCTION__, errno, sslerr); + switch (sslerr){ + case SSL_ERROR_WANT_WRITE: /* 3 */ + clicon_debug(1, "%s write SSL_ERROR_WANT_WRITE", __FUNCTION__); + usleep(1000); + continue; + break; case SSL_ERROR_SYSCALL: /* 5 */ if (er == ECONNRESET) {/* Connection reset by peer */ if (ssl) @@ -200,8 +208,12 @@ session_send_callback(nghttp2_session *session, goto ok; /* Close socket and ssl */ } else if (er == EAGAIN){ + /* same as want_write above, but different behaviour on different + * platforms, linux here, freebsd want_write, or possibly differnt + * ssl lib versions? + */ clicon_debug(1, "%s write EAGAIN", __FUNCTION__); - usleep(10000); + usleep(1000); continue; } else{ diff --git a/test/test_debug.sh b/test/test_debug.sh index 8992fb43..e2391e94 100755 --- a/test/test_debug.sh +++ b/test/test_debug.sh @@ -105,6 +105,9 @@ expectpart "$($clixon_cli -1 -f $cfg -l o debug restconf 1)" 0 "^$" new "get and put config using restconf" expectpart "$(curl $CURLOPTS -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data?content=config --next $CURLOPTS -H "Content-Type: application/yang-data+json" -X POST $RCPROTO://localhost/restconf/data -d '{"example:table":{"parameter":{"name":"local0","value":"foo"}}}')" 0 "HTTP/$HVER 200" '' "HTTP/$HVER 201" +# In freebsd, backend dies in stop_restconf below unless sleep +sleep $DEMSLEEP + if [ $RC -ne 0 ]; then new "Kill restconf daemon" stop_restconf @@ -115,7 +118,7 @@ if [ $BE -ne 0 ]; then # Check if premature kill pid=$(pgrep -u root -f clixon_backend) if [ -z "$pid" ]; then - err "backend already dead" + err1 "backend pid !=0" 0 fi # kill backend stop_backend -f $cfg diff --git a/test/test_perf_restconf.sh b/test/test_perf_restconf.sh index b07cd66b..6d8df8db 100755 --- a/test/test_perf_restconf.sh +++ b/test/test_perf_restconf.sh @@ -32,6 +32,7 @@ ftest=$dir/test.xml fconfig=$dir/large.xml fconfig2=$dir/large2.xml # leaf-list foutput=$dir/output.xml +foutput2=$dir/output2.xml cat < $fyang module scaling{ @@ -132,15 +133,20 @@ expecteof "time -p $clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO $foutput +r=$? +if [ $r -ne 0 ]; then + err1 "retval 0" $r +fi # Remove Content-Length line (depends on size) -sed -i '/Content-Length:/d' $foutput -sed -i '/content-length:/d' $foutput +# Note: do not use sed -i since it is not portable between gnu and bsd +sed '/Content-Length:/d' $foutput > $foutput2 && mv $foutput2 $foutput +sed '/content-length:/d' $foutput > $foutput2 && mv $foutput2 $foutput # Remove (nginx) web-server specific lines -sed -i '/Server:/d' $foutput -sed -i '/Date:/d' $foutput -sed -i '/Transfer-Encoding:/d' $foutput -sed -i '/Connection:/d' $foutput +sed '/Server:/d' $foutput > $foutput2 && mv $foutput2 $foutput +sed '/Date:/d' $foutput > $foutput2 && mv $foutput2 $foutput +sed '/Transfer-Encoding:/d' $foutput > $foutput2 && mv $foutput2 $foutput +sed '/Connection:/d' $foutput > $foutput2 && mv $foutput2 $foutput # Create a file to compare with if ${HAVE_LIBNGHTTP2}; then @@ -165,7 +171,7 @@ echo " " >> $ftest ret=$(diff -i $ftest $foutput) if [ $? -ne 0 ]; then - echo "$ret" + echo "diff -i $ftest $foutput" err1 "Matching running-db with $fconfigonly" fi From e451b7f43e7b9843052fc526cc0fea898aeac6ef Mon Sep 17 00:00:00 2001 From: Olof Hagsand Date: Sat, 26 Jun 2021 08:36:24 +0000 Subject: [PATCH 09/34] - changed debug printfs to work for i386 - restconf native centos8: fixed ssl shutdown error - libnghttp2 older version patch --- apps/restconf/restconf_evhtp.c | 2 +- apps/restconf/restconf_main_native.c | 10 +++++----- apps/restconf/restconf_nghttp2.c | 28 +++++++++++++++++++++++----- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/apps/restconf/restconf_evhtp.c b/apps/restconf/restconf_evhtp.c index b69eba42..49245afd 100644 --- a/apps/restconf/restconf_evhtp.c +++ b/apps/restconf/restconf_evhtp.c @@ -396,7 +396,7 @@ restconf_evhtp_reply(restconf_conn *rc, * [RFC7231]). */ if (sd->sd_code != 204 && sd->sd_code > 199) - if (restconf_reply_header(sd, "Content-Length", "%lu", sd->sd_body_len) < 0) + if (restconf_reply_header(sd, "Content-Length", "%zu", sd->sd_body_len) < 0) goto done; /* Create reply and write headers */ if (native_send_reply(rc, sd, req) < 0) diff --git a/apps/restconf/restconf_main_native.c b/apps/restconf/restconf_main_native.c index 5e7551f2..4bbe7b47 100644 --- a/apps/restconf/restconf_main_native.c +++ b/apps/restconf/restconf_main_native.c @@ -265,7 +265,7 @@ buf_write(char *buf, } memcpy(dbgstr, buf, sz); dbgstr[sz] = '\0'; - clicon_debug(1, "%s buflen:%lu buf:%s", __FUNCTION__, buflen, dbgstr); + clicon_debug(1, "%s buflen:%zu buf:%s", __FUNCTION__, buflen, dbgstr); free(dbgstr); } while (totlen < buflen){ @@ -641,7 +641,7 @@ send_badrequest(clicon_handle h, cprintf(cb, "HTTP/1.1 400 Bad Request\r\nConnection: close\r\n"); if (body){ cprintf(cb, "Content-Type: %s\r\n", media); - cprintf(cb, "Content-Length: %lu\r\n", strlen(body)); + cprintf(cb, "Content-Length: %u\r\n", strlen(body)); } else cprintf(cb, "Content-Length: 0\r\n"); @@ -703,7 +703,7 @@ restconf_connection(int s, */ if ((n = SSL_read(rc->rc_ssl, buf, sizeof(buf))) < 0){ sslerr = SSL_get_error(rc->rc_ssl, n); - clicon_debug(1, "%s SSL_read() n:%ld errno:%d sslerr:%d", __FUNCTION__, n, errno, sslerr); + clicon_debug(1, "%s SSL_read() n:%zd errno:%d sslerr:%d", __FUNCTION__, n, errno, sslerr); switch (sslerr){ case SSL_ERROR_WANT_READ: /* 2 */ /* SSL_ERROR_WANT_READ is returned when the last operation was a read operation @@ -745,10 +745,10 @@ restconf_connection(int s, continue; } } - clicon_debug(1, "%s read:%ld", __FUNCTION__, n); + clicon_debug(1, "%s (ssl)read:%zd", __FUNCTION__, n); if (n == 0){ clicon_debug(1, "%s n=0 closing socket", __FUNCTION__); - if (restconf_close_ssl_socket(rc, 1) < 0) + if (restconf_close_ssl_socket(rc, 0) < 0) goto done; restconf_conn_free(rc); rc = NULL; diff --git a/apps/restconf/restconf_nghttp2.c b/apps/restconf/restconf_nghttp2.c index 7f963c51..698e1a6e 100644 --- a/apps/restconf/restconf_nghttp2.c +++ b/apps/restconf/restconf_nghttp2.c @@ -184,7 +184,7 @@ session_send_callback(nghttp2_session *session, SSL *ssl; int sslerr; - clicon_debug(1, "%s buflen:%lu", __FUNCTION__, buflen); + clicon_debug(1, "%s buflen:%zu", __FUNCTION__, buflen); s = rc->rc_s; ssl = rc->rc_ssl; while (totlen < buflen){ @@ -259,7 +259,7 @@ session_send_callback(nghttp2_session *session, clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); return retval; } - clicon_debug(1, "%s retval:%lu", __FUNCTION__, totlen); + clicon_debug(1, "%s retval:%zd", __FUNCTION__, totlen); return totlen; } @@ -379,7 +379,7 @@ restconf_sd_read(nghttp2_session *session, #endif assert(cbuf_len(cb) > sd->sd_body_offset); remain = cbuf_len(cb) - sd->sd_body_offset; - clicon_debug(1, "%s length:%lu totlen:%d, offset:%lu remain:%lu", + clicon_debug(1, "%s length:%zu totlen:%d, offset:%zu remain:%zu", __FUNCTION__, length, cbuf_len(cb), @@ -395,7 +395,7 @@ restconf_sd_read(nghttp2_session *session, } memcpy(buf, cbuf_get(cb) + sd->sd_body_offset, len); sd->sd_body_offset += len; - clicon_debug(1, "%s retval:%lu", __FUNCTION__, len); + clicon_debug(1, "%s retval:%zu", __FUNCTION__, len); return len; } @@ -483,7 +483,7 @@ http2_exec(restconf_conn *rc, * [RFC7231]). */ if (sd->sd_code != 204 && sd->sd_code > 199) - if (restconf_reply_header(sd, "Content-Length", "%lu", sd->sd_body_len) < 0) + if (restconf_reply_header(sd, "Content-Length", "%zu", sd->sd_body_len) < 0) goto done; if (sd->sd_code){ if (restconf_submit_response(session, rc, stream_id, sd) < 0) @@ -835,6 +835,20 @@ on_extension_chunk_recv_callback(nghttp2_session *session, return 0; } +/*! Library provides the error code, and message for debugging purpose. + */ +static int +error_callback(nghttp2_session *session, + const char *msg, + size_t len, + void *user_data) +{ + // restconf_conn *rc = (restconf_conn *)user_data; + clicon_debug(1, "%s", __FUNCTION__); + return 0; +} + +#if (NGHTTP2_VERSION_NUM > 0x011201) /* Unsure of version number */ /*! Library provides the error code, and message for debugging purpose. */ static int @@ -849,6 +863,7 @@ error_callback2(nghttp2_session *session, clicon_err(OE_NGHTTP2, lib_error_code, "%s", msg); return 0; } +#endif /* * XXX see session_recv @@ -944,7 +959,10 @@ http2_session_init(restconf_conn *rc) nghttp2_session_callbacks_set_unpack_extension_callback(callbacks, unpack_extension_callback); #endif nghttp2_session_callbacks_set_on_extension_chunk_recv_callback(callbacks, on_extension_chunk_recv_callback); + nghttp2_session_callbacks_set_error_callback(callbacks, error_callback); +#if (NGHTTP2_VERSION_NUM > 0x011201) /* Unsure of version number */ nghttp2_session_callbacks_set_error_callback2(callbacks, error_callback2); +#endif /* Create session for server use, register callbacks */ if ((ngerr = nghttp2_session_server_new3(&session, callbacks, rc, NULL, NULL)) < 0){ From b46418d774a0a4a513b72d15df0db40569e9cffb Mon Sep 17 00:00:00 2001 From: Olof Hagsand Date: Sat, 26 Jun 2021 21:27:59 +0000 Subject: [PATCH 10/34] Minor test and type fixes --- apps/restconf/restconf_main_native.c | 6 +++++- test/test_restconf_op.sh | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/restconf/restconf_main_native.c b/apps/restconf/restconf_main_native.c index 4bbe7b47..0a4532cb 100644 --- a/apps/restconf/restconf_main_native.c +++ b/apps/restconf/restconf_main_native.c @@ -604,6 +604,10 @@ Note that in this case SSL_ERROR_ZERO_RETURN does not necessarily indicate that } SSL_free(rc->rc_ssl); rc->rc_ssl = NULL; +#ifdef HAVE_LIBEVHTP + if (rc->rc_evconn) + rc->rc_evconn->ssl = NULL; +#endif } if (close(rc->rc_s) < 0){ clicon_err(OE_UNIX, errno, "close"); @@ -641,7 +645,7 @@ send_badrequest(clicon_handle h, cprintf(cb, "HTTP/1.1 400 Bad Request\r\nConnection: close\r\n"); if (body){ cprintf(cb, "Content-Type: %s\r\n", media); - cprintf(cb, "Content-Length: %u\r\n", strlen(body)); + cprintf(cb, "Content-Length: %zu\r\n", strlen(body)); } else cprintf(cb, "Content-Length: 0\r\n"); diff --git a/test/test_restconf_op.sh b/test/test_restconf_op.sh index 23b82abc..4ff5228d 100755 --- a/test/test_restconf_op.sh +++ b/test/test_restconf_op.sh @@ -204,7 +204,7 @@ new "restconf DELETE whole datastore" expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data)" 0 "HTTP/$HVER 204" #--------------- Multiple request in single TCP tests - +# XXX THIS HAS LEAKS IN HTTP/1 ONLY! expectpart "$(curl $CURLOPTS -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data?content=config --next $CURLOPTS -H "Content-Type: application/yang-data+json" -X POST $RCPROTO://localhost/restconf/data -d '{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}')" 0 "HTTP/$HVER 200" '' "HTTP/$HVER 201" #--------------- json type tests From df05acdc9e464af132dd7a7cf4277f12c2733e2c Mon Sep 17 00:00:00 2001 From: Olof Hagsand Date: Sat, 26 Jun 2021 21:39:57 +0000 Subject: [PATCH 11/34] test arm curl different return values --- test/test_restconf_ssl_certs.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_restconf_ssl_certs.sh b/test/test_restconf_ssl_certs.sh index 18a363b7..ba179825 100755 --- a/test/test_restconf_ssl_certs.sh +++ b/test/test_restconf_ssl_certs.sh @@ -291,7 +291,7 @@ EOF expectpart "$(curl $CURLOPTS --key $certdir/limited.key --cert $certdir/limited.crt -X GET $RCPROTO://localhost/restconf/data/example:x 2>&1)" "16 35 55 56" # 55 "certificate expired" new "too weak cert (sign w md5)" - expectpart "$(curl $CURLOPTS --key $certdir/mymd5.key --cert $certdir/mymd5.crt -X GET $RCPROTO://localhost/restconf/data/example:x 2>&1)" 58 "md too weak" + expectpart "$(curl $CURLOPTS --key $certdir/mymd5.key --cert $certdir/mymd5.crt -X GET $RCPROTO://localhost/restconf/data/example:x 2>&1)" "35 58" # "md too weak" # Havent been able to generate "wrong CA" # new "invalid cert from wrong CA" From 3539a80d5e92b7ea189ed6c63a2bdef5bf5f2c2a Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 27 Jun 2021 16:32:10 +0200 Subject: [PATCH 12/34] restconf fcgi mem leak in indata test pkill needs sleep before name settles --- apps/restconf/restconf_api_fcgi.c | 5 ++++- apps/restconf/restconf_api_native.c | 6 +++--- apps/restconf/restconf_root.c | 4 ++++ lib/src/clixon_proc.c | 3 ++- test/lib.sh | 3 ++- test/test_restconf_internal.sh | 8 ++++---- 6 files changed, 19 insertions(+), 10 deletions(-) diff --git a/apps/restconf/restconf_api_fcgi.c b/apps/restconf/restconf_api_fcgi.c index 2eb788f3..7d1c58cd 100644 --- a/apps/restconf/restconf_api_fcgi.c +++ b/apps/restconf/restconf_api_fcgi.c @@ -220,8 +220,11 @@ restconf_reply_send(void *req0, return retval; } -/*! +/*! Get input data from http request, eg such as curl -X PUT http://... * @param[in] req Fastcgi request handle + * @retval indata + * @retval NULL Error + * @note: creates a new cbuf which differs from native api where a pointer is returned */ cbuf * restconf_get_indata(void *req0) diff --git a/apps/restconf/restconf_api_native.c b/apps/restconf/restconf_api_native.c index cf5df644..c4162b2e 100644 --- a/apps/restconf/restconf_api_native.c +++ b/apps/restconf/restconf_api_native.c @@ -172,9 +172,9 @@ restconf_reply_send(void *req0, return retval; } -/*! get input data - * @param[in] req Fastcgi request handle - * @note Pulls up an event buffer and then copies it to a cbuf. This is not efficient. +/*! Get input data from http request, eg such as curl -X PUT http://... + * @param[in] req Request handle + * @note: reuses cbuf from stream-data */ cbuf * restconf_get_indata(void *req0) diff --git a/apps/restconf/restconf_root.c b/apps/restconf/restconf_root.c index 5161f8e8..aa79953e 100644 --- a/apps/restconf/restconf_root.c +++ b/apps/restconf/restconf_root.c @@ -588,6 +588,10 @@ api_root_restconf(clicon_handle h, retval = 0; done: clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); +#ifdef WITH_RESTCONF_FCGI + if (cb) + cbuf_free(cb); +#endif if (xerr) xml_free(xerr); if (username) diff --git a/lib/src/clixon_proc.c b/lib/src/clixon_proc.c index b96fc877..8c51b6e1 100644 --- a/lib/src/clixon_proc.c +++ b/lib/src/clixon_proc.c @@ -648,7 +648,7 @@ clixon_process_operation(clicon_handle h, sched++;/* start: immediate stop/restart: not immediate: wait timeout */ } else{ - clicon_debug(1, "%s name:%s op %s cancelled by wrwap", __FUNCTION__, name, clicon_int2str(proc_operation_map, op0)); + clicon_debug(1, "%s name:%s op %s cancelled by wrap", __FUNCTION__, name, clicon_int2str(proc_operation_map, op0)); } break; /* hit break here */ } @@ -688,6 +688,7 @@ clixon_process_status(clicon_handle h, pe = _proc_entry_list; do { if (strcmp(pe->pe_name, name) == 0){ + clicon_debug(1, "%s found %s pid:%d", __FUNCTION__, name, pe->pe_pid); /* Check if running */ run = 0; if (pe->pe_pid && proc_op_run(pe->pe_pid, &run) < 0) diff --git a/test/lib.sh b/test/lib.sh index 67b7e82c..faee3d50 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -382,9 +382,10 @@ function stop_restconf_pre(){ } # Stop restconf daemon after test -# Two caveats in pkill: +# Some problems with pkill: # 1) Dont use $clixon_restconf (dont work in valgrind) # 2) Dont use -u $WWWUSER since clixon_restconf may drop privileges. +# 3) After fork, it seems to take some time before name is right function stop_restconf(){ sudo pkill -f clixon_restconf if [ $valgrindtest -eq 3 ]; then diff --git a/test/test_restconf_internal.sh b/test/test_restconf_internal.sh index 44cc31ab..905c4162 100755 --- a/test/test_restconf_internal.sh +++ b/test/test_restconf_internal.sh @@ -167,6 +167,7 @@ cat< $startupdb EOF new "kill old restconf" +sleep $DEMSLEEP stop_restconf_pre new "test params: -f $cfg" @@ -276,9 +277,8 @@ if [ $pid0 -eq $pid3 ]; then fi new "kill restconf using kill" -stop_restconf_pre - sleep $DEMSLEEP +stop_restconf_pre new "Wait for restconf to stop" wait_restconf_stopped @@ -378,6 +378,7 @@ cat< $startupdb EOF new "kill old restconf" +sleep $DEMSLEEP stop_restconf_pre new "test params: -f $cfg" @@ -485,10 +486,9 @@ fi #Start backend -s none should start new "kill restconf" +sleep $DEMSLEEP stop_restconf -sleep $DEMSLEEP # Lots of processes need to die before next test - new "endtest" endtest From 54357a2a78faadff73dbd36f75ad09999bf7997a Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 28 Jun 2021 17:33:17 +0200 Subject: [PATCH 13/34] * Fixed: [ restconf patch method unable to chage value to empty string #229](https://github.com/clicon/clixon/issues/229) --- CHANGELOG.md | 1 + lib/src/clixon_datastore_write.c | 35 +++++++++++++++++--------------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e81c6d5..52ae4212 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -110,6 +110,7 @@ Developers may need to change their code ### Corrected Bugs +* Fixed: [ restconf patch method unable to chage value to empty string #229](https://github.com/clicon/clixon/issues/229) * Fixed: [restconf patch method adds redundant namespaces #235](https://github.com/clicon/clixon/issues/235) * Fixed: Restconf HEAD did not work everywhere GET did, such as well-known and exact root. * Fixed: [JSON parsing error for a specific input. #236](https://github.com/clicon/clixon/issues/236) diff --git a/lib/src/clixon_datastore_write.c b/lib/src/clixon_datastore_write.c index a90259fb..1d9ce117 100644 --- a/lib/src/clixon_datastore_write.c +++ b/lib/src/clixon_datastore_write.c @@ -459,7 +459,7 @@ text_modify(clicon_handle h, changed++; if (op==OP_NONE) xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */ - if (x1bstr){ /* empty type does not have body */ + if (x1bstr){ /* empty type does not have body */ /* XXX Here x0 = */ if ((x0b = xml_new("body", x0, CX_BODY)) == NULL) goto done; } @@ -499,23 +499,26 @@ text_modify(clicon_handle h, if (assign_namespace_body(x1, x0) < 0) goto done; } - if ((x0b = xml_body_get(x0)) != NULL){ - x0bstr = xml_value(x0b); - if (x0bstr==NULL || strcmp(x0bstr, x1bstr)){ - if ((op != OP_NONE) && !permit && xnacm){ - if ((ret = nacm_datanode_write(h, x1, x1t, - x0bstr==NULL?NACM_CREATE:NACM_UPDATE, - username, xnacm, cbret)) < 0) - goto done; - if (ret == 0) - goto fail; - } - if (xml_value_set(x0b, x1bstr) < 0) + /* XXX here x1bstr is checked for null, while adding an empty string above */ + if ((x0b = xml_body_get(x0)) == NULL && x1bstr && strlen(x1bstr)){ + if ((x0b = xml_new("body", x0, CX_BODY)) == NULL) + goto done; + } + x0bstr = xml_value(x0b); + if (x0bstr==NULL || strcmp(x0bstr, x1bstr)){ + if ((op != OP_NONE) && !permit && xnacm){ + if ((ret = nacm_datanode_write(h, x1, x1t, + x0bstr==NULL?NACM_CREATE:NACM_UPDATE, + username, xnacm, cbret)) < 0) goto done; - /* If a default value ies replaced, then reset default flag */ - if (xml_flag(x0, XML_FLAG_DEFAULT)) - xml_flag_reset(x0, XML_FLAG_DEFAULT); + if (ret == 0) + goto fail; } + if (xml_value_set(x0b, x1bstr) < 0) + goto done; + /* If a default value ies replaced, then reset default flag */ + if (xml_flag(x0, XML_FLAG_DEFAULT)) + xml_flag_reset(x0, XML_FLAG_DEFAULT); } } /* x1bstr */ if (changed){ From 96c9296056ff592f220ff85050622f276ca5ab55 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 28 Jun 2021 22:43:33 +0200 Subject: [PATCH 14/34] testcase for patch empty field --- test/test_restconf_internal.sh | 6 ++---- test/test_restconf_patch.sh | 13 +++++++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/test/test_restconf_internal.sh b/test/test_restconf_internal.sh index 905c4162..41e449f5 100755 --- a/test/test_restconf_internal.sh +++ b/test/test_restconf_internal.sh @@ -167,7 +167,6 @@ cat< $startupdb EOF new "kill old restconf" -sleep $DEMSLEEP stop_restconf_pre new "test params: -f $cfg" @@ -276,9 +275,8 @@ if [ $pid0 -eq $pid3 ]; then err1 "A different pid" "same pid: $pid3" fi -new "kill restconf using kill" -sleep $DEMSLEEP -stop_restconf_pre +new "kill restconf" +sudo kill $pid3 new "Wait for restconf to stop" wait_restconf_stopped diff --git a/test/test_restconf_patch.sh b/test/test_restconf_patch.sh index 9f3531f7..02cf7c88 100755 --- a/test/test_restconf_patch.sh +++ b/test/test_restconf_patch.sh @@ -91,6 +91,9 @@ cat < $dir/example-system.yang leaf enable-jukebox-streaming { type boolean; } + leaf extraleaf { + type string; + } } } EOF @@ -250,6 +253,16 @@ expectpart "$(curl -u andy:bar $CURLOPTS -X PATCH -H "Content-Type: application new "GET check" # XXX: "data" should probably be namespaced? expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data?content=config -H 'Accept: application/yang-data+xml')" 0 "HTTP/$HVER 200" 'c' '' +new "Add empty leaf" +expectpart "$(curl -u andy:bar $CURLOPTS -X POST $RCPROTO://localhost/restconf/data -H 'Content-Type: application/yang-data+json' -d '{"example-system:system":{"extraleaf":""}}')" 0 "HTTP/$HVER 201" + +new "Add entry with PATCH" +expectpart "$(curl -u andy:bar $CURLOPTS -X PATCH $RCPROTO://localhost/restconf/data/example-system:system -H 'Content-Type: application/yang-data+json' -d '{"example-system:system":{"extraleaf":"something"}}')" 0 "HTTP/$HVER 204" + +new "GET check" +expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example-system:system -H 'Accept: application/yang-data+xml')" 0 "HTTP/$HVER 200" 'something' + + if [ $RC -ne 0 ]; then new "Kill restconf daemon" stop_restconf From 85e2945ec9109de35e7d968ea99492e69e508c09 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Wed, 30 Jun 2021 10:59:10 +0200 Subject: [PATCH 15/34] * Netconf message-id attribute changed from optional to mandatory * Made NETCONF message handling more strict according to RFC 6241 * Set `CLICON_NETCONF_MESSAGE_ID_OPTIONAL` to true to accept omission of message-id attribute * Fixed: [need make sure message-id exist in rpc validate #240](https://github.com/clicon/clixon/issues/240) --- CHANGELOG.md | 12 ++- apps/backend/backend_main.c | 5 -- apps/cli/cli_main.c | 5 -- apps/cli/cli_show.c | 4 +- apps/restconf/restconf_main_fcgi.c | 4 - apps/restconf/restconf_main_native.c | 4 - apps/restconf/restconf_methods.c | 10 ++- apps/restconf/restconf_methods_post.c | 13 ++-- apps/restconf/restconf_stream_fcgi.c | 4 +- lib/clixon/clixon_netconf_lib.h | 5 ++ lib/clixon/clixon_xml_bind.h | 1 + lib/src/clixon_client.c | 4 +- lib/src/clixon_netconf_lib.c | 95 +++++++++++++++++------ lib/src/clixon_proto_client.c | 60 +++++++++----- lib/src/clixon_xml_bind.c | 30 ++++++- test/test_c++.sh | 2 +- test/test_client.sh | 2 +- test/test_nacm_module_read.sh | 4 +- test/test_netconf.sh | 4 + test/test_restconf.sh | 2 +- test/test_rpc.sh | 4 +- yang/clixon/clixon-config@2021-05-20.yang | 11 +++ 22 files changed, 196 insertions(+), 89 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52ae4212..dcdc822c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,10 @@ Expected: June 2021 Users may have to change how they access the system +* Netconf message-id attribute changed from optional to mandatory + * Made NETCONF message handling more strict according to RFC 6241 + * Set `CLICON_NETCONF_MESSAGE_ID_OPTIONAL` to true to accept omission of message-id attribute + * See also [need make sure message-id exist in rpc validate #240](https://github.com/clicon/clixon/issues/240) * Changed config and install options for Restconf * clixon_restconf daemon is installed in /usr/local/sbin (as clixon_backend), instead of /www-data * `configure --with-wwwdir=` remains but only applies to fcgi socket and log @@ -64,11 +68,13 @@ Users may have to change how they access the system * Restconf drop privileges user is defined by `CLICON_RESTCONF_USER` * `configure --with-wwwuser=` is removed * clixon_restconf drop of privileges is defined by `CLICON_RESTCONF_PRIVILEGES` option -* New clixon-config@2020-05-20.yang revision +* New clixon-config@2021-05-20.yang revision * Added: `CLICON_RESTCONF_USER` * Added: `CLICON_RESTCONF_PRIVILEGES` * Added: `CLICON_RESTCONF_INSTALLDIR` + * Added: `CLICON_RESTCONF_STARTUP_DONTUPDATE` * Added: `CLICON_RESTCONF_STARTUP_DONTUPDATE` + * Added: `CLICON_NETCONF_MESSAGE_ID_OPTIONAL` * New clixon-restconf@2020-05-20.yang revision * Added: restconf `log-destination` * RESTCONF error replies have changed @@ -110,7 +116,9 @@ Developers may need to change their code ### Corrected Bugs -* Fixed: [ restconf patch method unable to chage value to empty string #229](https://github.com/clicon/clixon/issues/229) +* Fixed: [need make sure message-id exist in rpc validate #240](https://github.com/clicon/clixon/issues/240) + * Netconf message-id attribute changed from optional to mandatory (see API changes) +* Fixed: [restconf patch method unable to chage value to empty string #229](https://github.com/clicon/clixon/issues/229) * Fixed: [restconf patch method adds redundant namespaces #235](https://github.com/clicon/clixon/issues/235) * Fixed: Restconf HEAD did not work everywhere GET did, such as well-known and exact root. * Fixed: [JSON parsing error for a specific input. #236](https://github.com/clicon/clixon/issues/236) diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index 36494aff..9c10bd16 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -747,11 +747,6 @@ main(int argc, clicon_configfile(h)); goto done; } - - /* Treat unknown XML as anydata */ - if (clicon_option_bool(h, "CLICON_YANG_UNKNOWN_ANYDATA") == 1) - xml_bind_yang_unknown_anydata(1); - /* Publish stream on pubsub channels. * CLICON_STREAM_PUB should be set to URL to where streams are published * and configure should be run with --enable-publish diff --git a/apps/cli/cli_main.c b/apps/cli/cli_main.c index a78a128b..e965868f 100644 --- a/apps/cli/cli_main.c +++ b/apps/cli/cli_main.c @@ -635,11 +635,6 @@ main(int argc, goto done; /* Set default namespace according to CLICON_NAMESPACE_NETCONF_DEFAULT */ xml_nsctx_namespace_netconf_default(h); - - /* Treat unknwon XML as anydata */ - if (clicon_option_bool(h, "CLICON_YANG_UNKNOWN_ANYDATA") == 1) - xml_bind_yang_unknown_anydata(1); - /* Create top-level and store as option */ if ((yspec = yspec_new()) == NULL) goto done; diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index 28947ea4..114f178b 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -495,8 +495,8 @@ cli_show_config1(clicon_handle h, cli_xml2cli(xc, prefix, gt, cligen_output); /* cli syntax */ break; case FORMAT_NETCONF: - cligen_output(stdout, "\n", - NETCONF_BASE_NAMESPACE); + cligen_output(stdout, "\n", + NETCONF_BASE_NAMESPACE, NETCONF_MESSAGE_ID_ATTR); xc = NULL; /* Dont print xt itself */ while ((xc = xml_child_each(xt, xc, -1)) != NULL) cli_xml2file(xc, 2, 1, cligen_output); diff --git a/apps/restconf/restconf_main_fcgi.c b/apps/restconf/restconf_main_fcgi.c index 162f10c8..88622b6a 100644 --- a/apps/restconf/restconf_main_fcgi.c +++ b/apps/restconf/restconf_main_fcgi.c @@ -375,10 +375,6 @@ main(int argc, if ((yspec = yspec_new()) == NULL) goto done; clicon_dbspec_yang_set(h, yspec); - /* Treat unknown XML as anydata */ - if (clicon_option_bool(h, "CLICON_YANG_UNKNOWN_ANYDATA") == 1) - xml_bind_yang_unknown_anydata(1); - /* Initialize plugin module by creating a handle holding plugin and callback lists */ if (clixon_plugin_module_init(h) < 0) goto done; diff --git a/apps/restconf/restconf_main_native.c b/apps/restconf/restconf_main_native.c index 0a4532cb..ac89089a 100644 --- a/apps/restconf/restconf_main_native.c +++ b/apps/restconf/restconf_main_native.c @@ -1687,10 +1687,6 @@ restconf_clixon_init(clicon_handle h, if ((yspec = yspec_new()) == NULL) goto done; clicon_dbspec_yang_set(h, yspec); - /* Treat unknown XML as anydata */ - if (clicon_option_bool(h, "CLICON_YANG_UNKNOWN_ANYDATA") == 1) - xml_bind_yang_unknown_anydata(1); - /* Load restconf plugins before yangs are loaded (eg extension callbacks) */ if ((dir = clicon_restconf_dir(h)) != NULL) if (clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir, NULL) < 0) diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 40d8fd99..10844869 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -513,11 +513,12 @@ api_data_write(clicon_handle h, /* Create text buffer for transfer to backend */ if ((cbx = cbuf_new()) == NULL) goto done; - cprintf(cbx, "", + cprintf(cbx, "", NETCONF_BASE_NAMESPACE, username?username:"", NETCONF_BASE_PREFIX, - NETCONF_BASE_NAMESPACE); /* bind nc to netconf namespace */ + NETCONF_BASE_NAMESPACE, /* bind nc to netconf namespace */ + NETCONF_MESSAGE_ID_ATTR); cprintf(cbx, "", + cprintf(cbx, "", NETCONF_BASE_NAMESPACE, username?username:"", NETCONF_BASE_PREFIX, - NETCONF_BASE_NAMESPACE); /* bind nc to netconf namespace */ + NETCONF_BASE_NAMESPACE, + NETCONF_MESSAGE_ID_ATTR); /* bind nc to netconf namespace */ cprintf(cbx, "", + cprintf(cbx, "", NETCONF_BASE_NAMESPACE, username?username:"", NETCONF_BASE_PREFIX, - NETCONF_BASE_NAMESPACE); /* bind nc to netconf namespace */ + NETCONF_BASE_NAMESPACE, + NETCONF_MESSAGE_ID_ATTR); /* bind nc to netconf namespace */ cprintf(cbx, " */ if ((username = clicon_username_get(h)) != NULL){ - if (clixon_xml_parse_va(YB_NONE, NULL, &xtop, NULL, "", - NETCONF_BASE_NAMESPACE, username) < 0) + if (clixon_xml_parse_va(YB_NONE, NULL, &xtop, NULL, "", + NETCONF_BASE_NAMESPACE, username, NETCONF_MESSAGE_ID_ATTR) < 0) goto done; } else - if (clixon_xml_parse_va(YB_NONE, NULL, &xtop, NULL, "", - NETCONF_BASE_NAMESPACE) < 0) + if (clixon_xml_parse_va(YB_NONE, NULL, &xtop, NULL, "", + NETCONF_BASE_NAMESPACE, NETCONF_MESSAGE_ID_ATTR) < 0) goto done; if (xml_rootchild(xtop, 0, &xtop) < 0) goto done; diff --git a/apps/restconf/restconf_stream_fcgi.c b/apps/restconf/restconf_stream_fcgi.c index 680f8680..ddac456a 100644 --- a/apps/restconf/restconf_stream_fcgi.c +++ b/apps/restconf/restconf_stream_fcgi.c @@ -269,8 +269,8 @@ restconf_stream(clicon_handle h, clicon_err(OE_XML, errno, "cbuf_new"); goto done; } - cprintf(cb, "%s", - NETCONF_BASE_NAMESPACE, EVENT_RFC5277_NAMESPACE, name); + cprintf(cb, "%s", + NETCONF_BASE_NAMESPACE, NETCONF_MESSAGE_ID_ATTR, EVENT_RFC5277_NAMESPACE, name); /* Print all fields */ for (i=0; i" + cprintf(msg, "" "<%slock><%s/>", NETCONF_BASE_NAMESPACE, + NETCONF_MESSAGE_ID_ATTR, lock?"":"un", db, lock?"":"un"); if (clicon_rpc1(sock, msg, msgret) < 0) goto done; @@ -422,6 +423,7 @@ clixon_client_get_xdata(int sock, cprintf(msg, "<%s/>", db); if (xpath && strlen(xpath)){ cprintf(msg, "<%s:filter %s:type=\"xpath\" xmlns=\"%s\" %s:select=\"%s\"", diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c index d9be082a..febf5b93 100644 --- a/lib/src/clixon_netconf_lib.c +++ b/lib/src/clixon_netconf_lib.c @@ -65,6 +65,7 @@ #include "clixon_xml.h" #include "clixon_options.h" #include "clixon_data.h" +#include "clixon_xml_bind.h" #include "clixon_xml_map.h" #include "clixon_xml_io.h" #include "clixon_xpath_ctx.h" @@ -228,44 +229,82 @@ netconf_too_big(cbuf *cb, goto done; } +/*! Create Netconf missing-attribute error XML tree according to RFC 6241 App A + * + * An expected attribute is missing. + * @param[out] xret Error XML tree. Free with xml_free after use + * @param[in] type Error type: "rpc", "application" or "protocol" + * @param[in] attr bad-attribute and/or bad-element xml + * @param[in] message Error message (will be XML encoded) + */ +int +netconf_missing_attribute_xml(cxobj **xret, + char *type, + char *attr, + char *message) +{ + int retval = -1; + cxobj *xerr = NULL; + char *encstr = NULL; + cxobj *xa; + + if (*xret == NULL){ + if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) + goto done; + } + else if (xml_name_set(*xret, "rpc-reply") < 0) + goto done; + if ((xa = xml_new("xmlns", *xret, CX_ATTR)) == NULL) + goto done; + if (xml_value_set(xa, NETCONF_BASE_NAMESPACE) < 0) + goto done; + if ((xerr = xml_new("rpc-error", *xret, CX_ELMNT)) == NULL) + goto done; + if (clixon_xml_parse_va(YB_NONE, NULL, &xerr, NULL, "%s" + "missing-attribute" + "%s" + "error", type, attr) < 0) + goto done; + if (message){ + if (xml_chardata_encode(&encstr, "%s", message) < 0) + goto done; + if (clixon_xml_parse_va(YB_NONE, NULL, &xerr, NULL, "%s", + encstr) < 0) + goto done; + } + retval = 0; + done: + if (encstr) + free(encstr); + return retval; +} + /*! Create Netconf missing-attribute error XML tree according to RFC 6241 App A * * An expected attribute is missing. * @param[out] cb CLIgen buf. Error XML is written in this buffer * @param[in] type Error type: "rpc", "application" or "protocol" - * @param[in] info bad-attribute or bad-element xml - * @param[in] message Error message (will be XML encoded) + * @param[in] attr bad-attribute + * @param[in] message Error message (will be XML encoded) or NULL */ int netconf_missing_attribute(cbuf *cb, char *type, - char *info, + char *attr, char *message) { - int retval = -1; - char *encstr = NULL; + int retval = -1; + cxobj *xret = NULL; - if (cprintf(cb, "" - "%s" - "missing-attribute" - "%s" - "error", - NETCONF_BASE_NAMESPACE, type, info) <0) - goto err; - if (message){ - if (xml_chardata_encode(&encstr, "%s", message) < 0) - goto done; - if (cprintf(cb, "%s", encstr) < 0) - goto err; - } - if (cprintf(cb, "") <0) - goto err; + if (netconf_missing_attribute_xml(&xret, type, attr, message) < 0) + goto done; + if (clicon_xml2cbuf(cb, xret, 0, 0, -1) < 0) + goto done; retval = 0; done: + if (xret) + xml_free(xret); return retval; - err: - clicon_err(OE_XML, errno, "cprintf"); - goto done; } /*! Create Netconf bad-attribute error XML tree according to RFC 6241 App A @@ -1429,6 +1468,16 @@ netconf_module_load(clicon_handle h) /* Load restconf yang. Note this is also a part of clixon-config */ if (yang_spec_parse_module(h, "clixon-restconf", NULL, yspec)< 0) goto done; +#if 1 + /* XXX: Both the following settings are because clicon-handle is not part of all API + * functions + * Treat unknown XML as anydata */ + if (clicon_option_bool(h, "CLICON_YANG_UNKNOWN_ANYDATA") == 1) + xml_bind_yang_unknown_anydata(1); + /* Make message-id attribute optional */ + if (clicon_option_bool(h, "CLICON_NETCONF_MESSAGE_ID_OPTIONAL") == 1) + xml_bind_netconf_message_id_optional(1); +#endif retval = 0; done: return retval; diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index fb22d5dc..c81a6ae7 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -451,6 +451,7 @@ clicon_rpc_get_config(clicon_handle h, cprintf(cb, " username=\"%s\"", username); cprintf(cb, " xmlns:%s=\"%s\"", NETCONF_BASE_PREFIX, NETCONF_BASE_NAMESPACE); + cprintf(cb, " %s", NETCONF_MESSAGE_ID_ATTR); cprintf(cb, "><%s/>", db); if (xpath && strlen(xpath)){ cprintf(cb, "<%s:filter %s:type=\"xpath\" %s:select=\"%s\"", @@ -541,6 +542,7 @@ clicon_rpc_edit_config(clicon_handle h, cprintf(cb, " xmlns:%s=\"%s\"", NETCONF_BASE_PREFIX, NETCONF_BASE_NAMESPACE); if ((username = clicon_username_get(h)) != NULL) cprintf(cb, " username=\"%s\"", username); + cprintf(cb, " %s", NETCONF_MESSAGE_ID_ATTR); cprintf(cb, "><%s/>", db); cprintf(cb, "%s", xml_operation2str(op)); @@ -595,10 +597,11 @@ clicon_rpc_copy_config(clicon_handle h, goto done; username = clicon_username_get(h); if ((msg = clicon_msg_encode(session_id, - "" + "" "<%s/><%s/>", NETCONF_BASE_NAMESPACE, username?username:"", + NETCONF_MESSAGE_ID_ATTR, db1, db2)) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret) < 0) @@ -641,10 +644,12 @@ clicon_rpc_delete_config(clicon_handle h, goto done; username = clicon_username_get(h); if ((msg = clicon_msg_encode(session_id, - "" + "" "<%s/>none", NETCONF_BASE_NAMESPACE, - username?username:"", db)) == NULL) + username?username:"", + NETCONF_MESSAGE_ID_ATTR, + db)) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret) < 0) goto done; @@ -682,10 +687,12 @@ clicon_rpc_lock(clicon_handle h, goto done; username = clicon_username_get(h); if ((msg = clicon_msg_encode(session_id, - "" + "" "<%s/>", NETCONF_BASE_NAMESPACE, - username?username:"", db)) == NULL) + username?username:"", + NETCONF_MESSAGE_ID_ATTR, + db)) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret) < 0) goto done; @@ -723,10 +730,12 @@ clicon_rpc_unlock(clicon_handle h, goto done; username = clicon_username_get(h); if ((msg = clicon_msg_encode(session_id, - "" + "" "<%s/>", NETCONF_BASE_NAMESPACE, - username?username:"", db)) == NULL) + username?username:"", + NETCONF_MESSAGE_ID_ATTR, + db)) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret) < 0) goto done; @@ -805,6 +814,7 @@ clicon_rpc_get(clicon_handle h, cprintf(cb, " username=\"%s\"", username); cprintf(cb, " xmlns:%s=\"%s\"", NETCONF_BASE_PREFIX, NETCONF_BASE_NAMESPACE); + cprintf(cb, " %s", NETCONF_MESSAGE_ID_ATTR); cprintf(cb, ">", - NETCONF_BASE_NAMESPACE, username?username:"", 42)) == NULL) + "", + NETCONF_BASE_NAMESPACE, username?username:"", + NETCONF_MESSAGE_ID_ATTR)) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret) < 0) goto done; @@ -932,9 +943,11 @@ clicon_rpc_kill_session(clicon_handle h, goto done; username = clicon_username_get(h); if ((msg = clicon_msg_encode(my_session_id, - "%u", + "%u", NETCONF_BASE_NAMESPACE, - username?username:"", session_id)) == NULL) + username?username:"", + NETCONF_MESSAGE_ID_ATTR, + session_id)) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret) < 0) goto done; @@ -972,9 +985,11 @@ clicon_rpc_validate(clicon_handle h, goto done; username = clicon_username_get(h); if ((msg = clicon_msg_encode(session_id, - "<%s/>", + "<%s/>", NETCONF_BASE_NAMESPACE, - username?username:"", db)) == NULL) + username?username:"", + NETCONF_MESSAGE_ID_ATTR, + db)) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret) < 0) goto done; @@ -1010,9 +1025,10 @@ clicon_rpc_commit(clicon_handle h) goto done; username = clicon_username_get(h); if ((msg = clicon_msg_encode(session_id, - "", + "", NETCONF_BASE_NAMESPACE, - username?username:"")) == NULL) + username?username:"", + NETCONF_MESSAGE_ID_ATTR)) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret) < 0) goto done; @@ -1048,9 +1064,10 @@ clicon_rpc_discard_changes(clicon_handle h) goto done; username = clicon_username_get(h); if ((msg = clicon_msg_encode(session_id, - "", + "", NETCONF_BASE_NAMESPACE, - username?username:"")) == NULL) + username?username:"", + NETCONF_MESSAGE_ID_ATTR)) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret) < 0) goto done; @@ -1094,12 +1111,13 @@ clicon_rpc_create_subscription(clicon_handle h, goto done; username = clicon_username_get(h); if ((msg = clicon_msg_encode(session_id, - "" + "" "%s" "" "", NETCONF_BASE_NAMESPACE, username?username:"", + NETCONF_MESSAGE_ID_ATTR, EVENT_RFC5277_NAMESPACE, stream?stream:"", filter?filter:"")) == NULL) goto done; @@ -1139,9 +1157,10 @@ clicon_rpc_debug(clicon_handle h, goto done; username = clicon_username_get(h); if ((msg = clicon_msg_encode(session_id, - "%d", + "%d", NETCONF_BASE_NAMESPACE, username?username:"", + NETCONF_MESSAGE_ID_ATTR, CLIXON_LIB_NS, level)) == NULL) goto done; @@ -1189,9 +1208,10 @@ clicon_rpc_restconf_debug(clicon_handle h, goto done; username = clicon_username_get(h); if ((msg = clicon_msg_encode(session_id, - "%d", + "%d", NETCONF_BASE_NAMESPACE, username?username:"", + NETCONF_MESSAGE_ID_ATTR, CLIXON_RESTCONF_NS, level)) == NULL) goto done; diff --git a/lib/src/clixon_xml_bind.c b/lib/src/clixon_xml_bind.c index 35050cc5..7d38bff6 100644 --- a/lib/src/clixon_xml_bind.c +++ b/lib/src/clixon_xml_bind.c @@ -83,6 +83,7 @@ * Local variables */ static int _yang_unknown_anydata = 0; +static int _netconf_message_id_optional = 0; /*! Kludge to equate unknown XML with anydata * The problem with this is that its global and should be bound to a handle @@ -94,6 +95,16 @@ xml_bind_yang_unknown_anydata(int val) return 0; } +/*! Kludge to set message_id_optional + * The problem with this is that its global and should be bound to a handle + */ +int +xml_bind_netconf_message_id_optional(int val) +{ + _netconf_message_id_optional = val; + return 0; +} + /*! After yang binding, bodies of containers and lists are stripped from XML bodies * May apply to other nodes? */ @@ -510,16 +521,16 @@ xml_bind_yang0(cxobj *xt, * @retval -1 Error * The * @code - * if (xml_bind_yang_rpc(h, x, NULL) < 0) + * if (xml_bind_yang_rpc(x, NULL) < 0) * err; * @endcode * @see xml_bind_yang For other generic cases * @see xml_bind_yang_rpc_reply */ int -xml_bind_yang_rpc(cxobj *xrpc, - yang_stmt *yspec, - cxobj **xerr) +xml_bind_yang_rpc(cxobj *xrpc, + yang_stmt *yspec, + cxobj **xerr) { int retval = -1; yang_stmt *yrpc = NULL; /* yang node */ @@ -576,6 +587,17 @@ xml_bind_yang_rpc(cxobj *xrpc, goto done; goto fail; } + if (_netconf_message_id_optional == 0){ + /* RFC 6241 4.1: + * The element has a mandatory attribute "message-id" + */ + if (xml_find_type(xrpc, NULL, "message-id", CX_ATTR) == NULL){ + if (xerr && + netconf_missing_attribute_xml(xerr, "rpc", "message-id", "Incoming rpc") < 0) + goto done; + goto fail; + } + } x = NULL; while ((x = xml_child_each(xrpc, x, CX_ELMNT)) != NULL) { rpcname = xml_name(x); diff --git a/test/test_c++.sh b/test/test_c++.sh index 497058e3..c9be6f2a 100755 --- a/test/test_c++.sh +++ b/test/test_c++.sh @@ -154,7 +154,7 @@ new "wait backend" wait_backend new "Netconf runtime test" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO0]]>]]>" '^042]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO0]]>]]>" "^042]]>]]>$" if [ $BE -ne 0 ]; then new "Kill backend" diff --git a/test/test_client.sh b/test/test_client.sh index 7a13092c..ed37410c 100755 --- a/test/test_client.sh +++ b/test/test_client.sh @@ -91,7 +91,7 @@ main(int argc, /* Provide a clixon config-file, get a clixon handle */ if ((h = clixon_client_init("$cfg")) == NULL) return -1; - /* Make a conenction over netconf or ssh/netconf */ + /* Make a connection over netconf or ssh/netconf */ if ((ch = clixon_client_connect(h, CLIXON_CLIENT_NETCONF)) == NULL) return -1; diff --git a/test/test_nacm_module_read.sh b/test/test_nacm_module_read.sh index 660308c0..80f2bb1c 100755 --- a/test/test_nacm_module_read.sh +++ b/test/test_nacm_module_read.sh @@ -232,13 +232,13 @@ new "limit rpc ok" expectpart "$(curl -u wilma:bar $CURLOPTS -X POST $RCPROTO://localhost/restconf/operations/clixon-example:example -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":42}}' )" 0 "HTTP/$HVER 200" '{"clixon-example:output":{"x":"42","y":"42"}}' new "limit rpc netconf ok" -expecteof "$clixon_netconf -U wilma -qf $cfg" 0 "$DEFAULTHELLO0]]>]]>" '^042]]>]]>$' +expecteof "$clixon_netconf -U wilma -qf $cfg" 0 "$DEFAULTHELLO0]]>]]>" "^042]]>]]>$" new "guest rpc fail" expectpart "$(curl -u guest:bar $CURLOPTS -X POST $RCPROTO://localhost/restconf/operations/clixon-example:example -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":42}}' )" 0 "HTTP/$HVER 403" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}' new "guest rpc netconf fail" -expecteof "$clixon_netconf -U guest -qf $cfg" 0 "$DEFAULTHELLO0]]>]]>" '^applicationaccess-deniederroraccess denied]]>]]>$' +expecteof "$clixon_netconf -U guest -qf $cfg" 0 "$DEFAULTHELLO0]]>]]>" "^applicationaccess-deniederroraccess denied]]>]]>$" #------------------ Set read-default permit diff --git a/test/test_netconf.sh b/test/test_netconf.sh index dba65cf7..6339b0bc 100755 --- a/test/test_netconf.sh +++ b/test/test_netconf.sh @@ -26,6 +26,7 @@ cat < $cfg /usr/local/lib/$APPNAME/backend example_backend.so$ /usr/local/lib/$APPNAME/netconf + false /usr/local/lib/$APPNAME/restconf /usr/local/lib/$APPNAME/cli $APPNAME @@ -67,6 +68,9 @@ expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^protocolunknown-elementxxxerrorUnrecognized netconf operation]]>]]>$" +new "Frame without message-id attribute" +expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^rpcmissing-attributemessage-iderrorIncoming rpc]]>]]>$" + new "netconf rcv hello, disable RFC7895/ietf-yang-library" expecteof "$clixon_netconf -f $cfg -o CLICON_MODULE_LIBRARY_RFC7895=0" 0 "$DEFAULTHELLO]]>]]>" "^urn:ietf:params:netconf:base:1.1urn:ietf:params:netconf:base:1.0urn:ietf:params:netconf:capability:candidate:1.0urn:ietf:params:netconf:capability:validate:1.1urn:ietf:params:netconf:capability:startup:1.0urn:ietf:params:netconf:capability:xpath:1.0urn:ietf:params:netconf:capability:notification:1.0[0-9]*]]>]]>]]>]]>$" diff --git a/test/test_restconf.sh b/test/test_restconf.sh index 99b227ec..91df06ca 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -393,7 +393,7 @@ function testrun() new "restconf rpc using POST xml" ret=$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":42}}' $proto://$addr/restconf/operations/clixon-example:example) - expect='4242' + expect='4242' match=`echo $ret | grep --null -Eo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" diff --git a/test/test_rpc.sh b/test/test_rpc.sh index f0d61b17..9b8acd9c 100755 --- a/test/test_rpc.sh +++ b/test/test_rpc.sh @@ -89,10 +89,10 @@ new "restconf example rpc xml/json" expectpart "$(curl $CURLOPTS -X POST -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+json' -d '0' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 'Content-Type: application/yang-data+json' '{"clixon-example:output":{"x":"0","y":"42"}}' new "restconf example rpc json/xml" -expectpart "$(curl $CURLOPTS -X POST -H 'Content-Type: application/yang-data+json' -H 'Accept: application/yang-data+xml' -d '{"clixon-example:input":{"x":"0"}}' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 'Content-Type: application/yang-data+xml' '042' +expectpart "$(curl $CURLOPTS -X POST -H 'Content-Type: application/yang-data+json' -H 'Accept: application/yang-data+xml' -d '{"clixon-example:input":{"x":"0"}}' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 'Content-Type: application/yang-data+xml' '042' new "restconf example rpc xml/xml" -expectpart "$(curl $CURLOPTS -X POST -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+xml' -d '0' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 'Content-Type: application/yang-data+xml' '042' +expectpart "$(curl $CURLOPTS -X POST -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+xml' -d '0' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 'Content-Type: application/yang-data+xml' '042' new "restconf example rpc xml in w json encoding (expect fail)" expectpart "$(curl $CURLOPTS -X POST -H 'Content-Type: application/yang-data+json' -H 'Accept: application/yang-data+xml' -d '0' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 "HTTP/$HVER 400" "rpcmalformed-messageerrorjson_parse: line 1: syntax error at or before: '<'" diff --git a/yang/clixon/clixon-config@2021-05-20.yang b/yang/clixon/clixon-config@2021-05-20.yang index 4a8cca7b..0e729594 100644 --- a/yang/clixon/clixon-config@2021-05-20.yang +++ b/yang/clixon/clixon-config@2021-05-20.yang @@ -50,6 +50,7 @@ module clixon-config { CLICON_RESTCONF_PRIVILEGES CLICON_RESTCONF_INSTALLDIR CLICON_RESTCONF_STARTUP_DONTUPDATE + CLICON_NETCONF_MESSAGE_ID_OPTIONAL Released in Clixon 5.2"; } revision 2021-03-08 { @@ -465,6 +466,16 @@ module clixon-config { is returned, which conforms to the RFC. Note this applies only to external NETCONF, not the internal (IPC) netconf"; } + leaf CLICON_NETCONF_MESSAGE_ID_OPTIONAL { + type boolean; + default false; + description + "This option relates to RFC 6241 Sec 4.1 Element + The element has a mandatory attribute 'message-id', which is a + string chosen by the sender of the RPC. + If true, an RPC can be sent without a message-id. + This applies to both external NETCONF and internal (IPC) netconf"; + } leaf CLICON_RESTCONF_DIR { type string; description From 89f843f919e6a73de43c7a458943a756ba0143eb Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Thu, 1 Jul 2021 11:53:46 +0200 Subject: [PATCH 16/34] * Fixed: [uses oc-if:interface-ref error with openconfig #233](https://github.com/clicon/clixon/issues/233) --- CHANGELOG.md | 5 +++-- README.md | 4 ++-- lib/src/clixon_validate.c | 11 ++--------- lib/src/clixon_xpath.c | 15 ++++++++++++++- lib/src/clixon_xpath_parse.y | 6 +++--- 5 files changed, 24 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dcdc822c..1ec1c013 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Clixon Changelog -* [5.2.0](#520) Expected: June 2021 +* [5.2.0](#520) Expected: July 2021 * [5.1.0](#510) 15 April 2021 * [5.0.0](#500) 27 February 2021 * [5.0.1](#501) 10 March 2021 @@ -30,7 +30,7 @@ * [3.3.1](#331) June 7 2017 ## 5.2.0 -Expected: June 2021 +Expected: July 2021 ### New features @@ -116,6 +116,7 @@ Developers may need to change their code ### Corrected Bugs +* Fixed: [uses oc-if:interface-ref error with openconfig #233](https://github.com/clicon/clixon/issues/233) * Fixed: [need make sure message-id exist in rpc validate #240](https://github.com/clicon/clixon/issues/240) * Netconf message-id attribute changed from optional to mandatory (see API changes) * Fixed: [restconf patch method unable to chage value to empty string #229](https://github.com/clicon/clixon/issues/229) diff --git a/README.md b/README.md index 44a03c24..1a60994b 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Clixon is a YANG-based configuration manager, with interactive CLI, NETCONF and RESTCONF interfaces, an embedded database and transaction mechanism. -See [documentation](https://clixon-docs.readthedocs.io), [project page](https://www.clicon.org) and [examples](https://github.com/clicon/clixon-examples), [Travis-CI](https://travis-ci.org/clicon/clixon) +See [documentation](https://clixon-docs.readthedocs.io), [project page](https://www.clicon.org) and [examples](https://github.com/clicon/clixon-examples), [Github actions CI](https://github.com/clicon/clixon/actions/workflows/ci.yml) Clixon is open-source and dual licensed. Either Apache License, Version 2.0 or GNU General Public License Version 2; you choose, see [LICENSE.md](LICENSE.md). @@ -17,6 +17,6 @@ Latest release is 5.1.0. See [CHANGELOG.md](CHANGELOG.md) release history. Clixon interaction is best done posting issues, pull requests, or joining the [slack channel](https://clixondev.slack.com). -[Slack invite](https://join.slack.com/t/clixondev/shared_invite/zt-qk66zp47-ahdOlEHKEPmb~5ciVJilPQ) (updated 24/5 2021) +[Slack invite](https://join.slack.com/t/clixondev/shared_invite/zt-seopvltv-hs~BS7UrMjRdKoDRlCK97w) (updated 30/6 2021) Clixon is sponsored by [Rubicon Communications LLC(Netgate)](https://www.netgate.com/) diff --git a/lib/src/clixon_validate.c b/lib/src/clixon_validate.c index dd97facf..274d339a 100644 --- a/lib/src/clixon_validate.c +++ b/lib/src/clixon_validate.c @@ -105,7 +105,6 @@ validate_leafref(cxobj *xt, { int retval = -1; yang_stmt *ypath; - yang_stmt *yp; cxobj **xvec = NULL; cxobj *x; int i; @@ -124,14 +123,8 @@ validate_leafref(cxobj *xt, goto fail; } /* See comment^: If path is defined in typedef or not */ - if ((yp = yang_parent_get(ytype)) != NULL && - yang_keyword_get(yp) == Y_TYPEDEF){ - if (xml_nsctx_yang(ys, &nsc) < 0) - goto done; - } - else - if (xml_nsctx_yang(ytype, &nsc) < 0) - goto done; + if (xml_nsctx_node(xt, &nsc) < 0) + goto done; path = yang_argument_get(ypath); if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, path) < 0) goto done; diff --git a/lib/src/clixon_xpath.c b/lib/src/clixon_xpath.c index 03d08f57..64352055 100644 --- a/lib/src/clixon_xpath.c +++ b/lib/src/clixon_xpath.c @@ -239,7 +239,6 @@ xpath_tree_print(FILE *f, * @param[in] xs XPATH tree * @param[out] xpath XPath string as CLIgen buf * @see xpath_tree_print - * @note XXX Not complete */ int xpath_tree2cbuf(xpath_tree *xs, @@ -247,6 +246,7 @@ xpath_tree2cbuf(xpath_tree *xs, { int retval = -1; + /* 1. Before first child */ switch (xs->xs_type){ case XP_ABSPATH: if (xs->xs_int == A_DESCENDANT_OR_SELF) @@ -283,8 +283,10 @@ xpath_tree2cbuf(xpath_tree *xs, default: break; } + /* 2. First child */ if (xs->xs_c0 && xpath_tree2cbuf(xs->xs_c0, xcb) < 0) goto done; + /* 3. Between first and second child */ switch (xs->xs_type){ case XP_AND: /* and or */ case XP_ADD: /* div mod + * - */ @@ -293,6 +295,13 @@ xpath_tree2cbuf(xpath_tree *xs, if (xs->xs_c1) cprintf(xcb, " %s ", clicon_int2str(xpopmap, xs->xs_int)); break; + case XP_PATHEXPR: + /* [19] PathExpr ::= | FilterExpr '/' RelativeLocationPath + | FilterExpr '//' RelativeLocationPath + */ + if (xs->xs_s0) + cprintf(xcb, "%s", xs->xs_s0); + break; case XP_RELLOCPATH: if (xs->xs_c1){ if (xs->xs_int == A_DESCENDANT_OR_SELF) @@ -311,8 +320,10 @@ xpath_tree2cbuf(xpath_tree *xs, default: break; } + /* 4. Second child */ if (xs->xs_c1 && xpath_tree2cbuf(xs->xs_c1, xcb) < 0) goto done; + /* 5. After second child */ switch (xs->xs_type){ case XP_PRED: if (xs->xs_c1) @@ -915,6 +926,8 @@ xpath_vec_bool(cxobj *xcur, return retval; } +/*! + */ static int traverse_canonical(xpath_tree *xs, yang_stmt *yspec, diff --git a/lib/src/clixon_xpath_parse.y b/lib/src/clixon_xpath_parse.y index 91c383e5..825203aa 100644 --- a/lib/src/clixon_xpath_parse.y +++ b/lib/src/clixon_xpath_parse.y @@ -163,7 +163,7 @@ xpath_parse_exit(clixon_xpath_yacc *xpy) * @param[in] type XPATH tree node type * @param[in] i0 step-> axis_type * @param[in] numstr original string xs_double: numeric value - * @param[in] s0 String 0 set if XP_PRIME_STR, XP_PRIME_FN, XP_NODE[_FN] prefix + * @param[in] s0 String 0 set if XP_PRIME_STR, XP_PRIME_FN, XP_NODE[_FN] PATHEXPRE prefix * @param[in] s1 String 1 set if XP_NODE NAME * @param[in] c0 Child 0 * @param[in] c1 Child 1 @@ -393,8 +393,8 @@ unionexpr : unionexpr '|' pathexpr { $$=xp_new(XP_UNION,XO_UNION,NULL,NULL,NUL pathexpr : locationpath { $$=xp_new(XP_PATHEXPR,A_NAN,NULL,NULL,NULL,$1, NULL);clicon_debug(3,"pathexpr-> locationpath"); } | filterexpr { $$=xp_new(XP_PATHEXPR,A_NAN,NULL,NULL,NULL,$1, NULL);clicon_debug(3,"pathexpr-> filterexpr"); } - | filterexpr '/' rellocpath { $$=xp_new(XP_PATHEXPR,A_NAN,NULL,NULL,NULL,$1, $3);clicon_debug(3,"pathexpr-> filterexpr / rellocpath"); } - /* Filterexpr // relativelocationpath */ + | filterexpr '/' rellocpath { $$=xp_new(XP_PATHEXPR,A_NAN,NULL,strdup("/"),NULL,$1, $3);clicon_debug(3,"pathexpr-> filterexpr / rellocpath"); } + | filterexpr DOUBLESLASH rellocpath { $$=xp_new(XP_PATHEXPR,A_NAN,NULL,strdup("//"),NULL,$1, $3);clicon_debug(3,"pathexpr-> filterexpr // rellocpath"); } ; filterexpr : primaryexpr { $$=xp_new(XP_FILTEREXPR,A_NAN,NULL,NULL,NULL,$1, NULL);clicon_debug(3,"filterexpr-> primaryexpr"); } From ef131cf407e72df26e4e19e9f0b2ad39ae409e98 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Thu, 1 Jul 2021 16:34:38 +0200 Subject: [PATCH 17/34] * 5.2.0 preparations * Restconf native openssl - ifdef SSL_get0_next_proto_negotiated --- CHANGELOG.md | 59 +++++++++++++++------------- README.md | 2 +- apps/restconf/restconf_main_native.c | 2 + 3 files changed, 34 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ec1c013..8d80a857 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Clixon Changelog -* [5.2.0](#520) Expected: July 2021 +* [5.2.0](#520) 1 July 2021 * [5.1.0](#510) 15 April 2021 * [5.0.0](#500) 27 February 2021 * [5.0.1](#501) 10 March 2021 @@ -30,39 +30,42 @@ * [3.3.1](#331) June 7 2017 ## 5.2.0 -Expected: July 2021 +1 July 2021 ### New features -* New utility: clixon_util_validate for stand-alone application that validates or commits datastores * Restconf native HTTP/2 support using nghttp2 - * Enable using: `--with-restconf=native --enable-nghttp2` * FCGI/nginx not affected only for `--with-restconf=native` * HTTP/1 co-exists, unless `--disable-evhtp` which results in http/2 only + * For HTTP/2 only: `--disable-nghttp2` * Upgrade from HTTP/1.1 to HTTP/2 * https: ALPN upgrade - * http: Upgrade header -* YANG when statement in conjunction with grouping/uses/augment - * Several cases were not implemented fully according to RFC 7950: + * http: Upgrade header (using: `HTTP/1.1 101 Switching Protocols`) +* Full support of YANG `when` statement in conjunction with grouping/uses/augment + * SeveralThe following cases are now supported according to RFC 7950: * Do not extend default values if when statements evaluate to false * Do not allow edit-config of nodes if when statements evaluate to false (Sec 8.3.2) * If a key leaf is defined in a grouping that is used in a list, the "uses" statement MUST NOT have a "when" statement. (See 7.21.5) * See [yang uses's substatement when has no effect #218](https://github.com/clicon/clixon/issues/218) -* YANG deviation [deviation statement not yet support #211](https://github.com/clicon/clixon/issues/211) +* YANG `deviation`support [deviation statement not yet support #211](https://github.com/clicon/clixon/issues/211) * See RFC7950 Sec 5.6.3 * Added ordering sanity check for YANG modules and sub-modules - * If YANG sub-statements are placed in wrong order, clixon fails on error. + * If YANG sub-statements are placed in wrong order, clixon fails with error. +* New utility: clixon_util_validate for stand-alone application that validates or commits datastores + ### API changes on existing protocol/config features Users may have to change how they access the system * Netconf message-id attribute changed from optional to mandatory - * Made NETCONF message handling more strict according to RFC 6241 - * Set `CLICON_NETCONF_MESSAGE_ID_OPTIONAL` to true to accept omission of message-id attribute + * Example: + * Correct: `` + * Wrong: `` + * Set `CLICON_NETCONF_MESSAGE_ID_OPTIONAL` to `true` to accept omission of message-id attribute * See also [need make sure message-id exist in rpc validate #240](https://github.com/clicon/clixon/issues/240) * Changed config and install options for Restconf - * clixon_restconf daemon is installed in /usr/local/sbin (as clixon_backend), instead of /www-data + * clixon_restconf daemon is installed in `/usr/local/sbin` (as clixon_backend), instead of /www-data * `configure --with-wwwdir=` remains but only applies to fcgi socket and log * New option `CLICON_RESTCONF_INSTALLDIR` is set to where clixon_restconf is installed, with default `/usr/local/sbin/` * Restconf drop privileges user is defined by `CLICON_RESTCONF_USER` @@ -73,10 +76,9 @@ Users may have to change how they access the system * Added: `CLICON_RESTCONF_PRIVILEGES` * Added: `CLICON_RESTCONF_INSTALLDIR` * Added: `CLICON_RESTCONF_STARTUP_DONTUPDATE` - * Added: `CLICON_RESTCONF_STARTUP_DONTUPDATE` * Added: `CLICON_NETCONF_MESSAGE_ID_OPTIONAL` * New clixon-restconf@2020-05-20.yang revision - * Added: restconf `log-destination` + * Added: restconf `log-destination` (syslog or file:`/var/log/clixon_restconf.log`) * RESTCONF error replies have changed * Added Restconf-style xml/json message bodies everywhere * Clixon removed the message body from many errors in the 4.6 version since they used html encoding. @@ -90,41 +92,42 @@ Users may have to change how they access the system Developers may need to change their code -* Made backend transaction and commit/validate API available to plugin code. - * This enables that RPOC handles can call commit and validate via lib - * The commit/validate API is now: `candidate_validate()` and `candidate_commit()` * Event exit API changed to a single decrementing counter where 1 means exit. * Removed: `clicon_exit_reset()` * Changed: `clicon_exit_set()` --> `clixon_exit_set(int nr)` * Changed: `clicon_exit_get()` --> `clixon_exit_get()` +* Made backend transaction and commit/validate API available to plugin code. + * This enables RPC plugin code can call commit and validate via lib + * The commit/validate API is now: `candidate_validate()` and `candidate_commit()` ### Minor features -* Changed default CI to be restconf=native instead of fcgi -* Moved CI from travis to github actions -* Added autotool check for getresuid (+ related functions) necessary for lowering of priviliges for backend and restconf - * If getresuid is not available, CLICON_RESTCONF_PRIVILEGES must be set to 'none' +* CI testing: + * Changed default CI to be Ǹative restconf` instead of fcgi using nginx + * Moved CI from travis to github actions +* Added autotool check for `getresuid` (+ related functions) necessary for lowering of priviliges for backend and restconf + * If `getresuid` is not available, `CLICON_RESTCONF_PRIVILEGES` must be 'none' * Added new startup-mode: `running-startup`: First try running db, if it is empty try startup db. * See [Can startup mode to be extended to support running-startup mode? #234](https://github.com/clicon/clixon/issues/234) * Restconf: added inline configuration using `-R ` command line as an alternative to making advanced restconf configuration -* [Need an option to disable restconf mandatory action of overwriting startup_db #230](https://github.com/clicon/clixon/issues/230) - * Configure option `CLICON_RESTCONF_STARTUP_DONTUPDATE` added to disable RFC 8040 mandatory copy of running to startup after commit +* New option `CLICON_RESTCONF_STARTUP_DONTUPDATE` added to disable RFC 8040 mandatory copy of running to startup after commit* + * See [Need an option to disable restconf mandatory action of overwriting startup_db #230](https://github.com/clicon/clixon/issues/230) * Add default network namespace constant: `RESTCONF_NETNS_DEFAULT` with default value "default". * CLI: Two new hide variables added (thanks: shmuelnatan) * hide-database : specifies that a command is not visible in database. This can be useful for setting passwords and not exposing them to users. - * hide-database-auto-completion : specifies that a command is not visible in database and in auto completion. This can be useful for a password that was put in device by super user, not be changed. + * hide-database-auto-completion : specifies that a command is not visible in database and in auto completion. This can be useful for a password that was put in device by super user, not be changed. ### Corrected Bugs * Fixed: [uses oc-if:interface-ref error with openconfig #233](https://github.com/clicon/clixon/issues/233) * Fixed: [need make sure message-id exist in rpc validate #240](https://github.com/clicon/clixon/issues/240) * Netconf message-id attribute changed from optional to mandatory (see API changes) -* Fixed: [restconf patch method unable to chage value to empty string #229](https://github.com/clicon/clixon/issues/229) +* Fixed: [restconf patch method unable to change value to empty string #229](https://github.com/clicon/clixon/issues/229) * Fixed: [restconf patch method adds redundant namespaces #235](https://github.com/clicon/clixon/issues/235) * Fixed: Restconf HEAD did not work everywhere GET did, such as well-known and exact root. * Fixed: [JSON parsing error for a specific input. #236](https://github.com/clicon/clixon/issues/236) * JSON empty list parse problems, eg `a:[]` - * May also have fixed: [Json parser not work properly with empry array \[\] #228](https://github.com/clicon/clixon/issues/228) + * Also fixed: [Json parser not work properly with empty array \[\] #228](https://github.com/clicon/clixon/issues/228) * Fixed: [restconf patch method unable to chage value to empty string #229](https://github.com/clicon/clixon/issues/229) * Fixed: [when condition error under augment in restconf #227](https://github.com/clicon/clixon/issues/227) * Fixed: [Using YANG union with decimal64 and string leads to regexp match fail #226](https://github.com/clicon/clixon/issues/226) @@ -134,8 +137,8 @@ Developers may need to change their code * See [XPATH issues #219](https://github.com/clicon/clixon/issues/219) * Fix Union in xpath [XPATH issues #219](https://github.com/clicon/clixon/issues/219) * Fix: XPath:s used in netconf (eg get-config) did not correctly access default values -* [RESTCONF GET request of single-key list with empty string returns all elements #213](https://github.com/clicon/clixon/issues/213) -* [RESTCONF GETof lists with empty string keys does not work #214](https://github.com/clicon/clixon/issues/214) +* Fixed: [RESTCONF GET request of single-key list with empty string returns all elements #213](https://github.com/clicon/clixon/issues/213) +* Fixed: [RESTCONF GETof lists with empty string keys does not work #214](https://github.com/clicon/clixon/issues/214) * Fixed: [Multiple http requests in native restconf yields same reply #212](https://github.com/clicon/clixon/issues/212) ## 5.1.0 diff --git a/README.md b/README.md index 1a60994b..0cde4fe0 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ See [documentation](https://clixon-docs.readthedocs.io), [project page](https:// Clixon is open-source and dual licensed. Either Apache License, Version 2.0 or GNU General Public License Version 2; you choose, see [LICENSE.md](LICENSE.md). -Latest release is 5.1.0. See [CHANGELOG.md](CHANGELOG.md) release history. +Latest release is 5.2.0 released on July 1st 2021. See [CHANGELOG.md](CHANGELOG.md) release history. Clixon interaction is best done posting issues, pull requests, or joining the [slack channel](https://clixondev.slack.com). diff --git a/apps/restconf/restconf_main_native.c b/apps/restconf/restconf_main_native.c index ac89089a..631701c1 100644 --- a/apps/restconf/restconf_main_native.c +++ b/apps/restconf/restconf_main_native.c @@ -1210,7 +1210,9 @@ restconf_accept_client(int fd, } /* SSL_accept */ } /* while(readmore) */ /* Sets data and len to point to the client's requested protocol for this connection. */ +#ifndef OPENSSL_NO_NEXTPROTONEG SSL_get0_next_proto_negotiated(rc->rc_ssl, &alpn, &alpnlen); +#endif /* !OPENSSL_NO_NEXTPROTONEG */ if (alpn == NULL) { /* Returns a pointer to the selected protocol in data with length len. */ SSL_get0_alpn_selected(rc->rc_ssl, &alpn, &alpnlen); From 22563ee2de5d9557f601433fdca7c068866d0802 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Thu, 1 Jul 2021 17:47:31 +0200 Subject: [PATCH 18/34] - Clixon 5.2.0 release --- CHANGELOG.md | 7 ++++--- configure | 2 +- configure.ac | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d80a857..dfd7449d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,8 @@ ## 5.2.0 1 July 2021 +The 5.2 release has new YANG support for deviation, when and statement ordering. The native restconf mode also supports http/2 using libnghttp2 + ### New features * Restconf native HTTP/2 support using nghttp2 @@ -42,18 +44,17 @@ * https: ALPN upgrade * http: Upgrade header (using: `HTTP/1.1 101 Switching Protocols`) * Full support of YANG `when` statement in conjunction with grouping/uses/augment - * SeveralThe following cases are now supported according to RFC 7950: + * The following cases are now supported according to RFC 7950: * Do not extend default values if when statements evaluate to false * Do not allow edit-config of nodes if when statements evaluate to false (Sec 8.3.2) * If a key leaf is defined in a grouping that is used in a list, the "uses" statement MUST NOT have a "when" statement. (See 7.21.5) * See [yang uses's substatement when has no effect #218](https://github.com/clicon/clixon/issues/218) -* YANG `deviation`support [deviation statement not yet support #211](https://github.com/clicon/clixon/issues/211) +* YANG `deviation` support [deviation statement not yet support #211](https://github.com/clicon/clixon/issues/211) * See RFC7950 Sec 5.6.3 * Added ordering sanity check for YANG modules and sub-modules * If YANG sub-statements are placed in wrong order, clixon fails with error. * New utility: clixon_util_validate for stand-alone application that validates or commits datastores - ### API changes on existing protocol/config features Users may have to change how they access the system diff --git a/configure b/configure index 8b5469cc..c1d0ea2c 100755 --- a/configure +++ b/configure @@ -2266,7 +2266,7 @@ ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var. CLIXON_VERSION_MAJOR="5" CLIXON_VERSION_MINOR="2" CLIXON_VERSION_PATCH="0" -CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}.PRE\"" +CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}\"" # Check CLIgen if test "$prefix" = "NONE"; then diff --git a/configure.ac b/configure.ac index 16f0743f..7c30380e 100644 --- a/configure.ac +++ b/configure.ac @@ -51,7 +51,7 @@ AC_CONFIG_AUX_DIR(config-aux) CLIXON_VERSION_MAJOR="5" CLIXON_VERSION_MINOR="2" CLIXON_VERSION_PATCH="0" -CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}.PRE\"" +CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}\"" # Check CLIgen if test "$prefix" = "NONE"; then From 98c5fcd20f4028e613073f443668704d5fa8096f Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Wed, 7 Jul 2021 13:14:17 +0200 Subject: [PATCH 19/34] Clixon 5.3.0.PRE --- CHANGELOG.md | 6 +++++- configure | 4 ++-- configure.ac | 4 ++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfd7449d..55aa4a03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Clixon Changelog +* [5.3.0](#530) Expected: September 2021 * [5.2.0](#520) 1 July 2021 * [5.1.0](#510) 15 April 2021 * [5.0.0](#500) 27 February 2021 @@ -29,10 +30,13 @@ * [3.3.2](#332) Aug 27 2017 * [3.3.1](#331) June 7 2017 +## 5.3.0 +Expected: September, 2021 + ## 5.2.0 1 July 2021 -The 5.2 release has new YANG support for deviation, when and statement ordering. The native restconf mode also supports http/2 using libnghttp2 +The 5.2 release has YANG support for "deviation", "when" and statement ordering. The native restconf mode also supports http/2 using libnghttp2 ### New features diff --git a/configure b/configure index c1d0ea2c..a131afc1 100755 --- a/configure +++ b/configure @@ -2264,9 +2264,9 @@ ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var. CLIXON_VERSION_MAJOR="5" -CLIXON_VERSION_MINOR="2" +CLIXON_VERSION_MINOR="3" CLIXON_VERSION_PATCH="0" -CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}\"" +CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}.PRE\"" # Check CLIgen if test "$prefix" = "NONE"; then diff --git a/configure.ac b/configure.ac index 7c30380e..aaac5f5d 100644 --- a/configure.ac +++ b/configure.ac @@ -49,9 +49,9 @@ AC_INIT(lib/clixon/clixon.h.in) AC_CONFIG_AUX_DIR(config-aux) CLIXON_VERSION_MAJOR="5" -CLIXON_VERSION_MINOR="2" +CLIXON_VERSION_MINOR="3" CLIXON_VERSION_PATCH="0" -CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}\"" +CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}.PRE\"" # Check CLIgen if test "$prefix" = "NONE"; then From fe0541959f233b7856b381517631eeecf5bd5f51 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Wed, 7 Jul 2021 16:14:01 +0200 Subject: [PATCH 20/34] * Native Restconf is now default, not fcgi/nginx * That is, to configure with fcgi, you need to explicitly configure: `--with-restconf=fcgi` * Updated main example to native restconf and several other fixes --- CHANGELOG.md | 7 + configure | 8 +- configure.ac | 8 +- example/README.md | 4 +- example/main/README.md | 204 ++++++++++++++------ example/main/clixon-example@2020-03-11.yang | 185 ------------------ example/main/example.xml | 3 + 7 files changed, 169 insertions(+), 250 deletions(-) delete mode 100644 example/main/clixon-example@2020-03-11.yang diff --git a/CHANGELOG.md b/CHANGELOG.md index 55aa4a03..280aa77b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,13 @@ ## 5.3.0 Expected: September, 2021 +### C/CLI-API changes on existing features + +Developers may need to change their code + +* Native Restconf is now default, not fcgi/nginx + * That is, to configure with fcgi, you need to explicitly configure: `--with-restconf=fcgi` + ## 5.2.0 1 July 2021 diff --git a/configure b/configure index a131afc1..548b893a 100755 --- a/configure +++ b/configure @@ -1378,6 +1378,7 @@ Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) --with-cligen=dir Use CLIGEN installation in this dir + --with-restconf=native Integration with embedded web server (DEFAULT) --with-restconf=fcgi FCGI interface for stand-alone web rev-proxy eg nginx (default) --with-restconf=native Integrate restconf with embedded http server @@ -4995,15 +4996,17 @@ fi # This is for restconf. There are three options: # --without-restconf No restconf support # --with-restconf=fcgi FCGI interface for separate web reverse proxy like nginx -# --with-restconf=native Integration with embedded web server +# --with-restconf=native Integration with embedded web server (DEFAULT) # Check whether --with-restconf was given. if test "${with_restconf+set}" = set; then : withval=$with_restconf; else - with_restconf=fcgi + with_restconf=native fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: restconf mode ${with_restconf}" >&5 +$as_echo "restconf mode ${with_restconf}" >&6; } # Actions for each specific package if test "x${with_restconf}" == xfcgi; then # Lives in libfcgi-dev @@ -5063,6 +5066,7 @@ cat >>confdefs.h <<_ACEOF _ACEOF elif test "x${with_restconf}" == xnative; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for OPENSSL_init_ssl in -lssl" >&5 $as_echo_n "checking for OPENSSL_init_ssl in -lssl... " >&6; } if ${ac_cv_lib_ssl_OPENSSL_init_ssl_+:} false; then : diff --git a/configure.ac b/configure.ac index aaac5f5d..f6046718 100644 --- a/configure.ac +++ b/configure.ac @@ -206,11 +206,12 @@ AC_CHECK_LIB(cligen, cligen_init,, AC_MSG_ERROR([CLIgen missing. Try: git clone # This is for restconf. There are three options: # --without-restconf No restconf support # --with-restconf=fcgi FCGI interface for separate web reverse proxy like nginx -# --with-restconf=native Integration with embedded web server +# --with-restconf=native Integration with embedded web server (DEFAULT) AC_ARG_WITH([restconf], - AS_HELP_STRING([--with-restconf=fcgi],[FCGI interface for stand-alone web rev-proxy eg nginx (default)]), + AS_HELP_STRING([--with-restconf=native],[Integration with embedded web server (DEFAULT)]), , - [with_restconf=fcgi]) + [with_restconf=native]) +AC_MSG_RESULT(restconf mode ${with_restconf}) # Actions for each specific package if test "x${with_restconf}" == xfcgi; then # Lives in libfcgi-dev @@ -218,6 +219,7 @@ if test "x${with_restconf}" == xfcgi; then AC_DEFINE(WITH_RESTCONF_FCGI, 1, [Use fcgi restconf mode]) # For c-code that cant use strings AC_DEFINE_UNQUOTED(WWWDIR, "$wwwdir", [WWW dir for fcgi stuff / nginx]) elif test "x${with_restconf}" == xnative; then + AC_CHECK_LIB(ssl, OPENSSL_init_ssl ,, AC_MSG_ERROR([libssl missing])) AC_CHECK_LIB(crypto, CRYPTO_new_ex_data, , AC_MSG_ERROR([libcrypto missing])) # Check if evhtp is enabled for http/1 diff --git a/example/README.md b/example/README.md index db0d41b2..020e9afe 100644 --- a/example/README.md +++ b/example/README.md @@ -1,7 +1,7 @@ # Clixon examples -See the separate -[clixon-examples](https://github.com/clicon/clixon-examples) repo. +See also the separate +[clixon-examples](https://github.com/clicon/clixon-examples) repo The only Clixon example remaining is for internal testing: * [Main example](main/README.md) diff --git a/example/main/README.md b/example/main/README.md index 4430c2cb..71186626 100644 --- a/example/main/README.md +++ b/example/main/README.md @@ -1,5 +1,6 @@ # Clixon main example + * [Background](#background) * [Content](#content) * [Compile and run](#compile) * [Using the CLI](#using-the-cli) @@ -13,40 +14,45 @@ * [Docker](#docker) * [Plugins](#plugins) + +## Background + +The aim of the main clixon example is to illustrate common features +and for internal testing. See the simpler [hello world](https://github.com/clicon/clixon-examples/tree/master/hello) if you want to start from the simplest possible example. + +See also other examples in: [clixon-examples](https://github.com/clicon/clixon-examples). + ## Content -This directory contains a Clixon example used primarily as a part of the Clixon test suites. It can be used as a basis for making new Clixon applications. But please consider also the minimal [hello](../hello) example as well. It contains the following files: -* `example.xml` The configuration file. See [yang/clixon-config@.yang](../../yang/clixon-config@2019-03-05.yang) for the documentation of all available fields. -* `clixon-example@2019-01-13.yang` The yang spec of the example. -* `example_cli.cli` CLIgen specification. -* `example_cli.c` CLI callback plugin containing functions called in the cli file above: a generic callback (`mycallback`) and an example RPC call (`example_client_rpc`). -* `example_backend.c` Backend callback plugin including example of: - * transaction callbacks (validate/commit), - * notification, - * rpc handler - * state-data handler, ie non-config data +This directory contains a Clixon example used primarily as a part of the Clixon test suites. It can be used as a basis for making new Clixon applications. It contains the following files: +* `clixon-example@2020-12-20.yang` The yang spec of the example. +* `example_backend.c` Backend callback plugin including example of: * `example_backend_nacm.c` Secondary backend plugin. Plugins are loaded alphabetically. -* `example_restconf.c` Restconf callback plugin containing an HTTP basic authentication callback -* `example_netconf.c` Netconf callback plugin -* `Makefile.in` Example makefile where plugins are built and installed +* `example_cli.c` CLI callback plugin containing functions called in the .cli file +* `example_cli.cli` CLIgen specification of example CLI commands +* `example_netconf.c` Netconf callback plugin +* `example_restconf.c` Restconf callback plugin containing HTTP basic authentication +* `example.xml` Main configuration file. +* `Makefile.in` Example makefile where plugins are built and installed + +See [yang/clixon-config@.yang](https://github.com/clicon/clixon/blob/master/yang/clixon/clixon-config%402021-05-20.yang) for documentation of all available fields in `example.xml`. ## Compile and run Before you start, -* You must configure with: `--enable-optyangs` to run the main example. -* Make [group setup](../../doc/FAQ.md#do-i-need-to-setup-anything-important) -* Setup [restconf](../../doc/FAQ.md#how-do-i-use-restconf) - +* You must configure with: `--enable-optyangs` to install all yang files required for the example. This is not necessary for the base colixon system +* Setup clicon [groups](https://github.com/clicon/clixon/blob/master/doc/FAQ.md#do-i-need-to-setup-anything) ``` - cd example + cd example/main make && sudo make install ``` + Start backend: ``` sudo clixon_backend -f /usr/local/etc/example.xml -s init ``` -Edit cli: +Start cli: ``` clixon_cli -f /usr/local/etc/example.xml ``` @@ -54,13 +60,13 @@ Send netconf command: ``` clixon_netconf -f /usr/local/etc/example.xml ``` -Start clixon restconf daemon +Start clixon restconf daemon (default config listens on http IPv4 0.0.0.0 on port 8080): ``` - sudo /usr/local/bin/clixon_restconf -f /usr/local/etc/example.xml + sudo clixon_restconf -f /usr/local/etc/example.xml ``` Send restconf command ``` - curl -X GET http://127.0.0.1/restconf/data + curl -X GET http://127.0.0.1:8080/restconf/data ``` ## Using the CLI @@ -71,53 +77,139 @@ There are also many other commands available as examples. View the source file ( The following example shows how to add an interface in candidate, validate and commit it to running, then look at it (as xml) and finally delete it. ``` clixon_cli -f /usr/local/etc/example.xml -cli> set interfaces interface eth9 ? - description enabled ipv4 - ipv6 link-up-down-trap-enable type -cli> set interfaces interface eth9 type ex:eth +cli> set interfaces interface eth1 ? + + description A textual description of the interface. + enabled This leaf contains the configured, desired state of the + interface. + ipv4 Parameters for the IPv4 address family. + ipv6 Parameters for the IPv6 address family. + type The type of the interface. +cli> set interfaces interface eth1 type ianaift:ip +cli> set interfaces interface eth1 enabled true +cli> set interfaces interface eth1 ipv4 address 1.2.3.4 prefix-length 24 cli> validate cli> commit cli> show configuration xml - eth9 - ex:eth + eth1 + ianaift:ip true + + true + false + + 1.2.3.4 + 24 + + -cli> delete interfaces interface eth9 +cli> delete interfaces interface eth1 +cli> commit ``` ## Using Netconf The following example shows how to set data using netconf: ``` - - - - eth1 - true - -
- 9.2.3.4 - 24 -
-
-
-
-
]]>]]> +sh> clixon_netconf -qf /usr/local/etc/example.xml + +urn:ietf:params:netconf:base:1.1]]>]]> + + + + + + + eth1 + ianaift:ip + true + +
+ 1.2.3.4 + 24 +
+
+
+
+
+
+
]]>]]> +# Reply: ]]>]]> + + +]]>]]> ``` -### Getting data using netconf +Getting data: ``` -]]>]]> -]]>]]> -]]>]]> -eth9ex:eth]]>]]> -]]>]]> -]]>]]> +# Reply: ]]>]]> + + + + +]]>]]> +# Reply: eth1ianaift:iptruetruefalse1.2.3.424]]>]]> +``` + +Examples of a filtered GET statement: +``` +]]>]]> +``` + +## Restconf + +By default clixon from release 5.3 uses "native" restconf, see next +section for an alternative. General clixon [restconf +documentation](https://clixon-docs.readthedocs.io/en/latest/restconf.html). By +default restconf supports http/1.1 and http/2 with the standard way +(ALPN vs switch protocol) of selecting and upgrading from 1.1 to 2. + +In the example, a restconf config is included in the [config file](example.xml): +``` + + true + none + + default +
0.0.0.0
+ 80 + false +
+
+``` + +In this example, a listening socket is opened using http on port 80. You can extend the restconf config by modifying the entry or add multiple `` entries, such as IPv6, TLS and another network namespace, for example: +``` + + dataplane +
::
+ 443 + true +
+``` + +For TLS, cert files need to be given, such as follows: +``` + + ... + /path/to/server/cert + /path/to/server/key + /path/to/ca/cert +``` + +For more info, such as client-certs, authentication, etc, see: [restconf documentation](https://clixon-docs.readthedocs.io/en/latest/restconf.html) + +## Restconf using nginx + +Alternatively, restconf can use a reverse-proxy such as nginx. + +Configure: +``` + ./configure --with-restconf=fcgi ``` -## Restconf Setup a web/reverse-proxy server. For example, using nginx, install, and edit config file: /etc/nginx/sites-available/default: @@ -141,11 +233,13 @@ server { } } ``` + Start nginx daemon ``` sudo /etc/init.d/nginx start sudo systemctl start nginx.service # alternative using systemd ``` + Start the clixon restconf daemon ``` sudo /usr/local/sbin/clixon_restconf -f /usr/local/etc/example.xml @@ -155,8 +249,6 @@ then access using curl or wget: curl -X GET http://127.0.0.1/restconf/data/ietf-interfaces:interfaces/interface=eth1/type ``` -More info: (restconf)[../../apps/restconf/README.md]. - ## Streams The example has an EXAMPLE stream notification triggering every 5s. To start a notification @@ -181,9 +273,6 @@ cli> no notify cli> ``` -Restconf support is also supported, see (restconf)[../../apps/restconf/README.md]. - - ## RPC Operations Clixon implements Yang RPC operations by a mechanism that enables you @@ -307,8 +396,7 @@ Example systemd files for backend and restconf daemons are found under the [syst ## Docker -See [docker](../../docker/system) for instructions on how to build this example -as a docker container. +See [clixon docker main example](../../docker/main) for instructions on how to build this example as a docker container. ## Plugins diff --git a/example/main/clixon-example@2020-03-11.yang b/example/main/clixon-example@2020-03-11.yang deleted file mode 100644 index a4b0c23b..00000000 --- a/example/main/clixon-example@2020-03-11.yang +++ /dev/null @@ -1,185 +0,0 @@ -module clixon-example { - yang-version 1.1; - namespace "urn:example:clixon"; - prefix ex; - description - "Clixon example used as a part of the Clixon test suite. - It can be used as a basis for making new Clixon applications."; - revision 2020-03-11 { - description "Added container around translation list. Released in Clixon 4.4.0"; - } - revision 2019-11-05 { - description "Augment interface. Released in Clixon 4.3.0"; - } - revision 2019-07-23 { - description "Extension e4. Released in Clixon 4.1.0"; - } - revision 2019-01-13 { - description "Released in Clixon 3.9"; - } - import ietf-interfaces { - prefix if; - } - import ietf-ip { - prefix ip; - } - import iana-if-type { - prefix ianaift; - } - /* Example interface type for tests, local callbacks, etc */ - identity eth { - base if:interface-type; - } - identity loopback { - base if:interface-type; - } - /* Translation function example - See also example_cli */ - container translate{ - description "dont have lists directly under top since restconf cant address list directly"; - list translate{ - key k; - leaf k{ - type string; - } - leaf value{ - type string; - } - } - } - /* State data (not config) for the example application*/ - container state { - config false; - description "state data for the example application (must be here for example get operation)"; - leaf-list op { - type string; - } - } - augment "/if:interfaces/if:interface" { - container my-status { - config false; - description "For testing augment+state"; - leaf int { - type int32; - } - leaf str { - type string; - } - } - } - /* yang extension implemented by the example backend code. */ - extension e4 { - description - "The first child of the ex:e4 (unknown) statement is inserted into - the module as a regular data statement. This means that 'uses bar;' - in the ex:e4 statement below is a valid data node"; - argument arg; - } - grouping bar { - leaf bar{ - type string; - } - } - ex:e4 arg1{ - uses bar; - } - - /* Example notification as used in RFC 5277 and RFC 8040 */ - notification event { - description "Example notification event."; - leaf event-class { - type string; - description "Event class identifier."; - } - container reportingEntity { - description "Event specific information."; - leaf card { - type string; - description "Line card identifier."; - } - } - leaf severity { - type string; - description "Event severity description."; - } - } - rpc client-rpc { - description "Example local client-side RPC that is processed by the - the netconf/restconf and not sent to the backend. - This is a clixon implementation detail: some rpc:s - are better processed by the client for API or perf reasons"; - input { - leaf x { - type string; - } - } - output { - leaf x { - type string; - } - } - } - rpc empty { - description "Smallest possible RPC with no input or output sections"; - } - rpc optional { - description "Small RPC with optional input and output"; - input { - leaf x { - type string; - } - } - output { - leaf x { - type string; - } - } - } - rpc example { - description "Some example input/output for testing RFC7950 7.14. - RPC simply echoes the input for debugging."; - input { - leaf x { - description - "If a leaf in the input tree has a 'mandatory' statement with - the value 'true', the leaf MUST be present in an RPC invocation."; - type string; - mandatory true; - } - leaf y { - description - "If a leaf in the input tree has a 'mandatory' statement with the - value 'true', the leaf MUST be present in an RPC invocation."; - type string; - default "42"; - } - leaf-list z { - description - "If a leaf-list in the input tree has one or more default - values, the server MUST use these values (XXX not supported)"; - type string; - } - leaf w { - description - "If any node has a 'when' statement that would evaluate to - 'false',then this node MUST NOT be present in the input tree. - (XXX not supported)"; - type string; - when "/translate/k=5/value='w'"; - } - } - output { - leaf x { - type string; - } - leaf y { - type string; - } - leaf z { - type string; - } - leaf w { - type string; - } - } - } -} diff --git a/example/main/example.xml b/example/main/example.xml index d745a759..179db40f 100644 --- a/example/main/example.xml +++ b/example/main/example.xml @@ -1,6 +1,8 @@ /usr/local/etc/example.xml ietf-netconf:startup + clixon-restconf:allow-auth-none + clixon-restconf:fcgi /usr/local/share/clixon clixon-example example @@ -21,4 +23,5 @@ disabled true false + truenonedefault
0.0.0.0
8081false
From 1925ac68cd21d19f836cb562427e317f150c2ccd Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Thu, 8 Jul 2021 16:09:18 +0200 Subject: [PATCH 21/34] * Fixed: [RESTConf GET for a specific list instance retrieves data from other submodules that have same list name and key value #244](https://github.com/clicon/clixon/issues/244) * Fixed: Double clixon error messages in yang2cli code * Sanity checks for submodule belongs-to --- CHANGELOG.md | 4 ++ apps/cli/cli_generate.c | 10 ++-- lib/src/clixon_path.c | 9 +++- lib/src/clixon_yang.c | 7 ++- lib/src/clixon_yang_parse_lib.c | 35 +++++++++++--- test/test_openconfig.sh | 2 +- test/test_openconfig_interfaces.sh | 78 ++++++++++++++++++++++++++++-- 7 files changed, 126 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 280aa77b..dde41a15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,10 @@ Developers may need to change their code * Native Restconf is now default, not fcgi/nginx * That is, to configure with fcgi, you need to explicitly configure: `--with-restconf=fcgi` +### Corrected Bugs + +* Fixed: [RESTConf GET for a specific list instance retrieves data from other submodules that have same list name and key value #244](https://github.com/clicon/clixon/issues/244) + ## 5.2.0 1 July 2021 diff --git a/apps/cli/cli_generate.c b/apps/cli/cli_generate.c index 7712ccfb..701288cc 100644 --- a/apps/cli/cli_generate.c +++ b/apps/cli/cli_generate.c @@ -626,18 +626,18 @@ yang2cli_var(clicon_handle h, completionp = clicon_cli_genmodel_completion(h); if (completionp) cprintf(cb, "("); - if ((retval = yang2cli_var_sub(h, ys, yrestype, helptext, cvtype, - options, cvv, patterns, fraction_digits, cb)) < 0) + if (yang2cli_var_sub(h, ys, yrestype, helptext, cvtype, + options, cvv, patterns, fraction_digits, cb) < 0) goto done; if (completionp){ result = cli_expand_var_generate(h, ys, cvtype, options, fraction_digits, cb); if (result < 0) - goto done; + goto done; if (result == 0) - yang2cli_helptext(cb, helptext); - cprintf(cb, ")"); + yang2cli_helptext(cb, helptext); + cprintf(cb, ")"); } } retval = 0; diff --git a/lib/src/clixon_path.c b/lib/src/clixon_path.c index 38d62300..b7127939 100644 --- a/lib/src/clixon_path.c +++ b/lib/src/clixon_path.c @@ -731,7 +731,8 @@ api_path2xpath_cvv(cvec *api_path, cvk = yang_cvec_get(y); /* Use Y_LIST cache, see ys_populate_list() */ cvi = NULL; /* Iterate over individual yang keys */ - cprintf(xpath, "/"); + if (i != offset) + cprintf(xpath, "/"); if (xprefix) cprintf(xpath, "%s:", xprefix); cprintf(xpath, "%s", name); @@ -752,7 +753,8 @@ api_path2xpath_cvv(cvec *api_path, } break; case Y_LEAF_LIST: /* XXX: LOOP? */ - cprintf(xpath, "/"); + if (i != offset) + cprintf(xpath, "/"); if (xprefix) cprintf(xpath, "%s:", xprefix); cprintf(xpath, "%s", name); @@ -1129,6 +1131,9 @@ api_path2xml_vec(char **vec, } /*! Create xml tree from api-path + * + * Create an XML tree from "scratch" using api-path, ie no other input than the api-path itself, + * ie not from an existing datastore for example. * @param[in] api_path (Absolute) API-path as defined in RFC 8040 * @param[in] yspec Yang spec * @param[in,out] xtop Incoming XML tree diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 69f37a45..57f5bece 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -1414,6 +1414,7 @@ ys_real_module(yang_stmt *ys, { int retval = -1; yang_stmt *ym = NULL; + yang_stmt *ysubm; yang_stmt *yb; char *name; yang_stmt *yspec; @@ -1433,8 +1434,12 @@ ys_real_module(yang_stmt *ys, clicon_err(OE_YANG, ENOENT, "Belongs-to statement of submodule %s has no argument", yang_argument_get(ym)); /* shouldnt happen */ goto done; } - if ((ym = yang_find_module_by_name(yspec, name)) == NULL) + if ((ysubm = yang_find_module_by_name(yspec, name)) == NULL){ + clicon_err(OE_YANG, ENOENT, "submodule %s references non-existent module %s in its belongs-to statement", + yang_argument_get(ym), name); goto done; + } + ym = ysubm; } } *ymod = ym; diff --git a/lib/src/clixon_yang_parse_lib.c b/lib/src/clixon_yang_parse_lib.c index 629860e4..18c21fc4 100644 --- a/lib/src/clixon_yang_parse_lib.c +++ b/lib/src/clixon_yang_parse_lib.c @@ -1032,6 +1032,10 @@ yang_parse_module(clicon_handle h, ymod = NULL; goto done; } + /* Sanity check that requested module name matches loaded module + * If this does not match, the filename and containing module do not match + * RFC 7950 Sec 5.2 + */ if ((yrev = yang_find(ymod, Y_REVISION, NULL)) != NULL) revm = cv_uint32_get(yang_cv_get(yrev)); if (filename2revision(filename, NULL, &revf) < 0) @@ -1064,14 +1068,18 @@ yang_parse_recurse(clicon_handle h, yang_stmt *ymod, yang_stmt *ysp) { - int retval = -1; - yang_stmt *yi = NULL; /* import */ - yang_stmt *yrev; - char *submodule; - char *subrevision; - yang_stmt *subymod; + int retval = -1; + yang_stmt *yi = NULL; /* import */ + yang_stmt *yrev; + yang_stmt *ybelongto; + yang_stmt *yrealmod; + char *submodule; + char *subrevision; + yang_stmt *subymod; enum rfc_6020 keyw; + if (ys_real_module(ymod, &yrealmod) < 0) + goto done; /* go through all import (modules) and include(submodules) of ysp */ while ((yi = yn_each(ymod, yi)) != NULL){ keyw = yang_keyword_get(yi); @@ -1091,6 +1099,21 @@ yang_parse_recurse(clicon_handle h, /* recursive call */ if ((subymod = yang_parse_module(h, submodule, subrevision, ysp)) == NULL) goto done; + /* Sanity check: if submodule, its belongs-to statement shall point to the module */ + if (keyw == Y_INCLUDE){ + ybelongto = yang_find(subymod, Y_BELONGS_TO, NULL); + if (ybelongto == NULL){ + clicon_err(OE_YANG, ENOENT, "Sub-module \"%s\" does not have a belongs-to statement", submodule); /* shouldnt happen */ + goto done; + } + if (strcmp(yang_argument_get(ybelongto), yang_argument_get(yrealmod)) != 0){ + clicon_err(OE_YANG, ENOENT, "Sub-module \"%s\" references module \"%s\" in its belongs-to statement but should reference %s", + submodule, + yang_argument_get(ybelongto), + yang_argument_get(yrealmod)); + goto done; + } + } /* Go through its sub-modules recursively */ if (yang_parse_recurse(h, subymod, ysp) < 0){ ymod = NULL; diff --git a/test/test_openconfig.sh b/test/test_openconfig.sh index f974ff96..f7c23e2c 100755 --- a/test/test_openconfig.sh +++ b/test/test_openconfig.sh @@ -97,7 +97,7 @@ for f in $files; do fi done -new "Openconfig test: $clixon_cli -1f $cfg -y $f show version ($m modules)" +new "Openconfig test: $clixon_cli -1f $cfg show version ($m modules)" for f in $files; do if [ -n "$(head -1 $f|grep '^module')" ]; then new "$clixon_cli -D $DBG -1f $cfg -y $f show version" diff --git a/test/test_openconfig_interfaces.sh b/test/test_openconfig_interfaces.sh index 7f24fffc..1065f5cb 100755 --- a/test/test_openconfig_interfaces.sh +++ b/test/test_openconfig_interfaces.sh @@ -1,5 +1,6 @@ #!/usr/bin/env bash # Run a system around openconfig interface, ie: openconfig-if-ethernet +# Note first variant uses ietf-interfaces, maybe remove this? # Magic line must be first in script (see README.md) s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi @@ -22,13 +23,11 @@ cat < $cfg $cfg ietf-netconf:startup - $OPENCONFIG/third_party/ietf/ /usr/local/share/clixon $OCDIR - $OCDIR $OCDIR/interfaces $OCDIR/types - $OCDIR/wifi + $OCDIR/vlan $fyang /usr/local/lib/$APPNAME/clispec /usr/local/lib/$APPNAME/cli @@ -42,6 +41,7 @@ cat < $cfg EOF +# First using ietf-interfaces (not openconfig-interfaces) # Example yang cat < $fyang module clixon-example{ @@ -99,7 +99,7 @@ fi new "wait backend" wait_backend -new "$clixon_cli -D $DBG -1f $cfg -y $f show version" +new "$clixon_cli -D $DBG -1f $cfg show version" expectpart "$($clixon_cli -D $DBG -1f $cfg show version)" 0 "${CLIXON_VERSION}" new "$clixon_netconf -qf $cfg" @@ -126,6 +126,76 @@ if [ $BE -ne 0 ]; then stop_backend -f $cfg fi +# Second using openconfig-interfaces instead +# Example yang +cat < $fyang +module clixon-example{ + yang-version 1.1; + namespace "urn:example:example"; + prefix ex; + + import openconfig-vlan { + prefix oc-vlan; + } + import openconfig-if-ethernet { + prefix oc-eth; + } +} +EOF + +# Example system +cat < $dir/startup_db + + + + eth1 + + eth1 + ianaift:ethernetCsmacd + 9206 + true + oc-vlan-types:TPID_0X8100 + + + + 2c:53:4a:09:59:73 + + + + + +EOF + +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + sudo pkill -f clixon_backend # to be sure + + new "start backend -s startup -f $cfg" + start_backend -s startup -f $cfg +fi + +new "wait backend" +wait_backend + +new "$clixon_cli -D $DBG -1f $cfg show version" +expectpart "$($clixon_cli -D $DBG -1f $cfg show version)" 0 "${CLIXON_VERSION}" + +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 + + rm -rf $dir new "endtest" From ed1d5698e81abb34c7fa355b70f960959bddb654 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Thu, 8 Jul 2021 16:53:45 +0200 Subject: [PATCH 22/34] fix the belongs-to test --- lib/src/clixon_yang_parse_lib.c | 2 +- test/test_submodule.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/clixon_yang_parse_lib.c b/lib/src/clixon_yang_parse_lib.c index 18c21fc4..6668be60 100644 --- a/lib/src/clixon_yang_parse_lib.c +++ b/lib/src/clixon_yang_parse_lib.c @@ -1107,7 +1107,7 @@ yang_parse_recurse(clicon_handle h, goto done; } if (strcmp(yang_argument_get(ybelongto), yang_argument_get(yrealmod)) != 0){ - clicon_err(OE_YANG, ENOENT, "Sub-module \"%s\" references module \"%s\" in its belongs-to statement but should reference %s", + clicon_err(OE_YANG, ENOENT, "Sub-module \"%s\" references module \"%s\" in its belongs-to statement but should reference \"%s\"", submodule, yang_argument_get(ybelongto), yang_argument_get(yrealmod)); diff --git a/test/test_submodule.sh b/test/test_submodule.sh index 56396298..945fe611 100755 --- a/test/test_submodule.sh +++ b/test/test_submodule.sh @@ -122,7 +122,7 @@ EOF cat < $fsub2 submodule sub2 { yang-version 1.1; - belongs-to sub1 { + belongs-to main { prefix ex; } import extra2{ From 270c47b396a5af2ffde2de106c05e1aa5b5c8114 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Fri, 9 Jul 2021 15:00:47 +0200 Subject: [PATCH 23/34] Native restconf fixes: - memleak in http/1-only - exit if http/1 request sent to http/2-only - hang if http/1 TLS request sent to http/2 only --- CHANGELOG.md | 4 ++ apps/restconf/restconf_evhtp.c | 2 + apps/restconf/restconf_main_native.c | 25 ++++--- apps/restconf/restconf_nghttp2.c | 28 +++++++- test/test_restconf.sh | 101 ++++++++++++++++++++------- test/test_restconf_op.sh | 1 - 6 files changed, 123 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dde41a15..c4b5c37d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,10 @@ Developers may need to change their code ### Corrected Bugs +* Native RESTCONF fixes for http/1 or http/2 only modes + * Memleak in http/1-only + * Exit if http/1 request sent to http/2-only (bad client magic) + * Hang if http/1 TLS request sent to http/2 only (alpn accepted http/1.1) * Fixed: [RESTConf GET for a specific list instance retrieves data from other submodules that have same list name and key value #244](https://github.com/clicon/clixon/issues/244) ## 5.2.0 diff --git a/apps/restconf/restconf_evhtp.c b/apps/restconf/restconf_evhtp.c index 49245afd..dfb43597 100644 --- a/apps/restconf/restconf_evhtp.c +++ b/apps/restconf/restconf_evhtp.c @@ -515,6 +515,8 @@ restconf_path_root(evhtp_request_t *req, if (clicon_debug_get()) evhtp_headers_for_each(req->headers_in, evhtp_print_header, h); /* Query vector, ie the ?a=x&b=y stuff */ + if (sd->sd_qvec) + cvec_free(sd->sd_qvec); if ((sd->sd_qvec = cvec_new(0)) ==NULL){ clicon_err(OE_UNIX, errno, "cvec_new"); evhtp_internal_error(req); diff --git a/apps/restconf/restconf_main_native.c b/apps/restconf/restconf_main_native.c index 631701c1..29e65b7d 100644 --- a/apps/restconf/restconf_main_native.c +++ b/apps/restconf/restconf_main_native.c @@ -467,14 +467,16 @@ alpn_select_proto_cb(SSL *ssl, inp++; if (clicon_debug_get()) /* debug print the protoocol */ alpn_proto_dump(__FUNCTION__, (const char*)inp, len); +#ifdef HAVE_LIBEVHTP if (pref < 10 && len == 8 && strncmp((char*)inp, "http/1.1", len) == 0){ *outlen = len; *out = inp; pref = 10; } +#endif #ifdef HAVE_LIBNGHTTP2 /* Higher pref than http/1.1 */ - else if (pref < 20 && len == 2 && strncmp((char*)inp, "h2", len) == 0){ + if (pref < 20 && len == 2 && strncmp((char*)inp, "h2", len) == 0){ *outlen = len; *out = inp; pref = 20; @@ -684,7 +686,9 @@ restconf_connection(int s, char buf[BUFSIZ]; /* from stdio.h, typically 8K XXX: reduce for test */ int readmore = 1; int sslerr; - +#ifdef HAVE_LIBNGHTTP2 + int ret; +#endif #ifdef HAVE_LIBEVHTP clicon_handle h; evhtp_connection_t *evconn = NULL; @@ -889,9 +893,14 @@ restconf_connection(int s, #endif /* HAVE_LIBEVHTP */ #ifdef HAVE_LIBNGHTTP2 case HTTP_2: - if (http2_recv(rc, (unsigned char *)buf, n) < 0) + if ((ret = http2_recv(rc, (unsigned char *)buf, n)) < 0) goto done; - //notused sd = restconf_stream_find(rc, 0); /* default stream */ + if (ret == 0){ + restconf_close_ssl_socket(rc, 1); + if (restconf_conn_free(rc) < 0) + goto done; + goto ok; + } /* There may be more data frames */ readmore++; break; @@ -1011,7 +1020,7 @@ ssl_alpn_check(clicon_handle h, } if (alpn != NULL){ cprintf(cberr, "protocolmalformed-messageALPN: protocol not recognized: %s", alpn); - clicon_log(LOG_NOTICE, "Warning: %s", cbuf_get(cberr)); + clicon_log(LOG_INFO, "%s Warning: %s", __FUNCTION__, cbuf_get(cberr)); if (send_badrequest(h, rc->rc_s, rc->rc_ssl, "application/yang-data+xml", cbuf_get(cberr)) < 0) @@ -1019,7 +1028,7 @@ ssl_alpn_check(clicon_handle h, } else{ /* XXX Sending badrequest here gives a segv in SSL_shutdown() later or a SIGPIPE here */ - clicon_log(LOG_NOTICE, "Warning: ALPN: No protocol selected"); + clicon_log(LOG_INFO, "%s Warning: ALPN: No protocol selected", __FUNCTION__); } if (rc->rc_ssl){ @@ -1029,7 +1038,7 @@ ssl_alpn_check(clicon_handle h, if ((ret = SSL_shutdown(rc->rc_ssl)) < 0){ int e = SSL_get_error(rc->rc_ssl, ret); if (e == SSL_ERROR_SYSCALL){ - clicon_log(LOG_NOTICE, "Warning: SSL_shutdown SSL_ERROR_SYSCALL"); + clicon_log(LOG_INFO, "%s Warning: SSL_shutdown SSL_ERROR_SYSCALL", __FUNCTION__); /* Continue */ } else { @@ -1577,7 +1586,7 @@ restconf_openssl_init(clicon_handle h, } int status = setrlimit(RLIMIT_CORE, &rlp); if (status != 0) { - clicon_log(LOG_NOTICE, "%s: setrlimit() failed, %s", __func__, strerror(errno)); + clicon_log(LOG_INFO, "%s: setrlimit() failed, %s", __FUNCTION__, strerror(errno)); } } diff --git a/apps/restconf/restconf_nghttp2.c b/apps/restconf/restconf_nghttp2.c index 698e1a6e..4d23e67f 100644 --- a/apps/restconf/restconf_nghttp2.c +++ b/apps/restconf/restconf_nghttp2.c @@ -865,8 +865,14 @@ error_callback2(nghttp2_session *session, } #endif -/* - * XXX see session_recv +/*! Process an HTTP/2 request received in buffer, process request and send reply + * + * @param[in] rc Restconf connection + * @param[in] buf Character buffer + * @param[in] n Lenght of buf + * @retval 1 OK + * @retval 0 Invald request + * @retval -1 Fatal error */ int http2_recv(restconf_conn *rc, @@ -884,6 +890,18 @@ http2_recv(restconf_conn *rc, } /* may make additional pending frames */ if ((ngerr = nghttp2_session_mem_recv(rc->rc_ngsession, buf, n)) < 0){ + if (ngerr == NGHTTP2_ERR_BAD_CLIENT_MAGIC){ + /* :enum:`NGHTTP2_ERR_BAD_CLIENT_MAGIC` + * Invalid client magic was detected. This error only returns + * when |session| was configured as server and + * `nghttp2_option_set_no_recv_client_magic()` is not used with + * nonzero value. */ + clicon_log(LOG_INFO, "%s Received bad client magic byte strin", __FUNCTION__); + /* unsure if this does anything, byt does not seem to hurt */ + if ((ngerr = nghttp2_session_terminate_session(rc->rc_ngsession, ngerr)) < 0) + clicon_err(OE_NGHTTP2, ngerr, "nghttp2_session_terminate_session %d", ngerr); + goto fail; + } clicon_err(OE_NGHTTP2, ngerr, "nghttp2_session_mem_recv"); goto done; } @@ -895,9 +913,13 @@ http2_recv(restconf_conn *rc, clicon_err(OE_NGHTTP2, ngerr, "nghttp2_session_send"); goto done; } - retval = 0; + retval = 1; /* OK */ done: + clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); return retval; + fail: + retval = 0; + goto done; } /* Send HTTP/2 client connection header, which includes 24 bytes diff --git a/test/test_restconf.sh b/test/test_restconf.sh index 91df06ca..aea1ca80 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -172,37 +172,86 @@ function testrun() echo "curl $CURLOPTS -X GET $proto://$addr/.well-known/host-meta" expectpart "$(curl $CURLOPTS -X GET $proto://$addr/.well-known/host-meta)" 0 "HTTP/$HVER 200" "" "" "" - if ! ${HAVE_LIBNGHTTP2}; then # http/2 + if [ ${HAVE_LIBNGHTTP2} = true -a ${HAVE_LIBEVHTP} = false ]; then + echo "native + http/2 only" + # Important here is robustness of restconf daemon, not a meaningful reply + if [ $proto = http ]; then # see (2) https to http port in restconf_main_native.c + # http protocol mismatch can just close the socket if assumed its http/2 + # everything else would guess it is http/1 which is really wrong here + # The tr statement replaces null char to get rid of annoying message: + # ./test_restconf.sh: line 180: warning: command substitution: ignored null byte in input + new "restconf GET http/1.0 - close" + expectpart "$(curl $CURLOPTS --http1.0 -X GET $proto://$addr/.well-known/host-meta | tr '\0' '\n')" 0 "" --not-- 'HTTP' + else + new "restconf GET https/1.0 - close" + expectpart "$(curl $CURLOPTS --http1.0 -X GET $proto://$addr/.well-known/host-meta)" 52 "" --not-- 'HTTP' - if [ "${WITH_RESTCONF}" = "native" ]; then # XXX does not work with nginx - new "restconf GET http/1.0 - returns 1.0" - expectpart "$(curl $CURLOPTS --http1.0 -X GET $proto://$addr/.well-known/host-meta)" 0 'HTTP/1.0 200 OK' "" "" "" - fi - new "restconf GET http/1.1" - expectpart "$(curl $CURLOPTS --http1.1 -X GET $proto://$addr/.well-known/host-meta)" 0 'HTTP/1.1 200 OK' "" "" "" + fi - # Try http/2 - go back to http/1.1 - new "restconf GET http/2" - expectpart "$(curl $CURLOPTS --http2 -X GET $proto://$addr/.well-known/host-meta)" 0 "HTTP/1.1 200 OK" "" "" "" + if [ $proto = http ]; then + new "restconf GET http/1.1 - close" + expectpart "$(curl $CURLOPTS --http1.1 -X GET $proto://$addr/.well-known/host-meta | tr '\0' '\n')" 0 --not-- 'HTTP' + else + new "restconf GET https/1.1 - close" + expectpart "$(curl $CURLOPTS --http1.1 -X GET $proto://$addr/.well-known/host-meta)" 52 --not-- 'HTTP' + fi + + if [ $proto = http ]; then + new "restconf GET http/2 switch protocol" + expectpart "$(curl $CURLOPTS --http2 -X GET $proto://$addr/.well-known/host-meta | tr '\0' '\n')" 0 --not-- 'HTTP' + else + new "restconf GET https/2 alpn protocol" + expectpart "$(curl $CURLOPTS --http2 -X GET $proto://$addr/.well-known/host-meta)" 0 "HTTP/$HVER 200" "" "" "" + fi - if [ $proto = http ]; then # see (2) https to http port in restconf_main_native.c - new "restconf GET http/2 prior-knowledge (http)" - expectpart "$(curl $CURLOPTS --http2-prior-knowledge -X GET $proto://$addr/.well-known/host-meta 2>&1)" "16 52 55" # "Error in the HTTP2 framing layer" "Connection reset by peer" + # Wrong protocol http when https or vice versa + if [ $proto = http ]; then # see (2) https to http port in restconf_main_native.c + new "Wrong proto=https on http port, expect err 35 wrong version number" + expectpart "$(curl $CURLOPTS -X GET https://$addr:80/.well-known/host-meta 2>&1)" 35 #"wrong version number" # dependent on curl version + else # see (1) http to https port in restconf_main_native.c + new "Wrong proto=http on https port, expect bad request" + expectpart "$(curl $CURLOPTS -X GET http://$addr:443/.well-known/host-meta)" "16 52 55" --not-- 'HTTP' + fi else - new "restconf GET http/2 prior-knowledge(http2)" - expectpart "$(curl $CURLOPTS --http2-prior-knowledge -X GET $proto://$addr/.well-known/host-meta)" 0 'HTTP/1.1 200 OK' "" "" "" - fi + echo "fcgi or native+http/1 or native+http/1+http/2" + if [ "${WITH_RESTCONF}" = "native" ]; then # XXX does not work with nginx + new "restconf GET http/1.0 - returns 1.0" + expectpart "$(curl $CURLOPTS --http1.0 -X GET $proto://$addr/.well-known/host-meta)" 0 'HTTP/1.0 200 OK' "" "" "" + fi + new "restconf GET http/1.1" + expectpart "$(curl $CURLOPTS --http1.1 -X GET $proto://$addr/.well-known/host-meta)" 0 'HTTP/1.1 200 OK' "" "" "" - # Negative test GET datastore - if [ $proto = http ]; then # see (2) https to http port in restconf_main_native.c - new "Wrong proto=https on http port, expect err 35 wrong version number" - expectpart "$(curl $CURLOPTS -X GET https://$addr:80/.well-known/host-meta 2>&1)" 35 #"wrong version number" # dependent on curl version - else # see (1) http to https port in restconf_main_native.c - new "Wrong proto=http on https port, expect bad request" - expectpart "$(curl $CURLOPTS -X GET http://$addr:443/.well-known/host-meta)" 0 "HTTP/$HVER 400" - # expectpart "$(curl $CURLOPTS -X GET http://$addr:443/.well-known/host-meta 2>&1)" 56 "Connection reset by peer" - fi + if ${HAVE_LIBNGHTTP2}; then + # http/1 + http/2 + + new "restconf GET http/2 switch protocol" + expectpart "$(curl $CURLOPTS --http2 -X GET $proto://$addr/.well-known/host-meta)" 0 "" "HTTP/2 200" "" "" "" # Only if http: HTTP/1.1 101 Switching Protocols + else + # http/1 only Try http/2 - go back to http/1.1 + new "restconf GET http/2 switch protocol" + expectpart "$(curl $CURLOPTS --http2 -X GET $proto://$addr/.well-known/host-meta)" 0 "HTTP/1.1 200 OK" "" "" "" + fi + + # http2-prior knowledge + if [ $proto = http ]; then # see (2) https to http port in restconf_main_native.c + new "restconf GET http/2 prior-knowledge (http)" + expectpart "$(curl $CURLOPTS --http2-prior-knowledge -X GET $proto://$addr/.well-known/host-meta 2>&1)" "16 52 55" # "Error in the HTTP2 framing layer" "Connection reset by peer" + else + new "restconf GET https/2 prior-knowledge" + expectpart "$(curl $CURLOPTS --http2-prior-knowledge -X GET $proto://$addr/.well-known/host-meta)" 0 "HTTP/$HVER 200" "" "" "" + fi + + # Wrong protocol http when https or vice versa + if [ $proto = http ]; then # see (2) https to http port in restconf_main_native.c + new "Wrong proto=https on http port, expect err 35 wrong version number" + expectpart "$(curl $CURLOPTS -X GET https://$addr:80/.well-known/host-meta 2>&1)" 35 #"wrong version number" # dependent on curl version + else # see (1) http to https port in restconf_main_native.c + new "Wrong proto=http on https port, expect bad request" + expectpart "$(curl $CURLOPTS -X GET http://$addr:443/.well-known/host-meta)" 0 "HTTP/" "400" + # expectpart "$(curl $CURLOPTS -X GET http://$addr:443/.well-known/host-meta 2>&1)" 56 "Connection reset by peer" + fi fi # HTTP/2 + # Exact match new "restconf get restconf resource. RFC 8040 3.3 (json)" expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+json" $proto://$addr/restconf)" 0 "HTTP/$HVER 200" '{"ietf-restconf:restconf":{"data":{},"operations":{},"yang-library-version":"2019-01-04"}}' @@ -341,7 +390,7 @@ function testrun() new "restconf Check eth/0/0 GET augmented state level 2" expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0/clixon-example:my-status)" 0 "HTTP/$HVER 200" '{"clixon-example:my-status":{"int":42,"str":"foo"}}' - new "restconf Check eth/0/0 added state XXXXXXX" + new "restconf Check eth/0/0 added state" expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+json' $proto://$addr/restconf/data/clixon-example:state)" 0 "HTTP/$HVER 200" '{"clixon-example:state":{"op":\["41","42","43"\]}}' new "restconf Re-post eth/0/0 which should generate error" diff --git a/test/test_restconf_op.sh b/test/test_restconf_op.sh index 4ff5228d..40e58e06 100755 --- a/test/test_restconf_op.sh +++ b/test/test_restconf_op.sh @@ -204,7 +204,6 @@ new "restconf DELETE whole datastore" expectpart "$(curl $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data)" 0 "HTTP/$HVER 204" #--------------- Multiple request in single TCP tests -# XXX THIS HAS LEAKS IN HTTP/1 ONLY! expectpart "$(curl $CURLOPTS -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data?content=config --next $CURLOPTS -H "Content-Type: application/yang-data+json" -X POST $RCPROTO://localhost/restconf/data -d '{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}')" 0 "HTTP/$HVER 200" '' "HTTP/$HVER 201" #--------------- json type tests From 6bb6faadc9b2560594b9a19a5238d6ac7ee9e098 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 19 Jul 2021 08:14:10 +0200 Subject: [PATCH 24/34] * Fixed: mandatory leaf in a uses statement caused abort * Occurence was in ietf-yang-patch.yang --- CHANGELOG.md | 2 ++ lib/clixon/clixon_yang.h | 1 + lib/src/clixon_yang.c | 48 ++++++++++++++++++++++----------- lib/src/clixon_yang_parse_lib.c | 44 +++++++++++++++++++----------- 4 files changed, 64 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4b5c37d..627f204d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,8 @@ Developers may need to change their code ### Corrected Bugs +* Fixed: mandatory leaf in a uses statement caused abort + * Occurence was in ietf-yang-patch.yang * Native RESTCONF fixes for http/1 or http/2 only modes * Memleak in http/1-only * Exit if http/1 request sent to http/2-only (bad client magic) diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index 5d25249e..8e482cad 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -198,6 +198,7 @@ char *yang_argument_get(yang_stmt *ys); int yang_argument_set(yang_stmt *ys, char *arg); cg_var *yang_cv_get(yang_stmt *ys); +int yang_cv_set(yang_stmt *ys, cg_var *cv); cvec *yang_cvec_get(yang_stmt *ys); int yang_cvec_set(yang_stmt *ys, cvec *cvv); uint16_t yang_flag_get(yang_stmt *ys, uint16_t flag); diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 57f5bece..056dee6d 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -256,6 +256,18 @@ yang_cv_get(yang_stmt *ys) return ys->ys_cv; } +/*! Set yang statement CLIgen variable + * @param[in] ys Yang statement node + * @param[in] cv cligen variable + */ +int +yang_cv_set(yang_stmt *ys, + cg_var *cv) +{ + ys->ys_cv = cv; + return 0; +} + /*! Get yang statement CLIgen variable vector * @param[in] ys Yang statement node */ @@ -456,13 +468,14 @@ int ys_free1(yang_stmt *ys, int self) { + cg_var *cv; if (ys->ys_argument){ free(ys->ys_argument); ys->ys_argument = NULL; } - if (ys->ys_cv){ - cv_free(ys->ys_cv); - ys->ys_cv = NULL; + if ((cv = yang_cv_get(ys)) != NULL){ + cv_free(cv); + yang_cv_set(ys, NULL); } if (ys->ys_cvec){ cvec_free(ys->ys_cvec); @@ -601,6 +614,8 @@ ys_cp(yang_stmt *ynew, int i; yang_stmt *ycn; /* new child */ yang_stmt *yco; /* old child */ + cg_var *cvn; + cg_var *cvo; memcpy(ynew, yold, sizeof(*yold)); ynew->ys_parent = NULL; @@ -614,11 +629,13 @@ ys_cp(yang_stmt *ynew, clicon_err(OE_YANG, errno, "strdup"); goto done; } - if (yold->ys_cv) - if ((ynew->ys_cv = cv_dup(yold->ys_cv)) == NULL){ + if ((cvo = yang_cv_get(yold)) != NULL){ + if ((cvn = cv_dup(cvo)) == NULL){ clicon_err(OE_YANG, errno, "cv_dup"); goto done; } + yang_cv_set(ynew, cvn); + } if (yold->ys_cvec) if ((ynew->ys_cvec = cvec_dup(yold->ys_cvec)) == NULL){ clicon_err(OE_YANG, errno, "cvec_dup"); @@ -1770,8 +1787,7 @@ ys_populate_leaf(clicon_handle h, yparent = ys->ys_parent; /* Find parent: list/container */ /* 1. Find type specification and set cv type accordingly */ - if (yang_type_get(ys, &origtype, &yrestype, &options, NULL, NULL, NULL, &fraction_digits) - < 0) + if (yang_type_get(ys, &origtype, &yrestype, &options, NULL, NULL, NULL, &fraction_digits) < 0) goto done; restype = yrestype?yrestype->ys_argument:NULL; if (clicon_type2cv(origtype, restype, ys, &cvtype) < 0) /* This handles non-resolved also */ @@ -1828,7 +1844,7 @@ ys_populate_leaf(clicon_handle h, if ((ret = yang_key_match(yparent, ys->ys_argument)) < 0) goto done; } - ys->ys_cv = cv; + yang_cv_set(ys, cv); retval = 0; done: if (origtype) @@ -2241,7 +2257,7 @@ ys_populate_feature(clicon_handle h, cv_bool_set(cv, found); if (found) clicon_debug(1, "%s %s:%s", __FUNCTION__, module, feature); - ys->ys_cv = cv; + yang_cv_set(ys, cv); ok: retval = 0; done: @@ -2495,7 +2511,7 @@ yang_if_feature(clicon_handle h, yang_stmt *yfeat; /* feature yang node */ int opand = -1; /* -1:not set, 0:or, 1:and */ int enabled = 0; - + cg_var *cv; if ((vec = clicon_strsep(ys->ys_argument, " \t\r\n", &nvec)) == NULL) goto done; /* Two steps: first detect operators @@ -2584,7 +2600,8 @@ yang_if_feature(clicon_handle h, /* Check if this feature is enabled or not * Continue loop to catch unbound features and make verdict at end */ - if (yfeat->ys_cv == NULL || !cv_bool_get(yfeat->ys_cv)){ /* disabled */ + cv = yang_cv_get(yfeat); + if (cv == NULL || !cv_bool_get(cv)){ /* disabled */ /* if AND then this is permanently disabled */ if (opand && enabled) enabled = 0; @@ -3024,14 +3041,15 @@ int yang_mandatory(yang_stmt *ys) { yang_stmt *ym; + cg_var *cv; /* 1) A leaf, choice, anydata, or anyxml node with a "mandatory" * statement with the value "true". */ if (ys->ys_keyword == Y_LEAF || ys->ys_keyword == Y_CHOICE || ys->ys_keyword == Y_ANYDATA || ys->ys_keyword == Y_ANYXML){ if ((ym = yang_find(ys, Y_MANDATORY, NULL)) != NULL){ - if (ym->ys_cv != NULL) /* shouldnt happen */ - return cv_bool_get(ym->ys_cv); + if ((cv = yang_cv_get(ym)) != NULL) /* shouldnt happen */ + return cv_bool_get(cv); } } #if 0 /* See note above */ @@ -3071,9 +3089,9 @@ yang_config(yang_stmt *ys) yang_stmt *ym; if ((ym = yang_find(ys, Y_CONFIG, NULL)) != NULL){ - if (ym->ys_cv == NULL) /* shouldnt happen */ + if (yang_cv_get(ym) == NULL) /* shouldnt happen */ return 1; - return cv_bool_get(ym->ys_cv); + return cv_bool_get(yang_cv_get(ym)); } return 1; } diff --git a/lib/src/clixon_yang_parse_lib.c b/lib/src/clixon_yang_parse_lib.c index 6668be60..2548363b 100644 --- a/lib/src/clixon_yang_parse_lib.c +++ b/lib/src/clixon_yang_parse_lib.c @@ -1767,29 +1767,33 @@ cg_var * ys_parse(yang_stmt *ys, enum cv_type cvtype) { - int cvret; - char *reason = NULL; + int cvret; + char *reason = NULL; + cg_var *cv = NULL; - assert(yang_cv_get(ys) == NULL); /* Cv:s are parsed in different places, difficult to separate */ - if ((ys->ys_cv = cv_new(cvtype)) == NULL){ + if ((cv = yang_cv_get(ys)) != NULL){ + /* eg mandatory in uses is already set and then copied */ + cv_free(cv); + yang_cv_set(ys, NULL); + } + if ((cv = cv_new(cvtype)) == NULL){ clicon_err(OE_YANG, errno, "cv_new"); goto done; } - if ((cvret = cv_parse1(yang_argument_get(ys), ys->ys_cv, &reason)) < 0){ /* error */ + if ((cvret = cv_parse1(yang_argument_get(ys), cv, &reason)) < 0){ /* error */ clicon_err(OE_YANG, errno, "parsing cv"); - ys->ys_cv = NULL; goto done; } if (cvret == 0){ /* parsing failed */ clicon_err(OE_YANG, errno, "Parsing CV: %s", reason); - ys->ys_cv = NULL; goto done; } + yang_cv_set(ys, cv); /* cvret == 1 means parsing is OK */ done: if (reason) free(reason); - return ys->ys_cv; + return yang_cv_get(ys); } /*! First round yang syntactic statement specific checks. No context checks. @@ -1819,6 +1823,7 @@ ys_parse_sub(yang_stmt *ys, char *reason = NULL; int ret; uint32_t minmax; + cg_var *cv = NULL; arg = yang_argument_get(ys); keyword = yang_keyword_get(ys); @@ -1826,7 +1831,11 @@ ys_parse_sub(yang_stmt *ys, case Y_FRACTION_DIGITS: if (ys_parse(ys, CGV_UINT8) == NULL) goto done; - fd = cv_uint8_get(ys->ys_cv); + if ((cv = yang_cv_get(ys)) == NULL){ + clicon_err(OE_YANG, ENOENT, "Unexpected NULL cv"); + goto done; + } + fd = cv_uint8_get(cv); if (fd < 1 || fd > 18){ clicon_err(OE_YANG, errno, "%u: Out of range, should be [1:18]", fd); goto done; @@ -1841,11 +1850,12 @@ ys_parse_sub(yang_stmt *ys, case Y_REVISION_DATE: /* YYYY-MM-DD encoded as uint32 YYYYMMDD */ if (ys_parse_date_arg(arg, &date) < 0) goto done; - if ((ys->ys_cv = cv_new(CGV_UINT32)) == NULL){ + if ((cv = cv_new(CGV_UINT32)) == NULL){ clicon_err(OE_YANG, errno, "cv_new"); goto done; } - cv_uint32_set(ys->ys_cv, date); + yang_cv_set(ys, cv); + cv_uint32_set(cv, date); break; case Y_STATUS: /* RFC7950 7.21.2: "current", "deprecated", or "obsolete". */ if (strcmp(arg, "current") && @@ -1858,13 +1868,14 @@ ys_parse_sub(yang_stmt *ys, break; case Y_MAX_ELEMENTS: case Y_MIN_ELEMENTS: - if ((ys->ys_cv = cv_new(CGV_UINT32)) == NULL){ + if ((cv = cv_new(CGV_UINT32)) == NULL){ clicon_err(OE_YANG, errno, "cv_new"); goto done; } + yang_cv_set(ys, cv); if (keyword == Y_MAX_ELEMENTS && strcmp(arg, "unbounded") == 0) - cv_uint32_set(ys->ys_cv, 0); /* 0 means unbounded for max */ + cv_uint32_set(cv, 0); /* 0 means unbounded for max */ else{ if ((ret = parse_uint32(arg, &minmax, &reason)) < 0){ clicon_err(OE_YANG, errno, "parse_uint32"); @@ -1876,7 +1887,7 @@ ys_parse_sub(yang_stmt *ys, free(reason); goto done; } - cv_uint32_set(ys->ys_cv, minmax); + cv_uint32_set(cv, minmax); } break; case Y_MODIFIER: @@ -1888,11 +1899,12 @@ ys_parse_sub(yang_stmt *ys, case Y_UNKNOWN:{ /* save (optional) argument in ys_cv */ if (extra == NULL) break; - if ((ys->ys_cv = cv_new(CGV_STRING)) == NULL){ + if ((cv = cv_new(CGV_STRING)) == NULL){ clicon_err(OE_YANG, errno, "cv_new"); goto done; } - if ((ret = cv_parse1(extra, ys->ys_cv, &reason)) < 0){ /* error */ + yang_cv_set(ys, cv); + if ((ret = cv_parse1(extra, cv, &reason)) < 0){ /* error */ clicon_err(OE_YANG, errno, "parsing cv"); goto done; } From e0ee365958cc44831b3f776a3657c567c1c77ef5 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 19 Jul 2021 11:15:49 +0200 Subject: [PATCH 25/34] * Fixed: RFC 8040 yang-data extension allows non-key lists * Added YANG_FLAG_NOKEY as exception to mandatory key lists --- CHANGELOG.md | 2 ++ apps/restconf/restconf_lib.c | 11 ++++++++++ lib/clixon/clixon_yang.h | 6 +++++- lib/src/clixon_yang.c | 11 ++++++++-- lib/src/clixon_yang_parse_lib.c | 37 +++++++++++++++------------------ test/mem.sh | 2 +- 6 files changed, 45 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 627f204d..919d820f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,8 @@ Developers may need to change their code ### Corrected Bugs +* Fixed: RFC 8040 yang-data extension allows non-key lists + * Added YANG_FLAG_NOKEY as exception to mandatory key lists * Fixed: mandatory leaf in a uses statement caused abort * Occurence was in ietf-yang-patch.yang * Native RESTCONF fixes for http/1 or http/2 only modes diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index 9b0592c4..1f5c28c0 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -499,6 +499,14 @@ restconf_insert_attributes(cxobj *xdata, * @param[in] ys Yang node of (unknown) statement belonging to extension * @retval 0 OK, all callbacks executed OK * @retval -1 Error in one callback + * @note This extension adds semantics to YANG according to RFC8040 as follows: + * - The list-stmt is not required to have a key-stmt defined.(NB!!) + * - The if-feature-stmt is ignored if present. + * - The config-stmt is ignored if present. + * - The available identity values for any 'identityref' + * leaf or leaf-list nodes are limited to the module + * containing this extension statement and the modules + * imported into that module. */ int restconf_main_extension_cb(clicon_handle h, @@ -522,6 +530,9 @@ restconf_main_extension_cb(clicon_handle h, goto ok; if ((yn = ys_dup(yc)) == NULL) goto done; + /* yang-data extension: The list-stmt is not required to have a key-stmt defined. + */ + yang_flag_set(yn, YANG_FLAG_NOKEY); if (yn_insert(yang_parent_get(ys), yn) < 0) goto done; ok: diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index 8e482cad..437318a7 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -53,8 +53,12 @@ */ #define YANG_FLAG_MARK 0x01 /* (Dynamic) marker for dynamic algorithms, eg expand and DAG */ #define YANG_FLAG_TMP 0x02 /* (Dynamic) marker for dynamic algorithms, eg DAG detection */ +#define YANG_FLAG_NOKEY 0x04 /* Key not mandatory in this list, see eg yang-data extension in + * RFC 8040 / ietf-restconf.yang + * see restconf_main_extension_cb + */ #ifdef XML_EXPLICIT_INDEX -#define YANG_FLAG_INDEX 0x04 /* This yang node under list is (extra) index. --> you can access +#define YANG_FLAG_INDEX 0x08 /* This yang node under list is (extra) index. --> you can access * list elements using this index with binary search */ #endif diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 056dee6d..7c1af5a1 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -259,11 +259,15 @@ yang_cv_get(yang_stmt *ys) /*! Set yang statement CLIgen variable * @param[in] ys Yang statement node * @param[in] cv cligen variable + * @note: Frees on replace, not if cv is NULL. This is for some ys_cp/ys_dup cases, which means + * you need to free it explicitly to set it to NULL proper. */ int yang_cv_set(yang_stmt *ys, cg_var *cv) { + if (cv != NULL && ys->ys_cv != NULL) + cv_free(ys->ys_cv); ys->ys_cv = cv; return 0; } @@ -469,13 +473,14 @@ ys_free1(yang_stmt *ys, int self) { cg_var *cv; + if (ys->ys_argument){ free(ys->ys_argument); ys->ys_argument = NULL; } if ((cv = yang_cv_get(ys)) != NULL){ + yang_cv_set(ys, NULL); /* only frees on replace */ cv_free(cv); - yang_cv_set(ys, NULL); } if (ys->ys_cvec){ cvec_free(ys->ys_cvec); @@ -629,6 +634,7 @@ ys_cp(yang_stmt *ynew, clicon_err(OE_YANG, errno, "strdup"); goto done; } + yang_cv_set(ynew, NULL); if ((cvo = yang_cv_get(yold)) != NULL){ if ((cvn = cv_dup(cvo)) == NULL){ clicon_err(OE_YANG, errno, "cv_dup"); @@ -1469,7 +1475,8 @@ ys_real_module(yang_stmt *ys, * @param[in] ys Any yang statement in a yang tree * @retval yspec The top yang specification * @see ys_module - * @see yang_augment_node where shortcut is set + * @see yang_augment_node where shortcut is set for augment + * @see yang_myroot for first node under (sub)module */ yang_stmt * ys_spec(yang_stmt *ys) diff --git a/lib/src/clixon_yang_parse_lib.c b/lib/src/clixon_yang_parse_lib.c index 2548363b..c472f292 100644 --- a/lib/src/clixon_yang_parse_lib.c +++ b/lib/src/clixon_yang_parse_lib.c @@ -633,13 +633,16 @@ yang_expand_uses_node(yang_stmt *yn, yang_argument_get(yg), yang_argument_get(ygrouping) ); - goto done; + goto done; } if (yang_when_xpath_set(yg, wxpath) < 0) goto done; if (yang_when_nsc_set(yg, wnsc) < 0) goto done; } + /* This is for extensions that allow list keys to be optional, see restconf_main_extension_cb */ + if (yang_flag_get(ys, YANG_FLAG_NOKEY)) + yang_flag_set(yg, YANG_FLAG_NOKEY); yn->ys_stmt[i+k] = yg; yg->ys_parent = yn; k++; @@ -1196,7 +1199,7 @@ ys_schemanode_check(yang_stmt *ys, * Verify the following rule: * RFC 7950 7.8.2: The "key" statement, which MUST be present if the list represents * configuration and MAY be present otherwise - * Unless CLICON_YANG_LIST_CHECK is false + * Unless CLICON_YANG_LIST_CHECK is false (obsolete) * OR it is the "errors" rule of the ietf-restconf spec which seems to be a special case. */ static int @@ -1222,28 +1225,22 @@ ys_list_check(clicon_handle h, if (keyw == Y_LIST && yang_find(ys, Y_KEY, NULL) == 0){ ymod = ys_module(ys); -#if 1 - /* Except restconf error extension from sanity check, dont know why it has no keys */ - if (strcmp(yang_find_mynamespace(ys),"urn:ietf:params:xml:ns:yang:ietf-restconf")==0 && - strcmp(yang_argument_get(ys),"error") == 0) - ; - else -#endif - { - if (clicon_option_bool(h, "CLICON_YANG_LIST_CHECK")){ - clicon_log(LOG_ERR, "Error: LIST \"%s\" in module \"%s\" lacks key statement which MUST be present (See RFC 7950 Sec 7.8.2)", - yang_argument_get(ys), - yang_argument_get(ymod) - ); - - goto done; - } - else - clicon_log(LOG_WARNING, "Warning: LIST \"%s\" in module \"%s\" lacks key statement which MUST be present (See RFC 7950 Sec 7.8.2)", + /* Except nokey exceptions such as rrc 8040 yang-data */ + if (!yang_flag_get(yroot, YANG_FLAG_NOKEY)){ + /* Note obsolete */ + if (clicon_option_bool(h, "CLICON_YANG_LIST_CHECK")){ + clicon_log(LOG_ERR, "Error: LIST \"%s\" in module \"%s\" lacks key statement which MUST be present (See RFC 7950 Sec 7.8.2)", yang_argument_get(ys), yang_argument_get(ymod) ); + goto done; } + else + clicon_log(LOG_WARNING, "Warning: LIST \"%s\" in module \"%s\" lacks key statement which MUST be present (See RFC 7950 Sec 7.8.2)", + yang_argument_get(ys), + yang_argument_get(ymod) + ); + } } /* Traverse subs */ if (yang_schemanode(ys) || keyw == Y_MODULE || keyw == Y_SUBMODULE){ diff --git a/test/mem.sh b/test/mem.sh index 42e2667e..ed493d78 100755 --- a/test/mem.sh +++ b/test/mem.sh @@ -40,7 +40,7 @@ function memonce(){ sudo chmod 660 $valgrindfile sudo chown www-data $valgrindfile : ${DEMWAIT:=15} # valgrind backend needs some time to get up - clixon_restconf="/usr/bin/valgrind --leak-check=full --show-leak-kinds=all --suppressions=./valgrind-clixon.supp --track-fds=yes --trace-children=no --child-silent-after-fork=yes --log-file=$valgrindfile clixon_restconf" + clixon_restconf="/usr/bin/valgrind --num-callers=50 --leak-check=full --show-leak-kinds=all --suppressions=./valgrind-clixon.supp --track-fds=yes --trace-children=no --child-silent-after-fork=yes --log-file=$valgrindfile clixon_restconf" ;; *) From 0c72100a0577f5314fc17c1df797790e841e3540 Mon Sep 17 00:00:00 2001 From: Alan Yaniger Date: Mon, 19 Jul 2021 13:17:10 +0300 Subject: [PATCH 26/34] implement yang-patch --- apps/restconf/restconf_api.h | 2 + apps/restconf/restconf_api_native.c | 1 + apps/restconf/restconf_main_fcgi.c | 6 + apps/restconf/restconf_main_native.c | 6 + apps/restconf/restconf_methods.c | 540 +++++++++++++++++++- apps/restconf/restconf_methods_post.c | 3 +- apps/restconf/restconf_methods_post.h | 1 + apps/restconf/restconf_root.c | 2 +- example/main/clixon-example@2020-12-01.yang | 3 + example/main/example.xml | 2 + lib/clixon/clixon_json.h | 1 - yang/mandatory/Makefile.in | 1 + 12 files changed, 563 insertions(+), 5 deletions(-) diff --git a/apps/restconf/restconf_api.h b/apps/restconf/restconf_api.h index a44cb440..f94b8ce2 100644 --- a/apps/restconf/restconf_api.h +++ b/apps/restconf/restconf_api.h @@ -52,4 +52,6 @@ int restconf_reply_send(void *req, int code, cbuf *cb, int head); cbuf *restconf_get_indata(void *req); +#define YANG_PATCH + #endif /* _RESTCONF_API_H_ */ diff --git a/apps/restconf/restconf_api_native.c b/apps/restconf/restconf_api_native.c index c4162b2e..70a8915a 100644 --- a/apps/restconf/restconf_api_native.c +++ b/apps/restconf/restconf_api_native.c @@ -191,3 +191,4 @@ restconf_get_indata(void *req0) return cb; } + diff --git a/apps/restconf/restconf_main_fcgi.c b/apps/restconf/restconf_main_fcgi.c index 88622b6a..b8508102 100644 --- a/apps/restconf/restconf_main_fcgi.c +++ b/apps/restconf/restconf_main_fcgi.c @@ -419,6 +419,12 @@ main(int argc, if (yang_spec_parse_module(h, "ietf-restconf", NULL, yspec)< 0) goto done; +#ifdef YANG_PATCH + /* Load yang restconf patch module */ + if (yang_spec_parse_module(h, "ietf-yang-patch", NULL, yspec)< 0) + goto done; +#endif // YANG_PATCH + /* Add netconf yang spec, used as internal protocol */ if (netconf_module_load(h) < 0) goto done; diff --git a/apps/restconf/restconf_main_native.c b/apps/restconf/restconf_main_native.c index 631701c1..e2f940b1 100644 --- a/apps/restconf/restconf_main_native.c +++ b/apps/restconf/restconf_main_native.c @@ -1728,6 +1728,12 @@ restconf_clixon_init(clicon_handle h, if (yang_spec_parse_module(h, "ietf-restconf", NULL, yspec)< 0) goto done; +#ifdef YANG_PATCH + /* Load yang restconf patch module */ + if (yang_spec_parse_module(h, "ietf-yang-patch", NULL, yspec)< 0) + goto done; +#endif // YANG_PATCH + /* Add netconf yang spec, used as internal protocol */ if (netconf_module_load(h) < 0) goto done; diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 10844869..3d9630bb 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -74,6 +74,7 @@ #include "restconf_api.h" #include "restconf_err.h" #include "restconf_methods.h" +#include "restconf_methods_post.h" /*! REST OPTIONS method * According to restconf @@ -579,6 +580,536 @@ api_data_write(clicon_handle h, return retval; } /* api_data_write */ +#ifdef YANG_PATCH +/*! YANG PATCH method + * @param[in] h Clixon handle + * @param[in] req Generic Www handle + * @param[in] api_path0 According to restconf (Sec 3.5.3.1 in rfc8040) + * @param[in] pcvec Vector of path ie DOCUMENT_URI element + * @param[in] pi Offset, where to start pcvec + * @param[in] qvec Vector of query string (QUERY_STRING) + * @param[in] data Stream input data + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] media_out Output media + * Netconf: (nc:operation="merge") + * See RFC8072 + * YANG patch can be used to "create", "delete", "insert", "merge", "move", "replace", and/or + "remove" a resource within the target resource. + * Currently "move" not supported + */ +static int +api_data_yang_patch(clicon_handle h, + void *req, + char *api_path0, + cvec *pcvec, + int pi, + cvec *qvec, + char *data, + int pretty, + restconf_media media_out, + ietf_ds_t ds) +{ + int retval = -1; + int i; + cxobj *xdata0 = NULL; /* Original -d data struct (including top symbol) */ + cbuf *cbx = NULL; + cxobj *xtop = NULL; /* top of api-path */ + cxobj *xbot = NULL; /* bottom of api-path */ + yang_stmt *ybot = NULL; /* yang of xbot */ + cxobj *xbot_tmp = NULL; + yang_stmt *yspec; + char *api_path; + cxobj *xret = NULL; + cxobj *xretcom = NULL; /* return from commit */ + cxobj *xretdis = NULL; /* return from discard-changes */ + cxobj *xerr = NULL; /* malloced must be freed */ + int ret; + cvec *nsc = NULL; + yang_bind yb; + char *xpath = NULL; + const int temp_str_malloc_size = 5000; + char *path_orig_1 = NULL; + + clicon_debug(1, "%s api_path:\"%s\"", __FUNCTION__, api_path0); + if ((yspec = clicon_dbspec_yang(h)) == NULL){ + clicon_err(OE_FATAL, 0, "No DB_SPEC"); + goto done; + } + api_path=api_path0; + /* strip /... from start */ + for (i=0; i= 0; l--) { + if (target_val[l] == '/') { + idx = l; + break; + } + } + strncpy(post_req_uri, target_val, idx); + strcat(simple_patch_request_uri, post_req_uri); + free(post_req_uri); + for (int l = strlen(json_simple_patch); l>= 0; l--) { + char c = json_simple_patch[l]; + if (c == '}') { + json_simple_patch[l] = ']'; + json_simple_patch[l + 1] = '}'; + break; + } + } + + // Send the POST request + ret = api_data_post(h, req, simple_patch_request_uri, pi, qvec, json_simple_patch, pretty, YANG_DATA_JSON, media_out, ds ); + if (value_vec_tmp != NULL) + free(value_vec_tmp); + free(x_simple_patch); + free(patch_header); // NULL check was already done before + if (ret != 0) + goto done; + break; + } + + // For "create", put all the data values into a single POST request + if (strcmp(op_val,"create") == 0) { + for (int k = 0; k < value_vec_len; k++) { + if (value_vec[k] != NULL) { + value_vec_tmp = xml_dup(value_vec[k]); + xml_addsub(x_simple_patch, value_vec_tmp); + } + } + + // Send the POST request + cbuf* cb = cbuf_new(); + xml2json_cbuf(cb, x_simple_patch, 1); + char *json_simple_patch = cbuf_get(cb); + ret = api_data_post(h, req, simple_patch_request_uri, pi, qvec, json_simple_patch, pretty, YANG_DATA_JSON, media_out, ds ); + if (value_vec_tmp != NULL) + free(value_vec_tmp); + free(x_simple_patch); + free(patch_header); // NULL check was already done before + if (ret != 0) + goto done; + break; + } + // For "insert", make a api_data_post request + if (strcmp(op_val, "insert") == 0) { + char *json_simple_patch = malloc(temp_str_malloc_size); + if (json_simple_patch == NULL) + goto done; + memset(json_simple_patch, 0, temp_str_malloc_size); + + // Loop through the XML, and get each value + for (int k = 0; k < value_vec_len; k++) { + if (value_vec[k] != NULL) { + value_vec_tmp = xml_dup(value_vec[k]); + xml_addsub(x_simple_patch, value_vec_tmp); + } + } + cbuf* cb = cbuf_new(); + xml2json_cbuf(cb, x_simple_patch, 1); + + // Some ugly text processing to get the JSON to match what api_data_post() expects + char *json_simple_patch_tmp = cbuf_get(cb); + int brace_count = 0; + for (int l = 0; l < strlen(json_simple_patch_tmp); l++) { + char c = json_simple_patch_tmp[l]; + if (c == '{') { + brace_count++; + if (brace_count == 2) { + json_simple_patch[strlen(json_simple_patch)] = '['; + } + } + json_simple_patch[strlen(json_simple_patch)] = c; + } + for (int l = strlen(json_simple_patch); l>= 0; l--) { + char c = json_simple_patch[l]; + if (c == '}') { + json_simple_patch[l] = ']'; + json_simple_patch[l + 1] = '}'; + break; + } + } + + // Set the insert attributes + cvec* qvec_tmp = NULL; + qvec_tmp = cvec_new(0); + if (qvec_tmp == NULL) + goto done; + cg_var *cv; + if ((cv = cvec_add(qvec_tmp, CGV_STRING)) == NULL){ + goto done; + } + cv_name_set(cv, "insert"); + cv_string_set(cv, where_val); + char *point_str = malloc(temp_str_malloc_size); + if (point_str == NULL) + goto done; + memset(point_str, 0, temp_str_malloc_size); + strcpy(point_str, api_path); + strcat(point_str, point_val); + if ((cv = cvec_add(qvec_tmp, CGV_STRING)) == NULL){ + goto done; + } + cv_name_set(cv, "point"); + cv_string_set(cv, point_str); + + // Send the POST request + ret = api_data_post(h, req, simple_patch_request_uri, pi, qvec_tmp, json_simple_patch, pretty, YANG_DATA_JSON, media_out, ds ); + if (cb != NULL) + cbuf_free(cb); + if (value_vec_tmp != NULL) + free(value_vec_tmp); + free(point_str); // NULL check was already done above + free(json_simple_patch); // NULL check was already done above + free(patch_header); // NULL check was already done before + if (x_simple_patch != NULL) + free(x_simple_patch); + break; + } + + // For merge", make single simple patch requests for each value + if (strcmp(op_val,"merge") == 0) { + if (key_xn != NULL) + xml_addsub(x_simple_patch, key_xn); + + char *json_simple_patch = malloc(temp_str_malloc_size); + if (json_simple_patch == NULL) + goto done; + + // Loop through the XML, create JSON from each one, and submit a simple patch + for (int k = 0; k < value_vec_len; k++) { + if (value_vec[k] != NULL) { + value_vec_tmp = xml_dup(value_vec[k]); + xml_addsub(x_simple_patch, value_vec_tmp); + } + cbuf* cb = cbuf_new(); + xml2json_cbuf(cb, x_simple_patch, 1); + + // Some ugly text processing to get the JSON to match what api_data_write() expects for a simple patch + char *json_simple_patch_tmp = cbuf_get(cb); + memset(json_simple_patch, 0, temp_str_malloc_size); + int brace_count = 0; + for (int l = 0; l < strlen(json_simple_patch_tmp); l++) { + char c = json_simple_patch_tmp[l]; + if (c == '{') { + brace_count++; + if (brace_count == 2) { + json_simple_patch[strlen(json_simple_patch)] = '['; + } + } + json_simple_patch[strlen(json_simple_patch)] = c; + } + for (int l = strlen(json_simple_patch); l>= 0; l--) { + char c = json_simple_patch[l]; + if (c == '}') { + json_simple_patch[l] = ']'; + json_simple_patch[l + 1] = '}'; + break; + } + } + if (value_vec_tmp != NULL) + free(value_vec_tmp); + // Send the simple patch request + ret = api_data_write(h, req, simple_patch_request_uri, pcvec, pi, qvec, json_simple_patch, pretty, YANG_DATA_JSON, media_out, plain_patch_val, ds ); + cbuf_free(cb); + } + free(json_simple_patch); // NULL check was already done above + free(patch_header); // NULL check was already done before + if (x_simple_patch != NULL) + free(x_simple_patch); + } + } + if ((strcmp(op_val, "delete") == 0) || + (strcmp(op_val, "remove") == 0)) { + strcat(simple_patch_request_uri, target_val); + if (strcmp(op_val, "delete") == 0) { + // TODO - send error + } else { + // TODO - do not send error + } + api_data_delete(h, req, simple_patch_request_uri, pi, pretty, YANG_DATA_JSON, ds); + } + if (simple_patch_request_uri) + free(simple_patch_request_uri); + if (api_path_target) + free(api_path_target); + } + ok: + retval = 0; + done: + if (path_orig_1 != NULL) + free(path_orig_1); + if (vec) + free(vec); + if (xpath) + free(xpath); + if (nsc) + xml_nsctx_free(nsc); + if (xret) + xml_free(xret); + if (xerr) + xml_free(xerr); + if (xretcom) + xml_free(xretcom); + if (xretdis) + xml_free(xretdis); + if (xtop) + xml_free(xtop); + if (xdata0) + xml_free(xdata0); + if (cbx) + cbuf_free(cbx); + return retval; +} +#endif // YANG_PATCH + /*! Generic REST PUT method * @param[in] h Clixon handle * @param[in] req Generic Www handle @@ -673,9 +1204,16 @@ api_data_patch(clicon_handle h, media_in, media_out, 1, ds); break; case YANG_PATCH_XML: - case YANG_PATCH_JSON: /* RFC 8072 patch */ ret = restconf_notimplemented(h, req, pretty, media_out); break; + case YANG_PATCH_JSON: /* RFC 8072 patch */ +#ifdef YANG_PATCH + ret = api_data_yang_patch(h, req, api_path0, pcvec, pi, qvec, data, pretty, + media_out, ds); +#else + ret = restconf_unsupported_media(h, req, pretty, media_out); +#endif + break; default: ret = restconf_unsupported_media(h, req, pretty, media_out); break; diff --git a/apps/restconf/restconf_methods_post.c b/apps/restconf/restconf_methods_post.c index bc0e2bfb..d018b9e7 100644 --- a/apps/restconf/restconf_methods_post.c +++ b/apps/restconf/restconf_methods_post.c @@ -155,6 +155,7 @@ api_data_post(clicon_handle h, cvec *qvec, char *data, int pretty, + restconf_media media_in, restconf_media media_out, ietf_ds_t ds) { @@ -178,7 +179,6 @@ api_data_post(clicon_handle h, cxobj *x; char *username; int ret; - restconf_media media_in; int nrchildren0 = 0; yang_bind yb; @@ -231,7 +231,6 @@ api_data_post(clicon_handle h, * If xbot is top-level (api_path=null) it does not have a spec therefore look for * top-level (yspec) otherwise assume parent (xbot) is populated. */ - media_in = restconf_content_type(h); switch (media_in){ case YANG_DATA_XML: if ((ret = clixon_xml_parse_string(data, yb, yspec, &xbot, &xerr)) < 0){ diff --git a/apps/restconf/restconf_methods_post.h b/apps/restconf/restconf_methods_post.h index f01e53ee..3317d3ea 100644 --- a/apps/restconf/restconf_methods_post.h +++ b/apps/restconf/restconf_methods_post.h @@ -44,6 +44,7 @@ int api_data_post(clicon_handle h, void *req, char *api_path, int pi, cvec *qvec, char *data, int pretty, + restconf_media media_in, restconf_media media_out, ietf_ds_t ds); int api_operations_post(clicon_handle h, void *req, char *api_path, diff --git a/apps/restconf/restconf_root.c b/apps/restconf/restconf_root.c index aa79953e..068acb4b 100644 --- a/apps/restconf/restconf_root.c +++ b/apps/restconf/restconf_root.c @@ -331,7 +331,7 @@ api_data(clicon_handle h, retval = api_data_get(h, req, api_path, pcvec, pi, qvec, pretty, media_out, ds); } else if (strcmp(request_method, "POST")==0) { - retval = api_data_post(h, req, api_path, pi, qvec, data, pretty, media_out, ds); + retval = api_data_post(h, req, api_path, pi, qvec, data, pretty, restconf_content_type(h), media_out, ds); } else if (strcmp(request_method, "PUT")==0) { if (read_only) diff --git a/example/main/clixon-example@2020-12-01.yang b/example/main/clixon-example@2020-12-01.yang index 412e571d..361d81f0 100644 --- a/example/main/clixon-example@2020-12-01.yang +++ b/example/main/clixon-example@2020-12-01.yang @@ -15,6 +15,9 @@ module clixon-example { import ietf-datastores { prefix ds; } + import example-jukebox { + prefix ej; + } description "Clixon example used as a part of the Clixon test suite. It can be used as a basis for making new Clixon applications. diff --git a/example/main/example.xml b/example/main/example.xml index d745a759..058ecf9b 100644 --- a/example/main/example.xml +++ b/example/main/example.xml @@ -1,6 +1,7 @@ /usr/local/etc/example.xml ietf-netconf:startup + clixon-restconf:allow-auth-none /usr/local/share/clixon clixon-example example @@ -21,4 +22,5 @@ disabled true false + truenonedefault
0.0.0.0
80false
diff --git a/lib/clixon/clixon_json.h b/lib/clixon/clixon_json.h index 8bd45d10..2d4e1175 100644 --- a/lib/clixon/clixon_json.h +++ b/lib/clixon/clixon_json.h @@ -52,5 +52,4 @@ int json_print(FILE *f, cxobj *x); int xml2json_vec(FILE *f, cxobj **vec, size_t veclen, int pretty); int clixon_json_parse_string(char *str, yang_bind yb, yang_stmt *yspec, cxobj **xt, cxobj **xret); int clixon_json_parse_file(FILE *fp, yang_bind yb, yang_stmt *yspec, cxobj **xt, cxobj **xret); - #endif /* _CLIXON_JSON_H */ diff --git a/yang/mandatory/Makefile.in b/yang/mandatory/Makefile.in index ca68b32a..7a1d299e 100644 --- a/yang/mandatory/Makefile.in +++ b/yang/mandatory/Makefile.in @@ -50,6 +50,7 @@ YANGSPECS += ietf-restconf-monitoring@2017-01-26.yang YANGSPECS += ietf-yang-library@2019-01-04.yang YANGSPECS += ietf-yang-types@2013-07-15.yang YANGSPECS += ietf-datastores@2018-02-14.yang +YANGSPECS += ietf-yang-patch@2021-07-01.yang all: From df6f26c0debc38ebba5761aeb2eaf5709a2b69bd Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 19 Jul 2021 22:07:21 +0200 Subject: [PATCH 27/34] Misc cleanups, remove old yangs, typos, etc --- example/main/Makefile.in | 1 + example/main/example_backend.c | 1 + include/clixon_custom.h | 2 +- lib/src/clixon_yang_module.c | 1 + yang/clixon/Makefile.in | 2 +- yang/clixon/clixon-config.yang | 1 - ...-08.yang => clixon-config@2021-07-11.yang} | 95 +++++++- yang/clixon/clixon-lib@2020-12-30.yang | 180 -------------- yang/clixon/clixon-restconf@2021-03-15.yang | 221 ------------------ 9 files changed, 96 insertions(+), 408 deletions(-) delete mode 120000 yang/clixon/clixon-config.yang rename yang/clixon/{clixon-config@2021-03-08.yang => clixon-config@2021-07-11.yang} (90%) delete mode 100644 yang/clixon/clixon-lib@2020-12-30.yang delete mode 100644 yang/clixon/clixon-restconf@2021-03-15.yang diff --git a/example/main/Makefile.in b/example/main/Makefile.in index 356426c8..f1c28e09 100644 --- a/example/main/Makefile.in +++ b/example/main/Makefile.in @@ -101,6 +101,7 @@ BE_SRC = $(APPNAME)_backend.c BE_OBJ = $(BE_SRC:%.c=%.o) $(BE_PLUGIN): $(BE_OBJ) ifeq ($(LINKAGE),static) +# can include -L in LDFLAGS? $(CC) -Wall -shared $(LDFLAGS) -o $@ -lc $< -lclixon -L ../../apps/backend/ -lclixon_backend else $(CC) -Wall -shared $(LDFLAGS) -o $@ -lc $< -lclixon -lclixon_backend diff --git a/example/main/example_backend.c b/example/main/example_backend.c index cad6dc30..1d907030 100644 --- a/example/main/example_backend.c +++ b/example/main/example_backend.c @@ -1012,6 +1012,7 @@ example_exit(clicon_handle h) return 0; } +/* Forward declaration */ clixon_plugin_api *clixon_plugin_init(clicon_handle h); static clixon_plugin_api api = { diff --git a/include/clixon_custom.h b/include/clixon_custom.h index 30e114d0..d4af4463 100644 --- a/include/clixon_custom.h +++ b/include/clixon_custom.h @@ -108,7 +108,7 @@ * added to its parent but then it is more difficult to check trhe when condition. * This fix add the parent x0p as a "candidate" so that the xpath-eval function can use it as * an alernative if it exists. - * Note although this solves many usecases involving parents and absolute paths, itstill does not + * Note although this solves many usecases involving parents and absolute paths, it still does not * solve all usecases, such as absolute usecases where the added node is looked for */ #define XML_PARENT_CANDIDATE diff --git a/lib/src/clixon_yang_module.c b/lib/src/clixon_yang_module.c index 03e9695c..3d47f6f7 100644 --- a/lib/src/clixon_yang_module.c +++ b/lib/src/clixon_yang_module.c @@ -112,6 +112,7 @@ modstate_diff_free(modstate_diff_t *md) * * Load RFC7895 yang spec, module-set-id, etc. * @param[in] h Clicon handle + * @see netconf_module_load */ int yang_modules_init(clicon_handle h) diff --git a/yang/clixon/Makefile.in b/yang/clixon/Makefile.in index 555662be..ddd83935 100644 --- a/yang/clixon/Makefile.in +++ b/yang/clixon/Makefile.in @@ -41,7 +41,7 @@ datarootdir = @datarootdir@ # See also OPT_YANG_INSTALLDIR for the standard yang files YANG_INSTALLDIR = @YANG_INSTALLDIR@ -YANGSPECS = clixon-config@2021-05-20.yang # 5.2 +YANGSPECS = clixon-config@2021-07-11.yang # 5.3 YANGSPECS += clixon-lib@2021-03-08.yang # 5.1 YANGSPECS += clixon-rfc5277@2008-07-01.yang YANGSPECS += clixon-xml-changelog@2019-03-21.yang diff --git a/yang/clixon/clixon-config.yang b/yang/clixon/clixon-config.yang deleted file mode 120000 index 91d563e9..00000000 --- a/yang/clixon/clixon-config.yang +++ /dev/null @@ -1 +0,0 @@ -clixon-config@2021-03-08.yang \ No newline at end of file diff --git a/yang/clixon/clixon-config@2021-03-08.yang b/yang/clixon/clixon-config@2021-07-11.yang similarity index 90% rename from yang/clixon/clixon-config@2021-03-08.yang rename to yang/clixon/clixon-config@2021-07-11.yang index eb217e2e..dfc8d54b 100644 --- a/yang/clixon/clixon-config@2021-03-08.yang +++ b/yang/clixon/clixon-config@2021-07-11.yang @@ -43,12 +43,28 @@ module clixon-config { ***** END LICENSE BLOCK *****"; + revision 2021-07-11 { + description + "Added option + CLICON_SYSTEM_CAPABILITIES"; + } + revision 2021-05-20 { + description + "Added option: + CLICON_RESTCONF_USER + CLICON_RESTCONF_PRIVILEGES + CLICON_RESTCONF_INSTALLDIR + CLICON_RESTCONF_STARTUP_DONTUPDATE + CLICON_NETCONF_MESSAGE_ID_OPTIONAL + Released in Clixon 5.2"; + } revision 2021-03-08 { description "Added option: CLICON_NETCONF_HELLO_OPTIONAL CLICON_CLI_AUTOCLI_EXCLUDE - CLICON_XMLDB_UPGRADE_CHECKOLD"; + CLICON_XMLDB_UPGRADE_CHECKOLD + Released in Clixon 5.1"; } revision 2020-12-30 { description @@ -171,6 +187,10 @@ module clixon-config { "Commit startup configuration into running state After reboot when no persistent running db exists"; } + enum running-startup{ + description + "First try running db, if it is empty try startup db."; + } } } typedef datastore_format{ @@ -406,7 +426,11 @@ module clixon-config { "If false, skip Yang list check sanity checks from RFC 7950, Sec 7.8.2: The 'key' statement, which MUST be present if the list represents configuration. Some yang specs seem not to fulfil this. However, if you reset this, there may - be follow-up errors due to code that assumes a configuration list has keys"; + be follow-up errors due to code that assumes a configuration list has keys + Marked as obsolete since the observation above seemed to be related to the + yang-data extension in RFC8040 allows non-key lists. This has been implemented + by a YANG_FLAG_NOKEY yang flag mechanism"; + status obsolete; } leaf CLICON_YANG_UNKNOWN_ANYDATA{ type boolean; @@ -421,6 +445,18 @@ module clixon-config { only loading from startup but may occur in other circumstances as well. This means that sanity checks of erroneous XML/JSON may not be properly signalled."; } + leaf CLICON_SYSTEM_CAPABILITIES { + type boolean; + default false; + description + "Enable module ietf-system-capabilities and ietf-notification-capabilities + Note: There are several dependencies: + - ietf-yang-library revision 2019-01-04 is REQUIRED + - nacm + - ietf-yang-structure-ext.yang, + - ietf-yang-instance-data + see draft-ietf-netconf-notification-capabilities-17"; + } leaf CLICON_BACKEND_DIR { type string; description @@ -451,6 +487,16 @@ module clixon-config { is returned, which conforms to the RFC. Note this applies only to external NETCONF, not the internal (IPC) netconf"; } + leaf CLICON_NETCONF_MESSAGE_ID_OPTIONAL { + type boolean; + default false; + description + "This option relates to RFC 6241 Sec 4.1 Element + The element has a mandatory attribute 'message-id', which is a + string chosen by the sender of the RPC. + If true, an RPC can be sent without a message-id. + This applies to both external NETCONF and internal (IPC) netconf"; + } leaf CLICON_RESTCONF_DIR { type string; description @@ -470,7 +516,28 @@ module clixon-config { Note: Obsolete, use fcgi-socket in clixon-restconf.yang instead"; status obsolete; } - + leaf CLICON_RESTCONF_INSTALLDIR { + type string; + default "/usr/local/sbin"; + description + "Path to dir of clixon-restconf daemon binary as used by backend if started internally + Discussion: Somewhat problematic to have it as run time option. It may think it + should be known at configure or install time, but for example the main docker + installation moves the binaries, and this may be true elsewehere too. + Maybe one could locate it via PATHs search"; + } + leaf CLICON_RESTCONF_STARTUP_DONTUPDATE { + type boolean; + default false; + description + "According to RFC 8040 Sec 1.4: + If the NETCONF server supports :startup, the RESTCONF server MUST automatically + update the [...] startup configuration [...] as a consequence of a RESTCONF + edit operation. + Setting this option disables this behaviour, ie the startup configuration is NOT + automatically updated. + If this option is false, the startup is autoamtically updated following the RFC"; + } leaf CLICON_RESTCONF_PRETTY { type boolean; default true; @@ -486,6 +553,26 @@ module clixon-config { Note: Obsolete, use pretty in clixon-restconf.yang instead"; status obsolete; } + leaf CLICON_RESTCONF_USER { + type string; + description + "Run clixon_daemon as this user + When drop privileges is used, the daemon will drop privileges to this user. + In pre-5.2 code this was configured as compile-time constant WWWUSER with + default value www-data + See also CLICON_PRIVILEGES setting"; + default www-data; + } + leaf CLICON_RESTCONF_PRIVILEGES { + type priv_mode; + default drop_perm; + description + "Restconf privileges mode. + If drop_perm or drop_temp then drop privileges to CLICON_RESTCONF_USER. + If the platform does not support getresuid and accompanying functions, the mode + must be set to 'none'. + "; + } leaf CLICON_CLI_DIR { type string; description @@ -706,7 +793,7 @@ module clixon-config { user (eg datastores). It also sets the backend unix socket owner to this user, but its group is set by CLICON_SOCK_GROUP. - See also CLICON_PRIVILEGES setting"; + See also CLICON_BACKEND_PRIVILEGES setting"; } leaf CLICON_BACKEND_PRIVILEGES { type priv_mode; diff --git a/yang/clixon/clixon-lib@2020-12-30.yang b/yang/clixon/clixon-lib@2020-12-30.yang deleted file mode 100644 index c3780d1e..00000000 --- a/yang/clixon/clixon-lib@2020-12-30.yang +++ /dev/null @@ -1,180 +0,0 @@ -module clixon-lib { - yang-version 1.1; - namespace "http://clicon.org/lib"; - prefix cl; - - organization - "Clicon / Clixon"; - - contact - "Olof Hagsand "; - - description - "Clixon Netconf extensions for communication between clients and backend. - - ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2019 Olof Hagsand - 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 2020-12-30 { - description - "Changed: RPC process-control output parameter status to pid"; - } - revision 2020-12-08 { - description - "Added: autocli-op extension. - rpc process-control for process/daemon management - Released in clixon 4.9"; - } - revision 2020-04-23 { - description - "Added: stats RPC for clixon XML and memory statistics. - Added: restart-plugin RPC for restarting individual plugins without restarting backend."; - } - revision 2019-08-13 { - description - "No changes (reverted change)"; - } - revision 2019-06-05 { - description - "ping rpc added for liveness"; - } - revision 2019-01-02 { - description - "Released in Clixon 3.9"; - } - typedef service-operation { - type enumeration { - enum start { - description - "Start if not already running"; - } - enum stop { - description - "Stop if running"; - } - enum restart { - description - "Stop if running, then start"; - } - enum status { - description - "Check status"; - } - } - description - "Common operations that can be performed on a service"; - } - extension autocli-op { - description - "Takes an argument an operation defing how to modify the clispec at - this point in the YANG tree for the automated generated CLI. - Note that this extension is only used in clixon_cli. - Operations is expected to be extended, but the following operations are defined: - - hide This command is active but not shown by ? or TAB"; - argument cliop; - } - rpc debug { - description "Set debug level of backend."; - input { - leaf level { - type uint32; - } - } - } - rpc ping { - description "Check aliveness of backend daemon."; - } - rpc stats { - description "Clixon XML statistics."; - output { - container global{ - description "Clixon global statistics"; - leaf xmlnr{ - description "Number of XML objects: number of residing xml/json objects - in the internal 'cxobj' representation."; - type uint64; - } - } - list datastore{ - description "Datastore statistics"; - key "name"; - leaf name{ - description "name of datastore (eg running)."; - type string; - } - leaf nr{ - description "Number of XML objects. That is number of residing xml/json objects - in the internal 'cxobj' representation."; - type uint64; - } - leaf size{ - description "Size in bytes of internal datastore cache of datastore tree."; - type uint64; - } - } - - } - } - rpc restart-plugin { - description "Restart specific backend plugins."; - input { - leaf-list plugin { - description "Name of plugin to restart"; - type string; - } - } - } - - rpc process-control { - description - "Control a specific process or daemon: start/stop, etc. - This is for direct managing of a process by the backend. - Alternatively one can manage a daemon via systemd, containerd, kubernetes, etc."; - input { - leaf name { - description "Name of process"; - type string; - mandatory true; - } - leaf operation { - type service-operation; - mandatory true; - description - "One of the strings 'start', 'stop', 'restart', or 'status'."; - } - } - output { - leaf pid { - description "Process-id of running process or 0 if not running - Value is only valid for operation status"; - type uint32; - } - } - } -} diff --git a/yang/clixon/clixon-restconf@2021-03-15.yang b/yang/clixon/clixon-restconf@2021-03-15.yang deleted file mode 100644 index 7180054a..00000000 --- a/yang/clixon/clixon-restconf@2021-03-15.yang +++ /dev/null @@ -1,221 +0,0 @@ -module clixon-restconf { - yang-version 1.1; - namespace "http://clicon.org/restconf"; - prefix "clrc"; - - import ietf-inet-types { - prefix inet; - } - - organization - "Clixon"; - - contact - "Olof Hagsand "; - - description - "This YANG module provides a data-model for the Clixon RESTCONF daemon. - ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2020 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 2021-03-15 { - description - "make authentication-type none a feature - Added flag to enable core dumps"; - } - revision 2020-12-30 { - description - "Added: debug field - Added 'none' as default value for auth-type - Changed http-auth-type enum from 'password' to 'user'"; - } - revision 2020-10-30 { - description - "Initial release"; - } - - feature fcgi { - description - "This feature indicates that the restconf server supports the fast-cgi reverse - proxy solution. - That is, a reverse proxy is the HTTP front-end and the restconf daemon listens - to a fcgi socket. - The alternative is the internal HTTP solution using evhtp."; - } - - feature allow-auth-none { - description - "This feature allows the use of authentication-type none."; - } - - typedef http-auth-type { - type enumeration { - enum none { - if-feature "allow-auth-none"; - description - "Incoming message are set to authenticated by default. No ca-auth callback is called, - Authenticated user is set to special user 'none'. - Typically assumes NACM is not enabled."; - } - enum client-certificate { - description - "TLS client certificate validation is made on each incoming message. If it passes - the authenticated user is extracted from the SSL_CN parameter - The ca-auth callback can be used to revise this behavior."; - } - enum user { - description - "User-defined authentication as defined by the ca-auth callback. - One example is some form of password authentication, such as basic auth."; - } - } - description - "Enumeration of HTTP authorization types."; - } - grouping clixon-restconf{ - description - "HTTP RESTCONF configuration."; - leaf enable { - type boolean; - default "false"; - description - "Enables RESTCONF functionality. - Note that starting/stopping of a restconf daemon is different from it being - enabled or not. - For example, if the restconf daemon is under systemd management, the restconf - daemon will only start if enable=true."; - } - leaf auth-type { - type http-auth-type; - description - "The authentication type. - Note client-certificate applies only if ssl-enable is true and socket has ssl"; - default user; - } - leaf debug { - description - "Set debug level of restconf daemon. - 0 is no debug, 1 is debugging, more is detailed debug. - Debug logs will be directed to syslog with - ident: clixon_restconf and PID - facility: LOG_USER - level: LOG_DEBUG"; - type uint32; - default 0; - } - leaf enable-core-dump { - description - "enable core dumps. - this is a no-op on systems that don't support it."; - type boolean; - default false; - } - leaf pretty { - type boolean; - default true; - description - "Restconf return value pretty print. - Restconf clients may add HTTP header: - Accept: application/yang-data+json, or - Accept: application/yang-data+xml - to get return value in XML or JSON. - RFC 8040 examples print XML and JSON in pretty-printed form. - Setting this value to false makes restconf return not pretty-printed - which may be desirable for performance or tests - This replaces the CLICON_RESTCONF_PRETTY option in clixon-config.yang"; - } - /* From this point only specific options - * First fcgi-specific options - */ - leaf fcgi-socket { - if-feature fcgi; /* Set by default by fcgi clixon_restconf daemon */ - type string; - default "/www-data/fastcgi_restconf.sock"; - description - "Path to FastCGI unix socket. Should be specified in webserver - Eg in nginx: fastcgi_pass unix:/www-data/clicon_restconf.sock - Only if with-restconf=fcgi, NOT evhtp - This replaces CLICON_RESTCONF_PATH option in clixon-config.yang"; - } - /* Second, evhtp-specific options */ - leaf server-cert-path { - type string; - description - "Path to server certificate file. - Note only applies if socket has ssl enabled"; - } - leaf server-key-path { - type string; - description - "Path to server key file - Note only applies if socket has ssl enabled"; - } - leaf server-ca-cert-path { - type string; - description - "Path to server CA cert file - Note only applies if socket has ssl enabled"; - } - list socket { - description - "List of server sockets that the restconf daemon listens to"; - key "namespace address port"; - leaf namespace { - type string; - description - "Network namespace. - On platforms where namespaces are not suppported, 'default' - Default value can be changed by RESTCONF_NETNS_DEFAULT"; - } - leaf address { - type inet:ip-address; - description "IP address to bind to"; - } - leaf port { - type inet:port-number; - description "TCP port to bind to"; - } - leaf ssl { - type boolean; - default true; - description "Enable for HTTPS otherwise HTTP protocol"; - } - } - } - container restconf { - description - "This presence is strictly not necessary since the enable flag - in clixon-restconf is the flag bearing the actual semantics. - However, removing the presence leads to default config in all - clixon installations, even those which do not use backend-started restconf. - One could see this as mostly cosmetically annoying. - Alternative would be to make the inclusion of this yang conditional."; - presence "Enables RESTCONF"; - uses clixon-restconf; - } -} From b1e5e8548a27072311e63538c6d28d00745a2517 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 20 Jul 2021 15:54:42 +0200 Subject: [PATCH 28/34] added ietf-yang-patch.yang and enabled clixon_util_validate --- util/Makefile.in | 2 +- .../mandatory/ietf-yang-patch@2017-02-22.yang | 390 ++++++++++++++++++ 2 files changed, 391 insertions(+), 1 deletion(-) create mode 100644 yang/mandatory/ietf-yang-patch@2017-02-22.yang diff --git a/util/Makefile.in b/util/Makefile.in index f857033e..5737529b 100644 --- a/util/Makefile.in +++ b/util/Makefile.in @@ -91,7 +91,7 @@ APPSRC += clixon_util_path.c APPSRC += clixon_util_datastore.c APPSRC += clixon_util_regexp.c APPSRC += clixon_util_socket.c -# APPSRC += clixon_util_validate.c +APPSRC += clixon_util_validate.c APPSRC += clixon_netconf_ssh_callhome.c APPSRC += clixon_netconf_ssh_callhome_client.c ifdef with_restconf diff --git a/yang/mandatory/ietf-yang-patch@2017-02-22.yang b/yang/mandatory/ietf-yang-patch@2017-02-22.yang new file mode 100644 index 00000000..d0029ed2 --- /dev/null +++ b/yang/mandatory/ietf-yang-patch@2017-02-22.yang @@ -0,0 +1,390 @@ +module ietf-yang-patch { + yang-version 1.1; + namespace "urn:ietf:params:xml:ns:yang:ietf-yang-patch"; + prefix "ypatch"; + + import ietf-restconf { prefix rc; } + + organization + "IETF NETCONF (Network Configuration) Working Group"; + + contact + "WG Web: + WG List: + + Author: Andy Bierman + + + Author: Martin Bjorklund + + + Author: Kent Watsen + "; + + description + "This module contains conceptual YANG specifications + for the YANG Patch and YANG Patch Status data structures. + + Note that the YANG definitions within this module do not + represent configuration data of any kind. + The YANG grouping statements provide a normative syntax + for XML and JSON message-encoding purposes. + + Copyright (c) 2017 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 8072; see + the RFC itself for full legal notices."; + + revision 2017-02-22 { + description + "Initial revision."; + reference + "RFC 8072: YANG Patch Media Type."; + } + + typedef target-resource-offset { + type string; + description + "Contains a data resource identifier string representing + a sub-resource within the target resource. + The document root for this expression is the + target resource that is specified in the + protocol operation (e.g., the URI for the PATCH request). + + This string is encoded according to the same rules as those + for a data resource identifier in a RESTCONF request URI."; + reference + "RFC 8040, Section 3.5.3."; + } + + rc:yang-data "yang-patch" { + uses yang-patch; + } + + rc:yang-data "yang-patch-status" { + uses yang-patch-status; + } + + grouping yang-patch { + + description + "A grouping that contains a YANG container representing the + syntax and semantics of a YANG Patch edit request message."; + + container yang-patch { + description + "Represents a conceptual sequence of datastore edits, + called a patch. Each patch is given a client-assigned + patch identifier. Each edit MUST be applied + in ascending order, and all edits MUST be applied. + If any errors occur, then the target datastore MUST NOT + be changed by the YANG Patch operation. + + It is possible for a datastore constraint violation to occur + due to any node in the datastore, including nodes not + included in the 'edit' list. Any validation errors MUST + be reported in the reply message."; + + reference + "RFC 7950, Section 8.3."; + + leaf patch-id { + type string; + mandatory true; + description + "An arbitrary string provided by the client to identify + the entire patch. Error messages returned by the server + that pertain to this patch will be identified by this + 'patch-id' value. A client SHOULD attempt to generate + unique 'patch-id' values to distinguish between + transactions from multiple clients in any audit logs + maintained by the server."; + } + + leaf comment { + type string; + description + "An arbitrary string provided by the client to describe + the entire patch. This value SHOULD be present in any + audit logging records generated by the server for the + patch."; + } + + list edit { + key edit-id; + ordered-by user; + + description + "Represents one edit within the YANG Patch request message. + The 'edit' list is applied in the following manner: + + - The first edit is conceptually applied to a copy + of the existing target datastore, e.g., the + running configuration datastore. + - Each ascending edit is conceptually applied to + the result of the previous edit(s). + - After all edits have been successfully processed, + the result is validated according to YANG constraints. + - If successful, the server will attempt to apply + the result to the target datastore."; + + leaf edit-id { + type string; + description + "Arbitrary string index for the edit. + Error messages returned by the server that pertain + to a specific edit will be identified by this value."; + } + + leaf operation { + type enumeration { + enum create { + description + "The target data node is created using the supplied + value, only if it does not already exist. The + 'target' leaf identifies the data node to be + created, not the parent data node."; + } + enum delete { + description + "Delete the target node, only if the data resource + currently exists; otherwise, return an error."; + } + + enum insert { + description + "Insert the supplied value into a user-ordered + list or leaf-list entry. The target node must + represent a new data resource. If the 'where' + parameter is set to 'before' or 'after', then + the 'point' parameter identifies the insertion + point for the target node."; + } + enum merge { + description + "The supplied value is merged with the target data + node."; + } + enum move { + description + "Move the target node. Reorder a user-ordered + list or leaf-list. The target node must represent + an existing data resource. If the 'where' parameter + is set to 'before' or 'after', then the 'point' + parameter identifies the insertion point to move + the target node."; + } + enum replace { + description + "The supplied value is used to replace the target + data node."; + } + enum remove { + description + "Delete the target node if it currently exists."; + } + } + mandatory true; + description + "The datastore operation requested for the associated + 'edit' entry."; + } + + leaf target { + type target-resource-offset; + mandatory true; + description + "Identifies the target data node for the edit + operation. If the target has the value '/', then + the target data node is the target resource. + The target node MUST identify a data resource, + not the datastore resource."; + } + + leaf point { + when "(../operation = 'insert' or ../operation = 'move')" + + "and (../where = 'before' or ../where = 'after')" { + description + "This leaf only applies for 'insert' or 'move' + operations, before or after an existing entry."; + } + type target-resource-offset; + description + "The absolute URL path for the data node that is being + used as the insertion point or move point for the + target of this 'edit' entry."; + } + + leaf where { + when "../operation = 'insert' or ../operation = 'move'" { + description + "This leaf only applies for 'insert' or 'move' + operations."; + } + type enumeration { + enum before { + description + "Insert or move a data node before the data resource + identified by the 'point' parameter."; + } + enum after { + description + "Insert or move a data node after the data resource + identified by the 'point' parameter."; + } + + enum first { + description + "Insert or move a data node so it becomes ordered + as the first entry."; + } + enum last { + description + "Insert or move a data node so it becomes ordered + as the last entry."; + } + } + default last; + description + "Identifies where a data resource will be inserted + or moved. YANG only allows these operations for + list and leaf-list data nodes that are + 'ordered-by user'."; + } + + anydata value { + when "../operation = 'create' " + + "or ../operation = 'merge' " + + "or ../operation = 'replace' " + + "or ../operation = 'insert'" { + description + "The anydata 'value' is only used for 'create', + 'merge', 'replace', and 'insert' operations."; + } + description + "Value used for this edit operation. The anydata 'value' + contains the target resource associated with the + 'target' leaf. + + For example, suppose the target node is a YANG container + named foo: + + container foo { + leaf a { type string; } + leaf b { type int32; } + } + + The 'value' node contains one instance of foo: + + + + some value + 42 + + + "; + } + } + } + + } // grouping yang-patch + + grouping yang-patch-status { + + description + "A grouping that contains a YANG container representing the + syntax and semantics of a YANG Patch Status response + message."; + + container yang-patch-status { + description + "A container representing the response message sent by the + server after a YANG Patch edit request message has been + processed."; + + leaf patch-id { + type string; + mandatory true; + description + "The 'patch-id' value used in the request."; + } + + choice global-status { + description + "Report global errors or complete success. + If there is no case selected, then errors + are reported in the 'edit-status' container."; + + case global-errors { + uses rc:errors; + description + "This container will be present if global errors that + are unrelated to a specific edit occurred."; + } + leaf ok { + type empty; + description + "This leaf will be present if the request succeeded + and there are no errors reported in the 'edit-status' + container."; + } + } + + container edit-status { + description + "This container will be present if there are + edit-specific status responses to report. + If all edits succeeded and the 'global-status' + returned is 'ok', then a server MAY omit this + container."; + + list edit { + key edit-id; + + description + "Represents a list of status responses, + corresponding to edits in the YANG Patch + request message. If an 'edit' entry was + skipped or not reached by the server, + then this list will not contain a corresponding + entry for that edit."; + + leaf edit-id { + type string; + description + "Response status is for the 'edit' list entry + with this 'edit-id' value."; + } + + choice edit-status-choice { + description + "A choice between different types of status + responses for each 'edit' entry."; + leaf ok { + type empty; + description + "This 'edit' entry was invoked without any + errors detected by the server associated + with this edit."; + } + case errors { + uses rc:errors; + description + "The server detected errors associated with the + edit identified by the same 'edit-id' value."; + } + } + } + } + } + } // grouping yang-patch-status + +} From 91313b31e0de4b0fb2476945f1f988f6fe67b597 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sat, 24 Jul 2021 16:43:47 +0200 Subject: [PATCH 29/34] Removed space from xpath canonical form of relex/unionex --- lib/src/clixon_xpath.c | 7 +++++-- test/test_xpath_canonical.sh | 6 +++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/src/clixon_xpath.c b/lib/src/clixon_xpath.c index 64352055..43845787 100644 --- a/lib/src/clixon_xpath.c +++ b/lib/src/clixon_xpath.c @@ -290,11 +290,14 @@ xpath_tree2cbuf(xpath_tree *xs, switch (xs->xs_type){ case XP_AND: /* and or */ case XP_ADD: /* div mod + * - */ - case XP_RELEX: /* !=, >= <= < > = */ - case XP_UNION: if (xs->xs_c1) cprintf(xcb, " %s ", clicon_int2str(xpopmap, xs->xs_int)); break; + case XP_RELEX: /* !=, >= <= < > = */ + case XP_UNION: /* | */ + if (xs->xs_c1) + cprintf(xcb, "%s", clicon_int2str(xpopmap, xs->xs_int)); + break; case XP_PATHEXPR: /* [19] PathExpr ::= | FilterExpr '/' RelativeLocationPath | FilterExpr '//' RelativeLocationPath diff --git a/test/test_xpath_canonical.sh b/test/test_xpath_canonical.sh index e541234b..d339d31c 100755 --- a/test/test_xpath_canonical.sh +++ b/test/test_xpath_canonical.sh @@ -48,13 +48,13 @@ new "xpath canonical form (other)" expectpart "$($clixon_util_xpath -c -y $ydir -p /i:x/j:y -n i:urn:example:a -n j:urn:example:b)" 0 '/a:x/b:y' '0 : a = "urn:example:a"' '1 : b = "urn:example:b"' new "xpath canonical form predicate 1" -expectpart "$($clixon_util_xpath -c -y $ydir -p "/i:x[j:y='e1']" -n i:urn:example:a -n j:urn:example:b)" 0 "/a:x\[b:y = 'e1'\]" '0 : a = "urn:example:a"' '1 : b = "urn:example:b"' +expectpart "$($clixon_util_xpath -c -y $ydir -p "/i:x[j:y='e1']" -n i:urn:example:a -n j:urn:example:b)" 0 "/a:x\[b:y='e1'\]" '0 : a = "urn:example:a"' '1 : b = "urn:example:b"' new "xpath canonical form predicate self" -expectpart "$($clixon_util_xpath -c -y $ydir -p "/i:x[.='42']" -n i:urn:example:a -n j:urn:example:b)" 0 "/a:x\[. = '42'\]" '0 : a = "urn:example:a"' +expectpart "$($clixon_util_xpath -c -y $ydir -p "/i:x[.='42']" -n i:urn:example:a -n j:urn:example:b)" 0 "/a:x\[.='42'\]" '0 : a = "urn:example:a"' new "xpath canonical form descendants" -expectpart "$($clixon_util_xpath -c -y $ydir -p "//x[.='42']" -n null:urn:example:a -n j:urn:example:b)" 0 "//a:x\[. = '42'\]" '0 : a = "urn:example:a"' +expectpart "$($clixon_util_xpath -c -y $ydir -p "//x[.='42']" -n null:urn:example:a -n j:urn:example:b)" 0 "//a:x\[.='42'\]" '0 : a = "urn:example:a"' new "xpath canonical form (no default should fail)" expectpart "$($clixon_util_xpath -c -y $ydir -p /x/j:y -n i:urn:example:a -n j:urn:example:b 2> /dev/null)" 255 From c1c4e5b3f3a1059f3cf25f8fa56e8da3a3857327 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 25 Jul 2021 22:21:37 +0200 Subject: [PATCH 30/34] - cli set debug vars - fixed: restconf native evhtp appended indata to old data --- apps/cli/cli_common.c | 6 +++--- apps/restconf/restconf_evhtp.c | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index 2a3c74a9..4f1c6fdb 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -448,7 +448,7 @@ cli_debug_cli(clicon_handle h, cg_var *cv; int level; - if ((cv = cvec_find(vars, "level")) == NULL){ + if ((cv = cvec_find_var(vars, "level")) == NULL){ if (cvec_len(argv) != 1){ clicon_err(OE_PLUGIN, EINVAL, "Requires either label var or single arg: 0|1"); goto done; @@ -479,7 +479,7 @@ cli_debug_backend(clicon_handle h, cg_var *cv; int level; - if ((cv = cvec_find(vars, "level")) == NULL){ + if ((cv = cvec_find_var(vars, "level")) == NULL){ if (cvec_len(argv) != 1){ clicon_err(OE_PLUGIN, EINVAL, "Requires either label var or single arg: 0|1"); goto done; @@ -513,7 +513,7 @@ cli_debug_restconf(clicon_handle h, cg_var *cv; int level; - if ((cv = cvec_find(vars, "level")) == NULL){ + if ((cv = cvec_find_var(vars, "level")) == NULL){ if (cvec_len(argv) != 1){ clicon_err(OE_PLUGIN, EINVAL, "Requires either label var or single arg: 0|1"); goto done; diff --git a/apps/restconf/restconf_evhtp.c b/apps/restconf/restconf_evhtp.c index dfb43597..44983de8 100644 --- a/apps/restconf/restconf_evhtp.c +++ b/apps/restconf/restconf_evhtp.c @@ -529,6 +529,7 @@ restconf_path_root(evhtp_request_t *req, clicon_err(OE_CFG, errno, "evbuffer_pullup"); goto done; } + cbuf_reset(sd->sd_indata); /* Note the pullup may not be null-terminated */ cbuf_append_buf(sd->sd_indata, buf, len); } From 600f29a11771d2c211b284d90b77a1a19d385ea6 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 26 Jul 2021 12:20:06 +0200 Subject: [PATCH 31/34] - Added yang patch test: test_restconf_yang_patch.sh as placeholder for rfc 8072 tests - Added patch-xml as valid media --- apps/restconf/restconf_methods.c | 7 +- test/test_restconf_yang_patch.sh | 205 +++++++++++++++++++++++++++++++ 2 files changed, 208 insertions(+), 4 deletions(-) create mode 100755 test/test_restconf_yang_patch.sh diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 3d9630bb..355f77e3 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -1203,16 +1203,15 @@ api_data_patch(clicon_handle h, ret = api_data_write(h, req, api_path0, pcvec, pi, qvec, data, pretty, media_in, media_out, 1, ds); break; - case YANG_PATCH_XML: - ret = restconf_notimplemented(h, req, pretty, media_out); - break; case YANG_PATCH_JSON: /* RFC 8072 patch */ + case YANG_PATCH_XML: #ifdef YANG_PATCH ret = api_data_yang_patch(h, req, api_path0, pcvec, pi, qvec, data, pretty, media_out, ds); #else - ret = restconf_unsupported_media(h, req, pretty, media_out); + ret = restconf_notimplemented(h, req, pretty, media_out); #endif + break; break; default: ret = restconf_unsupported_media(h, req, pretty, media_out); diff --git a/test/test_restconf_yang_patch.sh b/test/test_restconf_yang_patch.sh new file mode 100755 index 00000000..6d91704a --- /dev/null +++ b/test/test_restconf_yang_patch.sh @@ -0,0 +1,205 @@ +#!/usr/bin/env bash +# Restconf RFC8072 yang patch +# XXX enable YANG_PACTH in include/clixon_custom.h to run this test +# Use nacm module in example/main/example_restconf.c hardcoded to +# andy:bar and wilma:bar + +# Magic line must be first in script (see README.md) +s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi + +echo "...skipped: YANG_PATCH NYI" +if [ "$s" = $0 ]; then exit 0; else return 0; fi + +APPNAME=example + +cfg=$dir/conf.xml +startupdb=$dir/startup_db +fjukebox=$dir/example-jukebox.yang + +# Define default restconfig config: RESTCONFIG +RESTCONFIG=$(restconf_config user false) + +cat < $cfg + + $cfg + /usr/local/share/clixon + $IETFRFC + $dir + /usr/local/var/$APPNAME/$APPNAME.sock + ietf-netconf:startup + /usr/local/lib/$APPNAME/restconf + $dir/restconf.pidfile + $dir + internal + true + $RESTCONFIG + +EOF + +NACM0=" + true + deny + deny + permit + + + admin + andy + + + limited + wilma + + + + admin + admin + + permit-all + * + * + permit + + Allow the 'admin' group complete access to all operations and data. + + + + + limited + limited + + limit-jukebox + jukebox-example + read create delete + deny + + + +" + +cat< $startupdb +<${DATASTORE_TOP}> + $NACM0 + +EOF + +# An extra testmodule that includes nacm +cat < $dir/example-system.yang + module example-system { + namespace "http://example.com/ns/example-system"; + prefix "ex"; + import ietf-netconf-acm { + prefix nacm; + } + container system { + leaf enable-jukebox-streaming { + type boolean; + } + leaf extraleaf { + type string; + } + } + } +EOF + +# Common Jukebox spec (fjukebox must be set) +. ./jukebox.sh + +new "test params: -s startup -f $cfg" +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + sudo pkill -f clixon_backend # to be sure + + new "start backend -s startup -f $cfg" + start_backend -s startup -f $cfg +fi + +new "wait backend" +wait_backend + +if [ $RC -ne 0 ]; then + new "kill old restconf daemon" + stop_restconf_pre + + new "start restconf daemon" + start_restconf -f $cfg +fi + +new "wait restconf" +wait_restconf + +# RFC 8072 A.1.1 +REQ=' + add-songs-patch + + edit1 + create + /song=Bridge%20Burning + + + Bridge Burning + /media/bridge_burning.mp3 + MP3 + 288 + + + + + edit2 + create + /song=Rope + + + Rope + /media/rope.mp3 + MP3 + 259 + + + + + edit3 + create + /song=Dear%20Rosemary + + + Dear Rosemary + /media/dear_rosemary.mp3 + MP3 + 269 + + + + ' + +new "RFC 8072 A.1.1 Add resources: Error." +expectpart "$(curl -u andy:bar $CURLOPTS -X PATCH -H 'Content-Type: application/yang-patch+xml' -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light -d "$REQ")" 0 "HTTP/$HVER 409" + + +if [ $RC -ne 0 ]; then + new "Kill restconf daemon" + stop_restconf +fi + +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 + +# Set by restconf_config +unset RESTCONFIG + +rm -rf $dir + +new "endtest" +endtest From dee081646cbbf08419c2778f8fa01f0a1fb5a46d Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 27 Jul 2021 09:39:44 +0200 Subject: [PATCH 32/34] * Fixed: SEGV in clixon_netconf_lib functions from internal errors including validation. * Check xerr argument both before and after call on netconf lib functions --- CHANGELOG.md | 2 ++ apps/backend/backend_client.c | 2 +- apps/backend/backend_commit.c | 33 ++++++++++++++++------ lib/clixon/clixon_netconf_lib.h | 3 +- lib/src/clixon_datastore_read.c | 2 +- lib/src/clixon_json.c | 2 +- lib/src/clixon_netconf_lib.c | 50 ++++++++++++++++++++++++++++++++- lib/src/clixon_validate.c | 46 +++++++++++++++--------------- lib/src/clixon_yang_module.c | 2 +- 9 files changed, 103 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61d19acf..a922c444 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,8 @@ Developers may need to change their code ### Corrected Bugs +* Fixed: SEGV in clixon_netconf_lib functions from internal errors including validation. + * Check xerr argument both before and after call on netconf lib functions * Fixed: RFC 8040 yang-data extension allows non-key lists * Added YANG_FLAG_NOKEY as exception to mandatory key lists * Fixed: mandatory leaf in a uses statement caused abort diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 6244d200..be7b9377 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -238,7 +238,7 @@ client_get_streams(clicon_handle h, cprintf(cb,"", top); if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, &x, NULL) < 0){ - if (netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0) + if (xret && netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0) goto done; goto fail; } diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index 03e318cf..8abcbcbc 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -132,7 +132,7 @@ generic_validate(clicon_handle h, cprintf(cb, "Mandatory variable of %s in module %s", xml_parent(x1)?xml_name(xml_parent(x1)):"", yang_argument_get(ys_module(ys))); - if (netconf_missing_element_xml(xret, "protocol", xml_name(x1), cbuf_get(cb)) < 0) + if (xret && netconf_missing_element_xml(xret, "protocol", xml_name(x1), cbuf_get(cb)) < 0) goto done; goto fail; } @@ -480,9 +480,9 @@ startup_commit(clicon_handle h, * and call application callback validations. * @param[in] h Clicon handle * @param[in] candidate The candidate database. The wanted backend state - * @param[out] xret Error XML tree. Free with xml_free after use + * @param[out] xret Error XML tree, if retval is 0. Free with xml_free after use * @retval -1 Error - or validation failed (but cbret not set) - * @retval 0 Validation failed (with cbret set) + * @retval 0 Validation failed (with xret set) * @retval 1 Validation OK * @note Need to differentiate between error and validation fail * (only done for generic_validate) @@ -505,16 +505,19 @@ validate_common(clicon_handle h, goto done; } /* This is the state we are going to */ - if (xmldb_get0(h, db, YB_MODULE, NULL, "/", 0, &td->td_target, NULL, NULL) < 0) + if ((ret = xmldb_get0(h, db, YB_MODULE, NULL, "/", 0, &td->td_target, NULL, xret)) < 0) goto done; - + if (ret == 0) + goto fail; /* Clear flags xpath for get */ xml_apply0(td->td_target, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)(XML_FLAG_MARK|XML_FLAG_CHANGE)); /* 2. Parse xml trees * This is the state we are going from */ - if (xmldb_get0(h, "running", YB_MODULE, NULL, "/", 0, &td->td_src, NULL, NULL) < 0) + if ((ret = xmldb_get0(h, "running", YB_MODULE, NULL, "/", 0, &td->td_src, NULL, xret)) < 0) goto done; + if (ret == 0) + goto fail; /* Clear flags xpath for get */ xml_apply0(td->td_src, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)(XML_FLAG_MARK|XML_FLAG_CHANGE)); @@ -606,11 +609,23 @@ candidate_validate(clicon_handle h, if ((td = transaction_new()) == NULL) goto done; /* Common steps (with commit) */ - if ((ret = validate_common(h, db, td, &xret)) < 1){ + if ((ret = validate_common(h, db, td, &xret)) < 0){ /* A little complex due to several sources of validation fails or errors. * (1) xerr is set -> translate to cbret; (2) cbret set use that; otherwise - * use clicon_err. */ - if (xret && clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 0) + * use clicon_err. + * TODO: -1 return should be fatal error, not failed validation + */ + if (!cbuf_len(cbret) && + netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) + goto done; + goto fail; + } + if (ret == 0){ + if (xret == NULL){ + clicon_err(OE_CFG, EINVAL, "xret is NULL"); + goto done; + } + if (clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 0) goto done; if (!cbuf_len(cbret) && netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) diff --git a/lib/clixon/clixon_netconf_lib.h b/lib/clixon/clixon_netconf_lib.h index f42cf33d..40b9bc96 100644 --- a/lib/clixon/clixon_netconf_lib.h +++ b/lib/clixon/clixon_netconf_lib.h @@ -127,8 +127,7 @@ int netconf_operation_failed(cbuf *cb, char *type, char *message); int netconf_operation_failed_xml(cxobj **xret, char *type, char *message); int netconf_malformed_message(cbuf *cb, char *message); int netconf_malformed_message_xml(cxobj **xret, char *message); -int netconf_data_not_unique_xml(cxobj **xret, cxobj *x, cvec *cvk); - +int netconf_data_not_unique_xml(cxobj **xret, cxobj *x, cvec *cvk); int netconf_minmax_elements_xml(cxobj **xret, cxobj *xp, char *name, int max); int netconf_trymerge(cxobj *x, yang_stmt *yspec, cxobj **xret); int netconf_module_features(clicon_handle h); diff --git a/lib/src/clixon_datastore_read.c b/lib/src/clixon_datastore_read.c index 0413d2c6..c4cf0293 100644 --- a/lib/src/clixon_datastore_read.c +++ b/lib/src/clixon_datastore_read.c @@ -557,7 +557,7 @@ xmldb_readfile(clicon_handle h, } cprintf(cberr, "Internal error: %s", clicon_err_reason); clicon_err_reset(); - if (netconf_operation_failed_xml(xerr, "application", cbuf_get(cberr))< 0) + if (xerr && netconf_operation_failed_xml(xerr, "application", cbuf_get(cberr))< 0) goto done; cbuf_free(cberr); goto fail; diff --git a/lib/src/clixon_json.c b/lib/src/clixon_json.c index b27733b4..caf79351 100644 --- a/lib/src/clixon_json.c +++ b/lib/src/clixon_json.c @@ -1238,7 +1238,7 @@ _json_parse(char *str, goto done; } cprintf(cberr, "Top-level JSON object %s is not qualified with namespace which is a MUST according to RFC 7951", xml_name(x)); - if (netconf_malformed_message_xml(xerr, cbuf_get(cberr)) < 0) + if (xerr && netconf_malformed_message_xml(xerr, cbuf_get(cberr)) < 0) goto done; goto fail; } diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c index febf5b93..8dadfbd5 100644 --- a/lib/src/clixon_netconf_lib.c +++ b/lib/src/clixon_netconf_lib.c @@ -131,6 +131,10 @@ netconf_invalid_value_xml(cxobj **xret, char *encstr = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -248,6 +252,10 @@ netconf_missing_attribute_xml(cxobj **xret, char *encstr = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -356,6 +364,10 @@ netconf_bad_attribute_xml(cxobj **xret, char *encstr = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -449,6 +461,10 @@ netconf_common_xml(cxobj **xret, char *encstr = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -705,6 +721,10 @@ netconf_access_denied_xml(cxobj **xret, char *encstr = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -941,6 +961,10 @@ netconf_data_missing_xml(cxobj **xret, cxobj *xerr; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -1005,6 +1029,10 @@ netconf_operation_not_supported_xml(cxobj **xret, char *encstr = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -1116,6 +1144,10 @@ netconf_operation_failed_xml(cxobj **xret, char *encstr = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -1200,6 +1232,10 @@ netconf_malformed_message_xml(cxobj **xret, char *encstr = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -1250,8 +1286,12 @@ netconf_data_not_unique_xml(cxobj **xret, cxobj *xerr; cxobj *xinfo; cbuf *cb = NULL; - cxobj *xa; + cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -1316,6 +1356,10 @@ netconf_minmax_elements_xml(cxobj **xret, cbuf *cb = NULL; cxobj *xa; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_new("rpc-reply", NULL, CX_ELMNT)) == NULL) goto done; @@ -1373,6 +1417,10 @@ netconf_trymerge(cxobj *x, char *reason = NULL; cxobj *xc; + if (xret == NULL){ + clicon_err(OE_NETCONF, EINVAL, "xret is NULL"); + goto done; + } if (*xret == NULL){ if ((*xret = xml_dup(x)) == NULL) goto done; diff --git a/lib/src/clixon_validate.c b/lib/src/clixon_validate.c index 274d339a..802330c8 100644 --- a/lib/src/clixon_validate.c +++ b/lib/src/clixon_validate.c @@ -118,7 +118,7 @@ validate_leafref(cxobj *xt, if ((leafrefbody = xml_body(xt)) == NULL) goto ok; if ((ypath = yang_find(ytype, Y_PATH, NULL)) == NULL){ - if (netconf_missing_element_xml(xret, "application", yang_argument_get(ytype), "Leafref requires path statement") < 0) + if (xret && netconf_missing_element_xml(xret, "application", yang_argument_get(ytype), "Leafref requires path statement") < 0) goto done; goto fail; } @@ -141,7 +141,7 @@ validate_leafref(cxobj *xt, goto done; } cprintf(cberr, "Leafref validation failed: No leaf %s matching path %s", leafrefbody, path); - if (netconf_bad_element_xml(xret, "application", leafrefbody, cbuf_get(cberr)) < 0) + if (xret && netconf_bad_element_xml(xret, "application", leafrefbody, cbuf_get(cberr)) < 0) goto done; goto fail; } @@ -211,7 +211,7 @@ validate_identityref(cxobj *xt, /* Get idref value. Then see if this value is derived from ytype. */ if ((node = xml_body(xt)) == NULL){ /* It may not be empty */ - if (netconf_bad_element_xml(xret, "application", xml_name(xt), "Identityref should not be empty") < 0) + if (xret && netconf_bad_element_xml(xret, "application", xml_name(xt), "Identityref should not be empty") < 0) goto done; goto fail; } @@ -219,13 +219,13 @@ validate_identityref(cxobj *xt, goto done; /* This is the type's base reference */ if ((ybaseref = yang_find(ytype, Y_BASE, NULL)) == NULL){ - if (netconf_missing_element_xml(xret, "application", yang_argument_get(ytype), "Identityref validation failed, no base") < 0) + if (xret && netconf_missing_element_xml(xret, "application", yang_argument_get(ytype), "Identityref validation failed, no base") < 0) goto done; goto fail; } /* This is the actual base identity */ if ((ybaseid = yang_find_identity(ybaseref, yang_argument_get(ybaseref))) == NULL){ - if (netconf_missing_element_xml(xret, "application", yang_argument_get(ybaseref), "Identityref validation failed, no base identity") < 0) + if (xret && netconf_missing_element_xml(xret, "application", yang_argument_get(ybaseref), "Identityref validation failed, no base identity") < 0) goto done; goto fail; } @@ -254,7 +254,7 @@ validate_identityref(cxobj *xt, if (cvec_find(idrefvec, idref) == NULL){ cprintf(cberr, "Identityref validation failed, %s not derived from %s", node, yang_argument_get(ybaseid)); - if (netconf_operation_failed_xml(xret, "application", cbuf_get(cberr)) < 0) + if (xret && netconf_operation_failed_xml(xret, "application", cbuf_get(cberr)) < 0) goto done; goto fail; } @@ -337,7 +337,7 @@ xml_yang_validate_rpc(clicon_handle h, goto done; /* Only accept resolved NETCONF base namespace */ if (namespace == NULL || strcmp(namespace, NETCONF_BASE_NAMESPACE) != 0){ - if (netconf_unknown_namespace_xml(xret, "protocol", rpcprefix, "No appropriate namespace associated with prefix")< 0) + if (xret && netconf_unknown_namespace_xml(xret, "protocol", rpcprefix, "No appropriate namespace associated with prefix")< 0) goto done; goto fail; } @@ -345,7 +345,7 @@ xml_yang_validate_rpc(clicon_handle h, /* xn is name of rpc, ie */ while ((xn = xml_child_each(xrpc, xn, CX_ELMNT)) != NULL) { if ((yn = xml_spec(xn)) == NULL){ - if (netconf_unknown_element_xml(xret, "application", xml_name(xn), NULL) < 0) + if (xret && netconf_unknown_element_xml(xret, "application", xml_name(xn), NULL) < 0) goto done; goto fail; } @@ -434,7 +434,7 @@ check_choice(cxobj *xt, continue; /* not choice */ break; } - if (netconf_bad_element_xml(xret, "application", xml_name(x), "Element in choice statement already exists") < 0) + if (xret && netconf_bad_element_xml(xret, "application", xml_name(x), "Element in choice statement already exists") < 0) goto done; goto fail; } /* while */ @@ -487,7 +487,7 @@ check_list_key(cxobj *xt, while ((cvi = cvec_each(cvk, cvi)) != NULL) { keyname = cv_string_get(cvi); if (xml_find_type(xt, NULL, keyname, CX_ELMNT) == NULL){ - if (netconf_missing_element_xml(xret, "application", keyname, "Mandatory key") < 0) + if (xret && netconf_missing_element_xml(xret, "application", keyname, "Mandatory key") < 0) goto done; goto fail; } @@ -557,7 +557,7 @@ check_mandatory(cxobj *xt, goto done; } cprintf(cb, "Mandatory variable of %s in module %s", xml_name(xt), yang_argument_get(ys_module(yc))); - if (netconf_missing_element_xml(xret, "application", yang_argument_get(yc), cbuf_get(cb)) < 0) + if (xret && netconf_missing_element_xml(xret, "application", yang_argument_get(yc), cbuf_get(cb)) < 0) goto done; goto fail; } @@ -574,7 +574,7 @@ check_mandatory(cxobj *xt, if (x == NULL){ /* @see RFC7950: 15.6 Error Message for Data That Violates * a Mandatory "choice" Statement */ - if (netconf_data_missing_xml(xret, yang_argument_get(yc), NULL) < 0) + if (xret && netconf_data_missing_xml(xret, yang_argument_get(yc), NULL) < 0) goto done; goto fail; } @@ -707,7 +707,7 @@ check_unique_list(cxobj *x, if (cvi==NULL){ /* Last element (i) is newly inserted, see if it is already there */ if (check_insert_duplicate(vec, i, vlen, sorted) < 0){ - if (netconf_data_not_unique_xml(xret, x, cvk) < 0) + if (xret && netconf_data_not_unique_xml(xret, x, cvk) < 0) goto done; goto fail; } @@ -751,7 +751,7 @@ check_min_max(cxobj *xp, if ((ymin = yang_find(y, Y_MIN_ELEMENTS, NULL)) != NULL){ cv = yang_cv_get(ymin); if (nr < cv_uint32_get(cv)){ - if (netconf_minmax_elements_xml(xret, xp, yang_argument_get(y), 0) < 0) + if (xret && netconf_minmax_elements_xml(xret, xp, yang_argument_get(y), 0) < 0) goto done; goto fail; } @@ -760,7 +760,7 @@ check_min_max(cxobj *xp, cv = yang_cv_get(ymax); if (cv_uint32_get(cv) > 0 && /* 0 means unbounded */ nr > cv_uint32_get(cv)){ - if (netconf_minmax_elements_xml(xret, xp, yang_argument_get(y), 1) < 0) + if (xret && netconf_minmax_elements_xml(xret, xp, yang_argument_get(y), 1) < 0) goto done; goto fail; } @@ -851,7 +851,7 @@ check_list_unique_minmax(cxobj *xt, /* Only lists and leaf-lists are allowed to be many * This checks duplicate container and leafs */ - if (netconf_minmax_elements_xml(xret, xt, xml_name(x), 1) < 0) + if (xret && netconf_minmax_elements_xml(xret, xt, xml_name(x), 1) < 0) goto done; goto fail; } @@ -1018,14 +1018,14 @@ xml_yang_validate_add(clicon_handle h, * are considered as "" */ cvtype = cv_type_get(cv); if (cv_isint(cvtype) || cvtype == CGV_BOOL || cvtype == CGV_DEC64){ - if (netconf_bad_element_xml(xret, "application", yang_argument_get(yt), "Invalid NULL value") < 0) + if (xret && netconf_bad_element_xml(xret, "application", yang_argument_get(yt), "Invalid NULL value") < 0) goto done; goto fail; } } else{ if (cv_parse1(body, cv, &reason) != 1){ - if (netconf_bad_element_xml(xret, "application", yang_argument_get(yt), reason) < 0) + if (xret && netconf_bad_element_xml(xret, "application", yang_argument_get(yt), reason) < 0) goto done; goto fail; } @@ -1033,7 +1033,7 @@ xml_yang_validate_add(clicon_handle h, if ((ret = ys_cv_validate(h, cv, yt, NULL, &reason)) < 0) goto done; if (ret == 0){ - if (netconf_bad_element_xml(xret, "application", yang_argument_get(yt), reason) < 0) + if (xret && netconf_bad_element_xml(xret, "application", yang_argument_get(yt), reason) < 0) goto done; goto fail; } @@ -1158,7 +1158,7 @@ xml_yang_validate_all(clicon_handle h, goto done; if (ns) cprintf(cb, " in namespace: %s", ns); - if (netconf_unknown_element_xml(xret, "application", xml_name(xt), cbuf_get(cb)) < 0) + if (xret && netconf_unknown_element_xml(xret, "application", xml_name(xt), cbuf_get(cb)) < 0) goto done; goto fail; } @@ -1217,7 +1217,7 @@ xml_yang_validate_all(clicon_handle h, } cprintf(cb, "Failed MUST xpath '%s' of '%s' in module %s", xpath, xml_name(xt), yang_argument_get(ys_module(ys))); - if (netconf_operation_failed_xml(xret, "application", + if (xret && netconf_operation_failed_xml(xret, "application", ye?yang_argument_get(ye):cbuf_get(cb)) < 0) goto done; goto fail; @@ -1247,7 +1247,7 @@ xml_yang_validate_all(clicon_handle h, cprintf(cb, "Failed WHEN condition of %s in module %s", xml_name(xt), yang_argument_get(ys_module(ys))); - if (netconf_operation_failed_xml(xret, "application", + if (xret && netconf_operation_failed_xml(xret, "application", cbuf_get(cb)) < 0) goto done; goto fail; @@ -1269,7 +1269,7 @@ xml_yang_validate_all(clicon_handle h, xpath, xml_name(xt), yang_argument_get(ys_module(ys))); - if (netconf_operation_failed_xml(xret, "application", + if (xret && netconf_operation_failed_xml(xret, "application", cbuf_get(cb)) < 0) goto done; goto fail; diff --git a/lib/src/clixon_yang_module.c b/lib/src/clixon_yang_module.c index 3d47f6f7..3718ceb8 100644 --- a/lib/src/clixon_yang_module.c +++ b/lib/src/clixon_yang_module.c @@ -329,7 +329,7 @@ yang_modules_state_get(clicon_handle h, * Note, list is not sorted since it is state (should not be) */ if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, &x, NULL) < 0){ - if (netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0) + if (xret && netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0) goto done; goto fail; } From 93a4777f0f98291d4b34dd35df36048cee3cc094 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 27 Jul 2021 10:53:47 +0200 Subject: [PATCH 33/34] Removed default of `CLICON_RESTCONF_INSTALLDIR` * The default behaviour is changed to use the config $(sbindir) to locate `clixon_restconf` when starting restconf internally --- CHANGELOG.md | 7 ++++-- apps/backend/Makefile.in | 4 +++- apps/backend/backend_plugin_restconf.c | 15 +++++++++---- test/test_restconf_internal.sh | 3 --- test/test_restconf_internal_usecases.sh | 3 --- yang/clixon/clixon-config@2021-07-11.yang | 26 ++++++++++++++++------- 6 files changed, 37 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a922c444..c3d768ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,12 +39,15 @@ Expected: September, 2021 * Experimental: enable by setting YANG_PATCH in include/clixon_custom.h * Thanks to Alan Yaniger for providing this patch -### C/CLI-API changes on existing features +### API changes on existing protocol/config features -Developers may need to change their code +Users may have to change how they access the system * Native Restconf is now default, not fcgi/nginx * That is, to configure with fcgi, you need to explicitly configure: `--with-restconf=fcgi` +* New clixon-config@2021-07-11.yang revision + * Removed default of `CLICON_RESTCONF_INSTALLDIR` + * The default behaviour is changed to use the config $(sbindir) to locate `clixon_restconf` when starting restconf internally ### Corrected Bugs diff --git a/apps/backend/Makefile.in b/apps/backend/Makefile.in index aecf95c7..4c55d23f 100644 --- a/apps/backend/Makefile.in +++ b/apps/backend/Makefile.in @@ -152,7 +152,9 @@ install-include: clixon_backend.h clixon_backend_handle.h clixon_backend_transac .SUFFIXES: .c .o .c.o: - $(CC) $(INCLUDES) $(CPPFLAGS) -D__PROGRAM__=\"$(APPL)\" $(CFLAGS) -c $< + # Note: CLIXON_CONFIG_SBINDIR is where clixon_restconf is believed to be installed, unless + # overruled by CLICON_RESTCONF_INSTALLDIR option + $(CC) $(INCLUDES) $(CPPFLAGS) -D__PROGRAM__=\"$(APPL)\" -DCLIXON_CONFIG_SBINDIR=\"$(sbindir)\" $(CFLAGS) -c $< # Just link test programs test.c : diff --git a/apps/backend/backend_plugin_restconf.c b/apps/backend/backend_plugin_restconf.c index 6b82417d..fe39c8c5 100644 --- a/apps/backend/backend_plugin_restconf.c +++ b/apps/backend/backend_plugin_restconf.c @@ -245,6 +245,7 @@ restconf_pseudo_process_control(clicon_handle h) int i; int nr; cbuf *cb = NULL; + char *dir = NULL; nr = 10; if ((argv = calloc(nr, sizeof(char *))) == NULL){ @@ -256,12 +257,18 @@ restconf_pseudo_process_control(clicon_handle h) clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; } - /* CLICON_RESTCONF_INSTALLDIR is where we think clixon_restconf is installed - * Problem is where to define it? Now in config file, but maybe it should be in configure? - * Tried Makefile but didnt work on Docker since it was moved around. + /* Try to figure out where clixon_restconf is installed + * If config option CLICON_RESTCONF_INSTALLDIR is installed, use that. + * If not, use the Makefile * Use PATH? */ - cprintf(cb, "%s/clixon_restconf", clicon_option_str(h, "CLICON_RESTCONF_INSTALLDIR")); + if ((dir = clicon_option_str(h, "CLICON_RESTCONF_INSTALLDIR")) == NULL){ + if ((dir = CLIXON_CONFIG_SBINDIR) == NULL){ + clicon_err(OE_RESTCONF, EINVAL, "Both option CLICON_RESTCONF_INSTALLDIR and makefile constant CLIXON_CONFIG_SBINDIR are NULL which make sit not possible to know where clixon_restconf is installed(shouldnt happen)"); + goto done; + } + } + cprintf(cb, "%s/clixon_restconf", dir); argv[i++] = cbuf_get(cb); argv[i++] = "-f"; argv[i++] = clicon_option_str(h, "CLICON_CONFIGFILE"); diff --git a/test/test_restconf_internal.sh b/test/test_restconf_internal.sh index 41e449f5..4eb894f0 100755 --- a/test/test_restconf_internal.sh +++ b/test/test_restconf_internal.sh @@ -23,8 +23,6 @@ startupdb=$dir/startup_db RESTCONFDBG=$DBG RCPROTO=http # no ssl here -RESTCONFDIR=$(dirname $(which clixon_restconf)) - # log-destination in restconf xml: syslog or file : ${LOGDST:=syslog} # Set daemon command-line to -f @@ -54,7 +52,6 @@ cat < $cfg /usr/local/lib/$APPNAME/backend example_backend.so$ /usr/local/lib/$APPNAME/restconf - $RESTCONFDIR /usr/local/lib/$APPNAME/cli $APPNAME /usr/local/var/$APPNAME/$APPNAME.sock diff --git a/test/test_restconf_internal_usecases.sh b/test/test_restconf_internal_usecases.sh index 70a94c37..f57c7cf9 100755 --- a/test/test_restconf_internal_usecases.sh +++ b/test/test_restconf_internal_usecases.sh @@ -34,8 +34,6 @@ startupdb=$dir/startup_db RESTCONFDBG=$DBG RCPROTO=http # no ssl here -RESTCONFDIR=$(dirname $(which clixon_restconf)) - INVALIDADDR=251.1.1.1 # used by fourth usecase as invalid # log-destination in restconf xml: syslog or file @@ -68,7 +66,6 @@ cat < $cfg /usr/local/lib/$APPNAME/backend example_backend.so$ /usr/local/lib/$APPNAME/restconf - $RESTCONFDIR /usr/local/lib/$APPNAME/cli $APPNAME /usr/local/var/$APPNAME/$APPNAME.sock diff --git a/yang/clixon/clixon-config@2021-07-11.yang b/yang/clixon/clixon-config@2021-07-11.yang index dfc8d54b..f4d065c8 100644 --- a/yang/clixon/clixon-config@2021-07-11.yang +++ b/yang/clixon/clixon-config@2021-07-11.yang @@ -45,8 +45,13 @@ module clixon-config { revision 2021-07-11 { description - "Added option - CLICON_SYSTEM_CAPABILITIES"; + "Added option: + CLICON_SYSTEM_CAPABILITIES + Removed default value: + CLICON_RESTCONF_INSTALLDIR + Marked as obsolete: + CLICON_YANG_LIST_CHECK + (Will be) Released in Clixon 5.3"; } revision 2021-05-20 { description @@ -518,13 +523,18 @@ module clixon-config { } leaf CLICON_RESTCONF_INSTALLDIR { type string; - default "/usr/local/sbin"; description - "Path to dir of clixon-restconf daemon binary as used by backend if started internally - Discussion: Somewhat problematic to have it as run time option. It may think it - should be known at configure or install time, but for example the main docker - installation moves the binaries, and this may be true elsewehere too. - Maybe one could locate it via PATHs search"; + "If set, path to dir of clixon-restconf daemon binary as used by backend if + started internally (run-time). + If this path is not set, clixon_restconf will be looked for according to + configured installdir: $(sbindir) (install-time) + Since programs can be moved around at install/cross-compile time the installed + dir may be difficult to know at install time, which is the reason why + CLICON_RESTCONF_INSTALLDIR exists, in order to override the Makefile + installdir. + Note on the installdir, DESTDIR is not included since according to man pages: + by specifying DESTDIR should not change the operation of the software in + any way, so its value should not be included in any file contents. "; } leaf CLICON_RESTCONF_STARTUP_DONTUPDATE { type boolean; From 0b08ba6ae56afd2aeba09225b88064886abb768c Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 27 Jul 2021 11:18:35 +0200 Subject: [PATCH 34/34] revert restconf internal tests using CLICON_RESTCONF_INSTALLDIR for docker --- test/test_restconf_internal.sh | 3 +++ test/test_restconf_internal_usecases.sh | 3 +++ 2 files changed, 6 insertions(+) diff --git a/test/test_restconf_internal.sh b/test/test_restconf_internal.sh index 4eb894f0..41e449f5 100755 --- a/test/test_restconf_internal.sh +++ b/test/test_restconf_internal.sh @@ -23,6 +23,8 @@ startupdb=$dir/startup_db RESTCONFDBG=$DBG RCPROTO=http # no ssl here +RESTCONFDIR=$(dirname $(which clixon_restconf)) + # log-destination in restconf xml: syslog or file : ${LOGDST:=syslog} # Set daemon command-line to -f @@ -52,6 +54,7 @@ cat < $cfg /usr/local/lib/$APPNAME/backend example_backend.so$ /usr/local/lib/$APPNAME/restconf + $RESTCONFDIR /usr/local/lib/$APPNAME/cli $APPNAME /usr/local/var/$APPNAME/$APPNAME.sock diff --git a/test/test_restconf_internal_usecases.sh b/test/test_restconf_internal_usecases.sh index f57c7cf9..70a94c37 100755 --- a/test/test_restconf_internal_usecases.sh +++ b/test/test_restconf_internal_usecases.sh @@ -34,6 +34,8 @@ startupdb=$dir/startup_db RESTCONFDBG=$DBG RCPROTO=http # no ssl here +RESTCONFDIR=$(dirname $(which clixon_restconf)) + INVALIDADDR=251.1.1.1 # used by fourth usecase as invalid # log-destination in restconf xml: syslog or file @@ -66,6 +68,7 @@ cat < $cfg /usr/local/lib/$APPNAME/backend example_backend.so$ /usr/local/lib/$APPNAME/restconf + $RESTCONFDIR /usr/local/lib/$APPNAME/cli $APPNAME /usr/local/var/$APPNAME/$APPNAME.sock