Merge branch 'develop' into nacm

This commit is contained in:
Olof hagsand 2019-01-12 12:29:34 +01:00
commit 9a7ce8e06d
194 changed files with 5941 additions and 3452 deletions

5
.gitignore vendored
View file

@ -14,10 +14,9 @@ lib/Makefile
lib/*/Makefile
autom4te.cache/
clixon.conf.cpp
clixon.mk
config.log
config.status
TAGS
apps/backend/clixon_backend
apps/backend/test
@ -45,5 +44,5 @@ build-root/*.tar.xz
build-root/*.rpm
build-root/rpmbuild
test/public
test/site.sh
doc/html

View file

@ -1,47 +1,105 @@
# Clixon Changelog
## 3.9.0 (Preliminary Target: 31 December 2018)
## 3.9.0 (Preliminary Target: Mid-January 2019)
### Planned new features
* [Roadmap](ROADMAP.md) (Uncommitted and unprioritized)
* [Roadmap](ROADMAP.md)
### Major New features
* NACM extension (RFC8341)
* NACM module support (RFC8341 A1+A2)
* Recovery user "_nacm_recovery" added.
* Example use is restconf PUT when NACM edit-config is permitted, then automatic commit and discard are permitted using recovery user.
* Example user changed adm1 to andy to comply with RFC8341 example
* Correct XML namespace handling
* XML multiple modules was based on non-strict semantics so that yang modules were found by iterating thorugh namespaces until a match was made. This did not adhere to proper [XML namespace handling](https://www.w3.org/TR/2009/REC-xml-names-20091208) as well as strict Netconf and Restconf namespace handling, which causes problems with overlapping names and false positives, and most importantly, with standard conformance.
* There are still the following non-strict namespace handling:
* Everything in ietf-netconf base syntax with namespace `urn:ietf:params:xml:ns:netconf:base:1.0` is default and need not be explicitly given
* edit-config xpath select statement does not support namespaces
* notifications do not support namespaces.
* Below see netconf old (but wrong) netconf RPC:
```
<rpc>
<my-own-method/>
</rpc>
<rpc-reply>
<route>
<address-family>ipv4</address-family>
</route>
</rpc-reply>
```
This is the currently correct Netconf RPC:
```
<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> # xmlns may be ommitted
<my-own-method xmlns="urn:example:my-own">
</rpc>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<route xmlns="urn:ietf:params:xml:ns:yang:ietf-routing">
<address-family>ipv4</address-family>
</route>
</rpc-reply>
```
* Another example for restconf rpc with new correct syntax. Note that while Netconf uses xmlns attribute syntax, Restconf uses module name prefix. First the request:
```
POST http://localhost/restconf/operations/example:example)
Content-Type: application/yang-data+json
{
"example:input":{
"x":0
}
}
```
then the reply:
```
HTTP/1.1 200 OK
{
"example:output": {
"x": "0",
"y": "42"
}
}
```
* To keep previous non-strict namespace handling (backwards compatible), set CLICON_XML_NS_STRICT to false.
* See https://github.com/clicon/clixon/issues/49
* Yang code upgrade (RFC7950)
* YANG parser cardinality checked (https://github.com/clicon/clixon/issues/48)
* See https://github.com/clicon/clixon/issues/84
* RPC method input parameters validated
* see https://github.com/clicon/clixon/issues/47
* Support of submodule, include and belongs-to.
* Openconfig yang specs parsed: https://github.com/openconfig/public
* Parsing of standard yang files supported, such as:
* https://github.com/openconfig/public - except [https://github.com/clicon/clixon/issues/60].
* See [test/test_openconfig.sh]
* https://github.com/YangModels/yang - except vendor-specific specs
* See [test/test_yangmodels.sh]
* Improved "unknown" handling
* More precise Yang validation and better error messages
* Example: adding bad-, missing-, or unknown-element error messages, instead of operation-failed.
* Validation of mandatory choice and recursive mandatory containers
* Yang load file configure options changed
* `CLICON_YANG_DIR` is changed from a single directory to a path of directories
* Note CLIXON_DATADIR (=/usr/local/share/clixon) need to be in the list
* CLICON_YANG_MAIN_FILE Provides a filename with a single module filename.
* CLICON_YANG_MAIN_DIR Provides a directory where all yang modules should be loaded.
* Correct XML namespace handling
* XML multiple modules was based on "loose" semantics so that yang modules were found by iterating thorugh namespaces until a match was made. This did not adhere to proper [XML namespace handling](https://www.w3.org/TR/2009/REC-xml-names-20091208), and causes problems with overlapping names and false positives. Below see XML accepted (but wrong), and correct namespace declaration:
```
<rpc><my-own-method></rpc> # Wrong but accepted
<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> # Correct
<my-own-method xmlns="http://example.net/me/my-own/1.0">
</rpc>
```
* To keep old loose semantics set config option CLICON_XML_NS_ITERATE (true by default)
* XML to JSON translator support for mapping xmlns attribute to module name prefix.
* Default namespace is still "urn:ietf:params:xml:ns:netconf:base:1.0"
* See https://github.com/clicon/clixon/issues/49
* NACM extension (RFC8341)
* NACM module support (RFC8341 A1+A2)
* Recovery user "_nacm_recovery" added.
* Example use is restconf PUT when NACM edit-config is permitted, then automatic commit and discard are permitted using recovery user.
* Example user changed adm1 to andy to comply with RFC8341 example
* Added -o "<option>=<value>" command-line option to all programs: backend, cli, netconf, restconf.
* Any config option from file can be overrided by giving them on command-line.
### API changes on existing features (you may need to change your code)
* Date-and-time type now properly uses ISO 8601 UTC timezone designators.
* Eg 2008-09-21T18:57:21.003456 is changed to 2008-09-21T18:57:21.003456Z
* Renamed yang file `ietf-netconf-notification@2008-07-01.yang` to `clixon-rfc5277`.
* Fixed validation problems, see [https://github.com/clicon/clixon/issues/62]
* Name confusion, the file is manually constructed from the rfc.
* Changed prefix to `ncevent`
* Stricter YANG choice validation leads to enforcement of structures like: `choice c{ mandatory true; leaf x` statements. `x` was not previously enforced.
* Many hand-crafted validation messages have been removed and replaced with generic validations, which may lead to changed rpc-error messages.
* CLICON_XML_SORT option (in clixon-config.yang) has been removed and set to true permanently. Unsorted XML lists leads to slower performance and old obsolete code can be removed.
* Strict namespace setting can be a problem when upgrading existing database files, such as startup-db or persistent running-db, or any other saved XML file.
* Removed `delete-config` support for candidate db since it is not supported in RFC6241.
* Switched the order of `error-type` and `error-tag` in all netconf and restconf error messages to comply to RFC order.
* Yang parser is stricter (see above) which may break parsing of existing yang specs.
* XML namespace handling is corrected (see above)
* For backward compatibility set config option CLICON_XML_NS_ITERATE
* For backward compatibility set config option CLICON_XML_NS_LOOSE
* Yang parser functions have changed signatures. Please check the source if you call these functions.
* Add `<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>` to your configuration file, or corresponding CLICON_DATADIR directory for Clixon system yang files.
* Change all @datamodel:tree to @datamodel in all CLI specification files
@ -49,6 +107,13 @@
* For backward compatibility, define CLICON_CLI_MODEL_TREENAME_PATCH in clixon_custom.h
### Minor changes
* Cligen uses posix regex while yang uses XSD. It differs in some aspects. A translator function has been added for `\d` -> `[0-9]` translation, there may be more.
* Added new clixon-lib yang module for internal netconf protocol. Currently only extends the standard with a debug RPC.
* Added three-valued return values for several validate functions where -1 is fatal error, 0 is validation failed and 1 is validation OK.
* This includes: `xmldb_put`, `xml_yang_validate_all`, `xml_yang_validate_add`, `xml_yang_validate_rpc`, `api_path2xml`, `api_path2xpath`
* Added new xml functions for specific types: `xml_child_nr_notype`, `xml_child_nr_notype`, `xml_child_i_type`, `xml_find_type`.
* Added example_rpc RPC to example backend
* Renamed xml_namespace[_set]() to xml_prefix[_set]()
* Changed all make tags --> make TAGS
* Keyvalue datastore removed (it has been disabled since 3.3.3)
* Removed return value ymodp from yang parse functions (eg yang_parse()).
@ -60,7 +125,14 @@
* <!DOCTYPE (ie DTD) is not supported.
### Corrected Bugs
* [ietf-netconf-notification@2008-07-01.yang validation problem #62](https://github.com/clicon/clixon/issues/62)
* Ignore CR(\r) in yang files for DOS files
* Keyword "min" (not only "max") can be used in built-in types "range" and "length" statements.
* Support for empty yang string added, eg `default "";`
* Removed CLI generation for yang notifications (and other non-data yang nodes)
* Some restconf error messages contained "rpc-reply" or "rpc-error" which have now been removed.
* getopt return value changed from char to int (https://github.com/clicon/clixon/issues/58)
* Netconf/Restconf RPC extra input arguments are ignored (https://github.com/clicon/clixon/issues/47)
### Known issues
* debug rpc added in example application (should be in clixon-config).
@ -265,7 +337,7 @@ translate {
### Known issues
* Namespace name relabeling is not supported.
* Eg: if "des" is defined as prefix for an imported module, then a relabeling using xmlfns is not supported, such as:
* Eg: if "des" is defined as prefix for an imported module, then a relabeling using xmlns is not supported, such as:
```
<crypto xmlns:x="urn:example:des">x:des3</crypto>
```

View file

@ -97,6 +97,9 @@ EOF
## New release
What to think about when doing a new release.
* valgrind for memory leaks
* New clixon-config.yang revision?
Tagging:
* git merge --no-ff develop
* change CLIXON_VERSION in configure.ac
* git tag -a <version"

View file

@ -1,4 +1,4 @@
Copyright 2009-2018 Olof Hagsand and Benny Holmgren
Copyright 2009-2019 Olof Hagsand and Benny Holmgren
CLIXON is dual license.

View file

@ -1,7 +1,7 @@
#
# ***** BEGIN LICENSE BLOCK *****
#
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
#
# This file is part of CLIXON
#

View file

@ -105,6 +105,23 @@ The standards covered include:
Not supported:
- !DOCTYPE (ie DTD)
Historically, Clixon has not until 3.9 made strict namespace
enforcing. For example, the following non-strict netconf was
previously accepted:
```
<rpc><my-own-method/></rpc>
```
In 3.9, the same statement should be, for example:
```
<rpc><my-own-method xmlns="urn:example:my-own"/></rpc>
```
Note that base netconf syntax is still not enforced but recommended:
```
<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<my-own-method xmlns="urn:example:my-own"/>
</rpc>
```
Yang
====
YANG and XML is the heart of Clixon. Yang modules are used as a
@ -125,8 +142,10 @@ However, the following YANG syntax modules are not implemented:
- belongs-to
Restrictions on Yang types are as follows:
- The range statement does not support multiple values (RFC7895 sec 9.2.4)
- The range statement for built-in integers does not support multiple values (RFC7950 9.2.4)
- The length statement for built-in strings does not support multiple values (RFC7950 9.4.4)
- Submodules cannot re-use a prefix in an import statement that is already used for another imported module in the module that the submodule belongs to. (see https://github.com/clicon/clixon/issues/60)
- default values on leaf-lists (RFC7950 7.7.2)
Netconf
=======
@ -152,6 +171,9 @@ Clixon does not support the following netconf features:
- edit-config config-text
- edit-config operation
Some other deviations from the RFC:
- edit-config xpath select statement does not support namespaces
Restconf
========
Clixon Restconf is a daemon based on FastCGI C-API. Instructions are available to

View file

@ -1,27 +1,42 @@
# Clixon roadmap
Not in prio order (yet)
## High prio
- NACM (RFC 8341)
- Module rules (done)
- Data node rules (read/create/delete/update/execute)
- Special handling of the initial startup transaction to avoid exit at startup
- Possibly - draft-wu-netconf-restconf-factory-restore-03
- Handle revisions to data model.
- Possibly draft-wang-netmod-module-revision-management-01
- XML [Namespace handling](https://github.com/clicon/clixon/issues/49)
- XML
- [Namespace handling](https://github.com/clicon/clixon/issues/49)
## Medium prio:
- Input validation on custom RPCs/ (done)
- [Sanity checks](https://github.com/clicon/clixon/issues/47)
- Support for XML regex's.
- Currently Posix extended regular expressions
- Support a plugin callback that is invoked when copy-config is called.
- Preserve CLI command history across sessions. The up/down arrows
## Low prio:
- Provide a client library to access netconf APIs provided by system services.
- Netconf backend (Clixon acts as netconf controller)
- Support for restconf call-home (RFC 8071)
Not prioritized:
- Support for restconf PATCH method
- NETCONF
- Support for additional Netconf [edit-config modes](https://github.com/clicon/clixon/issues/53)
- Netconf [framing](https://github.com/clicon/clixon/issues/50)
- [Sanity checks](https://github.com/clicon/clixon/issues/47)
- [Child ordering](https://github.com/clicon/clixon/issues/22)
- Netconf backend (Clixon acts as netconf controller)
- Restconf
- Query parameters
- NACM (RFC 8341) is somewhat limited
- Extend with data node access (read/create/delete/update/execute)
- Streams (netconf and restconf)
- Extend native stream mode with external persistent timeseries database, eg influxdb.
- Jenkins CI/CD and webhooks
- YANG
- [Cardinality](https://github.com/clicon/clixon/issues/48)
- RFC 6022 [NETCONF monitoring](https://github.com/clicon/clixon/issues/39)
- Factory default Setting - draft-wu-netconf-restconf-factory-restore-03
- Deviation, belongs-to, min/max-elements, action, unique
- Deviation, min/max-elements, action, unique
- Containers
- [Docker improvements](https://github.com/clicon/clixon/issues/44)
- Kubernetes Helm chart definition

View file

@ -1,7 +1,7 @@
#
# ***** BEGIN LICENSE BLOCK *****
#
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
#
# This file is part of CLIXON
#

View file

@ -1,7 +1,7 @@
#
# ***** BEGIN LICENSE BLOCK *****
#
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
#
# This file is part of CLIXON
#

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
@ -292,7 +292,7 @@ client_get_streams(clicon_handle h,
* @param[in,out] xret Existing XML tree, merge x into this
* @retval -1 Error (fatal)
* @retval 0 OK
* @retval 1 Statedata callback failed
* @retval 1 Statedata callback failed (clicon_err called)
*/
static int
client_statedata(clicon_handle h,
@ -309,14 +309,14 @@ client_statedata(clicon_handle h,
clicon_err(OE_YANG, ENOENT, "No yang spec");
goto done;
}
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277") &&
(retval = client_get_streams(h, yspec, xpath, "ietf-netconf-notification", "netconf", xret)) != 0)
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277"))
if ((retval = client_get_streams(h, yspec, xpath, "clixon-rfc5277", "netconf", xret)) != 0)
goto done;
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040") &&
(retval = client_get_streams(h, yspec, xpath, "ietf-restconf-monitoring", "restconf-state", xret)) != 0)
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040"))
if ((retval = client_get_streams(h, yspec, xpath, "ietf-restconf-monitoring", "restconf-state", xret)) != 0)
goto done;
if (clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895") &&
(retval = yang_modules_state_get(h, yspec, xret)) != 0)
if (clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895"))
if ((retval = yang_modules_state_get(h, yspec, xret)) != 0)
goto done;
if ((retval = clixon_plugin_statedata(h, yspec, xpath, xret)) != 0)
goto done;
@ -339,7 +339,7 @@ client_statedata(clicon_handle h,
/* reset flag */
if (xml_apply(*xret, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0)
goto done;
retval = 0;
retval = 0; /* OK */
done:
if (xvec)
free(xvec);
@ -363,7 +363,6 @@ from_client_get(clicon_handle h,
char *xpath = "/";
cxobj *xret = NULL;
int ret;
cbuf *cbx = NULL; /* Assist cbuf */
if ((xfilter = xml_find(xe, "filter")) != NULL)
if ((xpath = xml_find_value(xfilter, "select"))==NULL)
@ -379,8 +378,12 @@ from_client_get(clicon_handle h,
clicon_err_reset();
if ((ret = client_statedata(h, xpath, &xret)) < 0)
goto done;
if (ret == 0){ /* OK */
cprintf(cbret, "<rpc-reply>");
if (ret == 1){ /* Error from callback (error in xret) */
if (clicon_xml2cbuf(cbret, xret, 0, 0) < 0)
goto done;
goto ok;
}
cprintf(cbret, "<rpc-reply>"); /* OK */
if (xret==NULL)
cprintf(cbret, "<data/>");
else{
@ -390,22 +393,9 @@ from_client_get(clicon_handle h,
goto done;
}
cprintf(cbret, "</rpc-reply>");
}
else { /* 1 Error from callback */
if ((cbx = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
cprintf(cbx, "Internal error:%s", clicon_err_reason);
if (netconf_operation_failed(cbret, "rpc", cbuf_get(cbx))< 0)
goto done;
clicon_log(LOG_NOTICE, "%s Error in backend_statedata_call:%s", __FUNCTION__, xml_name(xe));
}
ok:
retval = 0;
done:
if (cbx)
cbuf_free(cbx);
if (xret)
xml_free(xret);
return retval;
@ -433,14 +423,16 @@ from_client_edit_config(clicon_handle h,
int non_config = 0;
yang_spec *yspec;
cbuf *cbx = NULL; /* Assist cbuf */
int ret;
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec9");
goto done;
}
if ((target = netconf_db_find(xn, "target")) == NULL){
clicon_err(OE_XML, 0, "db not found");
if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0)
goto done;
goto ok;
}
if ((cbx = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
@ -468,12 +460,12 @@ from_client_edit_config(clicon_handle h,
}
}
if ((xc = xpath_first(xn, "config")) == NULL){
if (netconf_missing_element(cbret, "protocol", "<bad-element>config</bad-element>", NULL) < 0)
if (netconf_missing_element(cbret, "protocol", "config", NULL) < 0)
goto done;
goto ok;
}
else{
/* <config> yang spec may be set to anyxml by ingress yang check,...*/
/* <config> yang spec may be set to anyxmly by ingress yang check,...*/
if (xml_spec(xc) != NULL)
xml_spec_set(xc, NULL);
/* Populate XML with Yang spec (why not do this in parser?)
@ -491,24 +483,27 @@ from_client_edit_config(clicon_handle h,
/* Cant do this earlier since we dont have a yang spec to
* the upper part of the tree, until we get the "config" tree.
*/
if (xml_child_sort && xml_apply0(xc, CX_ELMNT, xml_sort, NULL) < 0)
if (xml_apply0(xc, CX_ELMNT, xml_sort, NULL) < 0)
goto done;
if (xmldb_put(h, target, operation, xc, cbret) < 0){
if ((ret = xmldb_put(h, target, operation, xc, cbret)) < 0){
clicon_debug(1, "%s ERROR PUT", __FUNCTION__);
if (netconf_operation_failed(cbret, "protocol", clicon_err_reason)< 0)
goto done;
goto ok;
}
if (ret == 0)
goto ok;
}
ok:
if (!cbuf_len(cbret))
assert(cbuf_len(cbret) == 0);
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
ok:
retval = 0;
done:
if (cbx)
cbuf_free(cbx);
clicon_debug(1, "%s done cbret:%s", __FUNCTION__, cbuf_get(cbret));
return retval;
} /* from_client_edit_config */
/*! Internal message: Lock database
@ -530,7 +525,7 @@ from_client_lock(clicon_handle h,
cbuf *cbx = NULL; /* Assist cbuf */
if ((db = netconf_db_find(xe, "target")) == NULL){
if (netconf_missing_element(cbret, "protocol", "<bad-element>target</bad-element>", NULL) < 0)
if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0)
goto done;
goto ok;
}
@ -589,7 +584,7 @@ from_client_unlock(clicon_handle h,
cbuf *cbx = NULL; /* Assist cbuf */
if ((db = netconf_db_find(xe, "target")) == NULL){
if (netconf_missing_element(cbret, "protocol", "<bad-element>target</bad-element>", NULL) < 0)
if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0)
goto done;
goto ok;
}
@ -651,7 +646,7 @@ from_client_kill_session(clicon_handle h,
if ((x = xml_find(xe, "session-id")) == NULL ||
(str = xml_find_value(x, "body")) == NULL){
if (netconf_missing_element(cbret, "protocol", "<bad-element>session-id</bad-element>", NULL) < 0)
if (netconf_missing_element(cbret, "protocol", "session-id", NULL) < 0)
goto done;
goto ok;
}
@ -709,7 +704,7 @@ from_client_copy_config(clicon_handle h,
cbuf *cbx = NULL; /* Assist cbuf */
if ((source = netconf_db_find(xe, "source")) == NULL){
if (netconf_missing_element(cbret, "protocol", "<bad-element>source</bad-element>", NULL) < 0)
if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0)
goto done;
goto ok;
}
@ -724,7 +719,7 @@ from_client_copy_config(clicon_handle h,
goto ok;
}
if ((target = netconf_db_find(xe, "target")) == NULL){
if (netconf_missing_element(cbret, "protocol", "<bad-element>target</bad-element>", NULL) < 0)
if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0)
goto done;
goto ok;
}
@ -777,7 +772,7 @@ from_client_delete_config(clicon_handle h,
if ((target = netconf_db_find(xe, "target")) == NULL||
strcmp(target, "running")==0){
if (netconf_missing_element(cbret, "protocol", "<bad-element>target</bad-element>", NULL) < 0)
if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0)
goto done;
goto ok;
}
@ -856,7 +851,7 @@ from_client_create_subscription(clicon_handle h,
if ((x = xpath_first(xe, "//stopTime")) != NULL){
if ((stoptime = xml_find_value(x, "body")) != NULL &&
str2time(stoptime, &stop) < 0){
if (netconf_bad_element(cbret, "application", "<bad-element>stopTime</bad-element>", "Expected timestamp") < 0)
if (netconf_bad_element(cbret, "application", "stopTime", "Expected timestamp") < 0)
goto done;
goto ok;
}
@ -864,7 +859,7 @@ from_client_create_subscription(clicon_handle h,
if ((x = xpath_first(xe, "//startTime")) != NULL){
if ((starttime = xml_find_value(x, "body")) != NULL &&
str2time(starttime, &start) < 0){
if (netconf_bad_element(cbret, "application", "<bad-element>startTime</bad-element>", "Expected timestamp") < 0)
if (netconf_bad_element(cbret, "application", "startTime", "Expected timestamp") < 0)
goto done;
goto ok;
}
@ -925,7 +920,7 @@ from_client_debug(clicon_handle h,
char *valstr;
if ((valstr = xml_find_body(xe, "level")) == NULL){
if (netconf_missing_element(cbret, "application", "<bad-element>level</bad-element>", NULL) < 0)
if (netconf_missing_element(cbret, "application", "level", NULL) < 0)
goto done;
goto ok;
}
@ -971,6 +966,7 @@ from_client_msg(clicon_handle h,
clicon_debug(1, "%s", __FUNCTION__);
pid = ce->ce_pid;
yspec = clicon_dbspec_yang(h);
/* Return netconf message. Should be filled in by the dispatch(sub) functions
* as wither rpc-error or by positive response.
*/
@ -978,28 +974,26 @@ from_client_msg(clicon_handle h,
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
if (clicon_msg_decode(msg, &xt) < 0){
if (clicon_msg_decode(msg, yspec, &xt) < 0){
if (netconf_malformed_message(cbret, "XML parse error")< 0)
goto done;
goto reply;
}
/* Get yang spec */
yspec = clicon_dbspec_yang(h); /* XXX maybe move to clicon_msg_decode? */
if ((x = xpath_first(xt, "/rpc")) == NULL){
if (netconf_malformed_message(cbret, "rpc keyword expected")< 0)
goto done;
goto reply;
}
/* Populate incoming XML tree with yang */
/* Populate incoming XML tree with yang -
* should really have been dealt with by decode above
* maybe not necessary since it should be */
if (xml_spec_populate_rpc(h, x, yspec) < 0)
goto done;
if ((ret = xml_yang_validate_rpc(x)) < 0)
goto done;
if (ret == 0){
if (netconf_operation_failed(cbret, "application", "Validation failed")< 0)
if ((ret = xml_yang_validate_rpc(x, cbret)) < 0)
goto done;
if (ret == 0)
goto reply;
}
xe = NULL;
username = xml_find_value(x, "username");
while ((xe = xml_child_each(x, xe, CX_ELMNT)) != NULL) {
@ -1059,7 +1053,7 @@ from_client_msg(clicon_handle h,
}
else if (strcmp(rpc, "validate") == 0){
if ((db = netconf_db_find(xe, "source")) == NULL){
if (netconf_missing_element(cbret, "protocol", "<bad-element>source</bad-element>", NULL) < 0)
if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0)
goto done;
goto reply;
}

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
@ -81,78 +81,108 @@
* are if code comes via XML/NETCONF.
* @param[in] yspec Yang spec
* @param[in] td Transaction data
* @param[out] cbret Cligen buffer containing netconf error (if retval == 0)
* @retval -1 Error
* @retval 0 Validation failed (with cbret set)
* @retval 1 Validation OK
*/
static int
generic_validate(yang_spec *yspec,
transaction_data_t *td)
transaction_data_t *td,
cbuf *cbret)
{
int retval = -1;
cxobj *x1;
cxobj *x2;
yang_stmt *ys;
int i;
int ret;
/* All entries */
if (xml_apply(td->td_target, CX_ELMNT,
(xml_applyfn_t*)xml_yang_validate_all, NULL) < 0)
if ((ret = xml_yang_validate_all_top(td->td_target, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
/* changed entries */
for (i=0; i<td->td_clen; i++){
x1 = td->td_scvec[i]; /* source changed */
x2 = td->td_tcvec[i]; /* target changed */
if (xml_yang_validate_add(x2, NULL) < 0)
/* Should this be recursive? */
if ((ret = xml_yang_validate_add(x2, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
/* deleted entries */
for (i=0; i<td->td_dlen; i++){
x1 = td->td_dvec[i];
ys = xml_spec(x1);
if (ys && yang_mandatory(ys)){
clicon_err(OE_CFG, 0,"Removed mandatory variable: %s",
xml_name(x1));
if (ys && yang_mandatory(ys) && yang_config(ys)==0){
if (netconf_missing_element(cbret, "protocol", xml_name(x1), "Missing mandatory variable") < 0)
goto done;
goto fail;
}
}
/* added entries */
for (i=0; i<td->td_alen; i++){
x2 = td->td_avec[i];
if (xml_apply0(x2, CX_ELMNT,
(xml_applyfn_t*)xml_yang_validate_add, NULL) < 0)
if ((ret = xml_yang_validate_add(x2, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
retval = 0;
// ok:
retval = 1;
done:
return retval;
fail:
retval = 0;
goto done;
}
/*! Common code of candidate_validate and candidate_commit
* @param[in] h Clicon handle
* @param[in] candidate The candidate database. The wanted backend state
* @retval 0 OK
* @retval -1 Fatal error or validation fail
* @retval -1 Error - or validation failed (but cbret not set)
* @retval 0 Validation failed (with cbret set)
* @retval 1 Validation OK
* @note Need to differentiate between error and validation fail
* (only done for generic_validate)
*/
static int
validate_common(clicon_handle h,
char *candidate,
transaction_data_t *td)
transaction_data_t *td,
cbuf *cbret)
{
int retval = -1;
yang_spec *yspec;
int i;
cxobj *xn;
int ret;
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done;
}
/* 2. Parse xml trees */
/* 2. Parse xml trees
* This is the state we are going from */
if (xmldb_get(h, "running", "/", 1, &td->td_src) < 0)
goto done;
/* This is the state we are going to */
if (xmldb_get(h, candidate, "/", 1, &td->td_target) < 0)
goto done;
/* Validate the target state. It is not completely clear this should be done
* here. It is being made in generic_validate below.
* But xml_diff requires some basic validation, at least check that yang-specs
* have been assigned
*/
if ((ret = xml_yang_validate_all_top(td->td_target, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
/* 3. Compute differences */
if (xml_diff(yspec,
td->td_src,
@ -193,9 +223,12 @@ validate_common(clicon_handle h,
if (plugin_transaction_begin(h, td) < 0)
goto done;
/* 5. Make generic validation on all new or changed data. */
if (generic_validate(yspec, td) < 0)
/* 5. Make generic validation on all new or changed data.
Note this is only call that uses 3-values */
if ((ret = generic_validate(yspec, td, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
/* 6. Call plugin transaction validate callbacks */
if (plugin_transaction_validate(h, td) < 0)
@ -204,9 +237,12 @@ validate_common(clicon_handle h,
/* 7. Call plugin transaction complete callbacks */
if (plugin_transaction_complete(h, td) < 0)
goto done;
retval = 0;
retval = 1;
done:
return retval;
fail:
retval = 0;
goto done;
}
/*! Do a diff between candidate and running, then start a commit transaction
@ -216,57 +252,69 @@ validate_common(clicon_handle h,
* do something more drastic?
* @param[in] h Clicon handle
* @param[in] candidate A candidate database, not necessarily "candidate"
* @retval 0 OK
* @retval -1 Fatal error or validation fail
* @retval -1 Error - or validation failed
* @retval 0 Validation failed (with cbret set)
* @retval 1 Validation OK
* @note Need to differentiate between error and validation fail
* (only done for validate_common)
*/
int
candidate_commit(clicon_handle h,
char *candidate)
char *candidate,
cbuf *cbret)
{
int retval = -1;
transaction_data_t *td = NULL;
int ret;
/* 1. Start transaction */
if ((td = transaction_new()) == NULL)
goto done;
/* Common steps (with validate) */
if (validate_common(h, candidate, td) < 0)
/* Common steps (with validate). Load candidate and running and compute diffs
* Note this is only call that uses 3-values
*/
if ((ret = validate_common(h, candidate, td, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
/* 7. Call plugin transaction commit callbacks */
if (plugin_transaction_commit(h, td) < 0)
goto done;
/* Optionally write (potentially modified) tree back to candidate */
if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD"))
if (xmldb_put(h, candidate, OP_REPLACE, td->td_target, NULL) < 0)
if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")){
if ((ret = xmldb_put(h, candidate, OP_REPLACE, td->td_target, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
/* 8. Success: Copy candidate to running
*/
if (xmldb_copy(h, candidate, "running") < 0)
goto done;
/* 9. Call plugin transaction end callbacks */
plugin_transaction_end(h, td);
/* 8. Copy running back to candidate in case end functions updated running */
if (xmldb_copy(h, "running", candidate) < 0){
/* ignore errors or signal major setback ? */
clicon_log(LOG_NOTICE, "Error in rollback, trying to continue");
goto done;
}
retval = 0;
retval = 1;
done:
/* In case of failure, call plugin transaction termination callbacks */
if (retval < 0 && td)
/* In case of failure (or error), call plugin transaction termination callbacks */
if (retval < 1 && td)
plugin_transaction_abort(h, td);
if (td)
transaction_free(td);
return retval;
fail:
retval = 0;
goto done;
}
/*! Commit changes from candidate to running
@ -283,6 +331,7 @@ from_client_commit(clicon_handle h,
int retval = -1;
int piddb;
cbuf *cbx = NULL; /* Assist cbuf */
int ret;
/* Check if target locked by other client */
piddb = xmldb_islocked(h, "running");
@ -296,9 +345,10 @@ from_client_commit(clicon_handle h,
goto done;
goto ok;
}
if (candidate_commit(h, "candidate") < 0){ /* Assume validation fail, nofatal */
if ((ret = candidate_commit(h, "candidate", cbret)) < 0){ /* Assume validation fail, nofatal */
clicon_debug(1, "Commit candidate failed");
if (netconf_invalid_value(cbret, "protocol", clicon_err_reason)< 0)
if (ret < 0)
if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0)
goto done;
goto ok;
}
@ -367,6 +417,7 @@ from_client_validate(clicon_handle h,
{
int retval = -1;
transaction_data_t *td = NULL;
int ret;
if (strcmp(db, "candidate") != 0 && strcmp(db, "tmp") != 0){
if (netconf_invalid_value(cbret, "protocol", "No such database")< 0)
@ -379,17 +430,20 @@ from_client_validate(clicon_handle h,
if ((td = transaction_new()) == NULL)
goto done;
/* Common steps (with commit) */
if (validate_common(h, db, td) < 0){
if ((ret = validate_common(h, db, td, cbret)) < 1){
clicon_debug(1, "Validate %s failed", db);
/* XXX: candidate_validate should have proper error handling */
if (ret < 0){
if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0)
goto done;
}
goto ok;
}
/* Optionally write (potentially modified) tree back to candidate */
if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD"))
if (xmldb_put(h, "candidate", OP_REPLACE, td->td_target, NULL) < 0)
if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")){
if ((ret = xmldb_put(h, "candidate", OP_REPLACE, td->td_target, cbret)) < 0)
goto done;
goto ok;
}
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
ok:
retval = 0;

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
@ -43,6 +43,6 @@
int from_client_validate(clicon_handle h, char *db, cbuf *cbret);
int from_client_commit(clicon_handle h, int pid, cbuf *cbret);
int from_client_discard_changes(clicon_handle h, int pid, cbuf *cbret);
int candidate_commit(clicon_handle h, char *db);
int candidate_commit(clicon_handle h, char *db, cbuf *cbret);
#endif /* _BACKEND_COMMIT_H_ */

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
@ -73,7 +73,7 @@
#include "backend_handle.h"
/* Command line options to be passed to getopt(3) */
#define BACKEND_OPTS "hD:f:l:d:b:Fza:u:P:1s:c:g:y:x:" /* substitute s: for IRCc:r */
#define BACKEND_OPTS "hD:f:l:d:b:Fza:u:P:1s:c:g:y:x:o:"
#define BACKEND_LOGFILE "/usr/local/var/clixon_backend.log"
@ -154,7 +154,8 @@ usage(clicon_handle h,
"\t-g <group>\tClient membership required to this group (default: %s)\n"
"\t-y <file>\tLoad yang spec file (override yang main module)\n"
"\t-x <plugin>\tXMLDB plugin\n",
"\t-x <plugin>\tXMLDB plugin\n"
"\t-o \"<option>=<value>\"\tGive configuration option overriding config file (see clixon-config.yang)\n",
argv0,
plgdir ? plgdir : "none",
confsock ? confsock : "none",
@ -176,11 +177,15 @@ db_reset(clicon_handle h,
}
/*! Merge db1 into db2 without commit
* @retval -1 Error
* @retval 0 Validation failed (with cbret set)
* @retval 1 Validation OK
*/
static int
db_merge(clicon_handle h,
const char *db1,
const char *db2)
const char *db2,
cbuf *cbret)
{
int retval = -1;
cxobj *xt = NULL;
@ -189,9 +194,7 @@ db_merge(clicon_handle h,
if (xmldb_get(h, (char*)db1, NULL, 1, &xt) < 0)
goto done;
/* Merge xml into db2. Without commit */
if (xmldb_put(h, (char*)db2, OP_MERGE, xt, NULL) < 0)
goto done;
retval = 0;
retval = xmldb_put(h, (char*)db2, OP_MERGE, xt, cbret);
done:
if (xt)
xml_free(xt);
@ -266,7 +269,7 @@ nacm_load_external(clicon_handle h)
}
if ((yspec = yspec_new()) == NULL)
goto done;
if (yang_parse(h, NULL, "ietf-netconf-acm", NULL, yspec) < 0)
if (yang_spec_parse_module(h, "ietf-netconf-acm", NULL, yspec) < 0)
goto done;
fd = fileno(f);
/* Read configfile */
@ -288,18 +291,22 @@ nacm_load_external(clicon_handle h)
}
/*! Merge xml in filename into database
* @retval -1 Error
* @retval 0 Validation failed (with cbret set)
* @retval 1 Validation OK
*/
static int
load_extraxml(clicon_handle h,
char *filename,
const char *db)
const char *db,
cbuf *cbret)
{
int retval = -1;
cxobj *xt = NULL;
int fd = -1;
if (filename == NULL)
return 0;
return 1;
if ((fd = open(filename, O_RDONLY)) < 0){
clicon_err(OE_UNIX, errno, "open(%s)", filename);
goto done;
@ -310,9 +317,7 @@ load_extraxml(clicon_handle h,
if (xml_rootchild(xt, 0, &xt) < 0)
goto done;
/* Merge user reset state */
if (xmldb_put(h, (char*)db, OP_MERGE, xt, NULL) < 0)
goto done;
retval = 0;
retval = xmldb_put(h, (char*)db, OP_MERGE, xt, cbret);
done:
if (fd != -1)
close(fd);
@ -386,7 +391,12 @@ startup_mode_running(clicon_handle h,
char *extraxml_file)
{
int retval = -1;
cbuf *cbret = NULL;
if ((cbret = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
/* Stash original running to candidate for later commit */
if (xmldb_copy(h, "running", "candidate") < 0)
goto done;
@ -399,35 +409,47 @@ startup_mode_running(clicon_handle h,
/* Application may define extra xml in its reset function*/
if (clixon_plugin_reset(h, "tmp") < 0)
goto done;
/* XXX Kludge to low-level functions to search for xml in all yang modules */
_CLICON_XML_NS_STRICT = 0;
/* Get application extra xml from file */
if (load_extraxml(h, extraxml_file, "tmp") < 0)
goto done;
if (load_extraxml(h, extraxml_file, "tmp", cbret) < 1)
goto fail;
/* Clear running db */
if (db_reset(h, "running") < 0)
goto done;
/* Commit original running. Assume -1 is validate fail */
if (candidate_commit(h, "candidate") < 0){
if (candidate_commit(h, "candidate", cbret) < 1)
goto fail;
/* Merge user reset state and extra xml file (no commit) */
if (db_merge(h, "tmp", "running", cbret) < 1)
goto fail;
retval = 0;
done:
/* XXX Kludge to low-level functions to search for xml in all yang modules */
_CLICON_XML_NS_STRICT = clicon_option_bool(h, "CLICON_XML_NS_STRICT");
if (cbret)
cbuf_free(cbret);
if (xmldb_delete(h, "tmp") < 0)
goto done;
return retval;
fail:
/* (1) We cannot differentiate between fatal errors and validation
* failures
* (2) If fatal error, we should exit
* (3) If validation fails we cannot continue. How could we?
* (4) Need to restore the running db since we destroyed it above
*/
clicon_log(LOG_NOTICE, "%s: Commit of saved running failed, exiting.", __FUNCTION__);
if (strlen(cbuf_get(cbret)))
clicon_log(LOG_NOTICE, "%s: Commit of running failed, exiting: %s.",
__FUNCTION__, cbuf_get(cbret));
else
clicon_log(LOG_NOTICE, "%s: Commit of running failed, exiting: %s.",
__FUNCTION__, clicon_err_reason);
/* Reinstate original */
if (xmldb_copy(h, "candidate", "running") < 0)
goto done;
goto done;
}
/* Merge user reset state and extra xml file (no commit) */
if (db_merge(h, "tmp", "running") < 0)
goto done;
retval = 0;
done:
if (xmldb_delete(h, "tmp") < 0)
goto done;
return retval;
}
/*! Clixon startup startup mode: Commit startup configuration into running state
@ -455,7 +477,13 @@ startup_mode_startup(clicon_handle h,
char *extraxml_file)
{
int retval = -1;
cbuf *cbret = NULL;
/* Create return buffer for netconf xml errors */
if ((cbret = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
/* Stash original running to backup */
if (xmldb_copy(h, "running", "backup") < 0)
goto done;
@ -472,33 +500,46 @@ startup_mode_startup(clicon_handle h,
/* Application may define extra xml in its reset function*/
if (clixon_plugin_reset(h, "tmp") < 0)
goto done;
/* XXX Kludge to low-level functions to search for xml in all yang modules */
_CLICON_XML_NS_STRICT = 0;
/* Get application extra xml from file */
if (load_extraxml(h, extraxml_file, "tmp") < 0)
goto done;
if (load_extraxml(h, extraxml_file, "tmp", cbret) < 1)
goto fail;
/* Clear running db */
if (db_reset(h, "running") < 0)
goto done;
/* Commit startup */
if (candidate_commit(h, "startup") < 0){ /* diff */
/* We cannot differentiate between fatal errors and validation
* failures
* In both cases we copy back the original running and quit
*/
clicon_log(LOG_NOTICE, "%s: Commit of startup failed, exiting.", __FUNCTION__);
if (xmldb_copy(h, "backup", "running") < 0)
goto done;
goto done;
}
if (candidate_commit(h, "startup", cbret) < 1) /* diff */
goto fail;
/* Merge user reset state and extra xml file (no commit) */
if (db_merge(h, "tmp", "running") < 0)
goto done;
if (db_merge(h, "tmp", "running", cbret) < 1)
goto fail;
retval = 0;
done:
/* XXX Kludge to low-level functions to search for xml in all yang modules */
_CLICON_XML_NS_STRICT = clicon_option_bool(h, "CLICON_XML_NS_STRICT");
if (cbret)
cbuf_free(cbret);
if (xmldb_delete(h, "backup") < 0)
goto done;
if (xmldb_delete(h, "tmp") < 0)
goto done;
return retval;
fail:
/* We cannot differentiate between fatal errors and validation
* failures
* In both cases we copy back the original running and quit
*/
if (strlen(cbuf_get(cbret)))
clicon_log(LOG_NOTICE, "%s: Commit of startup failed, exiting: %s.",
__FUNCTION__, cbuf_get(cbret));
else
clicon_log(LOG_NOTICE, "%s: Commit of startup failed, exiting: %s.",
__FUNCTION__, clicon_err_reason);
if (xmldb_copy(h, "backup", "running") < 0)
goto done;
goto done;
}
int
@ -523,7 +564,6 @@ main(int argc,
int sockfamily;
char *xmldb_plugin;
int xml_cache;
int xml_pretty;
char *xml_format;
char *nacm_mode;
int logdst = CLICON_LOG_SYSLOG|CLICON_LOG_STDERR;
@ -662,6 +702,15 @@ main(int argc,
clicon_option_str_set(h, "CLICON_XMLDB_PLUGIN", optarg);
break;
}
case 'o':{ /* Configuration option */
char *val;
if ((val = index(optarg, '=')) == NULL)
usage(h, argv0);
*val++ = '\0';
if (clicon_option_add(h, optarg, val) < 0)
goto done;
break;
}
default:
usage(h, argv[0]);
break;
@ -766,6 +815,9 @@ main(int argc,
if ((str = clicon_yang_main_dir(h)) != NULL)
if (yang_spec_load_dir(h, str, yspec) < 0)
goto done;
/* Load clixon lib yang module */
if (yang_spec_parse_module(h, "clixon-lib", NULL, yspec) < 0)
goto done;
/* Load yang module library, RFC7895 */
if (yang_modules_init(h) < 0)
goto done;
@ -778,7 +830,7 @@ main(int argc,
goto done;
/* Load yang Netconf stream discovery */
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277") &&
yang_spec_parse_module(h, "ietf-netconf-notification", NULL, yspec)< 0)
yang_spec_parse_module(h, "clixon-rfc5277", NULL, yspec)< 0)
goto done;
/* Set options: database dir and yangspec (could be hidden in connect?)*/
if (xmldb_setopt(h, "dbdir", clicon_xmldb_dir(h)) < 0)
@ -791,8 +843,7 @@ main(int argc,
if ((xml_format = clicon_option_str(h, "CLICON_XMLDB_FORMAT")) >= 0)
if (xmldb_setopt(h, "format", (void*)xml_format) < 0)
goto done;
if ((xml_pretty = clicon_option_bool(h, "CLICON_XMLDB_PRETTY")) >= 0)
if (xmldb_setopt(h, "pretty", (void*)(intptr_t)xml_pretty) < 0)
if (xmldb_setopt(h, "pretty", (void*)(intptr_t)clicon_option_bool(h, "CLICON_XMLDB_PRETTY")) < 0)
goto done;
/* Startup mode needs to be defined, */
startup_mode = clicon_startup_mode(h);

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
@ -117,7 +117,7 @@ clixon_plugin_reset(clicon_handle h,
* @param[in,out] xtop State XML tree is merged with existing tree.
* @retval -1 Error
* @retval 0 OK
* @retval 1 Statedata callback failed
* @retval 1 Statedata callback failed (xret set with netconf-error)
* @note xtop can be replaced
*/
int

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -1,7 +1,7 @@
#
# ***** BEGIN LICENSE BLOCK *****
#
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
#
# This file is part of CLIXON
#

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
@ -236,7 +236,7 @@ cli_dbxml(clicon_handle h,
if ((xtop = xml_new("config", NULL, NULL)) == NULL)
goto done;
xbot = xtop;
if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0)
if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 1)
goto done;
if ((xa = xml_new("operation", xbot, NULL)) == NULL)
goto done;
@ -293,7 +293,7 @@ cli_set(clicon_handle h,
cvec *cvv,
cvec *argv)
{
int retval = 1;
int retval = -1;
if (cli_dbxml(h, cvv, argv, OP_REPLACE) < 0)
goto done;
@ -503,7 +503,7 @@ cli_start_shell(clicon_handle h,
{
char *cmd;
struct passwd *pw;
int retval;
int retval = -1;
char bcmd[128];
cg_var *cv1 = cvec_i(vars, 1);
@ -512,42 +512,43 @@ cli_start_shell(clicon_handle h,
if ((pw = getpwuid(getuid())) == NULL){
fprintf(stderr, "%s: getpwuid: %s\n",
__FUNCTION__, strerror(errno));
return -1;
goto done;
}
if (chdir(pw->pw_dir) < 0){
fprintf(stderr, "%s: chdir(%s): %s\n",
__FUNCTION__, pw->pw_dir, strerror(errno));
endpwent();
return -1;
goto done;
}
endpwent();
cli_signal_flush(h);
cli_signal_unblock(h);
if (cmd){
snprintf(bcmd, 128, "bash -l -c \"%s\"", cmd);
if ((retval = system(bcmd)) < 0){
if (system(bcmd) < 0){
cli_signal_block(h);
fprintf(stderr, "%s: system(bash -c): %s\n",
__FUNCTION__, strerror(errno));
return -1;
goto done;
}
}
else
if ((retval = system("bash -l")) < 0){
if (system("bash -l") < 0){
cli_signal_block(h);
fprintf(stderr, "%s: system(bash): %s\n",
__FUNCTION__, strerror(errno));
return -1;
goto done;
}
cli_signal_block(h);
#if 0 /* Allow errcodes from bash */
if (retval != 0){
fprintf(stderr, "%s: system(%s) code=%d\n", __FUNCTION__, cmd, retval);
return -1;
goto done;
}
#endif
return 0;
retval = 0;
done:
return retval;
}
/*! Generic quit callback
@ -958,7 +959,7 @@ cli_notification_cb(int s,
event_unreg_fd(s, cli_notification_cb);
goto done;
}
if (clicon_msg_decode(reply, &xt) < 0)
if (clicon_msg_decode(reply, NULL, &xt) < 0) /* XXX pass yang_spec */
goto done;
if ((xe = xpath_first(xt, "//event")) != NULL){
x = NULL;

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
@ -169,28 +169,6 @@ static int yang2cli_stmt(clicon_handle h, yang_stmt *ys, cbuf *cb,
static int yang2cli_var_union(clicon_handle h, yang_stmt *ys, char *origtype,
yang_stmt *ytype, cbuf *cb, char *helptext);
/*! Patched maxstring to account for DEC64 types
* @note kludge to fix overflow error -> Fix the original error in cvtype_max2str
* by adding a fraction_digits argument.
*/
static char *
cvtype_max2str_dup2(enum cv_type type,
int fraction_digits)
{
int len;
char *str;
if (type!=CGV_DEC64 || fraction_digits==0)
return cvtype_max2str_dup(type);
if ((len = cvtype_max2str(type, NULL, 0)) < 0)
return NULL;
if ((str = (char *)malloc(len+1)) == NULL)
return NULL;
memset(str, '\0', len+1);
len = snprintf(str, len+1, "%" PRId64 ".0", (INT64_MAX/((int)pow(10,fraction_digits))));
return str;
}
/*! Generate CLI code for Yang leaf statement to CLIgen variable of specific type
* Check for completion (of already existent values), ranges (eg range[min:max]) and
* patterns, (eg regexp:"[0.9]*").
@ -208,18 +186,17 @@ yang2cli_var_sub(clicon_handle h,
char *helptext,
enum cv_type cvtype,
int options,
cg_var *mincv,
cg_var *maxcv,
cvec *cvv,
char *pattern,
uint8_t fraction_digits
)
{
int retval = -1;
char *type;
char *r;
yang_stmt *yi = NULL;
int i = 0;
char *cvtypestr;
cg_var *cv;
if (cvtype == CGV_VOID){
retval = 0;
@ -276,47 +253,39 @@ yang2cli_var_sub(clicon_handle h,
if (options & YANG_OPTIONS_FRACTION_DIGITS)
cprintf(cb, " fraction-digits:%u", fraction_digits);
if (options & (YANG_OPTIONS_RANGE|YANG_OPTIONS_LENGTH)){
assert(mincv || maxcv);
/* Loop through range_min and range_min..rang_max */
i = 0;
while (i<cvec_len(cvv)){
// if (i)
// clicon_log(LOG_NOTICE, "%s: Warning %s has more ranges, ignoring", __FUNCTION__, ys->ys_argument);
cv = cvec_i(cvv, i++);
if (strcmp(cv_name_get(cv),"range_min") == 0){
cprintf(cb, " %s[", (options&YANG_OPTIONS_RANGE)?"range":"length");
if (mincv){
if ((r = cv2str_dup(mincv)) == NULL){
clicon_err(OE_UNIX, errno, "cv2str_dup");
cv2cbuf(cv, cb);
cprintf(cb,":");
/* probe next */
if (i<cvec_len(cvv) &&
(cv = cvec_i(cvv, i)) != NULL &&
strcmp(cv_name_get(cv),"range_max") == 0){
i++;
cv2cbuf(cv, cb);
}
else /* If not, it is a single number range [x:x]*/
cv2cbuf(cv, cb);
cprintf(cb,"]");
}
}
}
if (options & YANG_OPTIONS_PATTERN){
char *posix = NULL;
if (regexp_xsd2posix(pattern, &posix) < 0)
goto done;
cprintf(cb, " regexp:\"%s\"", posix);
if (posix)
free(posix);
}
cprintf(cb, "%s:", r);
free(r);
r = NULL;
}
if (maxcv != NULL){
if ((r = cv2str_dup(maxcv)) == NULL){
clicon_err(OE_UNIX, errno, "cv2str_dup");
goto done;
}
}
else{ /* Cligen does not have 'max' keyword in range so need to find actual
max value of type if yang range expression is 0..max
*/
if (cvtype==CGV_STRING){
if ((r = malloc(512)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
snprintf(r, 512, "%d", MAXPATHLEN);
}
else {
if ((r = cvtype_max2str_dup2(cvtype, fraction_digits)) == NULL){
clicon_err(OE_UNIX, errno, "cvtype_max2str");
goto done;
}
}
}
cprintf(cb, "%s]", r); /* range */
free(r);
r = NULL;
}
if (options & YANG_OPTIONS_PATTERN)
cprintf(cb, " regexp:\"%s\"", pattern);
cprintf(cb, ">");
if (helptext)
cprintf(cb, "(\"%s\")", helptext);
@ -347,8 +316,7 @@ yang2cli_var_union_one(clicon_handle h,
{
int retval = -1;
int options = 0;
cg_var *mincv = NULL;
cg_var *maxcv = NULL;
cvec *cvv = NULL;
char *pattern = NULL;
uint8_t fraction_digits = 0;
enum cv_type cvtype;
@ -356,9 +324,9 @@ yang2cli_var_union_one(clicon_handle h,
char *restype;
/* Resolve the sub-union type to a resolved type */
if (yang_type_resolve(ys, ytsub, /* in */
if (yang_type_resolve(ys, ys, ytsub, /* in */
&ytype, &options, /* resolved type */
&mincv, &maxcv, &pattern, &fraction_digits) < 0)
&cvv, &pattern, &fraction_digits) < 0)
goto done;
restype = ytype?ytype->ys_argument:NULL;
@ -367,10 +335,10 @@ yang2cli_var_union_one(clicon_handle h,
goto done;
}
else {
if (clicon_type2cv(origtype, restype, &cvtype) < 0)
if (clicon_type2cv(origtype, restype, ys, &cvtype) < 0)
goto done;
if ((retval = yang2cli_var_sub(h, ys, ytype, cb, helptext, cvtype,
options, mincv, maxcv, pattern, fraction_digits)) < 0)
options, cvv, pattern, fraction_digits)) < 0)
goto done;
}
retval = 0;
@ -438,8 +406,7 @@ yang2cli_var(clicon_handle h,
char *origtype;
yang_stmt *yrestype; /* resolved type */
char *restype; /* resolved type */
cg_var *mincv = NULL;
cg_var *maxcv = NULL;
cvec *cvv = NULL;
char *pattern = NULL;
uint8_t fraction_digits = 0;
enum cv_type cvtype;
@ -448,7 +415,7 @@ yang2cli_var(clicon_handle h,
char *type;
if (yang_type_get(ys, &origtype, &yrestype,
&options, &mincv, &maxcv, &pattern, &fraction_digits) < 0)
&options, &cvv, &pattern, &fraction_digits) < 0)
goto done;
restype = yrestype?yrestype->ys_argument:NULL;
@ -456,7 +423,7 @@ yang2cli_var(clicon_handle h,
retval = 0;
goto done;
}
if (clicon_type2cv(origtype, restype, &cvtype) < 0)
if (clicon_type2cv(origtype, restype, ys, &cvtype) < 0)
goto done;
/* Note restype can be NULL here for example with unresolved hardcoded uuid */
if (restype && strcmp(restype, "union") == 0){
@ -485,7 +452,7 @@ yang2cli_var(clicon_handle h,
if (completionp)
cprintf(cb, "(");
if ((retval = yang2cli_var_sub(h, ys, yrestype, cb, helptext, cvtype,
options, mincv, maxcv, pattern, fraction_digits)) < 0)
options, cvv, pattern, fraction_digits)) < 0)
goto done;
if (completionp){
if (cli_expand_var_generate(h, ys, cvtype, cb,
@ -751,11 +718,6 @@ yang2cli_stmt(clicon_handle h,
if (yang_config(ys)){
switch (ys->ys_keyword){
case Y_GROUPING:
case Y_RPC:
case Y_AUGMENT:
return 0;
break;
case Y_CONTAINER:
if (yang2cli_container(h, ys, cbuf, gt, level) < 0)
goto done;
@ -773,19 +735,21 @@ yang2cli_stmt(clicon_handle h,
if (yang2cli_leaf(h, ys, cbuf, gt, level, 1) < 0)
goto done;
break;
default:
case Y_CASE:
case Y_SUBMODULE:
case Y_MODULE:
for (i=0; i<ys->ys_len; i++)
if ((yc = ys->ys_stmt[i]) != NULL)
if (yang2cli_stmt(h, yc, cbuf, gt, level+1) < 0)
goto done;
break;
default: /* skip */
break;
}
}
retval = 0;
done:
return retval;
}
/*! Generate CLI code for Yang specification
@ -814,13 +778,13 @@ yang2cli(clicon_handle h,
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
/* Traverse YANG specification: loop through statements */
/* Traverse YANG, loop through all modules and generate CLI */
for (i=0; i<yspec->yp_len; i++)
if ((ymod = yspec->yp_stmt[i]) != NULL){
if (yang2cli_stmt(h, ymod, cbuf, gt, 0) < 0)
goto done;
}
clicon_debug(0, "%s: buf\n%s\n", __FUNCTION__, cbuf_get(cbuf));
clicon_debug(2, "%s: buf\n%s\n", __FUNCTION__, cbuf_get(cbuf));
/* Parse the buffer using cligen parser. XXX why this?*/
if ((globals = cvec_new(0)) == NULL)
goto done;

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
@ -71,7 +71,7 @@
#include "cli_handle.h"
/* Command line options to be passed to getopt(3) */
#define CLI_OPTS "hD:f:xl:F:1a:u:d:m:qpGLy:c:U:"
#define CLI_OPTS "hD:f:xl:F:1a:u:d:m:qpGLy:c:U:o:"
#define CLI_LOGFILE "/tmp/clixon_cli.log"
@ -227,7 +227,8 @@ usage(clicon_handle h,
"\t-l <s|e|o|f<file>> \tLog on (s)yslog, std(e)rr, std(o)ut or (f)ile (stderr is default)\n"
"\t-y <file>\tOverride yang spec file (dont include .yang suffix)\n"
"\t-c <file>\tSpecify cli spec file.\n"
"\t-U <user>\tOver-ride unix user with a pseudo user for NACM.\n",
"\t-U <user>\tOver-ride unix user with a pseudo user for NACM.\n"
"\t-o \"<option>=<value>\"\tGive configuration option overriding config file (see clixon-config.yang)\n",
argv0,
plgdir ? plgdir : "none"
);
@ -403,6 +404,15 @@ main(int argc, char **argv)
if (clicon_username_set(h, optarg) < 0)
goto done;
break;
case 'o':{ /* Configuration option */
char *val;
if ((val = index(optarg, '=')) == NULL)
usage(h, argv0);
*val++ = '\0';
if (clicon_option_add(h, optarg, val) < 0)
goto done;
break;
}
default:
usage(h, argv[0]);
break;
@ -446,6 +456,9 @@ main(int argc, char **argv)
if (yang_spec_load_dir(h, str, yspec) < 0)
goto done;
}
/* Load clixon lib yang module */
if (yang_spec_parse_module(h, "clixon-lib", NULL, yspec) < 0)
goto done;
/* Load yang module library, RFC7895 */
if (yang_modules_init(h) < 0)

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
@ -171,7 +171,7 @@ expand_dbvar(void *h,
/* This is primarily to get "y",
* xpath2xml would have worked!!
*/
if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0)
if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 1)
goto done;
if (y==NULL)
goto ok;
@ -443,6 +443,7 @@ cli_show_config(clicon_handle h,
cxobj *xc;
cxobj *xerr;
enum genmodel_type gt;
yang_spec *yspec;
if (cvec_len(argv) != 3 && cvec_len(argv) != 4){
clicon_err(OE_PLUGIN, 0, "Got %d arguments. Expected: <dbname>,<format>,<xpath>[,<attr>]", cvec_len(argv));
@ -496,6 +497,13 @@ cli_show_config(clicon_handle h,
clicon_rpc_generate_error("Get configuration", xerr);
goto done;
}
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done;
}
/* Some formats (eg cli) require yang */
if (xml_apply(xt, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
/* Print configuration according to format */
switch (format){
case FORMAT_XML:
@ -516,7 +524,7 @@ cli_show_config(clicon_handle h,
if ((gt = clicon_cli_genmodel_type(h)) == GT_ERR)
goto done;
xc = NULL; /* Dont print xt itself */
while ((xc = xml_child_each(xt, xc, -1)) != NULL)
while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL)
xml2cli(stdout, xc, NULL, gt); /* cli syntax */
break;
case FORMAT_NETCONF:

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -1,7 +1,7 @@
#
# ***** BEGIN LICENSE BLOCK *****
#
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
#
# This file is part of CLIXON
#

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
@ -44,6 +44,7 @@
* (Duplicated. Also in netconf_*.h)
*/
int netconf_output(int s, cbuf *xf, char *msg);
int netconf_output_encap(int s, cbuf *xf, char *msg);
int netconf_xpath(cxobj *xsearch,
cxobj *xfilter,

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
@ -182,9 +182,9 @@ netconf_get_target(cxobj *xn,
* @param[in] s
* @param[in] cb Cligen buffer that contains the XML message
* @param[in] msg Only for debug
* @note Assumes "cb" contains valid XML, ie encoding is correct. This is done
* if it is output by a xml render routine (xml_print et al), but NOT
* otherwise.
* @retval 0 OK
* @retval -1 Error
* @see netconf_output_encap for function with encapsulation
*/
int
netconf_output(int s,
@ -216,3 +216,34 @@ netconf_output(int s,
return retval;
}
/*! Encapsulate and send outgoing netconf packet as cbuf on socket
* @param[in] s
* @param[in] cb Cligen buffer that contains the XML message
* @param[in] msg Only for debug
* @retval 0 OK
* @retval -1 Error
* @note Assumes "cb" contains valid XML
* @see netconf_output without encapsulation
*/
int
netconf_output_encap(int s,
cbuf *cb,
char *msg)
{
int retval = -1;
cbuf *cb1 = NULL;
if ((cb1 = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
add_preamble(cb1);
cprintf(cb1, "%s", cbuf_get(cb));
add_postamble(cb1);
retval = netconf_output(s, cb1, msg);
done:
if (cb1)
cbuf_free(cb1);
return retval;
}

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
@ -75,5 +75,6 @@ int add_error_preamble(cbuf *xf, char *reason);
char *netconf_get_target(cxobj *xn, char *path);
int add_error_postamble(cbuf *xf);
int netconf_output(int s, cbuf *xf, char *msg);
int netconf_output_encap(int s, cbuf *cb, char *msg);
#endif /* _NETCONF_LIB_H_ */

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
@ -71,7 +71,7 @@
#include "netconf_rpc.h"
/* Command line options to be passed to getopt(3) */
#define NETCONF_OPTS "hD:f:l:qa:u:d:y:U:t:"
#define NETCONF_OPTS "hD:f:l:qa:u:d:y:U:t:o:"
#define NETCONF_LOGFILE "/tmp/clixon_netconf.log"
@ -80,9 +80,10 @@
* @param[in] cb Packet buffer
*/
static int
process_incoming_packet(clicon_handle h,
netconf_input_packet(clicon_handle h,
cbuf *cb)
{
int retval = -1;
char *str;
char *str0;
cxobj *xreq = NULL; /* Request (in) */
@ -92,9 +93,12 @@ process_incoming_packet(clicon_handle h,
cxobj *xrpc;
cxobj *xc;
yang_spec *yspec;
int ret;
cxobj *xa;
cxobj *xa2;
clicon_debug(1, "RECV");
clicon_debug(2, "%s: RCV: \"%s\"", __FUNCTION__, cbuf_get(cb));
clicon_debug(1, "%s", __FUNCTION__);
clicon_debug(2, "%s: \"%s\"", __FUNCTION__, cbuf_get(cb));
if ((cbret = cbuf_new()) == NULL){
clicon_err(LOG_ERR, errno, "cbuf_new");
goto done;
@ -108,22 +112,21 @@ process_incoming_packet(clicon_handle h,
/* Parse incoming XML message */
if (xml_parse_string(str, yspec, &xreq) < 0){
free(str0);
if (netconf_operation_failed(cbret, "rpc", "internal error")< 0)
if (netconf_operation_failed(cbret, "rpc", clicon_err_reason)< 0)
goto done;
netconf_output(1, cbret, "rpc-error");
netconf_output_encap(1, cbret, "rpc-error");
goto done;
}
free(str0);
if ((xrpc=xpath_first(xreq, "//rpc")) != NULL){
int ret;
isrpc++;
if ((ret = xml_yang_validate_rpc(xrpc)) < 0)
if (xml_spec_populate_rpc(h, xrpc, yspec) < 0)
goto done;
if ((ret = xml_yang_validate_rpc(xrpc, cbret)) < 0)
goto done;
if (ret == 0){
if (netconf_operation_failed(cbret, "application", "Validation failed")< 0)
goto done;
netconf_output(1, cbret, "rpc-error");
goto done;
netconf_output_encap(1, cbret, "rpc-error");
goto ok;
}
}
else
@ -142,8 +145,13 @@ process_incoming_packet(clicon_handle h,
goto done;
}
else{ /* there is a return message in xret */
cxobj *xa, *xa2;
assert(xret);
if (xret == NULL){
if (netconf_operation_failed(cbret, "rpc", "Internal error: no xml return")< 0)
goto done;
netconf_output_encap(1, cbret, "rpc-error");
goto done;
}
if ((xc = xml_child_i(xret,0))!=NULL){
xa=NULL;
/* Copy message-id attribute from incoming to reply.
@ -158,16 +166,15 @@ process_incoming_packet(clicon_handle h,
if (xml_addsub(xc, xa2) < 0)
goto done;
}
add_preamble(cbret);
clicon_xml2cbuf(cbret, xml_child_i(xret,0), 0, 0);
add_postamble(cbret);
if (netconf_output(1, cbret, "rpc-reply") < 0){
if (netconf_output_encap(1, cbret, "rpc-reply") < 0){
cbuf_free(cbret);
goto done;
}
}
}
ok:
retval = 0;
done:
if (xreq)
xml_free(xreq);
@ -175,7 +182,7 @@ process_incoming_packet(clicon_handle h,
xml_free(xret);
if (cbret)
cbuf_free(cbret);
return 0;
return retval;
}
/*! Get netconf message: detect end-of-msg
@ -228,8 +235,8 @@ netconf_input_cb(int s,
/* OK, we have an xml string from a client */
/* Remove trailer */
*(((char*)cbuf_get(cb)) + cbuf_len(cb) - strlen("]]>]]>")) = '\0';
if (process_incoming_packet(h, cb) < 0)
goto done;
if (netconf_input_packet(h, cb) < 0)
; //goto done; // ignore errors
if (cc_closed)
break;
cbuf_reset(cb);
@ -326,7 +333,8 @@ usage(clicon_handle h,
"\t-y <file>\tLoad yang spec file (override yang main module)\n"
"\t-U <user>\tOver-ride unix user with a pseudo user for NACM.\n"
"\t-t <sec>\tTimeout in seconds. Quit after this time.\n",
"\t-t <sec>\tTimeout in seconds. Quit after this time.\n"
"\t-o \"<option>=<value>\"\tGive configuration option overriding config file (see clixon-config.yang)\n",
argv0,
clicon_netconf_dir(h)
);
@ -439,7 +447,15 @@ main(int argc,
case 't': /* timeout in seconds */
tv.tv_sec = atoi(optarg);
break;
case 'o':{ /* Configuration option */
char *val;
if ((val = index(optarg, '=')) == NULL)
usage(h, argv0);
*val++ = '\0';
if (clicon_option_add(h, optarg, val) < 0)
goto done;
break;
}
default:
usage(h, argv[0]);
break;
@ -468,7 +484,9 @@ main(int argc,
if (yang_spec_load_dir(h, str, yspec) < 0)
goto done;
}
/* Load clixon lib yang module */
if (yang_spec_parse_module(h, "clixon-lib", NULL, yspec) < 0)
goto done;
/* Load yang module library, RFC7895 */
if (yang_modules_init(h) < 0)
goto done;

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
@ -56,6 +56,7 @@
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <syslog.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/param.h>
@ -134,20 +135,10 @@ netconf_get_config(clicon_handle h,
{
cxobj *xfilter; /* filter */
int retval = -1;
char *source;
char *ftype = NULL;
cxobj *xfilterconf;
cxobj *xconf;
if ((source = netconf_get_target(xn, "source")) == NULL){
xml_parse_va(xret, NULL, "<rpc-reply><rpc-error>"
"<error-tag>missing-element</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-info><bad-element>source</bad-element></error-info>"
"</rpc-error></rpc-reply>");
goto ok;
}
/* ie <filter>...</filter> */
if ((xfilter = xpath_first(xn, "filter")) != NULL)
ftype = xml_find_value(xfilter, "type");
@ -186,7 +177,6 @@ netconf_get_config(clicon_handle h,
"<error-info>type</error-info>"
"</rpc-error></rpc-reply>");
}
ok: /* netconf error is not fatal */
retval = 0;
done:
return retval;
@ -222,11 +212,9 @@ get_edit_opts(cxobj *xn,
if ((optstr = xml_body(x)) != NULL){
if (strcmp(optstr, "test-then-set") == 0)
*testopt = TEST_THEN_SET;
else
if (strcmp(optstr, "set") == 0)
else if (strcmp(optstr, "set") == 0)
*testopt = SET;
else
if (strcmp(optstr, "test-only") == 0)
else if (strcmp(optstr, "test-only") == 0)
*testopt = TEST_ONLY;
else
goto parerr;
@ -310,53 +298,18 @@ netconf_edit_config(clicon_handle h,
{
int retval = -1;
int optret;
enum operation_type operation = OP_MERGE;
enum test_option testopt = TEST_THEN_SET;/* only supports this */
enum error_option erropt = STOP_ON_ERROR; /* only supports this */
cxobj *xc; /* config */
cxobj *x;
cxobj *xfilter;
char *ftype = NULL;
char *target; /* db */
/* must have target, and it should be candidate */
if ((target = netconf_get_target(xn, "target")) == NULL ||
strcmp(target, "candidate")){
xml_parse_va(xret, NULL, "<rpc-reply><rpc-error>"
"<error-tag>missing-element</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-info><bad-element>target</bad-element></error-info>"
"</rpc-error></rpc-reply>");
goto ok;
}
/* CLICON addition, eg <filter type="restconf" select=<api-path> /> */
if ((xfilter = xpath_first(xn, "filter")) != NULL) {
if ((ftype = xml_find_value(xfilter, "type")) != NULL)
if (strcmp(ftype,"restconf")){
xml_parse_va(xret, NULL, "<rpc-reply><rpc-error>"
"<error-tag>invalid-value</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"</rpc-error></rpc-reply>");
goto ok;
}
}
if ((x = xpath_first(xn, "default-operation")) != NULL){
if (xml_operation(xml_body(x), &operation) < 0){
xml_parse_va(xret, NULL, "<rpc-reply><rpc-error>"
"<error-tag>invalid-value</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"</rpc-error></rpc-reply>");
goto ok;
}
}
if ((optret = get_edit_opts(xn, &testopt, &erropt, xret)) < 0)
goto done;
if (optret == 0) /* error in opt parameters */
goto ok;
/* not supported opts */
/* These constraints are clixon-specific since :validate should
* support all testopts, and erropts should be supported
* And therefore extends the validation
* (implement the features before removing these checks)
*/
if (testopt!=TEST_THEN_SET || erropt!=STOP_ON_ERROR){
xml_parse_va(xret, NULL, "<rpc-reply><rpc-error>"
"<error-tag>operation-not-supported</error-tag>"
@ -365,66 +318,6 @@ netconf_edit_config(clicon_handle h,
"</rpc-error></rpc-reply>");
goto ok;
}
/* operation is OP_REPLACE, OP_MERGE, or OP_NONE pass all to backend */
if ((xc = xpath_first(xn, "config")) != NULL){
#if 0
/* application-specific code registers 'config' */
if ((ret = netconf_plugin_callbacks(h, xc, xret)) < 0){
goto ok;
}
#endif
if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0)
goto done;
}
ok:
retval = 0;
done:
return retval;
}
/*! Netconf copy configuration
<copy-config>
<target>
<candidate/>
</target>
<source>
<url>
<!- - location specifier for file containing the new configuration - ->
</url>
</source>
<copy-config>
* @param[in] h clicon handle
* @param[in] xn Sub-tree (under xorig) at <rpc>...</rpc> level.
* @param[out] xret Return XML, error or OK
*/
static int
netconf_copy_config(clicon_handle h,
cxobj *xn,
cxobj **xret)
{
int retval = -1;
char *source;
char *target; /* filenames */
if ((source = netconf_get_target(xn, "source")) == NULL){
xml_parse_va(xret, NULL, "<rpc-reply><rpc-error>"
"<error-tag>missing-element</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-info><bad-element>source</bad-element></error-info>"
"</rpc-error></rpc-reply>");
goto ok;
}
if ((target = netconf_get_target(xn, "target")) == NULL){
xml_parse_va(xret, NULL, "<rpc-reply><rpc-error>"
"<error-tag>missing-element</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-info><bad-element>target</bad-element></error-info>"
"</rpc-error></rpc-reply>");
goto ok;
}
if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0)
goto done;
ok:
@ -433,99 +326,6 @@ netconf_copy_config(clicon_handle h,
return retval;
}
/*! Delete configuration
<delete-config>
<target>
<candidate/>
</target>
</delete-config>
Delete a configuration datastore. The <running>
configuration datastore cannot be deleted.
* @param[in] h clicon handle
* @param[in] xn Sub-tree (under xorig) at <rpc>...</rpc> level.
* @param[out] xret Return XML, error or OK
*/
static int
netconf_delete_config(clicon_handle h,
cxobj *xn,
cxobj **xret)
{
char *target; /* filenames */
int retval = -1;
if ((target = netconf_get_target(xn, "target")) == NULL ||
strcmp(target, "running")==0){
xml_parse_va(xret, NULL, "<rpc-reply><rpc-error>"
"<error-tag>missing-element</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-info><bad-element>target</bad-element></error-info>"
"</rpc-error></rpc-reply>");
goto ok;
}
if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0)
goto done;
ok:
retval = 0;
done:
return retval;
}
/*! Lock a database
<lock>
<target>
<candidate/>
</target>
</lock>
* @param[in] h clicon handle
* @param[in] xn Sub-tree (under xorig) at <rpc>...</rpc> level.
* @param[out] xret Return XML, error or OK
*/
static int
netconf_lock(clicon_handle h,
cxobj *xn,
cxobj **xret)
{
int retval = -1;
char *target;
if ((target = netconf_get_target(xn, "target")) == NULL){
xml_parse_va(xret, NULL, "<rpc-reply><rpc-error>"
"<error-tag>missing-element</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-info><bad-element>target</bad-element></error-info>"
"</rpc-error></rpc-reply>");
goto ok;
}
if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0)
goto done;
ok:
retval = 0;
done:
return retval;
}
/*! Unlock a database
<unlock>
<target>
<candidate/>
</target>
</unlock>
XXX
* @param[in] h clicon handle
* @param[in] xn Sub-tree (under xorig) at <rpc>...</rpc> level.
* @param[out] xret Return XML, error or OK
*/
static int
netconf_unlock(clicon_handle h,
cxobj *xn,
cxobj **xret)
{
return netconf_lock(h, xn, xret);
}
/*! Get running configuration and device state information
*
*
@ -589,133 +389,6 @@ netconf_get(clicon_handle h,
"<error-info>type</error-info>"
"</rpc-error></rpc-reply>");
}
// ok: /* netconf error is not fatal */
retval = 0;
done:
return retval;
}
/*! Close a (user) session
<close-session/>
* @param[in] xn Sub-tree (under xorig) at <rpc>...</rpc> level.
* @param[out] xret Return XML, error or OK
*/
static int
netconf_close_session(clicon_handle h,
cxobj *xn,
cxobj **xret)
{
int retval = -1;
cc_closed++;
if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Kill other user sessions
<kill-session>
<session-id>PID</session-id>
</kill-session>
* @param[in] xn Sub-tree (under xorig) at <rpc>...</rpc> level.
* @param[out] xret Return XML, error or OK
*/
static int
netconf_kill_session(clicon_handle h,
cxobj *xn,
cxobj **xret)
{
int retval=-1;
cxobj *xs;
if ((xs = xpath_first(xn, "//session-id")) == NULL){
xml_parse_va(xret, NULL, "<rpc-reply><rpc-error>"
"<error-tag>missing-element</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-info><bad-element>session-id</bad-element></error-info>"
"</rpc-error></rpc-reply>");
goto ok;
}
if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0)
goto done;
ok:
retval = 0;
done:
return retval;
}
/*! Check the semantic consistency of candidate
<validate/>
:validate
* @param[in] h clicon handle
* @param[in] xn Sub-tree (under xorig) at <rpc>...</rpc> level.
* @param[out] xret Return XML, error or OK
*/
static int
netconf_validate(clicon_handle h,
cxobj *xn,
cxobj **xret)
{
int retval = -1;
char *target;
if ((target = netconf_get_target(xn, "source")) == NULL){
xml_parse_va(xret, NULL, "<rpc-reply><rpc-error>"
"<error-tag>missing-element</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-info><bad-element>target</bad-element></error-info>"
"</rpc-error></rpc-reply>");
goto ok;
}
if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0)
goto done;
ok:
retval = 0;
done:
return retval;
}
/*! Commit candidate -> running
<commit/>
:candidate
* @param[in] h clicon handle
* @param[in] xn Sub-tree (under xorig) at <rpc>...</rpc> level.
* @param[out] xret Return XML, error or OK
*/
static int
netconf_commit(clicon_handle h,
cxobj *xn,
cxobj **xret)
{
int retval = -1;
if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Discard all changes in candidate / revert to running
<discard-changes/>
:candidate
* @param[in] h clicon handle
* @param[in] xn Sub-tree (under xorig) at <rpc>...</rpc> level.
* @param[out] xret Return XML, error or OK
*/
static int
netconf_discard_changes(clicon_handle h,
cxobj *xn,
cxobj **xret)
{
int retval = -1;
if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0)
goto done;
retval = 0;
done:
return retval;
@ -753,6 +426,8 @@ netconf_notification_cb(int s,
cbuf *cb;
cxobj *xn = NULL; /* event xml */
cxobj *xt = NULL; /* top xml */
clicon_handle h = (clicon_handle)arg;
yang_spec *yspec = NULL;
clicon_debug(1, "%s", __FUNCTION__);
/* get msg (this is the reason this function is called) */
@ -766,7 +441,8 @@ netconf_notification_cb(int s,
event_unreg_fd(s, netconf_notification_cb);
goto done;
}
if (clicon_msg_decode(reply, &xt) < 0)
yspec = clicon_dbspec_yang(h);
if (clicon_msg_decode(reply, yspec, &xt) < 0)
goto done;
if ((xn = xpath_first(xt, "notification")) == NULL)
goto ok;
@ -775,12 +451,10 @@ netconf_notification_cb(int s,
clicon_err(OE_PLUGIN, errno, "cbuf_new");
goto done;
}
add_preamble(cb); /* Make it well-formed netconf xml */
if (clicon_xml2cbuf(cb, xn, 0, 0) < 0)
goto done;
add_postamble(cb);
/* Send it to listening client on stdout */
if (netconf_output(1, cb, "notification") < 0){
if (netconf_output_encap(1, cb, "notification") < 0){
cbuf_free(cb);
goto done;
}
@ -840,7 +514,7 @@ netconf_create_subscription(clicon_handle h,
goto ok;
if (event_reg_fd(s,
netconf_notification_cb,
NULL,
h,
"notification socket") < 0)
goto done;
ok:
@ -882,6 +556,10 @@ netconf_application_rpc(clicon_handle h,
clicon_err(OE_UNIX, 0, "cbuf_new");
goto done;
}
if ((cbret = cbuf_new()) == NULL){
clicon_err(OE_UNIX, 0, "cbuf_new");
goto done;
}
/* Find yang rpc statement, return yang rpc statement if found
Check application RPC */
if ((yspec = clicon_dbspec_yang(h)) == NULL){
@ -902,13 +580,9 @@ netconf_application_rpc(clicon_handle h,
goto ok;
}
yrpc = yang_find((yang_node*)ymod, Y_RPC, xml_name(xn));
if ((yrpc==NULL) && _CLICON_XML_NS_ITERATE){
int i;
for (i=0; i<yspec->yp_len; i++){
ymod = yspec->yp_stmt[i];
if ((yrpc = yang_find((yang_node*)ymod, Y_RPC, xml_name(xn))) != NULL)
break;
}
if ((yrpc==NULL) && !_CLICON_XML_NS_STRICT){
if (xml_yang_find_non_strict(xn, yspec, &yrpc) < 0) /* Y_RPC */
goto done;
}
/* Check if found */
if (yrpc != NULL){
@ -917,15 +591,18 @@ netconf_application_rpc(clicon_handle h,
xml_spec_set(xn, yinput); /* needed for xml_spec_populate */
if (xml_apply(xn, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
if (xml_apply(xn, CX_ELMNT,
(xml_applyfn_t*)xml_yang_validate_all, NULL) < 0)
goto done;
if (xml_yang_validate_add(xn, NULL) < 0)
if ((ret = xml_yang_validate_all_top(xn, cbret)) < 0)
goto done;
if (ret == 0){
netconf_output_encap(1, cbret, "rpc-error");
goto ok;
}
if ((cbret = cbuf_new()) == NULL){
clicon_err(OE_UNIX, 0, "cbuf_new");
if ((ret = xml_yang_validate_add(xn, cbret)) < 0)
goto done;
if (ret == 0){
netconf_output_encap(1, cbret, "rpc-error");
goto ok;
}
}
/* Look for local (client-side) netconf plugins. */
if ((ret = rpc_callback_call(h, xn, cbret, NULL)) < 0)
@ -943,11 +620,18 @@ netconf_application_rpc(clicon_handle h,
xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */
if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
if (xml_apply(xoutput, CX_ELMNT,
(xml_applyfn_t*)xml_yang_validate_all, NULL) < 0)
if ((ret = xml_yang_validate_all_top(xoutput, cbret)) < 0)
goto done;
if (xml_yang_validate_add(xoutput, NULL) < 0)
if (ret == 0){
clicon_log(LOG_WARNING, "Errors in output netconf %s", cbuf_get(cbret));
goto ok;
}
if ((ret = xml_yang_validate_add(xoutput, cbret)) < 0)
goto done;
if (ret == 0){
clicon_log(LOG_WARNING, "Errors in output netconf %s", cbuf_get(cbret));
goto ok;
}
}
retval = 1; /* handled by callback */
goto done;
@ -992,9 +676,25 @@ netconf_rpc_dispatch(clicon_handle h,
if (xml_value_set(xa, username) < 0)
goto done;
}
/* Many of these calls are now calling generic clicon_rpc_netconf_xml
* directly, since the validation is generic and done before this place
* in the call. Some call however need extra validation, such as the
* filter parameter to get/get-config and tes- err-opts of edit-config.
*/
xe = NULL;
while ((xe = xml_child_each(xn, xe, CX_ELMNT)) != NULL) {
if (strcmp(xml_name(xe), "get-config") == 0){
if (strcmp(xml_name(xe), "copy-config") == 0 ||
strcmp(xml_name(xe), "delete-config") == 0 ||
strcmp(xml_name(xe), "lock") == 0 ||
strcmp(xml_name(xe), "unlock") == 0 ||
strcmp(xml_name(xe), "kill-session") == 0 ||
strcmp(xml_name(xe), "validate") == 0 || /* :validate */
strcmp(xml_name(xe), "commit") == 0 || /* :candidate */
strcmp(xml_name(xe), "discard-changes") == 0){
if (clicon_rpc_netconf_xml(h, xml_parent(xe), xret, NULL) < 0)
goto done;
}
else if (strcmp(xml_name(xe), "get-config") == 0){
if (netconf_get_config(h, xe, xret) < 0)
goto done;
}
@ -1002,46 +702,13 @@ netconf_rpc_dispatch(clicon_handle h,
if (netconf_edit_config(h, xe, xret) < 0)
goto done;
}
else if (strcmp(xml_name(xe), "copy-config") == 0){
if (netconf_copy_config(h, xe, xret) < 0)
goto done;
}
else if (strcmp(xml_name(xe), "delete-config") == 0){
if (netconf_delete_config(h, xe, xret) < 0)
goto done;
}
else if (strcmp(xml_name(xe), "lock") == 0) {
if (netconf_lock(h, xe, xret) < 0)
goto done;
}
else if (strcmp(xml_name(xe), "unlock") == 0){
if (netconf_unlock(h, xe, xret) < 0)
goto done;
}
else if (strcmp(xml_name(xe), "get") == 0){
if (netconf_get(h, xe, xret) < 0)
goto done;
}
else if (strcmp(xml_name(xe), "close-session") == 0){
if (netconf_close_session(h, xe, xret) < 0)
goto done;
}
else if (strcmp(xml_name(xe), "kill-session") == 0) {
if (netconf_kill_session(h, xe, xret) < 0)
goto done;
}
/* Validate capability :validate */
else if (strcmp(xml_name(xe), "validate") == 0){
if (netconf_validate(h, xe, xret) < 0)
goto done;
}
/* Candidate configuration capability :candidate */
else if (strcmp(xml_name(xe), "commit") == 0){
if (netconf_commit(h, xe, xret) < 0)
goto done;
}
else if (strcmp(xml_name(xe), "discard-changes") == 0){
if (netconf_discard_changes(h, xe, xret) < 0)
cc_closed++;
if (clicon_rpc_netconf_xml(h, xml_parent(xe), xret, NULL) < 0)
goto done;
}
/* RFC 5277 :notification */

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -1,7 +1,7 @@
#
# ***** BEGIN LICENSE BLOCK *****
#
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
#
# This file is part of CLIXON
#

View file

@ -1,11 +1,11 @@
# Clixon Restconf
* [Installation](#Installation)
* [Streams](Streams)
* [Nchan Streams](Nchan)
* [Debugging](Debugging)
* [Installation](#installation)
* [Streams](#streams)
* [Nchan Streams](#nchan)
* [Debugging](#debugging)
### 1. Installation
## Installation
The examples are based on Nginx. Other reverse proxies should work but are not verified.
@ -44,39 +44,39 @@ sudo systemctl start start.service
Start clixon restconf daemon
```
olof@vandal> sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data
> sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data
```
Make restconf calls with curl
```
olof@vandal> curl -G http://127.0.0.1/restconf/data/interfaces
> curl -G http://127.0.0.1/restconf/data/ietf-interfaces:interfaces
[
{
"interfaces": {
"ietf-interfaces:interfaces": {
"interface":[
{
"name": "eth0",
"type": "eth",
"enabled": "true",
"name": "eth9",
"type": "eth",
"enabled": "true"
"type": "ex:eth",
"enabled": true,
}
]
}
}
]
olof@vandal> curl -G http://127.0.0.1/restconf/data/interfaces/interface/name=eth9/type
[
```
Get the type of a specific interface:
```
> curl -G http://127.0.0.1/restconf/data/interfaces/interface=eth9/type
{
"type": "eth"
"ietf-interfaces:type": "eth"
}
]
curl -sX POST -d '{"interfaces":{"interface":{"name":"eth1","type":"eth","enabled":"true"}}}' http://localhost/restconf/data
```
Example of writing a new interfaces specification:
```
curl -sX PUT http://localhost/restconf/data -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth1","type":"ex:eth","enabled":true}}}'
```
### 2. Streams
## Streams
Clixon have two experimental restconf event stream implementations following
RFC8040 Section 6 using SSE. One native and one using Nginx
@ -112,7 +112,7 @@ Add the following to extend the nginx configuration file with the following stat
AN example of a stream access is as follows:
```
vandal> curl -H "Accept: text/event-stream" -s -X GET http://localhost/streams/EXAMPLE
> curl -H "Accept: text/event-stream" -s -X GET http://localhost/streams/EXAMPLE
data: <notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>2018-11-04T14:47:11.373124</eventTime><event><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event></notification>
data: <notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>2018-11-04T14:47:16.375265</eventTime><event><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event></notification>
@ -125,7 +125,7 @@ You can also specify start and stop time. Start-time enables replay of existing
See (stream tests)[../test/test_streams.sh] for more examples.
### 3. Nchan
## Nchan
As an alternative streams implementation, Nginx/Nchan can be used.
Nginx uses pub/sub channels and can be configured in a variety of
@ -180,7 +180,7 @@ curl -H "Accept: text/event-stream" -H "Last-Event-ID: 1539961709:0" -s -X GET h
See (https://nchan.io/#eventsource) on more info on how to access an SSE sub endpoint.
### 4. Debugging
## Debugging
Start the restconf fastcgi program with debug flag:
```

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
@ -71,10 +71,11 @@ static const map_str2int netconf_restconf_map[] = {
{"missing-attribute", 400},
{"bad-attribute", 400},
{"unknown-attribute", 400},
{"missing-element", 400},
{"bad-element", 400},
{"unknown-element", 400},
{"unknown-namespace", 400},
{"access-denied", 401},
{"access-denied", 401}, /* or 403 */
{"access-denied", 403},
{"lock-denied", 409},
{"resource-denied", 409},
@ -436,7 +437,8 @@ api_return_err(clicon_handle h,
goto ok;
}
tagstr = xml_body(xtag);
code = restconf_err2code(tagstr);
if ((code = restconf_err2code(tagstr)) < 0)
code = 500; /* internal server error */
if ((reason_phrase = restconf_code2reason(code)) == NULL)
reason_phrase="";
if (xml_name_set(xerr, "error") < 0)
@ -448,9 +450,12 @@ api_return_err(clicon_handle h,
else
if (xml2json_cbuf(cb, xerr, pretty) < 0)
goto done;
clicon_debug(1, "%s code:%d err:%s", __FUNCTION__, code, cbuf_get(cb));
FCGX_SetExitStatus(code, r->out); /* Created */
FCGX_FPrintF(r->out, "Status: %d %s\r\n", code, reason_phrase);
FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n\r\n",
use_xml?"xml":"json");
if (use_xml){
if (pretty){
FCGX_FPrintF(r->out, " <errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\">\n", cbuf_get(cb));

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
@ -81,7 +81,7 @@
#include "restconf_stream.h"
/* Command line options to be passed to getopt(3) */
#define RESTCONF_OPTS "hD:f:l:p:y:a:u:"
#define RESTCONF_OPTS "hD:f:l:p:y:a:u:o:"
/* RESTCONF enables deployments to specify where the RESTCONF API is
located. The client discovers this by getting the "/.well-known/host-meta"
@ -495,7 +495,8 @@ usage(clicon_handle h,
"\t-d <dir>\tSpecify restconf plugin directory dir (default: %s)\n"
"\t-y <file>\tLoad yang spec file (override yang main module)\n"
"\t-a UNIX|IPv4|IPv6\tInternal backend socket family\n"
"\t-u <path|addr>\tInternal socket domain path or IP addr (see -a)\n",
"\t-u <path|addr>\tInternal socket domain path or IP addr (see -a)\n"
"\t-o \"<option>=<value>\"\tGive configuration option overriding config file (see clixon-config.yang)\n",
argv0,
clicon_restconf_dir(h)
);
@ -611,6 +612,15 @@ main(int argc,
usage(h, argv[0]);
clicon_option_str_set(h, "CLICON_SOCK", optarg);
break;
case 'o':{ /* Configuration option */
char *val;
if ((val = index(optarg, '=')) == NULL)
usage(h, argv0);
*val++ = '\0';
if (clicon_option_add(h, optarg, val) < 0)
goto done;
break;
}
default:
usage(h, argv[0]);
break;
@ -645,7 +655,9 @@ main(int argc,
if (yang_spec_load_dir(h, str, yspec) < 0)
goto done;
}
/* Load clixon lib yang module */
if (yang_spec_parse_module(h, "clixon-lib", NULL, yspec) < 0)
goto done;
/* Load yang module library, RFC7895 */
if (yang_modules_init(h) < 0)
goto done;
@ -654,7 +666,7 @@ main(int argc,
yang_spec_parse_module(h, "ietf-restconf-monitoring", NULL, yspec)< 0)
goto done;
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277") &&
yang_spec_parse_module(h, "ietf-netconf-notification", NULL, yspec)< 0)
yang_spec_parse_module(h, "clixon-rfc5277", NULL, yspec)< 0)
goto done;
/* Call start function in all plugins before we go interactive
Pass all args after the standard options to plugin_start

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
@ -180,7 +180,7 @@ restconf_stream_cb(int s,
clicon_exit_set();
goto done;
}
if (clicon_msg_decode(reply, &xtop) < 0)
if (clicon_msg_decode(reply, NULL, &xtop) < 0) /* XXX pass yang_spec */
goto done;
/* create event */
if ((cb = cbuf_new()) == NULL){
@ -249,7 +249,7 @@ restconf_stream(clicon_handle h,
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
cprintf(cb, "<rpc><create-subscription><stream>%s</stream>", name);
cprintf(cb, "<rpc><create-subscription xmlns=\"urn:ietf:params:xml:ns:netmod:notification\"><stream>%s</stream>", name);
/* Print all fields */
for (i=0; i<cvec_len(qvec); i++){
cv = cvec_i(qvec, i);

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -1,7 +1,7 @@
#
# ***** BEGIN LICENSE BLOCK *****
#
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
#
# This file is part of CLIXON
#

View file

@ -1,7 +1,7 @@
#
# ***** BEGIN LICENSE BLOCK *****
#
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
#
# This file is part of CLIXON
#

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
@ -106,7 +106,7 @@ main(int argc, char **argv)
char *plugin = NULL;
char *cmd = NULL;
yang_spec *yspec = NULL;
char *yangmodule = NULL;
char *yangfilename = NULL;
char *dbdir = NULL;
int ret;
int pid;
@ -151,7 +151,7 @@ main(int argc, char **argv)
case 'y': /* Yang file */
if (!optarg)
usage(argv0);
yangmodule = optarg;
yangfilename = optarg;
break;
}
/*
@ -173,8 +173,8 @@ main(int argc, char **argv)
clicon_err(OE_DB, 0, "Missing dbdir -b option");
goto done;
}
if (yangmodule == NULL){
clicon_err(OE_YANG, 0, "Missing yang module -m option");
if (yangfilename == NULL){
clicon_err(OE_YANG, 0, "Missing yang filename -y option");
goto done;
}
/* Load datastore plugin */
@ -187,7 +187,7 @@ main(int argc, char **argv)
if ((yspec = yspec_new()) == NULL)
goto done;
/* Parse yang spec from given file */
if (yang_parse(h, yangmodule, NULL, NULL, yspec) < 0)
if (yang_spec_parse_file(h, yangfilename, yspec) < 0)
goto done;
/* Set database directory option */
if (xmldb_setopt(h, "dbdir", dbdir) < 0)
@ -239,15 +239,18 @@ main(int argc, char **argv)
clicon_err(OE_DB, 0, "Unrecognized operation: %s", argv[1]);
usage(argv0);
}
_CLICON_XML_NS_ITERATE = 1;
_CLICON_XML_NS_STRICT = 0;
if (xml_parse_string(argv[2], NULL, &xt) < 0)
goto done;
if (xml_rootchild(xt, 0, &xt) < 0)
goto done;
if ((cbret = cbuf_new()) == NULL)
if ((cbret = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
if (xmldb_put(h, db, op, xt, cbret) < 0)
}
if (xmldb_put(h, db, op, xt, cbret) < 1)
goto done;
}
else if (strcmp(cmd, "copy")==0){
if (argc != 2)

View file

@ -1,7 +1,7 @@
#
# ***** BEGIN LICENSE BLOCK *****
#
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
#
# This file is part of CLIXON
#

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
@ -358,6 +358,16 @@ xml_copy_marked(cxobj *x0,
assert(x0 && x1);
yt = xml_spec(x0); /* can be null */
/* Copy all attributes */
x = NULL;
while ((x = xml_child_each(x0, x, CX_ATTR)) != NULL) {
name = xml_name(x);
if ((xcopy = xml_new(name, x1, xml_spec(x))) == NULL)
goto done;
if (xml_copy(x, xcopy) < 0)
goto done;
}
/* Go through children to detect any marked nodes:
* (3) Special case: key nodes in lists are copied if any
* node in list is marked
@ -470,6 +480,10 @@ text_get(xmldb_handle xh,
if (singleconfigroot(xt, &xt) < 0)
goto done;
}
/* XXX: should we validate file if read from disk?
* Argument against: we may want to have a semantically wrong file and wish
* to edit?
*/
} /* xt == NULL */
/* Here xt looks like: <config>...</config> */
@ -530,12 +544,9 @@ text_get(xmldb_handle xh,
/* Add default values (if not set) */
if (xml_apply(xt, CX_ELMNT, xml_default, NULL) < 0)
goto done;
/* Order XML children according to YANG */
if (!xml_child_sort && xml_apply(xt, CX_ELMNT, xml_order, NULL) < 0)
goto done;
#if 0 /* debug */
if (xml_child_sort && xml_apply0(xt, -1, xml_sort_verify, NULL) < 0)
clicon_log(LOG_NOTICE, "%s: verify failed #2", __FUNCTION__);
#if 1 /* debug */
if (xml_apply0(xt, -1, xml_sort_verify, NULL) < 0)
clicon_log(LOG_NOTICE, "%s: sort verify failed #2", __FUNCTION__);
#endif
if (debug>1)
clicon_xml2file(stderr, xt, 0, 1);
@ -555,17 +566,22 @@ text_get(xmldb_handle xh,
}
/*! Modify a base tree x0 with x1 with yang spec y according to operation op
* @param[in] th text handle
* @param[in] x0 Base xml tree (can be NULL in add scenarios)
* @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL
* @param[in] x0p Parent of x0
* @param[in] x1 xml tree which modifies base
* @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc
* @param[out] cbret Initialized cligen buffer. Contains return XML or "".
* @param[out] cbret Initialized cligen buffer. Contains return XML if retval is 0.
* @retval -1 Error
* @retval 0 Failed (cbret set)
* @retval 1 OK
* Assume x0 and x1 are same on entry and that y is the spec
* @see put in clixon_keyvalue.c
* @see text_modify_top
*/
static int
text_modify(cxobj *x0,
text_modify(struct text_handle *th,
cxobj *x0,
yang_node *y0,
cxobj *x0p,
cxobj *x1,
@ -576,6 +592,8 @@ text_modify(cxobj *x0,
char *opstr;
char *x1name;
char *x1cname; /* child name */
cxobj *x0a; /* attribute */
cxobj *x1a; /* attribute */
cxobj *x0c; /* base child */
cxobj *x0b; /* base body */
cxobj *x1c; /* mod child */
@ -583,6 +601,7 @@ text_modify(cxobj *x0,
yang_stmt *yc; /* yang child */
cxobj **x0vec = NULL;
int i;
int ret;
assert(x1 && xml_type(x1) == CX_ELMNT);
assert(y0);
@ -598,7 +617,7 @@ text_modify(cxobj *x0,
if (x0){
if (netconf_data_exists(cbret, "Data already exists; cannot create new resource") < 0)
goto done;
goto ok;
goto fail;
}
case OP_NONE: /* fall thru */
case OP_MERGE:
@ -607,6 +626,13 @@ text_modify(cxobj *x0,
// int iamkey=0;
if ((x0 = xml_new(x1name, x0p, (yang_stmt*)y0)) == NULL)
goto done;
/* Copy xmlns attributes */
if ((x1a = xml_find_type(x1, NULL, "xmlns", CX_ATTR)) != 0){
if ((x0a = xml_dup(x1a)) == NULL)
goto done;
if (xml_addsub(x0, x0a) < 0)
goto done;
}
#if 0
/* If it is key I dont want to mark it */
if ((iamkey=yang_key_match(y0->yn_parent, x1name)) < 0)
@ -636,7 +662,7 @@ text_modify(cxobj *x0,
if (x0==NULL){
if (netconf_data_missing(cbret, "Data does not exist; cannot delete resource") < 0)
goto done;
goto ok;
goto fail;
}
case OP_REMOVE: /* fall thru */
if (x0){
@ -653,7 +679,7 @@ text_modify(cxobj *x0,
if (x0){
if (netconf_data_exists(cbret, "Data already exists; cannot create new resource") < 0)
goto done;
goto ok;
goto fail;
}
case OP_REPLACE: /* fall thru */
if (x0){
@ -679,11 +705,19 @@ text_modify(cxobj *x0,
if (x0==NULL){
if ((x0 = xml_new(x1name, x0p, (yang_stmt*)y0)) == NULL)
goto done;
/* Copy xmlns attributes */
if ((x1a = xml_find_type(x1, NULL, "xmlns", CX_ATTR)) != 0){
if ((x0a = xml_dup(x1a)) == NULL)
goto done;
if (xml_addsub(x0, x0a) < 0)
goto done;
}
if (op==OP_NONE)
xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */
}
/* First pass: mark existing children in base */
/* Loop through children of the modification tree */
/* First pass: Loop through children of the x1 modification tree
* collect matching nodes from x0 in x0vec (no changes to x0 children)
*/
if ((x0vec = calloc(xml_child_nr(x1), sizeof(x1))) == NULL){
clicon_err(OE_UNIX, errno, "calloc");
goto done;
@ -699,28 +733,40 @@ text_modify(cxobj *x0,
}
/* See if there is a corresponding node in the base tree */
x0c = NULL;
if (match_base_child(x0, x1c, &x0c, yc) < 0)
if (match_base_child(x0, x1c, yc, &x0c) < 0)
goto done;
x0vec[i++] = x0c;
#if 1
if (x0c && (yc != xml_spec(x0c))){
/* There is a match but is should be replaced (choice)*/
if (xml_purge(x0c) < 0)
goto done;
x0c = NULL;
}
/* Second pass: modify tree */
#endif
x0vec[i++] = x0c; /* != NULL if x0c is matching x1c */
}
/* Second pass: Loop through children of the x1 modification tree again
* Now potentially modify x0:s children
* Here x0vec contains one-to-one matching nodes of x1:s children.
*/
x1c = NULL;
i = 0;
while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) {
x1cname = xml_name(x1c);
x0c = x0vec[i++];
yc = yang_find_datanode(y0, x1cname);
if (text_modify(x0vec[i++], (yang_node*)yc, x0, x1c, op, cbret) < 0)
if ((ret = text_modify(th, x0c, (yang_node*)yc, x0, x1c, op, cbret)) < 0)
goto done;
/* If xml return - ie netconf error xml tree, then stop and return OK */
if (cbuf_len(cbret))
goto ok;
if (ret == 0)
goto fail;
}
break;
case OP_DELETE:
if (x0==NULL){
if (netconf_data_missing(cbret, "Data does not exist; cannot delete resource") < 0)
goto done;
goto ok;
goto fail;
}
case OP_REMOVE: /* fall thru */
if (x0)
@ -731,20 +777,26 @@ text_modify(cxobj *x0,
} /* CONTAINER switch op */
} /* else Y_CONTAINER */
xml_sort(x0p, NULL);
ok:
retval = 0;
retval = 1;
done:
if (x0vec)
free(x0vec);
return retval;
fail: /* cbret set */
retval = 0;
goto done;
} /* text_modify */
/*! Modify a top-level base tree x0 with modification tree x1
* @param[in] th text handle
* @param[in] x0 Base xml tree (can be NULL in add scenarios)
* @param[in] x1 xml tree which modifies base
* @param[in] yspec Top-level yang spec (if y is NULL)
* @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc
* @param[out] cbret Initialized cligen buffer. Contains return XML or "".
* @param[out] cbret Initialized cligen buffer. Contains return XML if retval is 0.
* @retval -1 Error
* @retval 0 Failed (cbret set)
* @retval 1 OK
* @see text_modify
*/
static int
@ -762,6 +814,7 @@ text_modify_top(struct text_handle *th,
yang_stmt *yc; /* yang child */
yang_stmt *ymod;/* yang module */
char *opstr;
int ret;
/* Assure top-levels are 'config' */
assert(x0 && strcmp(xml_name(x0),"config")==0);
@ -790,7 +843,7 @@ text_modify_top(struct text_handle *th,
case OP_DELETE:
if (netconf_data_missing(cbret, "Data does not exist; cannot delete resource") < 0)
goto done;
goto ok;
goto fail;
break;
default:
break;
@ -812,35 +865,42 @@ text_modify_top(struct text_handle *th,
goto done;
if (ymod != NULL)
yc = yang_find_datanode((yang_node*)ymod, x1cname);
if (yc == NULL && _CLICON_XML_NS_ITERATE){
int i;
for (i=0; i<yspec->yp_len; i++){
ymod = yspec->yp_stmt[i];
if ((yc = yang_find_datanode((yang_node*)ymod, x1cname)) != NULL)
break;
}
if (yc == NULL && !_CLICON_XML_NS_STRICT){
if (xml_yang_find_non_strict(x1c, yspec, &yc) < 0)
goto done;
}
if (yc == NULL){
if (netconf_operation_failed(cbret, "application", "Validation failed")< 0)
if (netconf_unknown_element(cbret, "application", x1cname, "Unassigned yang spec") < 0)
goto done;
goto ok;
goto fail;
}
/* See if there is a corresponding node in the base tree */
if (match_base_child(x0, x1c, &x0c, yc) < 0)
if (match_base_child(x0, x1c, yc, &x0c) < 0)
goto done;
if (text_modify(x0c, (yang_node*)yc, x0, x1c, op, cbret) < 0)
#if 1
if (x0c && (yc != xml_spec(x0c))){
/* There is a match but is should be replaced (choice)*/
if (xml_purge(x0c) < 0)
goto done;
x0c = NULL;
}
#endif
if ((ret = text_modify(th, x0c, (yang_node*)yc, x0, x1c, op, cbret)) < 0)
goto done;
/* If xml return - ie netconf error xml tree, then stop and return OK */
if (cbuf_len(cbret))
goto ok;
if (ret == 0)
goto fail;
}
ok:
retval = 0;
// ok:
retval = 1;
done:
return retval;
fail: /* cbret set */
retval = 0;
goto done;
} /* text_modify_top */
/*! For containers without presence and no children, remove
/*! For containers without presence and no children(except attrs), remove
* @param[in] x XML tree node
* See section 7.5.1 in rfc6020bis-02.txt:
* No presence:
@ -869,7 +929,7 @@ xml_container_presence(cxobj *x,
}
/* Mark node that is: container, have no children, dont have presence */
if (y->ys_keyword == Y_CONTAINER &&
xml_child_nr(x)==0 &&
xml_child_nr_notype(x, CX_ATTR)==0 &&
yang_find((yang_node*)y, Y_PRESENCE, NULL) == NULL)
xml_flag_set(x, XML_FLAG_MARK); /* Mark, remove later */
retval = 0;
@ -897,15 +957,12 @@ text_put(xmldb_handle xh,
yang_spec *yspec;
cxobj *x0 = NULL;
struct db_element *de = NULL;
int cbretlocal = 0; /* Set if cbret is NULL on entry */
int ret;
if (cbret == NULL){
if ((cbret = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
clicon_err(OE_XML, EINVAL, "cbret is NULL");
goto done;
}
cbretlocal++;
}
if ((yspec = th->th_yangspec) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec");
goto done;
@ -957,25 +1014,19 @@ text_put(xmldb_handle xh,
xml_name(x0));
goto done;
}
#if 0
/* Add yang specification backpointer to all XML nodes
* This is already done in from_client_edit_config() */
if (xml_apply(x1, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
#endif
#if 0 /* debug */
if (xml_child_sort && xml_apply0(x1, -1, xml_sort_verify, NULL) < 0)
if (xml_apply0(x1, -1, xml_sort_verify, NULL) < 0)
clicon_log(LOG_NOTICE, "%s: verify failed #1", __FUNCTION__);
#endif
/*
* Modify base tree x with modification x1. This is where the
* new tree is made.
*/
if (text_modify_top(th, x0, x1, yspec, op, cbret) < 0)
if ((ret = text_modify_top(th, x0, x1, yspec, op, cbret)) < 0)
goto done;
/* If xml return - ie netconf error xml tree, then stop and return OK */
if (cbuf_len(cbret))
goto ok;
if (ret == 0)
goto fail;
/* Remove NONE nodes if all subs recursively are also NONE */
if (xml_tree_prune_flagged_sub(x0, XML_FLAG_NONE, 0, NULL) <0)
@ -990,7 +1041,7 @@ text_put(xmldb_handle xh,
if (xml_tree_prune_flagged(x0, XML_FLAG_MARK, 1) < 0)
goto done;
#if 0 /* debug */
if (xml_child_sort && xml_apply0(x0, -1, xml_sort_verify, NULL) < 0)
if (xml_apply0(x0, -1, xml_sort_verify, NULL) < 0)
clicon_log(LOG_NOTICE, "%s: verify failed #3", __FUNCTION__);
#endif
/* Write back to datastore cache if first time */
@ -1025,11 +1076,8 @@ text_put(xmldb_handle xh,
}
else if (clicon_xml2file(f, x0, 0, th->th_pretty) < 0)
goto done;
ok:
retval = 0;
retval = 1;
done:
if (cbretlocal && cbret)
cbuf_free(cbret);
if (f != NULL)
fclose(f);
if (dbfile)
@ -1041,6 +1089,9 @@ text_put(xmldb_handle xh,
if (!th->th_cache && x0)
xml_free(x0);
return retval;
fail:
retval = 0;
goto done;
}
/*! Copy database from db1 to db2
@ -1386,6 +1437,8 @@ main(int argc,
char *yangmod; /* yang file */
yang_spec *yspec = NULL;
clicon_handle h;
cbuf *cbret = NULL;
int ret;
if ((h = clicon_handle_init()) == NULL)
goto done;
@ -1400,7 +1453,7 @@ main(int argc,
db_init(db);
if ((yspec = yspec_new()) == NULL)
goto done
if (yang_parse(h, NULL, yangmod, NULL, yspec) < 0)
if (yang_spec_parse_module(h, yangmod, NULL, yspec) < 0)
goto done;
if (strcmp(cmd, "get")==0){
if (argc < 5)
@ -1434,13 +1487,21 @@ main(int argc,
op = OP_REMOVE;
else
usage(argv[0]);
if (xmldb_put(h, db, op, NULL, xn, NULL) < 0)
if ((cbret = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if ((ret = xmldb_put(h, db, op, NULL, xn, cbret)) < 0)
goto done;
if (ret == 0)
fprintf(stderr, "%s\n", cbuf_get(cbret));
}
else
usage(argv[0]);
printf("\n");
done:
if (cbret)
cbuf_free(cbret);
return 0;
}

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -42,9 +42,9 @@ The example:
sudo make install
```
## How do you run Clixon example commands?
## How do I run Clixon example commands?
- Start a backend server: `clixon_backend -Ff /usr/local/etc/example.xml`
- Start a backend server: `clixon_backend -F -s init -f /usr/local/etc/example.xml`
- Start a cli session: `clixon_cli -f /usr/local/etc/example.xml`
- Start a netconf session: `clixon_netconf -f /usr/local/etc/example.xml`
- Start a restconf daemon: `sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data`
@ -72,6 +72,82 @@ grep clicon /etc/group
clicon:x:1001:<user>,www-data
```
## How do I use the CLI?
The easiest way to use Clixon is via the CLI. Once the backend is started
Example:
```
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> validate
cli> commit
cli> show configuration xml
<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
<interface>
<name>eth9</name>
<type>ex:eth</type>
<enabled>true</enabled>
</interface>
</interfaces>
cli> delete interfaces interface eth9
```
## How do I use netconf?
As an alternative to cli configuration, you can use netconf. Easiest is to just pipe netconf commands to the clixon_netconf application.
Example:
```
clixon_netconf -qf /usr/local/etc/example.xml
<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>
<rpc-reply><data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>eth9</name><type>ex:eth</type><enabled>true</enabled></interface></interfaces></data></rpc-reply>]]>]]>
```
However, more useful is to run clixon_netconf as an SSH
subsystem. Register the subsystem in /etc/sshd_config:
```
Subsystem netconf /usr/local/bin/clixon_netconf -f /usr/local/etc/example.xml
```
and then invoke it from a client using
```
ssh -s <host> netconf
```
## How do I use restconf?
You can access clixon via REST API using restconf, such as using
curl. GET, PUT, POST are supported.
You need a web-server, such as nginx, and start a restconf fcgi
daemon, clixon_restconf.
For example, using nginx, install, and edit config file: /etc/nginx/sites-available/default:
```
server {
...
location /restconf {
fastcgi_pass unix:/www-data/fastcgi_restconf.sock;
include fastcgi_params;
}
}
```
Start nginx daemon
```
sudo /etc/init.d/nginx start
```
Example:
```
curl -G http://127.0.0.1/restconf/data/ietf-interfaces:interfaces/interface=eth9/type
[
{
"ietf-interfaces:type": "ex:eth"
}
]
```
Read more in the (restconf)[../apps/restconf] docs.
## What about reference documentation?
Clixon uses [Doxygen](http://www.doxygen.nl/index.html) for reference documentation.
You need to install doxygen and graphviz on your system.
@ -121,7 +197,7 @@ are included.
The following configuration file options control the loading of Yang files:
- `CLICON_YANG_DIR` - A list of directories (yang dir path) where Clixon searches for module and submodules.
- `CLICON_YANG_MAIN_FILE` - Load a specific Yang module fiven by a file.
- `CLICON_YANG_MAIN_FILE` - Load a specific Yang module given by a file.
- `CLICON_YANG_MODULE_MAIN` - Specifies a single module to load. The module is searched for in the yang dir path.
- `CLICON_YANG_MODULE_REVISION` : Specifies a revision to the main module.
- `CLICON_YANG_MAIN_DIR` - Load all yang modules in this directory.
@ -161,59 +237,6 @@ sudo docker run -td olofhagsand/clixon_example
```
Look in the example documentation for more info.
## How do I use netconf?
As an alternative to cli configuration, you can use netconf. Easiest is to just pipe netconf commands to the clixon_netconf application.
Example:
```
echo "<rpc><get-config><source><candidate/></source><configuration/></get-config></rpc>]]>]]>" | clixon_netconf -f /usr/local/etc/example.xml
```
However, more useful is to run clixon_netconf as an SSH
subsystem. Register the subsystem in /etc/sshd_config:
```
Subsystem netconf /usr/local/bin/clixon_netconf -f /usr/local/etc/example.xml
```
and then invoke it from a client using
```
ssh -s <host> netconf
```
## How do I use restconf?
You can access clixon via REST API using restconf, such as using
curl. GET, PUT, POST are supported.
You need a web-server, such as nginx, and start a restconf fcgi
daemon, clixon_restconf.
For example, using nginx, install, and edit config file: /etc/nginx/sites-available/default:
```
server {
...
location /restconf {
fastcgi_pass unix:/www-data/fastcgi_restconf.sock;
include fastcgi_params;
}
}
```
Start nginx daemon
```
sudo /etc/init.d/nginx start
```
Example:
```
curl -G http://127.0.0.1/restconf/data/interfaces/interface/name=eth9/type
[
{
"type": "eth"
}
]
```
Read more in the (restconf)[../apps/restconf] docs.
## Does Clixon support event streams?
Yes, Clixon supports event notification streams in the CLI, Netconf and Restconf API:s.
@ -233,7 +256,7 @@ severity major;
or via NETCONF:
```
clixon_netconf -qf /usr/local/etc/example.xml
<rpc><create-subscription><stream>EXAMPLE</stream></create-subscription></rpc>]]>]]>
<rpc><create-subscription xmlns="urn:ietf:params:xml:ns:netmod:notification"><stream>EXAMPLE</stream></create-subscription></rpc>]]>]]>
<rpc-reply><ok/></rpc-reply>]]>]]>
<notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>2018-09-30T12:44:59.657276</eventTime><event xmlns="http://example.com/event/1.0"><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event></notification>]]>]]>
...
@ -242,7 +265,7 @@ or via restconf:
```
curl -H "Accept: text/event-stream" -s -X GET http://localhost/streams/EXAMPLE
```
Consult (../apps/restconf/README.md) on more information on how to setup a reverse proxy for restconf streams. It is also possible to configure a pub/sub system such as (Nginx Nchan)[https://nchan.io].
Consult [clixon restconf](../apps/restconf/README.md) on more information on how to setup a reverse proxy for restconf streams. It is also possible to configure a pub/sub system such as [Nginx Nchan](https://nchan.io).
## How should I start the backend daemon?
@ -277,7 +300,7 @@ There are two ways to add extra XML to running database after start. Note that
The first way is via a file. Assume you want to add this xml (the config tag is a necessary top-level tag):
```
<config>
<x>extra</x>
<x xmlns="urn:example:clixon">extra</x>
</config>
```
You add this via the -c option:
@ -289,12 +312,13 @@ The second way is by programming the plugin_reset() in the backend
plugin. The example code contains an example on how to do this (see plugin_reset() in example_backend.c).
## I want to program. How do I extend the example?
See [../apps/example]
See [../apps/example](../apps/example)
- example.xml - Change the configuration file
- The yang specifications - This is the central part. It changes the XML, database and the config cli.
- example_cli.cli - Change the fixed part of the CLI commands
- example_cli.c - Cli C-commands are placed here.
- example_backend.c - Commit and validate functions.
- example_backend_nacm.c - Secondary example plugin (for authorization)
- example_netconf.c - Netconf plugin
- example_restconf.c - Add restconf authentication, etc.

View file

@ -1,7 +1,7 @@
#
# ***** BEGIN LICENSE BLOCK *****
#
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
#
# This file is part of CLIXON
#

View file

@ -1,7 +1,7 @@
#
# ***** BEGIN LICENSE BLOCK *****
#
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
#
# This file is part of CLIXON
#

View file

@ -1,7 +1,7 @@
#
# ***** BEGIN LICENSE BLOCK *****
#
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
#
# This file is part of CLIXON
#

View file

@ -1,7 +1,7 @@
#
# ***** BEGIN LICENSE BLOCK *****
#
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
#
# This file is part of CLIXON
#

View file

@ -1,7 +1,7 @@
#
# ***** BEGIN LICENSE BLOCK *****
#
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
#
# This file is part of CLIXON
#

View file

@ -1,7 +1,7 @@
#
# ***** BEGIN LICENSE BLOCK *****
#
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
#
# This file is part of CLIXON
#

View file

@ -1,7 +1,7 @@
#
# ***** BEGIN LICENSE BLOCK *****
#
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
#
# This file is part of CLIXON
#

View file

@ -1,19 +1,33 @@
# Clixon example
* [Content](#content)
* [Compile and run](#compile)
* [Using the CLI](#using-the-cli)
* [Using netconf](#using-netconf)
* [Streams](#streams)
* [RPC Operations](#rpc-operations)
* [State data](#state-data)
* [Authentication and NACM](#authentication-and-nacm)
* [Systemd](#systemd)
* [Docker](#docker)
* [Plugins](#plugins)
## Content
This directory contains a Clixon example which includes a simple example. It contains the following files:
* example.xml The configuration file. See yang/clixon-config@<date>.yang for all available fields.
* example.yang The yang spec of the example. It mainly includes ietf routing and IP modules.
* 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 RPC (`fib_route_rpc`).
* example_backend.c Backend callback plugin including example of:
* `example.xml` The configuration file. See (yang/clixon-config@<date>.yang)[../yang/clixon-config@2018-10-21.yang] for the documentation of all available fields.
* `example.yang` The yang spec of the example. It mainly includes ietf routing and IP modules.
* `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 RPC (`fib_route_rpc`).
* `example_backend.c` Backend callback plugin including example of:
* transaction callbacks (validate/commit),
* notification,
* rpc handler
* state-data handler, ie non-config data
* 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_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
## Compile and run
@ -47,10 +61,37 @@ Send restconf command
curl -G http://127.0.0.1/restconf/data
```
## Setting data example using netconf
## Using the CLI
The example CLI allows you to modify and view the data model using `set`, `delete` and `show` via generated code.
There are also many other commands available as examples. View the source file (example_cli.cli)[example_cli.cli] for more details.
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> validate
cli> commit
cli> show configuration xml
<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
<interface>
<name>eth9</name>
<type>ex:eth</type>
<enabled>true</enabled>
</interface>
</interfaces>
cli> delete interfaces interface eth9
```
## Using Netconf
The following example shows how to set data using netconf:
```
<rpc><edit-config><target><candidate/></target><config>
<interfaces>
<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
<interface>
<name>eth1</name>
<enabled>true</enabled>
@ -65,13 +106,13 @@ Send restconf command
</config></edit-config></rpc>]]>]]>
```
## Getting data using netconf
### Getting data using netconf
```
<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>
<rpc><get-config><source><candidate/></source><filter/></get-config></rpc>]]>]]>
<rpc><get-config><source><candidate/></source><filter type="xpath"/></get-config></rpc>]]>]]>
<rpc><get-config><source><candidate/></source><filter type="subtree"><configuration><interfaces><interface><ipv4/></interface></interfaces></configuration></filter></get-config></rpc>]]>]]>
<rpc><get-config><source><candidate/></source><filter type="xpath" select="/interfaces/interface/ipv4"/></get-config></rpc>]]>]]>
<rpc><get-config><source><candidate/></source><filter type="subtree"><data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>eth9</name><type>ex:eth</type></interface></interfaces></data></filter></get-config></rpc>]]>]]>
<rpc><get-config><source><candidate/></source><filter type="xpath" select="/interfaces/interface"/></get-config></rpc>]]>]]>
<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>
```
@ -80,23 +121,134 @@ Send restconf command
The example has an EXAMPLE stream notification triggering every 5s. To start a notification
stream in the session using netconf, create a subscription:
```
<rpc><create-subscription><stream>EXAMPLE</stream></create-subscription></rpc>]]>]]>
<rpc><create-subscription xmlns="urn:ietf:params:xml:ns:netmod:notification"><stream>EXAMPLE</stream></create-subscription></rpc>]]>]]>
<rpc-reply><ok/></rpc-reply>]]>]]>
<notification><event>Routing notification</event></notification>]]>]]>
<notification><event>Routing notification</event></notification>]]>]]>
<notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>2019-01-02T10:20:05.929272</eventTime><event><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event></notification>]]>]]>
...
```
This can also be triggered via the CLI:
```
clixon_cli -f /usr/local/etc/example.xml
cli> notify
cli> Routing notification
Routing notification
cli> event-class fault;
reportingEntity {
card Ethernet0;
}
severity major;
...
cli> no notify
cli>
```
Restconf support is also supported, see [../apps/restconf/README.md].
Restconf support is also supported, see (restc)[../apps/restconf/README.md].
## Initializing a plugin
## RPC Operations
Clixon implements Yang RPC operations by an extension mechanism. The
extension mechanism enables you to add application-specific
operations. It works by adding user-defined callbacks for added
netconf operations. It is possible to use the extension mechanism
independent of the yang rpc construct, but it is recommended. The example includes an example:
Example using CLI:
```
cli> rpc ipv4
rpc-reply {
route {
address-family ipv4;
next-hop {
next-hop-list 2.3.4.5;
}
source-protocol static;
}
}
```
Netconf:
```
<rpc><fib-route xmlns="urn:ietf:params:xml:ns:yang:ietf-routing"><routing-instance-name>ipv4</routing-instance-name></fib-route></rpc>]]>]]>
<rpc-reply><route xmlns="urn:ietf:params:xml:ns:yang:ietf-routing"><address-family>ipv4</address-family><next-hop><next-hop-list>2.3.4.5</next-hop-list></next-hop><source-protocol>static</source-protocol></route></rpc-reply>]]>]]>
```
Restconf:
```
curl -X POST http://localhost/restconf/operations/ietf-routing:fib-route -d '{"ietf-routing:input":{"routing-instance-name":"ipv4"}}'
```
### Details
The example works by creating a netconf rpc call and sending it to the backend: (see the fib_route_rpc() function in [example_cli.c](example_cli.c)).
In the (example_backend.c)[example_backend.c], a callback is registered (fib_route()) which handles the RPC (this is just dummy data):
```
static int
fib_route(clicon_handle h,
cxobj *xe, /* Request: <rpc><xn></rpc> */
cbuf *cbret, /* Reply eg <rpc-reply>... */
void *arg, /* Client session */
void *regarg) /* Argument given at register */
{
cprintf(cbret, "<rpc-reply><route xmlns=\"urn:ietf:params:xml:ns:yang:ietf-routing\">"
"<address-family>ipv4</address-family>"
"<next-hop><next-hop-list>2.3.4.5</next-hop-list></next-hop>"
"<source-protocol>static</source-protocol>"
"</route></rpc-reply>");
return 0;
}
int
clixon_plugin_init(clicon_handle h)
{
...
rpc_callback_register(h, fib_route, NULL, "fib-route");
...
}
```
## State data
Netconf <get> and restconf GET also returns state data(not only configuration data).
In YANG state data is specified with `config false;`. In the example,
`state` is state data, see (example.yang)[example.yang]
To return state data, you need to write a backend state data callback
with the name "plugin_statedata" where you return an XML tree with
state. This is then merged with config data by the system.
A static example of returning state data is in the example. Note that
a real example would poll or get the interface counters via a system
call, as well as use the "xpath" argument to identify the requested
state data.
## Authentication and NACM
The example contains some stubs for authorization according to [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341):
* A basic auth HTTP callback, see: example_restconf_credentials() containing three example users: andy, wilma, and guest, according to the examples in Appendix A in [RFC8341](https://tools.ietf.org/html/rfc8341).
* A NACM backend plugin reporting the mandatory NACM state variables.
## Systemd
Example systemd files for backend and restconf daemons are found under the systemd directory. Install them under /etc/systemd/system for example.
## Docker
Run the example as a docker container and access it from a host CLI as follows:
```
ID=$(sudo docker run -td olofhagsand/clixon_example)
IP=$(sudo docker inspect -f '{{.NetworkSettings.IPAddress }}' $ID)
clixon_cli -a IPv4 -u $IP -f ./example.xml
```
Build the container and push yourself: First change the IMAGE variable in Makefile (eg to "you/clixon_example). Then build and push:
```
make docker
make push
sudo docker run -ti --rm you/clixon_example
```
Note that the configuration database is internal in the container, so
it is deleted if the container is restarted. To make the configuration
database persistent, you need to mount running_db using `-v`
## Plugins
The example includes a restonf, netconf, CLI and two backend plugins.
Each plugin is initiated with an API struct followed by a plugin init function.
@ -127,96 +279,3 @@ clixon_plugin_init(clicon_handle h)
return &api; /* Return NULL on error */
}
```
## Operation data
Clixon implements Yang RPC operations by an extension mechanism. The
extension mechanism enables you to add application-specific
operations. It works by adding user-defined callbacks for added
netconf operations. It is possible to use the extension mechanism
independent of the yang rpc construct, but it is recommended. The example includes an example:
Example:
```
cli> rpc ipv4
<rpc-reply>
<ok/>
</rpc-reply>
```
The example works by creating a netconf rpc call and sending it to the backend: (see the fib_route_rpc() function).
```
<rpc>
<fib-route>
<routing-instance-name>ipv4</routing-instance-name>
</fib-route>
</rpc>
```
In the backend, a callback is registered (fib_route()) which handles the RPC.
```
static int
fib_route(clicon_handle h,
cxobj *xe, /* Request: <rpc><xn></rpc> */
cbuf *cbret, /* Reply eg <rpc-reply>... */
void *arg, /* Client session */
void *regarg) /* Argument given at register */
{
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
return 0;
}
int
clixon_plugin_init(clicon_handle h)
{
...
rpc_callback_register(h, fib_route, NULL, "fib-route");
...
}
```
## State data
Netconf <get> and restconf GET also returns state data, in contrast to
config data.
p
In YANG state data is specified with "config false;". In the example, interface-state is state data.
To return state data, you need to write a backend state data callback
with the name "plugin_statedata" where you return an XML tree with
state. This is then merged with config data by the system.
A static example of returning state data is in the example. Note that
a real example would poll or get the interface counters via a system
call, as well as use the "xpath" argument to identify the requested
state data.
## Authentication and NACM
The example contains some stubs for authorization according to [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341):
* A basic auth HTTP callback, see: example_restconf_credentials() containing three example users: andy, wilma, and guest, according to the examples in Appendix A in [RFC8341](https://tools.ietf.org/html/rfc8341).
* A NACM backend plugin reporting the mandatory NACM state variables.
## Systemd files
Example systemd files for backend and restconf daemons are found under the systemd directory. Install them under /etc/systemd/system for example.
## Docker
Run the example as a docker container and access it from a host CLI as follows:
```
ID=$(sudo docker run -td olofhagsand/clixon_example)
IP=$(sudo docker inspect -f '{{.NetworkSettings.IPAddress }}' $ID)
clixon_cli -a IPv4 -u $IP -f ./example.xml
```
Build the container and push yourself: First change the IMAGE variable in Makefile (eg to "you/clixon_example). Then build and push:
```
make docker
make push
sudo docker run -ti --rm you/clixon_example
```
Note that the configuration database is internal in the container, so
it is deleted if the container is restarted. To make the configuration
database persistent, you need to mount running_db using `-v`

View file

@ -26,6 +26,10 @@ module example {
}
/* Translation function example - See also example_cli */
list translate{
key k;
leaf k{
type string;
}
leaf value{
type string;
}
@ -73,13 +77,55 @@ module example {
}
}
}
rpc debug {
description "Set debug level of backend. XXX should be in clixon-config";
rpc empty {
description "Smallest possible RPC with no input or output";
}
rpc example {
description "Some example input/output for testing RFC7950 7.14.
RPC simply echoes the input for debugging.";
input {
leaf level {
type uint32;
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;
}
}
}
}

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
@ -96,7 +96,7 @@ example_stream_timer(int fd,
int retval = -1;
clicon_handle h = (clicon_handle)arg;
/* XXX Change to actual netconf notifications */
/* XXX Change to actual netconf notifications and namespace */
if (stream_notify(h, "EXAMPLE", "<event><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event>") < 0)
goto done;
if (example_stream_timer_setup(h) < 0)
@ -129,9 +129,10 @@ fib_route(clicon_handle h, /* Clicon handle */
void *arg, /* Client session */
void *regarg) /* Argument given at register */
{
cprintf(cbret, "<rpc-reply><route>"
cprintf(cbret, "<rpc-reply><route xmlns=\"urn:ietf:params:xml:ns:yang:ietf-routing\">"
"<address-family>ipv4</address-family>"
"<next-hop><next-hop-list>2.3.4.5</next-hop-list></next-hop>"
"<source-protocol>static</source-protocol>"
"</route></rpc-reply>");
return 0;
}
@ -146,7 +147,7 @@ route_count(clicon_handle h,
void *arg,
void *regarg) /* Argument given at register */
{
cprintf(cbret, "<rpc-reply><number-of-routes>42</number-of-routes></rpc-reply>");
cprintf(cbret, "<rpc-reply><number-of-routes xmlns=\"urn:ietf:params:xml:ns:yang:ietf-routing\">42</number-of-routes></rpc-reply>");
return 0;
}
@ -157,7 +158,7 @@ route_count(clicon_handle h,
* in [RFC6241].
*/
static int
empty(clicon_handle h, /* Clicon handle */
empty_rpc(clicon_handle h, /* Clicon handle */
cxobj *xe, /* Request: <rpc><xn></rpc> */
cbuf *cbret, /* Reply eg <rpc-reply>... */
void *arg, /* client_entry */
@ -167,6 +168,39 @@ empty(clicon_handle h, /* Clicon handle */
return 0;
}
/*! More elaborate example RPC for testing
* The RPC returns the incoming parameters
*/
static int
example_rpc(clicon_handle h, /* Clicon handle */
cxobj *xe, /* Request: <rpc><xn></rpc> */
cbuf *cbret, /* Reply eg <rpc-reply>... */
void *arg, /* client_entry */
void *regarg) /* Argument given at register */
{
int retval = -1;
cxobj *x = NULL;
char *namespace;
/* get namespace from rpc name, return back in each output parameter */
if ((namespace = xml_find_type_value(xe, NULL, "xmlns", CX_ATTR)) == NULL){
clicon_err(OE_XML, ENOENT, "No namespace given in rpc %s", xml_name(xe));
goto done;
}
cprintf(cbret, "<rpc-reply>");
while ((x = xml_child_each(xe, x, CX_ELMNT)) != NULL) {
if (xmlns_set(x, NULL, namespace) < 0)
goto done;
if (clicon_xml2cbuf(cbret, x, 0, 0) < 0)
goto done;
}
cprintf(cbret, "</rpc-reply>");
retval = 0;
done:
return retval;
}
/*! Called to get state data from plugin
* @param[in] h Clicon handle
* @param[in] xpath String with XPATH syntax. or NULL for all
@ -196,7 +230,7 @@ example_statedata(clicon_handle h,
* Note this state needs to be accomanied by yang snippet
* above
*/
if (xml_parse_string("<state>"
if (xml_parse_string("<state xmlns=\"urn:example:clixon\">"
"<op>42</op>"
"</state>", NULL, &xstate) < 0)
goto done;
@ -225,19 +259,32 @@ example_reset(clicon_handle h,
{
int retval = -1;
cxobj *xt = NULL;
int ret;
cbuf *cbret = NULL;
if (xml_parse_string("<config><interfaces><interface>"
if (xml_parse_string("<config><interfaces xmlns=\"urn:ietf:params:xml:ns:yang:ietf-interfaces\"><interface>"
"<name>lo</name><type>ex:loopback</type>"
"</interface></interfaces></config>", NULL, &xt) < 0)
goto done;
/* Replace parent w fiorst child */
/* Replace parent w first child */
if (xml_rootchild(xt, 0, &xt) < 0)
goto done;
/* Merge user reset state */
if (xmldb_put(h, (char*)db, OP_MERGE, xt, NULL) < 0)
if ((cbret = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
/* Merge user reset state */
if ((ret = xmldb_put(h, (char*)db, OP_MERGE, xt, cbret)) < 0)
goto done;
if (ret == 0){
clicon_err(OE_XML, 0, "Error when writing to XML database: %s",
cbuf_get(cbret));
goto done;
}
retval = 0;
done:
if (cbret)
cbuf_free(cbret);
if (xt != NULL)
xml_free(xt);
return retval;
@ -315,22 +362,31 @@ clixon_plugin_init(clicon_handle h)
if (example_stream_timer_setup(h) < 0)
goto done;
/* Register callback for routing rpc calls */
/* Register callback for routing rpc calls
*/
if (rpc_callback_register(h, fib_route,
NULL,
"fib-route"/* Xml tag when callback is made */
) < 0)
goto done;
/* From ietf-routing.yang */
if (rpc_callback_register(h, route_count,
NULL,
"route-count"/* Xml tag when callback is made */
) < 0)
goto done;
if (rpc_callback_register(h, empty,
/* From example.yang (clicon) */
if (rpc_callback_register(h, empty_rpc,
NULL,
"empty"/* Xml tag when callback is made */
) < 0)
goto done;
if (rpc_callback_register(h, example_rpc,
NULL,
"example"/* Xml tag when callback is made */
) < 0)
goto done;
/* Return plugin API */
return &api;

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
@ -96,7 +96,7 @@ fib_route_rpc(clicon_handle h,
/* User supplied variable in CLI command */
instance = cvec_find(cvv, "instance"); /* get a cligen variable from vector */
/* Create XML for fib-route netconf RPC */
if (xml_parse_va(&xtop, NULL, "<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" username=\"%s\"><fib-route xmlns=\"urn:ietf:params:xml:ns:yang:ietf-routing\"><routing-instance-name>%s</routing-instance-name></fib-route></rpc>",
if (xml_parse_va(&xtop, NULL, "<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" username=\"%s\"><fib-route xmlns=\"urn:ietf:params:xml:ns:yang:ietf-routing\"><routing-instance-name>%s</routing-instance-name><destination-address><address-family>ipv4</address-family></destination-address></fib-route></rpc>",
clicon_username_get(h),
cv_string_get(instance)) < 0)
goto done;
@ -110,7 +110,7 @@ fib_route_rpc(clicon_handle h,
goto done;
}
/* Print result */
xml_print(stdout, xml_child_i(xret, 0));
xml2txt(stdout, xml_child_i(xret, 0), 0);
retval = 0;
done:
if (xret)

View file

@ -24,7 +24,7 @@ debug("Debugging parts of the system"), cli_debug_cli((int32)1);{
}
copy("Copy and create a new object") {
interface("Copy interface"){
<name:string expand_dbvar("candidate","/interfaces/interface=%s/name")>("name of interface to copy from") to("Copy to interface") <toname:string>("Name of interface to copy to"), cli_copy_config("candidate","//interface[%s='%s']","name","name","toname");
<name:string expand_dbvar("candidate","/ietf-interfaces:interfaces/interface=%s/name")>("name of interface to copy from") to("Copy to interface") <toname:string>("Name of interface to copy to"), cli_copy_config("candidate","//interface[%s='%s']","name","name","toname");
}
}
discard("Discard edits (rollback 0)"), discard_changes();

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
@ -71,7 +71,7 @@ int netconf_client_rpc(clicon_handle h,
void *regarg)
{
clicon_debug(1, "%s restconf", __FUNCTION__);
cprintf(cbret, "<rpc-reply><result>ok</result></rpc-reply>");
cprintf(cbret, "<rpc-reply><result xmlns=\"urn:example:clixon\">ok</result></rpc-reply>");
return 0;
}

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
@ -275,7 +275,7 @@ restconf_client_rpc(clicon_handle h,
{
// FCGX_Request *r = (FCGX_Request *)arg;
clicon_debug(1, "%s", __FUNCTION__);
cprintf(cbret, "<rpc-reply><result>ok</result></rpc-reply>");
cprintf(cbret, "<rpc-reply><result xmlns=\"urn:example:clixon\">ok</result></rpc-reply>");
return 0;
}

View file

@ -1,7 +1,7 @@
#
# ***** BEGIN LICENSE BLOCK *****
#
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
#
# This file is part of CLIXON
#

View file

@ -1,7 +1,7 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
@ -51,11 +51,6 @@ int strverscmp (__const char *__s1, __const char *__s2);
*/
#define XMLNS_YANG_ONLY 1
/* Set for full XML namespace code in XML, NETCONF and YANG
* Experimental
*/
#undef ENABLE_XMLNS
/* If set, patch all CLI spec calls to @datamodel:tree to @datamodel.
* This is a backward compatible fix for 3.9 for CLIgen specification files
* using model generation (CLIXON_CLI_GENMODEL).

View file

@ -1,7 +1,7 @@
#
# ***** BEGIN LICENSE BLOCK *****
#
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
#
# This file is part of CLIXON
#

View file

@ -1,7 +1,7 @@
#
# ***** BEGIN LICENSE BLOCK *****
#
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
#
# This file is part of CLIXON
#

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
@ -43,6 +43,7 @@ int xml2json_cbuf(cbuf *cb, cxobj *x, int pretty);
int xml2json_cbuf_vec(cbuf *cb, cxobj **vec, size_t veclen, int pretty);
int xml2json(FILE *f, cxobj *x, int pretty);
int xml2json_vec(FILE *f, cxobj **vec, size_t veclen, int pretty);
int json2xml_ns(yang_spec *yspec, cxobj *x, cxobj **xerr);
int json_parse_str(char *str, cxobj **xt);
int json_parse_file(int fd, yang_spec *yspec, cxobj **xt);

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
@ -46,10 +46,14 @@ int netconf_too_big(cbuf *cb, char *type, char *message);
int netconf_missing_attribute(cbuf *cb, char *type, char *info, char *message);
int netconf_bad_attribute(cbuf *cb, char *type, char *info, char *message);
int netconf_unknown_attribute(cbuf *cb, char *type, char *info, char *message);
int netconf_missing_element(cbuf *cb, char *type, char *info, char *message);
int netconf_bad_element(cbuf *cb, char *type, char *info, char *message);
int netconf_unknown_element(cbuf *cb, char *type, char *info, char *message);
int netconf_unknown_namespace(cbuf *cb, char *type, char *info, char *message);
int netconf_missing_element(cbuf *cb, char *type, char *element, char *message);
int netconf_missing_element_xml(cxobj **xret, char *type, char *element, char *message);
int netconf_bad_element(cbuf *cb, char *type, char *info, char *element);
int netconf_bad_element_xml(cxobj **xret, char *type, char *info, char *element);
int netconf_unknown_element(cbuf *cb, char *type, char *element, char *message);
int netconf_unknown_element_xml(cxobj **xret, char *type, char *element, char *message);
int netconf_unknown_namespace(cbuf *cb, char *type, char *namespace, char *message);
int netconf_unknown_namespace_xml(cxobj **xret, char *type, char *namespace, char *message);
int netconf_access_denied(cbuf *cb, char *type, char *message);
int netconf_access_denied_xml(cxobj **xret, char *type, char *message);
int netconf_lock_denied(cbuf *cb, char *info, char *message);

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
@ -80,6 +80,10 @@ enum startup_mode_t{
/* Print registry on file. For debugging. */
void clicon_option_dump(clicon_handle h, int dblevel);
/* Add a clicon options overriding file setting */
int clicon_option_add(clicon_handle h, char *name, char *value);
/* Initialize options: set defaults, read config-file, etc */
int clicon_options_main(clicon_handle h, yang_spec *yspec);
@ -174,10 +178,8 @@ int clicon_dbspec_yang_set(clicon_handle h, struct yang_spec *ys);
cxobj * clicon_nacm_ext(clicon_handle h);
int clicon_nacm_ext_set(clicon_handle h, cxobj *xn);
#if 1 /* Temporary function until "Top-level Yang symbol cannot be called "config"" is fixed */
yang_spec * clicon_config_yang(clicon_handle h);
int clicon_config_yang_set(clicon_handle h, struct yang_spec *ys);
#endif
cxobj *clicon_conf_xml(clicon_handle h);
int clicon_conf_xml_set(clicon_handle h, cxobj *x);

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
@ -66,7 +66,7 @@ struct clicon_msg *clicon_msg_encode(char *format, ...) __attribute__ ((format (
#else
struct clicon_msg *clicon_msg_encode(char *format, ...);
#endif
int clicon_msg_decode(struct clicon_msg *msg, cxobj **xml);
int clicon_msg_decode(struct clicon_msg *msg, yang_spec *yspec, cxobj **xml);
int clicon_connect_unix(char *sockpath);

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.

Some files were not shown because too many files have changed in this diff Show more