Directory change: Moved example to example/main to make room for other examples

This commit is contained in:
Olof hagsand 2019-03-28 17:58:47 +01:00
parent 60cb87f998
commit a269e26c0d
19 changed files with 525 additions and 439 deletions

159
example/main/Makefile.in Normal file
View file

@ -0,0 +1,159 @@
#
# ***** BEGIN LICENSE BLOCK *****
#
# Copyright (C) 2009-2019 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 *****
#
VPATH = @srcdir@
srcdir = @srcdir@
top_srcdir = @top_srcdir@
prefix = @prefix@
bindir = @bindir@
includedir = @includedir@
datarootdir = @datarootdir@
sysconfdir = @sysconfdir@
datarootdir = @datarootdir@
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@
CC = @CC@
CFLAGS = @CFLAGS@ -rdynamic -fPIC
INSTALLFLAGS = @INSTALLFLAGS@
with_restconf = @with_restconf@
INCLUDES = -I$(includedir) @INCLUDES@
CPPFLAGS = @CPPFLAGS@ -fPIC
BE_PLUGIN = $(APPNAME)_backend.so
BE2_PLUGIN = $(APPNAME)_backend_nacm.so
CLI_PLUGIN = $(APPNAME)_cli.so
NETCONF_PLUGIN = $(APPNAME)_netconf.so
RESTCONF_PLUGIN = $(APPNAME)_restconf.so
PLUGINS = $(BE_PLUGIN) $(BE2_PLUGIN) $(CLI_PLUGIN) $(NETCONF_PLUGIN)
ifeq ($(with_restconf),yes)
PLUGINS += $(RESTCONF_PLUGIN)
endif
.PHONY: all clean depend install
all: $(PLUGINS)
.SUFFIXES: .c .o
# implicit rule
.c.o:
$(CC) $(INCLUDES) $(CPPFLAGS) $(CFLAGS) -c $<
CLISPECS = $(APPNAME)_cli.cli
YANGSPECS = clixon-example@2019-01-13.yang
# Backend plugin
BE_SRC = $(APPNAME)_backend.c
BE_OBJ = $(BE_SRC:%.c=%.o)
$(BE_PLUGIN): $(BE_OBJ)
$(CC) -Wall -shared -o $@ -lc $<
# Secondary NACM backend plugin
BE2_SRC = $(APPNAME)_backend_nacm.c
BE2_OBJ = $(BE2_SRC:%.c=%.o)
$(BE2_PLUGIN): $(BE2_OBJ)
$(CC) -Wall -shared -o $@ -lc $<
# CLI frontend plugin
CLI_SRC = $(APPNAME)_cli.c
CLI_OBJ = $(CLI_SRC:%.c=%.o)
$(CLI_PLUGIN): $(CLI_OBJ)
$(CC) -Wall -shared -o $@ -lc $^
# NETCONF frontend plugin
NETCONF_SRC = $(APPNAME)_netconf.c
NETCONF_OBJ = $(NETCONF_SRC:%.c=%.o)
$(NETCONF_PLUGIN): $(NETCONF_OBJ)
$(CC) -Wall -shared -o $@ -lc $^
# See configure.ac
# RESTCONF frontend plugin
RESTCONF_SRC = $(APPNAME)_restconf.c
RESTCONF_OBJ = $(RESTCONF_SRC:%.c=%.o)
$(RESTCONF_PLUGIN): $(RESTCONF_OBJ)
$(CC) -Wall -shared -o $@ -lc $^
SRC = $(BE_SRC) $(BE2_SRC) $(CLI_SRC) $(NETCONF_SRC)
SRC += $(RESTCONF_SRC)
OBJS = $(BE_OBJ) $(BE2_OBJ) $(CLI_OBJ) $(NETCONF_OBJ)
OBJS += $(RESTCONF_OBJ)
clean:
rm -f $(PLUGINS) $(OBJS)
distclean: clean
rm -f Makefile *~ .depend
install: $(YANGSPECS) $(CLISPECS) $(PLUGINS) $(APPNAME).xml
install -d -m 0755 $(DESTDIR)$(sysconfdir)
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)$(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
install -m 0644 $(INSTALLFLAGS) $(BE_PLUGIN) $(BE2_PLUGIN) $(DESTDIR)$(libdir)/$(APPNAME)/backend
install -d -m 0755 $(DESTDIR)$(libdir)/$(APPNAME)/netconf
install -m 0644 $(INSTALLFLAGS) $(NETCONF_PLUGIN) $(DESTDIR)$(libdir)/$(APPNAME)/netconf
ifeq ($(with_restconf),yes)
install -d -m 0755 $(DESTDIR)$(libdir)/$(APPNAME)/restconf
install -m 0644 $(INSTALLFLAGS) $(RESTCONF_PLUGIN) $(DESTDIR)$(libdir)/$(APPNAME)/restconf
endif
install -d -m 0755 $(DESTDIR)$(libdir)/$(APPNAME)/clispec
install -m 0644 $(CLISPECS) $(DESTDIR)$(libdir)/$(APPNAME)/clispec
install -d -m 0755 $(DESTDIR)$(localstatedir)/$(APPNAME)
uninstall:
rm -rf $(DESTDIR)$(sysconfdir)/$(APPNAME).xml
rm -rf $(DESTDIR)$(datarootdir)/$(APPNAME)
rm -rf $(DESTDIR)$(localstatedir)/$(APPNAME)
rm -rf $(DESTDIR)$(libdir)/$(APPNAME)
install-include:
depend:
$(CC) $(DEPENDFLAGS) $(INCLUDES) $(CFLAGS) -MM $(SRC) > .depend
#include .depend

325
example/main/README.md Normal file
View file

@ -0,0 +1,325 @@
# Clixon main example
* [Content](#content)
* [Compile and run](#compile)
* [Using the CLI](#using-the-cli)
* [Using netconf](#using-netconf)
* [Streams](#streams)
* [RPC Operations](#rpc-operations)
* [State data](#state-data)
* [Authentication and NACM](#authentication-and-nacm)
* [Systemd](#systemd)
* [Docker](#docker)
* [Plugins](#plugins)
## Content
This directory contains a Clixon example which includes a simple example. It contains the following files:
* `example.xml` The configuration file. See [yang/clixon-config@<date>.yang](../../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
## Compile and run
Before you start,
* Make [group setup](../../doc/FAQ.md#do-i-need-to-setup-anything-important)
* Setup [restconf](../../doc/FAQ.md#how-do-i-use-restconf)
```
cd example
make && sudo make install
```
Start backend:
```
sudo clixon_backend -f /usr/local/etc/example.xml -s init
```
Edit cli:
```
clixon_cli -f /usr/local/etc/example.xml
```
Send netconf command:
```
clixon_netconf -f /usr/local/etc/example.xml
```
Start clixon restconf daemon
```
sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data
```
Send restconf command
```
curl -G http://127.0.0.1/restconf/data
```
## 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 xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
<interface>
<name>eth1</name>
<enabled>true</enabled>
<ipv4>
<address>
<ip>9.2.3.4</ip>
<prefix-length>24</prefix-length>
</address>
</ipv4>
</interface>
</interfaces>
</config></edit-config></rpc>]]>]]>
```
### 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"><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 / {
root /usr/share/nginx/html/restconf;
fastcgi_pass unix:/www-data/fastcgi_restconf.sock;
include fastcgi_params;
}
location /restconf {
fastcgi_pass unix:/www-data/fastcgi_restconf.sock;
include fastcgi_params;
}
location /streams {
fastcgi_pass unix:/www-data/fastcgi_restconf.sock;
include fastcgi_params;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
```
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 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>2019-01-02T10:20:05.929272</eventTime><event><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event></notification>]]>]]>
...
```
This can also be triggered via the CLI:
```
clixon_cli -f /usr/local/etc/example.xml
cli> notify
cli> event-class fault;
reportingEntity {
card Ethernet0;
}
severity major;
...
cli> no notify
cli>
```
Restconf support is also supported, see (restc)[../../apps/restconf/README.md].
## 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.
The state data is enabled by starting the backend with: `-- -s`.
## 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
See [../../docker/system] for instructions on how to build this example
as a docker container.
## 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.
The content of the API struct is different depending on what kind of plugin it is.
The plugin init function may also include registering RPC functions, see below is for a backend.
```
static clixon_plugin_api api = {
"example", /* name */
clixon_plugin_init,
plugin_start,
plugin_exit,
.ca_reset=plugin_reset,/* reset for extra XML at startup*/
.ca_statedata=plugin_statedata, /* statedata */
.ca_upgrade=example_upgrade, /* upgrade configuration */
.ca_trans_begin=NULL, /* trans begin */
.ca_trans_validate=transaction_validate,/* trans validate */
.ca_trans_complete=NULL, /* trans complete */
.ca_trans_commit=transaction_commit, /* trans commit */
.ca_trans_end=NULL, /* trans end */
.ca_trans_abort=NULL /* trans abort */
};
clixon_plugin_api *
clixon_plugin_init(clicon_handle h)
{
/* Optional callback registration for RPC calls */
rpc_callback_register(h, example_rpc, NULL, "example");
/* Return plugin API */
return &api; /* Return NULL on error */
}
```
Here is a corresponding example for a CLI plugin:
```
static clixon_plugin_api api = {
"example", /* name */
clixon_plugin_init, /* init */
NULL, /* start */
NULL, /* exit */
.ca_prompt=NULL, /* cli_prompthook_t */
.ca_suspend=NULL, /* cligen_susp_cb_t */
.ca_interrupt=NULL, /* cligen_interrupt_cb_t */
};
```

View 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;
}
}
}
}

21
example/main/example.xml Normal file
View file

@ -0,0 +1,21 @@
<clixon-config xmlns="http://clicon.org/config">
<CLICON_CONFIGFILE>/usr/local/etc/example.xml</CLICON_CONFIGFILE>
<CLICON_FEATURE>*:*</CLICON_FEATURE>
<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>
<CLICON_RESTCONF_DIR>/usr/local/lib/example/restconf</CLICON_RESTCONF_DIR>
<CLICON_CLI_DIR>/usr/local/lib/example/cli</CLICON_CLI_DIR>
<CLICON_CLISPEC_DIR>/usr/local/lib/example/clispec</CLICON_CLISPEC_DIR>
<CLICON_SOCK>/usr/local/var/example/example.sock</CLICON_SOCK>
<CLICON_BACKEND_PIDFILE>/usr/local/var/example/example.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_CLI_GENMODEL_COMPLETION>1</CLICON_CLI_GENMODEL_COMPLETION>
<CLICON_CLI_GENMODEL_TYPE>VARS</CLICON_CLI_GENMODEL_TYPE>
<CLICON_XMLDB_DIR>/usr/local/var/example</CLICON_XMLDB_DIR>
<CLICON_XMLDB_PLUGIN>/usr/local/lib/xmldb/text.so</CLICON_XMLDB_PLUGIN>
<CLICON_CLI_LINESCROLLING>0</CLICON_CLI_LINESCROLLING>
<CLICON_STARTUP_MODE>init</CLICON_STARTUP_MODE>
<CLICON_NACM_MODE>disabled</CLICON_NACM_MODE>
</clixon-config>

View file

@ -0,0 +1,632 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 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 *****
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <assert.h>
#include <syslog.h>
#include <sys/time.h>
/* clicon */
#include <cligen/cligen.h>
/* Clicon library functions. */
#include <clixon/clixon.h>
/* These include signatures for plugin and transaction callbacks. */
#include <clixon/clixon_backend.h>
/*! Variable to control if reset code is run.
* The reset code inserts "extra XML" which assumes ietf-interfaces is
* loaded, and this is not always the case.
* Therefore, the backend must be started with -- -r to enable the reset function
*/
static int _reset = 0;
/*! Variable to control if state code is run
* The state code adds extra non-config data
* Therefore, the backend must be started with -- -s to enable the state function
*/
static int _state = 0;
/*! Variable to control upgrade callbacks.
* If set, call test-case for upgrading ietf-interfaces, otherwise call
* auto-upgrade
*/
static int _upgrade = 0;
/* forward */
static int example_stream_timer_setup(clicon_handle h);
/*! This is called on validate (and commit). Check validity of candidate
*/
int
transaction_validate(clicon_handle h,
transaction_data td)
{
// transaction_print(stderr, td);
return 0;
}
/*! This is called on commit. Identify modifications and adjust machine state
*/
int
transaction_commit(clicon_handle h,
transaction_data td)
{
cxobj *target = transaction_target(td); /* wanted XML tree */
cxobj **vec = NULL;
int i;
size_t len;
clicon_debug(1, "%s", __FUNCTION__);
/* Get all added i/fs */
if (xpath_vec_flag(target, "//interface", XML_FLAG_ADD, &vec, &len) < 0)
return -1;
if (debug)
for (i=0; i<len; i++) /* Loop over added i/fs */
xml_print(stdout, vec[i]); /* Print the added interface */
// done:
if (vec)
free(vec);
return 0;
}
/*! Routing example notification timer handler. Here is where the periodic action is
*/
static int
example_stream_timer(int fd,
void *arg)
{
int retval = -1;
clicon_handle h = (clicon_handle)arg;
/* XXX Change to actual netconf notifications and namespace */
if (stream_notify(h, "EXAMPLE", "<event xmlns=\"urn:example:clixon\"><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event>") < 0)
goto done;
if (example_stream_timer_setup(h) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Set up example stream notification timer
*/
static int
example_stream_timer_setup(clicon_handle h)
{
struct timeval t, t1;
gettimeofday(&t, NULL);
t1.tv_sec = 5; t1.tv_usec = 0;
timeradd(&t, &t1, &t);
return event_reg_timeout(t, example_stream_timer, h, "example stream timer");
}
/*! Smallest possible RPC declaration for test
* Yang/XML:
* If the RPC operation invocation succeeded and no output parameters
* are returned, the <rpc-reply> contains a single <ok/> element defined
* in [RFC6241].
*/
static int
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;
}
/*! This will be called as a hook right after the original system copy-config
*/
static int
example_copy_extra(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;
fprintf(stderr, "%s\n", __FUNCTION__);
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
* @param[in] xtop XML tree, <config/> on entry.
* @retval 0 OK
* @retval -1 Error
* @see xmldb_get
* @note this example code returns requires this yang snippet:
container state {
config false;
description "state data for example application";
leaf-list op {
type string;
}
}
* This yang snippet is present in clixon-example.yang for exampl.
*/
int
example_statedata(clicon_handle h,
char *xpath,
cxobj *xstate)
{
int retval = -1;
cxobj **xvec = NULL;
if (!_state)
goto ok;
/* Example of (static) statedata, real code would poll state
* Note this state needs to be accomanied by yang snippet
* above
*/
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;
ok:
retval = 0;
done:
if (xvec)
free(xvec);
return retval;
}
/*! Testcase upgrade function moving interfaces-state to interfaces
* @param[in] h Clicon handle
* @param[in] xn XML tree to be updated
* @param[in] ns Namespace of module (for info)
* @param[in] from From revision on the form YYYYMMDD
* @param[in] to To revision on the form YYYYMMDD (0 not in system)
* @param[in] arg User argument given at rpc_callback_register()
* @param[out] cbret Return xml tree, eg <rpc-reply>..., <rpc-error..
* @retval 1 OK
* @retval 0 Invalid
* @retval -1 Error
* @see clicon_upgrade_cb
* @see test_upgrade_interfaces.sh
* @see upgrade_2016
* This example shows a two-step upgrade where the 2014 function does:
* - Move /if:interfaces-state/if:interface/if:admin-status to
* /if:interfaces/if:interface/
* - Move /if:interfaces-state/if:interface/if:statistics to
* /if:interfaces/if:interface/
* - Rename /interfaces/interface/description to descr
*/
static int
upgrade_2016(clicon_handle h,
cxobj *xt,
char *ns,
uint32_t from,
uint32_t to,
void *arg,
cbuf *cbret)
{
int retval = -1;
yang_spec *yspec;
yang_stmt *ym;
cxobj **vec = NULL;
cxobj *xc;
cxobj *xi; /* xml /interfaces-states/interface node */
cxobj *x;
cxobj *xif; /* xml /interfaces/interface node */
size_t vlen;
int i;
char *name;
/* Get Yang module for this namespace. Note it may not exist (if obsolete) */
yspec = clicon_dbspec_yang(h);
if ((ym = yang_find_module_by_namespace(yspec, ns)) == NULL)
goto ok; /* shouldnt happen */
clicon_debug(1, "%s module %s", __FUNCTION__, ym?ym->ys_argument:"none");
/* Get all XML nodes with that namespace */
if (xml_namespace_vec(h, xt, ns, &vec, &vlen) < 0)
goto done;
for (i=0; i<vlen; i++){
xc = vec[i];
/* Iterate through interfaces-state */
if (strcmp(xml_name(xc),"interfaces-state") == 0){
/* Note you cannot delete or move xml objects directly under xc
* in the loop (eg xi objects) but you CAN move children of xi
*/
xi = NULL;
while ((xi = xml_child_each(xc, xi, CX_ELMNT)) != NULL) {
if (strcmp(xml_name(xi), "interface"))
continue;
if ((name = xml_find_body(xi, "name")) == NULL)
continue; /* shouldnt happen */
/* Get corresponding /interfaces/interface entry */
xif = xpath_first(xt, "/interfaces/interface[name=\"%s\"]", name);
/* - Move /if:interfaces-state/if:interface/if:admin-status to
* /if:interfaces/if:interface/ */
if ((x = xml_find(xi, "admin-status")) != NULL && xif){
if (xml_addsub(xif, x) < 0)
goto done;
}
/* - Move /if:interfaces-state/if:interface/if:statistics to
* /if:interfaces/if:interface/*/
if ((x = xml_find(xi, "statistics")) != NULL){
if (xml_addsub(xif, x) < 0)
goto done;
}
}
}
else if (strcmp(xml_name(xc),"interfaces") == 0){
/* Iterate through interfaces */
xi = NULL;
while ((xi = xml_child_each(xc, xi, CX_ELMNT)) != NULL) {
if (strcmp(xml_name(xi), "interface"))
continue;
/* Rename /interfaces/interface/description to descr */
if ((x = xml_find(xi, "description")) != NULL)
if (xml_name_set(x, "descr") < 0)
goto done;
}
}
}
ok:
retval = 1;
done:
if (vec)
free(vec);
return retval;
}
/*! Testcase upgrade function removing interfaces-state
* @param[in] h Clicon handle
* @param[in] xn XML tree to be updated
* @param[in] ns Namespace of module (for info)
* @param[in] from From revision on the form YYYYMMDD
* @param[in] to To revision on the form YYYYMMDD (0 not in system)
* @param[in] arg User argument given at rpc_callback_register()
* @param[out] cbret Return xml tree, eg <rpc-reply>..., <rpc-error..
* @retval 1 OK
* @retval 0 Invalid
* @retval -1 Error
* @see clicon_upgrade_cb
* @see test_upgrade_interfaces.sh
* @see upgrade_2016
* The 2016 function does:
* - Delete /if:interfaces-state
* - Wrap /interfaces/interface/descr to /interfaces/interface/docs/descr
* - Change type /interfaces/interface/statistics/in-octets to decimal64 with
* fraction-digits 3 and divide all values with 1000
*/
static int
upgrade_2018(clicon_handle h,
cxobj *xt,
char *ns,
uint32_t from,
uint32_t to,
void *arg,
cbuf *cbret)
{
int retval = -1;
yang_spec *yspec;
yang_stmt *ym;
cxobj **vec = NULL;
cxobj *xc;
cxobj *xi;
cxobj *x;
cxobj *xb;
size_t vlen;
int i;
/* Get Yang module for this namespace. Note it may not exist (if obsolete) */
yspec = clicon_dbspec_yang(h);
if ((ym = yang_find_module_by_namespace(yspec, ns)) == NULL)
goto ok; /* shouldnt happen */
clicon_debug(1, "%s module %s", __FUNCTION__, ym?ym->ys_argument:"none");
/* Get all XML nodes with that namespace */
if (xml_namespace_vec(h, xt, ns, &vec, &vlen) < 0)
goto done;
for (i=0; i<vlen; i++){
xc = vec[i];
/* Delete /if:interfaces-state */
if (strcmp(xml_name(xc), "interfaces-state") == 0)
xml_purge(xc);
/* Iterate through interfaces */
else if (strcmp(xml_name(xc),"interfaces") == 0){
/* Iterate through interfaces */
xi = NULL;
while ((xi = xml_child_each(xc, xi, CX_ELMNT)) != NULL) {
if (strcmp(xml_name(xi), "interface"))
continue;
/* Wrap /interfaces/interface/descr to /interfaces/interface/docs/descr */
if ((x = xml_find(xi, "descr")) != NULL)
if (xml_wrap(x, "docs") < 0)
goto done;
/* Change type /interfaces/interface/statistics/in-octets to
* decimal64 with fraction-digits 3 and divide values with 1000
*/
if ((x = xpath_first(xi, "statistics/in-octets")) != NULL){
if ((xb = xml_body_get(x)) != NULL){
uint64_t u64;
cbuf *cb = cbuf_new();
parse_uint64(xml_value(xb), &u64, NULL);
cprintf(cb, "%" PRIu64 ".%03d", u64/1000, (int)(u64%1000));
xml_value_set(xb, cbuf_get(cb));
cbuf_free(cb);
}
}
}
}
}
ok:
retval = 1;
done:
if (vec)
free(vec);
return retval;
}
/*! Plugin state reset. Add xml or set state in backend machine.
* Called in each backend plugin. plugin_reset is called after all plugins
* have been initialized. This give the application a chance to reset
* system state back to a base state.
* This is generally done when a system boots up to
* make sure the initial system state is well defined. This can be creating
* default configuration files for various daemons, set interface flags etc.
* @param[in] h Clicon handle
* @param[in] db Name of database. Not may be other than "running"
* In this example, a loopback interface is added
* @note This assumes example yang with interfaces/interface
*/
int
example_reset(clicon_handle h,
const char *db)
{
int retval = -1;
cxobj *xt = NULL;
int ret;
cbuf *cbret = NULL;
if (!_reset)
goto ok; /* Note not enabled by default */
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 first child */
if (xml_rootchild(xt, 0, &xt) < 0)
goto done;
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;
}
ok:
retval = 0;
done:
if (cbret)
cbuf_free(cbret);
if (xt != NULL)
xml_free(xt);
return retval;
}
/*! Plugin start.
* @param[in] h Clicon handle
*
* plugin_start is called once everything has been initialized, right before
* the main event loop is entered.
*/
int
example_start(clicon_handle h)
{
return 0;
}
int
example_exit(clicon_handle h)
{
return 0;
}
clixon_plugin_api *clixon_plugin_init(clicon_handle h);
static clixon_plugin_api api = {
"example", /* name */
clixon_plugin_init, /* init - must be called clixon_plugin_init */
example_start, /* start */
example_exit, /* exit */
.ca_reset=example_reset, /* reset */
.ca_statedata=example_statedata, /* statedata */
.ca_trans_begin=NULL, /* trans begin */
.ca_trans_validate=transaction_validate,/* trans validate */
.ca_trans_complete=NULL, /* trans complete */
.ca_trans_commit=transaction_commit, /* trans commit */
.ca_trans_end=NULL, /* trans end */
.ca_trans_abort=NULL /* trans abort */
};
/*! Backend plugin initialization
* @param[in] h Clixon handle
* @retval NULL Error with clicon_err set
* @retval api Pointer to API struct
* In this example, you can pass -r, -s, -u to control the behaviour, mainly
* for use in the test suites.
*/
clixon_plugin_api *
clixon_plugin_init(clicon_handle h)
{
struct timeval retention = {0,0};
int argc; /* command-line options (after --) */
char **argv;
char c;
clicon_debug(1, "%s backend", __FUNCTION__);
/* Get user command-line options (after --) */
if (clicon_argv_get(h, &argc, &argv) < 0)
goto done;
opterr = 0;
optind = 1;
while ((c = getopt(argc, argv, "rsu")) != -1)
switch (c) {
case 'r':
_reset = 1;
break;
case 's':
_state = 1;
break;
case 'u':
_upgrade = 1;
break;
}
/* Example stream initialization:
* 1) Register EXAMPLE stream
* 2) setup timer for notifications, so something happens on stream
* 3) setup stream callbacks for notification to push channel
*/
if (clicon_option_exists(h, "CLICON_STREAM_RETENTION"))
retention.tv_sec = clicon_option_int(h, "CLICON_STREAM_RETENTION");
if (stream_add(h, "EXAMPLE", "Example event stream", 1, &retention) < 0)
goto done;
/* Enable nchan pub/sub streams
* assumes: CLIXON_PUBLISH_STREAMS, eg configure --enable-publish
*/
if (clicon_option_exists(h, "CLICON_STREAM_PUB") &&
stream_publish(h, "EXAMPLE") < 0)
goto done;
if (example_stream_timer_setup(h) < 0)
goto done;
/* Register callback for routing rpc calls
*/
/* From example.yang (clicon) */
if (rpc_callback_register(h, empty_rpc,
NULL,
"urn:example:clixon",
"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,
"urn:example:clixon",
"optional"/* 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,
"urn:example:clixon",
"example"/* Xml tag when callback is made */
) < 0)
goto done;
/* Called after the regular system copy_config callback */
if (rpc_callback_register(h, example_copy_extra,
NULL,
"urn:ietf:params:xml:ns:netconf:base:1.0",
"copy-config"
) < 0)
goto done;
/* Upgrade callback: if you start the backend with -- -u you will get the
* test interface example. Otherwise the auto-upgrade feature is enabled.
*/
if (_upgrade){
if (upgrade_callback_register(h, upgrade_2016, "urn:example:interfaces", 20140508, 20160101, NULL) < 0)
goto done;
if (upgrade_callback_register(h, upgrade_2018, "urn:example:interfaces", 20160101, 20180220, NULL) < 0)
goto done;
}
else
if (upgrade_callback_register(h, xml_changelog_upgrade, NULL, 0, 0, NULL) < 0)
goto done;
/* Return plugin API */
return &api;
done:
return NULL;
}

View file

@ -0,0 +1,125 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 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 *****
*
* IETF yang routing example
* Secondary backend for testing more than one backend plugin
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/syslog.h>
/* clicon */
#include <cligen/cligen.h>
/* Clicon library functions. */
#include <clixon/clixon.h>
/* These include signatures for plugin and transaction callbacks. */
#include <clixon/clixon_backend.h>
/*! Called to get NACM state data
* @param[in] h Clicon handle
* @param[in] xpath String with XPATH syntax. or NULL for all
* @param[in] xtop XML tree, <config/> on entry.
* @retval 0 OK
* @retval -1 Error
* @see xmldb_get
* @note this example code returns a static statedata used in testing.
* Real code would poll state
*/
int
nacm_statedata(clicon_handle h,
char *xpath,
cxobj *xstate)
{
int retval = -1;
cxobj **xvec = NULL;
/* Example of (static) statedata, real code would poll state */
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>"
"</nacm>", NULL, &xstate) < 0)
goto done;
retval = 0;
done:
if (xvec)
free(xvec);
return retval;
}
int
plugin_start(clicon_handle h)
{
return 0;
}
clixon_plugin_api *clixon_plugin_init(clicon_handle h);
static clixon_plugin_api api = {
"nacm", /* name */ /*--- Common fields. ---*/
clixon_plugin_init, /* init */
plugin_start, /* start */
NULL, /* exit */
.ca_reset=NULL, /* reset */
.ca_statedata=nacm_statedata, /* statedata */
};
/*! Backend plugin initialization
* @param[in] h Clixon handle
* @retval NULL Error with clicon_err set
* @retval api Pointer to API struct
*/
clixon_plugin_api *
clixon_plugin_init(clicon_handle h)
{
char *nacm_mode;
clicon_debug(1, "%s backend nacm", __FUNCTION__);
nacm_mode = clicon_option_str(h, "CLICON_NACM_MODE");
if (nacm_mode==NULL || strcmp(nacm_mode, "disabled") == 0){
clicon_log(LOG_DEBUG, "%s CLICON_NACM_MODE not enabled: example nacm module disabled", __FUNCTION__);
return NULL;
}
return &api;
}

169
example/main/example_cli.c Normal file
View file

@ -0,0 +1,169 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 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 *****
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <syslog.h>
#include <unistd.h>
#include <assert.h>
#include <math.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/param.h>
#include <netinet/in.h>
#include <fnmatch.h> /* matching strings */
#include <signal.h> /* matching strings */
/* clicon */
#include <cligen/cligen.h>
#include <clixon/clixon.h>
#include <clixon/clixon_cli.h>
/*! Example cli function */
int
mycallback(clicon_handle h, cvec *cvv, cvec *argv)
{
int retval = -1;
cxobj *xret = NULL;
cg_var *myvar;
/* Access cligen callback variables */
myvar = cvec_find(cvv, "var"); /* get a cligen variable from vector */
fprintf(stderr, "%s: %d\n", __FUNCTION__, cv_int32_get(myvar)); /* get int value */
fprintf(stderr, "arg = %s\n", cv_string_get(cvec_i(argv,0))); /* get string value */
/* Show eth0 interfaces config using XPATH */
if (clicon_rpc_get_config(h, "running",
"/interfaces/interface[name='eth0']",
&xret) < 0)
goto done;
xml_print(stdout, xret);
retval = 0;
done:
if (xret)
xml_free(xret);
return retval;
}
/*! Example "downcall", ie initiate an RPC to the backend */
int
example_client_rpc(clicon_handle h,
cvec *cvv,
cvec *argv)
{
int retval = -1;
cg_var *cva;
cxobj *xtop = NULL;
cxobj *xrpc;
cxobj *xret = NULL;
cxobj *xerr;
/* User supplied variable in CLI command */
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(cva)) < 0)
goto done;
/* Skip top-level */
xrpc = xml_child_i(xtop, 0);
/* Send to backend */
if (clicon_rpc_netconf_xml(h, xrpc, &xret, NULL) < 0)
goto done;
if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){
clicon_rpc_generate_error("Get configuration", xerr);
goto done;
}
/* Print result */
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)
xml_free(xret);
if (xtop)
xml_free(xtop);
return retval;
}
static clixon_plugin_api api = {
"example", /* name */
clixon_plugin_init, /* init */
NULL, /* start */
NULL, /* exit */
.ca_prompt=NULL, /* cli_prompthook_t */
.ca_suspend=NULL, /* cligen_susp_cb_t */
.ca_interrupt=NULL, /* cligen_interrupt_cb_t */
};
/*! CLI plugin initialization
* @param[in] h Clixon handle
* @retval NULL Error with clicon_err set
* @retval api Pointer to API struct
*/
clixon_plugin_api *
clixon_plugin_init(clicon_handle h)
{
struct timeval tv;
gettimeofday(&tv, NULL);
srandom(tv.tv_usec);
return &api;
}
/*! Translate function from an original value to a new.
* In this case, assume string and increment characters, eg HAL->IBM
*/
int
incstr(cligen_handle h,
cg_var *cv)
{
char *str;
int i;
if (cv_type_get(cv) != CGV_STRING)
return 0;
str = cv_string_get(cv);
for (i=0; i<strlen(str); i++)
str[i]++;
return 0;
}

View file

@ -0,0 +1,69 @@
# Common CLI syntax for both server and PMNode operatio mode
CLICON_MODE="example";
CLICON_PROMPT="%U@%H> ";
CLICON_PLUGIN="example_cli";
# Translate variable "value" by incrementing its characters
translate value (<value:string translate:incstr()>),cli_set("/translate/value");
# Note, when switching to PT, change datamodel to only @datamodel
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();
quit("Quit"), cli_quit();
delete("Delete a configuration item") all("Delete whole candidate configuration"), delete_all("candidate");
startup("Store running as startup config"), db_copy("running", "startup");
no("Negate or remove") debug("Debugging parts of the system"), cli_debug_cli((int32)0);
debug("Debugging parts of the system"), cli_debug_cli((int32)1);{
level("Set debug level: 1..n") <level:int32>("Set debug level (0..n)"), cli_debug_backend();
}
copy("Copy and create a new object") {
interface("Copy interface"){
<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();
compare("Compare running and candidate"), compare_dbs((int32)1);
show("Show a particular state of the system"){
xpath("Show configuration") <xpath:string>("XPATH expression"), show_conf_xpath("candidate");
version("Show version"), cli_show_version("candidate", "text", "/");
compare("Compare candidate and running databases"), compare_dbs((int32)0);{
xml("Show comparison in xml"), compare_dbs((int32)0);
text("Show comparison in text"), compare_dbs((int32)1);
}
configuration("Show configuration"), cli_show_config("candidate", "text", "/");{
xml("Show configuration as XML"), cli_show_config("candidate", "xml", "/");{
@datamodel, cli_show_auto("candidate", "xml");
}
cli("Show configuration as CLI commands"), cli_show_config("candidate", "cli", "/");{
@datamodel, cli_show_auto("candidate", "cli");
}
netconf("Show configuration as netconf edit-config operation"), cli_show_config("candidate", "netconf", "/");{
@datamodel, cli_show_auto("candidate", "netconf");
}
text("Show configuration as text"), cli_show_config("candidate","text","/");{
@datamodel, cli_show_auto("candidate", "text");
}
json("Show configuration as JSON"), cli_show_config("candidate", "json", "/");{
@datamodel, cli_show_auto("candidate", "json");
}
}
}
save("Save candidate configuration to XML file") <filename:string>("Filename (local filename)"), save_config_file("candidate","filename");
load("Load configuration from XML file") <filename:string>("Filename (local filename)"),load_config_file("filename", "replace");{
replace("Replace candidate with file contents"), load_config_file("filename", "replace");
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("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");
unlock,cli_unlock("candidate");

View file

@ -0,0 +1,123 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 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 *****
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <syslog.h>
#include <unistd.h>
#include <assert.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <cligen/cligen.h>
#include <clixon/clixon.h>
#include <clixon/clixon_netconf.h>
/*! Plugin start
* Called once everything has been initialized, right before
* the main event loop is entered.
*/
int
plugin_start(clicon_handle h)
{
return 0;
}
int
plugin_exit(clicon_handle h)
{
return 0;
}
/*! Local example netconf rpc callback
*/
int netconf_client_rpc(clicon_handle h,
cxobj *xe,
cbuf *cbret,
void *arg,
void *regarg)
{
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;
}
clixon_plugin_api * clixon_plugin_init(clicon_handle h);
static struct clixon_plugin_api api = {
"example", /* name */
clixon_plugin_init, /* init */
plugin_start, /* start */
plugin_exit /* exit */
};
/*! Netconf plugin initialization
* @param[in] h Clixon handle
* @retval NULL Error with clicon_err set
* @retval api Pointer to API struct
*/
clixon_plugin_api *
clixon_plugin_init(clicon_handle h)
{
clicon_debug(1, "%s restconf", __FUNCTION__);
/* Register local netconf rpc client (note not backend rpc client) */
if (rpc_callback_register(h, netconf_client_rpc, NULL,
"urn:example:clixon", "client-rpc") < 0)
return NULL;
return &api;
}

View file

@ -0,0 +1,352 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 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 *****
*
*/
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <signal.h>
#include <fcgiapp.h>
#include <curl/curl.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include <clixon/clixon.h>
#include <clixon/clixon_restconf.h>
static const char Base64[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const char Pad64 = '=';
/* Use http basic auth. Set by starting restonf with:
* clixon_restconf ... -- -a
*/
static int basic_auth = 0;
/* skips all whitespace anywhere.
converts characters, four at a time, starting at (or after)
src from base - 64 numbers into three 8 bit bytes in the target area.
it returns the number of data bytes stored at the target, or -1 on error.
@note what is copyright of this?
*/
int
b64_decode(const char *src,
char *target,
size_t targsize)
{
int tarindex, state, ch;
char *pos;
state = 0;
tarindex = 0;
while ((ch = *src++) != '\0') {
if (isspace(ch)) /* Skip whitespace anywhere. */
continue;
if (ch == Pad64)
break;
pos = strchr(Base64, ch);
if (pos == 0) /* A non-base64 character. */
return (-1);
switch (state) {
case 0:
if (target) {
if ((size_t)tarindex >= targsize)
return (-1);
target[tarindex] = (pos - Base64) << 2;
}
state = 1;
break;
case 1:
if (target) {
if ((size_t)tarindex + 1 >= targsize)
return (-1);
target[tarindex] |= (pos - Base64) >> 4;
target[tarindex+1] = ((pos - Base64) & 0x0f)
<< 4 ;
}
tarindex++;
state = 2;
break;
case 2:
if (target) {
if ((size_t)tarindex + 1 >= targsize)
return (-1);
target[tarindex] |= (pos - Base64) >> 2;
target[tarindex+1] = ((pos - Base64) & 0x03)
<< 6;
}
tarindex++;
state = 3;
break;
case 3:
if (target) {
if ((size_t)tarindex >= targsize)
return (-1);
target[tarindex] |= (pos - Base64);
}
tarindex++;
state = 0;
break;
default:
return -1;
}
}
/*
* We are done decoding Base-64 chars. Let's see if we ended
* on a byte boundary, and/or with erroneous trailing characters.
*/
if (ch == Pad64) { /* We got a pad char. */
ch = *src++; /* Skip it, get next. */
switch (state) {
case 0: /* Invalid = in first position */
case 1: /* Invalid = in second position */
return (-1);
case 2: /* Valid, means one byte of info */
/* Skip any number of spaces. */
for ((void)NULL; ch != '\0'; ch = *src++)
if (!isspace(ch))
break;
/* Make sure there is another trailing = sign. */
if (ch != Pad64)
return (-1);
ch = *src++; /* Skip the = */
/* Fall through to "single trailing =" case. */
/* FALLTHROUGH */
case 3: /* Valid, means two bytes of info */
/*
* We know this char is an =. Is there anything but
* whitespace after it?
*/
for ((void)NULL; ch != '\0'; ch = *src++)
if (!isspace(ch))
return (-1);
/*
* Now make sure for cases 2 and 3 that the "extra"
* bits that slopped past the last full byte were
* zeros. If we don't check them, they become a
* subliminal channel.
*/
if (target && target[tarindex] != 0)
return (-1);
}
} else {
/*
* We ended by seeing the end of the string. Make sure we
* have no partial bytes lying around.
*/
if (state != 0)
return (-1);
}
return (tarindex);
}
/*! Process a rest request that requires (cookie) "authentication"
* @param[in] h Clixon handle
* @param[in] arg Argument. Here: Fastcgi request handle
* @retval -1 Fatal error
* @retval 0 Unauth
* @retval 1 Auth
* @note: Three hardwired users: andy, wilma, guest w password "bar".
* Enabled by passing -- -a to the main function
*/
int
example_restconf_credentials(clicon_handle h,
void *arg)
{
int retval = -1;
FCGX_Request *r = (FCGX_Request *)arg;
cxobj *xt = NULL;
char *auth;
char *user = NULL;
char *passwd;
char *passwd2 = "";
size_t authlen;
cbuf *cb = NULL;
int ret;
/* HTTP basic authentication not enabled, pass with user "none" */
if (basic_auth==0)
goto ok;
/* At this point in the code we must use HTTP basic authentication */
if ((auth = FCGX_GetParam("HTTP_AUTHORIZATION", r->envp)) == NULL)
goto fail;
if (strlen(auth) < strlen("Basic "))
goto fail;
if (strncmp("Basic ", auth, strlen("Basic ")))
goto fail;
auth += strlen("Basic ");
authlen = strlen(auth)*2;
if ((user = malloc(authlen)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(user, 0, authlen);
if ((ret = b64_decode(auth, user, authlen)) < 0)
goto done;
/* auth string is on the format user:passwd */
if ((passwd = index(user,':')) == NULL)
goto fail;
*passwd = '\0';
passwd++;
clicon_debug(1, "%s http user:%s passwd:%s", __FUNCTION__, user, passwd);
/* Here get auth sub-tree whjere all the users are */
if ((cb = cbuf_new()) == NULL)
goto done;
/* 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))
goto fail;
retval = 1;
clicon_debug(1, "%s user:%s", __FUNCTION__, user);
if (clicon_username_set(h, user) < 0)
goto done;
ok: /* authenticated */
retval = 1;
done: /* error */
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (user)
free(user);
if (cb)
cbuf_free(cb);
if (xt)
xml_free(xt);
return retval;
fail: /* unauthenticated */
retval = 0;
goto done;
}
/*! Local example restconf rpc callback
*/
int
restconf_client_rpc(clicon_handle h,
cxobj *xe,
cbuf *cbret,
void *arg,
void *regarg)
{
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
*/
int
example_restconf_start(clicon_handle h)
{
clicon_debug(1, "%s", __FUNCTION__);
return 0;
}
clixon_plugin_api * clixon_plugin_init(clicon_handle h);
static clixon_plugin_api api = {
"example", /* name */
clixon_plugin_init, /* init */
example_restconf_start,/* start */
NULL, /* exit */
.ca_auth=example_restconf_credentials /* auth */
};
/*! Restconf plugin initialization
* @param[in] h Clixon handle
* @retval NULL Error with clicon_err set
* @retval api Pointer to API struct
* Arguments are argc/argv after --
* Currently defined: -a enable http basic authentication
* @note There are three hardwired users andy, wilma and guest from RFC8341 A.1
*/
clixon_plugin_api *
clixon_plugin_init(clicon_handle h)
{
int argc; /* command-line options (after --) */
char **argv = NULL;
char c;
clicon_debug(1, "%s restconf", __FUNCTION__);
/* Get user command-line options (after --) */
if (clicon_argv_get(h, &argc, &argv) < 0)
return NULL;
opterr = 0;
optind = 1;
while ((c = getopt(argc, argv, "a")) != -1)
switch (c) {
case 'a':
basic_auth = 1;
break;
default:
break;
}
/* Register local netconf rpc client (note not backend rpc client) */
if (rpc_callback_register(h, restconf_client_rpc, NULL, "urn:example:clixon", "client-rpc") < 0)
return NULL;
return &api;
}

View file

@ -0,0 +1,13 @@
[Unit]
Description=Starts and stops a clixon example service on this system
Wants=example_restconf.service
[Service]
Type=forking
User=root
RestartSec=60
Restart=on-failure
ExecStart=/usr/local/sbin/clixon_backend -s running -f /usr/local/etc/example.xml
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,14 @@
[Unit]
Description=Starts and stops an example clixon restconf service on this system
Wants=example.service
After=example.service
[Service]
Type=simple
User=www-data
WorkingDirectory=/www-data
Restart=on-failure
ExecStart=/www-data/clixon_restconf -f /usr/local/etc/example.xml
[Install]
WantedBy=multi-user.target