Merge branch 'develop'
This commit is contained in:
commit
ae8d28fae8
240 changed files with 15417 additions and 17103 deletions
9
.gitignore
vendored
9
.gitignore
vendored
|
|
@ -12,12 +12,14 @@ etc/Makefile
|
|||
example/Makefile
|
||||
lib/Makefile
|
||||
lib/*/Makefile
|
||||
test/Makefile
|
||||
yang/Makefile
|
||||
yang/*/Makefile
|
||||
autom4te.cache/
|
||||
|
||||
clixon.conf.cpp
|
||||
clixon.mk
|
||||
config.log
|
||||
config.status
|
||||
TAGS
|
||||
|
||||
apps/backend/clixon_backend
|
||||
apps/backend/test
|
||||
|
|
@ -44,3 +46,6 @@ lib/clixon/clixon.h
|
|||
build-root/*.tar.xz
|
||||
build-root/*.rpm
|
||||
build-root/rpmbuild
|
||||
|
||||
test/site.sh
|
||||
doc/html
|
||||
11
.travis.yml
Normal file
11
.travis.yml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
language: c
|
||||
# safelist
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- develop
|
||||
before_script:
|
||||
- sudo apt-get install -y libfcgi-dev
|
||||
- ./test/travis/before_script.sh
|
||||
|
||||
|
||||
167
CHANGELOG.md
167
CHANGELOG.md
|
|
@ -1,5 +1,170 @@
|
|||
# Clixon Changelog
|
||||
|
||||
## 3.9.0 (Preliminary Target: February 2019)
|
||||
|
||||
### Major New features
|
||||
1. Correct XML namespace handling
|
||||
* According to [XML 1.0](https://www.w3.org/TR/2009/REC-xml-names-20091208) in restconf and Netconf.
|
||||
* Remaining deviations from strict namespace handling:
|
||||
* edit-config xpath select statement does not support namespaces
|
||||
* notifications do not support namespaces.
|
||||
* ietf-netconf base syntax is default `urn:ietf:params:xml:ns:netconf:base:1.0` and may not be explicitly given. However, in future versions this may be mandatory.
|
||||
* CLI syntax (ie generated commands) do not have namespaces.
|
||||
|
||||
* The following example shows changes in netconf and restconf:
|
||||
* Wrong Netconf RPC:
|
||||
```
|
||||
<rpc>
|
||||
<my-own-method/>
|
||||
</rpc>
|
||||
<rpc-reply>
|
||||
<route>
|
||||
<address-family>ipv4</address-family>
|
||||
</route>
|
||||
</rpc-reply>
|
||||
```
|
||||
* 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>
|
||||
```
|
||||
* Example: Correct restconf request:
|
||||
```
|
||||
POST http://localhost/restconf/operations/example:example)
|
||||
Content-Type: application/yang-data+json
|
||||
{
|
||||
"example:input":{
|
||||
"x":0
|
||||
}
|
||||
}
|
||||
```
|
||||
* Example: correct Restconf 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]
|
||||
1. Yang 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.
|
||||
* 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.
|
||||
1. NACM (RFC8341)
|
||||
* Experimental support, no performance enhancements and need further testing
|
||||
* Incoming RPC Message validation is supported (3.4.4)
|
||||
* Data Node Access validation is supported (3.4.5), except:
|
||||
* rule-type data-node path is not supported
|
||||
* Outgoing noitification aithorization is _not_ supported (3.4.6)
|
||||
* RPC:s are supported _except_:
|
||||
* `copy-config`for other src/target combinations than running/startup (3.2.6)
|
||||
* `commit` - NACM is applied to candidate and running operations only (3.2.8)
|
||||
* Client-side RPC:s are _not_ supported.
|
||||
* Recovery user "_nacm_recovery" added.
|
||||
|
||||
### API changes on existing features (you may need to change your code)
|
||||
* Added `username` argument to `xmldb_put()` datastore function for NACM data-node write checks
|
||||
* Rearranged yang files
|
||||
* Moved and updated all standard ietf and iana yang files from example and yang/ to `yang/standard`.
|
||||
* Moved clixon yang files from yang to `yang/clixon`
|
||||
* New configure option to disable standard yang files: `./configure --disable-stdyangs`
|
||||
* This is to make it easier to use standard IETF/IANA yang files in separate directory
|
||||
* Renamed example yang from example.yang -> clixon-example.yang
|
||||
* clixon_cli -p (printspec) changed semantics to add new yang path dir (see minor changes).
|
||||
* 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.yang` to `clixon-rfc5277.yang`.
|
||||
* 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
|
||||
* Example: In `choice c{ mandatory true; leaf x; }`, `x` was not previously enforced but is now.
|
||||
* 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.
|
||||
* XML namespace handling is corrected (see major changes)
|
||||
* 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
|
||||
* If you generate CLI code from the model (CLIXON_CLI_GENMODEL).
|
||||
* For backward compatibility, define CLICON_CLI_MODEL_TREENAME_PATCH in clixon_custom.h
|
||||
|
||||
### Minor changes
|
||||
* Change GIT branch handling to a single working master branch
|
||||
* Develop branched abandoned
|
||||
* Travis CI supported, see [https://travis-ci.org/clicon/clixon]
|
||||
* XML parser conformance to W3 spec
|
||||
* Names lexically correct (NCName)
|
||||
* Syntactically Correct handling of '<?' (processing instructions) and '<?xml' (XML declaration)
|
||||
* XML prolog syntax for 'well-formed' XML
|
||||
* `<!DOCTYPE` (ie DTD) is not supported.
|
||||
* Added `make test` from top-level Makefile
|
||||
* Added `xml_rootchild_node()` lib function as variant of `xml_rootchild()`
|
||||
* 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.
|
||||
* Added -p <dir> command-line option to all programs: backend, cli, netconf, restconf.
|
||||
* -p adds a new dir to the yang path dir. (same as -o CLICON_YAN_DIR=<dir>)
|
||||
* 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. This fixes the most acute problems, but 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()` and `xml_namespace_set()` to `xml_prefix()` and `xml_prefix_set()`, respectively.
|
||||
* Changed all make tags --> make TAGS
|
||||
* Keyvalue datastore removed (it has been disabled since 3.3.3)
|
||||
* New config option: CLICON_CLI_MODEL_TREENAME defining name of generated syntax tree if CLIXON_CLI_GENMODEL is set.
|
||||
|
||||
### Corrected Bugs
|
||||
* Partially corrected: [yang type range statement does not support multiple values](https://github.com/clicon/clixon/issues/59).
|
||||
* Should work for netconf and restconf, but not for CLI.
|
||||
* Fixed: [Range parsing is not RFC 7950 compliant](https://github.com/clicon/clixon/issues/71)
|
||||
* xml_cmp() compares numeric nodes based on string value [https://github.com/clicon/clixon/issues/64]
|
||||
* xml_cmp() respects 'ordered-by user' for state nodes, which violates RFC 7950 [https://github.com/clicon/clixon/issues/63]. (Thanks JDL)
|
||||
* XML<>JSON conversion problems [https://github.com/clicon/clixon/issues/66]
|
||||
* CDATA sections stripped from XML when converted to JSON
|
||||
* Restconf returns error when RPC generates "ok" reply [https://github.com/clicon/clixon/issues/69]
|
||||
* xsd regular expression support for character classes [https://github.com/clicon/clixon/issues/68]
|
||||
* added support for \c, \d, \w, \W, \s, \S.
|
||||
* Removing newlines from XML data [https://github.com/clicon/clixon/issues/65]
|
||||
* Fixed [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)
|
||||
|
||||
## 3.8.0 (6 Nov 2018)
|
||||
|
||||
### Major New features
|
||||
|
|
@ -200,7 +365,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>
|
||||
```
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
* [How to work in git (branching)](#branching)
|
||||
* [How the meta-configure stuff works](#meta-configure)
|
||||
* [How to debug](#debug)
|
||||
* [New release](#new-release)
|
||||
|
||||
## Documentation
|
||||
How to document the code
|
||||
|
|
@ -30,10 +31,9 @@ How to document the code
|
|||
## Branching
|
||||
How to work in git (branching)
|
||||
|
||||
Basically follows: http://nvie.com/posts/a-successful-git-branching-model/
|
||||
only somewhat simplified:
|
||||
Try to keep a single master branch always working. Currently testing is made using [Travis CI](https://travis-ci.org/clicon/clixon).
|
||||
|
||||
Do commits in develop branch. When done, merge with master.
|
||||
However, releases are made periodically (ca every 3 months) which is more tested.
|
||||
|
||||
## How the meta-configure stuff works
|
||||
```
|
||||
|
|
@ -92,4 +92,14 @@ EOF
|
|||
valgrind --leak-check=full --show-leak-kinds=all clixon_netconf -qf /tmp/myconf.xml -y /tmp/my.yang
|
||||
valgrind --tool=callgrind clixon_netconf -qf /tmp/myconf.xml -y /tmp/my.yang
|
||||
sudo kcachegrind
|
||||
```
|
||||
```
|
||||
|
||||
## 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"
|
||||
* git push origin <version>
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
Copyright 2009-2018 Olof Hagsand and Benny Holmgren
|
||||
Copyright 2009-2019 Olof Hagsand and Benny Holmgren
|
||||
|
||||
CLIXON is dual license.
|
||||
|
||||
|
|
|
|||
10
Makefile.in
10
Makefile.in
|
|
@ -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
|
||||
#
|
||||
|
|
@ -54,7 +54,7 @@ SHELL = /bin/sh
|
|||
|
||||
SUBDIRS = lib apps include etc datastore util yang
|
||||
|
||||
.PHONY: doc all clean depend $(SUBDIRS) install loc TAGS .config.status docker
|
||||
.PHONY: doc example all clean depend $(SUBDIRS) install loc TAGS .config.status docker test
|
||||
|
||||
all: $(SUBDIRS)
|
||||
|
||||
|
|
@ -138,6 +138,12 @@ pkg-rpm: dist
|
|||
pkg-srpm: dist
|
||||
make -C extras/rpm srpm
|
||||
|
||||
example:
|
||||
(cd $@ && $(MAKE) $(MFLAGS) all)
|
||||
|
||||
test: #example
|
||||
(cd $@ && $(MAKE) $(MFLAGS) all)
|
||||
|
||||
docker:
|
||||
for i in docker; \
|
||||
do (cd $$i && $(MAKE) $(MFLAGS) $@); done
|
||||
|
|
|
|||
111
README.md
111
README.md
|
|
@ -1,3 +1,5 @@
|
|||
[](https://travis-ci.org/clicon/clixon)
|
||||
|
||||
# Clixon
|
||||
|
||||
Clixon is a YANG-based configuration manager, with interactive CLI,
|
||||
|
|
@ -5,7 +7,7 @@ NETCONF and RESTCONF interfaces, an embedded database and transaction
|
|||
support.
|
||||
|
||||
* [Background](#background)
|
||||
* [Frequently asked questions](doc/FAQ.md)
|
||||
* [Frequently asked questions (FAQ)](doc/FAQ.md)
|
||||
* [Installation](#installation)
|
||||
* [Licenses](#licenses)
|
||||
* [Support](#support)
|
||||
|
|
@ -24,15 +26,16 @@ support.
|
|||
* [Clixon project page](http://www.clicon.org)
|
||||
* [Tests](test/)
|
||||
* [Docker](docker/)
|
||||
* [Reference manual](http://www.clicon.org/doxygen/index.html) (Note: the link may not be up-to-date. It is better to build your own: `cd doc; make doc`)
|
||||
* [Roadmap](ROADMAP.md)
|
||||
* [Reference manual](#reference)
|
||||
|
||||
Background
|
||||
==========
|
||||
|
||||
Clixon was implemented to provide an open-source generic configuration
|
||||
tool. The existing [CLIgen](http://www.cligen.se) tool was for command-lines only, while clixon is a system with configuration database, xml and rest interfaces. Most of the projects using clixon are for embedded network and measuring devices. But Clixon is more generic than that.
|
||||
tool. The existing [CLIgen](http://www.cligen.se) tool was for command-lines only, while Clixon is a system with configuration database, xml and rest interfaces all defined by Yang. Most of the projects using Clixon are for embedded network and measuring devices. But Clixon can be used for other systems as well due to its modular and pluggable architecture.
|
||||
|
||||
Users of clixon currently include:
|
||||
Users of Clixon currently include:
|
||||
* [Netgate](https://www.netgate.com)
|
||||
* [CloudMon360](http://cloudmon360.com)
|
||||
* [Grideye](http://hagsand.se/grideye)
|
||||
|
|
@ -97,13 +100,33 @@ XML
|
|||
Clixon has its own implementation of XML and XPATH implementation.
|
||||
|
||||
The standards covered include:
|
||||
- [XML](https://www.w3.org/TR/2008/REC-xml-20081126)
|
||||
- [Namespaces](https://www.w3.org/TR/2009/REC-xml-names-20091208)
|
||||
- [XPATH](https://www.w3.org/TR/xpath-10)
|
||||
- [XML 1.0](https://www.w3.org/TR/2008/REC-xml-20081126)
|
||||
- [Namespaces in XML 1.0](https://www.w3.org/TR/2009/REC-xml-names-20091208)
|
||||
- [XPATH 1.0](https://www.w3.org/TR/xpath-10)
|
||||
|
||||
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 at the heart of Clixon. Yang modules are used as a
|
||||
YANG and XML is the heart of Clixon. Yang modules are used as a
|
||||
specification for handling XML configuration data. The YANG spec is
|
||||
used to generate an interactive CLI, netconf and restconf clients. It
|
||||
also manages an XML datastore.
|
||||
|
|
@ -120,6 +143,12 @@ However, the following YANG syntax modules are not implemented:
|
|||
- action
|
||||
- belongs-to
|
||||
|
||||
Restrictions on Yang types are as follows:
|
||||
- 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
|
||||
=======
|
||||
Clixon implements the following NETCONF proposals or standards:
|
||||
|
|
@ -128,7 +157,14 @@ Clixon implements the following NETCONF proposals or standards:
|
|||
- [RFC 5277: NETCONF Event Notifications](http://www.rfc-base.org/txt/rfc-5277.txt)
|
||||
- [RFC 8341: Network Configuration Access Control Model](http://www.rfc-base.org/txt/rfc-8341.txt)
|
||||
|
||||
Clixon does not yet support the following netconf features:
|
||||
The following RFC6241 capabilities/features are hardcoded in Clixon:
|
||||
- :candidate (RFC6241 8.3)
|
||||
- :validate (RFC6241 8.6)
|
||||
- :startup (RFC6241 8.7)
|
||||
- :xpath (RFC6241 8.9)
|
||||
- :notification: (RFC5277)
|
||||
|
||||
Clixon does not support the following netconf features:
|
||||
|
||||
- :url capability
|
||||
- copy-config source config
|
||||
|
|
@ -137,6 +173,9 @@ Clixon does not yet 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
|
||||
|
|
@ -158,7 +197,7 @@ Datastore
|
|||
=========
|
||||
The Clixon datastore is a stand-alone XML based datastore. The idea is
|
||||
to be able to use different datastores backends with the same
|
||||
API.
|
||||
API. Currently only an XML plain text datastore is supported.
|
||||
|
||||
The datastore is primarily designed to be used by Clixon but can be used
|
||||
separately.
|
||||
|
|
@ -175,7 +214,7 @@ subsystem can be used.
|
|||
Restconf however needs credentials. This is done by writing a credentials callback in a restconf plugin. See:
|
||||
* [FAQ](doc/FAQ.md#how-do-i-write-an-authentication-callback).
|
||||
* [Example](example/README.md) has an example how to do this with HTTP basic auth.
|
||||
* I have done this for another project using Oauth2 or (https://github.com/CESNET/Netopeer2/tree/master/server/configuration)
|
||||
* It has been done for other projects using Oauth2 or (https://github.com/CESNET/Netopeer2/tree/master/server/configuration)
|
||||
|
||||
The clients send the ID of the user using a "username" attribute with
|
||||
the RPC calls to the backend. Note that the backend trusts the clients
|
||||
|
|
@ -183,36 +222,28 @@ so the clients can in principle fake a username.
|
|||
|
||||
NACM
|
||||
====
|
||||
Clixon includes an experimental Network Configuration Access Control Model (NACM) according to [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341). It has limited functionality.
|
||||
Clixon includes an experimental Network Configuration Access Control Model (NACM) according to [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341).
|
||||
|
||||
The support is as follows:
|
||||
To enable NACM:
|
||||
|
||||
* There is a yang config variable `CLICON_NACM_MODE` to set whether NACM is disabled, uses internal(embedded) NACM configuration, or external configuration. (See yang/clixon-config.yang)
|
||||
* If the mode is internal, NACM configurations is expected to be in the regular configuration, managed by regular candidate/runing/commit procedures. This mode may have some problems with bootstrapping.
|
||||
* The `CLICON_NACM_MODE` config variable is by default `disabled`.
|
||||
* If the mode is internal`, NACM configurations are expected to be in the regular configuration, managed by regular candidate/runing/commit procedures. This mode may have some problems with bootstrapping.
|
||||
* If the mode is `external`, the `CLICON_NACM_FILE` yang config variable contains the name of a separate configuration file containing the NACM configurations. After changes in this file, the backend needs to be restarted.
|
||||
* The [example](example/README.md) contains a http basic auth and a NACM backend callback for mandatory state variables.
|
||||
* There are two [tests](test/README.md) using internal and external NACM config
|
||||
* The backend provides a limited NACM support (when enabled) described below
|
||||
|
||||
NACM is implemented in the backend and a single access check is made
|
||||
in `from_client_msg()` when an internal netconf RPC has
|
||||
just been received and decoded. The code is in `nacm_access()`.
|
||||
The [example](example/README.md) contains a http basic auth and a NACM backend callback for mandatory state variables.
|
||||
|
||||
The functionality is as follows:
|
||||
* Notification is not supported
|
||||
* Groups are supported
|
||||
* Rule-lists are supported
|
||||
* Rules are supported as follows
|
||||
* module-name: Only '*' supported
|
||||
* access-operations: only '*' and 'exec' supported
|
||||
* rpc-name: fully supported (eg edit-config/get-config, etc)
|
||||
* action: fully supported (permit/deny)
|
||||
|
||||
The tests outlines an example of three groups (taken from the RFC): admin, limited and guest:
|
||||
* admin: Full access
|
||||
* limited: Read access (get and get-config)
|
||||
* guest: No access
|
||||
NACM is implemented in the backend with incoming RPC and data node access control points.
|
||||
|
||||
The functionality is as follows (references to sections in [RFC8341](https://tools.ietf.org/html/rfc8341)):
|
||||
* Access control point support:
|
||||
* Incoming RPC Message validation is supported (3.4.4)
|
||||
* Data Node Access validation is supported (3.4.5), except:
|
||||
* rule-type data-node path is not supported
|
||||
* Outgoing noitification aithorization is _not_ supported (3.4.6)
|
||||
* RPC:s are supported _except_:
|
||||
* `copy-config`for other src/target combinations than running/startup (3.2.6)
|
||||
* `commit` - NACM is applied to candidate and running operations only (3.2.8)
|
||||
* Client-side RPC:s are _not_ supported.
|
||||
|
||||
Runtime
|
||||
=======
|
||||
|
|
@ -221,3 +252,13 @@ Runtime
|
|||
|
||||
The figure shows the SDK runtime of Clixon.
|
||||
|
||||
Reference
|
||||
=========
|
||||
Clixon uses [Doxygen](http://www.doxygen.nl/index.html) for reference documentation.
|
||||
You need to install doxygen and graphviz on your system.
|
||||
Build it in the doc directory and point the browser to `.../clixon/doc/html/index.html` as follows:
|
||||
```
|
||||
> cd doc
|
||||
> make doc
|
||||
> make graphs # detailed callgraphs
|
||||
```
|
||||
35
ROADMAP.md
35
ROADMAP.md
|
|
@ -1,27 +1,42 @@
|
|||
# Clixon roadmap
|
||||
|
||||
Not in prio order (yet)
|
||||
## High prio
|
||||
- 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
|
||||
- (DONE) NACM (RFC 8341)
|
||||
- NACM support for create, read, update, delete operations
|
||||
- ACM support for specifying a module name other than '*'
|
||||
- (DONE)XML [Namespace handling](https://github.com/clicon/clixon/issues/49) (DONE)
|
||||
|
||||
- XML
|
||||
- [Namespace handling](https://github.com/clicon/clixon/issues/49)
|
||||
## Medium prio:
|
||||
- Support a plugin callback that is invoked when copy-config is called.
|
||||
- Preserve CLI command history across sessions. The up/down arrows
|
||||
- (DONE)Support for XML regex's.
|
||||
- Currently Posix extended regular expressions
|
||||
- (DONE) Input validation on custom RPCs/
|
||||
- [Sanity checks](https://github.com/clicon/clixon/issues/47)
|
||||
|
||||
## 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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
#
|
||||
|
|
@ -44,6 +44,7 @@ SHELL = /bin/sh
|
|||
SUBDIRS = backend
|
||||
SUBDIRS += cli
|
||||
SUBDIRS += netconf
|
||||
# See configure.ac
|
||||
ifeq ($(with_restconf),yes)
|
||||
SUBDIRS += restconf
|
||||
endif
|
||||
|
|
@ -80,5 +81,5 @@ distclean: clean
|
|||
for i in $(SUBDIRS); \
|
||||
do (cd $$i; $(MAKE) $(MFLAGS) $@); done
|
||||
|
||||
tags:
|
||||
TAGS:
|
||||
find $(srcdir) -name '*.[chyl]' -print | etags -
|
||||
|
|
|
|||
|
|
@ -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
|
||||
#
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
@ -177,14 +177,19 @@ netconf_db_find(cxobj *xn,
|
|||
static int
|
||||
from_client_get_config(clicon_handle h,
|
||||
cxobj *xe,
|
||||
char *username,
|
||||
cbuf *cbret)
|
||||
{
|
||||
int retval = -1;
|
||||
char *db;
|
||||
cxobj *xfilter;
|
||||
char *selector = "/";
|
||||
cxobj *xret = NULL;
|
||||
cbuf *cbx = NULL; /* Assist cbuf */
|
||||
int retval = -1;
|
||||
char *db;
|
||||
cxobj *xfilter;
|
||||
char *xpath = "/";
|
||||
cxobj *xret = NULL;
|
||||
cbuf *cbx = NULL; /* Assist cbuf */
|
||||
cxobj *xnacm = NULL;
|
||||
cxobj **xvec = NULL;
|
||||
size_t xlen;
|
||||
int ret;
|
||||
|
||||
if ((db = netconf_db_find(xe, "source")) == NULL){
|
||||
clicon_err(OE_XML, 0, "db not found");
|
||||
|
|
@ -201,13 +206,23 @@ from_client_get_config(clicon_handle h,
|
|||
goto ok;
|
||||
}
|
||||
if ((xfilter = xml_find(xe, "filter")) != NULL)
|
||||
if ((selector = xml_find_value(xfilter, "select"))==NULL)
|
||||
selector="/";
|
||||
if (xmldb_get(h, db, selector, 1, &xret) < 0){
|
||||
if ((xpath = xml_find_value(xfilter, "select"))==NULL)
|
||||
xpath="/";
|
||||
if (xmldb_get(h, db, xpath, 1, &xret) < 0){
|
||||
if (netconf_operation_failed(cbret, "application", "read registry")< 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
/* Pre-NACM access step */
|
||||
if ((ret = nacm_access_pre(h, username, &xnacm)) < 0)
|
||||
goto done;
|
||||
if (ret == 0){ /* Do NACM validation */
|
||||
if (xpath_vec(xret, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
|
||||
goto done;
|
||||
/* NACM datanode/module read validation */
|
||||
if (nacm_datanode_read(xret, xvec, xlen, username, xnacm) < 0)
|
||||
goto done;
|
||||
}
|
||||
cprintf(cbret, "<rpc-reply>");
|
||||
if (xret==NULL)
|
||||
cprintf(cbret, "<data/>");
|
||||
|
|
@ -221,6 +236,10 @@ from_client_get_config(clicon_handle h,
|
|||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
if (xnacm)
|
||||
xml_free(xnacm);
|
||||
if (xvec)
|
||||
free(xvec);
|
||||
if (cbx)
|
||||
cbuf_free(cbx);
|
||||
if (xret)
|
||||
|
|
@ -292,7 +311,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,15 +328,15 @@ 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)
|
||||
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)
|
||||
goto done;
|
||||
if (clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895") &&
|
||||
(retval = yang_modules_state_get(h, yspec, xret)) != 0)
|
||||
goto done;
|
||||
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"))
|
||||
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"))
|
||||
if ((retval = yang_modules_state_get(h, yspec, xret)) != 0)
|
||||
goto done;
|
||||
if ((retval = clixon_plugin_statedata(h, yspec, xpath, xret)) != 0)
|
||||
goto done;
|
||||
/* Code complex to filter out anything that is outside of xpath */
|
||||
|
|
@ -339,7 +358,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);
|
||||
|
|
@ -356,6 +375,7 @@ client_statedata(clicon_handle h,
|
|||
static int
|
||||
from_client_get(clicon_handle h,
|
||||
cxobj *xe,
|
||||
char *username,
|
||||
cbuf *cbret)
|
||||
{
|
||||
int retval = -1;
|
||||
|
|
@ -363,8 +383,10 @@ from_client_get(clicon_handle h,
|
|||
char *xpath = "/";
|
||||
cxobj *xret = NULL;
|
||||
int ret;
|
||||
cbuf *cbx = NULL; /* Assist cbuf */
|
||||
|
||||
cxobj **xvec = NULL;
|
||||
size_t xlen;
|
||||
cxobj *xnacm = NULL;
|
||||
|
||||
if ((xfilter = xml_find(xe, "filter")) != NULL)
|
||||
if ((xpath = xml_find_value(xfilter, "select"))==NULL)
|
||||
xpath="/";
|
||||
|
|
@ -379,33 +401,38 @@ 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 (xret==NULL)
|
||||
cprintf(cbret, "<data/>");
|
||||
else{
|
||||
if (xml_name_set(xret, "data") < 0)
|
||||
goto done;
|
||||
if (clicon_xml2cbuf(cbret, xret, 0, 0) < 0)
|
||||
goto done;
|
||||
}
|
||||
cprintf(cbret, "</rpc-reply>");
|
||||
}
|
||||
else { /* 1 Error from callback */
|
||||
if ((cbx = cbuf_new()) == NULL){
|
||||
clicon_err(OE_XML, errno, "cbuf_new");
|
||||
if (ret == 1){ /* Error from callback (error in xret) */
|
||||
if (clicon_xml2cbuf(cbret, xret, 0, 0) < 0)
|
||||
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));
|
||||
goto ok;
|
||||
}
|
||||
/* Pre-NACM access step */
|
||||
if ((ret = nacm_access_pre(h, username, &xnacm)) < 0)
|
||||
goto done;
|
||||
if (ret == 0){ /* Do NACM validation */
|
||||
if (xpath_vec(xret, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
|
||||
goto done;
|
||||
/* NACM datanode/module read validation */
|
||||
if (nacm_datanode_read(xret, xvec, xlen, username, xnacm) < 0)
|
||||
goto done;
|
||||
}
|
||||
cprintf(cbret, "<rpc-reply>"); /* OK */
|
||||
if (xret==NULL)
|
||||
cprintf(cbret, "<data/>");
|
||||
else{
|
||||
if (xml_name_set(xret, "data") < 0)
|
||||
goto done;
|
||||
if (clicon_xml2cbuf(cbret, xret, 0, 0) < 0)
|
||||
goto done;
|
||||
}
|
||||
cprintf(cbret, "</rpc-reply>");
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
if (cbx)
|
||||
cbuf_free(cbx);
|
||||
if (xnacm)
|
||||
xml_free(xnacm);
|
||||
if (xvec)
|
||||
free(xvec);
|
||||
if (xret)
|
||||
xml_free(xret);
|
||||
return retval;
|
||||
|
|
@ -422,6 +449,7 @@ static int
|
|||
from_client_edit_config(clicon_handle h,
|
||||
cxobj *xn,
|
||||
int mypid,
|
||||
char *username,
|
||||
cbuf *cbret)
|
||||
{
|
||||
int retval = -1;
|
||||
|
|
@ -433,14 +461,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");
|
||||
goto done;
|
||||
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");
|
||||
|
|
@ -467,12 +497,18 @@ from_client_edit_config(clicon_handle h,
|
|||
goto ok;
|
||||
}
|
||||
}
|
||||
if ((xc = xpath_first(xn, "config")) == NULL){
|
||||
if (netconf_missing_element(cbret, "protocol", "<bad-element>config</bad-element>", NULL) < 0)
|
||||
if ((xc = xpath_first(xn, "config")) == NULL){
|
||||
if (netconf_missing_element(cbret, "protocol", "config", NULL) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
else{
|
||||
/* <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?)
|
||||
* Maybe validate xml here as in text_modify_top?
|
||||
*/
|
||||
if (xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec) < 0)
|
||||
goto done;
|
||||
if (xml_apply(xc, CX_ELMNT, xml_non_config_data, &non_config) < 0)
|
||||
|
|
@ -485,24 +521,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, username, 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;
|
||||
}
|
||||
assert(cbuf_len(cbret) == 0);
|
||||
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
|
||||
ok:
|
||||
if (!cbuf_len(cbret))
|
||||
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
|
||||
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
|
||||
|
|
@ -524,7 +563,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;
|
||||
}
|
||||
|
|
@ -583,7 +622,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;
|
||||
}
|
||||
|
|
@ -645,7 +684,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;
|
||||
}
|
||||
|
|
@ -689,6 +728,10 @@ from_client_kill_session(clicon_handle h,
|
|||
* @param[out] cbret Return xml value cligen buffer
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error. Send error message back to client.
|
||||
* NACM: If source running and target startup --> only exec permission
|
||||
* else:
|
||||
* - omit data nodes to which the client does not have read access
|
||||
* - access denied if user lacks create/delete/update
|
||||
*/
|
||||
static int
|
||||
from_client_copy_config(clicon_handle h,
|
||||
|
|
@ -703,7 +746,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;
|
||||
}
|
||||
|
|
@ -718,7 +761,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;
|
||||
}
|
||||
|
|
@ -771,7 +814,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;
|
||||
}
|
||||
|
|
@ -850,7 +893,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;
|
||||
}
|
||||
|
|
@ -858,7 +901,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;
|
||||
}
|
||||
|
|
@ -919,7 +962,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;
|
||||
}
|
||||
|
|
@ -935,272 +978,6 @@ from_client_debug(clicon_handle h,
|
|||
return retval;
|
||||
}
|
||||
|
||||
/*! Match nacm access operations according to RFC8341 3.4.4.
|
||||
* Incoming RPC Message Validation Step 7 (c)
|
||||
* The rule's "access-operations" leaf has the "exec" bit set or
|
||||
* has the special value "*".
|
||||
* @retval 0 No match
|
||||
* @retval 1 Match
|
||||
* XXX access_operations is bit-fields
|
||||
*/
|
||||
static int
|
||||
nacm_match_access(char *access_operations,
|
||||
char *mode)
|
||||
{
|
||||
if (access_operations==NULL)
|
||||
return 0;
|
||||
if (strcmp(access_operations,"*")==0)
|
||||
return 1;
|
||||
if (strstr(access_operations, mode)!=NULL)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Match nacm single rule. Either match with access or deny. Or not match.
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] name rpc name
|
||||
* @param[in] xrule NACM rule XML tree
|
||||
* @param[out] cbret Cligen buffer result. Set to an error msg if retval=0.
|
||||
* @retval -1 Error
|
||||
* @retval 0 Matching rule AND Not access and cbret set
|
||||
* @retval 1 Matchung rule AND Access
|
||||
* @retval 2 No matching rule Goto step 10
|
||||
* From RFC8341 3.4.4. Incoming RPC Message Validation
|
||||
+---------+-----------------+---------------------+-----------------+
|
||||
| Method | Resource class | NETCONF operation | Access |
|
||||
| | | | operation |
|
||||
+---------+-----------------+---------------------+-----------------+
|
||||
| OPTIONS | all | none | none |
|
||||
| HEAD | all | <get>, <get-config> | read |
|
||||
| GET | all | <get>, <get-config> | read |
|
||||
| POST | datastore, data | <edit-config> | create |
|
||||
| POST | operation | specified operation | execute |
|
||||
| PUT | data | <edit-config> | create, update |
|
||||
| PUT | datastore | <copy-config> | update |
|
||||
| PATCH | data, datastore | <edit-config> | update |
|
||||
| DELETE | data | <edit-config> | delete |
|
||||
|
||||
7.(cont) A rule matches if all of the following criteria are met:
|
||||
* The rule's "module-name" leaf is "*" or equals the name of
|
||||
the YANG module where the protocol operation is defined.
|
||||
|
||||
* Either (1) the rule does not have a "rule-type" defined or
|
||||
(2) the "rule-type" is "protocol-operation" and the
|
||||
"rpc-name" is "*" or equals the name of the requested
|
||||
protocol operation.
|
||||
|
||||
* The rule's "access-operations" leaf has the "exec" bit set or
|
||||
has the special value "*".
|
||||
*/
|
||||
static int
|
||||
nacm_match_rule(clicon_handle h,
|
||||
char *name,
|
||||
cxobj *xrule,
|
||||
cbuf *cbret)
|
||||
{
|
||||
int retval = -1;
|
||||
// cxobj *x;
|
||||
char *module_name;
|
||||
char *rpc_name;
|
||||
char *access_operations;
|
||||
char *action;
|
||||
|
||||
module_name = xml_find_body(xrule, "module-name");
|
||||
rpc_name = xml_find_body(xrule, "rpc-name");
|
||||
/* XXX access_operations can be a set of bits */
|
||||
access_operations = xml_find_body(xrule, "access-operations");
|
||||
action = xml_find_body(xrule, "action");
|
||||
clicon_debug(1, "%s: %s %s %s %s", __FUNCTION__,
|
||||
module_name, rpc_name, access_operations, action);
|
||||
if (module_name && strcmp(module_name,"*")==0){
|
||||
if (nacm_match_access(access_operations, "exec")){
|
||||
if (rpc_name==NULL ||
|
||||
strcmp(rpc_name, "*")==0 || strcmp(rpc_name, name)==0){
|
||||
/* Here is a matching rule */
|
||||
if (action && strcmp(action, "permit")==0){
|
||||
retval = 1;
|
||||
goto done;
|
||||
}
|
||||
else{
|
||||
if (netconf_access_denied(cbret, "protocol", "access denied") < 0)
|
||||
goto done;
|
||||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
retval = 2; /* no matching rule */
|
||||
done:
|
||||
return retval;
|
||||
|
||||
}
|
||||
|
||||
/*! Make nacm access control
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] mode NACMmode, internal or external
|
||||
* @param[in] name rpc name
|
||||
* @param[in] username
|
||||
* @param[out] cbret Cligen buffer result. Set to an error msg if retval=0.
|
||||
* @retval -1 Error
|
||||
* @retval 0 Not access and cbret set
|
||||
* @retval 1 Access
|
||||
* From RFC8341 3.4.4. Incoming RPC Message Validation
|
||||
*/
|
||||
static int
|
||||
nacm_access(clicon_handle h,
|
||||
char *mode,
|
||||
char *name,
|
||||
char *username,
|
||||
cbuf *cbret)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj *xtop = NULL;
|
||||
cxobj *xacm;
|
||||
cxobj *x;
|
||||
cxobj *xrlist;
|
||||
cxobj *xrule;
|
||||
char *enabled = NULL;
|
||||
cxobj **gvec = NULL; /* groups */
|
||||
size_t glen;
|
||||
cxobj **rlistvec = NULL; /* rule-list */
|
||||
size_t rlistlen;
|
||||
cxobj **rvec = NULL; /* rules */
|
||||
size_t rlen;
|
||||
int i, j;
|
||||
char *exec_default = NULL;
|
||||
int ret;
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
/* 0. If nacm-mode is external, get NACM defintion from separet tree,
|
||||
otherwise get it from internal configuration */
|
||||
if (strcmp(mode, "external")==0){
|
||||
if ((xtop = backend_nacm_list_get(h)) == NULL){
|
||||
clicon_err(OE_XML, 0, "No nacm external tree");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
else if (strcmp(mode, "internal")==0){
|
||||
if (xmldb_get(h, "running", "nacm", 0, &xtop) < 0)
|
||||
goto done;
|
||||
}
|
||||
else{
|
||||
clicon_err(OE_UNIX, 0, "Invalid NACM mode: %s", mode);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* 1. If the "enable-nacm" leaf is set to "false", then the protocol
|
||||
operation is permitted. (or config does not exist) */
|
||||
|
||||
if ((xacm = xpath_first(xtop, "nacm")) == NULL)
|
||||
goto permit;
|
||||
exec_default = xml_find_body(xacm, "exec-default");
|
||||
if ((x = xpath_first(xacm, "enable-nacm")) == NULL)
|
||||
goto permit;
|
||||
enabled = xml_body(x);
|
||||
if (strcmp(enabled, "true") != 0)
|
||||
goto permit;
|
||||
|
||||
/* 2. If the requesting session is identified as a recovery session,
|
||||
then the protocol operation is permitted. NYI */
|
||||
|
||||
/* 3. If the requested operation is the NETCONF <close-session>
|
||||
protocol operation, then the protocol operation is permitted.
|
||||
*/
|
||||
if (strcmp(name, "close-session") == 0)
|
||||
goto permit;
|
||||
/* 4. Check all the "group" entries to see if any of them contain a
|
||||
"user-name" entry that equals the username for the session
|
||||
making the request. (If the "enable-external-groups" leaf is
|
||||
"true", add to these groups the set of groups provided by the
|
||||
transport layer.) */
|
||||
if (username == NULL)
|
||||
goto step10;
|
||||
/* User's group */
|
||||
if (xpath_vec(xacm, "groups/group[user-name='%s']", &gvec, &glen, username) < 0)
|
||||
goto done;
|
||||
/* 5. If no groups are found, continue with step 10. */
|
||||
if (glen == 0)
|
||||
goto step10;
|
||||
/* 6. Process all rule-list entries, in the order they appear in the
|
||||
configuration. If a rule-list's "group" leaf-list does not
|
||||
match any of the user's groups, proceed to the next rule-list
|
||||
entry. */
|
||||
if (xpath_vec(xacm, "rule-list", &rlistvec, &rlistlen) < 0)
|
||||
goto done;
|
||||
for (i=0; i<rlistlen; i++){
|
||||
xrlist = rlistvec[i];
|
||||
/* Loop through user's group to find match in this rule-list */
|
||||
for (j=0; j<glen; j++){
|
||||
char *gname;
|
||||
gname = xml_find_body(gvec[j], "name");
|
||||
if (xpath_first(xrlist, ".[group='%s']", gname)!=NULL)
|
||||
break; /* found */
|
||||
}
|
||||
if (j==glen) /* not found */
|
||||
continue;
|
||||
/* 7. For each rule-list entry found, process all rules, in order,
|
||||
until a rule that matches the requested access operation is
|
||||
found.
|
||||
*/
|
||||
if (xpath_vec(xrlist, "rule", &rvec, &rlen) < 0)
|
||||
goto done;
|
||||
for (j=0; j<rlen; j++){
|
||||
xrule = rvec[j];
|
||||
/* -1 error, 0 deny, 1 permit, 2 continue */
|
||||
if ((ret = nacm_match_rule(h, name, xrule, cbret)) < 0)
|
||||
goto done;
|
||||
switch(ret){
|
||||
case 0: /* deny */
|
||||
goto deny;
|
||||
break;
|
||||
case 1: /* permit */
|
||||
goto permit;
|
||||
break;
|
||||
case 2: /* no match, continue */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
step10:
|
||||
/* 10. If the requested protocol operation is defined in a YANG module
|
||||
advertised in the server capabilities and the "rpc" statement
|
||||
contains a "nacm:default-deny-all" statement, then the protocol
|
||||
operation is denied. */
|
||||
/* 11. If the requested protocol operation is the NETCONF
|
||||
<kill-session> or <delete-config>, then the protocol operation
|
||||
is denied. */
|
||||
if (strcmp(name, "kill-session")==0 || strcmp(name, "delete-config")==0){
|
||||
if (netconf_access_denied(cbret, "protocol", "default deny") < 0)
|
||||
goto done;
|
||||
goto deny;
|
||||
}
|
||||
/* 12. If the "exec-default" leaf is set to "permit", then permit the
|
||||
protocol operation; otherwise, deny the request. */
|
||||
if (exec_default ==NULL || strcmp(exec_default, "permit")==0)
|
||||
goto permit;
|
||||
if (netconf_access_denied(cbret, "protocol", "default deny") < 0)
|
||||
goto done;
|
||||
goto deny;
|
||||
permit:
|
||||
retval = 1;
|
||||
done:
|
||||
clicon_debug(1, "%s retval:%d (0:deny 1:permit)", __FUNCTION__, retval);
|
||||
if (strcmp(mode, "internal")==0 && xtop)
|
||||
xml_free(xtop);
|
||||
if (gvec)
|
||||
free(gvec);
|
||||
if (rlistvec)
|
||||
free(rlistvec);
|
||||
if (rvec)
|
||||
free(rvec);
|
||||
return retval;
|
||||
deny: /* Here, cbret must contain a netconf error msg */
|
||||
assert(cbuf_len(cbret));
|
||||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*! An internal clicon message has arrived from a client. Receive and dispatch.
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] s Socket where message arrived. read from this.
|
||||
|
|
@ -1218,16 +995,21 @@ from_client_msg(clicon_handle h,
|
|||
cxobj *xt = NULL;
|
||||
cxobj *x;
|
||||
cxobj *xe;
|
||||
char *name = NULL;
|
||||
char *rpc = NULL;
|
||||
char *module = NULL;
|
||||
char *db;
|
||||
cbuf *cbret = NULL; /* return message */
|
||||
int pid;
|
||||
int ret;
|
||||
char *username;
|
||||
char *nacm_mode;
|
||||
|
||||
yang_spec *yspec;
|
||||
yang_stmt *ye;
|
||||
yang_stmt *ymod;
|
||||
cxobj *xnacm = NULL;
|
||||
|
||||
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.
|
||||
*/
|
||||
|
|
@ -1235,88 +1017,115 @@ 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;
|
||||
}
|
||||
|
||||
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 -
|
||||
* 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, cbret)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto reply;
|
||||
xe = NULL;
|
||||
username = xml_find_value(x, "username");
|
||||
/* May be used by callbacks, etc */
|
||||
clicon_username_set(h, username);
|
||||
while ((xe = xml_child_each(x, xe, CX_ELMNT)) != NULL) {
|
||||
name = xml_name(xe);
|
||||
clicon_debug(1, "%s name:%s", __FUNCTION__, name);
|
||||
/* Make NACM access control if enabled as "internal"*/
|
||||
nacm_mode = clicon_option_str(h, "CLICON_NACM_MODE");
|
||||
if (nacm_mode && strcmp(nacm_mode, "disabled") != 0){
|
||||
if ((ret = nacm_access(h, nacm_mode, name, username, cbret)) < 0)
|
||||
rpc = xml_name(xe);
|
||||
if ((ye = xml_spec(xe)) == NULL){
|
||||
if (netconf_operation_not_supported(cbret, "protocol", rpc) < 0)
|
||||
goto done;
|
||||
if (!ret)
|
||||
goto reply;
|
||||
}
|
||||
if ((ymod = ys_module(ye)) == NULL){
|
||||
clicon_err(OE_XML, ENOENT, "rpc yang does not have module");
|
||||
goto done;
|
||||
}
|
||||
module = ymod->ys_argument;
|
||||
clicon_debug(1, "%s module:%s rpc:%s", __FUNCTION__, module, rpc);
|
||||
/* Pre-NACM access step */
|
||||
xnacm = NULL;
|
||||
if ((ret = nacm_access_pre(h, username, &xnacm)) < 0)
|
||||
goto done;
|
||||
if (ret == 0){ /* Do NACM validation */
|
||||
/* NACM rpc operation exec validation */
|
||||
if ((ret = nacm_rpc(rpc, module, username, xnacm, cbret)) < 0)
|
||||
goto done;
|
||||
if (xnacm)
|
||||
xml_free(xnacm);
|
||||
if (ret == 0) /* Not permitted and cbret set */
|
||||
goto reply;
|
||||
}
|
||||
if (strcmp(name, "get-config") == 0){
|
||||
if (from_client_get_config(h, xe, cbret) <0)
|
||||
if (strcmp(rpc, "get-config") == 0){
|
||||
if (from_client_get_config(h, xe, username, cbret) <0)
|
||||
goto done;
|
||||
}
|
||||
else if (strcmp(name, "edit-config") == 0){
|
||||
if (from_client_edit_config(h, xe, pid, cbret) <0)
|
||||
else if (strcmp(rpc, "edit-config") == 0){
|
||||
if (from_client_edit_config(h, xe, pid, username, cbret) <0)
|
||||
goto done;
|
||||
}
|
||||
else if (strcmp(name, "copy-config") == 0){
|
||||
else if (strcmp(rpc, "copy-config") == 0){
|
||||
if (from_client_copy_config(h, xe, pid, cbret) <0)
|
||||
goto done;
|
||||
}
|
||||
else if (strcmp(name, "delete-config") == 0){
|
||||
else if (strcmp(rpc, "delete-config") == 0){
|
||||
if (from_client_delete_config(h, xe, pid, cbret) <0)
|
||||
goto done;
|
||||
}
|
||||
else if (strcmp(name, "lock") == 0){
|
||||
else if (strcmp(rpc, "lock") == 0){
|
||||
if (from_client_lock(h, xe, pid, cbret) < 0)
|
||||
goto done;
|
||||
}
|
||||
else if (strcmp(name, "unlock") == 0){
|
||||
else if (strcmp(rpc, "unlock") == 0){
|
||||
if (from_client_unlock(h, xe, pid, cbret) < 0)
|
||||
goto done;
|
||||
}
|
||||
else if (strcmp(name, "get") == 0){
|
||||
if (from_client_get(h, xe, cbret) < 0)
|
||||
else if (strcmp(rpc, "get") == 0){
|
||||
if (from_client_get(h, xe, username, cbret) < 0)
|
||||
goto done;
|
||||
}
|
||||
else if (strcmp(name, "close-session") == 0){
|
||||
else if (strcmp(rpc, "close-session") == 0){
|
||||
xmldb_unlock_all(h, pid);
|
||||
stream_ss_delete_all(h, ce_event_cb, (void*)ce);
|
||||
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
|
||||
}
|
||||
else if (strcmp(name, "kill-session") == 0){
|
||||
else if (strcmp(rpc, "kill-session") == 0){
|
||||
if (from_client_kill_session(h, xe, cbret) < 0)
|
||||
goto done;
|
||||
}
|
||||
else if (strcmp(name, "validate") == 0){
|
||||
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;
|
||||
}
|
||||
if (from_client_validate(h, db, cbret) < 0)
|
||||
goto done;
|
||||
}
|
||||
else if (strcmp(name, "commit") == 0){
|
||||
else if (strcmp(rpc, "commit") == 0){
|
||||
if (from_client_commit(h, pid, cbret) < 0)
|
||||
goto done;
|
||||
}
|
||||
else if (strcmp(name, "discard-changes") == 0){
|
||||
else if (strcmp(rpc, "discard-changes") == 0){
|
||||
if (from_client_discard_changes(h, pid, cbret) < 0)
|
||||
goto done;
|
||||
}
|
||||
else if (strcmp(name, "create-subscription") == 0){
|
||||
else if (strcmp(rpc, "create-subscription") == 0){
|
||||
if (from_client_create_subscription(h, xe, ce, cbret) < 0)
|
||||
goto done;
|
||||
}
|
||||
else if (strcmp(name, "debug") == 0){
|
||||
else if (strcmp(rpc, "debug") == 0){
|
||||
if (from_client_debug(h, xe, cbret) < 0)
|
||||
goto done;
|
||||
}
|
||||
|
|
@ -1370,7 +1179,7 @@ from_client_msg(clicon_handle h,
|
|||
/* Sanity: log if clicon_err() is not called ! */
|
||||
if (retval < 0 && clicon_errno < 0)
|
||||
clicon_log(LOG_NOTICE, "%s: Internal error: No clicon_err call on error (message: %s)",
|
||||
__FUNCTION__, name?name:"");
|
||||
__FUNCTION__, rpc?rpc:"");
|
||||
// clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
|
||||
return retval;// -1 here terminates backend
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
goto done;
|
||||
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
|
||||
* @note Need to differentiate between error and 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,70 @@ 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;
|
||||
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,
|
||||
clicon_username_get(h), 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
|
||||
|
|
@ -274,6 +323,10 @@ candidate_commit(clicon_handle h,
|
|||
* @param[out] cbret Return xml value cligen buffer
|
||||
* @retval 0 OK. This may indicate both ok and err msg back to client
|
||||
* @retval -1 (Local) Error
|
||||
* NACM: The server MUST determine the exact nodes in the running
|
||||
* configuration datastore that are actually different and only check
|
||||
* "create", "update", and "delete" access permissions for this set of
|
||||
* nodes, which could be empty.
|
||||
*/
|
||||
int
|
||||
from_client_commit(clicon_handle h,
|
||||
|
|
@ -283,6 +336,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 +350,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;
|
||||
}
|
||||
|
|
@ -317,6 +372,7 @@ from_client_commit(clicon_handle h,
|
|||
* @param[out] cbret Return xml value cligen buffer
|
||||
* @retval 0 OK. This may indicate both ok and err msg back to client
|
||||
* @retval -1 (Local) Error
|
||||
* NACM: No datastore permissions are needed.
|
||||
*/
|
||||
int
|
||||
from_client_discard_changes(clicon_handle h,
|
||||
|
|
@ -367,6 +423,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 +436,21 @@ 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 (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0)
|
||||
goto done;
|
||||
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)
|
||||
goto done;
|
||||
if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")){
|
||||
if ((ret = xmldb_put(h, "candidate", OP_REPLACE, td->td_target,
|
||||
clicon_username_get(h), cbret)) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
|
||||
ok:
|
||||
retval = 0;
|
||||
|
|
|
|||
|
|
@ -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_ */
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
@ -52,8 +52,4 @@ struct client_entry *backend_client_list(clicon_handle h);
|
|||
|
||||
int backend_client_delete(clicon_handle h, struct client_entry *ce);
|
||||
|
||||
int backend_nacm_list_set(clicon_handle h, cxobj *xnacm);
|
||||
|
||||
cxobj * backend_nacm_list_get(clicon_handle h);
|
||||
|
||||
#endif /* _BACKEND_HANDLE_H_ */
|
||||
|
|
|
|||
|
|
@ -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:p:b:Fza:u:P:1s:c:g:y:x:o:"
|
||||
|
||||
#define BACKEND_LOGFILE "/usr/local/var/clixon_backend.log"
|
||||
|
||||
|
|
@ -91,6 +91,8 @@ backend_terminate(clicon_handle h)
|
|||
yspec_free(yspec);
|
||||
if ((yspec = clicon_config_yang(h)) != NULL)
|
||||
yspec_free(yspec);
|
||||
if ((x = clicon_nacm_ext(h)) != NULL)
|
||||
xml_free(x);
|
||||
if ((x = clicon_conf_xml(h)) != NULL)
|
||||
xml_free(x);
|
||||
stream_publish_exit();
|
||||
|
|
@ -140,6 +142,7 @@ usage(clicon_handle h,
|
|||
"\t-f <file>\tCLICON config file\n"
|
||||
"\t-l (s|e|o|f<file>) Log on (s)yslog, std(e)rr or std(o)ut (stderr is default) Only valid if -F, if background syslog is on syslog.\n"
|
||||
"\t-d <dir>\tSpecify backend plugin directory (default: %s)\n"
|
||||
"\t-p <dir>\tYang directory path (see CLICON_YANG_DIR)\n"
|
||||
"\t-b <dir>\tSpecify XMLDB database directory\n"
|
||||
"\t-F\t\tRun in foreground, do not run as daemon\n"
|
||||
"\t-z\t\tKill other config daemon and exit\n"
|
||||
|
|
@ -152,7 +155,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",
|
||||
|
|
@ -174,22 +178,24 @@ 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;
|
||||
|
||||
int retval = -1;
|
||||
cxobj *xt = NULL;
|
||||
|
||||
/* Get data as xml from db1 */
|
||||
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, clicon_username_get(h), cbret);
|
||||
done:
|
||||
if (xt)
|
||||
xml_free(xt);
|
||||
|
|
@ -264,7 +270,7 @@ nacm_load_external(clicon_handle h)
|
|||
}
|
||||
if ((yspec = yspec_new()) == NULL)
|
||||
goto done;
|
||||
if (yang_parse(h, NULL, "ietf-netconf-acm", CLIXON_DATADIR, NULL, yspec, NULL) < 0)
|
||||
if (yang_spec_parse_module(h, "ietf-netconf-acm", NULL, yspec) < 0)
|
||||
goto done;
|
||||
fd = fileno(f);
|
||||
/* Read configfile */
|
||||
|
|
@ -274,7 +280,7 @@ nacm_load_external(clicon_handle h)
|
|||
clicon_err(OE_XML, 0, "No xml tree in %s", filename);
|
||||
goto done;
|
||||
}
|
||||
if (backend_nacm_list_set(h, xt) < 0)
|
||||
if (clicon_nacm_ext_set(h, xt) < 0)
|
||||
goto done;
|
||||
retval = 0;
|
||||
done:
|
||||
|
|
@ -286,18 +292,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;
|
||||
|
|
@ -308,9 +318,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, clicon_username_get(h), cbret);
|
||||
done:
|
||||
if (fd != -1)
|
||||
close(fd);
|
||||
|
|
@ -383,8 +391,13 @@ static int
|
|||
startup_mode_running(clicon_handle h,
|
||||
char *extraxml_file)
|
||||
{
|
||||
int retval = -1;
|
||||
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;
|
||||
|
|
@ -397,34 +410,46 @@ 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){
|
||||
/* (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__);
|
||||
/* Reinstate original */
|
||||
if (xmldb_copy(h, "candidate", "running") < 0)
|
||||
goto done;
|
||||
goto done;
|
||||
}
|
||||
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") < 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, "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
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/*! Clixon startup startup mode: Commit startup configuration into running state
|
||||
|
|
@ -450,10 +475,16 @@ startup -------------------------+--|
|
|||
*/
|
||||
static int
|
||||
startup_mode_startup(clicon_handle h,
|
||||
char *extraxml_file)
|
||||
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;
|
||||
|
|
@ -470,33 +501,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
|
||||
|
|
@ -504,7 +548,7 @@ main(int argc,
|
|||
char **argv)
|
||||
{
|
||||
int retval = -1;
|
||||
char c;
|
||||
int c;
|
||||
int zap;
|
||||
int foreground;
|
||||
int once;
|
||||
|
|
@ -521,13 +565,12 @@ 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;
|
||||
yang_spec *yspec = NULL;
|
||||
yang_spec *yspecfg = NULL; /* For config XXX clixon bug */
|
||||
char *yang_filename = NULL;
|
||||
char *str;
|
||||
|
||||
/* In the startup, logs to stderr & syslog and debug flag set later */
|
||||
clicon_log_init(__PROGRAM__, LOG_INFO, logdst);
|
||||
|
|
@ -612,12 +655,18 @@ main(int argc,
|
|||
case 'd': /* Plugin directory */
|
||||
if (!strlen(optarg))
|
||||
usage(h, argv[0]);
|
||||
clicon_option_str_set(h, "CLICON_BACKEND_DIR", optarg);
|
||||
if (clicon_option_add(h, "CLICON_BACKEND_DIR", optarg) < 0)
|
||||
goto done;
|
||||
break;
|
||||
case 'b': /* XMLDB database directory */
|
||||
if (!strlen(optarg))
|
||||
usage(h, argv[0]);
|
||||
clicon_option_str_set(h, "CLICON_XMLDB_DIR", optarg);
|
||||
if (clicon_option_add(h, "CLICON_XMLDB_DIR", optarg) < 0)
|
||||
goto done;
|
||||
break;
|
||||
case 'p' : /* yang dir path */
|
||||
if (clicon_option_add(h, "CLICON_YANG_DIR", optarg) < 0)
|
||||
goto done;
|
||||
break;
|
||||
case 'F' : /* foreground */
|
||||
foreground = 1;
|
||||
|
|
@ -626,21 +675,25 @@ main(int argc,
|
|||
zap++;
|
||||
break;
|
||||
case 'a': /* internal backend socket address family */
|
||||
clicon_option_str_set(h, "CLICON_SOCK_FAMILY", optarg);
|
||||
if (clicon_option_add(h, "CLICON_SOCK_FAMILY", optarg) < 0)
|
||||
goto done;
|
||||
break;
|
||||
case 'u': /* config unix domain path / ip address */
|
||||
if (!strlen(optarg))
|
||||
usage(h, argv[0]);
|
||||
clicon_option_str_set(h, "CLICON_SOCK", optarg);
|
||||
if (clicon_option_add(h, "CLICON_SOCK", optarg) < 0)
|
||||
goto done;
|
||||
break;
|
||||
case 'P': /* pidfile */
|
||||
clicon_option_str_set(h, "CLICON_BACKEND_PIDFILE", optarg);
|
||||
if (clicon_option_add(h, "CLICON_BACKEND_PIDFILE", optarg) < 0)
|
||||
goto done;
|
||||
break;
|
||||
case '1' : /* Quit after reading database once - dont wait for events */
|
||||
once = 1;
|
||||
break;
|
||||
case 's' : /* startup mode */
|
||||
clicon_option_str_set(h, "CLICON_STARTUP_MODE", optarg);
|
||||
if (clicon_option_add(h, "CLICON_STARTUP_MODE", optarg) < 0)
|
||||
goto done;
|
||||
if (clicon_startup_mode(h) < 0){
|
||||
fprintf(stderr, "Invalid startup mode: %s\n", optarg);
|
||||
usage(h, argv[0]);
|
||||
|
|
@ -650,14 +703,24 @@ main(int argc,
|
|||
extraxml_file = optarg;
|
||||
break;
|
||||
case 'g': /* config socket group */
|
||||
clicon_option_str_set(h, "CLICON_SOCK_GROUP", optarg);
|
||||
if (clicon_option_add(h, "CLICON_SOCK_GROUP", optarg) < 0)
|
||||
goto done;
|
||||
break;
|
||||
case 'y' :{ /* Load yang spec file (override yang main module) */
|
||||
yang_filename = optarg;
|
||||
case 'y' : /* Load yang absolute filename */
|
||||
if (clicon_option_add(h, "CLICON_YANG_MAIN_FILE", optarg) < 0)
|
||||
goto done;
|
||||
break;
|
||||
}
|
||||
case 'x' :{ /* xmldb plugin */
|
||||
clicon_option_str_set(h, "CLICON_XMLDB_PLUGIN", optarg);
|
||||
case 'x' : /* xmldb plugin */
|
||||
if (clicon_option_add(h, "CLICON_XMLDB_PLUGIN", 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:
|
||||
|
|
@ -750,18 +813,23 @@ main(int argc,
|
|||
if ((yspec = yspec_new()) == NULL)
|
||||
goto done;
|
||||
clicon_dbspec_yang_set(h, yspec);
|
||||
/* Load main application yang specification either module or specific file
|
||||
* If -y <file> is given, it overrides main module */
|
||||
if (yang_filename){
|
||||
if (yang_spec_parse_file(h, yang_filename, clicon_yang_dir(h), yspec, NULL) < 0)
|
||||
/* Load Yang modules
|
||||
* 1. Load a yang module as a specific absolute filename */
|
||||
if ((str = clicon_yang_main_file(h)) != NULL)
|
||||
if (yang_spec_parse_file(h, str, yspec) < 0)
|
||||
goto done;
|
||||
}
|
||||
else if (yang_spec_parse_module(h, clicon_yang_module_main(h),
|
||||
clicon_yang_dir(h),
|
||||
clicon_yang_module_revision(h),
|
||||
yspec, NULL) < 0)
|
||||
/* 2. Load a (single) main module */
|
||||
if ((str = clicon_yang_module_main(h)) != NULL)
|
||||
if (yang_spec_parse_module(h, str, clicon_yang_module_revision(h),
|
||||
yspec) < 0)
|
||||
goto done;
|
||||
/* 3. Load all modules in a directory */
|
||||
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;
|
||||
|
|
@ -770,11 +838,11 @@ main(int argc,
|
|||
goto done;
|
||||
/* Load yang Restconf stream discovery */
|
||||
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040") &&
|
||||
yang_spec_parse_module(h, "ietf-restconf-monitoring", CLIXON_DATADIR, NULL, yspec, NULL)< 0)
|
||||
yang_spec_parse_module(h, "ietf-restconf-monitoring", NULL, yspec)< 0)
|
||||
goto done;
|
||||
/* Load yang Netconf stream discovery */
|
||||
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277") &&
|
||||
yang_spec_parse_module(h, "ietf-netconf-notification", CLIXON_DATADIR, NULL, yspec, NULL)< 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)
|
||||
|
|
@ -787,9 +855,12 @@ 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)
|
||||
goto done;
|
||||
if (xmldb_setopt(h, "pretty", (void*)(intptr_t)clicon_option_bool(h, "CLICON_XMLDB_PRETTY")) < 0)
|
||||
goto done;
|
||||
if (xmldb_setopt(h, "nacm_mode", (void*)nacm_mode) < 0)
|
||||
goto done;
|
||||
if (xmldb_setopt(h, "nacm_xtree", (void*)clicon_nacm_ext(h)) < 0)
|
||||
goto done;
|
||||
/* Startup mode needs to be defined, */
|
||||
startup_mode = clicon_startup_mode(h);
|
||||
if (startup_mode == -1){
|
||||
|
|
@ -839,7 +910,6 @@ main(int argc,
|
|||
}
|
||||
}
|
||||
/* Write pid-file */
|
||||
|
||||
if ((pid = pidfile_write(pidfile)) < 0)
|
||||
goto done;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
@ -91,7 +91,6 @@ struct backend_handle {
|
|||
/* ------ end of common handle ------ */
|
||||
struct client_entry *bh_ce_list; /* The client list */
|
||||
int bh_ce_nr; /* Number of clients, just increment */
|
||||
cxobj *bh_nacm; /* NACM external struct */
|
||||
};
|
||||
|
||||
/*! Creates and returns a clicon config handle for other CLICON API calls
|
||||
|
|
@ -109,7 +108,6 @@ backend_handle_init(void)
|
|||
int
|
||||
backend_handle_exit(clicon_handle h)
|
||||
{
|
||||
struct backend_handle *bh = handle(h);
|
||||
struct client_entry *ce;
|
||||
|
||||
/* only delete client structs, not close sockets, etc, see backend_client_rm WHY NOT? */
|
||||
|
|
@ -120,8 +118,6 @@ backend_handle_exit(clicon_handle h)
|
|||
}
|
||||
backend_client_delete(h, ce);
|
||||
}
|
||||
if (bh->bh_nacm)
|
||||
xml_free(bh->bh_nacm);
|
||||
clicon_handle_exit(h); /* frees h and options (and streams) */
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -188,22 +184,3 @@ backend_client_delete(clicon_handle h,
|
|||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
backend_nacm_list_set(clicon_handle h,
|
||||
cxobj *xnacm)
|
||||
{
|
||||
struct backend_handle *bh = handle(h);
|
||||
|
||||
if (bh->bh_nacm)
|
||||
xml_free(bh->bh_nacm);
|
||||
bh->bh_nacm = xnacm;
|
||||
return 0;
|
||||
}
|
||||
|
||||
cxobj *
|
||||
backend_nacm_list_get(clicon_handle h)
|
||||
{
|
||||
struct backend_handle *bh = handle(h);
|
||||
|
||||
return bh->bh_nacm;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
#
|
||||
|
|
|
|||
|
|
@ -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,8 +236,8 @@ 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)
|
||||
goto done;
|
||||
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;
|
||||
xml_type_set(xa, CX_ATTR);
|
||||
|
|
@ -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;
|
||||
|
|
@ -501,53 +501,54 @@ cli_start_shell(clicon_handle h,
|
|||
cvec *vars,
|
||||
cvec *argv)
|
||||
{
|
||||
char *cmd;
|
||||
char *cmd;
|
||||
struct passwd *pw;
|
||||
int retval;
|
||||
char bcmd[128];
|
||||
cg_var *cv1 = cvec_i(vars, 1);
|
||||
int retval = -1;
|
||||
char bcmd[128];
|
||||
cg_var *cv1 = cvec_i(vars, 1);
|
||||
|
||||
cmd = (cvec_len(vars)>1 ? cv_string_get(cv1) : NULL);
|
||||
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
@ -50,8 +50,9 @@
|
|||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
#include <fcntl.h>
|
||||
#include <syslog.h>
|
||||
#include <sys/param.h>
|
||||
|
||||
#include <math.h> /* For pow() kludge in cvtype_max2str_dup2 */
|
||||
|
||||
/* cligen */
|
||||
#include <cligen/cligen.h>
|
||||
|
|
@ -162,6 +163,98 @@ cli_callback_generate(clicon_handle h,
|
|||
return retval;
|
||||
}
|
||||
|
||||
|
||||
/*! Generate identityref statements for CLI variables
|
||||
* @param[in] ys Yang statement
|
||||
* @param[in] ytype Yang union type being resolved
|
||||
* @param[in] helptext CLI help text
|
||||
* @param[out] cb Buffer where cligen code is written
|
||||
* @see yang2cli_var_sub Its sub-function
|
||||
*/
|
||||
static int
|
||||
yang2cli_var_identityref(yang_stmt *ys,
|
||||
yang_stmt *ytype,
|
||||
char *cvtypestr,
|
||||
char *helptext,
|
||||
cbuf *cb)
|
||||
{
|
||||
int retval = -1;
|
||||
yang_stmt *ybaseref;
|
||||
yang_stmt *ybaseid;
|
||||
cg_var *cv = NULL;
|
||||
char *name;
|
||||
char *id;
|
||||
int i;
|
||||
|
||||
/* Add a wildchar string first -let validate take it for default prefix */
|
||||
cprintf(cb, ">");
|
||||
if (helptext)
|
||||
cprintf(cb, "(\"%s\")", helptext);
|
||||
if ((ybaseref = yang_find((yang_node*)ytype, Y_BASE, NULL)) != NULL &&
|
||||
(ybaseid = yang_find_identity(ys, ybaseref->ys_argument)) != NULL){
|
||||
if (cvec_len(ybaseid->ys_cvec) > 0){
|
||||
cprintf(cb, "|<%s:%s choice:", ys->ys_argument, cvtypestr);
|
||||
i = 0;
|
||||
while ((cv = cvec_each(ybaseid->ys_cvec, cv)) != NULL){
|
||||
if (i++)
|
||||
cprintf(cb, "|");
|
||||
name = strdup(cv_name_get(cv));
|
||||
if ((id=strchr(name, ':')) != NULL)
|
||||
*id = '\0';
|
||||
cprintf(cb, "%s:%s", name, id+1);
|
||||
if (name)
|
||||
free(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
retval = 0;
|
||||
// done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Generate range check statements for CLI variables
|
||||
* @param[out] cb Buffer where cligen code is written
|
||||
* @see yang2cli_var_sub Its sub-function
|
||||
*/
|
||||
static int
|
||||
yang2cli_var_range(yang_stmt *ys,
|
||||
cvec *cvv,
|
||||
int options,
|
||||
cbuf *cb)
|
||||
{
|
||||
int retval = -1;
|
||||
int i;
|
||||
cg_var *cv;
|
||||
|
||||
/* Loop through range_min and range_min..range_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);
|
||||
break;
|
||||
}
|
||||
cv = cvec_i(cvv, i++);
|
||||
if (strcmp(cv_name_get(cv),"range_min") == 0){
|
||||
cprintf(cb, " %s[", (options&YANG_OPTIONS_RANGE)?"range":"length");
|
||||
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,"]");
|
||||
}
|
||||
}
|
||||
retval = 0;
|
||||
// done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* Forward */
|
||||
static int yang2cli_stmt(clicon_handle h, yang_stmt *ys, cbuf *cb,
|
||||
enum genmodel_type gt, int level);
|
||||
|
|
@ -175,7 +268,7 @@ static int yang2cli_var_union(clicon_handle h, yang_stmt *ys, char *origtype,
|
|||
* @param[in] h Clixon handle
|
||||
* @param[in] ys Yang statement
|
||||
* @param[in] ytype Yang union type being resolved
|
||||
* @param[in] cb Buffer where cligen code is written
|
||||
* @param[out] cb Buffer where cligen code is written
|
||||
* @param[in] helptext CLI help text
|
||||
*/
|
||||
static int
|
||||
|
|
@ -186,15 +279,13 @@ 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;
|
||||
|
|
@ -213,6 +304,7 @@ yang2cli_var_sub(clicon_handle h,
|
|||
if (strcmp(type, "enumeration") == 0 || strcmp(type, "bits") == 0){
|
||||
cprintf(cb, " choice:");
|
||||
i = 0;
|
||||
yi = NULL;
|
||||
while ((yi = yn_each((yang_node*)ytype, yi)) != NULL){
|
||||
if (yi->ys_keyword != Y_ENUM && yi->ys_keyword != Y_BIT)
|
||||
continue;
|
||||
|
|
@ -223,82 +315,30 @@ yang2cli_var_sub(clicon_handle h,
|
|||
}
|
||||
}
|
||||
else if (strcmp(type, "identityref") == 0){
|
||||
yang_stmt *ybaseref;
|
||||
yang_stmt *ybaseid;
|
||||
cg_var *cv = NULL;
|
||||
char *name;
|
||||
char *id;
|
||||
/* Add a wildchar string first -let validate take it for default prefix */
|
||||
cprintf(cb, ">");
|
||||
if (helptext)
|
||||
cprintf(cb, "(\"%s\")", helptext);
|
||||
if ((ybaseref = yang_find((yang_node*)ytype, Y_BASE, NULL)) != NULL &&
|
||||
(ybaseid = yang_find_identity(ys, ybaseref->ys_argument)) != NULL){
|
||||
if (cvec_len(ybaseid->ys_cvec) > 0){
|
||||
cprintf(cb, "|<%s:%s choice:", ys->ys_argument, cvtypestr);
|
||||
i = 0;
|
||||
while ((cv = cvec_each(ybaseid->ys_cvec, cv)) != NULL){
|
||||
if (i++)
|
||||
cprintf(cb, "|");
|
||||
name = strdup(cv_name_get(cv));
|
||||
if ((id=strchr(name, ':')) != NULL)
|
||||
*id = '\0';
|
||||
cprintf(cb, "%s:%s", name, id+1);
|
||||
if (name)
|
||||
free(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (yang2cli_var_identityref(ys, ytype, cvtypestr, helptext, cb) < 0)
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
if (options & YANG_OPTIONS_FRACTION_DIGITS)
|
||||
cprintf(cb, " fraction-digits:%u", fraction_digits);
|
||||
|
||||
if (options & (YANG_OPTIONS_RANGE|YANG_OPTIONS_LENGTH)){
|
||||
assert(mincv || maxcv);
|
||||
cprintf(cb, " %s[", (options&YANG_OPTIONS_RANGE)?"range":"length");
|
||||
if (mincv){
|
||||
if ((r = cv2str_dup(mincv)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cv2str_dup");
|
||||
goto done;
|
||||
}
|
||||
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_dup(cvtype)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cvtype_max2str");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
cprintf(cb, "%s]", r); /* range */
|
||||
free(r);
|
||||
r = NULL;
|
||||
if (yang2cli_var_range(ys, cvv, options, cb) < 0)
|
||||
goto done;
|
||||
}
|
||||
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);
|
||||
}
|
||||
if (options & YANG_OPTIONS_PATTERN)
|
||||
cprintf(cb, " regexp:\"%s\"", pattern);
|
||||
cprintf(cb, ">");
|
||||
if (helptext)
|
||||
cprintf(cb, "(\"%s\")", helptext);
|
||||
if (type && strcmp(type, "identityref") == 0)
|
||||
cprintf(cb, ")");
|
||||
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
|
|
@ -323,8 +363,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;
|
||||
|
|
@ -332,9 +371,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;
|
||||
|
||||
|
|
@ -343,10 +382,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;
|
||||
|
|
@ -410,12 +449,11 @@ yang2cli_var(clicon_handle h,
|
|||
cbuf *cb,
|
||||
char *helptext)
|
||||
{
|
||||
int retval = -1;
|
||||
int retval = -1;
|
||||
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;
|
||||
|
|
@ -424,7 +462,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;
|
||||
|
||||
|
|
@ -432,7 +470,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){
|
||||
|
|
@ -461,7 +499,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,
|
||||
|
|
@ -513,10 +551,12 @@ yang2cli_leaf(clicon_handle h,
|
|||
if (helptext)
|
||||
cprintf(cbuf, "(\"%s\")", helptext);
|
||||
cprintf(cbuf, " ");
|
||||
yang2cli_var(h, ys, cbuf, helptext);
|
||||
if (yang2cli_var(h, ys, cbuf, helptext) < 0)
|
||||
goto done;
|
||||
}
|
||||
else
|
||||
yang2cli_var(h, ys, cbuf, helptext);
|
||||
if (yang2cli_var(h, ys, cbuf, helptext) < 0)
|
||||
goto done;
|
||||
if (callback){
|
||||
if (cli_callback_generate(h, ys, cbuf) < 0)
|
||||
goto done;
|
||||
|
|
@ -725,11 +765,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;
|
||||
|
|
@ -747,19 +782,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
|
||||
|
|
@ -788,13 +825,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;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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:qp:GLy:c:U:o:"
|
||||
|
||||
#define CLI_LOGFILE "/tmp/clixon_cli.log"
|
||||
|
||||
|
|
@ -221,13 +221,14 @@ usage(clicon_handle h,
|
|||
"\t-d <dir>\tSpecify plugin directory (default: %s)\n"
|
||||
"\t-m <mode>\tSpecify plugin syntax mode\n"
|
||||
"\t-q \t\tQuiet mode, dont print greetings or prompt, terminate on ctrl-C\n"
|
||||
"\t-p \t\tPrint database yang specification\n"
|
||||
"\t-p <dir>\tYang directory path (see CLICON_YANG_DIR)\n"
|
||||
"\t-G \t\tPrint CLI syntax generated from dbspec (if CLICON_CLI_GENMODEL enabled)\n"
|
||||
"\t-L \t\tDebug print dynamic CLI syntax including completions and expansions\n"
|
||||
"\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"
|
||||
);
|
||||
|
|
@ -240,25 +241,23 @@ int
|
|||
main(int argc, char **argv)
|
||||
{
|
||||
int retval = -1;
|
||||
char c;
|
||||
int c;
|
||||
int once;
|
||||
char *tmp;
|
||||
char *argv0 = argv[0];
|
||||
clicon_handle h;
|
||||
int printspec = 0;
|
||||
int printgen = 0;
|
||||
int logclisyntax = 0;
|
||||
int help = 0;
|
||||
char *treename = NULL;
|
||||
int len;
|
||||
// int len;
|
||||
int logdst = CLICON_LOG_STDERR;
|
||||
char *restarg = NULL; /* what remains after options */
|
||||
int dump_configfile_xml = 0;
|
||||
yang_spec *yspec;
|
||||
yang_spec *yspecfg = NULL; /* For config XXX clixon bug */
|
||||
struct passwd *pw;
|
||||
char *yang_filename = NULL;
|
||||
yang_stmt *ymod = NULL; /* Main module */
|
||||
char *str;
|
||||
|
||||
/* Defaults */
|
||||
once = 0;
|
||||
|
|
@ -361,28 +360,33 @@ main(int argc, char **argv)
|
|||
once = 1;
|
||||
break;
|
||||
case 'a': /* internal backend socket address family */
|
||||
clicon_option_str_set(h, "CLICON_SOCK_FAMILY", optarg);
|
||||
if (clicon_option_add(h, "CLICON_SOCK_FAMILY", optarg) < 0)
|
||||
goto done;
|
||||
break;
|
||||
case 'u': /* internal backend socket unix domain path or ip host */
|
||||
if (!strlen(optarg))
|
||||
usage(h, argv[0]);
|
||||
clicon_option_str_set(h, "CLICON_SOCK", optarg);
|
||||
if (clicon_option_add(h, "CLICON_SOCK", optarg) < 0)
|
||||
goto done;
|
||||
break;
|
||||
case 'd': /* Plugin directory: overrides configfile */
|
||||
if (!strlen(optarg))
|
||||
usage(h, argv[0]);
|
||||
clicon_option_str_set(h, "CLICON_CLI_DIR", optarg);
|
||||
if (clicon_option_add(h, "CLICON_CLI_DIR", optarg) < 0)
|
||||
goto done;
|
||||
break;
|
||||
case 'm': /* CLI syntax mode */
|
||||
if (!strlen(optarg))
|
||||
usage(h, argv[0]);
|
||||
clicon_option_str_set(h, "CLICON_CLI_MODE", optarg);
|
||||
if (clicon_option_add(h, "CLICON_CLI_MODE", optarg) < 0)
|
||||
goto done;
|
||||
break;
|
||||
case 'q' : /* Quiet mode */
|
||||
clicon_quiet_mode_set(h, 1);
|
||||
break;
|
||||
case 'p' : /* Print spec */
|
||||
printspec++;
|
||||
case 'p' : /* yang dir path */
|
||||
if (clicon_option_add(h, "CLICON_YANG_DIR", optarg) < 0)
|
||||
goto done;
|
||||
break;
|
||||
case 'G' : /* Print generated CLI syntax */
|
||||
printgen++;
|
||||
|
|
@ -390,20 +394,29 @@ main(int argc, char **argv)
|
|||
case 'L' : /* Debug print dynamic CLI syntax */
|
||||
logclisyntax++;
|
||||
break;
|
||||
case 'y' :{ /* Load yang spec file (override yang main module) */
|
||||
yang_filename = optarg;
|
||||
case 'y' : /* Load yang absolute filename */
|
||||
if (clicon_option_add(h, "CLICON_YANG_MAIN_FILE", optarg) < 0)
|
||||
goto done;
|
||||
break;
|
||||
}
|
||||
case 'c' :{ /* Overwrite clispec with absolute filename */
|
||||
clicon_option_str_set(h, "CLICON_CLISPEC_FILE", optarg);
|
||||
case 'c' : /* Overwrite clispec with absolute filename */
|
||||
if (clicon_option_add(h, "CLICON_CLISPEC_FILE", optarg) < 0)
|
||||
goto done;
|
||||
break;
|
||||
}
|
||||
case 'U': /* Clixon 'pseudo' user */
|
||||
if (!strlen(optarg))
|
||||
usage(h, argv[0]);
|
||||
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;
|
||||
|
|
@ -430,44 +443,49 @@ main(int argc, char **argv)
|
|||
goto done;
|
||||
clicon_dbspec_yang_set(h, yspec);
|
||||
|
||||
/* Load main application yang specification either module or specific file
|
||||
* If -y <file> is given, it overrides main module */
|
||||
if (yang_filename){
|
||||
if (yang_spec_parse_file(h, yang_filename, clicon_yang_dir(h), yspec, &ymod) < 0)
|
||||
/* Load Yang modules
|
||||
* 1. Load a yang module as a specific absolute filename */
|
||||
if ((str = clicon_yang_main_file(h)) != NULL){
|
||||
if (yang_spec_parse_file(h, str, yspec) < 0)
|
||||
goto done;
|
||||
}
|
||||
else if (yang_spec_parse_module(h, clicon_yang_module_main(h),
|
||||
clicon_yang_dir(h),
|
||||
clicon_yang_module_revision(h),
|
||||
yspec, &ymod) < 0)
|
||||
/* 2. Load a (single) main module */
|
||||
if ((str = clicon_yang_module_main(h)) != NULL){
|
||||
if (yang_spec_parse_module(h, str, clicon_yang_module_revision(h),
|
||||
yspec) < 0)
|
||||
goto done;
|
||||
}
|
||||
/* 3. Load all modules in a directory */
|
||||
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;
|
||||
if (printspec)
|
||||
yang_print(stdout, (yang_node*)yspec);
|
||||
|
||||
/* Create tree generated from dataspec. If no other trees exists, this is
|
||||
* the only one.
|
||||
* The following code creates the tree @datamodel
|
||||
* This tree is referenced from the main CLI spec (CLICON_CLISPEC_DIR)
|
||||
* using the "tree reference"
|
||||
* syntax, ie @datamodel
|
||||
* But note that yang2cli generates syntax for ALL modules, not just for
|
||||
* <module>.
|
||||
*/
|
||||
if (clicon_cli_genmodel(h)){
|
||||
parse_tree pt = {0,}; /* cli parse tree */
|
||||
char *name; /* main module name */
|
||||
char *treeref;
|
||||
|
||||
treeref = clicon_cli_model_treename(h);
|
||||
/* Create cli command tree from dbspec */
|
||||
if (yang2cli(h, yspec, &pt, clicon_cli_genmodel_type(h)) < 0)
|
||||
goto done;
|
||||
|
||||
/* name of main module */
|
||||
name = ymod->ys_argument;
|
||||
|
||||
len = strlen("datamodel:") + strlen(name) + 1;
|
||||
if ((treename = malloc(len)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "malloc");
|
||||
goto done;
|
||||
}
|
||||
snprintf(treename, len, "datamodel:%s", name);
|
||||
cligen_tree_add(cli_cligen(h), treename, pt);
|
||||
cligen_tree_add(cli_cligen(h), treeref, pt);
|
||||
|
||||
if (printgen)
|
||||
cligen_print(stdout, pt, 1);
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
@ -209,6 +209,30 @@ clixon_str2fn(char *name,
|
|||
return NULL;
|
||||
}
|
||||
|
||||
#ifdef CLICON_CLI_MODEL_TREENAME_PATCH
|
||||
/*! 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).
|
||||
* All new references should use @datamodel (or CLICON_CLI_MODEL_TREENAME).
|
||||
* whereas older code used @datamodel:tree.
|
||||
*/
|
||||
static int
|
||||
mask_datamodel_fn(cg_obj *co,
|
||||
void *arg)
|
||||
{
|
||||
char *str = "datamodel:";
|
||||
int len = strlen(str);
|
||||
if (co->co_type == CO_REFERENCE){
|
||||
|
||||
if (strlen(co->co_command) > len &&
|
||||
strncmp(co->co_command, "datamodel:", len)==0){
|
||||
co->co_command[len-1] = '\0';
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif /* CLICON_CLI_MODEL_TREENAME_PATCH */
|
||||
|
||||
/*! Append to syntax mode from file
|
||||
* @param[in] h Clixon handle
|
||||
* @param[in] filename Name of file where syntax is specified (in syntax-group dir)
|
||||
|
|
@ -253,7 +277,10 @@ cli_load_syntax(clicon_handle h,
|
|||
goto done;
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
#ifdef CLICON_CLI_MODEL_TREENAME_PATCH
|
||||
if (pt_apply(pt, mask_datamodel_fn, h) < 0)
|
||||
goto done;
|
||||
#endif
|
||||
/* Get CLICON specific global variables */
|
||||
prompt = cvec_find_str(cvv, "CLICON_PROMPT");
|
||||
plgnam = cvec_find_str(cvv, "CLICON_PLUGIN");
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
#
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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_ */
|
||||
|
|
|
|||
|
|
@ -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:p:y:U:t:o:"
|
||||
|
||||
#define NETCONF_LOGFILE "/tmp/clixon_netconf.log"
|
||||
|
||||
|
|
@ -80,21 +80,29 @@
|
|||
* @param[in] cb Packet buffer
|
||||
*/
|
||||
static int
|
||||
process_incoming_packet(clicon_handle h,
|
||||
netconf_input_packet(clicon_handle h,
|
||||
cbuf *cb)
|
||||
{
|
||||
char *str;
|
||||
char *str0;
|
||||
cxobj *xreq = NULL; /* Request (in) */
|
||||
int isrpc = 0; /* either hello or rpc */
|
||||
cbuf *cbret = NULL;
|
||||
cxobj *xret = NULL; /* Return (out) */
|
||||
cxobj *xrpc;
|
||||
cxobj *xc;
|
||||
int retval = -1;
|
||||
char *str;
|
||||
char *str0;
|
||||
cxobj *xreq = NULL; /* Request (in) */
|
||||
int isrpc = 0; /* either hello or rpc */
|
||||
cbuf *cbret = NULL;
|
||||
cxobj *xret = NULL; /* Return (out) */
|
||||
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;
|
||||
}
|
||||
yspec = clicon_dbspec_yang(h);
|
||||
if ((str0 = strdup(cbuf_get(cb))) == NULL){
|
||||
clicon_log(LOG_ERR, "%s: strdup: %s", __FUNCTION__, strerror(errno));
|
||||
|
|
@ -103,19 +111,24 @@ process_incoming_packet(clicon_handle h,
|
|||
str = str0;
|
||||
/* Parse incoming XML message */
|
||||
if (xml_parse_string(str, yspec, &xreq) < 0){
|
||||
if ((cbret = cbuf_new()) == NULL){
|
||||
if (netconf_operation_failed(cbret, "rpc", "internal error")< 0)
|
||||
goto done;
|
||||
netconf_output(1, cbret, "rpc-error");
|
||||
}
|
||||
else
|
||||
clicon_log(LOG_ERR, "%s: cbuf_new", __FUNCTION__);
|
||||
free(str0);
|
||||
if (netconf_operation_failed(cbret, "rpc", clicon_err_reason)< 0)
|
||||
goto done;
|
||||
netconf_output_encap(1, cbret, "rpc-error");
|
||||
goto done;
|
||||
}
|
||||
free(str0);
|
||||
if ((xrpc=xpath_first(xreq, "//rpc")) != NULL)
|
||||
if ((xrpc=xpath_first(xreq, "//rpc")) != NULL){
|
||||
isrpc++;
|
||||
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){
|
||||
netconf_output_encap(1, cbret, "rpc-error");
|
||||
goto ok;
|
||||
}
|
||||
}
|
||||
else
|
||||
if (xpath_first(xreq, "//hello") != NULL)
|
||||
;
|
||||
|
|
@ -132,35 +145,36 @@ process_incoming_packet(clicon_handle h,
|
|||
goto done;
|
||||
}
|
||||
else{ /* there is a return message in xret */
|
||||
cxobj *xa, *xa2;
|
||||
assert(xret);
|
||||
|
||||
if ((cbret = cbuf_new()) != NULL){
|
||||
if ((xc = xml_child_i(xret,0))!=NULL){
|
||||
xa=NULL;
|
||||
/* Copy message-id attribute from incoming to reply.
|
||||
* RFC 6241:
|
||||
* If additional attributes are present in an <rpc> element, a NETCONF
|
||||
* peer MUST return them unmodified in the <rpc-reply> element. This
|
||||
* includes any "xmlns" attributes.
|
||||
*/
|
||||
while ((xa = xml_child_each(xrpc, xa, CX_ATTR)) != NULL){
|
||||
if ((xa2 = xml_dup(xa)) ==NULL)
|
||||
goto done;
|
||||
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){
|
||||
cbuf_free(cbret);
|
||||
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.
|
||||
* RFC 6241:
|
||||
* If additional attributes are present in an <rpc> element, a NETCONF
|
||||
* peer MUST return them unmodified in the <rpc-reply> element. This
|
||||
* includes any "xmlns" attributes.
|
||||
*/
|
||||
while ((xa = xml_child_each(xrpc, xa, CX_ATTR)) != NULL){
|
||||
if ((xa2 = xml_dup(xa)) ==NULL)
|
||||
goto done;
|
||||
}
|
||||
if (xml_addsub(xc, xa2) < 0)
|
||||
goto done;
|
||||
}
|
||||
clicon_xml2cbuf(cbret, xml_child_i(xret,0), 0, 0);
|
||||
if (netconf_output_encap(1, cbret, "rpc-reply") < 0){
|
||||
cbuf_free(cbret);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
}
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
if (xreq)
|
||||
xml_free(xreq);
|
||||
|
|
@ -168,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
|
||||
|
|
@ -221,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);
|
||||
|
|
@ -310,16 +324,17 @@ usage(clicon_handle h,
|
|||
"where options are\n"
|
||||
"\t-h\t\tHelp\n"
|
||||
"\t-D <level>\tDebug level\n"
|
||||
"\t-q\t\tQuiet: dont send hello prompt\n"
|
||||
"\t-f <file>\tConfiguration file (mandatory)\n"
|
||||
"\t-l (e|o|s|f<file>) \tLog on std(e)rr, std(o)ut, (s)yslog, (f)ile (syslog is default)\n"
|
||||
"\t-q\t\tQuiet: dont send hello prompt\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-d <dir>\tSpecify netconf plugin directory dir (default: %s)\n"
|
||||
|
||||
"\t-p <dir>\tYang directory path (see CLICON_YANG_DIR)\n"
|
||||
"\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)
|
||||
);
|
||||
|
|
@ -330,7 +345,7 @@ int
|
|||
main(int argc,
|
||||
char **argv)
|
||||
{
|
||||
char c;
|
||||
int c;
|
||||
char *tmp;
|
||||
char *argv0 = argv[0];
|
||||
int quiet = 0;
|
||||
|
|
@ -341,7 +356,7 @@ main(int argc,
|
|||
struct timeval tv = {0,}; /* timeout */
|
||||
yang_spec *yspec = NULL;
|
||||
yang_spec *yspecfg = NULL; /* For config XXX clixon bug */
|
||||
char *yang_filename = NULL;
|
||||
char *str;
|
||||
|
||||
/* Create handle */
|
||||
if ((h = clicon_handle_init()) == NULL)
|
||||
|
|
@ -403,6 +418,9 @@ main(int argc,
|
|||
case 'f': /* config file */
|
||||
case 'l': /* log */
|
||||
break; /* see above */
|
||||
case 'q': /* quiet: dont write hello */
|
||||
quiet++;
|
||||
break;
|
||||
case 'a': /* internal backend socket address family */
|
||||
clicon_option_str_set(h, "CLICON_SOCK_FAMILY", optarg);
|
||||
break;
|
||||
|
|
@ -411,18 +429,20 @@ main(int argc,
|
|||
usage(h, argv[0]);
|
||||
clicon_option_str_set(h, "CLICON_SOCK", optarg);
|
||||
break;
|
||||
case 'q': /* quiet: dont write hello */
|
||||
quiet++;
|
||||
break;
|
||||
case 'd': /* Plugin directory */
|
||||
if (!strlen(optarg))
|
||||
usage(h, argv[0]);
|
||||
clicon_option_str_set(h, "CLICON_NETCONF_DIR", optarg);
|
||||
if (clicon_option_add(h, "CLICON_NETCONF_DIR", optarg) < 0)
|
||||
goto done;
|
||||
break;
|
||||
case 'y' :{ /* Load yang spec file (override yang main module) */
|
||||
yang_filename = optarg;
|
||||
case 'p' : /* yang dir path */
|
||||
if (clicon_option_add(h, "CLICON_YANG_DIR", optarg) < 0)
|
||||
goto done;
|
||||
break;
|
||||
case 'y' : /* Load yang spec file (override yang main module) */
|
||||
if (clicon_option_add(h, "CLICON_YANG_MAIN_FILE", optarg) < 0)
|
||||
goto done;
|
||||
break;
|
||||
}
|
||||
case 'U': /* Clixon 'pseudo' user */
|
||||
if (!strlen(optarg))
|
||||
usage(h, argv[0]);
|
||||
|
|
@ -432,7 +452,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;
|
||||
|
|
@ -444,18 +472,26 @@ main(int argc,
|
|||
if ((yspec = yspec_new()) == NULL)
|
||||
goto done;
|
||||
clicon_dbspec_yang_set(h, yspec);
|
||||
/* Load main application yang specification either module or specific file
|
||||
* If -y <file> is given, it overrides main module */
|
||||
if (yang_filename){
|
||||
if (yang_spec_parse_file(h, yang_filename, clicon_yang_dir(h), yspec, NULL) < 0)
|
||||
/* Load Yang modules
|
||||
* 1. Load a yang module as a specific absolute filename */
|
||||
if ((str = clicon_yang_main_file(h)) != NULL){
|
||||
if (yang_spec_parse_file(h, str, yspec) < 0)
|
||||
goto done;
|
||||
}
|
||||
else if (yang_spec_parse_module(h, clicon_yang_module_main(h),
|
||||
clicon_yang_dir(h),
|
||||
clicon_yang_module_revision(h),
|
||||
yspec, NULL) < 0)
|
||||
/* 2. Load a (single) main module */
|
||||
if ((str = clicon_yang_module_main(h)) != NULL){
|
||||
if (yang_spec_parse_module(h, str, clicon_yang_module_revision(h),
|
||||
yspec) < 0)
|
||||
goto done;
|
||||
}
|
||||
/* 3. Load all modules in a directory */
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
@ -32,7 +32,13 @@
|
|||
***** END LICENSE BLOCK *****
|
||||
|
||||
*
|
||||
* Code for handling netconf rpc messages according to RFC 4741 and RFC 5277
|
||||
* Code for handling netconf rpc messages according to RFC 4741,5277,6241
|
||||
* All NETCONF protocol elements are defined in the following namespace:
|
||||
* urn:ietf:params:xml:ns:netconf:base:1.0
|
||||
* YANG defines an XML namespace for NETCONF <edit-config> operations,
|
||||
* <error-info> content, and the <action> element. The name of this
|
||||
* namespace is "urn:ietf:params:xml:ns:yang:1".
|
||||
*
|
||||
*****************************************************************************/
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "clixon_config.h" /* generated by config & autoconf */
|
||||
|
|
@ -50,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>
|
||||
|
|
@ -128,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");
|
||||
|
|
@ -180,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;
|
||||
|
|
@ -216,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;
|
||||
|
|
@ -304,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>"
|
||||
|
|
@ -358,168 +317,15 @@ netconf_edit_config(clicon_handle h,
|
|||
"<error-severity>error</error-severity>"
|
||||
"</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:
|
||||
retval = 0;
|
||||
done:
|
||||
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:
|
||||
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
|
||||
*
|
||||
*
|
||||
|
|
@ -583,138 +389,11 @@ 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;
|
||||
}
|
||||
|
||||
/*! Called when a notification has happened on backend
|
||||
* and this session has registered for that event.
|
||||
* Filter it and forward it.
|
||||
|
|
@ -747,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) */
|
||||
|
|
@ -760,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;
|
||||
|
|
@ -769,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;
|
||||
}
|
||||
|
|
@ -834,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:
|
||||
|
|
@ -863,18 +543,23 @@ netconf_application_rpc(clicon_handle h,
|
|||
int retval = -1;
|
||||
yang_spec *yspec = NULL; /* application yspec */
|
||||
yang_stmt *yrpc = NULL;
|
||||
yang_stmt *ymod = NULL;
|
||||
yang_stmt *yinput;
|
||||
yang_stmt *youtput;
|
||||
cxobj *xoutput;
|
||||
cbuf *cb = NULL;
|
||||
cbuf *cbret = NULL;
|
||||
int ret;
|
||||
|
||||
|
||||
/* First check system / netconf RPC:s */
|
||||
if ((cb = cbuf_new()) == NULL){
|
||||
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){
|
||||
|
|
@ -882,36 +567,42 @@ netconf_application_rpc(clicon_handle h,
|
|||
goto done;
|
||||
}
|
||||
cbuf_reset(cb);
|
||||
if (xml_namespace(xn) == NULL){
|
||||
if (ys_module_by_xml(yspec, xn, &ymod) < 0)
|
||||
goto done;
|
||||
if (ymod == NULL){
|
||||
xml_parse_va(xret, NULL, "<rpc-reply><rpc-error>"
|
||||
"<error-tag>operation-failed</error-tag>"
|
||||
"<error-type>rpc</error-type>"
|
||||
"<error-severity>error</error-severity>"
|
||||
"<error-message>%s</error-message>"
|
||||
"<error-info>Not recognized</error-info>"
|
||||
"<error-info>Not recognized module</error-info>"
|
||||
"</rpc-error></rpc-reply>", xml_name(xn));
|
||||
goto ok;
|
||||
}
|
||||
cprintf(cb, "/%s:%s", xml_namespace(xn), xml_name(xn));
|
||||
/* Find yang rpc statement, return yang rpc statement if found */
|
||||
if (yang_abs_schema_nodeid(yspec, cbuf_get(cb), Y_RPC, &yrpc) < 0)
|
||||
goto done;
|
||||
yrpc = yang_find((yang_node*)ymod, Y_RPC, xml_name(xn));
|
||||
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){
|
||||
/* 1. Check xn arguments with input statement. */
|
||||
if ((yinput = yang_find((yang_node*)yrpc, Y_INPUT, NULL)) != NULL){
|
||||
xml_spec_set(xn, yinput); /* needed for xml_spec_populate */
|
||||
if (xml_apply(xn, CX_ELMNT, xml_spec_populate, yinput) < 0)
|
||||
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)
|
||||
if ((ret = xml_yang_validate_all_top(xn, cbret)) < 0)
|
||||
goto done;
|
||||
if (xml_yang_validate_add(xn, NULL) < 0)
|
||||
if (ret == 0){
|
||||
netconf_output_encap(1, cbret, "rpc-error");
|
||||
goto ok;
|
||||
}
|
||||
if ((ret = xml_yang_validate_add(xn, cbret)) < 0)
|
||||
goto done;
|
||||
}
|
||||
if ((cbret = cbuf_new()) == NULL){
|
||||
clicon_err(OE_UNIX, 0, "cbuf_new");
|
||||
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)
|
||||
|
|
@ -923,17 +614,30 @@ netconf_application_rpc(clicon_handle h,
|
|||
else /* Send to backend */
|
||||
if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0)
|
||||
goto done;
|
||||
/* Sanity check of outgoing XML */
|
||||
/* Sanity check of outgoing XML
|
||||
* For now, skip outgoing checks.
|
||||
* (1) Does not handle <ok/> properly
|
||||
* (2) Uncertain how validation errors should be logged/handled
|
||||
*/
|
||||
if (0)
|
||||
if ((youtput = yang_find((yang_node*)yrpc, Y_OUTPUT, NULL)) != NULL){
|
||||
xoutput=xpath_first(*xret, "/");
|
||||
xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */
|
||||
if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, youtput) < 0)
|
||||
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;
|
||||
|
|
@ -948,7 +652,6 @@ netconf_application_rpc(clicon_handle h,
|
|||
return retval;
|
||||
}
|
||||
|
||||
|
||||
/*! The central netconf rpc dispatcher. Look at first tag and dispach to sub-functions.
|
||||
* Call plugin handler if tag not found. If not handled by any handler, return
|
||||
* error.
|
||||
|
|
@ -979,10 +682,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;
|
||||
}
|
||||
|
|
@ -990,47 +708,14 @@ 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)
|
||||
goto done;
|
||||
cc_closed++;
|
||||
if (clicon_rpc_netconf_xml(h, xml_parent(xe), xret, NULL) < 0)
|
||||
goto done;
|
||||
}
|
||||
/* RFC 5277 :notification */
|
||||
else if (strcmp(xml_name(xe), "create-subscription") == 0){
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
#
|
||||
|
|
|
|||
|
|
@ -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
|
||||
[
|
||||
{
|
||||
"type": "eth"
|
||||
}
|
||||
]
|
||||
|
||||
curl -sX POST -d '{"interfaces":{"interface":{"name":"eth1","type":"eth","enabled":"true"}}}' http://localhost/restconf/data
|
||||
```
|
||||
Get the type of a specific interface:
|
||||
```
|
||||
> curl -G http://127.0.0.1/restconf/data/interfaces/interface=eth9/type
|
||||
{
|
||||
"ietf-interfaces:type": "eth"
|
||||
}
|
||||
```
|
||||
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:
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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,11 +71,12 @@ 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", 403},
|
||||
{"access-denied", 401}, /* or 403 */
|
||||
{"access-denied", 403},
|
||||
{"lock-denied", 409},
|
||||
{"resource-denied", 409},
|
||||
{"rollback-failed", 500},
|
||||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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:d: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"
|
||||
|
|
@ -492,10 +492,12 @@ usage(clicon_handle h,
|
|||
"\t-D <level>\tDebug level\n"
|
||||
"\t-f <file>\tConfiguration file (mandatory)\n"
|
||||
"\t-l <s|f<file>> \tLog on (s)yslog, (f)ile (syslog is default)\n"
|
||||
"\t-p <dir>\tYang directory path (see CLICON_YANG_DIR)\n"
|
||||
"\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)
|
||||
);
|
||||
|
|
@ -513,19 +515,18 @@ main(int argc,
|
|||
char *argv0 = argv[0];
|
||||
FCGX_Request request;
|
||||
FCGX_Request *r = &request;
|
||||
char c;
|
||||
int c;
|
||||
char *sockpath;
|
||||
char *path;
|
||||
clicon_handle h;
|
||||
char *yangspec=NULL;
|
||||
char *dir;
|
||||
char *tmp;
|
||||
int logdst = CLICON_LOG_SYSLOG;
|
||||
yang_spec *yspec = NULL;
|
||||
yang_spec *yspecfg = NULL; /* For config XXX clixon bug */
|
||||
char *yang_filename = NULL;
|
||||
char *stream_path;
|
||||
int finish;
|
||||
char *str;
|
||||
|
||||
/* In the startup, logs to stderr & debug flag set later */
|
||||
clicon_log_init(__PROGRAM__, LOG_INFO, logdst);
|
||||
|
|
@ -596,13 +597,17 @@ main(int argc,
|
|||
case 'f': /* config file */
|
||||
case 'l': /* log */
|
||||
break; /* see above */
|
||||
case 'p' : /* yang dir path */
|
||||
if (clicon_option_add(h, "CLICON_YANG_DIR", optarg) < 0)
|
||||
goto done;
|
||||
break;
|
||||
case 'd': /* Plugin directory */
|
||||
if (!strlen(optarg))
|
||||
usage(h, argv[0]);
|
||||
clicon_option_str_set(h, "CLICON_RESTCONF_DIR", optarg);
|
||||
break;
|
||||
case 'y' : /* Load yang spec file (override yang main module) */
|
||||
yang_filename = optarg;
|
||||
clicon_option_str_set(h, "CLICON_YANG_MAIN_FILE", optarg);
|
||||
break;
|
||||
case 'a': /* internal backend socket address family */
|
||||
clicon_option_str_set(h, "CLICON_SOCK_FAMILY", optarg);
|
||||
|
|
@ -612,6 +617,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;
|
||||
|
|
@ -619,10 +633,6 @@ main(int argc,
|
|||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
/* Overwrite yang module with -y option */
|
||||
if (yangspec)
|
||||
clicon_option_str_set(h, "CLICON_YANG_MODULE_MAIN", yangspec);
|
||||
|
||||
/* Initialize plugins group */
|
||||
if ((dir = clicon_restconf_dir(h)) != NULL)
|
||||
if (clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir, NULL) < 0)
|
||||
|
|
@ -632,27 +642,36 @@ main(int argc,
|
|||
if ((yspec = yspec_new()) == NULL)
|
||||
goto done;
|
||||
clicon_dbspec_yang_set(h, yspec);
|
||||
/* Load main application yang specification either module or specific file
|
||||
* If -y <file> is given, it overrides main module */
|
||||
if (yang_filename){
|
||||
if (yang_spec_parse_file(h, yang_filename, clicon_yang_dir(h), yspec, NULL) < 0)
|
||||
|
||||
/* Load Yang modules
|
||||
* 1. Load a yang module as a specific absolute filename */
|
||||
if ((str = clicon_yang_main_file(h)) != NULL){
|
||||
if (yang_spec_parse_file(h, str, yspec) < 0)
|
||||
goto done;
|
||||
}
|
||||
else if (yang_spec_parse_module(h, clicon_yang_module_main(h),
|
||||
clicon_yang_dir(h),
|
||||
clicon_yang_module_revision(h),
|
||||
yspec, NULL) < 0)
|
||||
/* 2. Load a (single) main module */
|
||||
if ((str = clicon_yang_module_main(h)) != NULL){
|
||||
if (yang_spec_parse_module(h, str, clicon_yang_module_revision(h),
|
||||
yspec) < 0)
|
||||
goto done;
|
||||
}
|
||||
/* 3. Load all modules in a directory */
|
||||
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;
|
||||
/* Add system modules */
|
||||
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040") &&
|
||||
yang_spec_parse_module(h, "ietf-restconf-monitoring", CLIXON_DATADIR, NULL, yspec, NULL)< 0)
|
||||
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", CLIXON_DATADIR, NULL, yspec, NULL)< 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
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
45
configure
vendored
45
configure
vendored
|
|
@ -635,6 +635,7 @@ EXEEXT
|
|||
ac_ct_CC
|
||||
wwwuser
|
||||
wwwdir
|
||||
enable_stdyangs
|
||||
with_restconf
|
||||
RANLIB
|
||||
SH_SUFFIX
|
||||
|
|
@ -711,6 +712,7 @@ ac_user_opts='
|
|||
enable_option_checking
|
||||
enable_debug
|
||||
with_cligen
|
||||
enable_stdyangs
|
||||
enable_publish
|
||||
with_restconf
|
||||
with_configfile
|
||||
|
|
@ -1350,6 +1352,8 @@ Optional Features:
|
|||
--disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no)
|
||||
--enable-FEATURE[=ARG] include FEATURE [ARG=yes]
|
||||
--enable-debug Build with debug symbols, default: no
|
||||
--disable-stdyangs Include standard yang files in clixon install,
|
||||
default: yes
|
||||
--enable-publish Enable publish of notification streams using SSE and
|
||||
curl
|
||||
|
||||
|
|
@ -2161,9 +2165,9 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
|
|||
: ${INSTALLFLAGS="-s"}
|
||||
|
||||
CLIXON_VERSION_MAJOR="3"
|
||||
CLIXON_VERSION_MINOR="8"
|
||||
CLIXON_VERSION_MINOR="9"
|
||||
CLIXON_VERSION_PATCH="0"
|
||||
CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}\""
|
||||
CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}.PRE\""
|
||||
|
||||
# Fix to specific CLIgen version (eg 3.5) or head (3)
|
||||
CLIGEN_VERSION="3"
|
||||
|
|
@ -2457,6 +2461,7 @@ test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644'
|
|||
|
||||
|
||||
# If yes, compile apps/restconf
|
||||
|
||||
wwwdir=/www-data
|
||||
|
||||
wwwuser=www-data
|
||||
|
|
@ -3691,6 +3696,26 @@ if test "${with_cligen}"; then
|
|||
test -d "$with_cligen" && CLIGEN_PREFIX="$with_cligen"
|
||||
fi
|
||||
|
||||
# Disable/enable standard Yang files.
|
||||
# If enable - include yang/standard/*.yang in clixon yang files (default)
|
||||
# If disable - get standard yang files from elsewhere
|
||||
# Check whether --enable-stdyangs was given.
|
||||
if test "${enable_stdyangs+set}" = set; then :
|
||||
enableval=$enable_stdyangs;
|
||||
if test "$enableval" = no; then
|
||||
enable_stdyangs=no
|
||||
else
|
||||
enable_stdyangs=yes
|
||||
fi
|
||||
|
||||
else
|
||||
enable_stdyangs=yes
|
||||
fi
|
||||
|
||||
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: stdyangs is $enable_stdyangs" >&5
|
||||
$as_echo "stdyangs is $enable_stdyangs" >&6; }
|
||||
|
||||
# Experimental: Curl publish notification stream to eg Nginx nchan.
|
||||
# Check whether --enable-publish was given.
|
||||
if test "${enable_publish+set}" = set; then :
|
||||
|
|
@ -4391,15 +4416,14 @@ fi
|
|||
done
|
||||
|
||||
|
||||
# This is to find clixon system files in the source code and Makefile
|
||||
# CLIXON_DATADIR is where clixon installs the "system" yang files in yang/Makfile
|
||||
# This directory should most probably be included in each application,
|
||||
# so each application designer may need to place CLIXON_DATADIR in their config
|
||||
# (last in yang dir list):
|
||||
# <CLIXON-YANG-DIR>$CLIXON_DATADIR</CLIXON-YANG_DIR>
|
||||
|
||||
CLIXON_DATADIR="${prefix}/share/clixon"
|
||||
|
||||
cat >>confdefs.h <<_ACEOF
|
||||
#define CLIXON_DATADIR "${CLIXON_DATADIR}"
|
||||
_ACEOF
|
||||
|
||||
|
||||
# Default location for config file
|
||||
|
||||
cat >>confdefs.h <<_ACEOF
|
||||
|
|
@ -4409,7 +4433,7 @@ _ACEOF
|
|||
|
||||
|
||||
|
||||
ac_config_files="$ac_config_files Makefile lib/Makefile lib/src/Makefile lib/clixon/Makefile apps/Makefile apps/cli/Makefile apps/backend/Makefile apps/netconf/Makefile apps/restconf/Makefile include/Makefile etc/Makefile etc/clixonrc example/Makefile extras/rpm/Makefile docker/Makefile datastore/Makefile datastore/text/Makefile util/Makefile yang/Makefile doc/Makefile"
|
||||
ac_config_files="$ac_config_files Makefile lib/Makefile lib/src/Makefile lib/clixon/Makefile apps/Makefile apps/cli/Makefile apps/backend/Makefile apps/netconf/Makefile apps/restconf/Makefile include/Makefile etc/Makefile etc/clixonrc example/Makefile extras/rpm/Makefile docker/Makefile datastore/Makefile datastore/text/Makefile util/Makefile yang/Makefile yang/clixon/Makefile yang/standard/Makefile doc/Makefile test/Makefile"
|
||||
|
||||
cat >confcache <<\_ACEOF
|
||||
# This file is a shell script that caches the results of configure
|
||||
|
|
@ -5122,7 +5146,10 @@ do
|
|||
"datastore/text/Makefile") CONFIG_FILES="$CONFIG_FILES datastore/text/Makefile" ;;
|
||||
"util/Makefile") CONFIG_FILES="$CONFIG_FILES util/Makefile" ;;
|
||||
"yang/Makefile") CONFIG_FILES="$CONFIG_FILES yang/Makefile" ;;
|
||||
"yang/clixon/Makefile") CONFIG_FILES="$CONFIG_FILES yang/clixon/Makefile" ;;
|
||||
"yang/standard/Makefile") CONFIG_FILES="$CONFIG_FILES yang/standard/Makefile" ;;
|
||||
"doc/Makefile") CONFIG_FILES="$CONFIG_FILES doc/Makefile" ;;
|
||||
"test/Makefile") CONFIG_FILES="$CONFIG_FILES test/Makefile" ;;
|
||||
|
||||
*) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;;
|
||||
esac
|
||||
|
|
|
|||
31
configure.ac
31
configure.ac
|
|
@ -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
|
||||
#
|
||||
|
|
@ -43,9 +43,9 @@ AC_INIT(lib/clixon/clixon.h.in)
|
|||
: ${INSTALLFLAGS="-s"}
|
||||
|
||||
CLIXON_VERSION_MAJOR="3"
|
||||
CLIXON_VERSION_MINOR="8"
|
||||
CLIXON_VERSION_MINOR="9"
|
||||
CLIXON_VERSION_PATCH="0"
|
||||
CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}\""
|
||||
CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}.PRE\""
|
||||
|
||||
# Fix to specific CLIgen version (eg 3.5) or head (3)
|
||||
CLIGEN_VERSION="3"
|
||||
|
|
@ -91,6 +91,7 @@ AC_SUBST(LIBS)
|
|||
AC_SUBST(SH_SUFFIX)
|
||||
AC_SUBST(RANLIB)
|
||||
AC_SUBST(with_restconf) # If yes, compile apps/restconf
|
||||
AC_SUBST(enable_stdyangs)
|
||||
AC_SUBST(wwwdir,/www-data)
|
||||
AC_SUBST(wwwuser,www-data)
|
||||
#
|
||||
|
|
@ -145,6 +146,20 @@ if test "${with_cligen}"; then
|
|||
test -d "$with_cligen" && CLIGEN_PREFIX="$with_cligen"
|
||||
fi
|
||||
|
||||
# Disable/enable standard Yang files.
|
||||
# If enable - include yang/standard/*.yang in clixon yang files (default)
|
||||
# If disable - get standard yang files from elsewhere
|
||||
AC_ARG_ENABLE(stdyangs, AS_HELP_STRING([--disable-stdyangs],[Include standard yang files in clixon install, default: yes]),[
|
||||
if test "$enableval" = no; then
|
||||
enable_stdyangs=no
|
||||
else
|
||||
enable_stdyangs=yes
|
||||
fi
|
||||
],
|
||||
[ enable_stdyangs=yes])
|
||||
|
||||
AC_MSG_RESULT(stdyangs is $enable_stdyangs)
|
||||
|
||||
# Experimental: Curl publish notification stream to eg Nginx nchan.
|
||||
AC_ARG_ENABLE(publish, AS_HELP_STRING([--enable-publish],[Enable publish of notification streams using SSE and curl]),[
|
||||
if test "$enableval" = no; then
|
||||
|
|
@ -199,10 +214,13 @@ AC_CHECK_LIB(dl, dlopen)
|
|||
|
||||
AC_CHECK_FUNCS(inet_aton sigaction sigvec strlcpy strsep strndup alphasort versionsort strverscmp)
|
||||
|
||||
# This is to find clixon system files in the source code and Makefile
|
||||
# CLIXON_DATADIR is where clixon installs the "system" yang files in yang/Makfile
|
||||
# This directory should most probably be included in each application,
|
||||
# so each application designer may need to place CLIXON_DATADIR in their config
|
||||
# (last in yang dir list):
|
||||
# <CLIXON-YANG-DIR>$CLIXON_DATADIR</CLIXON-YANG_DIR>
|
||||
AC_SUBST(CLIXON_DATADIR)
|
||||
CLIXON_DATADIR="${prefix}/share/clixon"
|
||||
AC_DEFINE_UNQUOTED(CLIXON_DATADIR, "${CLIXON_DATADIR}", [Clixon data dir for system yang files etc])
|
||||
|
||||
# Default location for config file
|
||||
AC_DEFINE_UNQUOTED(CLIXON_DEFAULT_CONFIG,"${CLIXON_DEFAULT_CONFIG}",[Location for apps to find default config file])
|
||||
|
|
@ -228,6 +246,9 @@ AC_OUTPUT(Makefile
|
|||
datastore/text/Makefile
|
||||
util/Makefile
|
||||
yang/Makefile
|
||||
yang/clixon/Makefile
|
||||
yang/standard/Makefile
|
||||
doc/Makefile
|
||||
test/Makefile
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
#
|
||||
|
|
@ -49,7 +49,6 @@ INSTALLFLAGS = @INSTALLFLAGS@
|
|||
LDFLAGS = @LDFLAGS@
|
||||
LIBS = @LIBS@
|
||||
with_restconf = @with_restconf@
|
||||
with_keyvalue = @with_keyvalue@
|
||||
|
||||
SH_SUFFIX = @SH_SUFFIX@
|
||||
CLIXON_MAJOR = @CLIXON_VERSION_MAJOR@
|
||||
|
|
@ -117,5 +116,5 @@ distclean: clean
|
|||
for i in $(SUBDIRS); \
|
||||
do (cd $$i; $(MAKE) $(MFLAGS) $@); done
|
||||
|
||||
tags:
|
||||
TAGS:
|
||||
find $(srcdir) -name '*.[chyl]' -print | etags -
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@
|
|||
|
||||
The Clixon datastore is a stand-alone XML based datastore. The idea is
|
||||
to be able to use different datastores backends with the same
|
||||
API. There is currently a key-value plugin based on qdbm and a plain
|
||||
text-file datastore.
|
||||
API. There is currently only a plain text-file datastore.
|
||||
|
||||
The datastore is primarily designed to be used by Clixon but can be used
|
||||
separately.
|
||||
|
|
@ -38,7 +37,7 @@ int xmldb_create(clicon_handle h, char *db);
|
|||
|
||||
To use the API, a client needs the following:
|
||||
- A clicon handle.
|
||||
- A datastore plugin, such as a text.so or keyvalue.so. These are normally built and installed at Clixon make.
|
||||
- A datastore plugin, such as a text.so. These are normally built and installed at Clixon make.
|
||||
- A directory where to store databases
|
||||
- A yang specification. This needs to be parsed using the Clixon yang_parse() method.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
@ -31,14 +31,6 @@
|
|||
|
||||
***** END LICENSE BLOCK *****
|
||||
|
||||
* Examples:
|
||||
|
||||
./datastore_client -d candidate -b /usr/local/var/example -p /home/olof/src/clixon/datastore/keyvalue/keyvalue.so -y /usr/local/share/example/yang -m ietf-ip get /
|
||||
|
||||
sudo ./datastore_client -d candidate -b /usr/local/var/example -p /home/olof/src/clixon/datastore/keyvalue/keyvalue.so -y /usr/local/share/example/yang -m ietf-ip put merge /interfaces/interface=eth66 '<config>eth66</config>'
|
||||
|
||||
sudo ./datastore_client -d candidate -b /usr/local/var/example -p /home/olof/src/clixon/datastore/keyvalue/keyvalue.so -y /usr/local/share/example/yang -m ietf-ip put merge / '<config><interfaces><interface><name>eth0</name><enabled>true</enabled></interface></interfaces></config>'
|
||||
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
|
|
@ -71,7 +63,7 @@ sudo ./datastore_client -d candidate -b /usr/local/var/example -p /home/olof/src
|
|||
#include <clixon/clixon.h>
|
||||
|
||||
/* Command line options to be passed to getopt(3) */
|
||||
#define DATASTORE_OPTS "hDd:p:b:y:m:"
|
||||
#define DATASTORE_OPTS "hDd:p:b:y:"
|
||||
|
||||
/*! usage
|
||||
*/
|
||||
|
|
@ -85,8 +77,7 @@ usage(char *argv0)
|
|||
"\t-d <db>\t\tDatabase name. Default: running. Alt: candidate,startup\n"
|
||||
"\t-b <dir>\tDatabase directory. Mandatory\n"
|
||||
"\t-p <plugin>\tDatastore plugin. Mandatory\n"
|
||||
"\t-y <dir>\tYang directory (where modules are stored). Mandatory\n"
|
||||
"\t-m <module>\tYang module. Mandatory\n"
|
||||
"\t-y <file>\tYang file. Mandatory\n"
|
||||
"and command is either:\n"
|
||||
"\tget [<xpath>]\n"
|
||||
"\tmget <nr> [<xpath>]\n"
|
||||
|
|
@ -108,15 +99,14 @@ usage(char *argv0)
|
|||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
char c;
|
||||
int c;
|
||||
clicon_handle h;
|
||||
char *argv0;
|
||||
char *db = "running";
|
||||
char *plugin = NULL;
|
||||
char *cmd = NULL;
|
||||
yang_spec *yspec = NULL;
|
||||
char *yangdir = NULL;
|
||||
char *yangmodule = NULL;
|
||||
char *yangfilename = NULL;
|
||||
char *dbdir = NULL;
|
||||
int ret;
|
||||
int pid;
|
||||
|
|
@ -158,15 +148,10 @@ main(int argc, char **argv)
|
|||
usage(argv0);
|
||||
dbdir = optarg;
|
||||
break;
|
||||
case 'y': /* Yang directory */
|
||||
case 'y': /* Yang file */
|
||||
if (!optarg)
|
||||
usage(argv0);
|
||||
yangdir = optarg;
|
||||
break;
|
||||
case 'm': /* Yang module */
|
||||
if (!optarg)
|
||||
usage(argv0);
|
||||
yangmodule = optarg;
|
||||
yangfilename = optarg;
|
||||
break;
|
||||
}
|
||||
/*
|
||||
|
|
@ -188,12 +173,8 @@ main(int argc, char **argv)
|
|||
clicon_err(OE_DB, 0, "Missing dbdir -b option");
|
||||
goto done;
|
||||
}
|
||||
if (yangdir == NULL){
|
||||
clicon_err(OE_YANG, 0, "Missing yangdir -y 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 */
|
||||
|
|
@ -206,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, NULL, yangmodule, yangdir, NULL, yspec, NULL) < 0)
|
||||
if (yang_spec_parse_file(h, yangfilename, yspec) < 0)
|
||||
goto done;
|
||||
/* Set database directory option */
|
||||
if (xmldb_setopt(h, "dbdir", dbdir) < 0)
|
||||
|
|
@ -258,14 +239,18 @@ main(int argc, char **argv)
|
|||
clicon_err(OE_DB, 0, "Unrecognized operation: %s", argv[1]);
|
||||
usage(argv0);
|
||||
}
|
||||
_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, NULL, cbret) < 1)
|
||||
goto done;
|
||||
|
||||
}
|
||||
else if (strcmp(cmd, "copy")==0){
|
||||
if (argc != 2)
|
||||
|
|
|
|||
|
|
@ -1,794 +0,0 @@
|
|||
/*
|
||||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
the GNU General Public License Version 3 or later (the "GPL"),
|
||||
in which case the provisions of the GPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of the GPL, and not to allow others to
|
||||
use your version of this file under the terms of Apache License version 2, indicate
|
||||
your decision by deleting the provisions above and replace them with the
|
||||
notice and other provisions required by the GPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the Apache License version 2 or the GPL.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
|
||||
*/
|
||||
/* Error handling: dont use clicon_err, treat as unix system calls. That is,
|
||||
ensure errno is set and return -1/NULL */
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
/* clicon */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clicon */
|
||||
#include <clixon/clixon.h>
|
||||
|
||||
#include "clixon_chunk.h"
|
||||
|
||||
/*
|
||||
* The chunk head array for the predefined chunk sizes.
|
||||
*/
|
||||
static chunk_head_t chunk_heads[CHUNK_HEADS];
|
||||
|
||||
/*
|
||||
* Did we initialize the chunk heads yet?
|
||||
*/
|
||||
static int chunk_initialized = 0;
|
||||
|
||||
|
||||
/*
|
||||
* The pagesize of this system
|
||||
*/
|
||||
static int chunk_pagesz;
|
||||
|
||||
|
||||
/*
|
||||
* List of chunk groups
|
||||
*/
|
||||
static chunk_group_t *chunk_grp;
|
||||
|
||||
/*
|
||||
* Hack to tell unchunk() not to remove chunk_group if empty
|
||||
*/
|
||||
static int dont_unchunk_group;
|
||||
|
||||
|
||||
/*
|
||||
* Initialize chunk library
|
||||
*/
|
||||
static void
|
||||
chunk_initialize ()
|
||||
{
|
||||
int pgs;
|
||||
register int idx;
|
||||
|
||||
chunk_pagesz = getpagesize();
|
||||
|
||||
bzero (&chunk_heads, sizeof(chunk_heads));
|
||||
|
||||
for (idx = 0; idx < CHUNK_HEADS; idx++) {
|
||||
chunk_head_t *chead = &chunk_heads[idx];
|
||||
|
||||
|
||||
/*
|
||||
* Calculate the size of a block
|
||||
*/
|
||||
pgs = (0x01lu << (CHUNK_BASE + idx)) / chunk_pagesz;
|
||||
if (pgs == 0)
|
||||
pgs = 1;
|
||||
chead->ch_blksz = pgs * chunk_pagesz;
|
||||
|
||||
|
||||
/*
|
||||
* Chunks per block is 1 for all size above a page. For sizes
|
||||
* (including the chunk header) less than a page it's as many
|
||||
* as fits
|
||||
*/
|
||||
chead->ch_nchkperblk = chead->ch_blksz / (0x01lu << (CHUNK_BASE + idx));
|
||||
|
||||
|
||||
/*
|
||||
* Size of each chunk is:
|
||||
* (size + chnkhdr) * ncnkperblk = blksiz - blkhdr
|
||||
*/
|
||||
chead->ch_size =
|
||||
(chead->ch_blksz / chead->ch_nchkperblk)
|
||||
- sizeof(chunk_t);
|
||||
|
||||
}
|
||||
|
||||
/* Zero misc variables */
|
||||
chunk_grp = NULL;
|
||||
dont_unchunk_group = 0;
|
||||
|
||||
chunk_initialized = 1;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* chunk_new_block() - Allocate new block, initialize it and it's chunks.
|
||||
*/
|
||||
static int
|
||||
chunk_new_block (chunk_head_t *chead)
|
||||
{
|
||||
register int idx;
|
||||
register char *c;
|
||||
chunk_block_t *blk;
|
||||
chunk_t *cnk;
|
||||
|
||||
/* Map block header mem */
|
||||
blk = (chunk_block_t *)
|
||||
mmap(NULL, sizeof(chunk_block_t),
|
||||
PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
|
||||
if (blk == MAP_FAILED)
|
||||
return -1;
|
||||
memset((void *)blk, 0, sizeof(*blk));
|
||||
|
||||
/* Allocate chunk block */
|
||||
blk->cb_blk = (void *)
|
||||
mmap(NULL, chead->ch_blksz,
|
||||
PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
|
||||
if (blk->cb_blk == MAP_FAILED) {
|
||||
munmap(blk, chead->ch_blksz);
|
||||
return -1;
|
||||
}
|
||||
memset(blk->cb_blk, 0, chead->ch_blksz);
|
||||
|
||||
|
||||
/* Initialize chunk header */
|
||||
blk->cb_head = chead;
|
||||
INSQ(blk, chead->ch_blks);
|
||||
chead->ch_nblks++;
|
||||
|
||||
/* Initialize chunks */
|
||||
c = ((char *)blk->cb_blk);
|
||||
for (idx = 0; idx < chead->ch_nchkperblk; idx++) {
|
||||
|
||||
cnk = (chunk_t *)c;
|
||||
cnk->c_blk = blk;
|
||||
INSQ(cnk, chead->ch_free);
|
||||
chead->ch_nfree++;
|
||||
|
||||
c += (chead->ch_size + sizeof(chunk_t));
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* chunk_release_block() - Unqueue a block, it's chunks and free mem
|
||||
*/
|
||||
static void
|
||||
chunk_release_block(chunk_block_t *cblk)
|
||||
{
|
||||
int idx;
|
||||
char *c;
|
||||
chunk_t *cnk;
|
||||
chunk_head_t *chead;
|
||||
|
||||
|
||||
chead = cblk->cb_head;
|
||||
|
||||
/*
|
||||
* Dequeue block
|
||||
*/
|
||||
DELQ(cblk, chead->ch_blks, chunk_block_t *);
|
||||
chead->ch_nblks--;
|
||||
|
||||
/*
|
||||
* Dequeue all chunks in the block
|
||||
*/
|
||||
c = (char *)cblk->cb_blk;
|
||||
for (idx = 0; idx < chead->ch_nchkperblk; idx++) {
|
||||
|
||||
cnk = (chunk_t *)c;
|
||||
DELQ(cnk, chead->ch_free, chunk_t *);
|
||||
chead->ch_nfree--;
|
||||
|
||||
c += (chead->ch_size + sizeof(chunk_t));
|
||||
}
|
||||
|
||||
/*
|
||||
* Free block
|
||||
*/
|
||||
munmap((void *)cblk->cb_blk, chead->ch_blksz);
|
||||
munmap((void *)cblk, sizeof(*cblk));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* chunk_alloc() - Map new chunk of memory
|
||||
*/
|
||||
static void *
|
||||
chunk_alloc(size_t len)
|
||||
{
|
||||
register int idx;
|
||||
chunk_head_t *chead;
|
||||
chunk_t *cnk;
|
||||
|
||||
|
||||
if (!len)
|
||||
return (void *)NULL;
|
||||
|
||||
|
||||
|
||||
/* Find sufficient sized block head */
|
||||
for (idx = 0; idx < CHUNK_HEADS; idx++)
|
||||
if (chunk_heads[idx].ch_size >= len)
|
||||
break;
|
||||
|
||||
/* Too large chunk? */
|
||||
if (idx >= CHUNK_HEADS) {
|
||||
errno = ENOMEM;
|
||||
return (void *)NULL;
|
||||
}
|
||||
|
||||
chead = &chunk_heads[idx];
|
||||
|
||||
|
||||
/* Get new block if necessary */
|
||||
if (!chead->ch_nfree)
|
||||
if (chunk_new_block(chead))
|
||||
return (void *)NULL;
|
||||
|
||||
|
||||
/* Move a free chunk to the in-use list */
|
||||
cnk = chead->ch_free;
|
||||
DELQ(cnk, chead->ch_free, chunk_t *);
|
||||
chead->ch_nfree--;
|
||||
INSQ(cnk, chead->ch_cnks);
|
||||
/* Add reference to the corresponding block */
|
||||
cnk->c_blk->cb_ref++;
|
||||
|
||||
#ifdef CHUNK_DIAG
|
||||
/* Clear diag info */
|
||||
bzero((void *)&cnk->c_diag, sizeof(cnk->c_diag));
|
||||
#endif /* CHUNK_DIAG */
|
||||
|
||||
/* Return pointer to first byte after the chunk header */
|
||||
return (void *) (cnk + 1);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* chunk() - Map new chunk of memory in group
|
||||
*/
|
||||
void *
|
||||
#ifdef CHUNK_DIAG
|
||||
_chunk(size_t len, const char *name, const char *file, int line)
|
||||
#else
|
||||
chunk(size_t len, const char *name)
|
||||
#endif
|
||||
{
|
||||
int newgrp = 0;
|
||||
void *ptr = NULL;
|
||||
chunk_t *cnk;
|
||||
chunk_group_t *tmp;
|
||||
chunk_group_t *grp = NULL;
|
||||
chunk_grpent_t *ent = NULL;
|
||||
|
||||
/* Make sure chunk_heads are initialized */
|
||||
if (!chunk_initialized)
|
||||
chunk_initialize();
|
||||
|
||||
if (!len)
|
||||
return (void *)NULL;
|
||||
|
||||
/* Get actual chunk
|
||||
*/
|
||||
ptr = chunk_alloc(len);
|
||||
if (!ptr)
|
||||
goto error;
|
||||
cnk = (chunk_t *) (((char *)ptr) - sizeof(chunk_t));
|
||||
|
||||
#ifdef CHUNK_DIAG
|
||||
/* Store who reuested us
|
||||
*/
|
||||
cnk->c_diag.cd_file = file;
|
||||
cnk->c_diag.cd_line = line;
|
||||
#endif /* CHUNK_DIAG */
|
||||
|
||||
/* No name given. Get an ungrouped chunk
|
||||
*/
|
||||
if (!name)
|
||||
return ptr;
|
||||
|
||||
|
||||
/* Try to find already existing entry
|
||||
*/
|
||||
if (chunk_grp) {
|
||||
tmp = chunk_grp;
|
||||
do {
|
||||
if (!strcmp(tmp->cg_name, name)) {
|
||||
grp = tmp;
|
||||
break;
|
||||
}
|
||||
|
||||
tmp = NEXTQ(chunk_group_t *, tmp);
|
||||
|
||||
} while (tmp != chunk_grp);
|
||||
}
|
||||
|
||||
/* New group.
|
||||
*/
|
||||
if ( !grp ) {
|
||||
|
||||
grp = (chunk_group_t *) chunk_alloc(sizeof(chunk_group_t));
|
||||
if (!grp)
|
||||
goto error;
|
||||
|
||||
bzero(grp, sizeof(chunk_group_t));
|
||||
|
||||
grp->cg_name = (char *) chunk_alloc(strlen(name) + 1);
|
||||
if (!grp->cg_name)
|
||||
goto error;
|
||||
bcopy(name, grp->cg_name, strlen(name)+1);
|
||||
newgrp = 1;
|
||||
}
|
||||
|
||||
|
||||
/* Get new entry.
|
||||
*/
|
||||
ent = (chunk_grpent_t *) chunk_alloc(sizeof(chunk_grpent_t));
|
||||
if (!ent)
|
||||
goto error;
|
||||
bzero(ent, sizeof(chunk_grpent_t));
|
||||
|
||||
/* Now put everything together
|
||||
*/
|
||||
cnk->c_grpent = ent;
|
||||
|
||||
ent->ce_cnk = cnk;
|
||||
ent->ce_grp = grp;
|
||||
|
||||
INSQ(ent, grp->cg_ent);
|
||||
if (newgrp)
|
||||
INSQ(grp, chunk_grp);
|
||||
|
||||
return (ptr);
|
||||
|
||||
error:
|
||||
if (grp && newgrp) {
|
||||
if (grp->cg_name)
|
||||
unchunk(grp->cg_name);
|
||||
unchunk(grp);
|
||||
}
|
||||
if (ent)
|
||||
unchunk(ent);
|
||||
if (ptr)
|
||||
unchunk(ptr);
|
||||
|
||||
return (void *) NULL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* rechunk() - Resize previously allocated chunk.
|
||||
*/
|
||||
void *
|
||||
#ifdef CHUNK_DIAG
|
||||
_rechunk(void *ptr, size_t len, const char *name, const char *file, int line)
|
||||
#else
|
||||
rechunk(void *ptr, size_t len, const char *name)
|
||||
#endif
|
||||
{
|
||||
int idx;
|
||||
void *new;
|
||||
chunk_t *cnk;
|
||||
chunk_t *newcnk;
|
||||
chunk_head_t *chead;
|
||||
chunk_head_t *newchead;
|
||||
|
||||
|
||||
/* No previoud chunk, get new
|
||||
*/
|
||||
if (!ptr) {
|
||||
#ifdef CHUNK_DIAG
|
||||
return _chunk(len, name, file, line);
|
||||
#else
|
||||
return chunk(len, name);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Zero length, free chunk
|
||||
*/
|
||||
if (len == 0) {
|
||||
unchunk(ptr);
|
||||
return (void *) NULL;
|
||||
}
|
||||
|
||||
/* Rewind pointer to beginning of chunk header
|
||||
*/
|
||||
cnk = (chunk_t *) (((char *)ptr) - sizeof(chunk_t));
|
||||
chead = cnk->c_blk->cb_head;
|
||||
|
||||
/* Find sufficient sized block head
|
||||
*/
|
||||
for (idx = 0; idx < CHUNK_HEADS; idx++)
|
||||
if (chunk_heads[idx].ch_size >= len)
|
||||
break;
|
||||
/* Too large chunk? */
|
||||
if (idx >= CHUNK_HEADS) {
|
||||
errno = ENOMEM;
|
||||
return (void *)NULL;
|
||||
}
|
||||
|
||||
/* Check if chunk size remains unchanged
|
||||
*/
|
||||
if (chunk_heads[idx].ch_size == chead->ch_size)
|
||||
return (ptr);
|
||||
|
||||
/* Get new chunk
|
||||
*/
|
||||
#ifdef CHUNK_DIAG
|
||||
new = _chunk(len, name, file, line);
|
||||
#else
|
||||
new = chunk(len, name);
|
||||
#endif
|
||||
if (!new)
|
||||
return (void *) NULL;
|
||||
newcnk = (chunk_t *) (((char *)new) - sizeof(chunk_t));
|
||||
newchead = newcnk->c_blk->cb_head;
|
||||
|
||||
/* Copy contents to new chunk
|
||||
*/
|
||||
bcopy(ptr, new, MIN(newchead->ch_size, chead->ch_size));
|
||||
|
||||
/* Free old chunk
|
||||
*/
|
||||
unchunk(ptr);
|
||||
|
||||
|
||||
return (new);
|
||||
}
|
||||
|
||||
/*
|
||||
* unchunk() - Release chunk
|
||||
*/
|
||||
void
|
||||
unchunk(void *ptr)
|
||||
{
|
||||
chunk_t *cnk;
|
||||
chunk_head_t *chead;
|
||||
chunk_block_t *cblk;
|
||||
chunk_grpent_t *ent;
|
||||
chunk_group_t *grp;
|
||||
|
||||
if (!chunk_initialized)
|
||||
return;
|
||||
|
||||
/* Rewind pointer to beginning of chunk header
|
||||
*/
|
||||
cnk = (chunk_t *) (((char *)ptr) - sizeof(chunk_t));
|
||||
cblk = cnk->c_blk;
|
||||
chead = cblk->cb_head;
|
||||
|
||||
/* Move chunk back to free list
|
||||
*/
|
||||
DELQ(cnk, chead->ch_cnks, chunk_t *);
|
||||
INSQ(cnk, chead->ch_free);
|
||||
chead->ch_nfree++;
|
||||
|
||||
/* If chunk is grouped, remove from group.
|
||||
*/
|
||||
ent = cnk->c_grpent;
|
||||
if (ent) {
|
||||
grp = ent->ce_grp;
|
||||
DELQ(ent, grp->cg_ent, chunk_grpent_t *);
|
||||
unchunk(ent);
|
||||
cnk->c_grpent = NULL;
|
||||
|
||||
/* Group empty? */
|
||||
if (!dont_unchunk_group && !grp->cg_ent) {
|
||||
DELQ(grp, chunk_grp, chunk_group_t *);
|
||||
unchunk(grp->cg_name);
|
||||
unchunk(grp);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check block refs is nil, if so free it
|
||||
*/
|
||||
cblk->cb_ref--;
|
||||
if (cblk->cb_ref == 0)
|
||||
chunk_release_block (cblk);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* unchunk_group() - Release all group chunks.
|
||||
*/
|
||||
void
|
||||
unchunk_group(const char *name)
|
||||
{
|
||||
chunk_group_t *tmp;
|
||||
chunk_group_t *grp = NULL;
|
||||
chunk_t *cnk;
|
||||
|
||||
if (!chunk_initialized)
|
||||
return;
|
||||
|
||||
/* Try to find already existing entry
|
||||
*/
|
||||
if (chunk_grp) {
|
||||
tmp = chunk_grp;
|
||||
do {
|
||||
if (!strcmp(tmp->cg_name, name)) {
|
||||
grp = tmp;
|
||||
break;
|
||||
}
|
||||
|
||||
tmp = NEXTQ(chunk_group_t *, tmp);
|
||||
|
||||
} while (tmp != chunk_grp);
|
||||
}
|
||||
if (!grp)
|
||||
return;
|
||||
|
||||
|
||||
/* Walk through all chunks in group an free them
|
||||
*/
|
||||
dont_unchunk_group = 1;
|
||||
while (grp->cg_ent) {
|
||||
cnk = grp->cg_ent->ce_cnk;
|
||||
unchunk((chunk_t *)(((char *)cnk) + sizeof(chunk_t)));
|
||||
}
|
||||
dont_unchunk_group = 0;
|
||||
|
||||
|
||||
/* Remove group from list and free it
|
||||
*/
|
||||
DELQ(grp, chunk_grp, chunk_group_t *);
|
||||
unchunk(grp->cg_name);
|
||||
unchunk(grp);
|
||||
}
|
||||
|
||||
/*
|
||||
* chunkdup() - Copy block of data to a new chunk of memory in group
|
||||
*/
|
||||
void *
|
||||
#ifdef CHUNK_DIAG
|
||||
_chunkdup(const void *ptr, size_t len, const char *name, const char *file, int line)
|
||||
#else
|
||||
chunkdup(const void *ptr, size_t len, const char *name)
|
||||
#endif
|
||||
{
|
||||
void *new;
|
||||
|
||||
/* No input data or no length
|
||||
*/
|
||||
if (!ptr || len <= 0)
|
||||
return (void *)NULL;
|
||||
|
||||
/* Get new chunk
|
||||
*/
|
||||
#ifdef CHUNK_DIAG
|
||||
new = _chunk(len, name, file, line);
|
||||
#else
|
||||
new = chunk(len, name);
|
||||
#endif
|
||||
if (!new)
|
||||
return (void *)NULL;
|
||||
|
||||
/* Copy data to new chunk
|
||||
*/
|
||||
memcpy(new, ptr, len);
|
||||
|
||||
return (new);
|
||||
}
|
||||
|
||||
/*
|
||||
* chunksize() - Return size of memory chunk.
|
||||
*/
|
||||
size_t
|
||||
chunksize(void *ptr)
|
||||
{
|
||||
chunk_t *cnk;
|
||||
chunk_head_t *chead;
|
||||
chunk_block_t *cblk;
|
||||
|
||||
if (!chunk_initialized)
|
||||
return -1;
|
||||
|
||||
/* Rewind pointer to beginning of chunk header
|
||||
*/
|
||||
cnk = (chunk_t *) (((char *)ptr) - sizeof(chunk_t));
|
||||
cblk = cnk->c_blk;
|
||||
chead = cblk->cb_head;
|
||||
|
||||
return chead->ch_size;
|
||||
}
|
||||
|
||||
/*
|
||||
* chunk_strncat() - Concatenate 'n' characters to a chunk allocated string. If
|
||||
* 'n' is zero, do the whole src string.
|
||||
*
|
||||
*/
|
||||
char *
|
||||
#ifdef CHUNK_DIAG
|
||||
_chunk_strncat(const char *dst, const char *src, size_t n, const char *name,
|
||||
const char *file, int line)
|
||||
#else
|
||||
chunk_strncat(const char *dst, const char *src, size_t n, const char *name)
|
||||
#endif
|
||||
{
|
||||
size_t len;
|
||||
char *new;
|
||||
void *ptr = (void *)dst;
|
||||
|
||||
if (n == 0) /* zero length means cat whole string */
|
||||
n = strlen(src);
|
||||
len = (dst ? strlen(dst) : 0) + n + 1;
|
||||
#ifdef CHUNK_DIAG
|
||||
ptr = _rechunk(ptr, len, name, file, line);
|
||||
#else
|
||||
ptr = rechunk(ptr, len, name);
|
||||
#endif
|
||||
if (ptr == NULL)
|
||||
return NULL;
|
||||
|
||||
new = (char *)ptr;
|
||||
new += strlen(new);
|
||||
while (n-- > 0 && *src)
|
||||
*new++ = *src++;
|
||||
*new = '\0';
|
||||
|
||||
return (char *)ptr;
|
||||
}
|
||||
|
||||
/*
|
||||
* chunk_sprintf() - Format string into new chunk.
|
||||
*/
|
||||
char *
|
||||
#ifdef CHUNK_DIAG
|
||||
_chunk_sprintf(const char *name, const char *file,
|
||||
int line, const char *fmt, ...)
|
||||
#else
|
||||
chunk_sprintf(const char *name, char *fmt, ...)
|
||||
#endif
|
||||
{
|
||||
size_t len;
|
||||
char *str;
|
||||
va_list args;
|
||||
|
||||
/* Calculate formatted string length */
|
||||
va_start(args, fmt);
|
||||
len = vsnprintf(NULL, 0, fmt, args) + 1;
|
||||
va_end(args);
|
||||
|
||||
/* get chunk */
|
||||
#ifdef CHUNK_DIAG
|
||||
str = _chunk(len, name, file, line);
|
||||
#else
|
||||
str = chunk(len, name);
|
||||
#endif
|
||||
if (str == NULL)
|
||||
return NULL;
|
||||
|
||||
/* Format string */
|
||||
va_start(args, fmt);
|
||||
len = vsnprintf(str, len, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
#ifdef CHUNK_DIAG
|
||||
/*
|
||||
* chunk_check() - Report all non-freed chunk for given group (if any)
|
||||
*/
|
||||
void
|
||||
chunk_check(FILE *fout, const char *name)
|
||||
{
|
||||
int idx;
|
||||
chunk_t *cnk;
|
||||
chunk_group_t *tmp;
|
||||
chunk_group_t *grp = NULL;
|
||||
chunk_grpent_t *ent;
|
||||
|
||||
if (!chunk_initialized)
|
||||
return;
|
||||
/* No name given, walk through everything
|
||||
*/
|
||||
if (name == (const char *)NULL) {
|
||||
for (idx = 0; idx < CHUNK_HEADS; idx++) {
|
||||
chunk_head_t *chead = &chunk_heads[idx];
|
||||
cnk = chead->ch_cnks;
|
||||
if (cnk == (chunk_t *)NULL)
|
||||
continue;
|
||||
|
||||
do {
|
||||
|
||||
/* If no file name it's an internal chunk */
|
||||
if (cnk->c_diag.cd_file)
|
||||
clicon_debug(0,
|
||||
"%s:%d,\t%zu bytes (%p), group \"%s\"\n",
|
||||
cnk->c_diag.cd_file,
|
||||
cnk->c_diag.cd_line,
|
||||
cnk->c_blk->cb_head->ch_size,
|
||||
(cnk +1),
|
||||
cnk->c_grpent ?
|
||||
cnk->c_grpent->ce_grp->cg_name :
|
||||
"NULL");
|
||||
|
||||
cnk = NEXTQ(chunk_t *, cnk);
|
||||
|
||||
} while (cnk != chead->ch_cnks);
|
||||
}
|
||||
}
|
||||
|
||||
/* Walk through group
|
||||
*/
|
||||
else {
|
||||
|
||||
|
||||
/* Try to find already existing entry
|
||||
*/
|
||||
if (chunk_grp) {
|
||||
tmp = chunk_grp;
|
||||
do {
|
||||
if (!strcmp(tmp->cg_name, name)) {
|
||||
grp = tmp;
|
||||
break;
|
||||
}
|
||||
|
||||
tmp = NEXTQ(chunk_group_t *, tmp);
|
||||
|
||||
} while (tmp != chunk_grp);
|
||||
}
|
||||
if (!grp)
|
||||
return;
|
||||
|
||||
ent = grp->cg_ent;
|
||||
do {
|
||||
cnk = ent->ce_cnk;
|
||||
|
||||
fprintf(fout ? fout : stdout,
|
||||
"%s:%d,\t%zu bytes (%p), group \"%s\"\n",
|
||||
cnk->c_diag.cd_file,
|
||||
cnk->c_diag.cd_line,
|
||||
cnk->c_blk->cb_head->ch_size,
|
||||
(cnk +1),
|
||||
cnk->c_grpent ?
|
||||
cnk->c_grpent->ce_grp->cg_name :
|
||||
"NULL");
|
||||
|
||||
ent = NEXTQ(chunk_grpent_t *, ent);
|
||||
} while (ent != grp->cg_ent);
|
||||
}
|
||||
}
|
||||
#else /* CHUNK_DIAG */
|
||||
void
|
||||
chunk_check(FILE *fout, const char *name)
|
||||
{
|
||||
}
|
||||
#endif /* CHUNK_DIAG */
|
||||
|
|
@ -1,177 +0,0 @@
|
|||
/*
|
||||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
the GNU General Public License Version 3 or later (the "GPL"),
|
||||
in which case the provisions of the GPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of the GPL, and not to allow others to
|
||||
use your version of this file under the terms of Apache License version 2,
|
||||
indicate your decision by deleting the provisions above and replace them with
|
||||
the notice and other provisions required by the GPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the Apache License version 2 or the GPL.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _CLIXON_CHUNK_H_
|
||||
#define _CLIXON_CHUNK_H_
|
||||
|
||||
|
||||
/*
|
||||
* Compile with chunk diagnostics. XXX Should be in Makefile.in ??
|
||||
*/
|
||||
#undef CHUNK_DIAG
|
||||
|
||||
/*
|
||||
* Base number of bits to shift getting the size of a chunk_head.
|
||||
*/
|
||||
#define CHUNK_BASE 6
|
||||
|
||||
/*
|
||||
* Number of predefined chunk sizes. I.e. the number of chunk heads in the
|
||||
* chunk_heads vector.
|
||||
*/
|
||||
#define CHUNK_HEADS (32 - CHUNK_BASE)
|
||||
|
||||
|
||||
#ifdef CHUNK_DIAG
|
||||
/*
|
||||
* Chunk diagnostics
|
||||
*/
|
||||
typedef struct _chunk_diag_t {
|
||||
const char *cd_file; /* File which requested chunk */
|
||||
|
||||
int cd_line; /* Line in requesting file */
|
||||
|
||||
} chunk_diag_t;
|
||||
#endif /* CHUNK_DIAG */
|
||||
|
||||
/*
|
||||
* The block header.
|
||||
*/
|
||||
struct _chunk_head_t;
|
||||
typedef struct _chunk_head_t chunk_head_t;
|
||||
|
||||
typedef struct _chunk_block_t {
|
||||
qelem_t cb_qelem; /* Circular queue of blocks */
|
||||
|
||||
chunk_head_t *cb_head; /* The chunk head I belong to */
|
||||
|
||||
void *cb_blk; /* Allocated memory block */
|
||||
|
||||
uint16_t cb_ref; /* Number of used chunks of block */
|
||||
|
||||
} chunk_block_t;
|
||||
|
||||
|
||||
/*
|
||||
* The chunk header.
|
||||
*/
|
||||
struct _chunk_grpent_t;
|
||||
typedef struct _chunk_grpent_t chunk_grpent_t;
|
||||
typedef struct _chunk_t {
|
||||
qelem_t c_qelem; /* Circular queue of chunks */
|
||||
|
||||
chunk_block_t *c_blk; /* The block I belong to */
|
||||
|
||||
#ifdef CHUNK_DIAG
|
||||
chunk_diag_t c_diag; /* The diagnostics structure */
|
||||
#endif /* CHUNK_DIAG */
|
||||
|
||||
chunk_grpent_t *c_grpent;
|
||||
} chunk_t;
|
||||
|
||||
/*
|
||||
* The head of a chunk size. Each predefined size has it's own head keeping
|
||||
* track of all blocks and chunks for the size.
|
||||
*/
|
||||
struct _chunk_head_t {
|
||||
size_t ch_size; /* Chunk size */
|
||||
int ch_nchkperblk; /* Number pf chunks per block */
|
||||
|
||||
size_t ch_blksz; /* Size of a block */
|
||||
int ch_nblks; /* Number of allocated blocks */
|
||||
|
||||
chunk_block_t *ch_blks; /* Circular list of blocks */
|
||||
|
||||
chunk_t *ch_cnks; /* Circular list of chunks in use */
|
||||
|
||||
size_t ch_nfree; /* Number of free chunks */
|
||||
chunk_t *ch_free; /* Circular list of free chunks */
|
||||
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* The chunk group structure.
|
||||
*/
|
||||
typedef struct _chunk_group_t {
|
||||
qelem_t cg_qelem; /* List of chunk groups */
|
||||
|
||||
char *cg_name; /* Name of group */
|
||||
|
||||
chunk_grpent_t *cg_ent; /* List of chunks in the group */
|
||||
|
||||
} chunk_group_t;
|
||||
|
||||
|
||||
/*
|
||||
* The chunk group entry structure.
|
||||
*/
|
||||
struct _chunk_grpent_t {
|
||||
qelem_t ce_qelem; /* Circular list of entries */
|
||||
|
||||
chunk_group_t *ce_grp; /* The group I belong to */
|
||||
|
||||
chunk_t *ce_cnk; /* Pointer to the chunk */
|
||||
};
|
||||
|
||||
/*
|
||||
* Public function declarations
|
||||
*/
|
||||
#ifdef CHUNK_DIAG
|
||||
void *_chunk (size_t, const char *, const char *, int);
|
||||
#define chunk(siz,label) _chunk((siz),(label),__FILE__,__LINE__)
|
||||
void *_rechunk (void *, size_t, const char *, const char *, int);
|
||||
#define rechunk(ptr,siz,label) _rechunk((ptr),(siz),(label),__FILE__,__LINE__)
|
||||
void *_chunkdup (const void *, size_t, const char *, const char *, int);
|
||||
#define chunkdup(ptr,siz,label) _chunkdup((ptr),(siz),(label),__FILE__,__LINE__)
|
||||
char *_chunk_strncat (const char *, const char *, size_t, const char *, const char *, int);
|
||||
#define chunk_strncat(str,new,n,label) _chunk_strncat((str),(new),(n),(label),__FILE__,__LINE__)
|
||||
char *_chunk_sprintf (const char *, const char *, int, const char *, ...);
|
||||
#define chunk_sprintf(label,fmt,...) _chunk_sprintf((label),__FILE__,__LINE__,(fmt),__VA_ARGS__)
|
||||
#else /* CHUNK_DIAG */
|
||||
void *chunk (size_t, const char *);
|
||||
void *rechunk (void *, size_t, const char *);
|
||||
void *chunkdup (const void *, size_t, const char *);
|
||||
char *chunk_strncat (const char *, const char *, size_t, const char *);
|
||||
char *chunk_sprintf (const char *, char *, ...);
|
||||
#endif /* CHUNK_DIAG */
|
||||
void unchunk (void *);
|
||||
void unchunk_group (const char *);
|
||||
void chunk_check (FILE *, const char *);
|
||||
size_t chunksize (void *);
|
||||
|
||||
|
||||
#endif /* _CLIXON_CHUNK_H_ */
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,588 +0,0 @@
|
|||
/*
|
||||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
the GNU General Public License Version 3 or later (the "GPL"),
|
||||
in which case the provisions of the GPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of the GPL, and not to allow others to
|
||||
use your version of this file under the terms of Apache License version 2,
|
||||
indicate your decision by deleting the provisions above and replace them with
|
||||
the notice and other provisions required by the GPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the Apache License version 2 or the GPL.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
|
||||
* @note Some unclarities with locking. man dpopen defines the following flags
|
||||
* with dpopen:
|
||||
* `DP_ONOLCK', which means it opens a database file without
|
||||
* file locking,
|
||||
* `DP_OLCKNB', which means locking is performed without blocking.
|
||||
*
|
||||
* While connecting as a writer, an exclusive lock is invoked to
|
||||
* the database file. While connecting as a reader, a shared lock is
|
||||
* invoked to the database file. The thread blocks until the lock is
|
||||
* achieved. If `DP_ONOLCK' is used, the application is responsible
|
||||
* for exclusion control.
|
||||
* The code below uses for
|
||||
* write, delete: DP_OLCKNB
|
||||
* read: DP_OLCKNB
|
||||
* This means that a write fails if one or many reads are occurring, and
|
||||
* a read or write fails if a write is occurring, and
|
||||
* QDBM allows a single write _or_ multiple readers, but
|
||||
* not both. This is obviously extremely limiting.
|
||||
* NOTE, the locking in netconf and xmldb is a write lock.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "clixon_config.h"
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <syslog.h>
|
||||
#include <sys/types.h>
|
||||
#include <limits.h>
|
||||
#include <regex.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/param.h>
|
||||
|
||||
#ifdef HAVE_DEPOT_H
|
||||
#include <depot.h> /* qdb api */
|
||||
#else /* HAVE_QDBM_DEPOT_H */
|
||||
#include <qdbm/depot.h> /* qdb api */
|
||||
#endif
|
||||
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clicon */
|
||||
#include <clixon/clixon.h>
|
||||
|
||||
#include "clixon_chunk.h"
|
||||
#include "clixon_qdb.h"
|
||||
|
||||
/*! Initialize database
|
||||
* @param[in] file database file
|
||||
* @param[in] omode see man dpopen
|
||||
*/
|
||||
static int
|
||||
db_init_mode(char *file,
|
||||
int omode)
|
||||
{
|
||||
DEPOT *dp;
|
||||
|
||||
/* Open database for writing */
|
||||
if ((dp = dpopen(file, omode | DP_OLCKNB, 0)) == NULL){
|
||||
clicon_err(OE_DB, errno, "dpopen(%s): %s",
|
||||
file,
|
||||
dperrmsg(dpecode));
|
||||
return -1;
|
||||
}
|
||||
clicon_debug(1, "db_init(%s)", file);
|
||||
if (dpclose(dp) == 0){
|
||||
clicon_err(OE_DB, errno, "db_set: dpclose: %s",
|
||||
dperrmsg(dpecode));
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Open database for reading and writing
|
||||
* @param[in] file database file
|
||||
*/
|
||||
int
|
||||
db_init(char *file)
|
||||
{
|
||||
return db_init_mode(file, DP_OWRITER | DP_OCREAT ); /* DP_OTRUNC? */
|
||||
}
|
||||
|
||||
/*! Remove database by removing file, if it exists *
|
||||
* @param[in] file database file
|
||||
*/
|
||||
int
|
||||
db_delete(char *file)
|
||||
{
|
||||
struct stat sb;
|
||||
|
||||
if (stat(file, &sb) < 0){
|
||||
return 0;
|
||||
}
|
||||
if (unlink(file) < 0){
|
||||
clicon_err(OE_DB, errno, "unlink %s", file);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Write data to database
|
||||
* @param[in] file database file
|
||||
* @param[in] key database key
|
||||
* @param[out] data Buffer containing content
|
||||
* @param[out] datalen Length of buffer
|
||||
* @retval 0 if OK: value returned. If not found, zero string returned
|
||||
* @retval -1 on error
|
||||
*/
|
||||
int
|
||||
db_set(char *file,
|
||||
char *key,
|
||||
void *data,
|
||||
size_t datalen)
|
||||
{
|
||||
DEPOT *dp;
|
||||
|
||||
/* Open database for writing */
|
||||
if ((dp = dpopen(file, DP_OWRITER|DP_OLCKNB , 0)) == NULL){
|
||||
clicon_err(OE_DB, errno, "db_set: dpopen(%s): %s",
|
||||
file,
|
||||
dperrmsg(dpecode));
|
||||
return -1;
|
||||
}
|
||||
clicon_debug(2, "%s: db_put(%s, len:%d)",
|
||||
file, key, (int)datalen);
|
||||
if (dpput(dp, key, -1, data, datalen, DP_DOVER) == 0){
|
||||
clicon_err(OE_DB, errno, "%s: db_set: dpput(%s, %d): %s",
|
||||
file,
|
||||
key,
|
||||
datalen,
|
||||
dperrmsg(dpecode));
|
||||
dpclose(dp);
|
||||
return -1;
|
||||
}
|
||||
if (dpclose(dp) == 0){
|
||||
clicon_err(OE_DB, 0, "db_set: dpclose: %s", dperrmsg(dpecode));
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Get data from database
|
||||
* @param[in] file database file
|
||||
* @param[in] key database key
|
||||
* @param[out] data Pre-allocated buffer where data corresponding key is placed
|
||||
* @param[out] datalen Length of pre-allocated buffer
|
||||
* @retval 0 if OK: value returned. If not found, zero string returned
|
||||
* @retval -1 on error
|
||||
* @see db_get_alloc Allocates memory
|
||||
*/
|
||||
int
|
||||
db_get(char *file,
|
||||
char *key,
|
||||
void *data,
|
||||
size_t *datalen)
|
||||
{
|
||||
DEPOT *dp;
|
||||
int len;
|
||||
|
||||
/* Open database for readinf */
|
||||
if ((dp = dpopen(file, DP_OREADER | DP_OLCKNB, 0)) == NULL){
|
||||
clicon_err(OE_DB, errno, "%s: db_get(%s, %d): dpopen: %s",
|
||||
file,
|
||||
key,
|
||||
datalen,
|
||||
dperrmsg(dpecode));
|
||||
return -1;
|
||||
}
|
||||
len = dpgetwb(dp, key, -1, 0, *datalen, data);
|
||||
if (len < 0){
|
||||
if (dpecode == DP_ENOITEM){
|
||||
data = NULL;
|
||||
*datalen = 0;
|
||||
}
|
||||
else{
|
||||
clicon_err(OE_DB, errno, "db_get: dpgetwb: %s (%d)",
|
||||
dperrmsg(dpecode), dpecode);
|
||||
dpclose(dp);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
*datalen = len;
|
||||
clicon_debug(2, "db_get(%s, %s)=%s", file, key, (char*)data);
|
||||
if (dpclose(dp) == 0){
|
||||
clicon_err(OE_DB, errno, "db_get: dpclose: %s", dperrmsg(dpecode));
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Get data from database and allocates memory
|
||||
* Similar to db_get but returns a malloced pointer to the data instead
|
||||
* of copying data to pre-allocated buffer. This is necessary if the
|
||||
* length of the data is not known when calling the function.
|
||||
* @param[in] file database file
|
||||
* @param[in] key database key
|
||||
* @param[out] data Allocated buffer where data corresponding key is placed
|
||||
* @param[out] datalen Length of pre-allocated buffer
|
||||
* @retval 0 if OK: value returned. If not found, zero string returned
|
||||
* @retval -1 on error
|
||||
* @note: *data needs to be freed after use.
|
||||
* @code
|
||||
* char *lvec = NULL;
|
||||
* size_t len = 0;
|
||||
* if (db_get-alloc(dbname, "a.0", &val, &vlen) == NULL)
|
||||
* return -1;
|
||||
* ..do stuff..
|
||||
* if (val) free(val);
|
||||
* @endcode
|
||||
* @see db_get Pre-allocates memory
|
||||
*/
|
||||
int
|
||||
db_get_alloc(char *file,
|
||||
char *key,
|
||||
void **data,
|
||||
size_t *datalen)
|
||||
{
|
||||
DEPOT *dp;
|
||||
int len;
|
||||
|
||||
/* Open database for writing */
|
||||
if ((dp = dpopen(file, DP_OREADER | DP_OLCKNB, 0)) == NULL){
|
||||
clicon_err(OE_DB, errno, "%s: dpopen(%s): %s",
|
||||
__FUNCTION__,
|
||||
file,
|
||||
dperrmsg(dpecode));
|
||||
return -1;
|
||||
}
|
||||
if ((*data = dpget(dp, key, -1, 0, -1, &len)) == NULL){
|
||||
if (dpecode == DP_ENOITEM){
|
||||
*datalen = 0;
|
||||
*data = NULL;
|
||||
len = 0;
|
||||
}
|
||||
else{
|
||||
/* No entry vs error? */
|
||||
clicon_err(OE_DB, errno, "db_get_alloc: dpgetwb: %s (%d)",
|
||||
dperrmsg(dpecode), dpecode);
|
||||
dpclose(dp);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
*datalen = len;
|
||||
if (dpclose(dp) == 0){
|
||||
clicon_err(OE_DB, errno, "db_get_alloc: dpclose: %s", dperrmsg(dpecode));
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Delete database entry
|
||||
* @param[in] file database file
|
||||
* @param[in] key database key
|
||||
* @retval -1 on failure,
|
||||
* @retval 0 if key did not exist
|
||||
* @retval 1 if successful.
|
||||
*/
|
||||
int
|
||||
db_del(char *file, char *key)
|
||||
{
|
||||
int retval = 0;
|
||||
DEPOT *dp;
|
||||
|
||||
/* Open database for writing */
|
||||
if ((dp = dpopen(file, DP_OWRITER | DP_OLCKNB, 0)) == NULL){
|
||||
clicon_err(OE_DB, errno, "db_del: dpopen(%s): %s",
|
||||
file,
|
||||
dperrmsg(dpecode));
|
||||
return -1;
|
||||
}
|
||||
if (dpout(dp, key, -1)) {
|
||||
retval = 1;
|
||||
}
|
||||
if (dpclose(dp) == 0){
|
||||
clicon_err(OE_DB, errno, "db_del: dpclose: %s", dperrmsg(dpecode));
|
||||
return -1;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Check if entry in database exists
|
||||
* @param[in] file database file
|
||||
* @param[in] key database key
|
||||
* @retval 1 if key exists in database
|
||||
* @retval 0 key does not exist in database
|
||||
* @retval -1 error
|
||||
*/
|
||||
int
|
||||
db_exists(char *file,
|
||||
char *key)
|
||||
{
|
||||
DEPOT *dp;
|
||||
int len;
|
||||
|
||||
/* Open database for reading */
|
||||
if ((dp = dpopen(file, DP_OREADER | DP_OLCKNB, 0)) == NULL){
|
||||
clicon_err(OE_DB, errno, "%s: dpopen: %s",
|
||||
__FUNCTION__, dperrmsg(dpecode));
|
||||
return -1;
|
||||
}
|
||||
|
||||
len = dpvsiz(dp, key, -1);
|
||||
if (len < 0 && dpecode != DP_ENOITEM)
|
||||
clicon_err(OE_DB, errno, "^s: dpvsiz: %s (%d)",
|
||||
__FUNCTION__, dperrmsg(dpecode), dpecode);
|
||||
|
||||
if (dpclose(dp) == 0) {
|
||||
clicon_err(OE_DB, errno, "%s: dpclose: %s", dperrmsg(dpecode),__FUNCTION__);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (len < 0) ? 0 : 1;
|
||||
}
|
||||
|
||||
/*! Return all entries in database that match a regular expression.
|
||||
* @param[in] file database file
|
||||
* @param[in] regexp regular expression for database keys
|
||||
* @param[in] label for memory/chunk allocation
|
||||
* @param[out] pairs Vector of database keys and values
|
||||
* @param[in] noval If set don't retreive values, just keys
|
||||
* @retval -1 on error
|
||||
* @retval n Number of pairs
|
||||
* @code
|
||||
* struct db_pair *pairs;
|
||||
* int npairs;
|
||||
* if ((npairs = db_regexp(dbname, "^/test/kalle$", __FUNCTION__,
|
||||
* &pairs, 0)) < 0)
|
||||
* err;
|
||||
*
|
||||
* @endcode
|
||||
*/
|
||||
int
|
||||
db_regexp(char *file,
|
||||
char *regexp,
|
||||
const char *label,
|
||||
struct db_pair **pairs,
|
||||
int noval)
|
||||
{
|
||||
int npairs;
|
||||
int status;
|
||||
int retval = -1;
|
||||
int vlen = 0;
|
||||
char *key = NULL;
|
||||
void *val = NULL;
|
||||
char errbuf[512];
|
||||
struct db_pair *pair;
|
||||
struct db_pair *newpairs;
|
||||
regex_t iterre;
|
||||
DEPOT *iterdp = NULL;
|
||||
regmatch_t pmatch[1];
|
||||
size_t nmatch = 1;
|
||||
|
||||
npairs = 0;
|
||||
*pairs = NULL;
|
||||
|
||||
if (regexp) {
|
||||
if ((status = regcomp(&iterre, regexp, REG_EXTENDED)) != 0) {
|
||||
regerror(status, &iterre, errbuf, sizeof(errbuf));
|
||||
clicon_err(OE_DB, errno, "%s: regcomp: %s", __FUNCTION__, errbuf);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Open database for reading */
|
||||
if ((iterdp = dpopen(file, DP_OREADER | DP_OLCKNB, 0)) == NULL){
|
||||
clicon_err(OE_DB, 0, "%s: dpopen(%s): %s",
|
||||
__FUNCTION__, file, dperrmsg(dpecode));
|
||||
goto quit;
|
||||
}
|
||||
|
||||
/* Initiate iterator */
|
||||
if(dpiterinit(iterdp) == 0) {
|
||||
clicon_err(OE_DB, errno, "%s: dpiterinit: %s", __FUNCTION__, dperrmsg(dpecode));
|
||||
goto quit;
|
||||
}
|
||||
|
||||
/* Iterate through DB */
|
||||
while((key = dpiternext(iterdp, NULL)) != NULL) {
|
||||
|
||||
if (regexp && regexec(&iterre, key, nmatch, pmatch, 0) != 0) {
|
||||
free(key);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Retrieve value if required */
|
||||
if ( ! noval) {
|
||||
if((val = dpget(iterdp, key, -1, 0, -1, &vlen)) == NULL) {
|
||||
clicon_log(LOG_WARNING, "%s: dpget: %s", __FUNCTION__, dperrmsg(dpecode));
|
||||
goto quit;
|
||||
}
|
||||
}
|
||||
|
||||
/* Resize and populate resulting array */
|
||||
newpairs = rechunk(*pairs, (npairs+1) * sizeof(struct db_pair), label);
|
||||
if (newpairs == NULL) {
|
||||
clicon_err(OE_DB, errno, "%s: rechunk", __FUNCTION__);
|
||||
goto quit;
|
||||
}
|
||||
pair = &newpairs[npairs];
|
||||
memset(pair, 0, sizeof(*pair));
|
||||
|
||||
pair->dp_key = chunk_sprintf(label, "%s", key);
|
||||
if (regexp)
|
||||
pair->dp_matched = chunk_sprintf(label, "%.*s",
|
||||
pmatch[0].rm_eo - pmatch[0].rm_so,
|
||||
key + pmatch[0].rm_so);
|
||||
else
|
||||
pair->dp_matched = chunk_sprintf(label, "%s", key);
|
||||
if (pair->dp_key == NULL || pair->dp_matched == NULL) {
|
||||
clicon_err(OE_DB, errno, "%s: chunk_sprintf");
|
||||
goto quit;
|
||||
}
|
||||
if ( ! noval) {
|
||||
if (vlen){
|
||||
pair->dp_val = chunkdup(val, vlen, label);
|
||||
if (pair->dp_val == NULL) {
|
||||
clicon_err(OE_DB, errno, "%s: chunkdup", __FUNCTION__);
|
||||
goto quit;
|
||||
}
|
||||
}
|
||||
pair->dp_vlen = vlen;
|
||||
free(val);
|
||||
val = NULL;
|
||||
}
|
||||
|
||||
(*pairs) = newpairs;
|
||||
npairs++;
|
||||
free(key);
|
||||
}
|
||||
|
||||
retval = npairs;
|
||||
|
||||
quit:
|
||||
if (key)
|
||||
free(key);
|
||||
if (val)
|
||||
free(val);
|
||||
if (regexp)
|
||||
regfree(&iterre);
|
||||
if (iterdp)
|
||||
dpclose(iterdp);
|
||||
if (retval < 0)
|
||||
unchunk_group(label);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Sanitize regexp string. Escape '\' etc.
|
||||
*/
|
||||
char *
|
||||
db_sanitize(char *rx, const char *label)
|
||||
{
|
||||
char *new;
|
||||
char *k, *p, *s;
|
||||
|
||||
k = chunk_sprintf(__FUNCTION__, "%s", "");
|
||||
p = rx;
|
||||
while((s = strstr(p, "\\"))) {
|
||||
if ((k = chunk_sprintf(__FUNCTION__, "%s%.*s\\\\", k, s-p, p)) == NULL)
|
||||
goto quit;
|
||||
p = s+1;
|
||||
}
|
||||
if ((k = chunk_strncat(k, p, strlen(p), __FUNCTION__)) == NULL)
|
||||
goto quit;
|
||||
|
||||
new = (char *)chunkdup(k, strlen(k)+1, label);
|
||||
unchunk_group(__FUNCTION__);
|
||||
return new;
|
||||
|
||||
quit:
|
||||
unchunk_group(__FUNCTION__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#if 0 /* Test program */
|
||||
/*
|
||||
* Turn this on to get an xpath test program
|
||||
* Usage: clicon_xpath [<xpath>]
|
||||
* read xml from input
|
||||
* Example compile:
|
||||
gcc -g -o qdb -I. -I../clixon ./clixon_qdb.c -lclixon -lcligen -lqdbm
|
||||
*/
|
||||
|
||||
static int
|
||||
usage(char *argv0)
|
||||
{
|
||||
fprintf(stderr, "usage:\n");
|
||||
fprintf(stderr, "\t%s init <filename>\n", argv0);
|
||||
fprintf(stderr, "\t%s read <filename> <key>\n", argv0);
|
||||
fprintf(stderr, "\t%s write <filename> <key> <val>\n", argv0);
|
||||
fprintf(stderr, "\t%s openread <filename>\n", argv0);
|
||||
fprintf(stderr, "\t%s openwrite <filename>\n", argv0);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
char *verb;
|
||||
char *filename;
|
||||
char *key;
|
||||
char *val;
|
||||
size_t len;
|
||||
DEPOT *dp;
|
||||
|
||||
if (argc < 3)
|
||||
usage(argv[0]);
|
||||
clicon_log_init(__FILE__, LOG_INFO, CLICON_LOG_STDERR);
|
||||
verb = argv[1];
|
||||
filename = argv[2];
|
||||
if (strcmp(verb, "init")==0){
|
||||
db_init(filename);
|
||||
}
|
||||
else if (strcmp(verb, "read")==0){
|
||||
if (argc < 4)
|
||||
usage(argv[0]);
|
||||
key = argv[3];
|
||||
db_get_alloc(filename, key, (void**)&val, &len);
|
||||
fprintf(stdout, "%s\n", val);
|
||||
}
|
||||
else if (strcmp(verb, "write")==0){
|
||||
if (argc < 5)
|
||||
usage(argv[0]);
|
||||
key = argv[3];
|
||||
val = argv[4];
|
||||
db_set(filename, key, val, strlen(val)+1);
|
||||
}
|
||||
else if (strcmp(verb, "openread")==0){
|
||||
if ((dp = dpopen(filename, DP_OREADER | DP_OLCKNB, 0)) == NULL){
|
||||
clicon_err(OE_DB, errno, "dbopen: %s",
|
||||
dperrmsg(dpecode));
|
||||
return -1;
|
||||
}
|
||||
sleep(1000000);
|
||||
}
|
||||
else if (strcmp(verb, "openwrite")==0){
|
||||
if ((dp = dpopen(filename, DP_OWRITER | DP_OLCKNB, 0)) == NULL){
|
||||
clicon_err(OE_DB, errno, "dbopen: %s",
|
||||
dperrmsg(dpecode));
|
||||
return -1;
|
||||
}
|
||||
sleep(1000000);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* Test program */
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
#
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
@ -88,6 +88,8 @@ struct text_handle {
|
|||
Assumes single backend*/
|
||||
char *th_format; /* Datastroe format: xml / json */
|
||||
int th_pretty; /* Store xml/json pretty-printed. */
|
||||
char *th_nacm_mode;
|
||||
cxobj *th_nacm_xtree;
|
||||
};
|
||||
|
||||
/* Struct per database in hash */
|
||||
|
|
@ -207,6 +209,8 @@ text_disconnect(xmldb_handle xh)
|
|||
}
|
||||
hash_free(th->th_dbs);
|
||||
}
|
||||
if (th->th_nacm_mode)
|
||||
free(th->th_nacm_mode);
|
||||
free(th);
|
||||
}
|
||||
retval = 0;
|
||||
|
|
@ -239,6 +243,10 @@ text_getopt(xmldb_handle xh,
|
|||
*value = th->th_format;
|
||||
else if (strcmp(optname, "pretty") == 0)
|
||||
*value = &th->th_pretty;
|
||||
else if (strcmp(optname, "nacm_mode") == 0)
|
||||
*value = &th->th_nacm_mode;
|
||||
else if (strcmp(optname, "nacm_xtree") == 0)
|
||||
*value = th->th_nacm_xtree;
|
||||
else{
|
||||
clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname);
|
||||
goto done;
|
||||
|
|
@ -288,6 +296,15 @@ text_setopt(xmldb_handle xh,
|
|||
else if (strcmp(optname, "pretty") == 0){
|
||||
th->th_pretty = (intptr_t)value;
|
||||
}
|
||||
else if (strcmp(optname, "nacm_mode") == 0){
|
||||
if (value && (th->th_nacm_mode = strdup((char*)value)) == NULL){
|
||||
clicon_err(OE_UNIX, 0, "strdup");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
else if (strcmp(optname, "nacm_xtree") == 0){
|
||||
th->th_nacm_xtree = (cxobj*)value;
|
||||
}
|
||||
else{
|
||||
clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname);
|
||||
goto done;
|
||||
|
|
@ -358,6 +375,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
|
||||
|
|
@ -411,10 +438,17 @@ xml_copy_marked(cxobj *x0,
|
|||
* The function returns a minimal tree that includes all sub-trees that match
|
||||
* xpath.
|
||||
* This is a clixon datastore plugin of the the xmldb api
|
||||
* @see xmldb_get
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] dbname Name of database to search in (filename including dir path
|
||||
* @param[in] xpath String with XPATH syntax. or NULL for all
|
||||
* @param[in] config If set only configuration data, else also state
|
||||
* @param[out] xret Single return XML tree. Free with xml_free()
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
* @see xmldb_get the generic API function
|
||||
*/
|
||||
int
|
||||
text_get(xmldb_handle xh,
|
||||
text_get(xmldb_handle xh,
|
||||
const char *db,
|
||||
char *xpath,
|
||||
int config,
|
||||
|
|
@ -424,6 +458,7 @@ text_get(xmldb_handle xh,
|
|||
char *dbfile = NULL;
|
||||
yang_spec *yspec;
|
||||
cxobj *xt = NULL;
|
||||
cxobj *x;
|
||||
int fd = -1;
|
||||
cxobj **xvec = NULL;
|
||||
size_t xlen;
|
||||
|
|
@ -470,9 +505,12 @@ 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> */
|
||||
|
||||
if (xpath_vec(xt, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
|
||||
goto done;
|
||||
|
||||
|
|
@ -482,11 +520,13 @@ text_get(xmldb_handle xh,
|
|||
*/
|
||||
if (xvec != NULL)
|
||||
for (i=0; i<xlen; i++){
|
||||
xml_flag_set(xvec[i], XML_FLAG_MARK);
|
||||
if (th->th_cache)
|
||||
xml_apply_ancestor(xvec[i], (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE);
|
||||
x = xvec[i];
|
||||
xml_flag_set(x, XML_FLAG_MARK);
|
||||
if (1 || th->th_cache)
|
||||
xml_apply_ancestor(x, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE);
|
||||
}
|
||||
|
||||
|
||||
if (th->th_cache){
|
||||
/* Copy the matching parts of the (relevant) XML tree.
|
||||
* If cache was NULL, also write to datastore cache
|
||||
|
|
@ -520,22 +560,22 @@ text_get(xmldb_handle xh,
|
|||
/* reset flag */
|
||||
if (xml_apply(xt, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0)
|
||||
goto done;
|
||||
|
||||
/* filter out state (operations) data if config not set. Mark all nodes
|
||||
that are not config data */
|
||||
if (config && xml_apply(xt, CX_ELMNT, xml_non_config_data, NULL) < 0)
|
||||
goto done;
|
||||
/* Remove (prune) nodes that are marked (that does not pass test) */
|
||||
if (xml_tree_prune_flagged(xt, XML_FLAG_MARK, 1) < 0)
|
||||
goto done;
|
||||
if (config){
|
||||
if (xml_apply(xt, CX_ELMNT, xml_non_config_data, NULL) < 0)
|
||||
goto done;
|
||||
/* Remove (prune) nodes that are marked (that does not pass test) */
|
||||
if (xml_tree_prune_flagged(xt, XML_FLAG_MARK, 1) < 0)
|
||||
goto done;
|
||||
}
|
||||
/* 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,34 +595,49 @@ 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[in] username User name of requestor for nacm
|
||||
* @param[in] xnacm NACM XML tree
|
||||
* @param[in] permit If set, NACM has permitted this tree on an upper level
|
||||
* @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,
|
||||
enum operation_type op,
|
||||
char *username,
|
||||
cxobj *xnacm,
|
||||
int permit,
|
||||
cbuf *cbret)
|
||||
{
|
||||
int retval = -1;
|
||||
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 */
|
||||
char *x0bstr; /* mod body string */
|
||||
char *x1bstr; /* mod body string */
|
||||
yang_stmt *yc; /* yang child */
|
||||
cxobj **x0vec = NULL;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
assert(x1 && xml_type(x1) == CX_ELMNT);
|
||||
assert(y0);
|
||||
|
|
@ -598,15 +653,29 @@ 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:
|
||||
case OP_REPLACE:
|
||||
if (x0==NULL){
|
||||
if ((op != OP_NONE) && !permit && xnacm){
|
||||
if ((ret = nacm_datanode_write(NULL, x1, NACM_CREATE, username, xnacm, cbret)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
permit = 1;
|
||||
}
|
||||
// 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)
|
||||
|
|
@ -623,24 +692,39 @@ text_modify(cxobj *x0,
|
|||
}
|
||||
}
|
||||
if (x1bstr){
|
||||
if ((x0b = xml_body_get(x0)) == NULL){
|
||||
if ((x0b = xml_new("body", x0, NULL)) == NULL)
|
||||
goto done;
|
||||
xml_type_set(x0b, CX_BODY);
|
||||
if ((x0b = xml_body_get(x0)) != NULL){
|
||||
x0bstr = xml_value(x0b);
|
||||
if (x0bstr==NULL || strcmp(x0bstr, x1bstr)){
|
||||
if ((op != OP_NONE) && !permit && xnacm){
|
||||
if ((ret = nacm_datanode_write(NULL, x1,
|
||||
x0bstr==NULL?NACM_CREATE:NACM_UPDATE,
|
||||
username, xnacm, cbret)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
}
|
||||
if (xml_value_set(x0b, x1bstr) < 0)
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
if (xml_value_set(x0b, x1bstr) < 0)
|
||||
goto done;
|
||||
}
|
||||
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){
|
||||
xml_purge(x0);
|
||||
if ((op != OP_NONE) && !permit && xnacm){
|
||||
if ((ret = nacm_datanode_write(NULL, x0, NACM_DELETE, username, xnacm, cbret)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
}
|
||||
if (xml_purge(x0) < 0)
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
|
@ -653,9 +737,16 @@ 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 (xnacm && !permit){
|
||||
if ((ret = nacm_datanode_write(NULL, x1, x0?NACM_UPDATE:NACM_CREATE, username, xnacm, cbret)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
permit = 1;
|
||||
}
|
||||
if (x0){
|
||||
xml_purge(x0);
|
||||
x0 = NULL;
|
||||
|
|
@ -663,10 +754,21 @@ text_modify(cxobj *x0,
|
|||
case OP_MERGE: /* fall thru */
|
||||
case OP_NONE:
|
||||
/* Special case: anyxml, just replace tree,
|
||||
See 7.10.3 of RFC6020bis */
|
||||
See rfc6020 7.10.3:n
|
||||
An anyxml node is treated as an opaque chunk of data. This data
|
||||
can be modified in its entirety only.
|
||||
Any "operation" attributes present on subelements of an anyxml
|
||||
node are ignored by the NETCONF server.*/
|
||||
if (y0->yn_keyword == Y_ANYXML){
|
||||
if (op == OP_NONE)
|
||||
break;
|
||||
if (xnacm && op==OP_MERGE && !permit){
|
||||
if ((ret = nacm_datanode_write(NULL, x0, x0?NACM_UPDATE:NACM_CREATE, username, xnacm, cbret)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
permit = 1;
|
||||
}
|
||||
if (x0){
|
||||
xml_purge(x0);
|
||||
}
|
||||
|
|
@ -677,13 +779,28 @@ text_modify(cxobj *x0,
|
|||
break;
|
||||
}
|
||||
if (x0==NULL){
|
||||
if (xnacm && op==OP_MERGE && !permit){
|
||||
if ((ret = nacm_datanode_write(NULL, x0, x0?NACM_UPDATE:NACM_CREATE, username, xnacm, cbret)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
permit = 1;
|
||||
}
|
||||
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,59 +816,91 @@ 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;
|
||||
}
|
||||
#endif
|
||||
x0vec[i++] = x0c; /* != NULL if x0c is matching x1c */
|
||||
}
|
||||
/* Second pass: modify tree */
|
||||
/* 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,
|
||||
username, xnacm, permit, 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)
|
||||
xml_purge(x0);
|
||||
if (x0){
|
||||
if (xnacm){
|
||||
if ((ret = nacm_datanode_write(NULL, x0, NACM_DELETE, username, xnacm, cbret)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
}
|
||||
if (xml_purge(x0) < 0)
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
} /* 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] x0 Base xml tree (can be NULL in add scenarios)
|
||||
* @param[in] x1 xml tree which modifies base
|
||||
* @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[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc
|
||||
* @param[in] username User name of requestor for nacm
|
||||
* @param[in] xnacm NACM XML tree
|
||||
* @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
|
||||
text_modify_top(cxobj *x0,
|
||||
text_modify_top(struct text_handle *th,
|
||||
cxobj *x0,
|
||||
cxobj *x1,
|
||||
yang_spec *yspec,
|
||||
enum operation_type op,
|
||||
char *username,
|
||||
cxobj *xnacm,
|
||||
cbuf *cbret)
|
||||
{
|
||||
int retval = -1;
|
||||
|
|
@ -759,7 +908,10 @@ text_modify_top(cxobj *x0,
|
|||
cxobj *x0c; /* base child */
|
||||
cxobj *x1c; /* mod child */
|
||||
yang_stmt *yc; /* yang child */
|
||||
yang_stmt *ymod;/* yang module */
|
||||
char *opstr;
|
||||
int ret;
|
||||
int permit = 0;
|
||||
|
||||
/* Assure top-levels are 'config' */
|
||||
assert(x0 && strcmp(xml_name(x0),"config")==0);
|
||||
|
|
@ -770,62 +922,104 @@ text_modify_top(cxobj *x0,
|
|||
if (xml_operation(opstr, &op) < 0)
|
||||
goto done;
|
||||
/* Special case if x1 is empty, top-level only <config/> */
|
||||
if (xml_child_nr(x1) == 0){
|
||||
if (xml_child_nr(x0)) /* base tree not empty */
|
||||
if (xml_child_nr_type(x1, CX_ELMNT) == 0){
|
||||
if (xml_child_nr_type(x0, CX_ELMNT)){ /* base tree not empty */
|
||||
switch(op){
|
||||
case OP_DELETE:
|
||||
case OP_REMOVE:
|
||||
case OP_REPLACE:
|
||||
x0c = NULL;
|
||||
while ((x0c = xml_child_each(x0, x0c, CX_ELMNT)) != NULL)
|
||||
xml_purge(x0c);
|
||||
if (xnacm){
|
||||
if ((ret = nacm_datanode_write(NULL, x0, NACM_DELETE, username, xnacm, cbret)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
permit = 1;
|
||||
}
|
||||
while ((x0c = xml_child_i(x0, 0)) != 0)
|
||||
if (xml_purge(x0c) < 0)
|
||||
goto done;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else /* base tree empty */
|
||||
switch(op){
|
||||
#if 0 /* According to RFC6020 7.5.8 you cant delete a non-existing object.
|
||||
On the other hand, the top-level cannot be removed anyway.
|
||||
Additionally, I think this is irritating so I disable it.
|
||||
I.e., curl -u andy:bar -sS -X DELETE http://localhost/restconf/data
|
||||
*/
|
||||
case OP_DELETE:
|
||||
if (netconf_data_missing(cbret, "Data does not exist; cannot delete resource") < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
goto fail;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Special case top-level replace */
|
||||
if (op == OP_REPLACE || op == OP_DELETE){
|
||||
x0c = NULL;
|
||||
else if (op == OP_REPLACE || op == OP_DELETE){
|
||||
if (xnacm && !permit){
|
||||
if ((ret = nacm_datanode_write(NULL, x1, NACM_UPDATE, username, xnacm, cbret)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
permit = 1;
|
||||
}
|
||||
while ((x0c = xml_child_i(x0, 0)) != 0)
|
||||
xml_purge(x0c);
|
||||
if (xml_purge(x0c) < 0)
|
||||
goto done;
|
||||
}
|
||||
/* Loop through children of the modification tree */
|
||||
x1c = NULL;
|
||||
while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) {
|
||||
x1cname = xml_name(x1c);
|
||||
/* Get yang spec of the child */
|
||||
if ((yc = yang_find_topnode(yspec, x1cname, YC_DATANODE)) == NULL){
|
||||
clicon_err(OE_YANG, ENOENT, "XML node %s/%s has no corresponding yang specification (Invalid XML or wrong Yang spec?",
|
||||
xml_name(x1), x1cname);
|
||||
yc = NULL;
|
||||
if (ys_module_by_xml(yspec, x1c, &ymod) <0)
|
||||
goto done;
|
||||
if (ymod != NULL)
|
||||
yc = yang_find_datanode((yang_node*)ymod, x1cname);
|
||||
if (yc == NULL && !_CLICON_XML_NS_STRICT){
|
||||
if (xml_yang_find_non_strict(x1c, yspec, &yc) < 0)
|
||||
goto done;
|
||||
}
|
||||
if (yc == NULL){
|
||||
if (netconf_unknown_element(cbret, "application", x1cname, "Unassigned yang spec") < 0)
|
||||
goto done;
|
||||
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,
|
||||
username, xnacm, permit, 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:
|
||||
|
|
@ -854,7 +1048,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;
|
||||
|
|
@ -864,13 +1058,23 @@ xml_container_presence(cxobj *x,
|
|||
|
||||
/*! Modify database provided an xml tree and an operation
|
||||
* This is a clixon datastore plugin of the the xmldb api
|
||||
* @see xmldb_put
|
||||
* @param[in] h CLICON handle
|
||||
* @param[in] db running or candidate
|
||||
* @param[in] op Top-level operation, can be superceded by other op in tree
|
||||
* @param[in] x1 xml-tree. Top-level symbol is dummy
|
||||
* @param[in] username User name for nacm
|
||||
* @param[out] cbret Initialized cligen buffer. On exit contains XML if retval == 0
|
||||
* @retval 1 OK
|
||||
* @retval 0 Failed, cbret contains error xml message
|
||||
* @retval -1 Error
|
||||
* @see xmldb_put the generic API function
|
||||
*/
|
||||
int
|
||||
text_put(xmldb_handle xh,
|
||||
const char *db,
|
||||
enum operation_type op,
|
||||
cxobj *x1,
|
||||
char *username,
|
||||
cbuf *cbret)
|
||||
{
|
||||
int retval = -1;
|
||||
|
|
@ -882,14 +1086,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;
|
||||
cxobj *xnacm = NULL;
|
||||
|
||||
if (cbret == NULL){
|
||||
if ((cbret = cbuf_new()) == NULL){
|
||||
clicon_err(OE_XML, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
cbretlocal++;
|
||||
clicon_err(OE_XML, EINVAL, "cbret is NULL");
|
||||
goto done;
|
||||
}
|
||||
if ((yspec = th->th_yangspec) == NULL){
|
||||
clicon_err(OE_YANG, ENOENT, "No yang spec");
|
||||
|
|
@ -942,24 +1144,41 @@ text_put(xmldb_handle xh,
|
|||
xml_name(x0));
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Add yang specification backpointer to all XML nodes */
|
||||
/* XXX: where is this created? Add yspec */
|
||||
if (xml_apply(x1, CX_ELMNT, xml_spec_populate, yspec) < 0)
|
||||
goto done;
|
||||
#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
|
||||
#if 1
|
||||
{
|
||||
char *mode;
|
||||
cxobj *xnacm0 = NULL;
|
||||
|
||||
mode = th->th_nacm_mode;
|
||||
if (mode){
|
||||
if (strcmp(mode, "external")==0)
|
||||
xnacm0 = th->th_nacm_xtree;
|
||||
else if (strcmp(mode, "internal")==0)
|
||||
xnacm0 = x0;
|
||||
}
|
||||
if (xnacm0 != NULL &&
|
||||
(xnacm = xpath_first(xnacm0, "nacm")) != NULL){
|
||||
/* Pre-NACM access step */
|
||||
if ((ret = nacm_access(mode, xnacm, username)) < 0)
|
||||
goto done;
|
||||
}
|
||||
/* Here assume if xnacm is set (actually may be ret==0?) do NACM */
|
||||
}
|
||||
|
||||
#endif
|
||||
/*
|
||||
* Modify base tree x with modification x1. This is where the
|
||||
* new tree is made.
|
||||
*/
|
||||
if (text_modify_top(x0, x1, yspec, op, cbret) < 0)
|
||||
if ((ret = text_modify_top(th, x0, x1, yspec, op, username, xnacm, 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)
|
||||
|
|
@ -974,7 +1193,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 */
|
||||
|
|
@ -1009,11 +1228,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)
|
||||
|
|
@ -1025,6 +1241,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
|
||||
|
|
@ -1367,10 +1586,11 @@ main(int argc,
|
|||
enum operation_type op;
|
||||
char *cmd;
|
||||
char *db;
|
||||
char *yangdir;
|
||||
char *yangmod;
|
||||
char *yangmod; /* yang file */
|
||||
yang_spec *yspec = NULL;
|
||||
clicon_handle h;
|
||||
cbuf *cbret = NULL;
|
||||
int ret;
|
||||
|
||||
if ((h = clicon_handle_init()) == NULL)
|
||||
goto done;
|
||||
|
|
@ -1381,12 +1601,11 @@ main(int argc,
|
|||
}
|
||||
cmd = argv[1];
|
||||
db = argv[2];
|
||||
yangdir = argv[3];
|
||||
yangmod = argv[4];
|
||||
db_init(db);
|
||||
if ((yspec = yspec_new()) == NULL)
|
||||
goto done
|
||||
if (yang_parse(h, NULL, yangmod, yangdir, NULL, yspec) < 0)
|
||||
if (yang_spec_parse_module(h, yangmod, NULL, yspec) < 0)
|
||||
goto done;
|
||||
if (strcmp(cmd, "get")==0){
|
||||
if (argc < 5)
|
||||
|
|
@ -1420,13 +1639,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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
@ -40,7 +40,7 @@
|
|||
* Prototypes
|
||||
*/
|
||||
int text_get(xmldb_handle h, const char *db, char *xpath, int config, cxobj **xtop);
|
||||
int text_put(xmldb_handle h, const char *db, enum operation_type op, cxobj *xt, cbuf *cbret);
|
||||
int text_put(xmldb_handle h, const char *db, enum operation_type op, cxobj *xt, char *username, cbuf *cbret);
|
||||
int text_dump(FILE *f, char *dbfilename, char *rxkey);
|
||||
int text_copy(xmldb_handle h, const char *from, const char *to);
|
||||
int text_lock(xmldb_handle h, const char *db, int pid);
|
||||
|
|
|
|||
229
doc/FAQ.md
229
doc/FAQ.md
|
|
@ -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,70 +72,37 @@ grep clicon /etc/group
|
|||
clicon:x:1001:<user>,www-data
|
||||
```
|
||||
|
||||
## What about reference documentation?
|
||||
Clixon uses Doxygen for reference documentation.
|
||||
Build using 'make doc' and aim your browser at doc/html/index.html.
|
||||
## How do I use the CLI?
|
||||
|
||||
## How is configuration data stored?
|
||||
Configuration data is stored in an XML datastore. In the example the
|
||||
datastore are regular files found in /usr/local/var/example/.
|
||||
|
||||
## What is validate and commit?
|
||||
Clixon follows netconf in its validate and commit semantics.
|
||||
In short, you edit a 'candidate' configuration, which is first
|
||||
'validated' for consistency and then 'committed' to the 'running'
|
||||
configuration.
|
||||
|
||||
A clixon developer writes commit functions to incrementaly upgrade a
|
||||
system state based on configuration changes. Writing commit callbacks
|
||||
is the core functionality of a clixon system.
|
||||
|
||||
## What is a Clixon configuration file?
|
||||
|
||||
Clixon options are stored in an XML configuration file. The default
|
||||
configuration file is /usr/local/etc/clixon.xml. The example
|
||||
configuration file is installed at /usr/local/etc/example.xml. The
|
||||
YANG specification for the configuration file is clixon-config.yang.
|
||||
|
||||
You can change where Clixon looks for the configuration FILE as follows:
|
||||
- Provide -f FILE option when starting a program (eg clixon_backend -f FILE)
|
||||
- Provide --with-configfile=FILE when configuring
|
||||
- Provide --with-sysconfig=<dir> when configuring, then FILE is <dir>/clixon.xml
|
||||
- Provide --sysconfig=<dir> when configuring then FILE is <dir>/etc/clixon.xml
|
||||
- FILE is /usr/local/etc/clixon.xml
|
||||
|
||||
## How do I enable Yang features?
|
||||
|
||||
Yang models have features, and parts of a specification can be
|
||||
conditional using the if-feature statement. In Clixon, features are
|
||||
enabled in the configuration file using <CLICON_FEATURE>.
|
||||
|
||||
The example below shows enabling a specific feature; enabling all features in module; and enabling all features in all modules, respectively:
|
||||
The easiest way to use Clixon is via the CLI. Once the backend is started
|
||||
Example:
|
||||
```
|
||||
<CLICON_FEATURE>ietf-routing:router-id</CLICON_FEATURE>
|
||||
<CLICON_FEATURE>ietf-routing:*</CLICON_FEATURE>
|
||||
<CLICON_FEATURE>*:*</CLICON_FEATURE>
|
||||
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
|
||||
```
|
||||
|
||||
Features can be probed by using RFC 7895 Yang module library which provides
|
||||
information on all modules and which features are enabled.
|
||||
|
||||
## Can I run Clixon as docker containers?
|
||||
|
||||
Yes, the example works as docker containers as well. There should be a
|
||||
prepared container in docker hub for the example where the backend and
|
||||
CLI is bundled.
|
||||
```
|
||||
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
|
||||
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
|
||||
|
|
@ -170,18 +137,118 @@ Start nginx daemon
|
|||
```
|
||||
sudo /etc/init.d/nginx start
|
||||
```
|
||||
|
||||
Example:
|
||||
Start the clixon restconf daemon
|
||||
```
|
||||
curl -G http://127.0.0.1/restconf/data/interfaces/interface/name=eth9/type
|
||||
sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data
|
||||
```
|
||||
|
||||
Then acess:
|
||||
```
|
||||
curl -G http://127.0.0.1/restconf/data/ietf-interfaces:interfaces/interface=eth9/type
|
||||
[
|
||||
{
|
||||
"type": "eth"
|
||||
"ietf-interfaces:type": "ex:eth"
|
||||
}
|
||||
]
|
||||
```
|
||||
Read more in the (restconf)[../apps/restconf] docs.
|
||||
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.
|
||||
Build it in the doc directory and point the browser to `.../clixon/doc/html/index.html` as follows:
|
||||
```
|
||||
> cd doc
|
||||
> make doc
|
||||
> make graphs # detailed callgraphs
|
||||
```
|
||||
|
||||
## How is configuration data stored?
|
||||
Configuration data is stored in an XML datastore. In the example the
|
||||
datastore are regular files found in /usr/local/var/example/.
|
||||
|
||||
## What is validate and commit?
|
||||
Clixon follows netconf in its validate and commit semantics.
|
||||
In short, you edit a 'candidate' configuration, which is first
|
||||
'validated' for consistency and then 'committed' to the 'running'
|
||||
configuration.
|
||||
|
||||
A clixon developer writes commit functions to incrementaly upgrade a
|
||||
system state based on configuration changes. Writing commit callbacks
|
||||
is the core functionality of a clixon system.
|
||||
|
||||
## What is a Clixon configuration file?
|
||||
|
||||
Clixon options are stored in an XML configuration file. The default
|
||||
configuration file is /usr/local/etc/clixon.xml. The example
|
||||
configuration file is installed at /usr/local/etc/example.xml. The
|
||||
YANG specification for the configuration file is clixon-config.yang.
|
||||
|
||||
## How are Clixon configuration files found?
|
||||
|
||||
Clixon by default finds its configuration file at `/usr/local/etc/clixon.xml`. However, you can modify this location as follows:
|
||||
- Provide -f FILE option when starting a program (eg clixon_backend -f FILE)
|
||||
- Provide --with-configfile=FILE when configuring
|
||||
- Provide --with-sysconfig=<dir> when configuring. Then FILE is <dir>/clixon.xml
|
||||
- Provide --sysconfig=<dir> when configuring. Then FILE is <dir>/etc/clixon.xml
|
||||
- FILE is /usr/local/etc/clixon.xml
|
||||
|
||||
## Can I modify clixon options at runtime?
|
||||
|
||||
Yes, when you start a clixon program, you can supply the `-o` option to modify the configuration specified in the configuration file. Options that are leafs are overriden, whereas options that are leaf-lists are added to.
|
||||
|
||||
Example, add the "usr/local/share/ietf" directory to the list of directories where yang files are searched for:
|
||||
```
|
||||
clixon_cli -o CLICON_YANG_DIR=/usr/local/share/ietf
|
||||
```
|
||||
|
||||
## How are Yang files found?
|
||||
|
||||
Yang files contain the configuration specification. A Clixon
|
||||
application loads yang files and clixon itself loads system yang
|
||||
files. When Yang files are loaded modules are imported and submodules
|
||||
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 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.
|
||||
|
||||
Note that the special `CLIXON_DATADIR`, by default `/usr/local/share/clixon` should be included in the yang dir path for Clixon system files to be found.
|
||||
|
||||
You can combine the options, however, if there are different variants
|
||||
of the same module, more specific options override less
|
||||
specific. The precedence of the options are as follows:
|
||||
- `CLICON_YANG_MAIN_FILE`
|
||||
- `CLICON_YANG_MODULE_MAIN`
|
||||
- `CLICON_YANG_MAIN_DIR`
|
||||
|
||||
## How do I enable Yang features?
|
||||
|
||||
Yang models have features, and parts of a specification can be
|
||||
conditional using the if-feature statement. In Clixon, features are
|
||||
enabled in the configuration file using <CLICON_FEATURE>.
|
||||
|
||||
The example below shows enabling a specific feature; enabling all features in module; and enabling all features in all modules, respectively:
|
||||
```
|
||||
<CLICON_FEATURE>ietf-routing:router-id</CLICON_FEATURE>
|
||||
<CLICON_FEATURE>ietf-routing:*</CLICON_FEATURE>
|
||||
<CLICON_FEATURE>*:*</CLICON_FEATURE>
|
||||
```
|
||||
|
||||
Features can be probed by using RFC 7895 Yang module library which provides
|
||||
information on all modules and which features are enabled.
|
||||
|
||||
## Can I run Clixon as docker containers?
|
||||
|
||||
Yes, the example works as docker containers as well. There should be a
|
||||
prepared container in docker hub for the example where the backend and
|
||||
CLI is bundled.
|
||||
```
|
||||
sudo docker run -td olofhagsand/clixon_example
|
||||
```
|
||||
Look in the example documentation for more info.
|
||||
|
||||
## Does Clixon support event streams?
|
||||
|
||||
|
|
@ -202,7 +269,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>]]>]]>
|
||||
...
|
||||
|
|
@ -211,11 +278,11 @@ 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?
|
||||
|
||||
There are four different backend startup modes. There is differences in running state treatment, ie what state the machine is when you startthe daemon and how loading the configuration affects it:
|
||||
There are four different backend startup modes. There is differences in running state treatment, ie what state the machine is when you start the daemon and how loading the configuration affects it:
|
||||
- none - Do not touch running state. Typically after crash when running state and db are synched.
|
||||
- init - Initialize running state. Start with a completely clean running state.
|
||||
- running - Commit running db configuration into running state. Typically after reboot if a persistent running db exists.
|
||||
|
|
@ -239,6 +306,7 @@ You may also add a default method in the configuration file:
|
|||
Yes. Systemd example files are provide for the backend and the
|
||||
restconf daemon as part of the [example](../example/systemd).
|
||||
|
||||
|
||||
## How can I add extra XML?
|
||||
|
||||
There are two ways to add extra XML to running database after start. Note that this XML is not "committed" into running.
|
||||
|
|
@ -246,7 +314,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:
|
||||
|
|
@ -258,12 +326,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.
|
||||
|
||||
|
|
@ -349,7 +418,7 @@ Please look at the example for an example on how to write a state data callback.
|
|||
|
||||
A YANG RPC is an application specific operation. Example:
|
||||
```
|
||||
rpc fib-route {
|
||||
rpc example-rpc {
|
||||
input {
|
||||
leaf inarg { type string; }
|
||||
}
|
||||
|
|
@ -358,7 +427,7 @@ A YANG RPC is an application specific operation. Example:
|
|||
}
|
||||
}
|
||||
```
|
||||
which defines the fib-route operation present in the example (the arguments have been changed).
|
||||
which defines the example-rpc operation present in the example (the arguments have been changed).
|
||||
|
||||
Clixon automatically relays the RPC to the clixon backend. To
|
||||
implement the RFC, you need to register an RPC callback in the backend plugin:
|
||||
|
|
@ -368,18 +437,18 @@ int
|
|||
clixon_plugin_init(clicon_handle h)
|
||||
{
|
||||
...
|
||||
rpc_callback_register(h, fib_route, NULL, "fib-route");
|
||||
rpc_callback_register(h, example_rpc, NULL, "example-rpc");
|
||||
...
|
||||
}
|
||||
```
|
||||
And then define the callback itself:
|
||||
```
|
||||
static int
|
||||
fib_route(clicon_handle h, /* Clicon handle */
|
||||
cxobj *xe, /* Request: <rpc><xn></rpc> */
|
||||
cbuf *cbret, /* Reply eg <rpc-reply>... */
|
||||
void *arg, /* Client session */
|
||||
void *regarg) /* Argument given at register */
|
||||
example_rpc(clicon_handle h, /* Clicon handle */
|
||||
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;
|
||||
|
|
@ -406,10 +475,18 @@ To authenticate, the callback needs to return the value 1 and supply a username.
|
|||
See [../apps/example/example_restconf.c] example_restconf_credentials() for
|
||||
an example of HTTP basic auth.
|
||||
|
||||
## What about access control?
|
||||
|
||||
Clixon has experimental support of the Network Configuration Access
|
||||
Control Model defined in [RFC8341](https://tools.ietf.org/html/rfc8341)
|
||||
|
||||
Incoming RPC and data node access points are supported with some
|
||||
limitations. See the (README)(../README.md) for more information.
|
||||
|
||||
## How do I write a CLI translator function?
|
||||
|
||||
The CLI can perform variable translation. This is useful if you want to
|
||||
prcess the input, such as hashing, encrypting or in other way
|
||||
process the input, such as hashing, encrypting or in other way
|
||||
translate the input.
|
||||
|
||||
Yang example:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
#
|
||||
|
|
@ -44,11 +44,13 @@ all: $(SUBDIRS) doc
|
|||
echo "Build doxygen doc: make doc"
|
||||
|
||||
# Regular doxygen documentation
|
||||
# Need to install doxygen
|
||||
doc:
|
||||
doxygen Doxyfile # generates html dir
|
||||
echo "Build doxygen graphs: make graphs"
|
||||
|
||||
# doxygen documentation with callgraphs
|
||||
# Need to install graphviz
|
||||
graphs:
|
||||
doxygen Doxyfile.graphs # generates html dir + call graphs (takes time)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
#
|
||||
|
|
|
|||
|
|
@ -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
|
||||
#
|
||||
|
|
@ -55,7 +55,7 @@ distclean: clean
|
|||
rm -f Makefile *~ .depend
|
||||
|
||||
docker:
|
||||
sudo docker build -t $(IMAGE) .
|
||||
sudo docker build -t $(IMAGE) . # --no-cache
|
||||
echo "cd ../example; make docker to build example application"
|
||||
|
||||
push:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
#
|
||||
|
|
|
|||
|
|
@ -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
|
||||
#
|
||||
|
|
|
|||
|
|
@ -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
|
||||
#
|
||||
|
|
|
|||
|
|
@ -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
|
||||
#
|
||||
|
|
@ -43,6 +43,8 @@ localstatedir = @localstatedir@
|
|||
libdir = @exec_prefix@/lib
|
||||
|
||||
APPNAME = example
|
||||
# Here is where example yang appears
|
||||
CLIXON_DATADIR = @CLIXON_DATADIR@
|
||||
# Install here if you want default clixon location:
|
||||
CLIXON_DEFAULT_CONFIG = @CLIXON_DEFAULT_CONFIG@
|
||||
|
||||
|
|
@ -70,16 +72,7 @@ all: $(PLUGINS)
|
|||
|
||||
CLISPECS = $(APPNAME)_cli.cli
|
||||
|
||||
YANGSPECS = $(APPNAME).yang
|
||||
YANGSPECS += ietf-yang-types@2013-07-15.yang
|
||||
YANGSPECS += ietf-inet-types@2013-07-15.yang
|
||||
YANGSPECS += ietf-interfaces@2014-05-08.yang
|
||||
YANGSPECS += ietf-ip@2014-06-16.yang
|
||||
YANGSPECS += ietf-routing@2014-10-26.yang
|
||||
YANGSPECS += ietf-ipv4-unicast-routing@2014-10-26.yang
|
||||
YANGSPECS += ietf-ipv6-unicast-routing@2014-10-26.yang
|
||||
YANGSPECS += ietf-ipsec@2016-03-09.yang
|
||||
YANGSPECS += iana-if-type@2014-05-08.yang
|
||||
YANGSPECS = clixon-example@2019-01-13.yang
|
||||
|
||||
# Backend plugin
|
||||
BE_SRC = $(APPNAME)_backend.c
|
||||
|
|
@ -125,7 +118,7 @@ install: $(YANGSPECS) $(CLISPECS) $(BE_PLUGIN) $(BE2_PLUGIN) $(CLI_PLUGIN) $(NET
|
|||
install -m 0644 $(APPNAME).xml $(DESTDIR)$(sysconfdir)
|
||||
# install -m 0644 $(APPNAME).xml $(DESTDIR)$(CLIXON_DEFAULT_CONFIG)
|
||||
install -d -m 0755 $(DESTDIR)$(datarootdir)/$(APPNAME)/yang
|
||||
install -m 0644 $(YANGSPECS) $(DESTDIR)$(datarootdir)/$(APPNAME)/yang
|
||||
install -m 0644 $(YANGSPECS) $(DESTDIR)$(DESTDIR)$(CLIXON_DATADIR)
|
||||
install -d -m 0755 $(DESTDIR)$(libdir)/$(APPNAME)/cli
|
||||
install -m 0644 $(INSTALLFLAGS) $(CLI_PLUGIN) $(DESTDIR)$(libdir)/$(APPNAME)/cli
|
||||
install -d -m 0755 $(DESTDIR)$(libdir)/$(APPNAME)/backend
|
||||
|
|
|
|||
|
|
@ -1,20 +1,33 @@
|
|||
# Clixon example
|
||||
|
||||
This directory contains a Clixon example which includes a simple
|
||||
routing 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:
|
||||
* [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)[../yang/clixon-config@2018-10-21.yang] for the documentation of all available fields.
|
||||
* `clixon-example@2019-01-13.yang` The yang spec of the example.
|
||||
* `example_cli.cli` CLIgen specification.
|
||||
* `example_cli.c` CLI callback plugin containing functions called in the cli file above: a generic callback (`mycallback`) and an example RPC call (`example_client_rpc`).
|
||||
* `example_backend.c` Backend callback plugin including example of:
|
||||
* transaction callbacks (validate/commit),
|
||||
* notification,
|
||||
* rpc handler
|
||||
* state-data handler, ie non-config data
|
||||
* 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
|
||||
|
|
@ -48,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>
|
||||
|
|
@ -66,38 +106,181 @@ 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>]]>]]>
|
||||
```
|
||||
## Restconf
|
||||
|
||||
Setup a web/reverse-proxy server.
|
||||
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
|
||||
```
|
||||
Start the clixon restconf daemon
|
||||
```
|
||||
sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data
|
||||
```
|
||||
then access using curl or wget:
|
||||
```
|
||||
curl -G http://127.0.0.1/restconf/data/ietf-interfaces:interfaces/interface=eth9/type
|
||||
```
|
||||
|
||||
## Streams
|
||||
|
||||
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:
|
||||
```
|
||||
cli> notify
|
||||
cli> Routing notification
|
||||
Routing notification
|
||||
clixon_cli -f /usr/local/etc/example.xml
|
||||
cli> notify
|
||||
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:
|
||||
```
|
||||
clixon_cli -f /usr/local/etc/example.xml
|
||||
cli> rpc ipv4
|
||||
<rpc-reply><x xmlns="urn:example:clixon">ipv4</x><y xmlns="urn:example:clixon">42</y></rpc-reply>
|
||||
```
|
||||
Example using Netconf:
|
||||
```
|
||||
clixon_netconf -qf /usr/local/etc/example.xml
|
||||
<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><example xmlns="urn:example:clixon"><x>ipv4</x></example></rpc>]]>]]>
|
||||
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><x xmlns="urn:example:clixon">ipv4</x><y xmlns="urn:example:clixon">42</y></rpc-reply>]]>]]>
|
||||
```
|
||||
Restconf (assuming nginx started):
|
||||
```
|
||||
sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data&
|
||||
curl -X POST http://localhost/restconf/operations/clixon-example:example -d '{"clixon-example:input":{"x":"ipv4"}}'
|
||||
{
|
||||
"clixon-example:output": {
|
||||
"x": "ipv4",
|
||||
"y": "42"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Details
|
||||
|
||||
The example works by defining an RPC in clixon-example.yang:
|
||||
```
|
||||
rpc example {
|
||||
description "Some example input/output for testing RFC7950 7.14.
|
||||
RPC simply echoes the input for debugging.";
|
||||
input {
|
||||
leaf x {
|
||||
...
|
||||
```
|
||||
|
||||
In the CLI a netconf rpc call is constructed and sent to the backend: See `example_client_rpc()` in [example_cli.c] CLI plugin.
|
||||
|
||||
The clixon backend plugin [example_backend.c] reveives the netconf call and replies. This is made byregistering a callback handling handling the RPC:
|
||||
```
|
||||
static int
|
||||
example_rpc(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 */
|
||||
{
|
||||
/* code that echoes the request */
|
||||
return 0;
|
||||
}
|
||||
int
|
||||
clixon_plugin_init(clicon_handle h)
|
||||
{
|
||||
...
|
||||
rpc_callback_register(h, example_rpc, NULL, "example");
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
|
@ -123,102 +306,8 @@ clixon_plugin_api *
|
|||
clixon_plugin_init(clicon_handle h)
|
||||
{
|
||||
/* Optional callback registration for RPC calls */
|
||||
rpc_callback_register(h, fib_route, NULL, "fib-route");
|
||||
rpc_callback_register(h, example_rpc, NULL, "example");
|
||||
/* Return plugin API */
|
||||
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: adm1, wilma, and guest, according to the examples in Appendix A in the RFC.
|
||||
* 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`
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
142
example/clixon-example@2019-01-13.yang
Normal file
142
example/clixon-example@2019-01-13.yang
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
module clixon-example {
|
||||
yang-version 1.1;
|
||||
namespace "urn:example:clixon";
|
||||
prefix ex;
|
||||
revision 2019-01-13 {
|
||||
description
|
||||
"Released in Clixon 3.9";
|
||||
}
|
||||
import ietf-interfaces {
|
||||
prefix if;
|
||||
}
|
||||
import ietf-ip {
|
||||
prefix ip;
|
||||
}
|
||||
import iana-if-type {
|
||||
prefix ianaift;
|
||||
}
|
||||
/* Example interface type for tests, local callbacks, etc */
|
||||
identity eth {
|
||||
base if:interface-type;
|
||||
}
|
||||
identity loopback {
|
||||
base if:interface-type;
|
||||
}
|
||||
/* Translation function example - See also example_cli */
|
||||
list translate{
|
||||
key k;
|
||||
leaf k{
|
||||
type string;
|
||||
}
|
||||
leaf value{
|
||||
type string;
|
||||
}
|
||||
}
|
||||
/* State data (not config) for the example application*/
|
||||
container state {
|
||||
config false;
|
||||
description "state data for the example application (must be here for example get operation)";
|
||||
leaf-list op {
|
||||
type string;
|
||||
}
|
||||
}
|
||||
/* Example notification as used in RFC 5277 and RFC 8040 */
|
||||
notification event {
|
||||
description "Example notification event.";
|
||||
leaf event-class {
|
||||
type string;
|
||||
description "Event class identifier.";
|
||||
}
|
||||
container reportingEntity {
|
||||
description "Event specific information.";
|
||||
leaf card {
|
||||
type string;
|
||||
description "Line card identifier.";
|
||||
}
|
||||
}
|
||||
leaf severity {
|
||||
type string;
|
||||
description "Event severity description.";
|
||||
}
|
||||
}
|
||||
rpc client-rpc {
|
||||
description "Example local client-side RPC that is processed by the
|
||||
the netconf/restconf and not sent to the backend.
|
||||
This is a clixon implementation detail: some rpc:s
|
||||
are better processed by the client for API or perf reasons";
|
||||
input {
|
||||
leaf x {
|
||||
type string;
|
||||
}
|
||||
}
|
||||
output {
|
||||
leaf x {
|
||||
type string;
|
||||
}
|
||||
}
|
||||
}
|
||||
rpc empty {
|
||||
description "Smallest possible RPC with no input or output sections";
|
||||
}
|
||||
rpc optional {
|
||||
description "Small RPC with optional input and output";
|
||||
input {
|
||||
leaf x {
|
||||
type string;
|
||||
}
|
||||
}
|
||||
output {
|
||||
leaf x {
|
||||
type string;
|
||||
}
|
||||
}
|
||||
}
|
||||
rpc example {
|
||||
description "Some example input/output for testing RFC7950 7.14.
|
||||
RPC simply echoes the input for debugging.";
|
||||
input {
|
||||
leaf x {
|
||||
description
|
||||
"If a leaf in the input tree has a 'mandatory' statement with
|
||||
the value 'true', the leaf MUST be present in an RPC invocation.";
|
||||
type string;
|
||||
mandatory true;
|
||||
}
|
||||
leaf y {
|
||||
description
|
||||
"If a leaf in the input tree has a 'mandatory' statement with the
|
||||
value 'true', the leaf MUST be present in an RPC invocation.";
|
||||
type string;
|
||||
default "42";
|
||||
}
|
||||
leaf-list z {
|
||||
description
|
||||
"If a leaf-list in the input tree has one or more default
|
||||
values, the server MUST use these values (XXX not supported)";
|
||||
type string;
|
||||
}
|
||||
leaf w {
|
||||
description
|
||||
"If any node has a 'when' statement that would evaluate to
|
||||
'false',then this node MUST NOT be present in the input tree.
|
||||
(XXX not supported)";
|
||||
type string;
|
||||
when "/translate/k=5/value='w'";
|
||||
}
|
||||
}
|
||||
output {
|
||||
leaf x {
|
||||
type string;
|
||||
}
|
||||
leaf y {
|
||||
type string;
|
||||
}
|
||||
leaf z {
|
||||
type string;
|
||||
}
|
||||
leaf w {
|
||||
type string;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
<config>
|
||||
<CLICON_CONFIGFILE>/usr/local/etc/example.xml</CLICON_CONFIGFILE>
|
||||
<CLICON_FEATURE>*:*</CLICON_FEATURE>
|
||||
<CLICON_YANG_DIR>/usr/local/share/example/yang</CLICON_YANG_DIR>
|
||||
<CLICON_YANG_MODULE_MAIN>example</CLICON_YANG_MODULE_MAIN>
|
||||
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
|
||||
<CLICON_YANG_MODULE_MAIN>clixon-example</CLICON_YANG_MODULE_MAIN>
|
||||
<CLICON_CLI_MODE>example</CLICON_CLI_MODE>
|
||||
<CLICON_BACKEND_DIR>/usr/local/lib/example/backend</CLICON_BACKEND_DIR>
|
||||
<CLICON_NETCONF_DIR>/usr/local/lib/example/netconf</CLICON_NETCONF_DIR>
|
||||
|
|
|
|||
|
|
@ -1,73 +0,0 @@
|
|||
module example {
|
||||
prefix ex;
|
||||
import ietf-interfaces {
|
||||
prefix if;
|
||||
}
|
||||
import ietf-ip {
|
||||
prefix ip;
|
||||
}
|
||||
import ietf-routing {
|
||||
prefix rt;
|
||||
}
|
||||
import iana-if-type {
|
||||
prefix ianaift;
|
||||
}
|
||||
description
|
||||
"Example code that includes ietf-ip and ietf-routing";
|
||||
/* Example interface type for tests, local callbacks, etc */
|
||||
identity eth {
|
||||
base if:interface-type;
|
||||
}
|
||||
identity loopback {
|
||||
base if:interface-type;
|
||||
}
|
||||
/* Translation function example - See also example_cli */
|
||||
list translate{
|
||||
leaf value{
|
||||
type string;
|
||||
}
|
||||
}
|
||||
/* State data (not config) for the example application*/
|
||||
container state {
|
||||
config false;
|
||||
description "state data for the example application (must be here for example get operation)";
|
||||
leaf-list op {
|
||||
type string;
|
||||
}
|
||||
}
|
||||
/* Example notification as used in RFC 5277 and RFC 8040 */
|
||||
notification event {
|
||||
description "Example notification event.";
|
||||
leaf event-class {
|
||||
type string;
|
||||
description "Event class identifier.";
|
||||
}
|
||||
container reportingEntity {
|
||||
description "Event specific information.";
|
||||
leaf card {
|
||||
type string;
|
||||
description "Line card identifier.";
|
||||
}
|
||||
}
|
||||
leaf severity {
|
||||
type string;
|
||||
description "Event severity description.";
|
||||
}
|
||||
}
|
||||
rpc client-rpc {
|
||||
description "Example local client-side RPC that is processed by the
|
||||
the netconf/restconf and not sent to the backend.
|
||||
This is a clixon implementation detail: some rpc:s
|
||||
are better processed by the client for API or perf reasons";
|
||||
input {
|
||||
leaf request {
|
||||
type string;
|
||||
}
|
||||
}
|
||||
output {
|
||||
leaf result{
|
||||
type string;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
@ -119,37 +119,6 @@ example_stream_timer_setup(clicon_handle h)
|
|||
return event_reg_timeout(t, example_stream_timer, h, "example stream timer");
|
||||
}
|
||||
|
||||
/*! IETF Routing fib-route rpc
|
||||
* @see ietf-routing@2014-10-26.yang (fib-route)
|
||||
*/
|
||||
static int
|
||||
fib_route(clicon_handle h, /* Clicon handle */
|
||||
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>"
|
||||
"<address-family>ipv4</address-family>"
|
||||
"<next-hop><next-hop-list>2.3.4.5</next-hop-list></next-hop>"
|
||||
"</route></rpc-reply>");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! IETF Routing route-count rpc
|
||||
* @see ietf-routing@2014-10-26.yang (route-count)
|
||||
*/
|
||||
static int
|
||||
route_count(clicon_handle h,
|
||||
cxobj *xe, /* Request: <rpc><xn></rpc> */
|
||||
cbuf *cbret, /* Reply eg <rpc-reply>... */
|
||||
void *arg,
|
||||
void *regarg) /* Argument given at register */
|
||||
{
|
||||
cprintf(cbret, "<rpc-reply><number-of-routes>42</number-of-routes></rpc-reply>");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Smallest possible RPC declaration for test
|
||||
* Yang/XML:
|
||||
* If the RPC operation invocation succeeded and no output parameters
|
||||
|
|
@ -157,16 +126,50 @@ route_count(clicon_handle h,
|
|||
* in [RFC6241].
|
||||
*/
|
||||
static int
|
||||
empty(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 */
|
||||
empty_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 */
|
||||
{
|
||||
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
|
||||
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>");
|
||||
if (!xml_child_nr_type(xe, CX_ELMNT))
|
||||
cprintf(cbret, "<ok/>");
|
||||
else 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,8 +199,10 @@ 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>"
|
||||
"<op>41</op>"
|
||||
"<op>43</op>" /* should not be ordered */
|
||||
"</state>", NULL, &xstate) < 0)
|
||||
goto done;
|
||||
retval = 0;
|
||||
|
|
@ -225,19 +230,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, clicon_username_get(h), 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,26 +333,28 @@ clixon_plugin_init(clicon_handle h)
|
|||
if (example_stream_timer_setup(h) < 0)
|
||||
goto done;
|
||||
|
||||
/* 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;
|
||||
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,
|
||||
/* Register callback for routing rpc calls
|
||||
*/
|
||||
/* From example.yang (clicon) */
|
||||
if (rpc_callback_register(h, empty_rpc,
|
||||
NULL,
|
||||
"empty"/* Xml tag when callback is made */
|
||||
) < 0)
|
||||
goto done;
|
||||
/* Same as example but with optional input/output */
|
||||
if (rpc_callback_register(h, example_rpc,
|
||||
NULL,
|
||||
"optional"/* 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;
|
||||
done:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,7 +75,7 @@ nacm_statedata(clicon_handle h,
|
|||
cxobj **xvec = NULL;
|
||||
|
||||
/* Example of (static) statedata, real code would poll state */
|
||||
if (xml_parse_string("<nacm>"
|
||||
if (xml_parse_string("<nacm xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-acm\">"
|
||||
"<denied-data-writes>0</denied-data-writes>"
|
||||
"<denied-operations>0</denied-operations>"
|
||||
"<denied-notifications>0</denied-notifications>"
|
||||
|
|
|
|||
|
|
@ -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,6 @@ mycallback(clicon_handle h, cvec *cvv, cvec *argv)
|
|||
"/interfaces/interface[name='eth0']",
|
||||
&xret) < 0)
|
||||
goto done;
|
||||
|
||||
xml_print(stdout, xret);
|
||||
retval = 0;
|
||||
done:
|
||||
|
|
@ -80,25 +79,25 @@ mycallback(clicon_handle h, cvec *cvv, cvec *argv)
|
|||
return retval;
|
||||
}
|
||||
|
||||
/*! Example "downcall": ietf-routing fib-route RPC */
|
||||
/*! Example "downcall", ie initiate an RPC to the backend */
|
||||
int
|
||||
fib_route_rpc(clicon_handle h,
|
||||
cvec *cvv,
|
||||
cvec *argv)
|
||||
example_client_rpc(clicon_handle h,
|
||||
cvec *cvv,
|
||||
cvec *argv)
|
||||
{
|
||||
int retval = -1;
|
||||
cg_var *instance;
|
||||
cg_var *cva;
|
||||
cxobj *xtop = NULL;
|
||||
cxobj *xrpc;
|
||||
cxobj *xret = NULL;
|
||||
cxobj *xerr;
|
||||
|
||||
/* 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 username=\"%s\"><fib-route><routing-instance-name>%s</routing-instance-name></fib-route></rpc>",
|
||||
cva = cvec_find(cvv, "a"); /* get a cligen variable from vector */
|
||||
/* Create XML for example netconf RPC */
|
||||
if (xml_parse_va(&xtop, NULL, "<rpc message-id=\"101\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" username=\"%s\"><example xmlns=\"urn:example:clixon\"><x>%s</x></example></rpc>",
|
||||
clicon_username_get(h),
|
||||
cv_string_get(instance)) < 0)
|
||||
cv_string_get(cva)) < 0)
|
||||
goto done;
|
||||
/* Skip top-level */
|
||||
xrpc = xml_child_i(xtop, 0);
|
||||
|
|
@ -110,7 +109,12 @@ fib_route_rpc(clicon_handle h,
|
|||
goto done;
|
||||
}
|
||||
/* Print result */
|
||||
xml_print(stdout, xml_child_i(xret, 0));
|
||||
clicon_xml2file(stdout, xml_child_i(xret, 0), 0, 0);
|
||||
fprintf(stdout,"\n");
|
||||
|
||||
/* pretty-print:
|
||||
xml2txt(stdout, xml_child_i(xret, 0), 0);
|
||||
*/
|
||||
retval = 0;
|
||||
done:
|
||||
if (xret)
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ CLICON_PLUGIN="example_cli";
|
|||
translate value (<value:string translate:incstr()>),cli_set("/translate/value");
|
||||
|
||||
# Note, when switching to PT, change datamodel to only @datamodel
|
||||
set @datamodel:example, cli_set();
|
||||
merge @datamodel:example, cli_merge();
|
||||
create @datamodel:example, cli_create();
|
||||
delete("Delete a configuration item") @datamodel:example, cli_del();
|
||||
set @datamodel, cli_set();
|
||||
merge @datamodel, cli_merge();
|
||||
create @datamodel, cli_create();
|
||||
delete("Delete a configuration item") @datamodel, cli_del();
|
||||
|
||||
validate("Validate changes"), cli_validate();
|
||||
commit("Commit the changes"), cli_commit();
|
||||
|
|
@ -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();
|
||||
|
|
@ -40,19 +40,19 @@ show("Show a particular state of the system"){
|
|||
}
|
||||
configuration("Show configuration"), cli_show_config("candidate", "text", "/");{
|
||||
xml("Show configuration as XML"), cli_show_config("candidate", "xml", "/");{
|
||||
@datamodel:example, cli_show_auto("candidate", "text");
|
||||
@datamodel, cli_show_auto("candidate", "text");
|
||||
}
|
||||
cli("Show configuration as CLI commands"), cli_show_config("candidate", "cli", "/");{
|
||||
@datamodel:example, cli_show_auto("candidate", "cli");
|
||||
@datamodel, cli_show_auto("candidate", "cli");
|
||||
}
|
||||
netconf("Show configuration as netconf edit-config operation"), cli_show_config("candidate", "netconf", "/");{
|
||||
@datamodel:example, cli_show_auto("candidate", "netconf");
|
||||
@datamodel, cli_show_auto("candidate", "netconf");
|
||||
}
|
||||
text("Show configuration as text"), cli_show_config("candidate","text","/");{
|
||||
@datamodel:example, cli_show_auto("candidate", "text");
|
||||
@datamodel, cli_show_auto("candidate", "text");
|
||||
}
|
||||
json("Show configuration as JSON"), cli_show_config("candidate", "json", "/");{
|
||||
@datamodel:example, cli_show_auto("candidate", "json");
|
||||
@datamodel, cli_show_auto("candidate", "json");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -63,7 +63,7 @@ load("Load configuration from XML file") <filename:string>("Filename (local file
|
|||
merge("Merge file with existent candidate"), load_config_file("filename", "merge");
|
||||
}
|
||||
example("This is a comment") <var:int32>("Just a random number"), mycallback("myarg");
|
||||
rpc("ex:fib-route rpc") <instance:string>("routing instance"), fib_route_rpc("myarg");
|
||||
rpc("example rpc") <a:string>("routing instance"), example_client_rpc("");
|
||||
notify("Get notifications from backend"), cli_notify("EXAMPLE", "1", "text");
|
||||
no("Negate") notify("Get notifications from backend"), cli_notify("EXAMPLE", "0", "xml");
|
||||
lock,cli_lock("candidate");
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
@ -65,13 +65,34 @@ plugin_exit(clicon_handle h)
|
|||
/*! Local example netconf rpc callback
|
||||
*/
|
||||
int netconf_client_rpc(clicon_handle h,
|
||||
cxobj *xn,
|
||||
cxobj *xe,
|
||||
cbuf *cbret,
|
||||
void *arg,
|
||||
void *regarg)
|
||||
{
|
||||
clicon_debug(1, "%s restconf", __FUNCTION__);
|
||||
cprintf(cbret, "<rpc-reply><result>ok</result></rpc-reply>");
|
||||
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>");
|
||||
if (!xml_child_nr_type(xe, CX_ELMNT))
|
||||
cprintf(cbret, "<ok/>");
|
||||
else 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;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
@ -191,7 +191,7 @@ b64_decode(const char *src,
|
|||
* @retval -1 Fatal error
|
||||
* @retval 0 Unauth
|
||||
* @retval 1 Auth
|
||||
* @note: Three hardwired users: adm1, wilma, guest w password "bar".
|
||||
* @note: Three hardwired users: andy, wilma, guest w password "bar".
|
||||
* Enabled by passing -- -a to the main function
|
||||
*/
|
||||
int
|
||||
|
|
@ -237,9 +237,9 @@ example_restconf_credentials(clicon_handle h,
|
|||
/* Here get auth sub-tree whjere all the users are */
|
||||
if ((cb = cbuf_new()) == NULL)
|
||||
goto done;
|
||||
/* Hardcoded user/passwd */
|
||||
if (strcmp(user, "wilma")==0 || strcmp(user, "adm1")==0 ||
|
||||
strcmp(user, "quest")==0){
|
||||
/* XXX Three hardcoded user/passwd (from RFC8341 A.1)*/
|
||||
if (strcmp(user, "wilma")==0 || strcmp(user, "andy")==0 ||
|
||||
strcmp(user, "guest")==0){
|
||||
passwd2 = "bar";
|
||||
}
|
||||
if (strcmp(passwd, passwd2))
|
||||
|
|
@ -268,28 +268,46 @@ example_restconf_credentials(clicon_handle h,
|
|||
*/
|
||||
int
|
||||
restconf_client_rpc(clicon_handle h,
|
||||
cxobj *xn,
|
||||
cxobj *xe,
|
||||
cbuf *cbret,
|
||||
void *arg,
|
||||
void *regarg)
|
||||
{
|
||||
// FCGX_Request *r = (FCGX_Request *)arg;
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
cprintf(cbret, "<rpc-reply><result>ok</result></rpc-reply>");
|
||||
return 0;
|
||||
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>");
|
||||
if (!xml_child_nr_type(xe, CX_ELMNT))
|
||||
cprintf(cbret, "<ok/>");
|
||||
else 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;
|
||||
}
|
||||
|
||||
/*! Start example restonf plugin. Set authentication method
|
||||
* Arguments are argc/argv after --
|
||||
* Currently defined: -a enable http basic authentication
|
||||
* Note hardwired users adm1, wilma and guest
|
||||
* @note There are three hardwired users andy, wilma and guest from RFC8341 A.1
|
||||
*/
|
||||
int
|
||||
example_restconf_start(clicon_handle h,
|
||||
int argc,
|
||||
char **argv)
|
||||
{
|
||||
char c;
|
||||
int c;
|
||||
|
||||
clicon_debug(1, "%s argc:%d", __FUNCTION__, argc);
|
||||
optind = 1;
|
||||
|
|
|
|||
|
|
@ -1,698 +0,0 @@
|
|||
module ietf-interfaces {
|
||||
|
||||
namespace "urn:ietf:params:xml:ns:yang:ietf-interfaces";
|
||||
prefix if;
|
||||
|
||||
import ietf-yang-types {
|
||||
prefix yang;
|
||||
}
|
||||
|
||||
organization
|
||||
"IETF NETMOD (NETCONF Data Modeling Language) Working Group";
|
||||
|
||||
contact
|
||||
"WG Web: <http://tools.ietf.org/wg/netmod/>
|
||||
WG List: <mailto:netmod@ietf.org>
|
||||
|
||||
WG Chair: Thomas Nadeau
|
||||
<mailto:tnadeau@lucidvision.com>
|
||||
|
||||
WG Chair: Juergen Schoenwaelder
|
||||
<mailto:j.schoenwaelder@jacobs-university.de>
|
||||
|
||||
Editor: Martin Bjorklund
|
||||
<mailto:mbj@tail-f.com>";
|
||||
|
||||
description
|
||||
"This module contains a collection of YANG definitions for
|
||||
managing network interfaces.
|
||||
|
||||
Copyright (c) 2014 IETF Trust and the persons identified as
|
||||
authors of the code. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or
|
||||
without modification, is permitted pursuant to, and subject
|
||||
to the license terms contained in, the Simplified BSD License
|
||||
set forth in Section 4.c of the IETF Trust's Legal Provisions
|
||||
Relating to IETF Documents
|
||||
(http://trustee.ietf.org/license-info).
|
||||
|
||||
This version of this YANG module is part of RFC 7223; see
|
||||
the RFC itself for full legal notices.";
|
||||
|
||||
revision 2014-05-08 {
|
||||
description
|
||||
"Initial revision.";
|
||||
reference
|
||||
"RFC 7223: A YANG Data Model for Interface Management";
|
||||
}
|
||||
|
||||
/*
|
||||
* Typedefs
|
||||
*/
|
||||
|
||||
typedef interface-ref {
|
||||
type leafref {
|
||||
path "/if:interfaces/if:interface/if:name";
|
||||
}
|
||||
description
|
||||
"This type is used by data models that need to reference
|
||||
configured interfaces.";
|
||||
}
|
||||
|
||||
typedef interface-state-ref {
|
||||
type leafref {
|
||||
path "/if:interfaces-state/if:interface/if:name";
|
||||
}
|
||||
description
|
||||
"This type is used by data models that need to reference
|
||||
the operationally present interfaces.";
|
||||
}
|
||||
|
||||
/*
|
||||
* Identities
|
||||
*/
|
||||
|
||||
identity interface-type {
|
||||
description
|
||||
"Base identity from which specific interface types are
|
||||
derived.";
|
||||
}
|
||||
|
||||
/*
|
||||
* Features
|
||||
*/
|
||||
|
||||
feature arbitrary-names {
|
||||
description
|
||||
"This feature indicates that the device allows user-controlled
|
||||
interfaces to be named arbitrarily.";
|
||||
}
|
||||
|
||||
feature pre-provisioning {
|
||||
description
|
||||
"This feature indicates that the device supports
|
||||
pre-provisioning of interface configuration, i.e., it is
|
||||
possible to configure an interface whose physical interface
|
||||
hardware is not present on the device.";
|
||||
}
|
||||
|
||||
feature if-mib {
|
||||
description
|
||||
"This feature indicates that the device implements
|
||||
the IF-MIB.";
|
||||
reference
|
||||
"RFC 2863: The Interfaces Group MIB";
|
||||
}
|
||||
|
||||
/*
|
||||
* Configuration data nodes
|
||||
*/
|
||||
|
||||
container interfaces {
|
||||
description
|
||||
"Interface configuration parameters.";
|
||||
|
||||
list interface {
|
||||
key "name";
|
||||
|
||||
description
|
||||
"The list of configured interfaces on the device.
|
||||
|
||||
The operational state of an interface is available in the
|
||||
/interfaces-state/interface list. If the configuration of a
|
||||
system-controlled interface cannot be used by the system
|
||||
(e.g., the interface hardware present does not match the
|
||||
interface type), then the configuration is not applied to
|
||||
the system-controlled interface shown in the
|
||||
/interfaces-state/interface list. If the configuration
|
||||
of a user-controlled interface cannot be used by the system,
|
||||
the configured interface is not instantiated in the
|
||||
/interfaces-state/interface list.";
|
||||
|
||||
leaf name {
|
||||
type string;
|
||||
description
|
||||
"The name of the interface.
|
||||
|
||||
A device MAY restrict the allowed values for this leaf,
|
||||
possibly depending on the type of the interface.
|
||||
|
||||
For system-controlled interfaces, this leaf is the
|
||||
device-specific name of the interface. The 'config false'
|
||||
list /interfaces-state/interface contains the currently
|
||||
existing interfaces on the device.
|
||||
|
||||
If a client tries to create configuration for a
|
||||
system-controlled interface that is not present in the
|
||||
/interfaces-state/interface list, the server MAY reject
|
||||
the request if the implementation does not support
|
||||
pre-provisioning of interfaces or if the name refers to
|
||||
an interface that can never exist in the system. A
|
||||
NETCONF server MUST reply with an rpc-error with the
|
||||
error-tag 'invalid-value' in this case.
|
||||
|
||||
If the device supports pre-provisioning of interface
|
||||
configuration, the 'pre-provisioning' feature is
|
||||
advertised.
|
||||
|
||||
If the device allows arbitrarily named user-controlled
|
||||
interfaces, the 'arbitrary-names' feature is advertised.
|
||||
|
||||
When a configured user-controlled interface is created by
|
||||
the system, it is instantiated with the same name in the
|
||||
/interface-state/interface list.";
|
||||
}
|
||||
|
||||
leaf description {
|
||||
type string;
|
||||
description
|
||||
"A textual description of the interface.
|
||||
|
||||
A server implementation MAY map this leaf to the ifAlias
|
||||
MIB object. Such an implementation needs to use some
|
||||
mechanism to handle the differences in size and characters
|
||||
allowed between this leaf and ifAlias. The definition of
|
||||
such a mechanism is outside the scope of this document.
|
||||
|
||||
Since ifAlias is defined to be stored in non-volatile
|
||||
storage, the MIB implementation MUST map ifAlias to the
|
||||
value of 'description' in the persistently stored
|
||||
datastore.
|
||||
|
||||
Specifically, if the device supports ':startup', when
|
||||
ifAlias is read the device MUST return the value of
|
||||
'description' in the 'startup' datastore, and when it is
|
||||
written, it MUST be written to the 'running' and 'startup'
|
||||
datastores. Note that it is up to the implementation to
|
||||
|
||||
decide whether to modify this single leaf in 'startup' or
|
||||
perform an implicit copy-config from 'running' to
|
||||
'startup'.
|
||||
|
||||
If the device does not support ':startup', ifAlias MUST
|
||||
be mapped to the 'description' leaf in the 'running'
|
||||
datastore.";
|
||||
reference
|
||||
"RFC 2863: The Interfaces Group MIB - ifAlias";
|
||||
}
|
||||
|
||||
leaf type {
|
||||
type identityref {
|
||||
base interface-type;
|
||||
}
|
||||
mandatory true;
|
||||
description
|
||||
"The type of the interface.
|
||||
|
||||
When an interface entry is created, a server MAY
|
||||
initialize the type leaf with a valid value, e.g., if it
|
||||
is possible to derive the type from the name of the
|
||||
interface.
|
||||
|
||||
If a client tries to set the type of an interface to a
|
||||
value that can never be used by the system, e.g., if the
|
||||
type is not supported or if the type does not match the
|
||||
name of the interface, the server MUST reject the request.
|
||||
A NETCONF server MUST reply with an rpc-error with the
|
||||
error-tag 'invalid-value' in this case.";
|
||||
reference
|
||||
"RFC 2863: The Interfaces Group MIB - ifType";
|
||||
}
|
||||
|
||||
leaf enabled {
|
||||
type boolean;
|
||||
default "true";
|
||||
description
|
||||
"This leaf contains the configured, desired state of the
|
||||
interface.
|
||||
|
||||
Systems that implement the IF-MIB use the value of this
|
||||
leaf in the 'running' datastore to set
|
||||
IF-MIB.ifAdminStatus to 'up' or 'down' after an ifEntry
|
||||
has been initialized, as described in RFC 2863.
|
||||
|
||||
Changes in this leaf in the 'running' datastore are
|
||||
reflected in ifAdminStatus, but if ifAdminStatus is
|
||||
changed over SNMP, this leaf is not affected.";
|
||||
reference
|
||||
"RFC 2863: The Interfaces Group MIB - ifAdminStatus";
|
||||
}
|
||||
|
||||
leaf link-up-down-trap-enable {
|
||||
if-feature if-mib;
|
||||
type enumeration {
|
||||
enum enabled {
|
||||
value 1;
|
||||
}
|
||||
enum disabled {
|
||||
value 2;
|
||||
}
|
||||
}
|
||||
description
|
||||
"Controls whether linkUp/linkDown SNMP notifications
|
||||
should be generated for this interface.
|
||||
|
||||
If this node is not configured, the value 'enabled' is
|
||||
operationally used by the server for interfaces that do
|
||||
not operate on top of any other interface (i.e., there are
|
||||
no 'lower-layer-if' entries), and 'disabled' otherwise.";
|
||||
reference
|
||||
"RFC 2863: The Interfaces Group MIB -
|
||||
ifLinkUpDownTrapEnable";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Operational state data nodes
|
||||
*/
|
||||
|
||||
container interfaces-state {
|
||||
config false;
|
||||
description
|
||||
"Data nodes for the operational state of interfaces.";
|
||||
|
||||
list interface {
|
||||
key "name";
|
||||
|
||||
description
|
||||
"The list of interfaces on the device.
|
||||
|
||||
System-controlled interfaces created by the system are
|
||||
always present in this list, whether they are configured or
|
||||
not.";
|
||||
|
||||
leaf name {
|
||||
type string;
|
||||
description
|
||||
"The name of the interface.
|
||||
|
||||
A server implementation MAY map this leaf to the ifName
|
||||
MIB object. Such an implementation needs to use some
|
||||
mechanism to handle the differences in size and characters
|
||||
allowed between this leaf and ifName. The definition of
|
||||
such a mechanism is outside the scope of this document.";
|
||||
reference
|
||||
"RFC 2863: The Interfaces Group MIB - ifName";
|
||||
}
|
||||
|
||||
leaf type {
|
||||
type identityref {
|
||||
base interface-type;
|
||||
}
|
||||
mandatory true;
|
||||
description
|
||||
"The type of the interface.";
|
||||
reference
|
||||
"RFC 2863: The Interfaces Group MIB - ifType";
|
||||
}
|
||||
|
||||
leaf admin-status {
|
||||
if-feature if-mib;
|
||||
type enumeration {
|
||||
enum up {
|
||||
value 1;
|
||||
description
|
||||
"Ready to pass packets.";
|
||||
}
|
||||
enum down {
|
||||
value 2;
|
||||
description
|
||||
"Not ready to pass packets and not in some test mode.";
|
||||
}
|
||||
|
||||
enum testing {
|
||||
value 3;
|
||||
description
|
||||
"In some test mode.";
|
||||
}
|
||||
}
|
||||
mandatory true;
|
||||
description
|
||||
"The desired state of the interface.
|
||||
|
||||
This leaf has the same read semantics as ifAdminStatus.";
|
||||
reference
|
||||
"RFC 2863: The Interfaces Group MIB - ifAdminStatus";
|
||||
}
|
||||
|
||||
leaf oper-status {
|
||||
type enumeration {
|
||||
enum up {
|
||||
value 1;
|
||||
description
|
||||
"Ready to pass packets.";
|
||||
}
|
||||
enum down {
|
||||
value 2;
|
||||
description
|
||||
"The interface does not pass any packets.";
|
||||
}
|
||||
enum testing {
|
||||
value 3;
|
||||
description
|
||||
"In some test mode. No operational packets can
|
||||
be passed.";
|
||||
}
|
||||
enum unknown {
|
||||
value 4;
|
||||
description
|
||||
"Status cannot be determined for some reason.";
|
||||
}
|
||||
enum dormant {
|
||||
value 5;
|
||||
description
|
||||
"Waiting for some external event.";
|
||||
}
|
||||
enum not-present {
|
||||
value 6;
|
||||
description
|
||||
"Some component (typically hardware) is missing.";
|
||||
}
|
||||
|
||||
enum lower-layer-down {
|
||||
value 7;
|
||||
description
|
||||
"Down due to state of lower-layer interface(s).";
|
||||
}
|
||||
}
|
||||
mandatory true;
|
||||
description
|
||||
"The current operational state of the interface.
|
||||
|
||||
This leaf has the same semantics as ifOperStatus.";
|
||||
reference
|
||||
"RFC 2863: The Interfaces Group MIB - ifOperStatus";
|
||||
}
|
||||
|
||||
leaf last-change {
|
||||
type yang:date-and-time;
|
||||
description
|
||||
"The time the interface entered its current operational
|
||||
state. If the current state was entered prior to the
|
||||
last re-initialization of the local network management
|
||||
subsystem, then this node is not present.";
|
||||
reference
|
||||
"RFC 2863: The Interfaces Group MIB - ifLastChange";
|
||||
}
|
||||
|
||||
leaf if-index {
|
||||
if-feature if-mib;
|
||||
type int32 {
|
||||
range "1..2147483647";
|
||||
}
|
||||
mandatory true;
|
||||
description
|
||||
"The ifIndex value for the ifEntry represented by this
|
||||
interface.";
|
||||
reference
|
||||
"RFC 2863: The Interfaces Group MIB - ifIndex";
|
||||
}
|
||||
|
||||
leaf phys-address {
|
||||
type yang:phys-address;
|
||||
description
|
||||
"The interface's address at its protocol sub-layer. For
|
||||
example, for an 802.x interface, this object normally
|
||||
contains a Media Access Control (MAC) address. The
|
||||
interface's media-specific modules must define the bit
|
||||
and byte ordering and the format of the value of this
|
||||
object. For interfaces that do not have such an address
|
||||
(e.g., a serial line), this node is not present.";
|
||||
reference
|
||||
"RFC 2863: The Interfaces Group MIB - ifPhysAddress";
|
||||
}
|
||||
|
||||
leaf-list higher-layer-if {
|
||||
type interface-state-ref;
|
||||
description
|
||||
"A list of references to interfaces layered on top of this
|
||||
interface.";
|
||||
reference
|
||||
"RFC 2863: The Interfaces Group MIB - ifStackTable";
|
||||
}
|
||||
|
||||
leaf-list lower-layer-if {
|
||||
type interface-state-ref;
|
||||
description
|
||||
"A list of references to interfaces layered underneath this
|
||||
interface.";
|
||||
reference
|
||||
"RFC 2863: The Interfaces Group MIB - ifStackTable";
|
||||
}
|
||||
|
||||
leaf speed {
|
||||
type yang:gauge64;
|
||||
units "bits/second";
|
||||
description
|
||||
"An estimate of the interface's current bandwidth in bits
|
||||
per second. For interfaces that do not vary in
|
||||
bandwidth or for those where no accurate estimation can
|
||||
be made, this node should contain the nominal bandwidth.
|
||||
For interfaces that have no concept of bandwidth, this
|
||||
node is not present.";
|
||||
reference
|
||||
"RFC 2863: The Interfaces Group MIB -
|
||||
ifSpeed, ifHighSpeed";
|
||||
}
|
||||
|
||||
container statistics {
|
||||
description
|
||||
"A collection of interface-related statistics objects.";
|
||||
|
||||
leaf discontinuity-time {
|
||||
type yang:date-and-time;
|
||||
mandatory true;
|
||||
description
|
||||
"The time on the most recent occasion at which any one or
|
||||
more of this interface's counters suffered a
|
||||
discontinuity. If no such discontinuities have occurred
|
||||
since the last re-initialization of the local management
|
||||
subsystem, then this node contains the time the local
|
||||
management subsystem re-initialized itself.";
|
||||
}
|
||||
|
||||
leaf in-octets {
|
||||
type yang:counter64;
|
||||
description
|
||||
"The total number of octets received on the interface,
|
||||
including framing characters.
|
||||
|
||||
Discontinuities in the value of this counter can occur
|
||||
at re-initialization of the management system, and at
|
||||
other times as indicated by the value of
|
||||
'discontinuity-time'.";
|
||||
reference
|
||||
"RFC 2863: The Interfaces Group MIB - ifHCInOctets";
|
||||
}
|
||||
|
||||
leaf in-unicast-pkts {
|
||||
type yang:counter64;
|
||||
description
|
||||
"The number of packets, delivered by this sub-layer to a
|
||||
higher (sub-)layer, that were not addressed to a
|
||||
multicast or broadcast address at this sub-layer.
|
||||
|
||||
Discontinuities in the value of this counter can occur
|
||||
at re-initialization of the management system, and at
|
||||
other times as indicated by the value of
|
||||
'discontinuity-time'.";
|
||||
reference
|
||||
"RFC 2863: The Interfaces Group MIB - ifHCInUcastPkts";
|
||||
}
|
||||
|
||||
leaf in-broadcast-pkts {
|
||||
type yang:counter64;
|
||||
description
|
||||
"The number of packets, delivered by this sub-layer to a
|
||||
higher (sub-)layer, that were addressed to a broadcast
|
||||
address at this sub-layer.
|
||||
|
||||
Discontinuities in the value of this counter can occur
|
||||
at re-initialization of the management system, and at
|
||||
other times as indicated by the value of
|
||||
'discontinuity-time'.";
|
||||
reference
|
||||
"RFC 2863: The Interfaces Group MIB -
|
||||
ifHCInBroadcastPkts";
|
||||
}
|
||||
|
||||
leaf in-multicast-pkts {
|
||||
type yang:counter64;
|
||||
description
|
||||
"The number of packets, delivered by this sub-layer to a
|
||||
higher (sub-)layer, that were addressed to a multicast
|
||||
address at this sub-layer. For a MAC-layer protocol,
|
||||
this includes both Group and Functional addresses.
|
||||
|
||||
Discontinuities in the value of this counter can occur
|
||||
at re-initialization of the management system, and at
|
||||
other times as indicated by the value of
|
||||
'discontinuity-time'.";
|
||||
reference
|
||||
"RFC 2863: The Interfaces Group MIB -
|
||||
ifHCInMulticastPkts";
|
||||
}
|
||||
|
||||
leaf in-discards {
|
||||
type yang:counter32;
|
||||
description
|
||||
"The number of inbound packets that were chosen to be
|
||||
discarded even though no errors had been detected to
|
||||
prevent their being deliverable to a higher-layer
|
||||
protocol. One possible reason for discarding such a
|
||||
packet could be to free up buffer space.
|
||||
|
||||
Discontinuities in the value of this counter can occur
|
||||
at re-initialization of the management system, and at
|
||||
other times as indicated by the value of
|
||||
'discontinuity-time'.";
|
||||
|
||||
reference
|
||||
"RFC 2863: The Interfaces Group MIB - ifInDiscards";
|
||||
}
|
||||
|
||||
leaf in-errors {
|
||||
type yang:counter32;
|
||||
description
|
||||
"For packet-oriented interfaces, the number of inbound
|
||||
packets that contained errors preventing them from being
|
||||
deliverable to a higher-layer protocol. For character-
|
||||
oriented or fixed-length interfaces, the number of
|
||||
inbound transmission units that contained errors
|
||||
preventing them from being deliverable to a higher-layer
|
||||
protocol.
|
||||
|
||||
Discontinuities in the value of this counter can occur
|
||||
at re-initialization of the management system, and at
|
||||
other times as indicated by the value of
|
||||
'discontinuity-time'.";
|
||||
reference
|
||||
"RFC 2863: The Interfaces Group MIB - ifInErrors";
|
||||
}
|
||||
|
||||
leaf in-unknown-protos {
|
||||
type yang:counter32;
|
||||
description
|
||||
"For packet-oriented interfaces, the number of packets
|
||||
received via the interface that were discarded because
|
||||
of an unknown or unsupported protocol. For
|
||||
character-oriented or fixed-length interfaces that
|
||||
support protocol multiplexing, the number of
|
||||
transmission units received via the interface that were
|
||||
discarded because of an unknown or unsupported protocol.
|
||||
For any interface that does not support protocol
|
||||
multiplexing, this counter is not present.
|
||||
|
||||
Discontinuities in the value of this counter can occur
|
||||
at re-initialization of the management system, and at
|
||||
other times as indicated by the value of
|
||||
'discontinuity-time'.";
|
||||
reference
|
||||
"RFC 2863: The Interfaces Group MIB - ifInUnknownProtos";
|
||||
}
|
||||
leaf out-octets {
|
||||
type yang:counter64;
|
||||
description
|
||||
"The total number of octets transmitted out of the
|
||||
interface, including framing characters.
|
||||
|
||||
Discontinuities in the value of this counter can occur
|
||||
at re-initialization of the management system, and at
|
||||
other times as indicated by the value of
|
||||
'discontinuity-time'.";
|
||||
reference
|
||||
"RFC 2863: The Interfaces Group MIB - ifHCOutOctets";
|
||||
}
|
||||
|
||||
leaf out-unicast-pkts {
|
||||
type yang:counter64;
|
||||
description
|
||||
"The total number of packets that higher-level protocols
|
||||
requested be transmitted, and that were not addressed
|
||||
to a multicast or broadcast address at this sub-layer,
|
||||
including those that were discarded or not sent.
|
||||
|
||||
Discontinuities in the value of this counter can occur
|
||||
at re-initialization of the management system, and at
|
||||
other times as indicated by the value of
|
||||
'discontinuity-time'.";
|
||||
reference
|
||||
"RFC 2863: The Interfaces Group MIB - ifHCOutUcastPkts";
|
||||
}
|
||||
|
||||
leaf out-broadcast-pkts {
|
||||
type yang:counter64;
|
||||
description
|
||||
"The total number of packets that higher-level protocols
|
||||
requested be transmitted, and that were addressed to a
|
||||
broadcast address at this sub-layer, including those
|
||||
that were discarded or not sent.
|
||||
|
||||
Discontinuities in the value of this counter can occur
|
||||
at re-initialization of the management system, and at
|
||||
other times as indicated by the value of
|
||||
'discontinuity-time'.";
|
||||
reference
|
||||
"RFC 2863: The Interfaces Group MIB -
|
||||
ifHCOutBroadcastPkts";
|
||||
}
|
||||
|
||||
leaf out-multicast-pkts {
|
||||
type yang:counter64;
|
||||
description
|
||||
"The total number of packets that higher-level protocols
|
||||
requested be transmitted, and that were addressed to a
|
||||
multicast address at this sub-layer, including those
|
||||
that were discarded or not sent. For a MAC-layer
|
||||
protocol, this includes both Group and Functional
|
||||
addresses.
|
||||
|
||||
Discontinuities in the value of this counter can occur
|
||||
at re-initialization of the management system, and at
|
||||
other times as indicated by the value of
|
||||
'discontinuity-time'.";
|
||||
reference
|
||||
"RFC 2863: The Interfaces Group MIB -
|
||||
ifHCOutMulticastPkts";
|
||||
}
|
||||
|
||||
leaf out-discards {
|
||||
type yang:counter32;
|
||||
description
|
||||
"The number of outbound packets that were chosen to be
|
||||
discarded even though no errors had been detected to
|
||||
prevent their being transmitted. One possible reason
|
||||
for discarding such a packet could be to free up buffer
|
||||
space.
|
||||
|
||||
Discontinuities in the value of this counter can occur
|
||||
at re-initialization of the management system, and at
|
||||
other times as indicated by the value of
|
||||
'discontinuity-time'.";
|
||||
reference
|
||||
"RFC 2863: The Interfaces Group MIB - ifOutDiscards";
|
||||
}
|
||||
|
||||
leaf out-errors {
|
||||
type yang:counter32;
|
||||
description
|
||||
"For packet-oriented interfaces, the number of outbound
|
||||
packets that could not be transmitted because of errors.
|
||||
For character-oriented or fixed-length interfaces, the
|
||||
number of outbound transmission units that could not be
|
||||
transmitted because of errors.
|
||||
|
||||
Discontinuities in the value of this counter can occur
|
||||
at re-initialization of the management system, and at
|
||||
other times as indicated by the value of
|
||||
'discontinuity-time'.";
|
||||
reference
|
||||
"RFC 2863: The Interfaces Group MIB - ifOutErrors";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,232 +0,0 @@
|
|||
module ietf-ipv4-unicast-routing {
|
||||
|
||||
namespace "urn:ietf:params:xml:ns:yang:ietf-ipv4-unicast-routing";
|
||||
|
||||
prefix "v4ur";
|
||||
|
||||
import ietf-routing {
|
||||
prefix "rt";
|
||||
revision-date "2014-10-26";
|
||||
}
|
||||
|
||||
import ietf-inet-types {
|
||||
prefix "inet";
|
||||
revision-date "2013-07-15";
|
||||
}
|
||||
|
||||
organization
|
||||
"IETF NETMOD (NETCONF Data Modeling Language) Working Group";
|
||||
|
||||
contact
|
||||
"WG Web: <http://tools.ietf.org/wg/netmod/>
|
||||
WG List: <mailto:netmod@ietf.org>
|
||||
|
||||
WG Chair: Thomas Nadeau
|
||||
<mailto:tnadeau@lucidvision.com>
|
||||
|
||||
WG Chair: Juergen Schoenwaelder
|
||||
<mailto:j.schoenwaelder@jacobs-university.de>
|
||||
|
||||
Editor: Ladislav Lhotka
|
||||
<mailto:lhotka@nic.cz>";
|
||||
|
||||
description
|
||||
"This YANG module augments the 'ietf-routing' module with basic
|
||||
configuration and operational state data for IPv4 unicast
|
||||
routing.
|
||||
|
||||
Copyright (c) 2014 IETF Trust and the persons identified as
|
||||
authors of the code. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or
|
||||
without modification, is permitted pursuant to, and subject to
|
||||
the license terms contained in, the Simplified BSD License set
|
||||
forth in Section 4.c of the IETF Trust's Legal Provisions
|
||||
Relating to IETF Documents
|
||||
(http://trustee.ietf.org/license-info).
|
||||
|
||||
This version of this YANG module is part of RFC XXXX; see the
|
||||
RFC itself for full legal notices.";
|
||||
|
||||
revision 2014-10-26 {
|
||||
description
|
||||
"Initial revision.";
|
||||
reference
|
||||
"RFC XXXX: A YANG Data Model for Routing Management";
|
||||
}
|
||||
|
||||
/* Identities */
|
||||
|
||||
identity ipv4-unicast {
|
||||
base rt:ipv4;
|
||||
description
|
||||
"This identity represents the IPv4 unicast address family.";
|
||||
}
|
||||
|
||||
/* Operational state data */
|
||||
|
||||
augment "/rt:routing-state/rt:ribs/rt:rib/rt:routes/rt:route" {
|
||||
when "../../rt:address-family = 'v4ur:ipv4-unicast'" {
|
||||
description
|
||||
"This augment is valid only for IPv4 unicast.";
|
||||
}
|
||||
description
|
||||
"This leaf augments an IPv4 unicast route.";
|
||||
leaf destination-prefix {
|
||||
type inet:ipv4-prefix;
|
||||
description
|
||||
"IPv4 destination prefix.";
|
||||
}
|
||||
}
|
||||
|
||||
augment "/rt:routing-state/rt:ribs/rt:rib/rt:routes/rt:route/"
|
||||
+ "rt:next-hop/rt:next-hop-options/rt:simple-next-hop" {
|
||||
when "../../../rt:address-family = 'v4ur:ipv4-unicast'" {
|
||||
description
|
||||
"This augment is valid only for IPv4 unicast.";
|
||||
}
|
||||
description
|
||||
"This leaf augments the 'simple-next-hop' case of IPv4 unicast
|
||||
routes.";
|
||||
leaf next-hop-address {
|
||||
type inet:ipv4-address;
|
||||
description
|
||||
"IPv4 address of the next-hop.";
|
||||
}
|
||||
}
|
||||
|
||||
augment "/rt:routing-state/rt:next-hop-lists/rt:next-hop-list/"
|
||||
+ "rt:next-hop/rt:next-hop-options/rt:simple-next-hop" {
|
||||
when "../rt:address-family = 'v4ur:ipv4-unicast'" {
|
||||
description
|
||||
"This augment is valid only for IPv4 unicast.";
|
||||
}
|
||||
description
|
||||
"This leaf augments next-hop list with IPv4 next-hop address.
|
||||
routes.";
|
||||
leaf next-hop-address {
|
||||
type inet:ipv4-address;
|
||||
description
|
||||
"IPv4 address of the next-hop.";
|
||||
}
|
||||
}
|
||||
|
||||
/* Configuration data */
|
||||
|
||||
augment "/rt:routing/rt:routing-instance/rt:routing-protocols/"
|
||||
+ "rt:routing-protocol/rt:static-routes" {
|
||||
description
|
||||
"This augment defines the configuration of the 'static'
|
||||
pseudo-protocol with data specific to IPv4 unicast.";
|
||||
container ipv4 {
|
||||
description
|
||||
"Configuration of a 'static' pseudo-protocol instance
|
||||
consists of a list of routes.";
|
||||
list route {
|
||||
key "destination-prefix";
|
||||
ordered-by "user";
|
||||
description
|
||||
"A user-ordered list of static routes.";
|
||||
leaf destination-prefix {
|
||||
type inet:ipv4-prefix;
|
||||
mandatory "true";
|
||||
description
|
||||
"IPv4 destination prefix.";
|
||||
}
|
||||
leaf description {
|
||||
type string;
|
||||
description
|
||||
"Textual description of the route.";
|
||||
}
|
||||
container next-hop {
|
||||
description
|
||||
"Configuration of next-hop.";
|
||||
grouping next-hop-content {
|
||||
description
|
||||
"Next-hop content for IPv4 unicast static routes.";
|
||||
uses rt:next-hop-content {
|
||||
augment "next-hop-options" {
|
||||
description
|
||||
"Add next-hop address case.";
|
||||
leaf next-hop-address {
|
||||
type inet:ipv4-address;
|
||||
description
|
||||
"IPv4 address of the next-hop.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
choice simple-or-list {
|
||||
description
|
||||
"Options for next-hops.";
|
||||
list multipath-entry {
|
||||
if-feature rt:multipath-routes;
|
||||
key "name";
|
||||
description
|
||||
"List of alternative next-hops.";
|
||||
leaf name {
|
||||
type string;
|
||||
description
|
||||
"A unique identifier of the next-hop entry.";
|
||||
}
|
||||
uses next-hop-content;
|
||||
uses rt:next-hop-classifiers;
|
||||
}
|
||||
case simple-next-hop {
|
||||
uses next-hop-content;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* RPC methods */
|
||||
|
||||
augment "/rt:fib-route/rt:input/rt:destination-address" {
|
||||
when "rt:address-family='v4ur:ipv4-unicast'" {
|
||||
description
|
||||
"This augment is valid only for IPv4 unicast.";
|
||||
}
|
||||
description
|
||||
"This leaf augments the 'rt:destination-address' parameter of
|
||||
the 'rt:fib-route' operation.";
|
||||
leaf address {
|
||||
type inet:ipv4-address;
|
||||
description
|
||||
"IPv4 destination address.";
|
||||
}
|
||||
}
|
||||
|
||||
augment "/rt:fib-route/rt:output/rt:route" {
|
||||
when "rt:address-family='v4ur:ipv4-unicast'" {
|
||||
description
|
||||
"This augment is valid only for IPv4 unicast.";
|
||||
}
|
||||
description
|
||||
"This leaf augments the reply to the 'rt:fib-route'
|
||||
operation.";
|
||||
leaf destination-prefix {
|
||||
type inet:ipv4-prefix;
|
||||
description
|
||||
"IPv4 destination prefix.";
|
||||
}
|
||||
}
|
||||
|
||||
augment "/rt:fib-route/rt:output/rt:route/rt:next-hop/"
|
||||
+ "rt:next-hop-options/rt:simple-next-hop" {
|
||||
when "../rt:address-family='v4ur:ipv4-unicast'" {
|
||||
description
|
||||
"This augment is valid only for IPv4 unicast.";
|
||||
}
|
||||
description
|
||||
"This leaf augments the 'simple-next-hop' case in the reply to
|
||||
the 'rt:fib-route' operation.";
|
||||
leaf next-hop-address {
|
||||
type inet:ipv4-address;
|
||||
description
|
||||
"IPv4 address of the next-hop.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,636 +0,0 @@
|
|||
|
||||
module ietf-ipv6-unicast-routing {
|
||||
|
||||
namespace "urn:ietf:params:xml:ns:yang:ietf-ipv6-unicast-routing";
|
||||
|
||||
prefix "v6ur";
|
||||
|
||||
import ietf-routing {
|
||||
prefix "rt";
|
||||
revision-date "2014-10-26";
|
||||
}
|
||||
|
||||
import ietf-inet-types {
|
||||
prefix "inet";
|
||||
revision-date "2013-07-15";
|
||||
}
|
||||
|
||||
import ietf-interfaces {
|
||||
prefix "if";
|
||||
revision-date "2013-07-15";
|
||||
}
|
||||
|
||||
import ietf-ip {
|
||||
prefix "ip";
|
||||
revision-date "2014-06-16";
|
||||
}
|
||||
|
||||
organization
|
||||
"IETF NETMOD (NETCONF Data Modeling Language) Working Group";
|
||||
|
||||
contact
|
||||
"WG Web: <http://tools.ietf.org/wg/netmod/>
|
||||
WG List: <mailto:netmod@ietf.org>
|
||||
|
||||
WG Chair: Thomas Nadeau
|
||||
<mailto:tnadeau@lucidvision.com>
|
||||
|
||||
WG Chair: Juergen Schoenwaelder
|
||||
<mailto:j.schoenwaelder@jacobs-university.de>
|
||||
|
||||
Editor: Ladislav Lhotka
|
||||
<mailto:lhotka@nic.cz>";
|
||||
|
||||
description
|
||||
"This YANG module augments the 'ietf-routing' module with basic
|
||||
configuration and operational state data for IPv6 unicast
|
||||
routing.
|
||||
|
||||
Copyright (c) 2014 IETF Trust and the persons identified as
|
||||
authors of the code. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or
|
||||
without modification, is permitted pursuant to, and subject to
|
||||
the license terms contained in, the Simplified BSD License set
|
||||
forth in Section 4.c of the IETF Trust's Legal Provisions
|
||||
Relating to IETF Documents
|
||||
(http://trustee.ietf.org/license-info).
|
||||
|
||||
This version of this YANG module is part of RFC XXXX; see the
|
||||
RFC itself for full legal notices.";
|
||||
|
||||
revision 2014-10-26 {
|
||||
description
|
||||
"Initial revision.";
|
||||
reference
|
||||
"RFC XXXX: A YANG Data Model for Routing Management";
|
||||
}
|
||||
|
||||
/* Identities */
|
||||
|
||||
identity ipv6-unicast {
|
||||
base rt:ipv6;
|
||||
description
|
||||
"This identity represents the IPv6 unicast address family.";
|
||||
}
|
||||
|
||||
/* Operational state data */
|
||||
|
||||
augment "/rt:routing-state/rt:routing-instance/rt:interfaces/"
|
||||
+ "rt:interface" {
|
||||
description
|
||||
"IPv6-specific parameters of router interfaces.";
|
||||
container ipv6-router-advertisements {
|
||||
description
|
||||
"Parameters of IPv6 Router Advertisements.";
|
||||
leaf send-advertisements {
|
||||
type boolean;
|
||||
description
|
||||
"A flag indicating whether or not the router sends periodic
|
||||
Router Advertisements and responds to Router
|
||||
Solicitations.";
|
||||
}
|
||||
leaf max-rtr-adv-interval {
|
||||
type uint16 {
|
||||
range "4..1800";
|
||||
}
|
||||
units "seconds";
|
||||
description
|
||||
"The maximum time allowed between sending unsolicited
|
||||
multicast Router Advertisements from the interface.";
|
||||
}
|
||||
leaf min-rtr-adv-interval {
|
||||
type uint16 {
|
||||
range "3..1350";
|
||||
}
|
||||
units "seconds";
|
||||
description
|
||||
"The minimum time allowed between sending unsolicited
|
||||
multicast Router Advertisements from the interface.";
|
||||
}
|
||||
leaf managed-flag {
|
||||
type boolean;
|
||||
description
|
||||
"The value that is placed in the 'Managed address
|
||||
configuration' flag field in the Router Advertisement.";
|
||||
}
|
||||
leaf other-config-flag {
|
||||
type boolean;
|
||||
description
|
||||
"The value that is placed in the 'Other configuration' flag
|
||||
field in the Router Advertisement.";
|
||||
}
|
||||
leaf link-mtu {
|
||||
type uint32;
|
||||
description
|
||||
"The value that is placed in MTU options sent by the
|
||||
router. A value of zero indicates that no MTU options are
|
||||
sent.";
|
||||
}
|
||||
leaf reachable-time {
|
||||
type uint32 {
|
||||
range "0..3600000";
|
||||
}
|
||||
units "milliseconds";
|
||||
description
|
||||
"The value that is placed in the Reachable Time field in
|
||||
the Router Advertisement messages sent by the router. A
|
||||
value of zero means unspecified (by this router).";
|
||||
}
|
||||
leaf retrans-timer {
|
||||
type uint32;
|
||||
units "milliseconds";
|
||||
description
|
||||
"The value that is placed in the Retrans Timer field in the
|
||||
Router Advertisement messages sent by the router. A value
|
||||
of zero means unspecified (by this router).";
|
||||
}
|
||||
leaf cur-hop-limit {
|
||||
type uint8;
|
||||
description
|
||||
"The value that is placed in the Cur Hop Limit field in the
|
||||
Router Advertisement messages sent by the router. A value
|
||||
of zero means unspecified (by this router).";
|
||||
}
|
||||
leaf default-lifetime {
|
||||
type uint16 {
|
||||
range "0..9000";
|
||||
}
|
||||
units "seconds";
|
||||
description
|
||||
"The value that is placed in the Router Lifetime field of
|
||||
Router Advertisements sent from the interface, in seconds.
|
||||
A value of zero indicates that the router is not to be
|
||||
used as a default router.";
|
||||
}
|
||||
container prefix-list {
|
||||
description
|
||||
"A list of prefixes that are placed in Prefix Information
|
||||
options in Router Advertisement messages sent from the
|
||||
interface.
|
||||
|
||||
By default, these are all prefixes that the router
|
||||
advertises via routing protocols as being on-link for the
|
||||
interface from which the advertisement is sent.";
|
||||
list prefix {
|
||||
key "prefix-spec";
|
||||
description
|
||||
"Advertised prefix entry and its parameters.";
|
||||
leaf prefix-spec {
|
||||
type inet:ipv6-prefix;
|
||||
description
|
||||
"IPv6 address prefix.";
|
||||
}
|
||||
leaf valid-lifetime {
|
||||
type uint32;
|
||||
units "seconds";
|
||||
description
|
||||
"The value that is placed in the Valid Lifetime in the
|
||||
Prefix Information option. The designated value of all
|
||||
1's (0xffffffff) represents infinity.";
|
||||
}
|
||||
leaf on-link-flag {
|
||||
type boolean;
|
||||
description
|
||||
"The value that is placed in the on-link flag ('L-bit')
|
||||
field in the Prefix Information option.";
|
||||
}
|
||||
leaf preferred-lifetime {
|
||||
type uint32;
|
||||
units "seconds";
|
||||
description
|
||||
"The value that is placed in the Preferred Lifetime in
|
||||
the Prefix Information option, in seconds. The
|
||||
designated value of all 1's (0xffffffff) represents
|
||||
infinity.";
|
||||
}
|
||||
leaf autonomous-flag {
|
||||
type boolean;
|
||||
description
|
||||
"The value that is placed in the Autonomous Flag field
|
||||
in the Prefix Information option.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
augment "/rt:routing-state/rt:ribs/rt:rib/rt:routes/rt:route" {
|
||||
when "../../rt:address-family = 'v6ur:ipv6-unicast'" {
|
||||
description
|
||||
"This augment is valid only for IPv6 unicast.";
|
||||
}
|
||||
description
|
||||
"This leaf augments an IPv6 unicast route.";
|
||||
leaf destination-prefix {
|
||||
type inet:ipv6-prefix;
|
||||
description
|
||||
"IPv6 destination prefix.";
|
||||
}
|
||||
}
|
||||
|
||||
augment "/rt:routing-state/rt:ribs/rt:rib/rt:routes/rt:route/"
|
||||
+ "rt:next-hop/rt:next-hop-options/rt:simple-next-hop" {
|
||||
when "../../../rt:address-family = 'v6ur:ipv6-unicast'" {
|
||||
description
|
||||
"This augment is valid only for IPv6 unicast.";
|
||||
}
|
||||
description
|
||||
"This leaf augments the 'simple-next-hop' case of IPv6 unicast
|
||||
routes.";
|
||||
leaf next-hop {
|
||||
type inet:ipv6-address;
|
||||
description
|
||||
"IPv6 address of the next-hop.";
|
||||
}
|
||||
}
|
||||
|
||||
augment "/rt:routing-state/rt:next-hop-lists/rt:next-hop-list/"
|
||||
+ "rt:next-hop/rt:next-hop-options/rt:simple-next-hop" {
|
||||
when "../rt:address-family = 'v6ur:ipv6-unicast'" {
|
||||
description
|
||||
"This augment is valid only for IPv6 unicast.";
|
||||
}
|
||||
description
|
||||
"This leaf augments next-hop list with IPv6 next-hop address.
|
||||
routes.";
|
||||
leaf next-hop-address {
|
||||
type inet:ipv6-address;
|
||||
description
|
||||
"IPv6 address of the next-hop.";
|
||||
}
|
||||
}
|
||||
|
||||
/* Configuration data */
|
||||
|
||||
augment
|
||||
"/rt:routing/rt:routing-instance/rt:interfaces/rt:interface" {
|
||||
when "/if:interfaces/if:interface[if:name=current()/rt:name]/"
|
||||
+ "ip:ipv6/ip:enabled='true'" {
|
||||
description
|
||||
"This augment is only valid for router interfaces with
|
||||
enabled IPv6.";
|
||||
}
|
||||
description
|
||||
"Configuration of IPv6-specific parameters of router
|
||||
interfaces.";
|
||||
container ipv6-router-advertisements {
|
||||
description
|
||||
"Configuration of IPv6 Router Advertisements.";
|
||||
leaf send-advertisements {
|
||||
type boolean;
|
||||
default "false";
|
||||
description
|
||||
"A flag indicating whether or not the router sends periodic
|
||||
Router Advertisements and responds to Router
|
||||
Solicitations.";
|
||||
reference
|
||||
"RFC 4861: Neighbor Discovery for IP version 6 (IPv6) -
|
||||
AdvSendAdvertisements.";
|
||||
}
|
||||
leaf max-rtr-adv-interval {
|
||||
type uint16 {
|
||||
range "4..1800";
|
||||
}
|
||||
units "seconds";
|
||||
default "600";
|
||||
description
|
||||
"The maximum time allowed between sending unsolicited
|
||||
multicast Router Advertisements from the interface.";
|
||||
reference
|
||||
"RFC 4861: Neighbor Discovery for IP version 6 (IPv6) -
|
||||
MaxRtrAdvInterval.";
|
||||
}
|
||||
leaf min-rtr-adv-interval {
|
||||
type uint16 {
|
||||
range "3..1350";
|
||||
}
|
||||
units "seconds";
|
||||
must ". <= 0.75 * ../max-rtr-adv-interval" {
|
||||
description
|
||||
"The value MUST NOT be greater than 75 % of
|
||||
'max-rtr-adv-interval'.";
|
||||
}
|
||||
description
|
||||
"The minimum time allowed between sending unsolicited
|
||||
multicast Router Advertisements from the interface.
|
||||
|
||||
The default value to be used operationally if this leaf is
|
||||
not configured is determined as follows:
|
||||
|
||||
- if max-rtr-adv-interval >= 9 seconds, the default value
|
||||
is 0.33 * max-rtr-adv-interval;
|
||||
|
||||
- otherwise it is 0.75 * max-rtr-adv-interval.";
|
||||
reference
|
||||
"RFC 4861: Neighbor Discovery for IP version 6 (IPv6) -
|
||||
MinRtrAdvInterval.";
|
||||
}
|
||||
leaf managed-flag {
|
||||
type boolean;
|
||||
default "false";
|
||||
description
|
||||
"The value to be placed in the 'Managed address
|
||||
configuration' flag field in the Router Advertisement.";
|
||||
reference
|
||||
"RFC 4861: Neighbor Discovery for IP version 6 (IPv6) -
|
||||
AdvManagedFlag.";
|
||||
}
|
||||
leaf other-config-flag {
|
||||
type boolean;
|
||||
default "false";
|
||||
description
|
||||
"The value to be placed in the 'Other configuration' flag
|
||||
field in the Router Advertisement.";
|
||||
reference
|
||||
"RFC 4861: Neighbor Discovery for IP version 6 (IPv6) -
|
||||
AdvOtherConfigFlag.";
|
||||
}
|
||||
leaf link-mtu {
|
||||
type uint32;
|
||||
default "0";
|
||||
description
|
||||
"The value to be placed in MTU options sent by the router.
|
||||
A value of zero indicates that no MTU options are sent.";
|
||||
reference
|
||||
"RFC 4861: Neighbor Discovery for IP version 6 (IPv6) -
|
||||
AdvLinkMTU.";
|
||||
}
|
||||
leaf reachable-time {
|
||||
type uint32 {
|
||||
range "0..3600000";
|
||||
}
|
||||
units "milliseconds";
|
||||
default "0";
|
||||
description
|
||||
"The value to be placed in the Reachable Time field in the
|
||||
Router Advertisement messages sent by the router. A value
|
||||
of zero means unspecified (by this router).";
|
||||
reference
|
||||
"RFC 4861: Neighbor Discovery for IP version 6 (IPv6) -
|
||||
AdvReachableTime.";
|
||||
}
|
||||
leaf retrans-timer {
|
||||
type uint32;
|
||||
units "milliseconds";
|
||||
default "0";
|
||||
description
|
||||
"The value to be placed in the Retrans Timer field in the
|
||||
Router Advertisement messages sent by the router. A value
|
||||
of zero means unspecified (by this router).";
|
||||
reference
|
||||
"RFC 4861: Neighbor Discovery for IP version 6 (IPv6) -
|
||||
AdvRetransTimer.";
|
||||
}
|
||||
leaf cur-hop-limit {
|
||||
type uint8;
|
||||
description
|
||||
"The value to be placed in the Cur Hop Limit field in the
|
||||
Router Advertisement messages sent by the router. A value
|
||||
of zero means unspecified (by this router).
|
||||
|
||||
If this parameter is not configured, the device SHOULD use
|
||||
the value specified in IANA Assigned Numbers that was in
|
||||
effect at the time of implementation.";
|
||||
reference
|
||||
"RFC 4861: Neighbor Discovery for IP version 6 (IPv6) -
|
||||
AdvCurHopLimit.
|
||||
|
||||
IANA: IP Parameters,
|
||||
http://www.iana.org/assignments/ip-parameters";
|
||||
}
|
||||
leaf default-lifetime {
|
||||
type uint16 {
|
||||
range "0..9000";
|
||||
}
|
||||
units "seconds";
|
||||
description
|
||||
"The value to be placed in the Router Lifetime field of
|
||||
Router Advertisements sent from the interface, in seconds.
|
||||
It MUST be either zero or between max-rtr-adv-interval and
|
||||
9000 seconds. A value of zero indicates that the router is
|
||||
not to be used as a default router. These limits may be
|
||||
overridden by specific documents that describe how IPv6
|
||||
operates over different link layers.
|
||||
|
||||
If this parameter is not configured, the device SHOULD use
|
||||
a value of 3 * max-rtr-adv-interval.";
|
||||
reference
|
||||
"RFC 4861: Neighbor Discovery for IP version 6 (IPv6) -
|
||||
AdvDefaultLifeTime.";
|
||||
}
|
||||
container prefix-list {
|
||||
description
|
||||
"Configuration of prefixes to be placed in Prefix
|
||||
Information options in Router Advertisement messages sent
|
||||
from the interface.
|
||||
|
||||
Prefixes that are advertised by default but do not have
|
||||
their entries in the child 'prefix' list are advertised
|
||||
with the default values of all parameters.
|
||||
|
||||
The link-local prefix SHOULD NOT be included in the list
|
||||
of advertised prefixes.";
|
||||
reference
|
||||
"RFC 4861: Neighbor Discovery for IP version 6 (IPv6) -
|
||||
AdvPrefixList.";
|
||||
list prefix {
|
||||
key "prefix-spec";
|
||||
description
|
||||
"Configuration of an advertised prefix entry.";
|
||||
leaf prefix-spec {
|
||||
type inet:ipv6-prefix;
|
||||
description
|
||||
"IPv6 address prefix.";
|
||||
}
|
||||
choice control-adv-prefixes {
|
||||
default "advertise";
|
||||
description
|
||||
"The prefix either may be explicitly removed from the
|
||||
set of advertised prefixes, or parameters with which
|
||||
it is advertised may be specified (default case).";
|
||||
leaf no-advertise {
|
||||
type empty;
|
||||
description
|
||||
"The prefix will not be advertised.
|
||||
|
||||
This can be used for removing the prefix from the
|
||||
default set of advertised prefixes.";
|
||||
}
|
||||
case advertise {
|
||||
leaf valid-lifetime {
|
||||
type uint32;
|
||||
units "seconds";
|
||||
default "2592000";
|
||||
description
|
||||
"The value to be placed in the Valid Lifetime in
|
||||
the Prefix Information option. The designated
|
||||
value of all 1's (0xffffffff) represents
|
||||
infinity.";
|
||||
reference
|
||||
"RFC 4861: Neighbor Discovery for IP version 6
|
||||
(IPv6) - AdvValidLifetime.";
|
||||
}
|
||||
leaf on-link-flag {
|
||||
type boolean;
|
||||
default "true";
|
||||
description
|
||||
"The value to be placed in the on-link flag
|
||||
('L-bit') field in the Prefix Information
|
||||
option.";
|
||||
reference
|
||||
"RFC 4861: Neighbor Discovery for IP version 6
|
||||
(IPv6) - AdvOnLinkFlag.";
|
||||
}
|
||||
leaf preferred-lifetime {
|
||||
type uint32;
|
||||
units "seconds";
|
||||
must ". <= ../valid-lifetime" {
|
||||
description
|
||||
"This value MUST NOT be greater than
|
||||
valid-lifetime.";
|
||||
}
|
||||
default "604800";
|
||||
description
|
||||
"The value to be placed in the Preferred Lifetime
|
||||
in the Prefix Information option. The designated
|
||||
value of all 1's (0xffffffff) represents
|
||||
infinity.";
|
||||
reference
|
||||
"RFC 4861: Neighbor Discovery for IP version 6
|
||||
(IPv6) - AdvPreferredLifetime.";
|
||||
}
|
||||
leaf autonomous-flag {
|
||||
type boolean;
|
||||
default "true";
|
||||
description
|
||||
"The value to be placed in the Autonomous Flag
|
||||
field in the Prefix Information option.";
|
||||
reference
|
||||
"RFC 4861: Neighbor Discovery for IP version 6
|
||||
(IPv6) - AdvAutonomousFlag.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
augment "/rt:routing/rt:routing-instance/rt:routing-protocols/"
|
||||
+ "rt:routing-protocol/rt:static-routes" {
|
||||
description
|
||||
"This augment defines the configuration of the 'static'
|
||||
pseudo-protocol with data specific to IPv6 unicast.";
|
||||
container ipv6 {
|
||||
description
|
||||
"Configuration of a 'static' pseudo-protocol instance
|
||||
consists of a list of routes.";
|
||||
list route {
|
||||
key "destination-prefix";
|
||||
ordered-by "user";
|
||||
description
|
||||
"A user-ordered list of static routes.";
|
||||
leaf destination-prefix {
|
||||
type inet:ipv6-prefix;
|
||||
mandatory "true";
|
||||
description
|
||||
"IPv6 destination prefix.";
|
||||
}
|
||||
leaf description {
|
||||
type string;
|
||||
description
|
||||
"Textual description of the route.";
|
||||
}
|
||||
container next-hop {
|
||||
description
|
||||
"Configuration of next-hop.";
|
||||
grouping next-hop-content {
|
||||
description
|
||||
"Next-hop content for IPv6 unicast static routes.";
|
||||
uses rt:next-hop-content {
|
||||
augment "next-hop-options" {
|
||||
description
|
||||
"Add next-hop address case.";
|
||||
leaf next-hop-address {
|
||||
type inet:ipv6-address;
|
||||
description
|
||||
"IPv6 address of the next-hop.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
choice simple-or-list {
|
||||
description
|
||||
"Options for next-hops.";
|
||||
list multipath-entry {
|
||||
if-feature rt:multipath-routes;
|
||||
key "name";
|
||||
description
|
||||
"List of alternative next-hops.";
|
||||
leaf name {
|
||||
type string;
|
||||
description
|
||||
"A unique identifier of the next-hop entry.";
|
||||
}
|
||||
uses next-hop-content;
|
||||
uses rt:next-hop-classifiers;
|
||||
}
|
||||
case simple-next-hop {
|
||||
uses next-hop-content;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* RPC methods */
|
||||
|
||||
augment "/rt:fib-route/rt:input/rt:destination-address" {
|
||||
when "rt:address-family='v6ur:ipv6-unicast'" {
|
||||
description
|
||||
"This augment is valid only for IPv6 unicast.";
|
||||
}
|
||||
description
|
||||
"This leaf augments the 'rt:destination-address' parameter of
|
||||
the 'rt:fib-route' operation.";
|
||||
leaf address {
|
||||
type inet:ipv6-address;
|
||||
description
|
||||
"IPv6 destination address.";
|
||||
}
|
||||
}
|
||||
|
||||
augment "/rt:fib-route/rt:output/rt:route" {
|
||||
when "rt:address-family='v6ur:ipv6-unicast'" {
|
||||
description
|
||||
"This augment is valid only for IPv6 unicast.";
|
||||
}
|
||||
description
|
||||
"This leaf augments the reply to the 'rt:fib-route'
|
||||
operation.";
|
||||
leaf destination-prefix {
|
||||
type inet:ipv6-prefix;
|
||||
description
|
||||
"IPv6 destination prefix.";
|
||||
}
|
||||
}
|
||||
|
||||
augment "/rt:fib-route/rt:output/rt:route/rt:next-hop/"
|
||||
+ "rt:next-hop-options/rt:simple-next-hop" {
|
||||
when "../rt:address-family='v4ur:ipv6-unicast'" {
|
||||
description
|
||||
"This augment is valid only for IPv6 unicast.";
|
||||
}
|
||||
description
|
||||
"This leaf augments the 'simple-next-hop' case in the reply to
|
||||
the 'rt:fib-route' operation.";
|
||||
leaf next-hop-address {
|
||||
type inet:ipv6-address;
|
||||
description
|
||||
"IPv6 address of the next-hop.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -36,7 +36,7 @@ This package contains header files for CLIXON.
|
|||
%setup
|
||||
|
||||
%build
|
||||
%configure --with-cligen=%{cligen_prefix} --without-keyvalue
|
||||
%configure --with-cligen=%{cligen_prefix}
|
||||
make
|
||||
|
||||
%install
|
||||
|
|
|
|||
|
|
@ -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
|
||||
#
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
/* include/clixon_config.h.in. Generated from configure.ac by autoheader. */
|
||||
|
||||
/* Clixon data dir for system yang files etc */
|
||||
#undef CLIXON_DATADIR
|
||||
|
||||
/* Location for apps to find default config file */
|
||||
#undef CLIXON_DEFAULT_CONFIG
|
||||
|
||||
|
|
@ -42,6 +39,9 @@
|
|||
/* Define to 1 if you have the `crypt' library (-lcrypt). */
|
||||
#undef HAVE_LIBCRYPT
|
||||
|
||||
/* Define to 1 if you have the `curl' library (-lcurl). */
|
||||
#undef HAVE_LIBCURL
|
||||
|
||||
/* Define to 1 if you have the `dl' library (-ldl). */
|
||||
#undef HAVE_LIBDL
|
||||
|
||||
|
|
@ -133,4 +133,4 @@
|
|||
`char[]'. */
|
||||
#undef YYTEXT_POINTER
|
||||
|
||||
#include "clixon_custom.h"
|
||||
#include <clixon_custom.h>
|
||||
|
|
|
|||
|
|
@ -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,3 +51,10 @@ int strverscmp (__const char *__s1, __const char *__s2);
|
|||
*/
|
||||
#define XMLNS_YANG_ONLY 1
|
||||
|
||||
/* 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).
|
||||
* All new references should use @datamodel (or CLICON_CLI_MODEL_TREENAME).
|
||||
* whereas older code used @datamodel:tree.
|
||||
*/
|
||||
#define CLICON_CLI_MODEL_TREENAME_PATCH
|
||||
|
|
|
|||
|
|
@ -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
|
||||
#
|
||||
|
|
@ -80,5 +80,5 @@ distclean: clean
|
|||
do (cd $$i; $(MAKE) $(MFLAGS) distclean); done; \
|
||||
(cd clixon; $(MAKE) $(MFLAGS) $@)
|
||||
|
||||
tags:
|
||||
TAGS:
|
||||
find $(srcdir) -name '*.[chyl]' -print | etags -
|
||||
|
|
|
|||
|
|
@ -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
|
||||
#
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue