Merging upstream version 3.5.5 (Closes: #1098233).
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
c86ae7dcba
commit
6af28b7e8e
144 changed files with 43534 additions and 11497 deletions
21
examples/CMakeLists.txt
Normal file
21
examples/CMakeLists.txt
Normal file
|
@ -0,0 +1,21 @@
|
|||
if(NOT LIBNETCONF2_VERSION)
|
||||
message(FATAL_ERROR "Please use the root CMakeLists file instead.")
|
||||
endif()
|
||||
|
||||
# correct RPATH usage on OS X
|
||||
set(CMAKE_MACOSX_RPATH TRUE)
|
||||
|
||||
# include all the library headers
|
||||
include_directories(BEFORE "${CMAKE_SOURCE_DIR}/src")
|
||||
|
||||
# generate example header
|
||||
include_directories(BEFORE ${CMAKE_CURRENT_BINARY_DIR})
|
||||
configure_file(example.h.in example.h)
|
||||
|
||||
# list of all the examples
|
||||
set(examples server client)
|
||||
|
||||
foreach(app_name IN LISTS examples)
|
||||
add_executable(${app_name} ${app_name}.c)
|
||||
target_link_libraries(${app_name} netconf2)
|
||||
endforeach(app_name)
|
134
examples/README.md
Normal file
134
examples/README.md
Normal file
|
@ -0,0 +1,134 @@
|
|||
# libnetconf2 - examples
|
||||
There are two examples `server` and `client` demonstrating a simple NETCONF server and client using libnetconf2 C library. This is an extensively documented example, which is trying to showcase the key parts of the libnetconf2 library in a simple way. The library configuration is kept to the minimum just to achieve basic functionality. Two types of transport are supported in this example: _UNIX Socket_ and _SSH_. Both examples have the `-h` option that displays their usage.
|
||||
|
||||
## Server
|
||||
The example server provides `ietf-yang-library` state data that are returned as a reply to `get` RPC. In case an XPath filter is used it is properly applied on these data. If some unsupported parameters are specified, the server replies with a NETCONF error.
|
||||
|
||||
### Server Configuration
|
||||
The server's default configuration can be found in the `config.json` file. The YANG data stored in this file define two SSH endpoints - they differ in port and in how clients get authenticated.
|
||||
You can modify this configuration in any way you want, however, configuring the server may fail if the configuration is not valid.
|
||||
|
||||
## Example usage
|
||||
### Server
|
||||
First start the server:
|
||||
```
|
||||
$ server -u ./example-socket
|
||||
```
|
||||
The server will be started and configured per YANG data stored in the file `config.json`.
|
||||
Two SSH endpoints with the addresses `127.0.0.1:10000` and `127.0.0.1:10001` will start listening for new connections.
|
||||
This first endpoint has a single user that can authenticate with a password (which is set to `admin` by default).
|
||||
The second endpoint has a single user that can authenticate with a publickey (the asymmetric key pair used is stored in `admin_key` and `admin_key.pub`).
|
||||
The `-u` option specifies that a UNIX socket endpoint will be created and `./example-socket` is the path to where the socket will be listening.
|
||||
|
||||
### Client
|
||||
#### UNIX socket
|
||||
After the server has been run, in another terminal instance, with the default configuration:
|
||||
```
|
||||
$ client -u ./example-socket get "/ietf-yang-library:yang-library/module-set/module[name='ietf-netconf']"
|
||||
```
|
||||
In this case, `-u` means that a connection to an UNIX socket will be attempted and a path to the socket needs to be specified.
|
||||
The `get` parameter is the name of the RPC and `/ietf-yang-library:yang-library/module-set/module[name='ietf-netconf']` is the RPC's optional XPath filter.
|
||||
|
||||
##### Server output
|
||||
```
|
||||
Listening for new connections! <-- server created
|
||||
Connection established <-- client joined
|
||||
Received RPC:
|
||||
get-schema <-- name of the RPC
|
||||
identifier = "ietf-datastores" <-- name of the requested YANG module
|
||||
format = "ietf-netconf-monitoring:yang" <-- format of the requested YANG module
|
||||
Received RPC:
|
||||
get-schema
|
||||
identifier = "ietf-netconf-nmda"
|
||||
format = "ietf-netconf-monitoring:yang"
|
||||
Received RPC:
|
||||
get
|
||||
filter = "(null)" <-- XPath filter has no value in the anyxml
|
||||
type = "xpath" <-- defines XPath filter type (which may also be subtree)
|
||||
select = "/ietf-yang-library:*" <-- contains a string representing the XPath filter
|
||||
Received RPC:
|
||||
get
|
||||
filter = "(null)"
|
||||
type = "xpath"
|
||||
select = "/ietf-yang-library:yang-library/module-set/module[name='ietf-netconf']"
|
||||
Received RPC:
|
||||
close-session <-- communication with client terminated
|
||||
```
|
||||
The server received five supported RPCs. First, the client attempts to obtain basic YANG modules using `get-schema`. Then, it retrieves all the `ietf-yang-library` data to be used for creating its context, which should ideally be the same as that of the server. Next the example `get` RPC is received and lastly `close-session` RPC terminates the connection.
|
||||
|
||||
##### Client output
|
||||
```
|
||||
<get xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
|
||||
<data>
|
||||
<yang-library xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library">
|
||||
<module-set>
|
||||
<name>complete</name>
|
||||
<module>
|
||||
<name>ietf-netconf</name> <-- requested name of a module
|
||||
<revision>2013-09-29</revision>
|
||||
<namespace>urn:ietf:params:xml:ns:netconf:base:1.0</namespace>
|
||||
<location>file:///home/roman/libnetconf2/modules/ietf-netconf@2013-09-29.yang</location>
|
||||
<feature>writable-running</feature>
|
||||
<feature>candidate</feature>
|
||||
<feature>confirmed-commit</feature>
|
||||
<feature>rollback-on-error</feature>
|
||||
<feature>validate</feature>
|
||||
<feature>startup</feature>
|
||||
<feature>url</feature>
|
||||
<feature>xpath</feature>
|
||||
</module>
|
||||
</module-set>
|
||||
</yang-library>
|
||||
</data>
|
||||
</get>
|
||||
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="4"/>
|
||||
```
|
||||
The client received a single `ietf-yang-library` module based on the used filter.
|
||||
|
||||
#### SSH
|
||||
After the server has been run, in another terminal instance, with the default configuration:
|
||||
```
|
||||
$ client -p 10000 get-config startup
|
||||
```
|
||||
In this case, `-p 10000` is the port to connect to. By default the endpoint with this port has a single authorized client that needs to authenticate with a password.
|
||||
The parameter `get-config` is the name of the RPC and `startup` is the source datastore for the retrieved data of the get-config RPC.
|
||||
|
||||
##### Server output
|
||||
```
|
||||
Using SSH!
|
||||
Connection established
|
||||
Received RPC:
|
||||
get-schema
|
||||
identifier = "ietf-datastores"
|
||||
format = "ietf-netconf-monitoring:yang"
|
||||
Received RPC:
|
||||
get-schema
|
||||
identifier = "ietf-netconf-nmda"
|
||||
format = "ietf-netconf-monitoring:yang"
|
||||
Received RPC:
|
||||
get
|
||||
filter = "(null)"
|
||||
type = "xpath"
|
||||
select = "/ietf-yang-library:*"
|
||||
Received RPC:
|
||||
get-config <-- name of the RPC
|
||||
candidate = "" <-- source datastore, which is of type empty
|
||||
Received RPC:
|
||||
close-session
|
||||
```
|
||||
|
||||
##### Client output
|
||||
```
|
||||
admin@127.0.0.1 password: <-- prompts for password, type in 'admin'
|
||||
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="4">
|
||||
<ok/>
|
||||
</rpc-reply>
|
||||
```
|
||||
No `startup` configuration is returned, because the example server lacks this functionality.
|
||||
The _username_ in the `example.h` header file. The _password_ is located in `config.json`.
|
||||
|
||||
If you wish to connect to the SSH public key endpoint, you need to specify its port and the asymmetric key pair to use.
|
||||
By default the command to connect would look like so:
|
||||
```
|
||||
$ client -p 10001 -P ~/libnetconf2/examples/admin_key.pub -i ~/libnetconf2/examples/admin_key get
|
||||
```
|
7
examples/admin_key
Normal file
7
examples/admin_key
Normal file
|
@ -0,0 +1,7 @@
|
|||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACDq+Oq6bYOgbFoTtSTKJrod3LgmJnrjuiXzlD7P2Dt+cAAAAJC1rL1gtay9
|
||||
YAAAAAtzc2gtZWQyNTUxOQAAACDq+Oq6bYOgbFoTtSTKJrod3LgmJnrjuiXzlD7P2Dt+cA
|
||||
AAAEAQm84SEphEUZEbuCRmXrMcYyv70wNEVziE/SbBC6+trOr46rptg6BsWhO1JMomuh3c
|
||||
uCYmeuO6JfOUPs/YO35wAAAADXJvbWFuQHBjdmFza28=
|
||||
-----END OPENSSH PRIVATE KEY-----
|
1
examples/admin_key.pub
Normal file
1
examples/admin_key.pub
Normal file
|
@ -0,0 +1 @@
|
|||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOr46rptg6BsWhO1JMomuh3cuCYmeuO6JfOUPs/YO35w test@libnetconf2
|
266
examples/client.c
Normal file
266
examples/client.c
Normal file
|
@ -0,0 +1,266 @@
|
|||
/**
|
||||
* @file client.c
|
||||
* @author Roman Janota <xjanot04@fit.vutbr.cz>
|
||||
* @brief libnetconf2 client example
|
||||
*
|
||||
* @copyright
|
||||
* Copyright (c) 2022 CESNET, z.s.p.o.
|
||||
*
|
||||
* This source code is licensed under BSD 3-Clause License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "example.h"
|
||||
|
||||
#include <getopt.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <libyang/libyang.h>
|
||||
|
||||
#include "log.h"
|
||||
#include "messages_client.h"
|
||||
#include "netconf.h"
|
||||
#include "session_client.h"
|
||||
#include "session_client_ch.h"
|
||||
|
||||
static void
|
||||
help_print()
|
||||
{
|
||||
printf("Example usage:\n"
|
||||
" client get\n"
|
||||
"\n"
|
||||
" Available options:\n"
|
||||
" -h, --help\t \tPrint usage help.\n"
|
||||
" -p, --port\t\t<port>\tSpecify the port to connect to.\n"
|
||||
" -u, --unix-path\t<path>\tConnect to a UNIX socket located at <path>.\n"
|
||||
" -P, --ssh-pubkey\t<path>\tSet the path to an SSH Public key.\n"
|
||||
" -i, --ssh-privkey\t<path>\tSet the path to an SSH Private key.\n\n"
|
||||
" Available RPCs:\n"
|
||||
" get [xpath-filter]\t\t\t\t\t send a <get> RPC with optional XPath filter\n"
|
||||
" get-config [datastore] [xpath-filter]\t\t send a <get-config> RPC with optional XPath filter and datastore, the default datastore is \"running\" \n\n");
|
||||
}
|
||||
|
||||
static enum NC_DATASTORE_TYPE
|
||||
string2datastore(const char *str)
|
||||
{
|
||||
if (!str) {
|
||||
return NC_DATASTORE_RUNNING;
|
||||
}
|
||||
|
||||
if (!strcmp(str, "candidate")) {
|
||||
return NC_DATASTORE_CANDIDATE;
|
||||
} else if (!strcmp(str, "running")) {
|
||||
return NC_DATASTORE_RUNNING;
|
||||
} else if (!strcmp(str, "startup")) {
|
||||
return NC_DATASTORE_STARTUP;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
send_rpc(struct nc_session *session, NC_RPC_TYPE rpc_type, const char *param1, const char *param2)
|
||||
{
|
||||
enum NC_DATASTORE_TYPE datastore;
|
||||
int r = 0, rc = 0;
|
||||
uint64_t msg_id = 0;
|
||||
struct lyd_node *envp = NULL, *op = NULL;
|
||||
struct nc_rpc *rpc = NULL;
|
||||
|
||||
/* decide which type of RPC to send */
|
||||
switch (rpc_type) {
|
||||
case NC_RPC_GET:
|
||||
/* create get RPC with an optional filter */
|
||||
rpc = nc_rpc_get(param1, NC_WD_UNKNOWN, NC_PARAMTYPE_CONST);
|
||||
break;
|
||||
|
||||
case NC_RPC_GETCONFIG:
|
||||
/* create get-config RPC with a source datastore and an optional filter */
|
||||
datastore = string2datastore(param1);
|
||||
if (!datastore) {
|
||||
ERR_MSG_CLEANUP("Invalid name of a datastore. Use candidate, running, startup or neither.\n");
|
||||
}
|
||||
rpc = nc_rpc_getconfig(datastore, param2, NC_WD_UNKNOWN, NC_PARAMTYPE_CONST);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!rpc) {
|
||||
ERR_MSG_CLEANUP("Error while creating a RPC\n");
|
||||
}
|
||||
|
||||
/* send the RPC on the session and remember NETCONF message ID */
|
||||
r = nc_send_rpc(session, rpc, 100, &msg_id);
|
||||
if (r != NC_MSG_RPC) {
|
||||
ERR_MSG_CLEANUP("Couldn't send a RPC\n");
|
||||
}
|
||||
|
||||
/* receive the server's reply with the expected message ID
|
||||
* as separate rpc-reply NETCONF envelopes and the parsed YANG output itself, if any */
|
||||
r = nc_recv_reply(session, rpc, msg_id, 100, &envp, &op);
|
||||
if (r != NC_MSG_REPLY) {
|
||||
ERR_MSG_CLEANUP("Couldn't receive a reply from the server\n");
|
||||
}
|
||||
|
||||
/* print the whole reply */
|
||||
if (!op) {
|
||||
r = lyd_print_file(stdout, envp, LYD_XML, 0);
|
||||
} else {
|
||||
r = lyd_print_file(stdout, op, LYD_XML, 0);
|
||||
if (r) {
|
||||
ERR_MSG_CLEANUP("Couldn't print the RPC to stdout\n");
|
||||
}
|
||||
r = lyd_print_file(stdout, envp, LYD_XML, 0);
|
||||
}
|
||||
if (r) {
|
||||
ERR_MSG_CLEANUP("Couldn't print the RPC to stdout\n");
|
||||
}
|
||||
|
||||
cleanup:
|
||||
lyd_free_all(envp);
|
||||
lyd_free_all(op);
|
||||
nc_rpc_free(rpc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
int rc = 0, opt, port = 0;
|
||||
struct nc_session *session = NULL;
|
||||
const char *unix_socket_path = NULL, *rpc_parameter_1 = NULL, *rpc_parameter_2 = NULL;
|
||||
const char *ssh_pubkey_path = NULL, *ssh_privkey_path = NULL;
|
||||
|
||||
struct option options[] = {
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{"port", required_argument, NULL, 'p'},
|
||||
{"unix-path", required_argument, NULL, 'u'},
|
||||
{"ssh-pubkey", required_argument, NULL, 'P'},
|
||||
{"ssh-privkey", required_argument, NULL, 'i'},
|
||||
{"debug", no_argument, NULL, 'd'},
|
||||
{NULL, 0, NULL, 0}
|
||||
};
|
||||
|
||||
if (argc == 1) {
|
||||
help_print();
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* set the path to search for schemas */
|
||||
nc_client_set_schema_searchpath(MODULES_DIR);
|
||||
|
||||
opterr = 0;
|
||||
|
||||
while ((opt = getopt_long(argc, argv, "hp:u:P:i:d", options, NULL)) != -1) {
|
||||
switch (opt) {
|
||||
case 'h':
|
||||
help_print();
|
||||
goto cleanup;
|
||||
|
||||
case 'p':
|
||||
port = strtoul(optarg, NULL, 10);
|
||||
break;
|
||||
|
||||
case 'u':
|
||||
unix_socket_path = optarg;
|
||||
break;
|
||||
|
||||
case 'P':
|
||||
ssh_pubkey_path = optarg;
|
||||
break;
|
||||
|
||||
case 'i':
|
||||
ssh_privkey_path = optarg;
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
nc_verbosity(NC_VERB_DEBUG);
|
||||
nc_libssh_thread_verbosity(2);
|
||||
break;
|
||||
|
||||
default:
|
||||
ERR_MSG_CLEANUP("Invalid option or missing argument\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (optind == argc) {
|
||||
ERR_MSG_CLEANUP("Expected the name of RPC after options\n");
|
||||
}
|
||||
|
||||
/* check invalid args combinations */
|
||||
if (unix_socket_path && port) {
|
||||
ERR_MSG_CLEANUP("Both UNIX socket path and port specified. Please choose either SSH or UNIX.\n");
|
||||
} else if (unix_socket_path && (ssh_pubkey_path || ssh_privkey_path)) {
|
||||
ERR_MSG_CLEANUP("Both UNIX socket path and a path to key(s) specified. Please choose either SSH or UNIX.\n");
|
||||
} else if ((port == 10001) && (!ssh_pubkey_path || !ssh_privkey_path)) {
|
||||
ERR_MSG_CLEANUP("You need to specify both paths to private and public keys, if you want to connect to a publickey endpoint.\n");
|
||||
} else if ((port == 10000) && (ssh_pubkey_path || ssh_privkey_path)) {
|
||||
ERR_MSG_CLEANUP("Public or private key specified, when connecting to the password endpoint.\n");
|
||||
} else if (!unix_socket_path && !port) {
|
||||
ERR_MSG_CLEANUP("Neither UNIX socket or SSH specified.\n");
|
||||
}
|
||||
|
||||
/* connect to the server using the specified transport protocol */
|
||||
if (unix_socket_path) {
|
||||
/* it's UNIX socket */
|
||||
session = nc_connect_unix(unix_socket_path, NULL);
|
||||
} else {
|
||||
/* it must be SSH, so set the client SSH username to always be used when connecting to the server */
|
||||
if (nc_client_ssh_set_username(SSH_USERNAME)) {
|
||||
ERR_MSG_CLEANUP("Couldn't set the SSH username\n");
|
||||
}
|
||||
|
||||
if (ssh_pubkey_path && ssh_privkey_path) {
|
||||
/* set the client's SSH keypair to be used for authentication if necessary */
|
||||
if (nc_client_ssh_add_keypair(ssh_pubkey_path, ssh_privkey_path)) {
|
||||
ERR_MSG_CLEANUP("Couldn't set client's SSH keypair.\n");
|
||||
}
|
||||
}
|
||||
|
||||
/* try to connect via SSH */
|
||||
session = nc_connect_ssh(SSH_ADDRESS, port, NULL);
|
||||
}
|
||||
if (!session) {
|
||||
ERR_MSG_CLEANUP("Couldn't connect to the server\n");
|
||||
}
|
||||
|
||||
/* sending a get RPC */
|
||||
if (!strcmp(argv[optind], "get")) {
|
||||
if (optind + 1 < argc) {
|
||||
/* use the specified XPath filter */
|
||||
rpc_parameter_1 = argv[optind + 1];
|
||||
}
|
||||
if (send_rpc(session, NC_RPC_GET, rpc_parameter_1, rpc_parameter_2)) {
|
||||
rc = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
/* sending a get-config RPC */
|
||||
} else if (!strcmp(argv[optind], "get-config")) {
|
||||
/* use the specified datastore and optional XPath filter */
|
||||
if (optind + 2 < argc) {
|
||||
rpc_parameter_1 = argv[optind + 1];
|
||||
rpc_parameter_2 = argv[optind + 2];
|
||||
} else if (optind + 1 < argc) {
|
||||
rpc_parameter_1 = argv[optind + 1];
|
||||
}
|
||||
if (send_rpc(session, NC_RPC_GETCONFIG, rpc_parameter_1, rpc_parameter_2)) {
|
||||
rc = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
} else {
|
||||
ERR_MSG_CLEANUP("Invalid name of a RPC\n");
|
||||
}
|
||||
|
||||
cleanup:
|
||||
nc_session_free(session, NULL);
|
||||
nc_client_destroy();
|
||||
return rc;
|
||||
}
|
93
examples/config.json
Normal file
93
examples/config.json
Normal file
|
@ -0,0 +1,93 @@
|
|||
{
|
||||
"ietf-netconf-server:netconf-server": {
|
||||
"listen": {
|
||||
"idle-timeout": 10,
|
||||
"endpoints": {
|
||||
"endpoint": [
|
||||
{
|
||||
"name": "ssh-password-auth-endpt",
|
||||
"ssh": {
|
||||
"tcp-server-parameters": {
|
||||
"local-address": "127.0.0.1",
|
||||
"local-port": 10000
|
||||
},
|
||||
"ssh-server-parameters": {
|
||||
"server-identity": {
|
||||
"host-key": [
|
||||
{
|
||||
"name": "key",
|
||||
"public-key": {
|
||||
"inline-definition": {
|
||||
"public-key-format": "ietf-crypto-types:ssh-public-key-format",
|
||||
"public-key": "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBDRIB2eNSRWU+HNWRUGKr76ghCLg8RaMlUCps9lBjnc6ggaJl2Q+TOLn8se2wAdK3lYBMz3dcqR+SlU7eB8wJAc=",
|
||||
"private-key-format": "ietf-crypto-types:ec-private-key-format",
|
||||
"cleartext-private-key": "MHcCAQEEICQ2fr9Jt2xluom0YQQ7HseE8YTo5reZRVcQENKUWOrooAoGCCqGSM49AwEHoUQDQgAENEgHZ41JFZT4c1ZFQYqvvqCEIuDxFoyVQKmz2UGOdzqCBomXZD5M4ufyx7bAB0reVgEzPd1ypH5KVTt4HzAkBw=="
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"client-authentication": {
|
||||
"users": {
|
||||
"user": [
|
||||
{
|
||||
"name": "admin",
|
||||
"password": "$0$admin"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssh-pubkey-auth-endpt",
|
||||
"ssh": {
|
||||
"tcp-server-parameters": {
|
||||
"local-address": "127.0.0.1",
|
||||
"local-port": 10001
|
||||
},
|
||||
"ssh-server-parameters": {
|
||||
"server-identity": {
|
||||
"host-key": [
|
||||
{
|
||||
"name": "key",
|
||||
"public-key": {
|
||||
"inline-definition": {
|
||||
"public-key-format": "ietf-crypto-types:ssh-public-key-format",
|
||||
"public-key": "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBDRIB2eNSRWU+HNWRUGKr76ghCLg8RaMlUCps9lBjnc6ggaJl2Q+TOLn8se2wAdK3lYBMz3dcqR+SlU7eB8wJAc=",
|
||||
"private-key-format": "ietf-crypto-types:ec-private-key-format",
|
||||
"cleartext-private-key": "MHcCAQEEICQ2fr9Jt2xluom0YQQ7HseE8YTo5reZRVcQENKUWOrooAoGCCqGSM49AwEHoUQDQgAENEgHZ41JFZT4c1ZFQYqvvqCEIuDxFoyVQKmz2UGOdzqCBomXZD5M4ufyx7bAB0reVgEzPd1ypH5KVTt4HzAkBw=="
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"client-authentication": {
|
||||
"users": {
|
||||
"user": [
|
||||
{
|
||||
"name": "admin",
|
||||
"public-keys": {
|
||||
"inline-definition": {
|
||||
"public-key": [
|
||||
{
|
||||
"name": "admin_key.pub",
|
||||
"public-key-format": "ietf-crypto-types:ssh-public-key-format",
|
||||
"public-key": "AAAAC3NzaC1lZDI1NTE5AAAAIOr46rptg6BsWhO1JMomuh3cuCYmeuO6JfOUPs/YO35w"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
41
examples/example.h.in
Normal file
41
examples/example.h.in
Normal file
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* @file example.h
|
||||
* @author Roman Janota <xjanot04@fit.vutbr.cz>
|
||||
* @brief libnetconf2 example header
|
||||
*
|
||||
* @copyright
|
||||
* Copyright (c) 2022 CESNET, z.s.p.o.
|
||||
*
|
||||
* This source code is licensed under BSD 3-Clause License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef _EXAMPLE_H_
|
||||
#define _EXAMPLE_H_
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
/* directory with library YANG modules */
|
||||
#define MODULES_DIR "@CMAKE_SOURCE_DIR@/modules"
|
||||
|
||||
/* directory with examples source code and this header */
|
||||
#define EXAMPLES_DIR "@CMAKE_SOURCE_DIR@/examples"
|
||||
|
||||
/* SSH listening IP address */
|
||||
#define SSH_ADDRESS "127.0.0.1"
|
||||
|
||||
/* SSH 'password' authentication exptected username and password */
|
||||
#define SSH_USERNAME "admin"
|
||||
|
||||
/* time in microseconds to sleep for if there are no new RPCs and no new sessions */
|
||||
#define BACKOFF_TIMEOUT_USECS 100
|
||||
|
||||
#define ERR_MSG_CLEANUP(msg) \
|
||||
rc = 1; \
|
||||
fprintf(stderr, "%s", msg); \
|
||||
goto cleanup
|
||||
|
||||
#endif
|
380
examples/server.c
Normal file
380
examples/server.c
Normal file
|
@ -0,0 +1,380 @@
|
|||
/**
|
||||
* @file server.c
|
||||
* @author Roman Janota <xjanot04@fit.vutbr.cz>
|
||||
* @brief libnetconf2 server example
|
||||
*
|
||||
* @copyright
|
||||
* Copyright (c) 2022 CESNET, z.s.p.o.
|
||||
*
|
||||
* This source code is licensed under BSD 3-Clause License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include "example.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <getopt.h>
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <libyang/libyang.h>
|
||||
|
||||
#include "log.h"
|
||||
#include "messages_server.h"
|
||||
#include "netconf.h"
|
||||
#include "server_config.h"
|
||||
#include "session_server.h"
|
||||
#include "session_server_ch.h"
|
||||
|
||||
volatile int exit_application = 0;
|
||||
struct lyd_node *tree;
|
||||
|
||||
static void
|
||||
sigint_handler(int signum)
|
||||
{
|
||||
(void) signum;
|
||||
/* notify the main loop if we should exit */
|
||||
exit_application = 1;
|
||||
}
|
||||
|
||||
static struct nc_server_reply *
|
||||
get_rpc(struct lyd_node *rpc, struct nc_session *session)
|
||||
{
|
||||
const struct ly_ctx *ctx;
|
||||
const char *xpath;
|
||||
struct lyd_node *root = NULL, *root2 = NULL, *duplicate = NULL;
|
||||
struct lyd_node *filter, *err;
|
||||
struct lyd_meta *m, *type = NULL, *select = NULL;
|
||||
struct ly_set *set = NULL;
|
||||
LY_ERR ret;
|
||||
|
||||
ctx = nc_session_get_ctx(session);
|
||||
|
||||
/* load the ietf-yang-library data of the session, which represent this server's state data */
|
||||
if (ly_ctx_get_yanglib_data(ctx, &root, "%u", ly_ctx_get_change_count(ctx))) {
|
||||
err = nc_err(ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* search for the optional filter in the RPC */
|
||||
ret = lyd_find_path(rpc, "filter", 0, &filter);
|
||||
if (ret && (ret != LY_ENOTFOUND)) {
|
||||
err = nc_err(ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (filter) {
|
||||
/* look for the expected filter attributes type and select */
|
||||
LY_LIST_FOR(filter->meta, m) {
|
||||
if (!strcmp(m->name, "type")) {
|
||||
type = m;
|
||||
}
|
||||
if (!strcmp(m->name, "select")) {
|
||||
select = m;
|
||||
}
|
||||
}
|
||||
|
||||
/* only XPath filter is supported */
|
||||
if (!type || strcmp(lyd_get_meta_value(type), "xpath") || !select) {
|
||||
err = nc_err(ctx, NC_ERR_OP_NOT_SUPPORTED, NC_ERR_TYPE_APP);
|
||||
goto error;
|
||||
}
|
||||
xpath = lyd_get_meta_value(select);
|
||||
|
||||
/* find all the subtrees matching the filter */
|
||||
if (lyd_find_xpath(root, xpath, &set)) {
|
||||
err = nc_err(ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
|
||||
goto error;
|
||||
}
|
||||
|
||||
root2 = NULL;
|
||||
for (uint32_t i = 0; i < set->count; i++) {
|
||||
/* create a copy of the subtree with its parent nodes */
|
||||
if (lyd_dup_single(set->dnodes[i], NULL, LYD_DUP_RECURSIVE | LYD_DUP_WITH_PARENTS, &duplicate)) {
|
||||
err = nc_err(ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* merge another top-level filtered subtree into the result */
|
||||
while (duplicate->parent) {
|
||||
duplicate = lyd_parent(duplicate);
|
||||
}
|
||||
if (lyd_merge_tree(&root2, duplicate, LYD_MERGE_DESTRUCT)) {
|
||||
err = nc_err(ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
|
||||
goto error;
|
||||
}
|
||||
duplicate = NULL;
|
||||
}
|
||||
|
||||
/* replace the original full data with only the filtered data */
|
||||
lyd_free_siblings(root);
|
||||
root = root2;
|
||||
root2 = NULL;
|
||||
}
|
||||
|
||||
/* duplicate the rpc node without its input nodes so the output nodes can be appended */
|
||||
if (lyd_dup_single(rpc, NULL, 0, &duplicate)) {
|
||||
err = nc_err(ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* create the get RPC anyxml "data" output node with the requested data */
|
||||
if (lyd_new_any(duplicate, NULL, "data", root, LYD_ANYDATA_DATATREE, LYD_NEW_ANY_USE_VALUE | LYD_NEW_VAL_OUTPUT, NULL)) {
|
||||
err = nc_err(ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
|
||||
goto error;
|
||||
}
|
||||
|
||||
ly_set_free(set, NULL);
|
||||
|
||||
/* send data reply with the RPC output data */
|
||||
return nc_server_reply_data(duplicate, NC_WD_UNKNOWN, NC_PARAMTYPE_FREE);
|
||||
|
||||
error:
|
||||
ly_set_free(set, NULL);
|
||||
lyd_free_siblings(root);
|
||||
lyd_free_siblings(duplicate);
|
||||
lyd_free_siblings(root2);
|
||||
|
||||
/* send error reply with the specific NETCONF error */
|
||||
return nc_server_reply_err(err);
|
||||
}
|
||||
|
||||
static struct nc_server_reply *
|
||||
glob_rpc(struct lyd_node *rpc, struct nc_session *session)
|
||||
{
|
||||
struct lyd_node *iter;
|
||||
struct lyd_meta *m;
|
||||
|
||||
printf("Received RPC:\n");
|
||||
|
||||
/* iterate over all the nodes in the RPC */
|
||||
LYD_TREE_DFS_BEGIN(rpc, iter) {
|
||||
/* if the node has a value, then print its name and value */
|
||||
if (iter->schema->nodetype & (LYD_NODE_TERM | LYD_NODE_ANY)) {
|
||||
printf(" %s = \"%s\"\n", LYD_NAME(iter), lyd_get_value(iter));
|
||||
/* then iterate through all the metadata, which may include the XPath filter */
|
||||
LY_LIST_FOR(iter->meta, m) {
|
||||
printf(" %s = \"%s\"\n", m->name, lyd_get_meta_value(m));
|
||||
}
|
||||
/* else print just the name */
|
||||
} else if (iter->schema->nodetype == LYS_RPC) {
|
||||
printf(" %s\n", LYD_NAME(iter));
|
||||
}
|
||||
|
||||
LYD_TREE_DFS_END(rpc, iter);
|
||||
}
|
||||
|
||||
/* if close-session RPC is received, then call library's default function to properly close the session */
|
||||
if (!strcmp(LYD_NAME(rpc), "close-session") && !strcmp(lyd_owner_module(rpc)->name, "ietf-netconf")) {
|
||||
return nc_clb_default_close_session(rpc, session);
|
||||
}
|
||||
|
||||
/* if get-schema RPC is received, then use the library implementation of this RPC */
|
||||
if (!strcmp(LYD_NAME(rpc), "get-schema") && !strcmp(lyd_owner_module(rpc)->name, "ietf-netconf-monitoring")) {
|
||||
return nc_clb_default_get_schema(rpc, session);
|
||||
}
|
||||
|
||||
if (!strcmp(LYD_NAME(rpc), "get") && !strcmp(lyd_owner_module(rpc)->name, "ietf-netconf")) {
|
||||
return get_rpc(rpc, session);
|
||||
}
|
||||
|
||||
/* return an okay reply to every other RPC */
|
||||
return nc_server_reply_ok();
|
||||
}
|
||||
|
||||
static void
|
||||
help_print()
|
||||
{
|
||||
printf("Example usage:\n"
|
||||
" server -u ./unix_socket\n"
|
||||
"\n"
|
||||
" Available options:\n"
|
||||
" -h, --help\t \tPrint usage help.\n"
|
||||
" -u, --unix\t<path>\tCreate a UNIX socket endpoint at the place specified by <path>.\n\n");
|
||||
}
|
||||
|
||||
static int
|
||||
init(const char *unix_socket_path, struct ly_ctx **context, struct nc_pollsession **ps)
|
||||
{
|
||||
int rc = 0;
|
||||
struct lyd_node *config = NULL;
|
||||
|
||||
/* create a libyang context that will determine which YANG modules will be supported by the server */
|
||||
rc = ly_ctx_new(MODULES_DIR, 0, context);
|
||||
if (rc) {
|
||||
ERR_MSG_CLEANUP("Error while creating a new context.\n");
|
||||
}
|
||||
|
||||
/* implement the base NETCONF modules */
|
||||
rc = nc_server_init_ctx(context);
|
||||
if (rc) {
|
||||
ERR_MSG_CLEANUP("Error while initializing context.\n");
|
||||
}
|
||||
|
||||
/* load all required modules for configuration, so the configuration of the server can be done */
|
||||
rc = nc_server_config_load_modules(context);
|
||||
if (rc) {
|
||||
ERR_MSG_CLEANUP("Error loading modules required for configuration of the server.\n");
|
||||
}
|
||||
|
||||
/* apply the YANG data stored in config.json */
|
||||
rc = nc_server_config_setup_path(*context, EXAMPLES_DIR "/config.json");
|
||||
if (rc) {
|
||||
ERR_MSG_CLEANUP("Application of configuration data failed.\n");
|
||||
}
|
||||
|
||||
/* initialize the server */
|
||||
if (nc_server_init()) {
|
||||
ERR_MSG_CLEANUP("Error occurred while initializing the server.\n");
|
||||
}
|
||||
|
||||
/* create unix socket endpoint if path was set */
|
||||
if (unix_socket_path) {
|
||||
rc = nc_server_add_endpt_unix_socket_listen("unix-socket-endpt", unix_socket_path, -1, -1, -1);
|
||||
if (rc) {
|
||||
ERR_MSG_CLEANUP("Creating UNIX socket endpoint failed.\n");
|
||||
}
|
||||
}
|
||||
|
||||
/* create a new poll session structure, which is used for polling RPCs sent by clients */
|
||||
*ps = nc_ps_new();
|
||||
if (!*ps) {
|
||||
ERR_MSG_CLEANUP("Couldn't create a poll session\n");
|
||||
}
|
||||
|
||||
/* set the global RPC callback, which is called every time a new RPC is received */
|
||||
nc_set_global_rpc_clb(glob_rpc);
|
||||
|
||||
/* upon receiving SIGINT the handler will notify the program that is should terminate */
|
||||
signal(SIGINT, sigint_handler);
|
||||
|
||||
cleanup:
|
||||
lyd_free_all(config);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
int r, opt, no_new_sessions, rc = 0;
|
||||
struct ly_ctx *context = NULL;
|
||||
struct nc_session *session, *new_session;
|
||||
struct nc_pollsession *ps = NULL;
|
||||
const char *unix_socket_path = NULL;
|
||||
|
||||
struct option options[] = {
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{"debug", no_argument, NULL, 'd'},
|
||||
{"unix", required_argument, NULL, 'u'},
|
||||
{NULL, 0, NULL, 0}
|
||||
};
|
||||
|
||||
opterr = 0;
|
||||
|
||||
while ((opt = getopt_long(argc, argv, "hdu:", options, NULL)) != -1) {
|
||||
switch (opt) {
|
||||
case 'h':
|
||||
help_print();
|
||||
goto cleanup;
|
||||
|
||||
case 'd':
|
||||
nc_verbosity(NC_VERB_DEBUG);
|
||||
break;
|
||||
|
||||
case 'u':
|
||||
unix_socket_path = optarg;
|
||||
break;
|
||||
|
||||
default:
|
||||
ERR_MSG_CLEANUP("Invalid option or missing argument\n");
|
||||
}
|
||||
}
|
||||
|
||||
/* initialize the server */
|
||||
r = init(unix_socket_path, &context, &ps);
|
||||
if (r) {
|
||||
ERR_MSG_CLEANUP("Initializing the server failed.");
|
||||
}
|
||||
|
||||
printf("Listening for new connections!\n");
|
||||
|
||||
while (!exit_application) {
|
||||
no_new_sessions = 0;
|
||||
|
||||
/* try to accept new NETCONF sessions on all configured endpoints */
|
||||
r = nc_accept(0, context, &session);
|
||||
|
||||
switch (r) {
|
||||
|
||||
/* session accepted and its hello message received */
|
||||
case NC_MSG_HELLO:
|
||||
printf("Connection established\n");
|
||||
|
||||
/* add the new session to the poll structure */
|
||||
if (nc_ps_add_session(ps, session)) {
|
||||
ERR_MSG_CLEANUP("Couldn't add session to poll\n");
|
||||
}
|
||||
break;
|
||||
|
||||
/* there were no new sessions */
|
||||
case NC_MSG_WOULDBLOCK:
|
||||
no_new_sessions = 1;
|
||||
break;
|
||||
|
||||
/* session accepted, but its hello message was invalid */
|
||||
case NC_MSG_BAD_HELLO:
|
||||
printf("Parsing client hello message error.\n");
|
||||
break;
|
||||
|
||||
/* something else went wrong */
|
||||
case NC_MSG_ERROR:
|
||||
/* accepting a session failed, but the server should continue handling RPCs on established sessions */
|
||||
printf("Error while accepting a hello message.\n");
|
||||
rc = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
/* poll all the sessions in the structure and process a single event on a session which is then returned,
|
||||
* in case it is a new RPC then the global RPC callback is also called */
|
||||
r = nc_ps_poll(ps, 0, &new_session);
|
||||
|
||||
/* a fatal error occurred */
|
||||
if (r & NC_PSPOLL_ERROR) {
|
||||
ERR_MSG_CLEANUP("Error polling RPCs\n");
|
||||
}
|
||||
|
||||
/* a session was terminated, so remove it from the ps structure and free it */
|
||||
if (r & NC_PSPOLL_SESSION_TERM) {
|
||||
r = nc_ps_del_session(ps, new_session);
|
||||
assert(!r);
|
||||
nc_session_free(new_session, NULL);
|
||||
}
|
||||
|
||||
/* there were no new sessions and no new events on any established sessions,
|
||||
* prevent active waiting by sleeping for a short period of time */
|
||||
if (no_new_sessions && (r & (NC_PSPOLL_TIMEOUT | NC_PSPOLL_NOSESSIONS))) {
|
||||
usleep(BACKOFF_TIMEOUT_USECS);
|
||||
}
|
||||
|
||||
/* other set bits of the return value of nc_ps_poll() are not interesting in this example */
|
||||
}
|
||||
|
||||
cleanup:
|
||||
/* free all the remaining sessions in the ps structure before destroying the context */
|
||||
if (ps) {
|
||||
nc_ps_clear(ps, 1, NULL);
|
||||
}
|
||||
nc_ps_free(ps);
|
||||
nc_server_destroy();
|
||||
lyd_free_all(tree);
|
||||
ly_ctx_destroy(context);
|
||||
return rc;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue