diff --git a/apps/backend/backend_get.c b/apps/backend/backend_get.c
index 6b21605e..55c74162 100644
--- a/apps/backend/backend_get.c
+++ b/apps/backend/backend_get.c
@@ -721,7 +721,8 @@ get_common(clicon_handle h,
int list_pagination = 0;
cxobj **xvec = NULL;
size_t xlen;
- cxobj *xlistpag;
+ cxobj *xfind;
+ char *with_defaults;
clicon_debug(1, "%s", __FUNCTION__);
username = clicon_username_get(h);
@@ -762,14 +763,14 @@ get_common(clicon_handle h,
}
}
/* Check if list pagination */
- if ((xlistpag = xml_find_type(xe, NULL, "list-pagination", CX_ELMNT)) != NULL)
+ if ((xfind = xml_find_type(xe, NULL, "list-pagination", CX_ELMNT)) != NULL)
list_pagination = 1;
/* Sanity check for list pagination: path must be a list/leaf-list, if it is,
* check config/state
*/
if (list_pagination){
if (get_list_pagination(h, ce,
- xlistpag,
+ xfind,
content, db,
depth, yspec, xpath, nsc, username,
cbret) < 0)
@@ -842,7 +843,33 @@ get_common(clicon_handle h,
}
break;
}
-
+ /* rfc6243 Handle with-defaults. */
+ if ((xfind = xml_find(xe, "with-defaults")) != NULL) {
+ if ((with_defaults = xml_find_value(xfind, "body")) != NULL) {
+ if (strcmp(with_defaults, "explicit") == 0) {
+ /* Traverse XML and mark state nodes */
+ if (xml_non_config_data(xret, NULL) < 0)
+ goto done;
+ /* Remove default configuration nodes from XML */
+ if (xml_tree_prune_flags(xret, XML_FLAG_DEFAULT, XML_FLAG_MARK|XML_FLAG_DEFAULT) < 0)
+ goto done;
+ }
+ else if (strcmp(with_defaults, "report-all") == 0) {
+ /* Accept mode, do nothing */
+ }
+ else {
+ /* Mode not supported */
+ if ((cbmsg = cbuf_new()) == NULL){
+ clicon_err(OE_UNIX, errno, "cbuf_new");
+ goto done;
+ }
+ cprintf(cbmsg, "with-defaults retrieval mode \"%s\" is not supported", with_defaults);
+ if (netconf_operation_failed(cbret, "application", cbuf_get(cbmsg)) < 0)
+ goto done;
+ goto ok;
+ }
+ }
+ }
if (content != CONTENT_CONFIG &&
clicon_option_bool(h, "CLICON_VALIDATE_STATE_XML")){
/* Check XML by validating it. return internal error with error cause
diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h
index 434d3282..aa93d85d 100644
--- a/lib/clixon/clixon_xml_map.h
+++ b/lib/clixon/clixon_xml_map.h
@@ -59,6 +59,7 @@ int xml_diff(yang_stmt *yspec, cxobj *x0, cxobj *x1,
cxobj ***changed_x0, cxobj ***changed_x1, int *changedlen);
int xml_tree_prune_flagged_sub(cxobj *xt, int flag, int test, int *upmark);
int xml_tree_prune_flagged(cxobj *xt, int flag, int test);
+int xml_tree_prune_flags(cxobj *xt, int flags, int mask);
int xml_namespace_change(cxobj *x, char *ns, char *prefix);
int xml_default_recurse(cxobj *xn, int state);
int xml_global_defaults(clicon_handle h, cxobj *xn, cvec *nsc, const char *xpath, yang_stmt *yspec, int state);
diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c
index 8eae6d3a..9a7d0194 100644
--- a/lib/src/clixon_netconf_lib.c
+++ b/lib/src/clixon_netconf_lib.c
@@ -1529,6 +1529,9 @@ netconf_module_load(clicon_handle h)
/* Load ietf list pagination netconf */
if (yang_spec_parse_module(h, "ietf-list-pagination-nc", NULL, yspec)< 0)
goto done;
+ /* Load rfc6243 with-defaults module explicit (imported by ietf-list-pagination-nc) */
+ if (yang_spec_parse_module(h, "ietf-netconf-with-defaults", NULL, yspec)< 0)
+ goto done;
/* Framing: If hello protocol skipped, set framing direct, ie fix chunked framing if NETCONF-1.1
* But start with default: RFC 4741 EOM ]]>]]>
* For now this only applies to external protocol
@@ -1721,6 +1724,8 @@ netconf_hello_server(clicon_handle h,
cprintf(cb, "urn:ietf:params:netconf:capability:startup:1.0");
cprintf(cb, "urn:ietf:params:netconf:capability:xpath:1.0");
cprintf(cb, "urn:ietf:params:netconf:capability:notification:1.0");
+ /* rfc6243 with-defaults capability modes */
+ cprintf(cb, "urn:ietf:params:netconf:capability:with-defaults:1.0?basic-mode=explicit");
cprintf(cb, "");
if (session_id)
cprintf(cb, "%lu", (long unsigned int)session_id);
diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c
index 9a33f129..d7778194 100644
--- a/lib/src/clixon_xml_map.c
+++ b/lib/src/clixon_xml_map.c
@@ -561,6 +561,42 @@ xml_tree_prune_flagged(cxobj *xt,
return retval;
}
+/*! Prune everything that passes test
+ * @param[in] xt XML tree with some node marked
+ * @param[in] flags Flags set
+ * @param[in] mask Which flags to test for
+ * The function removes all branches that does pass test
+ * @code
+ * xml_tree_prune_flaggs(xt, XML_FLAG_MARK, XML_FLAG_MARK|XML_FLAG_DEFAULT);
+ * @endcode
+ */
+int
+xml_tree_prune_flags(cxobj *xt,
+ int flags,
+ int mask)
+{
+ int retval = -1;
+ cxobj *x;
+ cxobj *xprev;
+
+ x = NULL;
+ xprev = NULL;
+ while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
+ if (xml_flag(x, mask) == flags){ /* Pass test means purge */
+ if (xml_purge(x) < 0)
+ goto done;
+ x = xprev;
+ continue;
+ }
+ if (xml_tree_prune_flags(x, flags, mask) < 0)
+ goto done;
+ xprev = x;
+ }
+ retval = 0;
+ done:
+ return retval;
+}
+
/*! Add prefix:namespace pair to xml node, set cache, etc
* @param[in] x XML node whose namespace should change
* @param[in] xp XML node where namespace attribute should be declared (can be same)
diff --git a/test/test_netconf_hello.sh b/test/test_netconf_hello.sh
index 0f91edb8..4b6e24fc 100755
--- a/test/test_netconf_hello.sh
+++ b/test/test_netconf_hello.sh
@@ -106,7 +106,7 @@ new "Netconf snd hello with prefix"
expecteof "$clixon_netconf -qef $cfg" 0 "urn:ietf:params:netconf:base:1.1]]>]]>" '^$'
new "netconf snd + rcv hello"
-expecteof "$clixon_netconf -f $cfg" 0 "urn:ietf:params:netconf:base:1.1]]>]]>" "^urn:ietf:params:netconf:base:1.1urn:ietf:params:netconf:base:1.0urn:ietf:params:netconf:capability:yang-library:1.0?revision=2019-01-04&module-set-id=42urn: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]*]]>]]>$" '^$'
+expecteof "$clixon_netconf -f $cfg" 0 "urn:ietf:params:netconf:base:1.1]]>]]>" "^urn:ietf:params:netconf:base:1.1urn:ietf:params:netconf:base:1.0urn:ietf:params:netconf:capability:yang-library:1.0?revision=2019-01-04&module-set-id=42urn: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.0urn:ietf:params:netconf:capability:with-defaults:1.0?basic-mode=explicit[0-9]*]]>]]>$" '^$'
new "Netconf snd hello with extra element"
expecteof "$clixon_netconf -qef $cfg" 0 "urn:ietf:params:netconf:base:1.1]]>]]>" '^protocolunknown-elementextra-elementerrorUnrecognized hello/capabilities element]]>]]>$' '^$'
diff --git a/test/test_netconf_ssh_callhome.sh b/test/test_netconf_ssh_callhome.sh
index b17f163e..4f7e2d98 100755
--- a/test/test_netconf_ssh_callhome.sh
+++ b/test/test_netconf_ssh_callhome.sh
@@ -132,7 +132,7 @@ EOF
new "Start Listener client"
echo "ssh -s -F $sshcfg -v -i $key -o ProxyUseFdpass=yes -o ProxyCommand=\"clixon_netconf_ssh_callhome_client -a 127.0.0.1\" . netconf"
#-F $sshcfg
-expectpart "$(ssh -s -F $sshcfg -v -i $key -o ProxyUseFdpass=yes -o ProxyCommand="${clixon_netconf_ssh_callhome_client} -a 127.0.0.1" . netconf < $rpccmd)" 0 "urn:ietf:params:netconf:base:1.1urn:ietf:params:netconf:base:1.0urn:ietf:params:netconf:capability:yang-library:1.0?revision=2019-01-04&module-set-id=42urn: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.02" ""
+expectpart "$(ssh -s -F $sshcfg -v -i $key -o ProxyUseFdpass=yes -o ProxyCommand="${clixon_netconf_ssh_callhome_client} -a 127.0.0.1" . netconf < $rpccmd)" 0 "urn:ietf:params:netconf:base:1.1urn:ietf:params:netconf:base:1.0urn:ietf:params:netconf:capability:yang-library:1.0?revision=2019-01-04&module-set-id=42urn: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.0urn:ietf:params:netconf:capability:with-defaults:1.0?basic-mode=explicit2" ""
# Wait
wait
diff --git a/test/test_yang_with_defaults.sh b/test/test_yang_with_defaults.sh
new file mode 100755
index 00000000..4896f21a
--- /dev/null
+++ b/test/test_yang_with_defaults.sh
@@ -0,0 +1,336 @@
+#!/usr/bin/env bash
+
+
+# Test of the IETF rfc6243: With-defaults Capability for NETCONF
+#
+# Test cases below follows the RFC.
+#
+
+# Magic line must be first in script (see README.md)
+s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
+
+APPNAME=example
+
+cfg=$dir/conf_yang.xml
+fyang=$dir/example-default.yang
+fstate=$dir/state.xml
+
+cat < $cfg
+
+ $cfg
+ ietf-netconf:startup
+ 42
+ ${YANG_INSTALLDIR}
+ $fyang
+ /usr/local/lib/$APPNAME/clispec
+ /usr/local/lib/$APPNAME/backend
+ example_backend.so$
+ /usr/local/lib/$APPNAME/netconf
+ /usr/local/lib/$APPNAME/restconf
+ /usr/local/lib/$APPNAME/cli
+ $APPNAME
+ $dir/$APPNAME.sock
+ /usr/local/var/$APPNAME/$APPNAME.pidfile
+ $dir
+ false
+
+EOF
+
+# A.1. Example YANG Module
+# The following YANG module defines an example interfaces table to
+# demonstrate how the parameter behaves for a specific
+# data model.
+cat < $fyang
+module example {
+
+ namespace "http://example.com/ns/interfaces";
+
+ prefix exam;
+
+ typedef status-type {
+ description "Interface status";
+ type enumeration {
+ enum ok;
+ enum 'waking up';
+ enum 'not feeling so good';
+ enum 'better check it out';
+ enum 'better call for help';
+ }
+ default ok;
+ }
+
+ container interfaces {
+ description "Example interfaces group";
+
+ list interface {
+ description "Example interface entry";
+ key name;
+
+ leaf name {
+ description
+ "The administrative name of the interface.
+ This is an identifier that is only unique
+ within the scope of this list, and only
+ within a specific server.";
+ type string {
+ length "1 .. max";
+ }
+ }
+
+ leaf mtu {
+ description
+ "The maximum transmission unit (MTU) value assigned to
+ this interface.";
+ type uint32;
+ default 1500;
+ }
+
+ leaf status {
+ description
+ "The current status of this interface.";
+ type status-type;
+ config false;
+ }
+ }
+ }
+ }
+EOF
+
+# A.2. Example Data Set
+
+EXAMPLENS="xmlns=\"http://example.com/ns/interfaces\""
+
+XML="\
+eth08192\
+eth1\
+eth29000\
+eth31500\
+"
+
+cat < $fstate
+
+eth2not feeling so good
+eth3waking up
+
+EOF
+
+
+db=startup
+if [ $db = startup ]; then
+ sudo echo "<${DATASTORE_TOP}>$XML${DATASTORE_TOP}>" > $dir/startup_db
+fi
+if [ $BE -ne 0 ]; then # Bring your own backend
+ new "kill old backend"
+ sudo clixon_backend -zf $cfg
+ if [ $? -ne 0 ]; then
+ err
+ fi
+ new "start backend -s $db -f $cfg"
+ start_backend -s $db -f $cfg -- -sS $fstate
+fi
+
+new "wait backend"
+wait_backend
+
+# permission kludges
+new "chmod datastores"
+sudo chmod 666 $dir/running_db
+if [ $? -ne 0 ]; then
+ err1 "chmod $dir/running_db"
+fi
+sudo chmod 666 $dir/startup_db
+if [ $? -ne 0 ]; then
+ err1 "chmod $dir/startup_db"
+fi
+
+new "Checking startup unchanged"
+ret=$(diff $dir/startup_db <(echo "<${DATASTORE_TOP}>$XML${DATASTORE_TOP}>"))
+if [ $? -ne 0 ]; then
+ err "<${DATASTORE_TOP}>$XML${DATASTORE_TOP}>" "$ret"
+fi
+
+new "Checking running unchanged"
+ret=$(diff $dir/running_db <(echo -n "<${DATASTORE_TOP}>$XML${DATASTORE_TOP}>"))
+if [ $? -ne 0 ]; then
+ err "<${DATASTORE_TOP}>$XML${DATASTORE_TOP}>" "$ret"
+fi
+
+new "rfc4243 4.3. Capability Identifier"
+expecteof "$clixon_netconf -ef $cfg" 0 "$DEFAULTHELLO" \
+"urn:ietf:params:netconf:capability:with-defaults:1.0?basic-mode=explicit"
+
+new "rfc6243 3.1. 'report-all' Retrieval Mode"
+expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" \
+"\
+report-all" \
+"" \
+"\
+eth08192ok\
+eth11500ok\
+eth29000not feeling so good\
+eth31500waking up\
+"
+
+new "rfc6243 3.2. 'trim' Retrieval Mode"
+expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" \
+"\
+trim" \
+"" \
+"applicationoperation-failed\
+error\
+with-defaults retrieval mode \"trim\" is not supported"
+
+new "rfc6243 3.3. 'explicit' Retrieval Mode"
+expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" \
+"\
+explicit" \
+"" \
+"\
+eth08192ok\
+eth1ok\
+eth29000not feeling so good\
+eth31500waking up\
+"
+
+new "rfc6243 3.4. 'report-all-tagged' Retrieval Mode"
+expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" \
+"\
+report-all-tagged" \
+"" \
+"applicationoperation-failed\
+error\
+with-defaults retrieval mode \"report-all-tagged\" is not supported"
+
+new "rfc6243 2.3.1. 'explicit' Basic Mode Retrieval"
+expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" \
+"" "" \
+"\
+eth08192ok\
+eth11500ok\
+eth29000not feeling so good\
+eth31500waking up\
+" ""
+
+new "rfc6243 2.3.3. 'explicit' and Behavior (part 1): create explicit node"
+# A valid 'create' operation attribute for a data node that has
+# been set by a client to its schema default value MUST fail with a
+# 'data-exists' error-tag.
+# (test: try to create mtu=3000 on interface eth3)
+expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" \
+"\
+\
+eth33000\
+none " "" \
+"\
+application\
+data-exists\
+error\
+Data already exists; cannot create new resource\
+"
+# nothing to commit here, but just to verify
+expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" ""
+# verify no change
+expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" \
+"" "" \
+"\
+eth08192ok\
+eth11500ok\
+eth29000not feeling so good\
+eth31500waking up\
+" ""
+
+new "rfc6243 2.3.3. 'explicit' and Behavior (part 2): create default node"
+# A valid 'create' operation attribute for a
+# data node that has been set by the server to its schema default value
+# MUST succeed.
+# (test: set mtu=3000 on interface eth1)
+expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" \
+"\
+\
+eth13000\
+none " \
+"" \
+""
+# commit change
+expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" ""
+# verify that the mtu value has changed
+expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" \
+"" "" \
+"\
+eth08192ok\
+eth13000ok\
+eth29000not feeling so good\
+eth31500waking up\
+" ""
+
+new "rfc6243 2.3.3. 'explicit' and Behavior (part 3): delete explicit node"
+# A valid 'delete' operation attribute for a data node
+# that has been set by a client to its schema default value MUST
+# succeed.
+# (test: try to delete mtu on interface eth1)
+expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" \
+"\
+\
+eth1\
+none" \
+"" \
+""
+# commit delete
+expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" ""
+# check thet the default mtu vale has been restored
+expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" \
+"" "" \
+"\
+eth08192ok\
+eth11500ok\
+eth29000not feeling so good\
+eth31500waking up\
+" ""
+
+new "rfc6243 2.3.3. 'explicit' and Behavior (part 4): delete default node"
+# A valid 'delete' operation attribute for a data node that
+# has been set by the server to its schema default value MUST fail with
+# a 'data-missing' error-tag.
+#(test: try to delete default mtu on interface eth1)
+expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" \
+"\
+\
+eth11500\
+none" \
+"" \
+"\
+application\
+data-missing\
+error\
+Data does not exist; cannot delete resource\
+"
+# nothing to commit
+expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" ""
+# verify that the configuration has not changed
+expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" \
+"" "" \
+"\
+eth08192ok\
+eth11500ok\
+eth29000not feeling so good\
+eth31500waking up\
+" ""
+
+
+if [ $BE -ne 0 ]; then # Bring your own backend
+ 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
+
+unset ret
+
+new "endtest"
+endtest