2947 lines
96 KiB
C
2947 lines
96 KiB
C
|
/**
|
||
|
* \file session_client.c
|
||
|
* \author Michal Vasko <mvasko@cesnet.cz>
|
||
|
* \brief libnetconf2 session client functions
|
||
|
*
|
||
|
* Copyright (c) 2015 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
|
||
|
|
||
|
#ifdef __linux__
|
||
|
# include <sys/syscall.h>
|
||
|
#endif
|
||
|
|
||
|
#include <arpa/inet.h>
|
||
|
#include <assert.h>
|
||
|
#include <ctype.h>
|
||
|
#include <errno.h>
|
||
|
#include <fcntl.h>
|
||
|
#include <netdb.h>
|
||
|
#include <netinet/in.h>
|
||
|
#include <netinet/tcp.h>
|
||
|
#include <poll.h>
|
||
|
#include <pthread.h>
|
||
|
#include <pwd.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <sys/select.h>
|
||
|
#include <sys/socket.h>
|
||
|
#include <sys/stat.h>
|
||
|
#include <sys/types.h>
|
||
|
#include <sys/un.h>
|
||
|
#include <unistd.h>
|
||
|
|
||
|
#include <libyang/libyang.h>
|
||
|
|
||
|
#include "compat.h"
|
||
|
#include "libnetconf.h"
|
||
|
#include "messages_client.h"
|
||
|
#include "session_client.h"
|
||
|
|
||
|
#include "../modules/ietf_netconf@2013-09-29_yang.h"
|
||
|
#include "../modules/ietf_netconf_monitoring@2010-10-04_yang.h"
|
||
|
|
||
|
static const char *ncds2str[] = {NULL, "config", "url", "running", "startup", "candidate"};
|
||
|
|
||
|
#ifdef NC_ENABLED_SSH
|
||
|
int sshauth_hostkey_check(const char *hostname, ssh_session session, void *priv);
|
||
|
char *sshauth_password(const char *username, const char *hostname, void *priv);
|
||
|
char *sshauth_interactive(const char *auth_name, const char *instruction, const char *prompt, int echo, void *priv);
|
||
|
char *sshauth_privkey_passphrase(const char *privkey_path, void *priv);
|
||
|
#endif /* NC_ENABLED_SSH */
|
||
|
|
||
|
static pthread_once_t nc_client_context_once = PTHREAD_ONCE_INIT;
|
||
|
static pthread_key_t nc_client_context_key;
|
||
|
#ifdef __linux__
|
||
|
static struct nc_client_context context_main = {
|
||
|
.opts.ka = {
|
||
|
.enabled = 1,
|
||
|
.idle_time = 1,
|
||
|
.max_probes = 10,
|
||
|
.probe_interval = 5
|
||
|
},
|
||
|
#ifdef NC_ENABLED_SSH
|
||
|
.ssh_opts = {
|
||
|
.auth_pref = {{NC_SSH_AUTH_INTERACTIVE, 3}, {NC_SSH_AUTH_PASSWORD, 2}, {NC_SSH_AUTH_PUBLICKEY, 1}},
|
||
|
.auth_hostkey_check = sshauth_hostkey_check,
|
||
|
.auth_password = sshauth_password,
|
||
|
.auth_interactive = sshauth_interactive,
|
||
|
.auth_privkey_passphrase = sshauth_privkey_passphrase
|
||
|
},
|
||
|
.ssh_ch_opts = {
|
||
|
.auth_pref = {{NC_SSH_AUTH_INTERACTIVE, 1}, {NC_SSH_AUTH_PASSWORD, 2}, {NC_SSH_AUTH_PUBLICKEY, 3}},
|
||
|
.auth_hostkey_check = sshauth_hostkey_check,
|
||
|
.auth_password = sshauth_password,
|
||
|
.auth_interactive = sshauth_interactive,
|
||
|
.auth_privkey_passphrase = sshauth_privkey_passphrase
|
||
|
},
|
||
|
#endif /* NC_ENABLED_SSH */
|
||
|
/* .tls_ structures zeroed */
|
||
|
.refcount = 0
|
||
|
};
|
||
|
#endif
|
||
|
|
||
|
static void
|
||
|
nc_client_context_free(void *ptr)
|
||
|
{
|
||
|
struct nc_client_context *c = (struct nc_client_context *)ptr;
|
||
|
|
||
|
if (--(c->refcount)) {
|
||
|
/* still used */
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
#ifdef __linux__
|
||
|
/* in __linux__ we use static memory in the main thread,
|
||
|
* so this check is for programs terminating the main()
|
||
|
* function by pthread_exit() :)
|
||
|
*/
|
||
|
if (c != &context_main)
|
||
|
#endif
|
||
|
{
|
||
|
/* for the main thread the same is done in nc_client_destroy() */
|
||
|
free(c->opts.schema_searchpath);
|
||
|
|
||
|
#if defined (NC_ENABLED_SSH) || defined (NC_ENABLED_TLS)
|
||
|
int i;
|
||
|
for (i = 0; i < c->opts.ch_bind_count; ++i) {
|
||
|
close(c->opts.ch_binds[i].sock);
|
||
|
free((char *)c->opts.ch_binds[i].address);
|
||
|
}
|
||
|
free(c->opts.ch_binds);
|
||
|
c->opts.ch_binds = NULL;
|
||
|
c->opts.ch_bind_count = 0;
|
||
|
#endif
|
||
|
#ifdef NC_ENABLED_SSH
|
||
|
_nc_client_ssh_destroy_opts(&c->ssh_opts);
|
||
|
_nc_client_ssh_destroy_opts(&c->ssh_ch_opts);
|
||
|
#endif
|
||
|
#ifdef NC_ENABLED_TLS
|
||
|
_nc_client_tls_destroy_opts(&c->tls_opts);
|
||
|
_nc_client_tls_destroy_opts(&c->tls_ch_opts);
|
||
|
#endif
|
||
|
free(c);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
nc_client_context_createkey(void)
|
||
|
{
|
||
|
int r;
|
||
|
|
||
|
/* initiate */
|
||
|
while ((r = pthread_key_create(&nc_client_context_key, nc_client_context_free)) == EAGAIN) {}
|
||
|
pthread_setspecific(nc_client_context_key, NULL);
|
||
|
}
|
||
|
|
||
|
struct nc_client_context *
|
||
|
nc_client_context_location(void)
|
||
|
{
|
||
|
struct nc_client_context *e;
|
||
|
|
||
|
pthread_once(&nc_client_context_once, nc_client_context_createkey);
|
||
|
e = pthread_getspecific(nc_client_context_key);
|
||
|
if (!e) {
|
||
|
/* prepare ly_err storage */
|
||
|
#ifdef __linux__
|
||
|
if (getpid() == syscall(SYS_gettid)) {
|
||
|
/* main thread - use global variable instead of thread-specific variable. */
|
||
|
e = &context_main;
|
||
|
} else
|
||
|
#endif /* __linux__ */
|
||
|
{
|
||
|
e = calloc(1, sizeof *e);
|
||
|
/* set default values */
|
||
|
e->refcount = 1;
|
||
|
#ifdef NC_ENABLED_SSH
|
||
|
e->ssh_opts.auth_pref[0].type = NC_SSH_AUTH_INTERACTIVE;
|
||
|
e->ssh_opts.auth_pref[0].value = 3;
|
||
|
e->ssh_opts.auth_pref[1].type = NC_SSH_AUTH_PASSWORD;
|
||
|
e->ssh_opts.auth_pref[1].value = 2;
|
||
|
e->ssh_opts.auth_pref[2].type = NC_SSH_AUTH_PUBLICKEY;
|
||
|
e->ssh_opts.auth_pref[2].value = 1;
|
||
|
e->ssh_opts.auth_hostkey_check = sshauth_hostkey_check;
|
||
|
e->ssh_opts.auth_password = sshauth_password;
|
||
|
e->ssh_opts.auth_interactive = sshauth_interactive;
|
||
|
e->ssh_opts.auth_privkey_passphrase = sshauth_privkey_passphrase;
|
||
|
|
||
|
/* callhome settings are the same except the inverted auth methods preferences */
|
||
|
memcpy(&e->ssh_ch_opts, &e->ssh_opts, sizeof e->ssh_ch_opts);
|
||
|
e->ssh_ch_opts.auth_pref[0].value = 1;
|
||
|
e->ssh_ch_opts.auth_pref[1].value = 2;
|
||
|
e->ssh_ch_opts.auth_pref[2].value = 3;
|
||
|
#endif /* NC_ENABLED_SSH */
|
||
|
}
|
||
|
pthread_setspecific(nc_client_context_key, e);
|
||
|
}
|
||
|
|
||
|
return e;
|
||
|
}
|
||
|
|
||
|
#define client_opts nc_client_context_location()->opts
|
||
|
|
||
|
API void *
|
||
|
nc_client_get_thread_context(void)
|
||
|
{
|
||
|
return nc_client_context_location();
|
||
|
}
|
||
|
|
||
|
API void
|
||
|
nc_client_set_thread_context(void *context)
|
||
|
{
|
||
|
struct nc_client_context *old, *new;
|
||
|
|
||
|
if (!context) {
|
||
|
ERRARG(context);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
new = (struct nc_client_context *)context;
|
||
|
old = nc_client_context_location();
|
||
|
if (old == new) {
|
||
|
/* nothing to change */
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* replace old by new, increase reference counter in the newly set context */
|
||
|
nc_client_context_free(old);
|
||
|
new->refcount++;
|
||
|
pthread_setspecific(nc_client_context_key, new);
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_session_new_ctx(struct nc_session *session, struct ly_ctx *ctx)
|
||
|
{
|
||
|
/* assign context (dicionary needed for handshake) */
|
||
|
if (!ctx) {
|
||
|
if (ly_ctx_new(NULL, LY_CTX_NO_YANGLIBRARY, &ctx)) {
|
||
|
return EXIT_FAILURE;
|
||
|
}
|
||
|
|
||
|
/* user path must be first, the first path is used to store schemas retreived via get-schema */
|
||
|
if (client_opts.schema_searchpath) {
|
||
|
ly_ctx_set_searchdir(ctx, client_opts.schema_searchpath);
|
||
|
} else if (!access(NC_YANG_DIR, F_OK)) {
|
||
|
ly_ctx_set_searchdir(ctx, NC_YANG_DIR);
|
||
|
}
|
||
|
|
||
|
/* set callback for getting schemas, if provided */
|
||
|
ly_ctx_set_module_imp_clb(ctx, client_opts.schema_clb, client_opts.schema_clb_data);
|
||
|
} else {
|
||
|
session->flags |= NC_SESSION_SHAREDCTX;
|
||
|
}
|
||
|
|
||
|
session->ctx = ctx;
|
||
|
|
||
|
return EXIT_SUCCESS;
|
||
|
}
|
||
|
|
||
|
API int
|
||
|
nc_client_set_schema_searchpath(const char *path)
|
||
|
{
|
||
|
if (client_opts.schema_searchpath) {
|
||
|
free(client_opts.schema_searchpath);
|
||
|
}
|
||
|
|
||
|
if (path) {
|
||
|
client_opts.schema_searchpath = strdup(path);
|
||
|
if (!client_opts.schema_searchpath) {
|
||
|
ERRMEM;
|
||
|
return 1;
|
||
|
}
|
||
|
} else {
|
||
|
client_opts.schema_searchpath = NULL;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
API const char *
|
||
|
nc_client_get_schema_searchpath(void)
|
||
|
{
|
||
|
return client_opts.schema_searchpath;
|
||
|
}
|
||
|
|
||
|
API int
|
||
|
nc_client_set_schema_callback(ly_module_imp_clb clb, void *user_data)
|
||
|
{
|
||
|
client_opts.schema_clb = clb;
|
||
|
if (clb) {
|
||
|
client_opts.schema_clb_data = user_data;
|
||
|
} else {
|
||
|
client_opts.schema_clb_data = NULL;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
API ly_module_imp_clb
|
||
|
nc_client_get_schema_callback(void **user_data)
|
||
|
{
|
||
|
if (user_data) {
|
||
|
(*user_data) = client_opts.schema_clb_data;
|
||
|
}
|
||
|
return client_opts.schema_clb;
|
||
|
}
|
||
|
|
||
|
struct schema_info {
|
||
|
char *name;
|
||
|
char *revision;
|
||
|
struct {
|
||
|
char *name;
|
||
|
char *revision;
|
||
|
} *submodules;
|
||
|
char **features;
|
||
|
int implemented;
|
||
|
};
|
||
|
|
||
|
struct clb_data_s {
|
||
|
void *user_data;
|
||
|
ly_module_imp_clb user_clb;
|
||
|
struct schema_info *schemas;
|
||
|
struct nc_session *session;
|
||
|
int has_get_schema;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* @brief Retrieve YANG schema content from a local file.
|
||
|
*
|
||
|
* @param[in] name Schema name.
|
||
|
* @param[in] rev Schema revision.
|
||
|
* @param[in] clb_data get-schema callback data.
|
||
|
* @param[out] format Schema format.
|
||
|
* @return Schema content.
|
||
|
*/
|
||
|
static char *
|
||
|
retrieve_schema_data_localfile(const char *name, const char *rev, struct clb_data_s *clb_data,
|
||
|
LYS_INFORMAT *format)
|
||
|
{
|
||
|
char *localfile = NULL;
|
||
|
FILE *f;
|
||
|
long length, l;
|
||
|
char *model_data = NULL;
|
||
|
|
||
|
if (lys_search_localfile(ly_ctx_get_searchdirs(clb_data->session->ctx),
|
||
|
!(ly_ctx_get_options(clb_data->session->ctx) & LY_CTX_DISABLE_SEARCHDIR_CWD),
|
||
|
name, rev, &localfile, format)) {
|
||
|
return NULL;
|
||
|
}
|
||
|
if (localfile) {
|
||
|
VRB(clb_data->session, "Reading schema from localfile \"%s\".", localfile);
|
||
|
f = fopen(localfile, "r");
|
||
|
if (!f) {
|
||
|
ERR(clb_data->session, "Unable to open \"%s\" file to get schema (%s).", localfile, strerror(errno));
|
||
|
free(localfile);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
fseek(f, 0, SEEK_END);
|
||
|
length = ftell(f);
|
||
|
if (length < 0) {
|
||
|
ERR(clb_data->session, "Unable to get size of schema file \"%s\".", localfile);
|
||
|
free(localfile);
|
||
|
fclose(f);
|
||
|
return NULL;
|
||
|
}
|
||
|
fseek(f, 0, SEEK_SET);
|
||
|
|
||
|
model_data = malloc(length + 1);
|
||
|
if (!model_data) {
|
||
|
ERRMEM;
|
||
|
} else if ((l = fread(model_data, 1, length, f)) != length) {
|
||
|
ERR(clb_data->session, "Reading schema from \"%s\" failed (%d bytes read, but %d expected).", localfile, l,
|
||
|
length);
|
||
|
free(model_data);
|
||
|
model_data = NULL;
|
||
|
} else {
|
||
|
/* terminating NULL byte */
|
||
|
model_data[length] = '\0';
|
||
|
}
|
||
|
fclose(f);
|
||
|
free(localfile);
|
||
|
}
|
||
|
|
||
|
return model_data;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @brief Retrieve YANG schema content from a reply to get-schema RPC.
|
||
|
*
|
||
|
* @param[in] name Schema name.
|
||
|
* @param[in] rev Schema revision.
|
||
|
* @param[in] clb_data get-schema callback data.
|
||
|
* @param[out] format Schema format.
|
||
|
* @return Schema content.
|
||
|
*/
|
||
|
static char *
|
||
|
retrieve_schema_data_getschema(const char *name, const char *rev, struct clb_data_s *clb_data,
|
||
|
LYS_INFORMAT *format)
|
||
|
{
|
||
|
struct nc_rpc *rpc;
|
||
|
struct lyd_node *envp = NULL, *op = NULL;
|
||
|
struct lyd_node_any *get_schema_data;
|
||
|
NC_MSG_TYPE msg;
|
||
|
uint64_t msgid;
|
||
|
char *localfile = NULL;
|
||
|
FILE *f;
|
||
|
char *model_data = NULL;
|
||
|
|
||
|
VRB(clb_data->session, "Reading schema from server via get-schema.");
|
||
|
rpc = nc_rpc_getschema(name, rev, "yang", NC_PARAMTYPE_CONST);
|
||
|
|
||
|
while ((msg = nc_send_rpc(clb_data->session, rpc, 0, &msgid)) == NC_MSG_WOULDBLOCK) {
|
||
|
usleep(1000);
|
||
|
}
|
||
|
if (msg == NC_MSG_ERROR) {
|
||
|
ERR(clb_data->session, "Failed to send the <get-schema> RPC.");
|
||
|
nc_rpc_free(rpc);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
do {
|
||
|
msg = nc_recv_reply(clb_data->session, rpc, msgid, NC_READ_ACT_TIMEOUT * 1000, &envp, &op);
|
||
|
} while (msg == NC_MSG_NOTIF || msg == NC_MSG_REPLY_ERR_MSGID);
|
||
|
nc_rpc_free(rpc);
|
||
|
if (msg == NC_MSG_WOULDBLOCK) {
|
||
|
ERR(clb_data->session, "Timeout for receiving reply to a <get-schema> expired.");
|
||
|
goto cleanup;
|
||
|
} else if ((msg == NC_MSG_ERROR) || !op) {
|
||
|
ERR(clb_data->session, "Failed to receive a reply to <get-schema>.");
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
if (!lyd_child(op) || (lyd_child(op)->schema->nodetype != LYS_ANYXML)) {
|
||
|
ERR(clb_data->session, "Unexpected data in reply to a <get-schema> RPC.");
|
||
|
goto cleanup;
|
||
|
}
|
||
|
get_schema_data = (struct lyd_node_any *)lyd_child(op);
|
||
|
switch (get_schema_data->value_type) {
|
||
|
case LYD_ANYDATA_STRING:
|
||
|
case LYD_ANYDATA_XML:
|
||
|
model_data = strdup(get_schema_data->value.str);
|
||
|
break;
|
||
|
case LYD_ANYDATA_DATATREE:
|
||
|
lyd_print_mem(&model_data, get_schema_data->value.tree, LYD_XML, LYD_PRINT_WITHSIBLINGS);
|
||
|
break;
|
||
|
case LYD_ANYDATA_JSON:
|
||
|
case LYD_ANYDATA_LYB:
|
||
|
ERRINT;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (model_data && !model_data[0]) {
|
||
|
/* empty data */
|
||
|
free(model_data);
|
||
|
model_data = NULL;
|
||
|
}
|
||
|
|
||
|
/* try to store the model_data into local schema repository */
|
||
|
if (model_data) {
|
||
|
*format = LYS_IN_YANG;
|
||
|
if (client_opts.schema_searchpath) {
|
||
|
if (asprintf(&localfile, "%s/%s%s%s.yang", client_opts.schema_searchpath, name, rev ? "@" : "",
|
||
|
rev ? rev : "") == -1) {
|
||
|
ERRMEM;
|
||
|
} else {
|
||
|
f = fopen(localfile, "w");
|
||
|
if (!f) {
|
||
|
WRN(clb_data->session, "Unable to store \"%s\" as a local copy of schema retrieved via <get-schema> (%s).",
|
||
|
localfile, strerror(errno));
|
||
|
} else {
|
||
|
fputs(model_data, f);
|
||
|
fclose(f);
|
||
|
}
|
||
|
free(localfile);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
cleanup:
|
||
|
lyd_free_tree(envp);
|
||
|
lyd_free_tree(op);
|
||
|
return model_data;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
free_with_user_data(void *data, void *user_data)
|
||
|
{
|
||
|
free(data);
|
||
|
(void)user_data;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @brief Retrieve YANG schema content.
|
||
|
*
|
||
|
* @param[in] mod_name Schema name.
|
||
|
* @param[in] mod_rev Schema revision.
|
||
|
* @param[in] submod_name Optional submodule name.
|
||
|
* @param[in] sub_rev Submodule revision.
|
||
|
* @param[in] user_data get-schema callback data.
|
||
|
* @param[out] format Schema format.
|
||
|
* @param[out] module_data Schema content.
|
||
|
* @param[out] free_module_data Callback for freeing @p module_data.
|
||
|
* @return LY_ERR value.
|
||
|
*/
|
||
|
static LY_ERR
|
||
|
retrieve_schema_data(const char *mod_name, const char *mod_rev, const char *submod_name, const char *sub_rev,
|
||
|
void *user_data, LYS_INFORMAT *format, const char **module_data,
|
||
|
void (**free_module_data)(void *model_data, void *user_data))
|
||
|
{
|
||
|
struct clb_data_s *clb_data = (struct clb_data_s *)user_data;
|
||
|
unsigned int u, v, match = 1;
|
||
|
const char *name = NULL, *rev = NULL;
|
||
|
char *model_data = NULL;
|
||
|
|
||
|
/* get and check the final name and revision of the schema to be retrieved */
|
||
|
if (!mod_rev || !mod_rev[0]) {
|
||
|
/* newest revision requested - get the newest revision from the list of available modules on server */
|
||
|
match = 0;
|
||
|
for (u = 0; clb_data->schemas[u].name; ++u) {
|
||
|
if (strcmp(mod_name, clb_data->schemas[u].name)) {
|
||
|
continue;
|
||
|
}
|
||
|
if (!match || (strcmp(mod_rev, clb_data->schemas[u].revision) > 0)) {
|
||
|
mod_rev = clb_data->schemas[u].revision;
|
||
|
}
|
||
|
match = u + 1;
|
||
|
}
|
||
|
if (!match) {
|
||
|
/* valid situation if we are retrieving YANG 1.1 schema and have only capabilities for now
|
||
|
* (when loading ietf-datastore for ietf-yang-library) */
|
||
|
VRB(clb_data->session, "Unable to identify revision of the schema \"%s\" from "
|
||
|
"the available server side information.", mod_name);
|
||
|
}
|
||
|
}
|
||
|
if (submod_name) {
|
||
|
name = submod_name;
|
||
|
if (sub_rev) {
|
||
|
rev = sub_rev;
|
||
|
} else if (match) {
|
||
|
if (!clb_data->schemas[match - 1].submodules) {
|
||
|
VRB(clb_data->session, "Unable to identify revision of the requested submodule \"%s\", "
|
||
|
"in schema \"%s\", from the available server side information.", submod_name, mod_name);
|
||
|
} else {
|
||
|
for (v = 0; clb_data->schemas[match - 1].submodules[v].name; ++v) {
|
||
|
if (!strcmp(submod_name, clb_data->schemas[match - 1].submodules[v].name)) {
|
||
|
rev = sub_rev = clb_data->schemas[match - 1].submodules[v].revision;
|
||
|
}
|
||
|
}
|
||
|
if (!rev) {
|
||
|
ERR(clb_data->session, "Requested submodule \"%s\" is not known for schema \"%s\" on server side.",
|
||
|
submod_name, mod_name);
|
||
|
return LY_ENOTFOUND;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
name = mod_name;
|
||
|
rev = mod_rev;
|
||
|
}
|
||
|
|
||
|
VRB(clb_data->session, "Retrieving data for schema \"%s\", revision \"%s\".", name, rev ? rev : "<latest>");
|
||
|
|
||
|
if (match) {
|
||
|
/* we have enough information to avoid communication with server and try to get
|
||
|
* the schema locally */
|
||
|
|
||
|
/* 1. try to get data locally */
|
||
|
model_data = retrieve_schema_data_localfile(name, rev, clb_data, format);
|
||
|
|
||
|
/* 2. try to use <get-schema> */
|
||
|
if (!model_data && clb_data->has_get_schema) {
|
||
|
model_data = retrieve_schema_data_getschema(name, rev, clb_data, format);
|
||
|
}
|
||
|
} else {
|
||
|
/* we are unsure which revision of the schema we should load, so first try to get
|
||
|
* the newest revision from the server via get-schema and only if the server does not
|
||
|
* implement get-schema, try to load the newest revision locally. This is imperfect
|
||
|
* solution, but there are situation when a client does not know what revision is
|
||
|
* actually implemented by the server. */
|
||
|
|
||
|
/* 1. try to use <get-schema> */
|
||
|
if (clb_data->has_get_schema) {
|
||
|
model_data = retrieve_schema_data_getschema(name, rev, clb_data, format);
|
||
|
}
|
||
|
|
||
|
/* 2. try to get data locally */
|
||
|
if (!model_data) {
|
||
|
model_data = retrieve_schema_data_localfile(name, rev, clb_data, format);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* 3. try to use user callback */
|
||
|
if (!model_data && clb_data->user_clb) {
|
||
|
VRB(clb_data->session, "Reading schema via user callback.");
|
||
|
clb_data->user_clb(mod_name, mod_rev, submod_name, sub_rev, clb_data->user_data, format,
|
||
|
(const char **)&model_data, free_module_data);
|
||
|
}
|
||
|
|
||
|
*free_module_data = free_with_user_data;
|
||
|
*module_data = model_data;
|
||
|
return *module_data ? LY_SUCCESS : LY_ENOTFOUND;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @brief Load a YANG schema into context.
|
||
|
*
|
||
|
* @param[in] session NC session.
|
||
|
* @param[in] name Schema name.
|
||
|
* @param[in] revision Schema revision.
|
||
|
* @param[in] schemas Server schema info built from capabilities.
|
||
|
* @param[in] user_clb User callback for retireving schema data.
|
||
|
* @param[in] user_data User data for @p user_clb.
|
||
|
* @param[in] has_get_schema Whether the server supports get-schema.
|
||
|
* @param[out] mod Loaded module.
|
||
|
* @return 0 on success.
|
||
|
* @return -1 on error.
|
||
|
*/
|
||
|
static int
|
||
|
nc_ctx_load_module(struct nc_session *session, const char *name, const char *revision, struct schema_info *schemas,
|
||
|
ly_module_imp_clb user_clb, void *user_data, int has_get_schema, struct lys_module **mod)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
struct ly_err_item *eitem;
|
||
|
const char *module_data = NULL;
|
||
|
LYS_INFORMAT format;
|
||
|
|
||
|
void (*free_module_data)(void *, void *) = NULL;
|
||
|
struct clb_data_s clb_data;
|
||
|
|
||
|
*mod = NULL;
|
||
|
if (revision) {
|
||
|
*mod = ly_ctx_get_module(session->ctx, name, revision);
|
||
|
}
|
||
|
if (*mod) {
|
||
|
if (!(*mod)->implemented) {
|
||
|
/* make the present module implemented */
|
||
|
if (lys_set_implemented(*mod, NULL)) {
|
||
|
ERR(session, "Failed to implement model \"%s\".", (*mod)->name);
|
||
|
ret = -1;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
/* missing implemented module, load it ... */
|
||
|
clb_data.has_get_schema = has_get_schema;
|
||
|
clb_data.schemas = schemas;
|
||
|
clb_data.session = session;
|
||
|
clb_data.user_clb = user_clb;
|
||
|
clb_data.user_data = user_data;
|
||
|
|
||
|
/* clear all the errors and just collect them for now */
|
||
|
ly_err_clean(session->ctx, NULL);
|
||
|
ly_log_options(LY_LOSTORE);
|
||
|
|
||
|
/* get module data */
|
||
|
retrieve_schema_data(name, revision, NULL, NULL, &clb_data, &format, &module_data, &free_module_data);
|
||
|
|
||
|
if (module_data) {
|
||
|
/* parse the schema */
|
||
|
ly_ctx_set_module_imp_clb(session->ctx, retrieve_schema_data, &clb_data);
|
||
|
|
||
|
lys_parse_mem(session->ctx, module_data, format, mod);
|
||
|
if (*free_module_data) {
|
||
|
(*free_module_data)((char *)module_data, user_data);
|
||
|
}
|
||
|
|
||
|
ly_ctx_set_module_imp_clb(session->ctx, NULL, NULL);
|
||
|
}
|
||
|
|
||
|
/* restore logging options, then print errors on definite failure */
|
||
|
ly_log_options(LY_LOLOG | LY_LOSTORE_LAST);
|
||
|
if (!(*mod)) {
|
||
|
for (eitem = ly_err_first(session->ctx); eitem && eitem->next; eitem = eitem->next) {
|
||
|
ly_err_print(session->ctx, eitem);
|
||
|
}
|
||
|
ret = -1;
|
||
|
} else {
|
||
|
/* print only warnings */
|
||
|
for (eitem = ly_err_first(session->ctx); eitem && eitem->next; eitem = eitem->next) {
|
||
|
if (eitem->level == LY_LLWRN) {
|
||
|
ly_err_print(session->ctx, eitem);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* clean the errors */
|
||
|
ly_err_clean(session->ctx, NULL);
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
free_schema_info(struct schema_info *list)
|
||
|
{
|
||
|
uint32_t u, v;
|
||
|
|
||
|
if (!list) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
for (u = 0; list[u].name; ++u) {
|
||
|
free(list[u].name);
|
||
|
free(list[u].revision);
|
||
|
if (list[u].features) {
|
||
|
for (v = 0; list[u].features[v]; ++v) {
|
||
|
free(list[u].features[v]);
|
||
|
}
|
||
|
free(list[u].features);
|
||
|
}
|
||
|
if (list[u].submodules) {
|
||
|
for (v = 0; list[u].submodules[v].name; ++v) {
|
||
|
free(list[u].submodules[v].name);
|
||
|
free(list[u].submodules[v].revision);
|
||
|
}
|
||
|
free(list[u].submodules);
|
||
|
}
|
||
|
}
|
||
|
free(list);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @brief Build server schema info from ietf-yang-library data.
|
||
|
*
|
||
|
* @param[in] session NC session.
|
||
|
* @param[in] has_get_data Whether get-data RPC is available or only get.
|
||
|
* @param[out] result Server schemas.
|
||
|
* @return 0 on success.
|
||
|
* @return -1 on error.
|
||
|
*/
|
||
|
static int
|
||
|
build_schema_info_yl(struct nc_session *session, int has_get_data, struct schema_info **result)
|
||
|
{
|
||
|
struct nc_rpc *rpc = NULL;
|
||
|
struct lyd_node *op = NULL, *envp = NULL;
|
||
|
struct lyd_node_any *data;
|
||
|
NC_MSG_TYPE msg;
|
||
|
uint64_t msgid;
|
||
|
struct ly_set *modules = NULL;
|
||
|
uint32_t u, v, submodules_count, feature_count;
|
||
|
struct lyd_node *iter, *child;
|
||
|
struct lys_module *mod;
|
||
|
int ret = 0;
|
||
|
const char *rpc_name;
|
||
|
|
||
|
/* get yang-library data from the server */
|
||
|
if (has_get_data) {
|
||
|
rpc_name = "get-data";
|
||
|
if (nc_session_cpblt(session, "urn:ietf:params:netconf:capability:xpath:1.0")) {
|
||
|
rpc = nc_rpc_getdata("ietf-datastores:operational", "/ietf-yang-library:*", "false", NULL, 0, 0, 0, 0, 0,
|
||
|
NC_PARAMTYPE_CONST);
|
||
|
} else {
|
||
|
rpc = nc_rpc_getdata("ietf-datastores:operational",
|
||
|
"<modules-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-yang-library\"/>", "false", NULL, 0, 0, 0, 0,
|
||
|
0, NC_PARAMTYPE_CONST);
|
||
|
}
|
||
|
} else {
|
||
|
rpc_name = "get";
|
||
|
if (nc_session_cpblt(session, "urn:ietf:params:netconf:capability:xpath:1.0")) {
|
||
|
rpc = nc_rpc_get("/ietf-yang-library:*", 0, NC_PARAMTYPE_CONST);
|
||
|
} else {
|
||
|
rpc = nc_rpc_get("<modules-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-yang-library\"/>", 0,
|
||
|
NC_PARAMTYPE_CONST);
|
||
|
}
|
||
|
}
|
||
|
if (!rpc) {
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
while ((msg = nc_send_rpc(session, rpc, 0, &msgid)) == NC_MSG_WOULDBLOCK) {
|
||
|
usleep(1000);
|
||
|
}
|
||
|
if (msg == NC_MSG_ERROR) {
|
||
|
WRN(session, "Failed to send request for yang-library data.");
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
do {
|
||
|
lyd_free_tree(envp);
|
||
|
lyd_free_tree(op);
|
||
|
|
||
|
msg = nc_recv_reply(session, rpc, msgid, NC_READ_ACT_TIMEOUT * 1000, &envp, &op);
|
||
|
} while (msg == NC_MSG_NOTIF || msg == NC_MSG_REPLY_ERR_MSGID);
|
||
|
if (msg == NC_MSG_WOULDBLOCK) {
|
||
|
WRN(session, "Timeout for receiving reply to a <%s> yang-library data expired.", rpc_name);
|
||
|
goto cleanup;
|
||
|
} else if (msg == NC_MSG_ERROR) {
|
||
|
WRN(session, "Failed to receive a reply to <%s> of yang-library data.", rpc_name);
|
||
|
goto cleanup;
|
||
|
} else if (!op || !lyd_child(op) || strcmp(lyd_child(op)->schema->name, "data")) {
|
||
|
WRN(session, "Unexpected reply without data to a yang-library <%s> RPC.", rpc_name);
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
data = (struct lyd_node_any *)lyd_child(op);
|
||
|
if (data->value_type != LYD_ANYDATA_DATATREE) {
|
||
|
WRN(session, "Unexpected data in reply to a yang-library <%s> RPC.", rpc_name);
|
||
|
goto cleanup;
|
||
|
} else if (!data->value.tree) {
|
||
|
WRN(session, "No data in reply to a yang-library <%s> RPC.", rpc_name);
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
if (lyd_find_xpath(data->value.tree, "/ietf-yang-library:modules-state/module", &modules)) {
|
||
|
WRN(session, "No module information in reply to a yang-library <%s> RPC.", rpc_name);
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
(*result) = calloc(modules->count + 1, sizeof **result);
|
||
|
if (!(*result)) {
|
||
|
ERRMEM;
|
||
|
ret = -1;
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
for (u = 0; u < modules->count; ++u) {
|
||
|
submodules_count = 0;
|
||
|
feature_count = 0;
|
||
|
mod = ((struct lyd_node *)modules->dnodes[u])->schema->module;
|
||
|
LY_LIST_FOR(lyd_child(modules->dnodes[u]), iter) {
|
||
|
if (!iter->schema || (iter->schema->module != mod)) {
|
||
|
/* ignore node from other schemas (augments) */
|
||
|
continue;
|
||
|
}
|
||
|
if (!lyd_get_value(iter) || !lyd_get_value(iter)[0]) {
|
||
|
/* ignore empty nodes */
|
||
|
continue;
|
||
|
}
|
||
|
if (!strcmp(iter->schema->name, "name")) {
|
||
|
(*result)[u].name = strdup(lyd_get_value(iter));
|
||
|
} else if (!strcmp(iter->schema->name, "revision")) {
|
||
|
(*result)[u].revision = strdup(lyd_get_value(iter));
|
||
|
} else if (!strcmp(iter->schema->name, "conformance-type")) {
|
||
|
(*result)[u].implemented = !strcmp(lyd_get_value(iter), "implement");
|
||
|
} else if (!strcmp(iter->schema->name, "feature")) {
|
||
|
(*result)[u].features = nc_realloc((*result)[u].features, (feature_count + 2) * sizeof *(*result)[u].features);
|
||
|
if (!(*result)[u].features) {
|
||
|
ERRMEM;
|
||
|
free_schema_info(*result);
|
||
|
*result = NULL;
|
||
|
ret = -1;
|
||
|
goto cleanup;
|
||
|
}
|
||
|
(*result)[u].features[feature_count] = strdup(lyd_get_value(iter));
|
||
|
(*result)[u].features[feature_count + 1] = NULL;
|
||
|
++feature_count;
|
||
|
} else if (!strcmp(iter->schema->name, "submodule")) {
|
||
|
submodules_count++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (submodules_count) {
|
||
|
(*result)[u].submodules = calloc(submodules_count + 1, sizeof *(*result)[u].submodules);
|
||
|
if (!(*result)[u].submodules) {
|
||
|
ERRMEM;
|
||
|
free_schema_info(*result);
|
||
|
*result = NULL;
|
||
|
ret = -1;
|
||
|
goto cleanup;
|
||
|
} else {
|
||
|
v = 0;
|
||
|
LY_LIST_FOR(lyd_child(modules->dnodes[u]), iter) {
|
||
|
mod = modules->dnodes[u]->schema->module;
|
||
|
if ((mod == iter->schema->module) && !strcmp(iter->schema->name, "submodule")) {
|
||
|
LY_LIST_FOR(lyd_child(iter), child) {
|
||
|
if (mod != child->schema->module) {
|
||
|
continue;
|
||
|
} else if (!strcmp(child->schema->name, "name")) {
|
||
|
(*result)[u].submodules[v].name = strdup(lyd_get_value(child));
|
||
|
} else if (!strcmp(child->schema->name, "revision")) {
|
||
|
(*result)[u].submodules[v].revision = strdup(lyd_get_value(child));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
cleanup:
|
||
|
nc_rpc_free(rpc);
|
||
|
lyd_free_tree(envp);
|
||
|
lyd_free_tree(op);
|
||
|
ly_set_free(modules, NULL);
|
||
|
|
||
|
if (session->status != NC_STATUS_RUNNING) {
|
||
|
/* something bad happened, discard the session */
|
||
|
ERR(session, "Invalid session, discarding.");
|
||
|
ret = -1;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @brief Build server schema info from received capabilities.
|
||
|
*
|
||
|
* @param[in] cpblts Server capabilities.
|
||
|
* @param[out] result Server schemas.
|
||
|
* @return 0 on success.
|
||
|
* @return -1 on error.
|
||
|
*/
|
||
|
static int
|
||
|
build_schema_info_cpblts(char **cpblts, struct schema_info **result)
|
||
|
{
|
||
|
uint32_t u, v, feature_count;
|
||
|
char *module_cpblt, *ptr, *ptr2;
|
||
|
|
||
|
for (u = 0; cpblts[u]; ++u) {}
|
||
|
(*result) = calloc(u + 1, sizeof **result);
|
||
|
if (!(*result)) {
|
||
|
ERRMEM;
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
for (u = v = 0; cpblts[u]; ++u) {
|
||
|
module_cpblt = strstr(cpblts[u], "module=");
|
||
|
/* this capability requires a module */
|
||
|
if (!module_cpblt) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/* get module's name */
|
||
|
ptr = (char *)module_cpblt + 7;
|
||
|
ptr2 = strchr(ptr, '&');
|
||
|
if (!ptr2) {
|
||
|
ptr2 = ptr + strlen(ptr);
|
||
|
}
|
||
|
(*result)[v].name = strndup(ptr, ptr2 - ptr);
|
||
|
|
||
|
/* get module's revision */
|
||
|
ptr = strstr(module_cpblt, "revision=");
|
||
|
if (ptr) {
|
||
|
ptr += 9;
|
||
|
ptr2 = strchr(ptr, '&');
|
||
|
if (!ptr2) {
|
||
|
ptr2 = ptr + strlen(ptr);
|
||
|
}
|
||
|
(*result)[v].revision = strndup(ptr, ptr2 - ptr);
|
||
|
}
|
||
|
|
||
|
/* all are implemented since there is no better information in capabilities list */
|
||
|
(*result)[v].implemented = 1;
|
||
|
|
||
|
/* get module's features */
|
||
|
ptr = strstr(module_cpblt, "features=");
|
||
|
if (ptr) {
|
||
|
ptr += 9;
|
||
|
feature_count = 0;
|
||
|
for (ptr2 = ptr; *ptr && *ptr != '&'; ++ptr) {
|
||
|
if (*ptr == ',') {
|
||
|
(*result)[v].features = nc_realloc((*result)[v].features, (feature_count + 2) * sizeof *(*result)[v].features);
|
||
|
(*result)[v].features[feature_count] = strndup(ptr2, ptr - ptr2);
|
||
|
(*result)[v].features[feature_count + 1] = NULL;
|
||
|
++feature_count;
|
||
|
|
||
|
ptr2 = ptr + 1;
|
||
|
}
|
||
|
}
|
||
|
/* the last one */
|
||
|
(*result)[v].features = nc_realloc((*result)[v].features, (feature_count + 2) * sizeof *(*result)[v].features);
|
||
|
(*result)[v].features[feature_count] = strndup(ptr2, ptr - ptr2);
|
||
|
(*result)[v].features[feature_count + 1] = NULL;
|
||
|
++feature_count;
|
||
|
}
|
||
|
++v;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @brief Fill client context based on server schema info.
|
||
|
*
|
||
|
* @param[in] session NC session with the context to modify.
|
||
|
* @param[in] modules Server schema info.
|
||
|
* @param[in] user_clb User callback for retrieving specific schemas.
|
||
|
* @param[in] user_data User data for @p user_clb.
|
||
|
* @param[in] has_get_schema Whether server supports get-schema RPC.
|
||
|
* @return 0 on success.
|
||
|
* @return -1 on error.
|
||
|
*/
|
||
|
static int
|
||
|
nc_ctx_fill(struct nc_session *session, struct schema_info *modules, ly_module_imp_clb user_clb, void *user_data,
|
||
|
int has_get_schema)
|
||
|
{
|
||
|
int ret = -1;
|
||
|
struct lys_module *mod;
|
||
|
uint32_t u;
|
||
|
|
||
|
for (u = 0; modules[u].name; ++u) {
|
||
|
/* skip import-only modules */
|
||
|
if (!modules[u].implemented) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/* we can continue even if it fails */
|
||
|
nc_ctx_load_module(session, modules[u].name, modules[u].revision, modules, user_clb, user_data, has_get_schema, &mod);
|
||
|
|
||
|
if (!mod) {
|
||
|
if (session->status != NC_STATUS_RUNNING) {
|
||
|
/* something bad heppened, discard the session */
|
||
|
ERR(session, "Invalid session, discarding.");
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
/* all loading ways failed, the schema will be ignored in the received data */
|
||
|
WRN(session, "Failed to load schema \"%s@%s\".", modules[u].name, modules[u].revision ?
|
||
|
modules[u].revision : "<latest>");
|
||
|
session->flags |= NC_SESSION_CLIENT_NOT_STRICT;
|
||
|
} else {
|
||
|
/* set the features */
|
||
|
lys_set_implemented(mod, (const char **)modules[u].features);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* success */
|
||
|
ret = 0;
|
||
|
|
||
|
cleanup:
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @brief Fill client context with ietf-netconf schema.
|
||
|
*
|
||
|
* @param[in] session NC session with the context to modify.
|
||
|
* @param[in] modules Server schema info.
|
||
|
* @param[in] user_clb User callback for retrieving specific schemas.
|
||
|
* @param[in] user_data User data for @p user_clb.
|
||
|
* @param[in] has_get_schema Whether server supports get-schema RPC.
|
||
|
* @return 0 on success.
|
||
|
* @return -1 on error.
|
||
|
*/
|
||
|
static int
|
||
|
nc_ctx_fill_ietf_netconf(struct nc_session *session, struct schema_info *modules, ly_module_imp_clb user_clb,
|
||
|
void *user_data, int has_get_schema)
|
||
|
{
|
||
|
uint32_t u;
|
||
|
struct lys_module *ietfnc;
|
||
|
|
||
|
ietfnc = ly_ctx_get_module_implemented(session->ctx, "ietf-netconf");
|
||
|
if (!ietfnc) {
|
||
|
nc_ctx_load_module(session, "ietf-netconf", NULL, modules, user_clb, user_data, has_get_schema, &ietfnc);
|
||
|
if (!ietfnc) {
|
||
|
lys_parse_mem(session->ctx, ietf_netconf_2013_09_29_yang, LYS_IN_YANG, &ietfnc);
|
||
|
}
|
||
|
}
|
||
|
if (!ietfnc) {
|
||
|
ERR(session, "Loading base NETCONF schema failed.");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/* set supported capabilities from ietf-netconf */
|
||
|
for (u = 0; modules[u].name; ++u) {
|
||
|
if (strcmp(modules[u].name, "ietf-netconf") || !modules[u].implemented) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
lys_set_implemented(ietfnc, (const char **)modules[u].features);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_ctx_check_and_fill(struct nc_session *session)
|
||
|
{
|
||
|
int i, get_schema_support = 0, yanglib_support = 0, get_data_support = 0, ret = -1;
|
||
|
ly_module_imp_clb old_clb = NULL;
|
||
|
void *old_data = NULL;
|
||
|
struct lys_module *mod = NULL;
|
||
|
char *revision;
|
||
|
struct schema_info *server_modules = NULL, *sm = NULL;
|
||
|
|
||
|
assert(session->opts.client.cpblts && session->ctx);
|
||
|
|
||
|
/* store the original user's callback, we will be switching between local search, get-schema and user callback */
|
||
|
old_clb = ly_ctx_get_module_imp_clb(session->ctx, &old_data);
|
||
|
|
||
|
/* switch off default searchpath to use only our callback integrating modifying searchpath algorithm to limit
|
||
|
* schemas only to those present on the server side */
|
||
|
ly_ctx_set_options(session->ctx, LY_CTX_DISABLE_SEARCHDIRS);
|
||
|
|
||
|
/* our callback is set later with appropriate data */
|
||
|
ly_ctx_set_module_imp_clb(session->ctx, NULL, NULL);
|
||
|
|
||
|
/* check if get-schema and yang-library is supported */
|
||
|
for (i = 0; session->opts.client.cpblts[i]; ++i) {
|
||
|
if (!strncmp(session->opts.client.cpblts[i], "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring?", 52)) {
|
||
|
get_schema_support = 1 + i;
|
||
|
if (yanglib_support) {
|
||
|
break;
|
||
|
}
|
||
|
} else if (!strncmp(session->opts.client.cpblts[i], "urn:ietf:params:netconf:capability:yang-library:", 48)) {
|
||
|
yanglib_support = 1 + i;
|
||
|
if (get_schema_support) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (get_schema_support) {
|
||
|
VRB(session, "Capability for <get-schema> support found.");
|
||
|
} else {
|
||
|
VRB(session, "Capability for <get-schema> support not found.");
|
||
|
}
|
||
|
if (yanglib_support) {
|
||
|
VRB(session, "Capability for yang-library support found.");
|
||
|
} else {
|
||
|
VRB(session, "Capability for yang-library support not found.");
|
||
|
}
|
||
|
|
||
|
/* get information about server's schemas from capabilities list until we will have yang-library */
|
||
|
if (build_schema_info_cpblts(session->opts.client.cpblts, &server_modules) || !server_modules) {
|
||
|
ERR(session, "Unable to get server's schema information from the <hello>'s capabilities.");
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
/* get-schema is supported, load local ietf-netconf-monitoring so we can create <get-schema> RPCs */
|
||
|
if (get_schema_support && lys_parse_mem(session->ctx, ietf_netconf_monitoring_2010_10_04_yang, LYS_IN_YANG, NULL)) {
|
||
|
WRN(session, "Loading NETCONF monitoring schema failed, cannot use <get-schema>.");
|
||
|
get_schema_support = 0;
|
||
|
}
|
||
|
|
||
|
/* load base model disregarding whether it's in capabilities (but NETCONF capabilities are used to enable features) */
|
||
|
if (nc_ctx_fill_ietf_netconf(session, server_modules, old_clb, old_data, get_schema_support)) {
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
/* get correct version of ietf-yang-library into context */
|
||
|
if (yanglib_support) {
|
||
|
/* use get schema to get server's ietf-yang-library */
|
||
|
revision = strstr(session->opts.client.cpblts[yanglib_support - 1], "revision=");
|
||
|
if (!revision) {
|
||
|
WRN(session, "Loading NETCONF ietf-yang-library schema failed, missing revision in NETCONF <hello> message.");
|
||
|
WRN(session, "Unable to automatically use <get-schema>.");
|
||
|
yanglib_support = 0;
|
||
|
} else {
|
||
|
revision = strndup(&revision[9], 10);
|
||
|
if (nc_ctx_load_module(session, "ietf-yang-library", revision, server_modules, old_clb, old_data,
|
||
|
get_schema_support, &mod)) {
|
||
|
WRN(session, "Loading NETCONF ietf-yang-library schema failed, unable to use it to learn all "
|
||
|
"the supported modules.");
|
||
|
yanglib_support = 0;
|
||
|
}
|
||
|
if (strcmp(revision, "2019-01-04") >= 0) {
|
||
|
/* we also need ietf-datastores to be implemented */
|
||
|
if (nc_ctx_load_module(session, "ietf-datastores", NULL, server_modules, old_clb, old_data,
|
||
|
get_schema_support, &mod)) {
|
||
|
WRN(session, "Loading NETCONF ietf-datastores schema failed, unable to use yang-library "
|
||
|
"to learn all the supported modules.");
|
||
|
yanglib_support = 0;
|
||
|
}
|
||
|
}
|
||
|
free(revision);
|
||
|
|
||
|
/* ietf-netconf-nmda is needed to issue get-data */
|
||
|
if (!nc_ctx_load_module(session, "ietf-netconf-nmda", NULL, server_modules, old_clb, old_data,
|
||
|
get_schema_support, &mod)) {
|
||
|
VRB(session, "Support for <get-data> from ietf-netcon-nmda found.");
|
||
|
get_data_support = 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* prepare structured information about server's schemas */
|
||
|
if (yanglib_support) {
|
||
|
if (build_schema_info_yl(session, get_data_support, &sm)) {
|
||
|
goto cleanup;
|
||
|
} else if (!sm) {
|
||
|
VRB(session, "Trying to use capabilities instead of ietf-yang-library data.");
|
||
|
} else {
|
||
|
/* prefer yang-library information, currently we have it from capabilities used for getting correct
|
||
|
* yang-library schema */
|
||
|
free_schema_info(server_modules);
|
||
|
server_modules = sm;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (nc_ctx_fill(session, server_modules, old_clb, old_data, get_schema_support)) {
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
/* succsess */
|
||
|
ret = 0;
|
||
|
|
||
|
if (session->flags & NC_SESSION_CLIENT_NOT_STRICT) {
|
||
|
WRN(session, "Some models failed to be loaded, any data from these models (and any other unknown) will "
|
||
|
"be ignored.");
|
||
|
}
|
||
|
|
||
|
cleanup:
|
||
|
free_schema_info(server_modules);
|
||
|
|
||
|
/* set user callback back */
|
||
|
ly_ctx_set_module_imp_clb(session->ctx, old_clb, old_data);
|
||
|
ly_ctx_unset_options(session->ctx, LY_CTX_DISABLE_SEARCHDIRS);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
API struct nc_session *
|
||
|
nc_connect_inout(int fdin, int fdout, struct ly_ctx *ctx)
|
||
|
{
|
||
|
struct nc_session *session;
|
||
|
|
||
|
if (fdin < 0) {
|
||
|
ERRARG("fdin");
|
||
|
return NULL;
|
||
|
} else if (fdout < 0) {
|
||
|
ERRARG("fdout");
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/* prepare session structure */
|
||
|
session = nc_new_session(NC_CLIENT, 0);
|
||
|
if (!session) {
|
||
|
ERRMEM;
|
||
|
return NULL;
|
||
|
}
|
||
|
session->status = NC_STATUS_STARTING;
|
||
|
|
||
|
/* transport specific data */
|
||
|
session->ti_type = NC_TI_FD;
|
||
|
session->ti.fd.in = fdin;
|
||
|
session->ti.fd.out = fdout;
|
||
|
|
||
|
if (nc_session_new_ctx(session, ctx) != EXIT_SUCCESS) {
|
||
|
goto fail;
|
||
|
}
|
||
|
ctx = session->ctx;
|
||
|
|
||
|
/* NETCONF handshake */
|
||
|
if (nc_handshake_io(session) != NC_MSG_HELLO) {
|
||
|
goto fail;
|
||
|
}
|
||
|
session->status = NC_STATUS_RUNNING;
|
||
|
|
||
|
if (nc_ctx_check_and_fill(session) == -1) {
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
return session;
|
||
|
|
||
|
fail:
|
||
|
nc_session_free(session, NULL);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
API struct nc_session *
|
||
|
nc_connect_unix(const char *address, struct ly_ctx *ctx)
|
||
|
{
|
||
|
struct nc_session *session = NULL;
|
||
|
struct sockaddr_un sun;
|
||
|
struct passwd *pw, pw_buf;
|
||
|
char *username;
|
||
|
int sock = -1;
|
||
|
char *buf = NULL;
|
||
|
size_t buf_size = 0;
|
||
|
|
||
|
if (address == NULL) {
|
||
|
ERRARG("address");
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
||
|
if (sock < 0) {
|
||
|
ERR(NULL, "Failed to create socket (%s).", strerror(errno));
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
memset(&sun, 0, sizeof(sun));
|
||
|
sun.sun_family = AF_UNIX;
|
||
|
snprintf(sun.sun_path, sizeof(sun.sun_path), "%s", address);
|
||
|
|
||
|
if (connect(sock, (struct sockaddr *)&sun, sizeof(sun)) < 0) {
|
||
|
ERR(NULL, "Cannot connect to sock server %s (%s)", address, strerror(errno));
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
if (fcntl(sock, F_SETFL, O_NONBLOCK) < 0) {
|
||
|
ERR(NULL, "fcntl failed (%s).", strerror(errno));
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
/* prepare session structure */
|
||
|
session = nc_new_session(NC_CLIENT, 0);
|
||
|
if (!session) {
|
||
|
ERRMEM;
|
||
|
goto fail;
|
||
|
}
|
||
|
session->status = NC_STATUS_STARTING;
|
||
|
|
||
|
/* transport specific data */
|
||
|
session->ti_type = NC_TI_UNIX;
|
||
|
session->ti.unixsock.sock = sock;
|
||
|
sock = -1; /* do not close sock in fail label anymore */
|
||
|
|
||
|
if (nc_session_new_ctx(session, ctx) != EXIT_SUCCESS) {
|
||
|
goto fail;
|
||
|
}
|
||
|
ctx = session->ctx;
|
||
|
|
||
|
lydict_insert(ctx, address, 0, &session->path);
|
||
|
|
||
|
pw = nc_getpwuid(geteuid(), &pw_buf, &buf, &buf_size);
|
||
|
if (!pw) {
|
||
|
ERR(NULL, "Failed to find username for UID %u.", (unsigned int)geteuid());
|
||
|
goto fail;
|
||
|
}
|
||
|
username = strdup(pw->pw_name);
|
||
|
free(buf);
|
||
|
if (!username) {
|
||
|
ERRMEM;
|
||
|
goto fail;
|
||
|
}
|
||
|
lydict_insert_zc(ctx, username, &session->username);
|
||
|
|
||
|
/* NETCONF handshake */
|
||
|
if (nc_handshake_io(session) != NC_MSG_HELLO) {
|
||
|
goto fail;
|
||
|
}
|
||
|
session->status = NC_STATUS_RUNNING;
|
||
|
|
||
|
if (nc_ctx_check_and_fill(session) == -1) {
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
return session;
|
||
|
|
||
|
fail:
|
||
|
nc_session_free(session, NULL);
|
||
|
if (sock >= 0) {
|
||
|
close(sock);
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
Helper for a non-blocking connect (which is required because of the locking
|
||
|
concept for e.g. call home settings). For more details see nc_sock_connect().
|
||
|
*/
|
||
|
static int
|
||
|
_non_blocking_connect(int timeout, int *sock_pending, struct addrinfo *res, struct nc_keepalives *ka)
|
||
|
{
|
||
|
int flags, ret, error;
|
||
|
int sock = -1;
|
||
|
fd_set wset;
|
||
|
struct timeval ts;
|
||
|
socklen_t len = sizeof(int);
|
||
|
struct in_addr *addr;
|
||
|
uint16_t port;
|
||
|
char str[INET6_ADDRSTRLEN];
|
||
|
|
||
|
if (sock_pending && (*sock_pending != -1)) {
|
||
|
VRB(NULL, "Trying to connect the pending socket %d.", *sock_pending);
|
||
|
sock = *sock_pending;
|
||
|
} else {
|
||
|
assert(res);
|
||
|
if (res->ai_family == AF_INET6) {
|
||
|
addr = (struct in_addr *) &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr;
|
||
|
port = ntohs(((struct sockaddr_in6 *)res->ai_addr)->sin6_port);
|
||
|
} else {
|
||
|
addr = &((struct sockaddr_in *)res->ai_addr)->sin_addr;
|
||
|
port = ntohs(((struct sockaddr_in *)res->ai_addr)->sin_port);
|
||
|
}
|
||
|
if (!inet_ntop(res->ai_family, addr, str, res->ai_addrlen)) {
|
||
|
WRN(NULL, "inet_ntop() failed (%s).", strerror(errno));
|
||
|
} else {
|
||
|
VRB(NULL, "Trying to connect via %s to %s:%u.", (res->ai_family == AF_INET6) ? "IPv6" : "IPv4", str, port);
|
||
|
}
|
||
|
|
||
|
/* connect to a server */
|
||
|
sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
|
||
|
if (sock == -1) {
|
||
|
ERR(NULL, "Socket could not be created (%s).", strerror(errno));
|
||
|
return -1;
|
||
|
}
|
||
|
/* make the socket non-blocking */
|
||
|
if (((flags = fcntl(sock, F_GETFL)) == -1) || (fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1)) {
|
||
|
ERR(NULL, "fcntl() failed (%s).", strerror(errno));
|
||
|
goto cleanup;
|
||
|
}
|
||
|
/* non-blocking connect! */
|
||
|
if (connect(sock, res->ai_addr, res->ai_addrlen) < 0) {
|
||
|
if (errno != EINPROGRESS) {
|
||
|
/* network connection failed, try another resource */
|
||
|
ERR(NULL, "connect() failed (%s).", strerror(errno));
|
||
|
goto cleanup;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
ts.tv_sec = timeout;
|
||
|
ts.tv_usec = 0;
|
||
|
|
||
|
FD_ZERO(&wset);
|
||
|
FD_SET(sock, &wset);
|
||
|
|
||
|
if ((ret = select(sock + 1, NULL, &wset, NULL, (timeout != -1) ? &ts : NULL)) < 0) {
|
||
|
ERR(NULL, "select() failed (%s).", strerror(errno));
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
if (ret == 0) {
|
||
|
/* there was a timeout */
|
||
|
VRB(NULL, "Timed out after %ds (%s).", timeout, strerror(errno));
|
||
|
if (sock_pending) {
|
||
|
/* no sock-close, we'll try it again */
|
||
|
*sock_pending = sock;
|
||
|
} else {
|
||
|
close(sock);
|
||
|
}
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/* check the usability of the socket */
|
||
|
error = 0;
|
||
|
if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
|
||
|
ERR(NULL, "getsockopt() failed (%s).", strerror(errno));
|
||
|
goto cleanup;
|
||
|
}
|
||
|
if (error) {
|
||
|
/* network connection failed, try another resource */
|
||
|
VRB(NULL, "getsockopt() error (%s).", strerror(error));
|
||
|
errno = error;
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
/* enable keep-alive */
|
||
|
if (nc_sock_enable_keepalive(sock, ka)) {
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
return sock;
|
||
|
|
||
|
cleanup:
|
||
|
if (sock_pending) {
|
||
|
*sock_pending = -1;
|
||
|
}
|
||
|
close(sock);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/* A given timeout value limits the time how long the function blocks. If it has to block
|
||
|
only for some seconds, a socket connection might not yet have been fully established.
|
||
|
Therefore the active (pending) socket will be stored in *sock_pending, but the return
|
||
|
value will be -1. In such a case a subsequent invokation is required, by providing the
|
||
|
stored sock_pending, again.
|
||
|
In general, if this function returns -1, when a timeout has been given, this function
|
||
|
has to be invoked, until it returns a valid socket.
|
||
|
*/
|
||
|
int
|
||
|
nc_sock_connect(const char *host, uint16_t port, int timeout, struct nc_keepalives *ka, int *sock_pending, char **ip_host)
|
||
|
{
|
||
|
int i, opt;
|
||
|
int sock = sock_pending ? *sock_pending : -1;
|
||
|
struct addrinfo hints, *res_list = NULL, *res;
|
||
|
char *buf, port_s[6]; /* length of string representation of short int */
|
||
|
void *addr;
|
||
|
|
||
|
DBG(NULL, "nc_sock_connect(%s, %u, %d, %d)", host, port, timeout, sock);
|
||
|
|
||
|
/* no pending socket */
|
||
|
if (sock == -1) {
|
||
|
/* connect to a server */
|
||
|
snprintf(port_s, 6, "%u", port);
|
||
|
memset(&hints, 0, sizeof hints);
|
||
|
hints.ai_family = AF_UNSPEC;
|
||
|
hints.ai_socktype = SOCK_STREAM;
|
||
|
hints.ai_protocol = IPPROTO_TCP;
|
||
|
i = getaddrinfo(host, port_s, &hints, &res_list);
|
||
|
if (i != 0) {
|
||
|
ERR(NULL, "Unable to translate the host address (%s).", gai_strerror(i));
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
for (res = res_list; res != NULL; res = res->ai_next) {
|
||
|
sock = _non_blocking_connect(timeout, sock_pending, res, ka);
|
||
|
if (sock == -1) {
|
||
|
if (!sock_pending || (*sock_pending == -1)) {
|
||
|
/* try the next resource */
|
||
|
continue;
|
||
|
} else {
|
||
|
/* timeout, keep pending socket */
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
VRB(NULL, "Successfully connected to %s:%s over %s.", host, port_s, (res->ai_family == AF_INET6) ? "IPv6" : "IPv4");
|
||
|
|
||
|
opt = 1;
|
||
|
if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof opt) == -1) {
|
||
|
ERR(NULL, "Could not set TCP_NODELAY socket option (%s).", strerror(errno));
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
if (ip_host && ((res->ai_family == AF_INET6) || (res->ai_family == AF_INET))) {
|
||
|
buf = malloc(INET6_ADDRSTRLEN);
|
||
|
if (!buf) {
|
||
|
ERRMEM;
|
||
|
goto error;
|
||
|
}
|
||
|
if (res->ai_family == AF_INET) {
|
||
|
addr = &((struct sockaddr_in *)res->ai_addr)->sin_addr;
|
||
|
} else {
|
||
|
addr = &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr;
|
||
|
}
|
||
|
if (!inet_ntop(res->ai_family, addr, buf, INET6_ADDRSTRLEN)) {
|
||
|
ERR(NULL, "Converting host to IP address failed (%s).", strerror(errno));
|
||
|
free(buf);
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
*ip_host = buf;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
freeaddrinfo(res_list);
|
||
|
|
||
|
} else {
|
||
|
/* try to get a connection with the pending socket */
|
||
|
assert(sock_pending);
|
||
|
sock = _non_blocking_connect(timeout, sock_pending, NULL, ka);
|
||
|
}
|
||
|
|
||
|
return sock;
|
||
|
|
||
|
error:
|
||
|
if (res_list) {
|
||
|
freeaddrinfo(res_list);
|
||
|
}
|
||
|
if (sock != -1) {
|
||
|
close(sock);
|
||
|
}
|
||
|
if (sock_pending) {
|
||
|
*sock_pending = -1;
|
||
|
}
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
#if defined (NC_ENABLED_SSH) || defined (NC_ENABLED_TLS)
|
||
|
|
||
|
int
|
||
|
nc_client_ch_add_bind_listen(const char *address, uint16_t port, NC_TRANSPORT_IMPL ti)
|
||
|
{
|
||
|
int sock;
|
||
|
|
||
|
if (!address) {
|
||
|
ERRARG("address");
|
||
|
return -1;
|
||
|
} else if (!port) {
|
||
|
ERRARG("port");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
sock = nc_sock_listen_inet(address, port, &client_opts.ka);
|
||
|
if (sock == -1) {
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
++client_opts.ch_bind_count;
|
||
|
client_opts.ch_binds = nc_realloc(client_opts.ch_binds, client_opts.ch_bind_count * sizeof *client_opts.ch_binds);
|
||
|
if (!client_opts.ch_binds) {
|
||
|
ERRMEM;
|
||
|
close(sock);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
client_opts.ch_bind_ti = nc_realloc(client_opts.ch_bind_ti, client_opts.ch_bind_count * sizeof *client_opts.ch_bind_ti);
|
||
|
if (!client_opts.ch_bind_ti) {
|
||
|
ERRMEM;
|
||
|
close(sock);
|
||
|
return -1;
|
||
|
}
|
||
|
client_opts.ch_bind_ti[client_opts.ch_bind_count - 1] = ti;
|
||
|
|
||
|
client_opts.ch_binds[client_opts.ch_bind_count - 1].address = strdup(address);
|
||
|
if (!client_opts.ch_binds[client_opts.ch_bind_count - 1].address) {
|
||
|
ERRMEM;
|
||
|
close(sock);
|
||
|
return -1;
|
||
|
}
|
||
|
client_opts.ch_binds[client_opts.ch_bind_count - 1].port = port;
|
||
|
client_opts.ch_binds[client_opts.ch_bind_count - 1].sock = sock;
|
||
|
client_opts.ch_binds[client_opts.ch_bind_count - 1].pollin = 0;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_client_ch_del_bind(const char *address, uint16_t port, NC_TRANSPORT_IMPL ti)
|
||
|
{
|
||
|
uint32_t i;
|
||
|
int ret = -1;
|
||
|
|
||
|
if (!address && !port && !ti) {
|
||
|
for (i = 0; i < client_opts.ch_bind_count; ++i) {
|
||
|
close(client_opts.ch_binds[i].sock);
|
||
|
free((char *)client_opts.ch_binds[i].address);
|
||
|
|
||
|
ret = 0;
|
||
|
}
|
||
|
free(client_opts.ch_binds);
|
||
|
client_opts.ch_binds = NULL;
|
||
|
client_opts.ch_bind_count = 0;
|
||
|
} else {
|
||
|
for (i = 0; i < client_opts.ch_bind_count; ++i) {
|
||
|
if ((!address || !strcmp(client_opts.ch_binds[i].address, address)) &&
|
||
|
(!port || (client_opts.ch_binds[i].port == port)) &&
|
||
|
(!ti || (client_opts.ch_bind_ti[i] == ti))) {
|
||
|
close(client_opts.ch_binds[i].sock);
|
||
|
free((char *)client_opts.ch_binds[i].address);
|
||
|
|
||
|
--client_opts.ch_bind_count;
|
||
|
if (!client_opts.ch_bind_count) {
|
||
|
free(client_opts.ch_binds);
|
||
|
client_opts.ch_binds = NULL;
|
||
|
} else if (i < client_opts.ch_bind_count) {
|
||
|
memcpy(&client_opts.ch_binds[i], &client_opts.ch_binds[client_opts.ch_bind_count], sizeof *client_opts.ch_binds);
|
||
|
client_opts.ch_bind_ti[i] = client_opts.ch_bind_ti[client_opts.ch_bind_count];
|
||
|
}
|
||
|
|
||
|
ret = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
API int
|
||
|
nc_accept_callhome(int timeout, struct ly_ctx *ctx, struct nc_session **session)
|
||
|
{
|
||
|
int sock;
|
||
|
char *host = NULL;
|
||
|
uint16_t port, idx;
|
||
|
|
||
|
if (!client_opts.ch_binds) {
|
||
|
ERRINIT;
|
||
|
return -1;
|
||
|
} else if (!session) {
|
||
|
ERRARG("session");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
sock = nc_sock_accept_binds(client_opts.ch_binds, client_opts.ch_bind_count, timeout, &host, &port, &idx);
|
||
|
|
||
|
if (sock < 1) {
|
||
|
free(host);
|
||
|
return sock;
|
||
|
}
|
||
|
|
||
|
#ifdef NC_ENABLED_SSH
|
||
|
if (client_opts.ch_bind_ti[idx] == NC_TI_LIBSSH) {
|
||
|
*session = nc_accept_callhome_ssh_sock(sock, host, port, ctx, NC_TRANSPORT_TIMEOUT);
|
||
|
} else
|
||
|
#endif
|
||
|
#ifdef NC_ENABLED_TLS
|
||
|
if (client_opts.ch_bind_ti[idx] == NC_TI_OPENSSL) {
|
||
|
*session = nc_accept_callhome_tls_sock(sock, host, port, ctx, NC_TRANSPORT_TIMEOUT);
|
||
|
} else
|
||
|
#endif
|
||
|
{
|
||
|
close(sock);
|
||
|
*session = NULL;
|
||
|
}
|
||
|
|
||
|
free(host);
|
||
|
|
||
|
if (!(*session)) {
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
#endif /* NC_ENABLED_SSH || NC_ENABLED_TLS */
|
||
|
|
||
|
API const char * const *
|
||
|
nc_session_get_cpblts(const struct nc_session *session)
|
||
|
{
|
||
|
if (!session) {
|
||
|
ERRARG("session");
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
return (const char * const *)session->opts.client.cpblts;
|
||
|
}
|
||
|
|
||
|
API const char *
|
||
|
nc_session_cpblt(const struct nc_session *session, const char *capab)
|
||
|
{
|
||
|
int i, len;
|
||
|
|
||
|
if (!session) {
|
||
|
ERRARG("session");
|
||
|
return NULL;
|
||
|
} else if (!capab) {
|
||
|
ERRARG("capab");
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
len = strlen(capab);
|
||
|
for (i = 0; session->opts.client.cpblts[i]; ++i) {
|
||
|
if (!strncmp(session->opts.client.cpblts[i], capab, len)) {
|
||
|
return session->opts.client.cpblts[i];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
API int
|
||
|
nc_session_ntf_thread_running(const struct nc_session *session)
|
||
|
{
|
||
|
if (!session || (session->side != NC_CLIENT)) {
|
||
|
ERRARG("session");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
return ATOMIC_LOAD_RELAXED(session->opts.client.ntf_thread) ? 1 : 0;
|
||
|
}
|
||
|
|
||
|
API void
|
||
|
nc_client_init(void)
|
||
|
{
|
||
|
nc_init();
|
||
|
}
|
||
|
|
||
|
API void
|
||
|
nc_client_destroy(void)
|
||
|
{
|
||
|
nc_client_set_schema_searchpath(NULL);
|
||
|
#if defined (NC_ENABLED_SSH) || defined (NC_ENABLED_TLS)
|
||
|
nc_client_ch_del_bind(NULL, 0, 0);
|
||
|
#endif
|
||
|
#ifdef NC_ENABLED_SSH
|
||
|
nc_client_ssh_destroy_opts();
|
||
|
#endif
|
||
|
#ifdef NC_ENABLED_TLS
|
||
|
nc_client_tls_destroy_opts();
|
||
|
#endif
|
||
|
nc_destroy();
|
||
|
}
|
||
|
|
||
|
static NC_MSG_TYPE
|
||
|
recv_reply_check_msgid(struct nc_session *session, const struct lyd_node *envp, uint64_t msgid)
|
||
|
{
|
||
|
char *ptr;
|
||
|
struct lyd_attr *attr;
|
||
|
uint64_t cur_msgid;
|
||
|
|
||
|
assert(envp && !envp->schema);
|
||
|
|
||
|
/* find the message-id attribute */
|
||
|
LY_LIST_FOR(((struct lyd_node_opaq *)envp)->attr, attr) {
|
||
|
if (!strcmp(attr->name.name, "message-id")) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!attr) {
|
||
|
ERR(session, "Received a <rpc-reply> without a message-id.");
|
||
|
return NC_MSG_REPLY_ERR_MSGID;
|
||
|
}
|
||
|
|
||
|
cur_msgid = strtoul(attr->value, &ptr, 10);
|
||
|
if (cur_msgid != msgid) {
|
||
|
ERR(session, "Received a <rpc-reply> with an unexpected message-id %" PRIu64 " (expected %" PRIu64 ").",
|
||
|
cur_msgid, msgid);
|
||
|
return NC_MSG_REPLY_ERR_MSGID;
|
||
|
}
|
||
|
|
||
|
return NC_MSG_REPLY;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @brief Used to roughly estimate the type of the message, does not actually parse or verify it.
|
||
|
*
|
||
|
* @param[in] session NETCONF session used to send error messages.
|
||
|
* @param[in] msg Message to check for type.
|
||
|
* @return NC_MSG_REPLY If format roughly matches a rpc-reply;
|
||
|
* @return NC_MSG_NOTIF If format roughly matches a notification;
|
||
|
* @return NC_MSG_ERROR If format is malformed or unrecognized.
|
||
|
*/
|
||
|
static NC_MSG_TYPE
|
||
|
get_msg_type(struct nc_session *session, struct ly_in *msg)
|
||
|
{
|
||
|
const char *str, *end;
|
||
|
|
||
|
str = ly_in_memory(msg, NULL);
|
||
|
|
||
|
while (*str) {
|
||
|
/* Skip whitespaces */
|
||
|
while (isspace(*str)) {
|
||
|
str++;
|
||
|
}
|
||
|
|
||
|
if (*str == '<') {
|
||
|
str++;
|
||
|
if (!strncmp(str, "!--", 3)) {
|
||
|
/* Skip comments */
|
||
|
end = "-->";
|
||
|
str = strstr(str, end);
|
||
|
} else if (!strncmp(str, "?xml", 4)) {
|
||
|
/* Skip xml declaration */
|
||
|
end = "?>";
|
||
|
str = strstr(str, end);
|
||
|
} else if (!strncmp(str, "rpc-reply", 9)) {
|
||
|
return NC_MSG_REPLY;
|
||
|
} else if (!strncmp(str, "notification", 12)) {
|
||
|
return NC_MSG_NOTIF;
|
||
|
} else {
|
||
|
ERR(session, "Unknown xml element '%.10s'.", str);
|
||
|
return NC_MSG_ERROR;
|
||
|
}
|
||
|
if (!str) {
|
||
|
/* No matching ending tag found */
|
||
|
ERR(session, "No matching ending tag '%s' found in xml message.", end);
|
||
|
return NC_MSG_ERROR;
|
||
|
}
|
||
|
str += strlen(end);
|
||
|
} else {
|
||
|
/* Not a valid xml */
|
||
|
ERR(session, "Unexpected character '%c' in xml message.", *str);
|
||
|
return NC_MSG_ERROR;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Unexpected end of message */
|
||
|
ERR(session, "Unexpected end of xml message.");
|
||
|
return NC_MSG_ERROR;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @brief Function to receive either replies or notifications.
|
||
|
*
|
||
|
* @param[in] session NETCONF session from which this function receives messages.
|
||
|
* @param[in] timeout Timeout for reading in milliseconds. Use negative value for infinite.
|
||
|
* @param[in] expected Type of the message the caller desired.
|
||
|
* @param[out] message If receiving a message succeeded this is the message, NULL otherwise.
|
||
|
* @return NC_MSG_REPLY If a rpc-reply was received;
|
||
|
* @return NC_MSG_NOTIF If a notification was received;
|
||
|
* @return NC_MSG_ERROR If any error occured;
|
||
|
* @return NC_MSG_WOULDBLOCK If the timeout was reached.
|
||
|
*/
|
||
|
static NC_MSG_TYPE
|
||
|
recv_msg(struct nc_session *session, int timeout, NC_MSG_TYPE expected, struct ly_in **message)
|
||
|
{
|
||
|
struct nc_msg_cont **cont_ptr;
|
||
|
struct ly_in *msg = NULL;
|
||
|
struct nc_msg_cont *cont, *prev;
|
||
|
NC_MSG_TYPE ret = NC_MSG_ERROR;
|
||
|
int r;
|
||
|
|
||
|
*message = NULL;
|
||
|
|
||
|
/* MSGS LOCK */
|
||
|
r = nc_session_client_msgs_lock(session, &timeout, __func__);
|
||
|
if (!r) {
|
||
|
ret = NC_MSG_WOULDBLOCK;
|
||
|
goto cleanup;
|
||
|
} else if (r == -1) {
|
||
|
ret = NC_MSG_ERROR;
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
/* Find the expected message in the buffer */
|
||
|
prev = NULL;
|
||
|
for (cont = session->opts.client.msgs; cont && (cont->type != expected); cont = cont->next) {
|
||
|
prev = cont;
|
||
|
}
|
||
|
|
||
|
if (cont) {
|
||
|
/* Remove found message from buffer */
|
||
|
if (prev) {
|
||
|
prev->next = cont->next;
|
||
|
} else {
|
||
|
session->opts.client.msgs = cont->next;
|
||
|
}
|
||
|
|
||
|
/* Use the buffer message */
|
||
|
ret = cont->type;
|
||
|
msg = cont->msg;
|
||
|
free(cont);
|
||
|
goto cleanup_unlock;
|
||
|
}
|
||
|
|
||
|
/* Read a message from the wire */
|
||
|
r = nc_read_msg_poll_io(session, timeout, &msg);
|
||
|
if (!r) {
|
||
|
ret = NC_MSG_WOULDBLOCK;
|
||
|
goto cleanup_unlock;
|
||
|
} else if (r == -1) {
|
||
|
ret = NC_MSG_ERROR;
|
||
|
goto cleanup_unlock;
|
||
|
}
|
||
|
|
||
|
/* Basic check to determine message type */
|
||
|
ret = get_msg_type(session, msg);
|
||
|
if (ret == NC_MSG_ERROR) {
|
||
|
goto cleanup_unlock;
|
||
|
}
|
||
|
|
||
|
/* If received a message of different type store it in the buffer */
|
||
|
if (ret != expected) {
|
||
|
cont_ptr = &session->opts.client.msgs;
|
||
|
while (*cont_ptr) {
|
||
|
cont_ptr = &((*cont_ptr)->next);
|
||
|
}
|
||
|
*cont_ptr = malloc(sizeof **cont_ptr);
|
||
|
if (!*cont_ptr) {
|
||
|
ERRMEM;
|
||
|
ret = NC_MSG_ERROR;
|
||
|
goto cleanup_unlock;
|
||
|
}
|
||
|
(*cont_ptr)->msg = msg;
|
||
|
msg = NULL;
|
||
|
(*cont_ptr)->type = ret;
|
||
|
(*cont_ptr)->next = NULL;
|
||
|
}
|
||
|
|
||
|
cleanup_unlock:
|
||
|
/* MSGS UNLOCK */
|
||
|
nc_session_client_msgs_unlock(session, __func__);
|
||
|
|
||
|
cleanup:
|
||
|
if (ret == expected) {
|
||
|
*message = msg;
|
||
|
} else {
|
||
|
ly_in_free(msg, 1);
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static NC_MSG_TYPE
|
||
|
recv_reply(struct nc_session *session, int timeout, struct lyd_node *op, uint64_t msgid, struct lyd_node **envp)
|
||
|
{
|
||
|
LY_ERR lyrc;
|
||
|
struct ly_in *msg = NULL;
|
||
|
NC_MSG_TYPE ret = NC_MSG_ERROR;
|
||
|
|
||
|
assert(op && (op->schema->nodetype & (LYS_RPC | LYS_ACTION)));
|
||
|
|
||
|
*envp = NULL;
|
||
|
|
||
|
/* Receive messages until a rpc-reply is found or a timeout or error reached */
|
||
|
ret = recv_msg(session, timeout, NC_MSG_REPLY, &msg);
|
||
|
if (ret != NC_MSG_REPLY) {
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
/* parse */
|
||
|
lyrc = lyd_parse_op(NULL, op, msg, LYD_XML, LYD_TYPE_REPLY_NETCONF, envp, NULL);
|
||
|
if (!lyrc) {
|
||
|
ret = recv_reply_check_msgid(session, *envp, msgid);
|
||
|
goto cleanup;
|
||
|
} else {
|
||
|
ERR(session, "Received an invalid message (%s).", ly_errmsg(LYD_CTX(op)));
|
||
|
ret = NC_MSG_ERROR;
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
cleanup:
|
||
|
ly_in_free(msg, 1);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
recv_reply_dup_rpc(struct nc_session *session, struct nc_rpc *rpc, struct lyd_node **op)
|
||
|
{
|
||
|
LY_ERR lyrc = LY_SUCCESS;
|
||
|
struct nc_rpc_act_generic *rpc_gen;
|
||
|
struct ly_in *in;
|
||
|
struct lyd_node *tree, *op2;
|
||
|
const struct lys_module *mod;
|
||
|
const char *module_name = NULL, *rpc_name = NULL, *module_check = NULL;
|
||
|
|
||
|
switch (rpc->type) {
|
||
|
case NC_RPC_ACT_GENERIC:
|
||
|
rpc_gen = (struct nc_rpc_act_generic *)rpc;
|
||
|
if (rpc_gen->has_data) {
|
||
|
tree = rpc_gen->content.data;
|
||
|
|
||
|
/* find the operation node */
|
||
|
lyrc = LY_EINVAL;
|
||
|
LYD_TREE_DFS_BEGIN(tree, op2) {
|
||
|
if (op2->schema->nodetype & (LYS_RPC | LYS_ACTION)) {
|
||
|
lyrc = lyd_dup_single(op2, NULL, 0, op);
|
||
|
break;
|
||
|
}
|
||
|
LYD_TREE_DFS_END(tree, op2);
|
||
|
}
|
||
|
} else {
|
||
|
ly_in_new_memory(rpc_gen->content.xml_str, &in);
|
||
|
lyrc = lyd_parse_op(session->ctx, NULL, in, LYD_XML, LYD_TYPE_RPC_YANG, &tree, &op2);
|
||
|
ly_in_free(in, 0);
|
||
|
if (lyrc) {
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/* we want just the operation node */
|
||
|
lyrc = lyd_dup_single(op2, NULL, 0, op);
|
||
|
|
||
|
lyd_free_tree(tree);
|
||
|
}
|
||
|
break;
|
||
|
case NC_RPC_GETCONFIG:
|
||
|
module_name = "ietf-netconf";
|
||
|
rpc_name = "get-config";
|
||
|
break;
|
||
|
case NC_RPC_EDIT:
|
||
|
module_name = "ietf-netconf";
|
||
|
rpc_name = "edit-config";
|
||
|
break;
|
||
|
case NC_RPC_COPY:
|
||
|
module_name = "ietf-netconf";
|
||
|
rpc_name = "copy-config";
|
||
|
break;
|
||
|
case NC_RPC_DELETE:
|
||
|
module_name = "ietf-netconf";
|
||
|
rpc_name = "delete-config";
|
||
|
break;
|
||
|
case NC_RPC_LOCK:
|
||
|
module_name = "ietf-netconf";
|
||
|
rpc_name = "lock";
|
||
|
break;
|
||
|
case NC_RPC_UNLOCK:
|
||
|
module_name = "ietf-netconf";
|
||
|
rpc_name = "unlock";
|
||
|
break;
|
||
|
case NC_RPC_GET:
|
||
|
module_name = "ietf-netconf";
|
||
|
rpc_name = "get";
|
||
|
break;
|
||
|
case NC_RPC_KILL:
|
||
|
module_name = "ietf-netconf";
|
||
|
rpc_name = "kill-session";
|
||
|
break;
|
||
|
case NC_RPC_COMMIT:
|
||
|
module_name = "ietf-netconf";
|
||
|
rpc_name = "commit";
|
||
|
break;
|
||
|
case NC_RPC_DISCARD:
|
||
|
module_name = "ietf-netconf";
|
||
|
rpc_name = "discard-changes";
|
||
|
break;
|
||
|
case NC_RPC_CANCEL:
|
||
|
module_name = "ietf-netconf";
|
||
|
rpc_name = "cancel-commit";
|
||
|
break;
|
||
|
case NC_RPC_VALIDATE:
|
||
|
module_name = "ietf-netconf";
|
||
|
rpc_name = "validate";
|
||
|
break;
|
||
|
case NC_RPC_GETSCHEMA:
|
||
|
module_name = "ietf-netconf-monitoring";
|
||
|
rpc_name = "get-schema";
|
||
|
break;
|
||
|
case NC_RPC_SUBSCRIBE:
|
||
|
module_name = "notifications";
|
||
|
rpc_name = "create-subscription";
|
||
|
break;
|
||
|
case NC_RPC_GETDATA:
|
||
|
module_name = "ietf-netconf-nmda";
|
||
|
rpc_name = "get-data";
|
||
|
break;
|
||
|
case NC_RPC_EDITDATA:
|
||
|
module_name = "ietf-netconf-nmda";
|
||
|
rpc_name = "edit-data";
|
||
|
break;
|
||
|
case NC_RPC_ESTABLISHSUB:
|
||
|
module_name = "ietf-subscribed-notifications";
|
||
|
rpc_name = "establish-subscription";
|
||
|
break;
|
||
|
case NC_RPC_MODIFYSUB:
|
||
|
module_name = "ietf-subscribed-notifications";
|
||
|
rpc_name = "modify-subscription";
|
||
|
break;
|
||
|
case NC_RPC_DELETESUB:
|
||
|
module_name = "ietf-subscribed-notifications";
|
||
|
rpc_name = "delete-subscription";
|
||
|
break;
|
||
|
case NC_RPC_KILLSUB:
|
||
|
module_name = "ietf-subscribed-notifications";
|
||
|
rpc_name = "kill-subscription";
|
||
|
break;
|
||
|
case NC_RPC_ESTABLISHPUSH:
|
||
|
module_name = "ietf-subscribed-notifications";
|
||
|
rpc_name = "establish-subscription";
|
||
|
module_check = "ietf-yang-push";
|
||
|
break;
|
||
|
case NC_RPC_MODIFYPUSH:
|
||
|
module_name = "ietf-subscribed-notifications";
|
||
|
rpc_name = "modify-subscription";
|
||
|
module_check = "ietf-yang-push";
|
||
|
break;
|
||
|
case NC_RPC_RESYNCSUB:
|
||
|
module_name = "ietf-yang-push";
|
||
|
rpc_name = "resync-subscription";
|
||
|
break;
|
||
|
case NC_RPC_UNKNOWN:
|
||
|
lyrc = LY_EINT;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (module_name && rpc_name) {
|
||
|
mod = ly_ctx_get_module_implemented(session->ctx, module_name);
|
||
|
if (!mod) {
|
||
|
ERR(session, "Missing \"%s\" schema in the context.", module_name);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/* create the operation node */
|
||
|
lyrc = lyd_new_inner(NULL, mod, rpc_name, 0, op);
|
||
|
}
|
||
|
if (module_check) {
|
||
|
if (!ly_ctx_get_module_implemented(session->ctx, module_check)) {
|
||
|
ERR(session, "Missing \"%s\" schema in the context.", module_check);
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (lyrc) {
|
||
|
return -1;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
API NC_MSG_TYPE
|
||
|
nc_recv_reply(struct nc_session *session, struct nc_rpc *rpc, uint64_t msgid, int timeout, struct lyd_node **envp,
|
||
|
struct lyd_node **op)
|
||
|
{
|
||
|
NC_MSG_TYPE ret;
|
||
|
|
||
|
if (!session) {
|
||
|
ERRARG("session");
|
||
|
return NC_MSG_ERROR;
|
||
|
} else if (!rpc) {
|
||
|
ERRARG("rpc");
|
||
|
return NC_MSG_ERROR;
|
||
|
} else if (!msgid) {
|
||
|
ERRARG("msgid");
|
||
|
return NC_MSG_ERROR;
|
||
|
} else if (!envp) {
|
||
|
ERRARG("envp");
|
||
|
return NC_MSG_ERROR;
|
||
|
} else if (!op) {
|
||
|
ERRARG("op");
|
||
|
return NC_MSG_ERROR;
|
||
|
} else if ((session->status != NC_STATUS_RUNNING) || (session->side != NC_CLIENT)) {
|
||
|
ERR(session, "Invalid session to receive RPC replies.");
|
||
|
return NC_MSG_ERROR;
|
||
|
}
|
||
|
|
||
|
/* get a duplicate of the RPC node to append reply to */
|
||
|
if (recv_reply_dup_rpc(session, rpc, op)) {
|
||
|
return NC_MSG_ERROR;
|
||
|
}
|
||
|
|
||
|
/* receive a reply */
|
||
|
ret = recv_reply(session, timeout, *op, msgid, envp);
|
||
|
|
||
|
/* do not return the RPC copy on error or if the reply includes no data */
|
||
|
if (((ret != NC_MSG_REPLY) && (ret != NC_MSG_REPLY_ERR_MSGID)) || !lyd_child(*op)) {
|
||
|
lyd_free_tree(*op);
|
||
|
*op = NULL;
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static NC_MSG_TYPE
|
||
|
recv_notif(struct nc_session *session, int timeout, struct lyd_node **envp, struct lyd_node **op)
|
||
|
{
|
||
|
LY_ERR lyrc;
|
||
|
struct ly_in *msg = NULL;
|
||
|
NC_MSG_TYPE ret = NC_MSG_ERROR;
|
||
|
|
||
|
*op = NULL;
|
||
|
*envp = NULL;
|
||
|
|
||
|
/* Receive messages until a notification is found or a timeout or error reached */
|
||
|
ret = recv_msg(session, timeout, NC_MSG_NOTIF, &msg);
|
||
|
if (ret != NC_MSG_NOTIF) {
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
/* Parse */
|
||
|
lyrc = lyd_parse_op(session->ctx, NULL, msg, LYD_XML, LYD_TYPE_NOTIF_NETCONF, envp, op);
|
||
|
if (!lyrc) {
|
||
|
goto cleanup;
|
||
|
} else {
|
||
|
ERR(session, "Received an invalid message (%s).", ly_errmsg(session->ctx));
|
||
|
ret = NC_MSG_ERROR;
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
cleanup:
|
||
|
ly_in_free(msg, 1);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
API NC_MSG_TYPE
|
||
|
nc_recv_notif(struct nc_session *session, int timeout, struct lyd_node **envp, struct lyd_node **op)
|
||
|
{
|
||
|
if (!session) {
|
||
|
ERRARG("session");
|
||
|
return NC_MSG_ERROR;
|
||
|
} else if (!envp) {
|
||
|
ERRARG("envp");
|
||
|
return NC_MSG_ERROR;
|
||
|
} else if (!op) {
|
||
|
ERRARG("op");
|
||
|
return NC_MSG_ERROR;
|
||
|
} else if ((session->status != NC_STATUS_RUNNING) || (session->side != NC_CLIENT)) {
|
||
|
ERR(session, "Invalid session to receive Notifications.");
|
||
|
return NC_MSG_ERROR;
|
||
|
}
|
||
|
|
||
|
/* receive a notification */
|
||
|
return recv_notif(session, timeout, envp, op);
|
||
|
}
|
||
|
|
||
|
static void *
|
||
|
nc_recv_notif_thread(void *arg)
|
||
|
{
|
||
|
struct nc_ntf_thread_arg *ntarg;
|
||
|
struct nc_session *session;
|
||
|
|
||
|
void (*notif_clb)(struct nc_session *session, const struct lyd_node *envp, const struct lyd_node *op);
|
||
|
struct lyd_node *envp, *op;
|
||
|
NC_MSG_TYPE msgtype;
|
||
|
|
||
|
/* detach ourselves */
|
||
|
pthread_detach(pthread_self());
|
||
|
|
||
|
ntarg = (struct nc_ntf_thread_arg *)arg;
|
||
|
session = ntarg->session;
|
||
|
notif_clb = ntarg->notif_clb;
|
||
|
free(ntarg);
|
||
|
|
||
|
while (ATOMIC_LOAD_RELAXED(session->opts.client.ntf_thread) == 1) {
|
||
|
msgtype = nc_recv_notif(session, NC_CLIENT_NOTIF_THREAD_SLEEP / 1000, &envp, &op);
|
||
|
if (msgtype == NC_MSG_NOTIF) {
|
||
|
notif_clb(session, envp, op);
|
||
|
if (!strcmp(op->schema->name, "notificationComplete") && !strcmp(op->schema->module->name, "nc-notifications")) {
|
||
|
lyd_free_tree(envp);
|
||
|
lyd_free_tree(op);
|
||
|
break;
|
||
|
}
|
||
|
lyd_free_tree(envp);
|
||
|
lyd_free_tree(op);
|
||
|
} else if ((msgtype == NC_MSG_ERROR) && (session->status != NC_STATUS_RUNNING)) {
|
||
|
/* quit this thread once the session is broken */
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
usleep(NC_CLIENT_NOTIF_THREAD_SLEEP);
|
||
|
}
|
||
|
|
||
|
VRB(session, "Notification thread exit.");
|
||
|
ATOMIC_STORE_RELAXED(session->opts.client.ntf_thread, 0);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
API int
|
||
|
nc_recv_notif_dispatch(struct nc_session *session, void (*notif_clb)(struct nc_session *session,
|
||
|
const struct lyd_node *envp, const struct lyd_node *op))
|
||
|
{
|
||
|
struct nc_ntf_thread_arg *ntarg;
|
||
|
pthread_t tid;
|
||
|
int ret;
|
||
|
|
||
|
if (!session) {
|
||
|
ERRARG("session");
|
||
|
return -1;
|
||
|
} else if (!notif_clb) {
|
||
|
ERRARG("notif_clb");
|
||
|
return -1;
|
||
|
} else if ((session->status != NC_STATUS_RUNNING) || (session->side != NC_CLIENT)) {
|
||
|
ERR(session, "Invalid session to receive Notifications.");
|
||
|
return -1;
|
||
|
} else if (ATOMIC_LOAD_RELAXED(session->opts.client.ntf_thread)) {
|
||
|
ERR(session, "Separate notification thread is already running.");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
ntarg = malloc(sizeof *ntarg);
|
||
|
if (!ntarg) {
|
||
|
ERRMEM;
|
||
|
return -1;
|
||
|
}
|
||
|
ntarg->session = session;
|
||
|
ntarg->notif_clb = notif_clb;
|
||
|
|
||
|
/* just so that nc_recv_notif_thread() does not immediately exit */
|
||
|
ATOMIC_STORE_RELAXED(session->opts.client.ntf_thread, 1);
|
||
|
|
||
|
ret = pthread_create(&tid, NULL, nc_recv_notif_thread, ntarg);
|
||
|
if (ret) {
|
||
|
ERR(session, "Failed to create a new thread (%s).", strerror(errno));
|
||
|
free(ntarg);
|
||
|
ATOMIC_STORE_RELAXED(session->opts.client.ntf_thread, 0);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const char *
|
||
|
nc_wd2str(NC_WD_MODE wd)
|
||
|
{
|
||
|
switch (wd) {
|
||
|
case NC_WD_ALL:
|
||
|
return "report-all";
|
||
|
case NC_WD_ALL_TAG:
|
||
|
return "report-all-tagged";
|
||
|
case NC_WD_TRIM:
|
||
|
return "trim";
|
||
|
case NC_WD_EXPLICIT:
|
||
|
return "explicit";
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
API NC_MSG_TYPE
|
||
|
nc_send_rpc(struct nc_session *session, struct nc_rpc *rpc, int timeout, uint64_t *msgid)
|
||
|
{
|
||
|
NC_MSG_TYPE r;
|
||
|
int dofree = 1;
|
||
|
struct ly_in *in;
|
||
|
struct nc_rpc_act_generic *rpc_gen;
|
||
|
struct nc_rpc_getconfig *rpc_gc;
|
||
|
struct nc_rpc_edit *rpc_e;
|
||
|
struct nc_rpc_copy *rpc_cp;
|
||
|
struct nc_rpc_delete *rpc_del;
|
||
|
struct nc_rpc_lock *rpc_lock;
|
||
|
struct nc_rpc_get *rpc_g;
|
||
|
struct nc_rpc_kill *rpc_k;
|
||
|
struct nc_rpc_commit *rpc_com;
|
||
|
struct nc_rpc_cancel *rpc_can;
|
||
|
struct nc_rpc_validate *rpc_val;
|
||
|
struct nc_rpc_getschema *rpc_gs;
|
||
|
struct nc_rpc_subscribe *rpc_sub;
|
||
|
struct nc_rpc_getdata *rpc_getd;
|
||
|
struct nc_rpc_editdata *rpc_editd;
|
||
|
struct nc_rpc_establishsub *rpc_estsub;
|
||
|
struct nc_rpc_modifysub *rpc_modsub;
|
||
|
struct nc_rpc_deletesub *rpc_delsub;
|
||
|
struct nc_rpc_killsub *rpc_killsub;
|
||
|
struct nc_rpc_establishpush *rpc_estpush;
|
||
|
struct nc_rpc_modifypush *rpc_modpush;
|
||
|
struct nc_rpc_resyncsub *rpc_resyncsub;
|
||
|
struct lyd_node *data = NULL, *node, *cont;
|
||
|
const struct lys_module *mod = NULL, *mod2 = NULL, *ietfncwd;
|
||
|
LY_ERR lyrc = 0;
|
||
|
int i;
|
||
|
char str[11];
|
||
|
uint64_t cur_msgid;
|
||
|
|
||
|
if (!session) {
|
||
|
ERRARG("session");
|
||
|
return NC_MSG_ERROR;
|
||
|
} else if (!rpc) {
|
||
|
ERRARG("rpc");
|
||
|
return NC_MSG_ERROR;
|
||
|
} else if (!msgid) {
|
||
|
ERRARG("msgid");
|
||
|
return NC_MSG_ERROR;
|
||
|
} else if ((session->status != NC_STATUS_RUNNING) || (session->side != NC_CLIENT)) {
|
||
|
ERR(session, "Invalid session to send RPCs.");
|
||
|
return NC_MSG_ERROR;
|
||
|
}
|
||
|
|
||
|
switch (rpc->type) {
|
||
|
case NC_RPC_ACT_GENERIC:
|
||
|
/* checked when parsing */
|
||
|
break;
|
||
|
case NC_RPC_GETCONFIG:
|
||
|
case NC_RPC_EDIT:
|
||
|
case NC_RPC_COPY:
|
||
|
case NC_RPC_DELETE:
|
||
|
case NC_RPC_LOCK:
|
||
|
case NC_RPC_UNLOCK:
|
||
|
case NC_RPC_GET:
|
||
|
case NC_RPC_KILL:
|
||
|
case NC_RPC_COMMIT:
|
||
|
case NC_RPC_DISCARD:
|
||
|
case NC_RPC_CANCEL:
|
||
|
case NC_RPC_VALIDATE:
|
||
|
mod = ly_ctx_get_module_implemented(session->ctx, "ietf-netconf");
|
||
|
if (!mod) {
|
||
|
ERR(session, "Missing \"ietf-netconf\" schema in the context.");
|
||
|
return NC_MSG_ERROR;
|
||
|
}
|
||
|
break;
|
||
|
case NC_RPC_GETSCHEMA:
|
||
|
mod = ly_ctx_get_module_implemented(session->ctx, "ietf-netconf-monitoring");
|
||
|
if (!mod) {
|
||
|
ERR(session, "Missing \"ietf-netconf-monitoring\" schema in the context.");
|
||
|
return NC_MSG_ERROR;
|
||
|
}
|
||
|
break;
|
||
|
case NC_RPC_SUBSCRIBE:
|
||
|
mod = ly_ctx_get_module_implemented(session->ctx, "notifications");
|
||
|
if (!mod) {
|
||
|
ERR(session, "Missing \"notifications\" schema in the context.");
|
||
|
return NC_MSG_ERROR;
|
||
|
}
|
||
|
break;
|
||
|
case NC_RPC_GETDATA:
|
||
|
case NC_RPC_EDITDATA:
|
||
|
mod = ly_ctx_get_module_implemented(session->ctx, "ietf-netconf-nmda");
|
||
|
if (!mod) {
|
||
|
ERR(session, "Missing \"ietf-netconf-nmda\" schema in the context.");
|
||
|
return NC_MSG_ERROR;
|
||
|
}
|
||
|
break;
|
||
|
case NC_RPC_ESTABLISHSUB:
|
||
|
case NC_RPC_MODIFYSUB:
|
||
|
case NC_RPC_DELETESUB:
|
||
|
case NC_RPC_KILLSUB:
|
||
|
mod = ly_ctx_get_module_implemented(session->ctx, "ietf-subscribed-notifications");
|
||
|
if (!mod) {
|
||
|
ERR(session, "Missing \"ietf-subscribed-notifications\" schema in the context.");
|
||
|
return NC_MSG_ERROR;
|
||
|
}
|
||
|
break;
|
||
|
case NC_RPC_ESTABLISHPUSH:
|
||
|
case NC_RPC_MODIFYPUSH:
|
||
|
mod = ly_ctx_get_module_implemented(session->ctx, "ietf-subscribed-notifications");
|
||
|
if (!mod) {
|
||
|
ERR(session, "Missing \"ietf-subscribed-notifications\" schema in the context.");
|
||
|
return NC_MSG_ERROR;
|
||
|
}
|
||
|
mod2 = ly_ctx_get_module_implemented(session->ctx, "ietf-yang-push");
|
||
|
if (!mod2) {
|
||
|
ERR(session, "Missing \"ietf-yang-push\" schema in the context.");
|
||
|
return NC_MSG_ERROR;
|
||
|
}
|
||
|
break;
|
||
|
case NC_RPC_RESYNCSUB:
|
||
|
mod = ly_ctx_get_module_implemented(session->ctx, "ietf-yang-push");
|
||
|
if (!mod) {
|
||
|
ERR(session, "Missing \"ietf-yang-push\" schema in the context.");
|
||
|
return NC_MSG_ERROR;
|
||
|
}
|
||
|
break;
|
||
|
case NC_RPC_UNKNOWN:
|
||
|
ERRINT;
|
||
|
return NC_MSG_ERROR;
|
||
|
}
|
||
|
|
||
|
#define CHECK_LYRC_BREAK(func_call) if ((lyrc = func_call)) break;
|
||
|
|
||
|
switch (rpc->type) {
|
||
|
case NC_RPC_ACT_GENERIC:
|
||
|
rpc_gen = (struct nc_rpc_act_generic *)rpc;
|
||
|
|
||
|
if (rpc_gen->has_data) {
|
||
|
data = rpc_gen->content.data;
|
||
|
dofree = 0;
|
||
|
} else {
|
||
|
ly_in_new_memory(rpc_gen->content.xml_str, &in);
|
||
|
lyrc = lyd_parse_op(session->ctx, NULL, in, LYD_XML, LYD_TYPE_RPC_YANG, &data, NULL);
|
||
|
ly_in_free(in, 0);
|
||
|
if (lyrc) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case NC_RPC_GETCONFIG:
|
||
|
rpc_gc = (struct nc_rpc_getconfig *)rpc;
|
||
|
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(NULL, mod, "get-config", 0, &data));
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(data, mod, "source", 0, &cont));
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(cont, mod, ncds2str[rpc_gc->source], NULL, 0, NULL));
|
||
|
if (rpc_gc->filter) {
|
||
|
if (!rpc_gc->filter[0] || (rpc_gc->filter[0] == '<')) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_any(data, mod, "filter", rpc_gc->filter, 0, LYD_ANYDATA_XML, 0, &node));
|
||
|
CHECK_LYRC_BREAK(lyd_new_meta(NULL, node, NULL, "ietf-netconf:type", "subtree", 0, NULL));
|
||
|
} else {
|
||
|
CHECK_LYRC_BREAK(lyd_new_any(data, mod, "filter", NULL, 0, LYD_ANYDATA_STRING, 0, &node));
|
||
|
CHECK_LYRC_BREAK(lyd_new_meta(NULL, node, NULL, "ietf-netconf:type", "xpath", 0, NULL));
|
||
|
CHECK_LYRC_BREAK(lyd_new_meta(NULL, node, NULL, "ietf-netconf:select", rpc_gc->filter, 0, NULL));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (rpc_gc->wd_mode) {
|
||
|
ietfncwd = ly_ctx_get_module_implemented(session->ctx, "ietf-netconf-with-defaults");
|
||
|
if (!ietfncwd) {
|
||
|
ERR(session, "Missing \"ietf-netconf-with-defaults\" schema in the context.", session->id);
|
||
|
lyrc = LY_ENOTFOUND;
|
||
|
break;
|
||
|
}
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, ietfncwd, "with-defaults", nc_wd2str(rpc_gc->wd_mode), 0, NULL));
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case NC_RPC_EDIT:
|
||
|
rpc_e = (struct nc_rpc_edit *)rpc;
|
||
|
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(NULL, mod, "edit-config", 0, &data));
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(data, mod, "target", 0, &cont));
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(cont, mod, ncds2str[rpc_e->target], NULL, 0, NULL));
|
||
|
|
||
|
if (rpc_e->default_op) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "default-operation", rpcedit_dfltop2str[rpc_e->default_op], 0, NULL));
|
||
|
}
|
||
|
if (rpc_e->test_opt) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "test-option", rpcedit_testopt2str[rpc_e->test_opt], 0, NULL));
|
||
|
}
|
||
|
if (rpc_e->error_opt) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "error-option", rpcedit_erropt2str[rpc_e->error_opt], 0, NULL));
|
||
|
}
|
||
|
if (!rpc_e->edit_cont[0] || (rpc_e->edit_cont[0] == '<')) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_any(data, mod, "config", rpc_e->edit_cont, 0, LYD_ANYDATA_XML, 0, NULL));
|
||
|
} else {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "url", rpc_e->edit_cont, 0, NULL));
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case NC_RPC_COPY:
|
||
|
rpc_cp = (struct nc_rpc_copy *)rpc;
|
||
|
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(NULL, mod, "copy-config", 0, &data));
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(data, mod, "target", 0, &cont));
|
||
|
if (rpc_cp->url_trg) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(cont, mod, "url", rpc_cp->url_trg, 0, NULL));
|
||
|
} else {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(cont, mod, ncds2str[rpc_cp->target], NULL, 0, NULL));
|
||
|
}
|
||
|
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(data, mod, "source", 0, &cont));
|
||
|
if (rpc_cp->url_config_src) {
|
||
|
if (!rpc_cp->url_config_src[0] || (rpc_cp->url_config_src[0] == '<')) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_any(cont, mod, "config", rpc_cp->url_config_src, 0, LYD_ANYDATA_XML, 0, NULL));
|
||
|
} else {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(cont, mod, "url", rpc_cp->url_config_src, 0, NULL));
|
||
|
}
|
||
|
} else {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(cont, mod, ncds2str[rpc_cp->source], NULL, 0, NULL));
|
||
|
}
|
||
|
|
||
|
if (rpc_cp->wd_mode) {
|
||
|
ietfncwd = ly_ctx_get_module_implemented(session->ctx, "ietf-netconf-with-defaults");
|
||
|
if (!ietfncwd) {
|
||
|
ERR(session, "Missing \"ietf-netconf-with-defaults\" schema in the context.", session->id);
|
||
|
lyrc = LY_ENOTFOUND;
|
||
|
break;
|
||
|
}
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, ietfncwd, "with-defaults", nc_wd2str(rpc_cp->wd_mode), 0, NULL));
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case NC_RPC_DELETE:
|
||
|
rpc_del = (struct nc_rpc_delete *)rpc;
|
||
|
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(NULL, mod, "delete-config", 0, &data));
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(data, mod, "target", 0, &cont));
|
||
|
if (rpc_del->url) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(cont, mod, "url", rpc_del->url, 0, NULL));
|
||
|
} else {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(cont, mod, ncds2str[rpc_del->target], NULL, 0, NULL));
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case NC_RPC_LOCK:
|
||
|
rpc_lock = (struct nc_rpc_lock *)rpc;
|
||
|
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(NULL, mod, "lock", 0, &data));
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(data, mod, "target", 0, &cont));
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(cont, mod, ncds2str[rpc_lock->target], NULL, 0, NULL));
|
||
|
break;
|
||
|
|
||
|
case NC_RPC_UNLOCK:
|
||
|
rpc_lock = (struct nc_rpc_lock *)rpc;
|
||
|
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(NULL, mod, "unlock", 0, &data));
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(data, mod, "target", 0, &cont));
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(cont, mod, ncds2str[rpc_lock->target], NULL, 0, NULL));
|
||
|
break;
|
||
|
|
||
|
case NC_RPC_GET:
|
||
|
rpc_g = (struct nc_rpc_get *)rpc;
|
||
|
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(NULL, mod, "get", 0, &data));
|
||
|
if (rpc_g->filter) {
|
||
|
if (!rpc_g->filter[0] || (rpc_g->filter[0] == '<')) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_any(data, mod, "filter", rpc_g->filter, 0, LYD_ANYDATA_XML, 0, &node));
|
||
|
CHECK_LYRC_BREAK(lyd_new_meta(NULL, node, NULL, "ietf-netconf:type", "subtree", 0, NULL));
|
||
|
} else {
|
||
|
CHECK_LYRC_BREAK(lyd_new_any(data, mod, "filter", NULL, 0, LYD_ANYDATA_STRING, 0, &node));
|
||
|
CHECK_LYRC_BREAK(lyd_new_meta(NULL, node, NULL, "ietf-netconf:type", "xpath", 0, NULL));
|
||
|
CHECK_LYRC_BREAK(lyd_new_meta(NULL, node, NULL, "ietf-netconf:select", rpc_g->filter, 0, NULL));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (rpc_g->wd_mode) {
|
||
|
ietfncwd = ly_ctx_get_module_implemented(session->ctx, "ietf-netconf-with-defaults");
|
||
|
if (!ietfncwd) {
|
||
|
ERR(session, "Missing \"ietf-netconf-with-defaults\" schema in the context.", session->id);
|
||
|
lyrc = LY_ENOTFOUND;
|
||
|
break;
|
||
|
}
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, ietfncwd, "with-defaults", nc_wd2str(rpc_g->wd_mode), 0, NULL));
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case NC_RPC_KILL:
|
||
|
rpc_k = (struct nc_rpc_kill *)rpc;
|
||
|
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(NULL, mod, "kill-session", 0, &data));
|
||
|
sprintf(str, "%u", rpc_k->sid);
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "session-id", str, 0, NULL));
|
||
|
break;
|
||
|
|
||
|
case NC_RPC_COMMIT:
|
||
|
rpc_com = (struct nc_rpc_commit *)rpc;
|
||
|
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(NULL, mod, "commit", 0, &data));
|
||
|
if (rpc_com->confirmed) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "confirmed", NULL, 0, NULL));
|
||
|
}
|
||
|
|
||
|
if (rpc_com->confirm_timeout) {
|
||
|
sprintf(str, "%u", rpc_com->confirm_timeout);
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "confirm-timeout", str, 0, NULL));
|
||
|
}
|
||
|
if (rpc_com->persist) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "persist", rpc_com->persist, 0, NULL));
|
||
|
}
|
||
|
if (rpc_com->persist_id) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "persist-id", rpc_com->persist_id, 0, NULL));
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case NC_RPC_DISCARD:
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(NULL, mod, "discard-changes", 0, &data));
|
||
|
break;
|
||
|
|
||
|
case NC_RPC_CANCEL:
|
||
|
rpc_can = (struct nc_rpc_cancel *)rpc;
|
||
|
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(NULL, mod, "cancel-commit", 0, &data));
|
||
|
if (rpc_can->persist_id) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "persist-id", rpc_can->persist_id, 0, NULL));
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case NC_RPC_VALIDATE:
|
||
|
rpc_val = (struct nc_rpc_validate *)rpc;
|
||
|
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(NULL, mod, "validate", 0, &data));
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(data, mod, "source", 0, &cont));
|
||
|
if (rpc_val->url_config_src) {
|
||
|
if (!rpc_val->url_config_src[0] || (rpc_val->url_config_src[0] == '<')) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_any(cont, mod, "config", rpc_val->url_config_src, 0, LYD_ANYDATA_XML, 0, NULL));
|
||
|
} else {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(cont, mod, "url", rpc_val->url_config_src, 0, NULL));
|
||
|
}
|
||
|
} else {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(cont, mod, ncds2str[rpc_val->source], NULL, 0, NULL));
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case NC_RPC_GETSCHEMA:
|
||
|
rpc_gs = (struct nc_rpc_getschema *)rpc;
|
||
|
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(NULL, mod, "get-schema", 0, &data));
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "identifier", rpc_gs->identifier, 0, NULL));
|
||
|
if (rpc_gs->version) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "version", rpc_gs->version, 0, NULL));
|
||
|
}
|
||
|
if (rpc_gs->format) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "format", rpc_gs->format, 0, NULL));
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case NC_RPC_SUBSCRIBE:
|
||
|
rpc_sub = (struct nc_rpc_subscribe *)rpc;
|
||
|
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(NULL, mod, "create-subscription", 0, &data));
|
||
|
if (rpc_sub->stream) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "stream", rpc_sub->stream, 0, NULL));
|
||
|
}
|
||
|
|
||
|
if (rpc_sub->filter) {
|
||
|
if (!rpc_sub->filter[0] || (rpc_sub->filter[0] == '<')) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_any(data, mod, "filter", rpc_sub->filter, 0, LYD_ANYDATA_XML, 0, &node));
|
||
|
CHECK_LYRC_BREAK(lyd_new_meta(NULL, node, NULL, "ietf-netconf:type", "subtree", 0, NULL));
|
||
|
} else {
|
||
|
CHECK_LYRC_BREAK(lyd_new_any(data, mod, "filter", NULL, 0, LYD_ANYDATA_STRING, 0, &node));
|
||
|
CHECK_LYRC_BREAK(lyd_new_meta(NULL, node, NULL, "ietf-netconf:type", "xpath", 0, NULL));
|
||
|
CHECK_LYRC_BREAK(lyd_new_meta(NULL, node, NULL, "ietf-netconf:select", rpc_sub->filter, 0, NULL));
|
||
|
}
|
||
|
}
|
||
|
if (rpc_sub->start) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "startTime", rpc_sub->start, 0, NULL));
|
||
|
}
|
||
|
if (rpc_sub->stop) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "stopTime", rpc_sub->stop, 0, NULL));
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case NC_RPC_GETDATA:
|
||
|
rpc_getd = (struct nc_rpc_getdata *)rpc;
|
||
|
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(NULL, mod, "get-data", 0, &data));
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "datastore", rpc_getd->datastore, 0, NULL));
|
||
|
|
||
|
if (rpc_getd->filter) {
|
||
|
if (!rpc_getd->filter[0] || (rpc_getd->filter[0] == '<')) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_any(data, mod, "subtree-filter", rpc_getd->filter, 0, LYD_ANYDATA_XML, 0, NULL));
|
||
|
} else {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "xpath-filter", rpc_getd->filter, 0, NULL));
|
||
|
}
|
||
|
}
|
||
|
if (rpc_getd->config_filter) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "config-filter", rpc_getd->config_filter, 0, NULL));
|
||
|
}
|
||
|
for (i = 0; i < rpc_getd->origin_filter_count; ++i) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, rpc_getd->negated_origin_filter ? "negated-origin-filter" :
|
||
|
"origin-filter", rpc_getd->origin_filter[i], 0, NULL));
|
||
|
}
|
||
|
if (rpc_getd->max_depth) {
|
||
|
sprintf(str, "%u", rpc_getd->max_depth);
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "max-depth", str, 0, NULL));
|
||
|
}
|
||
|
if (rpc_getd->with_origin) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "with-origin", NULL, 0, NULL));
|
||
|
}
|
||
|
|
||
|
if (rpc_getd->wd_mode) {
|
||
|
/* "with-defaults" are used from a grouping so it belongs to the ietf-netconf-nmda module */
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "with-defaults", nc_wd2str(rpc_getd->wd_mode), 0, NULL));
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case NC_RPC_EDITDATA:
|
||
|
rpc_editd = (struct nc_rpc_editdata *)rpc;
|
||
|
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(NULL, mod, "edit-data", 0, &data));
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "datastore", rpc_editd->datastore, 0, NULL));
|
||
|
|
||
|
if (rpc_editd->default_op) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "default-operation", rpcedit_dfltop2str[rpc_editd->default_op], 0,
|
||
|
NULL));
|
||
|
}
|
||
|
if (!rpc_editd->edit_cont[0] || (rpc_editd->edit_cont[0] == '<')) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_any(data, mod, "config", rpc_editd->edit_cont, 0, LYD_ANYDATA_XML, 0, NULL));
|
||
|
} else {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "url", rpc_editd->edit_cont, 0, NULL));
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case NC_RPC_ESTABLISHSUB:
|
||
|
rpc_estsub = (struct nc_rpc_establishsub *)rpc;
|
||
|
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(NULL, mod, "establish-subscription", 0, &data));
|
||
|
|
||
|
if (rpc_estsub->filter) {
|
||
|
if (!rpc_estsub->filter[0] || (rpc_estsub->filter[0] == '<')) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_any(data, mod, "stream-subtree-filter", rpc_estsub->filter, 0, LYD_ANYDATA_XML,
|
||
|
0, NULL));
|
||
|
} else if (rpc_estsub->filter[0] == '/') {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "stream-xpath-filter", rpc_estsub->filter, 0, NULL));
|
||
|
} else {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "stream-filter-name", rpc_estsub->filter, 0, NULL));
|
||
|
}
|
||
|
}
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "stream", rpc_estsub->stream, 0, NULL));
|
||
|
|
||
|
if (rpc_estsub->start) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "replay-start-time", rpc_estsub->start, 0, NULL));
|
||
|
}
|
||
|
if (rpc_estsub->stop) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "stop-time", rpc_estsub->stop, 0, NULL));
|
||
|
}
|
||
|
if (rpc_estsub->encoding) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "encoding", rpc_estsub->encoding, 0, NULL));
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case NC_RPC_MODIFYSUB:
|
||
|
rpc_modsub = (struct nc_rpc_modifysub *)rpc;
|
||
|
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(NULL, mod, "modify-subscription", 0, &data));
|
||
|
|
||
|
sprintf(str, "%u", rpc_modsub->id);
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "id", str, 0, NULL));
|
||
|
|
||
|
if (rpc_modsub->filter) {
|
||
|
if (!rpc_modsub->filter[0] || (rpc_modsub->filter[0] == '<')) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_any(data, mod, "stream-subtree-filter", rpc_modsub->filter, 0, LYD_ANYDATA_XML,
|
||
|
0, NULL));
|
||
|
} else if (rpc_modsub->filter[0] == '/') {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "stream-xpath-filter", rpc_modsub->filter, 0, NULL));
|
||
|
} else {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "stream-filter-name", rpc_modsub->filter, 0, NULL));
|
||
|
}
|
||
|
}
|
||
|
if (rpc_modsub->stop) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "stop-time", rpc_modsub->stop, 0, NULL));
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case NC_RPC_DELETESUB:
|
||
|
rpc_delsub = (struct nc_rpc_deletesub *)rpc;
|
||
|
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(NULL, mod, "delete-subscription", 0, &data));
|
||
|
|
||
|
sprintf(str, "%u", rpc_delsub->id);
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "id", str, 0, NULL));
|
||
|
break;
|
||
|
|
||
|
case NC_RPC_KILLSUB:
|
||
|
rpc_killsub = (struct nc_rpc_killsub *)rpc;
|
||
|
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(NULL, mod, "kill-subscription", 0, &data));
|
||
|
|
||
|
sprintf(str, "%u", rpc_killsub->id);
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "id", str, 0, NULL));
|
||
|
break;
|
||
|
|
||
|
case NC_RPC_ESTABLISHPUSH:
|
||
|
rpc_estpush = (struct nc_rpc_establishpush *)rpc;
|
||
|
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(NULL, mod, "establish-subscription", 0, &data));
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod2, "datastore", rpc_estpush->datastore, 0, NULL));
|
||
|
|
||
|
if (rpc_estpush->filter) {
|
||
|
if (!rpc_estpush->filter[0] || (rpc_estpush->filter[0] == '<')) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_any(data, mod2, "datastore-subtree-filter", rpc_estpush->filter, 0,
|
||
|
LYD_ANYDATA_XML, 0, NULL));
|
||
|
} else if (rpc_estpush->filter[0] == '/') {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod2, "datastore-xpath-filter", rpc_estpush->filter, 0, NULL));
|
||
|
} else {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod2, "selection-filter-ref", rpc_estpush->filter, 0, NULL));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (rpc_estpush->stop) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "stop-time", rpc_estpush->stop, 0, NULL));
|
||
|
}
|
||
|
if (rpc_estpush->encoding) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "encoding", rpc_estpush->encoding, 0, NULL));
|
||
|
}
|
||
|
|
||
|
if (rpc_estpush->periodic) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(data, mod2, "periodic", 0, &cont));
|
||
|
sprintf(str, "%" PRIu32, rpc_estpush->period);
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(cont, mod2, "period", str, 0, NULL));
|
||
|
if (rpc_estpush->anchor_time) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(cont, mod2, "anchor-time", rpc_estpush->anchor_time, 0, NULL));
|
||
|
}
|
||
|
} else {
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(data, mod2, "on-change", 0, &cont));
|
||
|
if (rpc_estpush->dampening_period) {
|
||
|
sprintf(str, "%" PRIu32, rpc_estpush->dampening_period);
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(cont, mod2, "dampening-period", str, 0, NULL));
|
||
|
}
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(cont, mod2, "sync-on-start", rpc_estpush->sync_on_start ? "true" : "false", 0,
|
||
|
NULL));
|
||
|
if (rpc_estpush->excluded_change) {
|
||
|
for (i = 0; rpc_estpush->excluded_change[i]; ++i) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(cont, mod2, "excluded-change", rpc_estpush->excluded_change[i], 0,
|
||
|
NULL));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case NC_RPC_MODIFYPUSH:
|
||
|
rpc_modpush = (struct nc_rpc_modifypush *)rpc;
|
||
|
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(NULL, mod, "modify-subscription", 0, &data));
|
||
|
|
||
|
sprintf(str, "%u", rpc_modpush->id);
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "id", str, 0, NULL));
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod2, "datastore", rpc_modpush->datastore, 0, NULL));
|
||
|
|
||
|
if (rpc_modpush->filter) {
|
||
|
if (!rpc_modpush->filter[0] || (rpc_modpush->filter[0] == '<')) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_any(data, mod2, "datastore-subtree-filter", rpc_modpush->filter, 0,
|
||
|
LYD_ANYDATA_XML, 0, NULL));
|
||
|
} else if (rpc_modpush->filter[0] == '/') {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod2, "datastore-xpath-filter", rpc_modpush->filter, 0, NULL));
|
||
|
} else {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod2, "selection-filter-ref", rpc_modpush->filter, 0, NULL));
|
||
|
}
|
||
|
}
|
||
|
if (rpc_modpush->stop) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "stop-time", rpc_modpush->stop, 0, NULL));
|
||
|
}
|
||
|
|
||
|
if (rpc_modpush->periodic) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(data, mod2, "periodic", 0, &cont));
|
||
|
sprintf(str, "%" PRIu32, rpc_modpush->period);
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(cont, mod2, "period", str, 0, NULL));
|
||
|
if (rpc_modpush->anchor_time) {
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(cont, mod2, "anchor-time", rpc_modpush->anchor_time, 0, NULL));
|
||
|
}
|
||
|
} else {
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(data, mod2, "on-change", 0, &cont));
|
||
|
if (rpc_modpush->dampening_period) {
|
||
|
sprintf(str, "%" PRIu32, rpc_modpush->dampening_period);
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(cont, mod2, "dampening-period", str, 0, NULL));
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case NC_RPC_RESYNCSUB:
|
||
|
rpc_resyncsub = (struct nc_rpc_resyncsub *)rpc;
|
||
|
|
||
|
CHECK_LYRC_BREAK(lyd_new_inner(NULL, mod, "resync-subscription", 0, &data));
|
||
|
sprintf(str, "%u", rpc_resyncsub->id);
|
||
|
CHECK_LYRC_BREAK(lyd_new_term(data, mod, "id", str, 0, NULL));
|
||
|
break;
|
||
|
|
||
|
case NC_RPC_UNKNOWN:
|
||
|
ERRINT;
|
||
|
return NC_MSG_ERROR;
|
||
|
}
|
||
|
|
||
|
#undef CHECK_LYRC_BREAK
|
||
|
|
||
|
if (lyrc) {
|
||
|
ERR(session, "Failed to create RPC, perhaps a required feature is disabled.");
|
||
|
lyd_free_tree(data);
|
||
|
return NC_MSG_ERROR;
|
||
|
}
|
||
|
|
||
|
/* send RPC, store its message ID */
|
||
|
r = nc_send_msg_io(session, timeout, data);
|
||
|
cur_msgid = session->opts.client.msgid;
|
||
|
|
||
|
if (dofree) {
|
||
|
lyd_free_tree(data);
|
||
|
}
|
||
|
|
||
|
if (r == NC_MSG_RPC) {
|
||
|
*msgid = cur_msgid;
|
||
|
}
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
API void
|
||
|
nc_client_session_set_not_strict(struct nc_session *session)
|
||
|
{
|
||
|
if (session->side != NC_CLIENT) {
|
||
|
ERRARG("session");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
session->flags |= NC_SESSION_CLIENT_NOT_STRICT;
|
||
|
}
|