2025-02-18 11:18:17 +01:00
|
|
|
/**
|
2025-02-18 11:33:30 +01:00
|
|
|
* @file session_client.c
|
|
|
|
* @author Michal Vasko <mvasko@cesnet.cz>
|
|
|
|
* @brief libnetconf2 session client functions
|
2025-02-18 11:18:17 +01:00
|
|
|
*
|
2025-02-18 11:33:30 +01:00
|
|
|
* @copyright
|
|
|
|
* Copyright (c) 2015 - 2021 CESNET, z.s.p.o.
|
2025-02-18 11:18:17 +01:00
|
|
|
*
|
|
|
|
* 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>
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
#ifdef NC_ENABLED_SSH_TLS
|
|
|
|
#include <libssh/libssh.h>
|
|
|
|
#endif
|
2025-02-18 11:18:17 +01:00
|
|
|
#include <libyang/libyang.h>
|
|
|
|
|
|
|
|
#include "compat.h"
|
2025-02-18 11:33:30 +01:00
|
|
|
#include "config.h"
|
|
|
|
#include "log_p.h"
|
|
|
|
#include "messages_p.h"
|
2025-02-18 11:18:17 +01:00
|
|
|
#include "session_client.h"
|
2025-02-18 11:33:30 +01:00
|
|
|
#include "session_client_ch.h"
|
|
|
|
#include "session_p.h"
|
2025-02-18 11:18:17 +01:00
|
|
|
|
|
|
|
#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"};
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
#ifdef NC_ENABLED_SSH_TLS
|
2025-02-18 11:18:17 +01:00
|
|
|
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);
|
2025-02-18 11:33:30 +01:00
|
|
|
#endif /* NC_ENABLED_SSH_TLS */
|
2025-02-18 11:18:17 +01:00
|
|
|
|
|
|
|
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
|
|
|
|
},
|
2025-02-18 11:33:30 +01:00
|
|
|
.opts.monitoring_thread_data.lock = PTHREAD_MUTEX_INITIALIZER,
|
|
|
|
#ifdef NC_ENABLED_SSH_TLS
|
2025-02-18 11:18:17 +01:00
|
|
|
.ssh_opts = {
|
2025-02-18 11:33:30 +01:00
|
|
|
.auth_pref = {{NC_SSH_AUTH_INTERACTIVE, 1}, {NC_SSH_AUTH_PASSWORD, 2}, {NC_SSH_AUTH_PUBLICKEY, 3}},
|
2025-02-18 11:18:17 +01:00
|
|
|
.auth_password = sshauth_password,
|
|
|
|
.auth_interactive = sshauth_interactive,
|
2025-02-18 11:33:30 +01:00
|
|
|
.auth_privkey_passphrase = sshauth_privkey_passphrase,
|
|
|
|
.knownhosts_mode = NC_SSH_KNOWNHOSTS_ASK
|
2025-02-18 11:18:17 +01:00
|
|
|
},
|
|
|
|
.ssh_ch_opts = {
|
|
|
|
.auth_pref = {{NC_SSH_AUTH_INTERACTIVE, 1}, {NC_SSH_AUTH_PASSWORD, 2}, {NC_SSH_AUTH_PUBLICKEY, 3}},
|
|
|
|
.auth_password = sshauth_password,
|
|
|
|
.auth_interactive = sshauth_interactive,
|
2025-02-18 11:33:30 +01:00
|
|
|
.auth_privkey_passphrase = sshauth_privkey_passphrase,
|
|
|
|
.knownhosts_mode = NC_SSH_KNOWNHOSTS_ASK
|
2025-02-18 11:18:17 +01:00
|
|
|
},
|
2025-02-18 11:33:30 +01:00
|
|
|
#endif /* NC_ENABLED_SSH_TLS */
|
2025-02-18 11:18:17 +01:00
|
|
|
/* .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);
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
#ifdef NC_ENABLED_SSH_TLS
|
2025-02-18 11:18:17 +01:00
|
|
|
int i;
|
2025-02-18 11:33:30 +01:00
|
|
|
|
2025-02-18 11:18:17 +01:00
|
|
|
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;
|
2025-02-18 11:33:30 +01:00
|
|
|
|
2025-02-18 11:18:17 +01:00
|
|
|
_nc_client_ssh_destroy_opts(&c->ssh_opts);
|
|
|
|
_nc_client_ssh_destroy_opts(&c->ssh_ch_opts);
|
2025-02-18 11:33:30 +01:00
|
|
|
|
2025-02-18 11:18:17 +01:00
|
|
|
_nc_client_tls_destroy_opts(&c->tls_opts);
|
|
|
|
_nc_client_tls_destroy_opts(&c->tls_ch_opts);
|
2025-02-18 11:33:30 +01:00
|
|
|
#endif /* NC_ENABLED_SSH_TLS */
|
2025-02-18 11:18:17 +01:00
|
|
|
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;
|
2025-02-18 11:33:30 +01:00
|
|
|
e->opts.ka.enabled = 1;
|
|
|
|
e->opts.ka.idle_time = 1;
|
|
|
|
e->opts.ka.max_probes = 10;
|
|
|
|
e->opts.ka.probe_interval = 5;
|
|
|
|
#ifdef NC_ENABLED_SSH_TLS
|
|
|
|
# ifdef HAVE_TERMIOS
|
|
|
|
e->ssh_opts.knownhosts_mode = NC_SSH_KNOWNHOSTS_ASK;
|
|
|
|
# else
|
|
|
|
e->ssh_opts.knownhosts_mode = NC_SSH_KNOWNHOSTS_ACCEPT;
|
|
|
|
# endif
|
2025-02-18 11:18:17 +01:00
|
|
|
e->ssh_opts.auth_pref[0].type = NC_SSH_AUTH_INTERACTIVE;
|
2025-02-18 11:33:30 +01:00
|
|
|
e->ssh_opts.auth_pref[0].value = 1;
|
2025-02-18 11:18:17 +01:00
|
|
|
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;
|
2025-02-18 11:33:30 +01:00
|
|
|
e->ssh_opts.auth_pref[2].value = 3;
|
2025-02-18 11:18:17 +01:00
|
|
|
e->ssh_opts.auth_password = sshauth_password;
|
|
|
|
e->ssh_opts.auth_interactive = sshauth_interactive;
|
|
|
|
e->ssh_opts.auth_privkey_passphrase = sshauth_privkey_passphrase;
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
/* callhome settings are the same */
|
2025-02-18 11:18:17 +01:00
|
|
|
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;
|
2025-02-18 11:33:30 +01:00
|
|
|
#endif /* NC_ENABLED_SSH_TLS */
|
2025-02-18 11:18:17 +01:00
|
|
|
}
|
2025-02-18 11:33:30 +01:00
|
|
|
|
|
|
|
/* init the monitoring thread data lock */
|
|
|
|
pthread_mutex_init(&e->opts.monitoring_thread_data.lock, NULL);
|
|
|
|
|
2025-02-18 11:18:17 +01:00
|
|
|
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) {
|
2025-02-18 11:33:30 +01:00
|
|
|
ERRARG(NULL, "context");
|
2025-02-18 11:18:17 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
/**
|
|
|
|
* @brief Ext data callback for a context to provide schema mount data.
|
|
|
|
*/
|
|
|
|
static LY_ERR
|
|
|
|
nc_ly_ext_data_clb(const struct lysc_ext_instance *ext, void *user_data, void **ext_data, ly_bool *ext_data_free)
|
|
|
|
{
|
|
|
|
struct nc_session *session = user_data;
|
|
|
|
|
|
|
|
if (strcmp(ext->def->module->name, "ietf-yang-schema-mount") || strcmp(ext->def->name, "mount-point")) {
|
|
|
|
return LY_EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!session->opts.client.ext_data) {
|
|
|
|
ERR(session, "Unable to parse mounted data, no operational schema-mounts data received from the server.");
|
|
|
|
return LY_ENOTFOUND;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* return ext data */
|
|
|
|
*ext_data = session->opts.client.ext_data;
|
|
|
|
*ext_data_free = 0;
|
|
|
|
|
|
|
|
return LY_SUCCESS;
|
|
|
|
}
|
|
|
|
|
2025-02-18 11:18:17 +01:00
|
|
|
int
|
2025-02-18 11:33:30 +01:00
|
|
|
nc_client_session_new_ctx(struct nc_session *session, struct ly_ctx *ctx)
|
2025-02-18 11:18:17 +01:00
|
|
|
{
|
|
|
|
/* assign context (dicionary needed for handshake) */
|
|
|
|
if (!ctx) {
|
|
|
|
if (ly_ctx_new(NULL, LY_CTX_NO_YANGLIBRARY, &ctx)) {
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
}
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
/* user path must be first, the first path is used to store modules retreived via get-schema */
|
2025-02-18 11:18:17 +01:00
|
|
|
if (client_opts.schema_searchpath) {
|
|
|
|
ly_ctx_set_searchdir(ctx, client_opts.schema_searchpath);
|
2025-02-18 11:33:30 +01:00
|
|
|
}
|
|
|
|
if (!access(NC_CLIENT_SEARCH_DIR, F_OK)) {
|
|
|
|
ly_ctx_set_searchdir(ctx, NC_CLIENT_SEARCH_DIR);
|
2025-02-18 11:18:17 +01:00
|
|
|
}
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
/* set callback for getting modules, if provided */
|
2025-02-18 11:18:17 +01:00
|
|
|
ly_ctx_set_module_imp_clb(ctx, client_opts.schema_clb, client_opts.schema_clb_data);
|
2025-02-18 11:33:30 +01:00
|
|
|
|
|
|
|
/* set ext data callback to avoid errors that no callback is set, the data are stored later, if any */
|
|
|
|
ly_ctx_set_ext_data_clb(ctx, nc_ly_ext_data_clb, session);
|
2025-02-18 11:18:17 +01:00
|
|
|
} 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);
|
2025-02-18 11:33:30 +01:00
|
|
|
NC_CHECK_ERRMEM_RET(!client_opts.schema_searchpath, 1);
|
2025-02-18 11:18:17 +01:00
|
|
|
} 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;
|
|
|
|
}
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
API void
|
|
|
|
nc_client_set_new_session_context_autofill(int enabled)
|
|
|
|
{
|
|
|
|
client_opts.auto_context_fill_disabled = !enabled;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct module_info {
|
2025-02-18 11:18:17 +01:00
|
|
|
char *name;
|
|
|
|
char *revision;
|
2025-02-18 11:33:30 +01:00
|
|
|
|
2025-02-18 11:18:17 +01:00
|
|
|
struct {
|
|
|
|
char *name;
|
|
|
|
char *revision;
|
|
|
|
} *submodules;
|
|
|
|
char **features;
|
|
|
|
int implemented;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct clb_data_s {
|
|
|
|
void *user_data;
|
|
|
|
ly_module_imp_clb user_clb;
|
2025-02-18 11:33:30 +01:00
|
|
|
struct module_info *modules;
|
2025-02-18 11:18:17 +01:00
|
|
|
struct nc_session *session;
|
|
|
|
int has_get_schema;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2025-02-18 11:33:30 +01:00
|
|
|
* @brief Retrieve YANG module content from a local file.
|
2025-02-18 11:18:17 +01:00
|
|
|
*
|
2025-02-18 11:33:30 +01:00
|
|
|
* @param[in] name Module name.
|
|
|
|
* @param[in] rev Module revision.
|
2025-02-18 11:18:17 +01:00
|
|
|
* @param[in] clb_data get-schema callback data.
|
2025-02-18 11:33:30 +01:00
|
|
|
* @param[out] format Module format.
|
|
|
|
* @return Module content.
|
2025-02-18 11:18:17 +01:00
|
|
|
*/
|
|
|
|
static char *
|
2025-02-18 11:33:30 +01:00
|
|
|
retrieve_module_data_localfile(const char *name, const char *rev, struct clb_data_s *clb_data,
|
2025-02-18 11:18:17 +01:00
|
|
|
LYS_INFORMAT *format)
|
|
|
|
{
|
2025-02-18 11:33:30 +01:00
|
|
|
char *localfile = NULL, *model_data = NULL;
|
|
|
|
const char *ptr;
|
2025-02-18 11:18:17 +01:00
|
|
|
FILE *f;
|
|
|
|
long length, l;
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2025-02-18 11:33:30 +01:00
|
|
|
if (localfile && rev) {
|
|
|
|
ptr = strrchr(localfile, '/');
|
|
|
|
if (!strchr(ptr, '@')) {
|
|
|
|
/* we do not know the revision of the module and we require a specific one, so ignore this module */
|
|
|
|
localfile = NULL;
|
2025-02-18 11:18:17 +01:00
|
|
|
}
|
2025-02-18 11:33:30 +01:00
|
|
|
}
|
2025-02-18 11:18:17 +01:00
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
if (!localfile) {
|
|
|
|
return NULL;
|
|
|
|
}
|
2025-02-18 11:18:17 +01:00
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
VRB(clb_data->session, "Reading module \"%s@%s\" from local file \"%s\".", name, rev ? rev : "<latest>",
|
|
|
|
localfile);
|
|
|
|
f = fopen(localfile, "r");
|
|
|
|
if (!f) {
|
|
|
|
ERR(clb_data->session, "Unable to open file \"%s\" (%s).", localfile, strerror(errno));
|
2025-02-18 11:18:17 +01:00
|
|
|
free(localfile);
|
2025-02-18 11:33:30 +01:00
|
|
|
return NULL;
|
2025-02-18 11:18:17 +01:00
|
|
|
}
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
fseek(f, 0, SEEK_END);
|
|
|
|
length = ftell(f);
|
|
|
|
if (length < 0) {
|
|
|
|
ERR(clb_data->session, "Unable to get the size of module 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 module 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);
|
|
|
|
|
2025-02-18 11:18:17 +01:00
|
|
|
return model_data;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2025-02-18 11:33:30 +01:00
|
|
|
* @brief Retrieve YANG module content from a reply to get-schema RPC.
|
2025-02-18 11:18:17 +01:00
|
|
|
*
|
2025-02-18 11:33:30 +01:00
|
|
|
* @param[in] name Module name.
|
|
|
|
* @param[in] rev Module revision.
|
2025-02-18 11:18:17 +01:00
|
|
|
* @param[in] clb_data get-schema callback data.
|
2025-02-18 11:33:30 +01:00
|
|
|
* @param[out] format Module format.
|
|
|
|
* @return Module content.
|
2025-02-18 11:18:17 +01:00
|
|
|
*/
|
|
|
|
static char *
|
2025-02-18 11:33:30 +01:00
|
|
|
retrieve_module_data_getschema(const char *name, const char *rev, struct clb_data_s *clb_data,
|
2025-02-18 11:18:17 +01:00
|
|
|
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;
|
2025-02-18 11:33:30 +01:00
|
|
|
char *localfile = NULL, *envp_str = NULL, *model_data = NULL;
|
2025-02-18 11:18:17 +01:00
|
|
|
FILE *f;
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
VRB(clb_data->session, "Reading module \"%s@%s\" from server via get-schema.", name, rev ? rev : "<latest>");
|
2025-02-18 11:18:17 +01:00
|
|
|
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;
|
2025-02-18 11:33:30 +01:00
|
|
|
} else if (msg == NC_MSG_ERROR) {
|
2025-02-18 11:18:17 +01:00
|
|
|
ERR(clb_data->session, "Failed to receive a reply to <get-schema>.");
|
|
|
|
goto cleanup;
|
2025-02-18 11:33:30 +01:00
|
|
|
} else if (!op) {
|
|
|
|
assert(envp);
|
|
|
|
lyd_print_mem(&envp_str, envp, LYD_XML, 0);
|
|
|
|
WRN(clb_data->session, "Received an unexpected reply to <get-schema>:\n%s", envp_str);
|
|
|
|
free(envp_str);
|
|
|
|
goto cleanup;
|
2025-02-18 11:18:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2025-02-18 11:33:30 +01:00
|
|
|
if (!model_data) {
|
|
|
|
goto cleanup;
|
|
|
|
}
|
2025-02-18 11:18:17 +01:00
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
/* set format */
|
|
|
|
*format = LYS_IN_YANG;
|
|
|
|
|
|
|
|
/* try to store the model_data into local module repository */
|
|
|
|
lys_search_localfile(ly_ctx_get_searchdirs(clb_data->session->ctx), 0, name, rev, &localfile, NULL);
|
|
|
|
if (client_opts.schema_searchpath && !localfile) {
|
|
|
|
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 module retrieved via <get-schema> (%s).",
|
|
|
|
localfile, strerror(errno));
|
2025-02-18 11:18:17 +01:00
|
|
|
} else {
|
2025-02-18 11:33:30 +01:00
|
|
|
fputs(model_data, f);
|
|
|
|
fclose(f);
|
2025-02-18 11:18:17 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2025-02-18 11:33:30 +01:00
|
|
|
free(localfile);
|
2025-02-18 11:18:17 +01:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2025-02-18 11:33:30 +01:00
|
|
|
* @brief Retrieve YANG module content.
|
|
|
|
*
|
|
|
|
* @param[in] mod_name Module name.
|
|
|
|
* @param[in] mod_rev Module revision.
|
|
|
|
* @param[in] user_data get-schema callback data.
|
|
|
|
* @param[out] format Module format.
|
|
|
|
* @param[out] module_data Module content.
|
|
|
|
* @param[out] free_module_data Callback for freeing @p module_data.
|
|
|
|
* @return LY_ERR value.
|
|
|
|
*/
|
|
|
|
static LY_ERR
|
|
|
|
retrieve_module_data(const char *mod_name, const char *mod_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;
|
|
|
|
char *model_data = NULL;
|
|
|
|
|
|
|
|
/* 1. try to get data locally */
|
|
|
|
model_data = retrieve_module_data_localfile(mod_name, mod_rev, clb_data, format);
|
|
|
|
|
|
|
|
/* 2. try to use <get-schema> */
|
|
|
|
if (!model_data && clb_data->has_get_schema) {
|
|
|
|
model_data = retrieve_module_data_getschema(mod_name, mod_rev, clb_data, format);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 3. try to use user callback */
|
|
|
|
if (!model_data && clb_data->user_clb) {
|
|
|
|
VRB(clb_data->session, "Reading module \"%s@%s\" via user callback.", mod_name, mod_rev ? mod_rev : "<latest>");
|
|
|
|
clb_data->user_clb(mod_name, mod_rev, NULL, NULL, 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 Retrieve YANG import module content.
|
2025-02-18 11:18:17 +01:00
|
|
|
*
|
2025-02-18 11:33:30 +01:00
|
|
|
* @param[in] mod_name Module name.
|
|
|
|
* @param[in] mod_rev Module revision.
|
2025-02-18 11:18:17 +01:00
|
|
|
* @param[in] submod_name Optional submodule name.
|
|
|
|
* @param[in] sub_rev Submodule revision.
|
|
|
|
* @param[in] user_data get-schema callback data.
|
2025-02-18 11:33:30 +01:00
|
|
|
* @param[out] format Module format.
|
|
|
|
* @param[out] module_data Module content.
|
2025-02-18 11:18:17 +01:00
|
|
|
* @param[out] free_module_data Callback for freeing @p module_data.
|
|
|
|
* @return LY_ERR value.
|
|
|
|
*/
|
|
|
|
static LY_ERR
|
2025-02-18 11:33:30 +01:00
|
|
|
retrieve_module_data_imp(const char *mod_name, const char *mod_rev, const char *submod_name, const char *sub_rev,
|
2025-02-18 11:18:17 +01:00
|
|
|
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;
|
2025-02-18 11:33:30 +01:00
|
|
|
uint32_t u, v, match = 1;
|
2025-02-18 11:18:17 +01:00
|
|
|
const char *name = NULL, *rev = NULL;
|
|
|
|
char *model_data = NULL;
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
/* get and check the final name and revision of the module to be retrieved */
|
2025-02-18 11:18:17 +01:00
|
|
|
if (!mod_rev || !mod_rev[0]) {
|
|
|
|
/* newest revision requested - get the newest revision from the list of available modules on server */
|
|
|
|
match = 0;
|
2025-02-18 11:33:30 +01:00
|
|
|
for (u = 0; clb_data->modules[u].name; ++u) {
|
|
|
|
if (strcmp(mod_name, clb_data->modules[u].name)) {
|
2025-02-18 11:18:17 +01:00
|
|
|
continue;
|
|
|
|
}
|
2025-02-18 11:33:30 +01:00
|
|
|
if (!match || (strcmp(mod_rev, clb_data->modules[u].revision) > 0)) {
|
|
|
|
mod_rev = clb_data->modules[u].revision;
|
2025-02-18 11:18:17 +01:00
|
|
|
}
|
|
|
|
match = u + 1;
|
|
|
|
}
|
|
|
|
if (!match) {
|
2025-02-18 11:33:30 +01:00
|
|
|
/* valid situation if we are retrieving YANG 1.1 module and have only capabilities for now
|
2025-02-18 11:18:17 +01:00
|
|
|
* (when loading ietf-datastore for ietf-yang-library) */
|
2025-02-18 11:33:30 +01:00
|
|
|
VRB(clb_data->session, "Unable to identify revision of the import module \"%s\" from "
|
2025-02-18 11:18:17 +01:00
|
|
|
"the available server side information.", mod_name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (submod_name) {
|
|
|
|
name = submod_name;
|
|
|
|
if (sub_rev) {
|
|
|
|
rev = sub_rev;
|
|
|
|
} else if (match) {
|
2025-02-18 11:33:30 +01:00
|
|
|
if (!clb_data->modules[match - 1].submodules) {
|
2025-02-18 11:18:17 +01:00
|
|
|
VRB(clb_data->session, "Unable to identify revision of the requested submodule \"%s\", "
|
2025-02-18 11:33:30 +01:00
|
|
|
"in import module \"%s\", from the available server side information.", submod_name, mod_name);
|
2025-02-18 11:18:17 +01:00
|
|
|
} else {
|
2025-02-18 11:33:30 +01:00
|
|
|
for (v = 0; clb_data->modules[match - 1].submodules[v].name; ++v) {
|
|
|
|
if (!strcmp(submod_name, clb_data->modules[match - 1].submodules[v].name)) {
|
|
|
|
rev = sub_rev = clb_data->modules[match - 1].submodules[v].revision;
|
2025-02-18 11:18:17 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!rev) {
|
2025-02-18 11:33:30 +01:00
|
|
|
ERR(clb_data->session, "Requested submodule \"%s\" is not found in import module \"%s\" on server side.",
|
2025-02-18 11:18:17 +01:00
|
|
|
submod_name, mod_name);
|
|
|
|
return LY_ENOTFOUND;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
name = mod_name;
|
|
|
|
rev = mod_rev;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (match) {
|
2025-02-18 11:33:30 +01:00
|
|
|
/* we have enough information to avoid communication with server and try to get the module locally */
|
2025-02-18 11:18:17 +01:00
|
|
|
|
|
|
|
/* 1. try to get data locally */
|
2025-02-18 11:33:30 +01:00
|
|
|
model_data = retrieve_module_data_localfile(name, rev, clb_data, format);
|
2025-02-18 11:18:17 +01:00
|
|
|
|
|
|
|
/* 2. try to use <get-schema> */
|
|
|
|
if (!model_data && clb_data->has_get_schema) {
|
2025-02-18 11:33:30 +01:00
|
|
|
model_data = retrieve_module_data_getschema(name, rev, clb_data, format);
|
2025-02-18 11:18:17 +01:00
|
|
|
}
|
|
|
|
} else {
|
2025-02-18 11:33:30 +01:00
|
|
|
/* we are unsure which revision of the module we should load, so first try to get
|
2025-02-18 11:18:17 +01:00
|
|
|
* 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) {
|
2025-02-18 11:33:30 +01:00
|
|
|
model_data = retrieve_module_data_getschema(name, rev, clb_data, format);
|
2025-02-18 11:18:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* 2. try to get data locally */
|
|
|
|
if (!model_data) {
|
2025-02-18 11:33:30 +01:00
|
|
|
model_data = retrieve_module_data_localfile(name, rev, clb_data, format);
|
2025-02-18 11:18:17 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 3. try to use user callback */
|
|
|
|
if (!model_data && clb_data->user_clb) {
|
2025-02-18 11:33:30 +01:00
|
|
|
VRB(clb_data->session, "Reading module \"%s@%s\" via user callback.", name, rev ? rev : "<latest>");
|
2025-02-18 11:18:17 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2025-02-18 11:33:30 +01:00
|
|
|
* @brief Load a YANG module into context.
|
2025-02-18 11:18:17 +01:00
|
|
|
*
|
|
|
|
* @param[in] session NC session.
|
2025-02-18 11:33:30 +01:00
|
|
|
* @param[in] name Module name.
|
|
|
|
* @param[in] revision Module revision.
|
|
|
|
* @param[in] features Enabled module features.
|
|
|
|
* @param[in] modules Server module info built from capabilities.
|
|
|
|
* @param[in] user_clb User callback for retrieving module data.
|
2025-02-18 11:18:17 +01:00
|
|
|
* @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
|
2025-02-18 11:33:30 +01:00
|
|
|
nc_ctx_load_module(struct nc_session *session, const char *name, const char *revision, const char **features,
|
|
|
|
struct module_info *modules, ly_module_imp_clb user_clb, void *user_data, int has_get_schema, struct lys_module **mod)
|
2025-02-18 11:18:17 +01:00
|
|
|
{
|
|
|
|
int ret = 0;
|
2025-02-18 11:33:30 +01:00
|
|
|
const struct ly_err_item *eitem;
|
2025-02-18 11:18:17 +01:00
|
|
|
const char *module_data = NULL;
|
2025-02-18 11:33:30 +01:00
|
|
|
struct ly_in *in;
|
2025-02-18 11:18:17 +01:00
|
|
|
LYS_INFORMAT format;
|
2025-02-18 11:33:30 +01:00
|
|
|
uint32_t temp_lo = LY_LOSTORE, *prev_lo;
|
2025-02-18 11:18:17 +01:00
|
|
|
|
|
|
|
void (*free_module_data)(void *, void *) = NULL;
|
|
|
|
struct clb_data_s clb_data;
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
/* try to use a module from the context */
|
|
|
|
*mod = ly_ctx_get_module_implemented(session->ctx, name);
|
|
|
|
if (!*mod) {
|
|
|
|
if (revision) {
|
|
|
|
*mod = ly_ctx_get_module(session->ctx, name, revision);
|
|
|
|
} else {
|
|
|
|
*mod = ly_ctx_get_module_latest(session->ctx, name);
|
|
|
|
}
|
|
|
|
} else if (revision && (!(*mod)->revision || strcmp((*mod)->revision, revision))) {
|
|
|
|
WRN(session, "Server implements module \"%s\" in revision \"%s\" but revision \"%s\" is already implemented"
|
|
|
|
" and will be used instead.", name, revision, (*mod)->revision ? (*mod)->revision : "<none>");
|
2025-02-18 11:18:17 +01:00
|
|
|
}
|
2025-02-18 11:33:30 +01:00
|
|
|
|
2025-02-18 11:18:17 +01:00
|
|
|
if (*mod) {
|
2025-02-18 11:33:30 +01:00
|
|
|
/* make the present module implemented and/or enable all its features */
|
|
|
|
if (lys_set_implemented(*mod, features)) {
|
|
|
|
ERR(session, "Failed to implement module \"%s\".", (*mod)->name);
|
|
|
|
return -1;
|
2025-02-18 11:18:17 +01:00
|
|
|
}
|
2025-02-18 11:33:30 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* missing implemented module, load it ... */
|
|
|
|
clb_data.has_get_schema = has_get_schema;
|
|
|
|
clb_data.modules = modules;
|
|
|
|
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);
|
|
|
|
prev_lo = ly_temp_log_options(&temp_lo);
|
|
|
|
|
|
|
|
/* get module data */
|
|
|
|
if (!retrieve_module_data(name, revision, &clb_data, &format, &module_data, &free_module_data)) {
|
|
|
|
/* set import callback */
|
|
|
|
ly_ctx_set_module_imp_clb(session->ctx, retrieve_module_data_imp, &clb_data);
|
2025-02-18 11:18:17 +01:00
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
/* parse the module */
|
|
|
|
ly_in_new_memory(module_data, &in);
|
|
|
|
lys_parse(session->ctx, in, format, features, mod);
|
|
|
|
ly_in_free(in, 0);
|
|
|
|
if (free_module_data) {
|
|
|
|
free_module_data((char *)module_data, user_data);
|
2025-02-18 11:18:17 +01:00
|
|
|
}
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
ly_ctx_set_module_imp_clb(session->ctx, NULL, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* restore logging options, then print errors on definite failure */
|
|
|
|
ly_temp_log_options(prev_lo);
|
|
|
|
if (!(*mod)) {
|
|
|
|
for (eitem = ly_err_first(session->ctx); eitem; eitem = eitem->next) {
|
|
|
|
ly_err_print(session->ctx, eitem);
|
|
|
|
}
|
|
|
|
ret = -1;
|
|
|
|
} else {
|
|
|
|
/* print only warnings */
|
|
|
|
for (eitem = ly_err_first(session->ctx); eitem; eitem = eitem->next) {
|
|
|
|
if (eitem->level == LY_LLWRN) {
|
2025-02-18 11:18:17 +01:00
|
|
|
ly_err_print(session->ctx, eitem);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
/* clean the errors */
|
|
|
|
ly_err_clean(session->ctx, NULL);
|
|
|
|
|
2025-02-18 11:18:17 +01:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2025-02-18 11:33:30 +01:00
|
|
|
free_module_info(struct module_info *list)
|
2025-02-18 11:18:17 +01:00
|
|
|
{
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2025-02-18 11:33:30 +01:00
|
|
|
* @brief Retrieve yang-library and schema-mounts operational data from the server.
|
2025-02-18 11:18:17 +01:00
|
|
|
*
|
|
|
|
* @param[in] session NC session.
|
|
|
|
* @param[in] has_get_data Whether get-data RPC is available or only get.
|
2025-02-18 11:33:30 +01:00
|
|
|
* @param[in] filter Filter to use.
|
|
|
|
* @param[out] oper_data Received data.
|
2025-02-18 11:18:17 +01:00
|
|
|
* @return 0 on success.
|
|
|
|
* @return -1 on error.
|
|
|
|
*/
|
|
|
|
static int
|
2025-02-18 11:33:30 +01:00
|
|
|
get_oper_data(struct nc_session *session, int has_get_data, const char *filter, struct lyd_node **oper_data)
|
2025-02-18 11:18:17 +01:00
|
|
|
{
|
|
|
|
struct nc_rpc *rpc = NULL;
|
|
|
|
struct lyd_node *op = NULL, *envp = NULL;
|
|
|
|
struct lyd_node_any *data;
|
|
|
|
NC_MSG_TYPE msg;
|
|
|
|
uint64_t msgid;
|
|
|
|
int ret = 0;
|
|
|
|
const char *rpc_name;
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
/* get data from the server */
|
2025-02-18 11:18:17 +01:00
|
|
|
if (has_get_data) {
|
2025-02-18 11:33:30 +01:00
|
|
|
rpc_name = "<get-data>";
|
|
|
|
rpc = nc_rpc_getdata("ietf-datastores:operational", filter, "false", NULL, 0, 0, 0, 0, 0, NC_PARAMTYPE_CONST);
|
2025-02-18 11:18:17 +01:00
|
|
|
} else {
|
2025-02-18 11:33:30 +01:00
|
|
|
rpc_name = "<get>";
|
|
|
|
rpc = nc_rpc_get(filter, 0, NC_PARAMTYPE_CONST);
|
2025-02-18 11:18:17 +01:00
|
|
|
}
|
|
|
|
if (!rpc) {
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
while ((msg = nc_send_rpc(session, rpc, 0, &msgid)) == NC_MSG_WOULDBLOCK) {
|
|
|
|
usleep(1000);
|
|
|
|
}
|
|
|
|
if (msg == NC_MSG_ERROR) {
|
2025-02-18 11:33:30 +01:00
|
|
|
WRN(session, "Failed to send %s RPC.", rpc_name);
|
2025-02-18 11:18:17 +01:00
|
|
|
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) {
|
2025-02-18 11:33:30 +01:00
|
|
|
WRN(session, "Timeout for receiving reply to a %s RPC expired.", rpc_name);
|
2025-02-18 11:18:17 +01:00
|
|
|
goto cleanup;
|
|
|
|
} else if (msg == NC_MSG_ERROR) {
|
2025-02-18 11:33:30 +01:00
|
|
|
WRN(session, "Failed to receive a reply to %s RPC.", rpc_name);
|
2025-02-18 11:18:17 +01:00
|
|
|
goto cleanup;
|
2025-02-18 11:33:30 +01:00
|
|
|
} else if (!op || !lyd_child(op) || !lyd_child(op)->schema || strcmp(lyd_child(op)->schema->name, "data")) {
|
|
|
|
WRN(session, "Unexpected reply without data to a %s RPC.", rpc_name);
|
2025-02-18 11:18:17 +01:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
data = (struct lyd_node_any *)lyd_child(op);
|
|
|
|
if (data->value_type != LYD_ANYDATA_DATATREE) {
|
2025-02-18 11:33:30 +01:00
|
|
|
WRN(session, "Unexpected data in reply to a %s RPC.", rpc_name);
|
2025-02-18 11:18:17 +01:00
|
|
|
goto cleanup;
|
|
|
|
} else if (!data->value.tree) {
|
2025-02-18 11:33:30 +01:00
|
|
|
WRN(session, "No data in reply to a %s RPC.", rpc_name);
|
2025-02-18 11:18:17 +01:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
*oper_data = data->value.tree;
|
|
|
|
data->value.tree = NULL;
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
nc_rpc_free(rpc);
|
|
|
|
lyd_free_tree(envp);
|
|
|
|
lyd_free_tree(op);
|
|
|
|
|
|
|
|
if (session->status != NC_STATUS_RUNNING) {
|
|
|
|
/* something bad happened, discard the session */
|
|
|
|
ERR(session, "Invalid session, discarding.");
|
|
|
|
ret = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Build server module info from ietf-yang-library data.
|
|
|
|
*
|
|
|
|
* @param[in] session NC session.
|
|
|
|
* @param[in] get_data_sup Whether get-data RPC is available or only get.
|
|
|
|
* @param[in] xpath_sup Whether XPath filter is supported or only subtree filter.
|
|
|
|
* @param[out] result Server modules.
|
|
|
|
* @return 0 on success.
|
|
|
|
* @return -1 on error.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
build_module_info_yl(struct nc_session *session, int get_data_sup, int xpath_sup, struct module_info **result)
|
|
|
|
{
|
|
|
|
struct ly_set *modules = NULL;
|
|
|
|
uint32_t u, v, submodules_count, feature_count;
|
|
|
|
struct lyd_node *iter, *child, *oper_data = NULL;
|
|
|
|
struct lys_module *mod;
|
|
|
|
int ret = 0;
|
|
|
|
uint8_t notifications_found = 0;
|
|
|
|
uint8_t nc_notifications_found = 0;
|
|
|
|
|
|
|
|
/* get yang-library operational data */
|
|
|
|
if (xpath_sup) {
|
|
|
|
if (get_oper_data(session, get_data_sup, "/ietf-yang-library:*", &oper_data)) {
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (get_oper_data(session, get_data_sup,
|
|
|
|
"<modules-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-yang-library\"/>", &oper_data)) {
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!oper_data) {
|
2025-02-18 11:18:17 +01:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
if (lyd_find_xpath(oper_data, "/ietf-yang-library:modules-state/module", &modules)) {
|
|
|
|
WRN(NULL, "No yang-library module information found.");
|
2025-02-18 11:18:17 +01:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
(*result) = calloc(modules->count + 1, sizeof **result);
|
|
|
|
NC_CHECK_ERRMEM_GOTO(!(*result), ret = -1, cleanup);
|
|
|
|
|
2025-02-18 11:18:17 +01:00
|
|
|
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));
|
2025-02-18 11:33:30 +01:00
|
|
|
if (!strcmp((*result)[u].name, "notifications")) {
|
|
|
|
notifications_found = 1;
|
|
|
|
} else if (!strcmp((*result)[u].name, "nc-notifications")) {
|
|
|
|
nc_notifications_found = 1;
|
|
|
|
}
|
2025-02-18 11:18:17 +01:00
|
|
|
} 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);
|
2025-02-18 11:33:30 +01:00
|
|
|
NC_CHECK_ERRMEM_GOTO(!(*result)[u].features, free_module_info(*result); *result = NULL; ret = -1, cleanup);
|
2025-02-18 11:18:17 +01:00
|
|
|
(*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);
|
2025-02-18 11:33:30 +01:00
|
|
|
NC_CHECK_ERRMEM_GOTO(!(*result)[u].submodules, free_module_info(*result); *result = NULL; ret = -1, cleanup);
|
|
|
|
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));
|
2025-02-18 11:18:17 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
/* If NETCONF server supports RFC5277 notification capability and libnetconf2
|
|
|
|
* required notifications and nc-notifications are not present on the NETCONF
|
|
|
|
* server (which it is not obligated to support), then the libyang context
|
|
|
|
* needs to be initialized using client side local YANG schema files */
|
|
|
|
if (nc_session_cpblt(session, "urn:ietf:params:netconf:capability:notification:1.0") &&
|
|
|
|
!notifications_found && !nc_notifications_found) {
|
2025-02-18 11:18:17 +01:00
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
(*result) = nc_realloc(*result, (modules->count + 3) * sizeof **result);
|
|
|
|
NC_CHECK_ERRMEM_GOTO(!(*result), ret = -1, cleanup);
|
|
|
|
|
|
|
|
(*result)[u].name = strdup("notifications");
|
|
|
|
(*result)[u].revision = strdup("2008-07-14");
|
|
|
|
(*result)[u].implemented = 1;
|
|
|
|
u++;
|
|
|
|
|
|
|
|
(*result)[u].name = strdup("nc-notifications");
|
|
|
|
(*result)[u].revision = strdup("2008-07-14");
|
|
|
|
(*result)[u].implemented = 1;
|
2025-02-18 11:18:17 +01:00
|
|
|
}
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
cleanup:
|
|
|
|
lyd_free_siblings(oper_data);
|
|
|
|
ly_set_free(modules, NULL);
|
2025-02-18 11:18:17 +01:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2025-02-18 11:33:30 +01:00
|
|
|
* @brief Build server module info from received capabilities.
|
2025-02-18 11:18:17 +01:00
|
|
|
*
|
|
|
|
* @param[in] cpblts Server capabilities.
|
2025-02-18 11:33:30 +01:00
|
|
|
* @param[out] result Server modules.
|
2025-02-18 11:18:17 +01:00
|
|
|
* @return 0 on success.
|
|
|
|
* @return -1 on error.
|
|
|
|
*/
|
|
|
|
static int
|
2025-02-18 11:33:30 +01:00
|
|
|
build_module_info_cpblts(char **cpblts, struct module_info **result)
|
2025-02-18 11:18:17 +01:00
|
|
|
{
|
|
|
|
uint32_t u, v, feature_count;
|
|
|
|
char *module_cpblt, *ptr, *ptr2;
|
|
|
|
|
|
|
|
for (u = 0; cpblts[u]; ++u) {}
|
|
|
|
(*result) = calloc(u + 1, sizeof **result);
|
2025-02-18 11:33:30 +01:00
|
|
|
NC_CHECK_ERRMEM_RET(!(*result), -1);
|
2025-02-18 11:18:17 +01:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2025-02-18 11:33:30 +01:00
|
|
|
* @brief Fill client context based on server modules info.
|
2025-02-18 11:18:17 +01:00
|
|
|
*
|
|
|
|
* @param[in] session NC session with the context to modify.
|
2025-02-18 11:33:30 +01:00
|
|
|
* @param[in] modules Server modules info.
|
|
|
|
* @param[in] user_clb User callback for retrieving specific modules.
|
2025-02-18 11:18:17 +01:00
|
|
|
* @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
|
2025-02-18 11:33:30 +01:00
|
|
|
nc_ctx_fill(struct nc_session *session, struct module_info *modules, ly_module_imp_clb user_clb, void *user_data,
|
2025-02-18 11:18:17 +01:00
|
|
|
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 */
|
2025-02-18 11:33:30 +01:00
|
|
|
nc_ctx_load_module(session, modules[u].name, modules[u].revision, (const char **)modules[u].features, modules,
|
|
|
|
user_clb, user_data, has_get_schema, &mod);
|
2025-02-18 11:18:17 +01:00
|
|
|
|
|
|
|
if (!mod) {
|
|
|
|
if (session->status != NC_STATUS_RUNNING) {
|
|
|
|
/* something bad heppened, discard the session */
|
|
|
|
ERR(session, "Invalid session, discarding.");
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
/* all loading ways failed, the module will be ignored in the received data */
|
|
|
|
WRN(session, "Failed to load module \"%s@%s\".", modules[u].name, modules[u].revision ?
|
2025-02-18 11:18:17 +01:00
|
|
|
modules[u].revision : "<latest>");
|
|
|
|
session->flags |= NC_SESSION_CLIENT_NOT_STRICT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* success */
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2025-02-18 11:33:30 +01:00
|
|
|
* @brief Fill client context with ietf-netconf module.
|
2025-02-18 11:18:17 +01:00
|
|
|
*
|
|
|
|
* @param[in] session NC session with the context to modify.
|
2025-02-18 11:33:30 +01:00
|
|
|
* @param[in] modules Server module info.
|
|
|
|
* @param[in] user_clb User callback for retrieving specific modules.
|
2025-02-18 11:18:17 +01:00
|
|
|
* @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
|
2025-02-18 11:33:30 +01:00
|
|
|
nc_ctx_fill_ietf_netconf(struct nc_session *session, struct module_info *modules, ly_module_imp_clb user_clb,
|
2025-02-18 11:18:17 +01:00
|
|
|
void *user_data, int has_get_schema)
|
|
|
|
{
|
|
|
|
uint32_t u;
|
2025-02-18 11:33:30 +01:00
|
|
|
const char **features = NULL;
|
|
|
|
struct ly_in *in;
|
2025-02-18 11:18:17 +01:00
|
|
|
struct lys_module *ietfnc;
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
/* find supported features (capabilities) in ietf-netconf */
|
|
|
|
for (u = 0; modules[u].name; ++u) {
|
|
|
|
if (!strcmp(modules[u].name, "ietf-netconf")) {
|
|
|
|
assert(modules[u].implemented);
|
|
|
|
features = (const char **)modules[u].features;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!modules[u].name) {
|
|
|
|
ERR(session, "Base NETCONF module not supported by the server.");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2025-02-18 11:18:17 +01:00
|
|
|
ietfnc = ly_ctx_get_module_implemented(session->ctx, "ietf-netconf");
|
2025-02-18 11:33:30 +01:00
|
|
|
if (ietfnc) {
|
|
|
|
/* make sure to enable all the features if already loaded */
|
|
|
|
lys_set_implemented(ietfnc, features);
|
|
|
|
} else {
|
|
|
|
/* load the module */
|
|
|
|
nc_ctx_load_module(session, "ietf-netconf", NULL, features, modules, user_clb, user_data, has_get_schema, &ietfnc);
|
2025-02-18 11:18:17 +01:00
|
|
|
if (!ietfnc) {
|
2025-02-18 11:33:30 +01:00
|
|
|
ly_in_new_memory(ietf_netconf_2013_09_29_yang, &in);
|
|
|
|
lys_parse(session->ctx, in, LYS_IN_YANG, features, &ietfnc);
|
|
|
|
ly_in_free(in, 0);
|
2025-02-18 11:18:17 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!ietfnc) {
|
2025-02-18 11:33:30 +01:00
|
|
|
ERR(session, "Loading base NETCONF module failed.");
|
2025-02-18 11:18:17 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
API int
|
|
|
|
nc_client_set_new_session_context_schema_mount(struct nc_session *session)
|
|
|
|
{
|
|
|
|
int rc = 0, yanglib_support = 0, xpath_support = 0, nmda_support = 0;
|
|
|
|
struct lyd_node *oper_data = NULL;
|
|
|
|
const struct lys_module *mod;
|
|
|
|
|
|
|
|
if (session->flags & NC_SESSION_SHAREDCTX) {
|
|
|
|
/* context is already fully set up */
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* check all useful capabilities */
|
|
|
|
if (ly_ctx_get_module_implemented(session->ctx, "ietf-yang-library")) {
|
|
|
|
yanglib_support = 1;
|
|
|
|
}
|
|
|
|
if ((mod = ly_ctx_get_module_implemented(session->ctx, "ietf-netconf")) && !lys_feature_value(mod, "xpath")) {
|
|
|
|
xpath_support = 1;
|
|
|
|
}
|
|
|
|
if (ly_ctx_get_module_implemented(session->ctx, "ietf-netconf-nmda")) {
|
|
|
|
nmda_support = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!yanglib_support) {
|
|
|
|
ERR(session, "Module \"ietf-yang-library\" missing to retrieve schema-mount data.");
|
|
|
|
rc = -1;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* get yang-library and schema-mounts operational data */
|
|
|
|
if (xpath_support) {
|
|
|
|
if ((rc = get_oper_data(session, nmda_support, "/ietf-yang-library:* | /ietf-yang-schema-mount:*", &oper_data))) {
|
|
|
|
goto cleanup;
|
2025-02-18 11:18:17 +01:00
|
|
|
}
|
2025-02-18 11:33:30 +01:00
|
|
|
} else {
|
|
|
|
if ((rc = get_oper_data(session, nmda_support,
|
|
|
|
"<modules-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-yang-library\"/>"
|
|
|
|
"<schema-mounts xmlns=\"urn:ietf:params:xml:ns:yang:ietf-yang-schema-mount\"/>", &oper_data))) {
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
}
|
2025-02-18 11:18:17 +01:00
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
if (!oper_data || lyd_find_path(oper_data, "/ietf-yang-schema-mount:schema-mounts", 0, NULL)) {
|
|
|
|
/* no schema-mounts operational data */
|
|
|
|
goto cleanup;
|
2025-02-18 11:18:17 +01:00
|
|
|
}
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
/* validate the data for the parent reference prefixes to be resolved */
|
|
|
|
if (lyd_validate_all(&oper_data, NULL, LYD_VALIDATE_PRESENT, NULL)) {
|
|
|
|
ERR(session, "Invalid operational data received from the server (%s).", ly_err_last(LYD_CTX(oper_data))->msg);
|
|
|
|
rc = -1;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* store the data in the session */
|
|
|
|
lyd_free_siblings(session->opts.client.ext_data);
|
|
|
|
session->opts.client.ext_data = oper_data;
|
|
|
|
oper_data = NULL;
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
lyd_free_siblings(oper_data);
|
|
|
|
return rc;
|
2025-02-18 11:18:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
nc_ctx_check_and_fill(struct nc_session *session)
|
|
|
|
{
|
2025-02-18 11:33:30 +01:00
|
|
|
int i, get_schema_support = 0, yanglib_support = 0, xpath_support = 0, nmda_support = 0, ret = -1;
|
2025-02-18 11:18:17 +01:00
|
|
|
ly_module_imp_clb old_clb = NULL;
|
|
|
|
void *old_data = NULL;
|
|
|
|
struct lys_module *mod = NULL;
|
|
|
|
char *revision;
|
2025-02-18 11:33:30 +01:00
|
|
|
struct module_info *server_modules = NULL, *sm = NULL;
|
2025-02-18 11:18:17 +01:00
|
|
|
|
|
|
|
assert(session->opts.client.cpblts && session->ctx);
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
if (client_opts.auto_context_fill_disabled) {
|
|
|
|
VRB(session, "Context of the new session is left only with the default YANG modules.");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2025-02-18 11:18:17 +01:00
|
|
|
/* 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
|
2025-02-18 11:33:30 +01:00
|
|
|
* modules only to those present on the server side */
|
2025-02-18 11:18:17 +01:00
|
|
|
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;
|
|
|
|
} else if (!strncmp(session->opts.client.cpblts[i], "urn:ietf:params:netconf:capability:yang-library:", 48)) {
|
|
|
|
yanglib_support = 1 + i;
|
2025-02-18 11:33:30 +01:00
|
|
|
} else if (!strncmp(session->opts.client.cpblts[i], "urn:ietf:params:netconf:capability:xpath:1.0", 44)) {
|
|
|
|
xpath_support = 1 + i;
|
2025-02-18 11:18:17 +01:00
|
|
|
}
|
2025-02-18 11:33:30 +01:00
|
|
|
/* NMDA is YANG 1.1 module, which is not present in the capabilities */
|
2025-02-18 11:18:17 +01:00
|
|
|
}
|
2025-02-18 11:33:30 +01:00
|
|
|
VRB(session, "Capability for <get-schema> support%s found.", get_schema_support ? "" : " not");
|
|
|
|
VRB(session, "Capability for yang-library support%s found.", yanglib_support ? "" : " not");
|
|
|
|
VRB(session, "Capability for XPath filter support%s found.", xpath_support ? "" : " not");
|
2025-02-18 11:18:17 +01:00
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
/* get information about server's modules from capabilities list until we will have yang-library */
|
|
|
|
if (build_module_info_cpblts(session->opts.client.cpblts, &server_modules) || !server_modules) {
|
|
|
|
ERR(session, "Unable to get server module information from the <hello>'s capabilities.");
|
2025-02-18 11:18:17 +01:00
|
|
|
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)) {
|
2025-02-18 11:33:30 +01:00
|
|
|
WRN(session, "Loading NETCONF monitoring module failed, cannot use <get-schema>.");
|
2025-02-18 11:18:17 +01:00
|
|
|
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) {
|
2025-02-18 11:33:30 +01:00
|
|
|
/* use get-schema to get server's ietf-yang-library */
|
2025-02-18 11:18:17 +01:00
|
|
|
revision = strstr(session->opts.client.cpblts[yanglib_support - 1], "revision=");
|
|
|
|
if (!revision) {
|
2025-02-18 11:33:30 +01:00
|
|
|
WRN(session, "Loading NETCONF ietf-yang-library module failed, missing revision in NETCONF <hello> message.");
|
2025-02-18 11:18:17 +01:00
|
|
|
WRN(session, "Unable to automatically use <get-schema>.");
|
|
|
|
yanglib_support = 0;
|
|
|
|
} else {
|
|
|
|
revision = strndup(&revision[9], 10);
|
2025-02-18 11:33:30 +01:00
|
|
|
if (nc_ctx_load_module(session, "ietf-yang-library", revision, NULL, server_modules, old_clb, old_data,
|
2025-02-18 11:18:17 +01:00
|
|
|
get_schema_support, &mod)) {
|
2025-02-18 11:33:30 +01:00
|
|
|
WRN(session, "Loading NETCONF ietf-yang-library module failed, unable to use it to learn all "
|
2025-02-18 11:18:17 +01:00
|
|
|
"the supported modules.");
|
|
|
|
yanglib_support = 0;
|
|
|
|
}
|
|
|
|
if (strcmp(revision, "2019-01-04") >= 0) {
|
|
|
|
/* we also need ietf-datastores to be implemented */
|
2025-02-18 11:33:30 +01:00
|
|
|
if (nc_ctx_load_module(session, "ietf-datastores", NULL, NULL, server_modules, old_clb, old_data,
|
2025-02-18 11:18:17 +01:00
|
|
|
get_schema_support, &mod)) {
|
2025-02-18 11:33:30 +01:00
|
|
|
WRN(session, "Loading NETCONF ietf-datastores module failed, unable to use yang-library "
|
2025-02-18 11:18:17 +01:00
|
|
|
"to learn all the supported modules.");
|
|
|
|
yanglib_support = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
free(revision);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
/* prepare structured information about server's modules */
|
2025-02-18 11:18:17 +01:00
|
|
|
if (yanglib_support) {
|
2025-02-18 11:33:30 +01:00
|
|
|
if (build_module_info_yl(session, 0, xpath_support, &sm)) {
|
2025-02-18 11:18:17 +01:00
|
|
|
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
|
2025-02-18 11:33:30 +01:00
|
|
|
* yang-library module */
|
|
|
|
free_module_info(server_modules);
|
2025-02-18 11:18:17 +01:00
|
|
|
server_modules = sm;
|
2025-02-18 11:33:30 +01:00
|
|
|
|
|
|
|
/* check for NMDA support */
|
|
|
|
for (i = 0; server_modules[i].name; ++i) {
|
|
|
|
if (!strcmp(server_modules[i].name, "ietf-netconf-nmda") && server_modules[i].implemented) {
|
|
|
|
nmda_support = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ietf-netconf-nmda is needed to issue get-data */
|
|
|
|
if (nmda_support && nc_ctx_load_module(session, "ietf-netconf-nmda", NULL, NULL, server_modules, old_clb,
|
|
|
|
old_data, get_schema_support, &mod)) {
|
|
|
|
WRN(session, "Loading NMDA module failed, unable to use <get-data>.");
|
|
|
|
}
|
2025-02-18 11:18:17 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
/* compile all modules at once to avoid invalid errors or warnings */
|
|
|
|
ly_ctx_set_options(session->ctx, LY_CTX_EXPLICIT_COMPILE);
|
|
|
|
|
|
|
|
/* fill the context */
|
2025-02-18 11:18:17 +01:00
|
|
|
if (nc_ctx_fill(session, server_modules, old_clb, old_data, get_schema_support)) {
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
/* compile it */
|
|
|
|
if (ly_ctx_compile(session->ctx)) {
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* set support for schema-mount, if possible (requires ietf-yang-library support) */
|
|
|
|
if (yanglib_support && nc_client_set_new_session_context_schema_mount(session)) {
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* success */
|
2025-02-18 11:18:17 +01:00
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
if (session->flags & NC_SESSION_CLIENT_NOT_STRICT) {
|
2025-02-18 11:33:30 +01:00
|
|
|
WRN(session, "Some modules failed to be loaded, any data from these modules (and any other unknown) will "
|
2025-02-18 11:18:17 +01:00
|
|
|
"be ignored.");
|
|
|
|
}
|
|
|
|
|
|
|
|
cleanup:
|
2025-02-18 11:33:30 +01:00
|
|
|
free_module_info(server_modules);
|
2025-02-18 11:18:17 +01:00
|
|
|
|
|
|
|
/* 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);
|
2025-02-18 11:33:30 +01:00
|
|
|
ly_ctx_unset_options(session->ctx, LY_CTX_EXPLICIT_COMPILE);
|
2025-02-18 11:18:17 +01:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
API struct nc_session *
|
|
|
|
nc_connect_inout(int fdin, int fdout, struct ly_ctx *ctx)
|
|
|
|
{
|
|
|
|
struct nc_session *session;
|
|
|
|
|
|
|
|
if (fdin < 0) {
|
2025-02-18 11:33:30 +01:00
|
|
|
ERRARG(NULL, "fdin");
|
2025-02-18 11:18:17 +01:00
|
|
|
return NULL;
|
|
|
|
} else if (fdout < 0) {
|
2025-02-18 11:33:30 +01:00
|
|
|
ERRARG(NULL, "fdout");
|
2025-02-18 11:18:17 +01:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* prepare session structure */
|
|
|
|
session = nc_new_session(NC_CLIENT, 0);
|
2025-02-18 11:33:30 +01:00
|
|
|
NC_CHECK_ERRMEM_RET(!session, NULL);
|
2025-02-18 11:18:17 +01:00
|
|
|
session->status = NC_STATUS_STARTING;
|
|
|
|
|
|
|
|
/* transport specific data */
|
|
|
|
session->ti_type = NC_TI_FD;
|
|
|
|
session->ti.fd.in = fdin;
|
|
|
|
session->ti.fd.out = fdout;
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
if (nc_client_session_new_ctx(session, ctx) != EXIT_SUCCESS) {
|
2025-02-18 11:18:17 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
/* start monitoring the session if the monitoring thread is running */
|
|
|
|
if (nc_client_monitoring_session_start(session)) {
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
2025-02-18 11:18:17 +01:00
|
|
|
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;
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
NC_CHECK_ARG_RET(NULL, address, NULL);
|
2025-02-18 11:18:17 +01:00
|
|
|
|
|
|
|
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);
|
2025-02-18 11:33:30 +01:00
|
|
|
NC_CHECK_ERRMEM_GOTO(!session, , fail);
|
2025-02-18 11:18:17 +01:00
|
|
|
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 */
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
if (nc_client_session_new_ctx(session, ctx) != EXIT_SUCCESS) {
|
2025-02-18 11:18:17 +01:00
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
ctx = session->ctx;
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
session->path = strdup(address);
|
2025-02-18 11:18:17 +01:00
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
pw = nc_getpw(geteuid(), NULL, &pw_buf, &buf, &buf_size);
|
2025-02-18 11:18:17 +01:00
|
|
|
if (!pw) {
|
|
|
|
ERR(NULL, "Failed to find username for UID %u.", (unsigned int)geteuid());
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
username = strdup(pw->pw_name);
|
|
|
|
free(buf);
|
2025-02-18 11:33:30 +01:00
|
|
|
NC_CHECK_ERRMEM_GOTO(!username, , fail);
|
|
|
|
session->username = username;
|
2025-02-18 11:18:17 +01:00
|
|
|
|
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
/* start monitoring the session if the monitoring thread is running */
|
|
|
|
if (nc_client_monitoring_session_start(session)) {
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
2025-02-18 11:18:17 +01:00
|
|
|
return session;
|
|
|
|
|
|
|
|
fail:
|
|
|
|
nc_session_free(session, NULL);
|
|
|
|
if (sock >= 0) {
|
|
|
|
close(sock);
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
/**
|
|
|
|
* @brief Convert socket IP address to string.
|
|
|
|
*
|
|
|
|
* @param[in] saddr Sockaddr to convert.
|
|
|
|
* @param[out] str_ip String IP address.
|
|
|
|
* @param[out] port Optional port.
|
|
|
|
* @return 0 on success.
|
|
|
|
* @return -1 on error.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
nc_saddr2str(const struct sockaddr *saddr, char **str_ip, uint16_t *port)
|
|
|
|
{
|
|
|
|
void *addr;
|
|
|
|
socklen_t str_len;
|
|
|
|
|
|
|
|
assert((saddr->sa_family == AF_INET) || (saddr->sa_family == AF_INET6));
|
|
|
|
|
|
|
|
str_len = (saddr->sa_family == AF_INET) ? INET_ADDRSTRLEN : INET6_ADDRSTRLEN;
|
|
|
|
*str_ip = malloc(str_len);
|
|
|
|
NC_CHECK_ERRMEM_RET(!(*str_ip), -1);
|
|
|
|
|
|
|
|
if (saddr->sa_family == AF_INET) {
|
|
|
|
addr = &((struct sockaddr_in *)saddr)->sin_addr;
|
|
|
|
if (port) {
|
|
|
|
*port = ntohs(((struct sockaddr_in *)saddr)->sin_port);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
addr = &((struct sockaddr_in6 *)saddr)->sin6_addr;
|
|
|
|
if (port) {
|
|
|
|
*port = ntohs(((struct sockaddr_in6 *)saddr)->sin6_port);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!inet_ntop(saddr->sa_family, addr, *str_ip, str_len)) {
|
|
|
|
ERR(NULL, "Converting host to IP address failed (%s).", strerror(errno));
|
|
|
|
free(*str_ip);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Try to connect a socket, optionally a pending one from a previous attempt.
|
|
|
|
*
|
|
|
|
* @param[in] src_addr Specific source address to bind to, used only for CH.
|
|
|
|
* @param[in] src_port Specific source port to bind to, used only for CH.
|
|
|
|
* @param[in] timeout_ms Timeout in ms to wait for the connection to be fully established, -1 to block.
|
|
|
|
* @param[in,out] sock_pending Optional previously created socked that was not fully connected yet. If provided and
|
|
|
|
* connected, is set to -1.
|
|
|
|
* @param[in] res Addrinfo resource to use when creating a new socket.
|
|
|
|
* @param[in] ka Keepalives to set.
|
|
|
|
* @return Connected socket or -1 on error.
|
2025-02-18 11:18:17 +01:00
|
|
|
*/
|
|
|
|
static int
|
2025-02-18 11:33:30 +01:00
|
|
|
sock_connect(const char *src_addr, uint16_t src_port, int timeout_ms, int *sock_pending, struct addrinfo *res,
|
|
|
|
const struct nc_keepalives *ka)
|
2025-02-18 11:18:17 +01:00
|
|
|
{
|
2025-02-18 11:33:30 +01:00
|
|
|
int flags, ret, error, opt;
|
2025-02-18 11:18:17 +01:00
|
|
|
int sock = -1;
|
2025-02-18 11:33:30 +01:00
|
|
|
struct pollfd fds = {0};
|
2025-02-18 11:18:17 +01:00
|
|
|
socklen_t len = sizeof(int);
|
|
|
|
uint16_t port;
|
2025-02-18 11:33:30 +01:00
|
|
|
char *str;
|
2025-02-18 11:18:17 +01:00
|
|
|
|
|
|
|
if (sock_pending && (*sock_pending != -1)) {
|
|
|
|
VRB(NULL, "Trying to connect the pending socket %d.", *sock_pending);
|
|
|
|
sock = *sock_pending;
|
|
|
|
} else {
|
|
|
|
assert(res);
|
2025-02-18 11:33:30 +01:00
|
|
|
if (nc_saddr2str(res->ai_addr, &str, &port)) {
|
|
|
|
return -1;
|
2025-02-18 11:18:17 +01:00
|
|
|
}
|
2025-02-18 11:33:30 +01:00
|
|
|
VRB(NULL, "Trying to connect via %s to %s:%u.", (res->ai_family == AF_INET6) ? "IPv6" : "IPv4", str, port);
|
|
|
|
free(str);
|
2025-02-18 11:18:17 +01:00
|
|
|
|
|
|
|
/* 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;
|
|
|
|
}
|
2025-02-18 11:33:30 +01:00
|
|
|
|
|
|
|
/* bind the socket to a specific address/port to make the connection from (CH only) */
|
|
|
|
if (src_addr || src_port) {
|
|
|
|
/* enable address reuse, so that we're able to bind this address again when the CH conn is dropped and retried */
|
|
|
|
opt = 1;
|
|
|
|
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt) == -1) {
|
|
|
|
ERR(NULL, "Could not set SO_REUSEADDR socket option (%s).", strerror(errno));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (nc_sock_bind_inet(sock, src_addr, src_port, (res->ai_family == AF_INET) ? 1 : 0)) {
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-02-18 11:18:17 +01:00
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
fds.fd = sock;
|
|
|
|
fds.events = POLLOUT;
|
2025-02-18 11:18:17 +01:00
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
/* wait until we can write data to the socket */
|
|
|
|
ret = poll(&fds, 1, timeout_ms);
|
|
|
|
if (ret == -1) {
|
|
|
|
/* error */
|
|
|
|
ERR(NULL, "poll() failed (%s).", strerror(errno));
|
2025-02-18 11:18:17 +01:00
|
|
|
goto cleanup;
|
2025-02-18 11:33:30 +01:00
|
|
|
} else if (ret == 0) {
|
2025-02-18 11:18:17 +01:00
|
|
|
/* there was a timeout */
|
2025-02-18 11:33:30 +01:00
|
|
|
VRB(NULL, "Timed out after %d ms (%s).", timeout_ms, strerror(errno));
|
2025-02-18 11:18:17 +01:00
|
|
|
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 */
|
2025-02-18 11:33:30 +01:00
|
|
|
VRB(NULL, "Connection error (%s).", strerror(error));
|
2025-02-18 11:18:17 +01:00
|
|
|
errno = error;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
/* configure keepalives */
|
|
|
|
if (nc_sock_configure_ka(sock, ka)) {
|
2025-02-18 11:18:17 +01:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
/* connected */
|
|
|
|
if (sock_pending) {
|
|
|
|
*sock_pending = -1;
|
|
|
|
}
|
2025-02-18 11:18:17 +01:00
|
|
|
return sock;
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
if (sock_pending) {
|
|
|
|
*sock_pending = -1;
|
|
|
|
}
|
|
|
|
close(sock);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2025-02-18 11:33:30 +01:00
|
|
|
nc_sock_connect(const char *src_addr, uint16_t src_port, const char *dst_addr, uint16_t dst_port, int timeout_ms,
|
|
|
|
struct nc_keepalives *ka, int *sock_pending, char **ip_host)
|
2025-02-18 11:18:17 +01:00
|
|
|
{
|
|
|
|
int i, opt;
|
|
|
|
int sock = sock_pending ? *sock_pending : -1;
|
|
|
|
struct addrinfo hints, *res_list = NULL, *res;
|
2025-02-18 11:33:30 +01:00
|
|
|
char dst_port_str[6]; /* length of string representation of short int */
|
|
|
|
struct sockaddr_storage saddr;
|
|
|
|
socklen_t addr_len = sizeof saddr;
|
2025-02-18 11:18:17 +01:00
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
*ip_host = NULL;
|
2025-02-18 11:18:17 +01:00
|
|
|
|
|
|
|
/* no pending socket */
|
|
|
|
if (sock == -1) {
|
|
|
|
/* connect to a server */
|
2025-02-18 11:33:30 +01:00
|
|
|
snprintf(dst_port_str, 6, "%u", dst_port);
|
2025-02-18 11:18:17 +01:00
|
|
|
memset(&hints, 0, sizeof hints);
|
|
|
|
hints.ai_family = AF_UNSPEC;
|
|
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
|
|
hints.ai_protocol = IPPROTO_TCP;
|
2025-02-18 11:33:30 +01:00
|
|
|
i = getaddrinfo(dst_addr, dst_port_str, &hints, &res_list);
|
2025-02-18 11:18:17 +01:00
|
|
|
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) {
|
2025-02-18 11:33:30 +01:00
|
|
|
sock = sock_connect(src_addr, src_port, timeout_ms, sock_pending, res, ka);
|
2025-02-18 11:18:17 +01:00
|
|
|
if (sock == -1) {
|
|
|
|
if (!sock_pending || (*sock_pending == -1)) {
|
|
|
|
/* try the next resource */
|
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
/* timeout, keep pending socket */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2025-02-18 11:33:30 +01:00
|
|
|
|
|
|
|
if (res->ai_family == AF_INET) {
|
|
|
|
VRB(NULL, "Successfully connected to %s:%s over IPv4.", dst_addr, dst_port_str);
|
|
|
|
} else {
|
|
|
|
VRB(NULL, "Successfully connected to [%s]:%s over IPv6.", dst_addr, dst_port_str);
|
|
|
|
}
|
2025-02-18 11:18:17 +01:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
if (nc_saddr2str(res->ai_addr, ip_host, NULL)) {
|
|
|
|
goto error;
|
2025-02-18 11:18:17 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
freeaddrinfo(res_list);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
/* try to get a connection with the pending socket */
|
|
|
|
assert(sock_pending);
|
2025-02-18 11:33:30 +01:00
|
|
|
sock = sock_connect(src_addr, src_port, timeout_ms, sock_pending, NULL, ka);
|
|
|
|
|
|
|
|
if (sock > 0) {
|
|
|
|
if (getpeername(sock, (struct sockaddr *)&saddr, &addr_len)) {
|
|
|
|
ERR(NULL, "getpeername failed (%s).", strerror(errno));
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (nc_saddr2str((struct sockaddr *)&saddr, ip_host, NULL)) {
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
}
|
2025-02-18 11:18:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return sock;
|
|
|
|
|
|
|
|
error:
|
|
|
|
if (res_list) {
|
|
|
|
freeaddrinfo(res_list);
|
|
|
|
}
|
|
|
|
if (sock != -1) {
|
|
|
|
close(sock);
|
|
|
|
}
|
|
|
|
if (sock_pending) {
|
|
|
|
*sock_pending = -1;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
#ifdef NC_ENABLED_SSH_TLS
|
2025-02-18 11:18:17 +01:00
|
|
|
|
|
|
|
int
|
2025-02-18 11:33:30 +01:00
|
|
|
nc_client_ch_add_bind_listen(const char *address, uint16_t port, const char *hostname, NC_TRANSPORT_IMPL ti)
|
2025-02-18 11:18:17 +01:00
|
|
|
{
|
|
|
|
int sock;
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
NC_CHECK_ARG_RET(NULL, address, port, -1);
|
2025-02-18 11:18:17 +01:00
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
sock = nc_sock_listen_inet(address, port);
|
2025-02-18 11:18:17 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
client_opts.ch_binds_aux = nc_realloc(client_opts.ch_binds_aux, client_opts.ch_bind_count * sizeof *client_opts.ch_binds_aux);
|
|
|
|
if (!client_opts.ch_binds_aux) {
|
2025-02-18 11:18:17 +01:00
|
|
|
ERRMEM;
|
|
|
|
close(sock);
|
|
|
|
return -1;
|
|
|
|
}
|
2025-02-18 11:33:30 +01:00
|
|
|
client_opts.ch_binds_aux[client_opts.ch_bind_count - 1].ti = ti;
|
|
|
|
client_opts.ch_binds_aux[client_opts.ch_bind_count - 1].hostname = hostname ? strdup(hostname) : NULL;
|
2025-02-18 11:18:17 +01:00
|
|
|
|
|
|
|
client_opts.ch_binds[client_opts.ch_bind_count - 1].address = strdup(address);
|
|
|
|
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);
|
2025-02-18 11:33:30 +01:00
|
|
|
free(client_opts.ch_binds[i].address);
|
|
|
|
|
|
|
|
free(client_opts.ch_binds_aux[i].hostname);
|
2025-02-18 11:18:17 +01:00
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
}
|
2025-02-18 11:33:30 +01:00
|
|
|
client_opts.ch_bind_count = 0;
|
|
|
|
|
2025-02-18 11:18:17 +01:00
|
|
|
free(client_opts.ch_binds);
|
|
|
|
client_opts.ch_binds = NULL;
|
2025-02-18 11:33:30 +01:00
|
|
|
|
|
|
|
free(client_opts.ch_binds_aux);
|
|
|
|
client_opts.ch_binds_aux = NULL;
|
2025-02-18 11:18:17 +01:00
|
|
|
} 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)) &&
|
2025-02-18 11:33:30 +01:00
|
|
|
(!ti || (client_opts.ch_binds_aux[i].ti == ti))) {
|
2025-02-18 11:18:17 +01:00
|
|
|
close(client_opts.ch_binds[i].sock);
|
2025-02-18 11:33:30 +01:00
|
|
|
free(client_opts.ch_binds[i].address);
|
2025-02-18 11:18:17 +01:00
|
|
|
|
|
|
|
--client_opts.ch_bind_count;
|
|
|
|
if (!client_opts.ch_bind_count) {
|
|
|
|
free(client_opts.ch_binds);
|
|
|
|
client_opts.ch_binds = NULL;
|
2025-02-18 11:33:30 +01:00
|
|
|
|
|
|
|
free(client_opts.ch_binds_aux);
|
|
|
|
client_opts.ch_binds_aux = NULL;
|
2025-02-18 11:18:17 +01:00
|
|
|
} else if (i < client_opts.ch_bind_count) {
|
2025-02-18 11:33:30 +01:00
|
|
|
memcpy(&client_opts.ch_binds[i], &client_opts.ch_binds[client_opts.ch_bind_count],
|
|
|
|
sizeof *client_opts.ch_binds);
|
|
|
|
|
|
|
|
memcpy(&client_opts.ch_binds_aux[i], &client_opts.ch_binds_aux[client_opts.ch_bind_count],
|
|
|
|
sizeof *client_opts.ch_binds_aux);
|
2025-02-18 11:18:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
API int
|
|
|
|
nc_accept_callhome(int timeout, struct ly_ctx *ctx, struct nc_session **session)
|
|
|
|
{
|
2025-02-18 11:33:30 +01:00
|
|
|
int ret, sock;
|
2025-02-18 11:18:17 +01:00
|
|
|
char *host = NULL;
|
|
|
|
uint16_t port, idx;
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
NC_CHECK_ARG_RET(NULL, session, -1);
|
|
|
|
|
2025-02-18 11:18:17 +01:00
|
|
|
if (!client_opts.ch_binds) {
|
2025-02-18 11:33:30 +01:00
|
|
|
ERR(NULL, "Call-Home binds not set.");
|
2025-02-18 11:18:17 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
ret = nc_sock_accept_binds(client_opts.ch_binds, client_opts.ch_bind_count, &client_opts.ch_bind_lock, timeout,
|
|
|
|
&host, &port, &idx, &sock);
|
|
|
|
if (ret < 1) {
|
|
|
|
free(host);
|
|
|
|
return ret;
|
|
|
|
}
|
2025-02-18 11:18:17 +01:00
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
/* configure keepalives */
|
|
|
|
if (nc_sock_configure_ka(sock, &client_opts.ka)) {
|
2025-02-18 11:18:17 +01:00
|
|
|
free(host);
|
2025-02-18 11:33:30 +01:00
|
|
|
close(sock);
|
|
|
|
return -1;
|
2025-02-18 11:18:17 +01:00
|
|
|
}
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
if (client_opts.ch_binds_aux[idx].ti == NC_TI_SSH) {
|
2025-02-18 11:18:17 +01:00
|
|
|
*session = nc_accept_callhome_ssh_sock(sock, host, port, ctx, NC_TRANSPORT_TIMEOUT);
|
2025-02-18 11:33:30 +01:00
|
|
|
} else if (client_opts.ch_binds_aux[idx].ti == NC_TI_TLS) {
|
|
|
|
*session = nc_accept_callhome_tls_sock(sock, host, port, ctx, NC_TRANSPORT_TIMEOUT,
|
|
|
|
client_opts.ch_binds_aux[idx].hostname);
|
|
|
|
} else {
|
2025-02-18 11:18:17 +01:00
|
|
|
close(sock);
|
|
|
|
*session = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
free(host);
|
|
|
|
|
|
|
|
if (!(*session)) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
#endif /* NC_ENABLED_SSH_TLS */
|
2025-02-18 11:18:17 +01:00
|
|
|
|
|
|
|
API const char * const *
|
|
|
|
nc_session_get_cpblts(const struct nc_session *session)
|
|
|
|
{
|
2025-02-18 11:33:30 +01:00
|
|
|
NC_CHECK_ARG_RET(session, session, NULL);
|
2025-02-18 11:18:17 +01:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
NC_CHECK_ARG_RET(session, session, capab, NULL);
|
2025-02-18 11:18:17 +01:00
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2025-02-18 11:33:30 +01:00
|
|
|
NC_CHECK_ARG_RET(session, session, 0);
|
|
|
|
|
|
|
|
if (session->side != NC_CLIENT) {
|
|
|
|
ERRARG(NULL, "session");
|
2025-02-18 11:18:17 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
return ATOMIC_LOAD_RELAXED(session->opts.client.ntf_thread_running);
|
2025-02-18 11:18:17 +01:00
|
|
|
}
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
API int
|
2025-02-18 11:18:17 +01:00
|
|
|
nc_client_init(void)
|
|
|
|
{
|
2025-02-18 11:33:30 +01:00
|
|
|
int r;
|
|
|
|
|
|
|
|
if ((r = pthread_mutex_init(&client_opts.ch_bind_lock, NULL))) {
|
|
|
|
ERR(NULL, "%s: failed to init bind lock(%s).", __func__, strerror(r));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef NC_ENABLED_SSH_TLS
|
|
|
|
if (ssh_init()) {
|
|
|
|
ERR(NULL, "%s: failed to init libssh.", __func__);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return 0;
|
2025-02-18 11:18:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
API void
|
|
|
|
nc_client_destroy(void)
|
|
|
|
{
|
2025-02-18 11:33:30 +01:00
|
|
|
pthread_mutex_destroy(&client_opts.ch_bind_lock);
|
2025-02-18 11:18:17 +01:00
|
|
|
nc_client_set_schema_searchpath(NULL);
|
2025-02-18 11:33:30 +01:00
|
|
|
#ifdef NC_ENABLED_SSH_TLS
|
2025-02-18 11:18:17 +01:00
|
|
|
nc_client_ch_del_bind(NULL, 0, 0);
|
|
|
|
nc_client_ssh_destroy_opts();
|
|
|
|
nc_client_tls_destroy_opts();
|
2025-02-18 11:33:30 +01:00
|
|
|
ssh_finalize();
|
|
|
|
#endif /* NC_ENABLED_SSH_TLS */
|
2025-02-18 11:18:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2025-02-18 11:33:30 +01:00
|
|
|
* @return NC_MSG_ERROR If any error occurred;
|
2025-02-18 11:18:17 +01:00
|
|
|
* @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);
|
2025-02-18 11:33:30 +01:00
|
|
|
NC_CHECK_ERRMEM_GOTO(!*cont_ptr, ret = NC_MSG_ERROR, cleanup_unlock);
|
2025-02-18 11:18:17 +01:00
|
|
|
(*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;
|
2025-02-18 11:33:30 +01:00
|
|
|
uint32_t temp_lo = LY_LOSTORE, *prev_lo;
|
2025-02-18 11:18:17 +01:00
|
|
|
|
|
|
|
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 */
|
2025-02-18 11:33:30 +01:00
|
|
|
prev_lo = ly_temp_log_options(&temp_lo);
|
2025-02-18 11:18:17 +01:00
|
|
|
lyrc = lyd_parse_op(NULL, op, msg, LYD_XML, LYD_TYPE_REPLY_NETCONF, envp, NULL);
|
2025-02-18 11:33:30 +01:00
|
|
|
ly_temp_log_options(prev_lo);
|
|
|
|
|
|
|
|
if (*envp) {
|
|
|
|
/* if the envelopes were parsed, check the message-id, even on error */
|
2025-02-18 11:18:17 +01:00
|
|
|
ret = recv_reply_check_msgid(session, *envp, msgid);
|
|
|
|
goto cleanup;
|
2025-02-18 11:33:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (lyrc) {
|
|
|
|
/* parsing error */
|
|
|
|
ERR(session, "Received an invalid message (%s).", ly_err_last(LYD_CTX(op))->msg);
|
|
|
|
lyd_free_tree(*envp);
|
|
|
|
*envp = NULL;
|
2025-02-18 11:18:17 +01:00
|
|
|
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) {
|
2025-02-18 11:33:30 +01:00
|
|
|
lyd_free_tree(tree);
|
2025-02-18 11:18:17 +01:00
|
|
|
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) {
|
2025-02-18 11:33:30 +01:00
|
|
|
ERR(session, "Missing \"%s\" module in the context.", module_name);
|
2025-02-18 11:18:17 +01:00
|
|
|
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)) {
|
2025-02-18 11:33:30 +01:00
|
|
|
ERR(session, "Missing \"%s\" module in the context.", module_check);
|
2025-02-18 11:18:17 +01:00
|
|
|
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;
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
NC_CHECK_ARG_RET(session, session, rpc, envp, op, NC_MSG_ERROR);
|
|
|
|
|
|
|
|
if ((session->status != NC_STATUS_RUNNING) || (session->side != NC_CLIENT)) {
|
2025-02-18 11:18:17 +01:00
|
|
|
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 {
|
2025-02-18 11:33:30 +01:00
|
|
|
ERR(session, "Received an invalid message (%s).", ly_err_last(session->ctx)->msg);
|
|
|
|
lyd_free_tree(*envp);
|
|
|
|
*envp = NULL;
|
2025-02-18 11:18:17 +01:00
|
|
|
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)
|
|
|
|
{
|
2025-02-18 11:33:30 +01:00
|
|
|
NC_CHECK_ARG_RET(session, session, envp, op, NC_MSG_ERROR);
|
|
|
|
|
|
|
|
if ((session->status != NC_STATUS_RUNNING) || (session->side != NC_CLIENT)) {
|
2025-02-18 11:18:17 +01:00
|
|
|
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;
|
2025-02-18 11:33:30 +01:00
|
|
|
nc_notif_dispatch_clb notif_clb;
|
|
|
|
void *user_data;
|
2025-02-18 11:18:17 +01:00
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
void (*free_data)(void *);
|
2025-02-18 11:18:17 +01:00
|
|
|
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;
|
2025-02-18 11:33:30 +01:00
|
|
|
user_data = ntarg->user_data;
|
|
|
|
free_data = ntarg->free_data;
|
2025-02-18 11:18:17 +01:00
|
|
|
free(ntarg);
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
while (ATOMIC_LOAD_RELAXED(session->opts.client.ntf_thread_running)) {
|
2025-02-18 11:18:17 +01:00
|
|
|
msgtype = nc_recv_notif(session, NC_CLIENT_NOTIF_THREAD_SLEEP / 1000, &envp, &op);
|
|
|
|
if (msgtype == NC_MSG_NOTIF) {
|
2025-02-18 11:33:30 +01:00
|
|
|
notif_clb(session, envp, op, user_data);
|
2025-02-18 11:18:17 +01:00
|
|
|
if (!strcmp(op->schema->name, "notificationComplete") && !strcmp(op->schema->module->name, "nc-notifications")) {
|
2025-02-18 11:33:30 +01:00
|
|
|
lyd_free_all(envp);
|
|
|
|
lyd_free_all(op);
|
2025-02-18 11:18:17 +01:00
|
|
|
break;
|
|
|
|
}
|
2025-02-18 11:33:30 +01:00
|
|
|
lyd_free_all(envp);
|
|
|
|
lyd_free_all(op);
|
2025-02-18 11:18:17 +01:00
|
|
|
} 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.");
|
2025-02-18 11:33:30 +01:00
|
|
|
ATOMIC_DEC_RELAXED(session->opts.client.ntf_thread_count);
|
|
|
|
if (free_data) {
|
|
|
|
free_data(user_data);
|
|
|
|
}
|
|
|
|
|
2025-02-18 11:18:17 +01:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
API int
|
2025-02-18 11:33:30 +01:00
|
|
|
nc_recv_notif_dispatch(struct nc_session *session, nc_notif_dispatch_clb notif_clb)
|
|
|
|
{
|
|
|
|
return nc_recv_notif_dispatch_data(session, notif_clb, NULL, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
API int
|
|
|
|
nc_recv_notif_dispatch_data(struct nc_session *session, nc_notif_dispatch_clb notif_clb, void *user_data,
|
|
|
|
void (*free_data)(void *))
|
2025-02-18 11:18:17 +01:00
|
|
|
{
|
|
|
|
struct nc_ntf_thread_arg *ntarg;
|
|
|
|
pthread_t tid;
|
|
|
|
int ret;
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
NC_CHECK_ARG_RET(session, session, notif_clb, -1);
|
|
|
|
|
|
|
|
if ((session->status != NC_STATUS_RUNNING) || (session->side != NC_CLIENT)) {
|
2025-02-18 11:18:17 +01:00
|
|
|
ERR(session, "Invalid session to receive Notifications.");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ntarg = malloc(sizeof *ntarg);
|
2025-02-18 11:33:30 +01:00
|
|
|
NC_CHECK_ERRMEM_RET(!ntarg, -1);
|
|
|
|
|
2025-02-18 11:18:17 +01:00
|
|
|
ntarg->session = session;
|
|
|
|
ntarg->notif_clb = notif_clb;
|
2025-02-18 11:33:30 +01:00
|
|
|
ntarg->user_data = user_data;
|
|
|
|
ntarg->free_data = free_data;
|
|
|
|
ATOMIC_INC_RELAXED(session->opts.client.ntf_thread_count);
|
2025-02-18 11:18:17 +01:00
|
|
|
|
|
|
|
/* just so that nc_recv_notif_thread() does not immediately exit */
|
2025-02-18 11:33:30 +01:00
|
|
|
ATOMIC_STORE_RELAXED(session->opts.client.ntf_thread_running, 1);
|
2025-02-18 11:18:17 +01:00
|
|
|
|
|
|
|
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);
|
2025-02-18 11:33:30 +01:00
|
|
|
if (ATOMIC_DEC_RELAXED(session->opts.client.ntf_thread_count) == 1) {
|
|
|
|
ATOMIC_STORE_RELAXED(session->opts.client.ntf_thread_running, 0);
|
|
|
|
}
|
2025-02-18 11:18:17 +01:00
|
|
|
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;
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
NC_CHECK_ARG_RET(session, session, rpc, msgid, NC_MSG_ERROR);
|
|
|
|
|
|
|
|
if ((session->status != NC_STATUS_RUNNING) || (session->side != NC_CLIENT)) {
|
2025-02-18 11:18:17 +01:00
|
|
|
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) {
|
2025-02-18 11:33:30 +01:00
|
|
|
ERR(session, "Missing \"ietf-netconf\" module in the context.");
|
2025-02-18 11:18:17 +01:00
|
|
|
return NC_MSG_ERROR;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case NC_RPC_GETSCHEMA:
|
|
|
|
mod = ly_ctx_get_module_implemented(session->ctx, "ietf-netconf-monitoring");
|
|
|
|
if (!mod) {
|
2025-02-18 11:33:30 +01:00
|
|
|
ERR(session, "Missing \"ietf-netconf-monitoring\" module in the context.");
|
2025-02-18 11:18:17 +01:00
|
|
|
return NC_MSG_ERROR;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case NC_RPC_SUBSCRIBE:
|
|
|
|
mod = ly_ctx_get_module_implemented(session->ctx, "notifications");
|
|
|
|
if (!mod) {
|
2025-02-18 11:33:30 +01:00
|
|
|
ERR(session, "Missing \"notifications\" module in the context.");
|
2025-02-18 11:18:17 +01:00
|
|
|
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) {
|
2025-02-18 11:33:30 +01:00
|
|
|
ERR(session, "Missing \"ietf-netconf-nmda\" module in the context.");
|
2025-02-18 11:18:17 +01:00
|
|
|
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) {
|
2025-02-18 11:33:30 +01:00
|
|
|
ERR(session, "Missing \"ietf-subscribed-notifications\" module in the context.");
|
2025-02-18 11:18:17 +01:00
|
|
|
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) {
|
2025-02-18 11:33:30 +01:00
|
|
|
ERR(session, "Missing \"ietf-subscribed-notifications\" module in the context.");
|
2025-02-18 11:18:17 +01:00
|
|
|
return NC_MSG_ERROR;
|
|
|
|
}
|
|
|
|
mod2 = ly_ctx_get_module_implemented(session->ctx, "ietf-yang-push");
|
|
|
|
if (!mod2) {
|
2025-02-18 11:33:30 +01:00
|
|
|
ERR(session, "Missing \"ietf-yang-push\" module in the context.");
|
2025-02-18 11:18:17 +01:00
|
|
|
return NC_MSG_ERROR;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case NC_RPC_RESYNCSUB:
|
|
|
|
mod = ly_ctx_get_module_implemented(session->ctx, "ietf-yang-push");
|
|
|
|
if (!mod) {
|
2025-02-18 11:33:30 +01:00
|
|
|
ERR(session, "Missing \"ietf-yang-push\" module in the context.");
|
2025-02-18 11:18:17 +01:00
|
|
|
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] == '<')) {
|
2025-02-18 11:33:30 +01:00
|
|
|
CHECK_LYRC_BREAK(lyd_new_any(data, mod, "filter", rpc_gc->filter, LYD_ANYDATA_XML, 0, &node));
|
2025-02-18 11:18:17 +01:00
|
|
|
CHECK_LYRC_BREAK(lyd_new_meta(NULL, node, NULL, "ietf-netconf:type", "subtree", 0, NULL));
|
|
|
|
} else {
|
2025-02-18 11:33:30 +01:00
|
|
|
CHECK_LYRC_BREAK(lyd_new_any(data, mod, "filter", NULL, LYD_ANYDATA_STRING, 0, &node));
|
2025-02-18 11:18:17 +01:00
|
|
|
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) {
|
2025-02-18 11:33:30 +01:00
|
|
|
ERR(session, "Missing \"ietf-netconf-with-defaults\" module in the context.");
|
2025-02-18 11:18:17 +01:00
|
|
|
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] == '<')) {
|
2025-02-18 11:33:30 +01:00
|
|
|
CHECK_LYRC_BREAK(lyd_new_any(data, mod, "config", rpc_e->edit_cont, LYD_ANYDATA_XML, 0, NULL));
|
2025-02-18 11:18:17 +01:00
|
|
|
} 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] == '<')) {
|
2025-02-18 11:33:30 +01:00
|
|
|
CHECK_LYRC_BREAK(lyd_new_any(cont, mod, "config", rpc_cp->url_config_src, LYD_ANYDATA_XML, 0, NULL));
|
2025-02-18 11:18:17 +01:00
|
|
|
} 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) {
|
2025-02-18 11:33:30 +01:00
|
|
|
ERR(session, "Missing \"ietf-netconf-with-defaults\" module in the context.");
|
2025-02-18 11:18:17 +01:00
|
|
|
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] == '<')) {
|
2025-02-18 11:33:30 +01:00
|
|
|
CHECK_LYRC_BREAK(lyd_new_any(data, mod, "filter", rpc_g->filter, LYD_ANYDATA_XML, 0, &node));
|
2025-02-18 11:18:17 +01:00
|
|
|
CHECK_LYRC_BREAK(lyd_new_meta(NULL, node, NULL, "ietf-netconf:type", "subtree", 0, NULL));
|
|
|
|
} else {
|
2025-02-18 11:33:30 +01:00
|
|
|
CHECK_LYRC_BREAK(lyd_new_any(data, mod, "filter", NULL, LYD_ANYDATA_STRING, 0, &node));
|
2025-02-18 11:18:17 +01:00
|
|
|
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) {
|
2025-02-18 11:33:30 +01:00
|
|
|
ERR(session, "Missing \"ietf-netconf-with-defaults\" module in the context.");
|
2025-02-18 11:18:17 +01:00
|
|
|
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));
|
2025-02-18 11:33:30 +01:00
|
|
|
sprintf(str, "%" PRIu32, rpc_k->sid);
|
2025-02-18 11:18:17 +01:00
|
|
|
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) {
|
2025-02-18 11:33:30 +01:00
|
|
|
sprintf(str, "%" PRIu32, rpc_com->confirm_timeout);
|
2025-02-18 11:18:17 +01:00
|
|
|
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] == '<')) {
|
2025-02-18 11:33:30 +01:00
|
|
|
CHECK_LYRC_BREAK(lyd_new_any(cont, mod, "config", rpc_val->url_config_src, LYD_ANYDATA_XML, 0, NULL));
|
2025-02-18 11:18:17 +01:00
|
|
|
} 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] == '<')) {
|
2025-02-18 11:33:30 +01:00
|
|
|
CHECK_LYRC_BREAK(lyd_new_any(data, mod, "filter", rpc_sub->filter, LYD_ANYDATA_XML, 0, &node));
|
2025-02-18 11:18:17 +01:00
|
|
|
CHECK_LYRC_BREAK(lyd_new_meta(NULL, node, NULL, "ietf-netconf:type", "subtree", 0, NULL));
|
|
|
|
} else {
|
2025-02-18 11:33:30 +01:00
|
|
|
CHECK_LYRC_BREAK(lyd_new_any(data, mod, "filter", NULL, LYD_ANYDATA_STRING, 0, &node));
|
2025-02-18 11:18:17 +01:00
|
|
|
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] == '<')) {
|
2025-02-18 11:33:30 +01:00
|
|
|
CHECK_LYRC_BREAK(lyd_new_any(data, mod, "subtree-filter", rpc_getd->filter, LYD_ANYDATA_XML, 0, NULL));
|
2025-02-18 11:18:17 +01:00
|
|
|
} 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] == '<')) {
|
2025-02-18 11:33:30 +01:00
|
|
|
CHECK_LYRC_BREAK(lyd_new_any(data, mod, "config", rpc_editd->edit_cont, LYD_ANYDATA_XML, 0, NULL));
|
2025-02-18 11:18:17 +01:00
|
|
|
} 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] == '<')) {
|
2025-02-18 11:33:30 +01:00
|
|
|
CHECK_LYRC_BREAK(lyd_new_any(data, mod, "stream-subtree-filter", rpc_estsub->filter, LYD_ANYDATA_XML,
|
2025-02-18 11:18:17 +01:00
|
|
|
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));
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
sprintf(str, "%" PRIu32, rpc_modsub->id);
|
2025-02-18 11:18:17 +01:00
|
|
|
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] == '<')) {
|
2025-02-18 11:33:30 +01:00
|
|
|
CHECK_LYRC_BREAK(lyd_new_any(data, mod, "stream-subtree-filter", rpc_modsub->filter, LYD_ANYDATA_XML,
|
2025-02-18 11:18:17 +01:00
|
|
|
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));
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
sprintf(str, "%" PRIu32, rpc_delsub->id);
|
2025-02-18 11:18:17 +01:00
|
|
|
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));
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
sprintf(str, "%" PRIu32, rpc_killsub->id);
|
2025-02-18 11:18:17 +01:00
|
|
|
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] == '<')) {
|
2025-02-18 11:33:30 +01:00
|
|
|
CHECK_LYRC_BREAK(lyd_new_any(data, mod2, "datastore-subtree-filter", rpc_estpush->filter,
|
2025-02-18 11:18:17 +01:00
|
|
|
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));
|
|
|
|
|
2025-02-18 11:33:30 +01:00
|
|
|
sprintf(str, "%" PRIu32, rpc_modpush->id);
|
2025-02-18 11:18:17 +01:00
|
|
|
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] == '<')) {
|
2025-02-18 11:33:30 +01:00
|
|
|
CHECK_LYRC_BREAK(lyd_new_any(data, mod2, "datastore-subtree-filter", rpc_modpush->filter,
|
2025-02-18 11:18:17 +01:00
|
|
|
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));
|
2025-02-18 11:33:30 +01:00
|
|
|
sprintf(str, "%" PRIu32, rpc_resyncsub->id);
|
2025-02-18 11:18:17 +01:00
|
|
|
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) {
|
2025-02-18 11:33:30 +01:00
|
|
|
ERRARG(NULL, "session");
|
2025-02-18 11:18:17 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
session->flags |= NC_SESSION_CLIENT_NOT_STRICT;
|
|
|
|
}
|
2025-02-18 11:33:30 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Get the file descriptor of a session.
|
|
|
|
*
|
|
|
|
* @param[in] session Session.
|
|
|
|
*
|
|
|
|
* @return File descriptor of the session or -1 in case of an error.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
nc_client_monitoring_get_session_fd(struct nc_session *session)
|
|
|
|
{
|
|
|
|
int fd = -1;
|
|
|
|
|
|
|
|
switch (session->ti_type) {
|
|
|
|
case NC_TI_FD:
|
|
|
|
fd = session->ti.fd.in;
|
|
|
|
break;
|
|
|
|
case NC_TI_UNIX:
|
|
|
|
fd = session->ti.unixsock.sock;
|
|
|
|
break;
|
|
|
|
#ifdef NC_ENABLED_SSH_TLS
|
|
|
|
case NC_TI_SSH:
|
|
|
|
fd = ssh_get_fd(session->ti.libssh.session);
|
|
|
|
break;
|
|
|
|
case NC_TI_TLS:
|
|
|
|
fd = nc_tls_get_fd_wrap(session);
|
|
|
|
break;
|
|
|
|
#endif /* NC_ENABLED_SSH_TLS */
|
|
|
|
case NC_TI_NONE:
|
|
|
|
/* invalid */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return fd;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Lock the client monitoring data.
|
|
|
|
*
|
|
|
|
* @param[in] timeout Timeout in msec to use.
|
|
|
|
*
|
|
|
|
* @return 1 on success;
|
|
|
|
* @return 0 on timeout;
|
|
|
|
* @return -1 on error.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
nc_client_monitoring_lock(int timeout)
|
|
|
|
{
|
|
|
|
int r;
|
|
|
|
struct timespec ts;
|
|
|
|
|
|
|
|
if (timeout > 0) {
|
|
|
|
nc_timeouttime_get(&ts, timeout);
|
|
|
|
r = pthread_mutex_clocklock(&client_opts.monitoring_thread_data.lock, COMPAT_CLOCK_ID, &ts);
|
|
|
|
} else if (!timeout) {
|
|
|
|
r = pthread_mutex_trylock(&client_opts.monitoring_thread_data.lock);
|
|
|
|
} else {
|
|
|
|
r = pthread_mutex_lock(&client_opts.monitoring_thread_data.lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (r) {
|
|
|
|
ERR(NULL, "Failed to lock the client monitoring data lock (%s).", strerror(r));
|
|
|
|
|
|
|
|
if ((r == EBUSY) || (r == ETIMEDOUT)) {
|
|
|
|
/* timeout */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* error */
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
nc_client_monitoring_session_start(struct nc_session *session)
|
|
|
|
{
|
|
|
|
int ret = 0, fd, r;
|
|
|
|
struct nc_client_monitoring_thread_arg *mtarg;
|
|
|
|
void *tmp, *tmp2;
|
|
|
|
|
|
|
|
/* LOCK */
|
|
|
|
r = nc_client_monitoring_lock(NC_CLIENT_MONITORING_LOCK_TIMEOUT);
|
|
|
|
if (r < 1) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* check if the monitoring thread is running */
|
|
|
|
if (!client_opts.monitoring_thread_data.thread_running) {
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
mtarg = &client_opts.monitoring_thread_data;
|
|
|
|
|
|
|
|
/* get the session's file descriptor */
|
|
|
|
fd = nc_client_monitoring_get_session_fd(session);
|
|
|
|
assert(fd != -1);
|
|
|
|
|
|
|
|
/* if realloc fails, keep the original sessions without adding the new one */
|
|
|
|
tmp = realloc(mtarg->sessions, (mtarg->session_count + 1) * sizeof *mtarg->sessions);
|
|
|
|
NC_CHECK_ERRMEM_GOTO(!tmp, ret = 1, cleanup);
|
|
|
|
tmp2 = realloc(mtarg->pfds, (mtarg->pfd_count + 1) * sizeof *mtarg->pfds);
|
|
|
|
NC_CHECK_ERRMEM_GOTO(!tmp2, free(tmp); ret = 1, cleanup);
|
|
|
|
|
|
|
|
mtarg->sessions = tmp;
|
|
|
|
mtarg->sessions[mtarg->session_count] = session;
|
|
|
|
mtarg->session_count++;
|
|
|
|
|
|
|
|
/* we are only interested in POLLHUP or POLLNVAL */
|
|
|
|
mtarg->pfds = tmp2;
|
|
|
|
mtarg->pfds[mtarg->pfd_count].fd = fd;
|
|
|
|
mtarg->pfds[mtarg->pfd_count].events = 0;
|
|
|
|
mtarg->pfds[mtarg->pfd_count].revents = 0;
|
|
|
|
mtarg->pfd_count++;
|
|
|
|
|
|
|
|
session->flags |= NC_SESSION_CLIENT_MONITORED;
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
/* UNLOCK */
|
|
|
|
pthread_mutex_unlock(&client_opts.monitoring_thread_data.lock);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
nc_client_monitoring_session_stop(struct nc_session *session, int lock)
|
|
|
|
{
|
|
|
|
int i, r;
|
|
|
|
struct nc_client_monitoring_thread_arg *mtarg;
|
|
|
|
|
|
|
|
if (lock) {
|
|
|
|
/* LOCK */
|
|
|
|
r = nc_client_monitoring_lock(NC_CLIENT_MONITORING_LOCK_TIMEOUT);
|
|
|
|
if (r < 1) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mtarg = &client_opts.monitoring_thread_data;
|
|
|
|
|
|
|
|
/* session is no longer monitored (is being freed) */
|
|
|
|
if (!(session->flags & NC_SESSION_CLIENT_MONITORED)) {
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* find the session */
|
|
|
|
for (i = 0; i < mtarg->session_count; i++) {
|
|
|
|
if (mtarg->sessions[i] == session) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (i == mtarg->session_count) {
|
|
|
|
/* not found */
|
|
|
|
ERRINT;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* remove the session */
|
|
|
|
mtarg->session_count--;
|
|
|
|
mtarg->pfd_count--;
|
|
|
|
if (!mtarg->session_count) {
|
|
|
|
free(mtarg->sessions);
|
|
|
|
mtarg->sessions = NULL;
|
|
|
|
free(mtarg->pfds);
|
|
|
|
mtarg->pfds = NULL;
|
|
|
|
} else if (mtarg->sessions[i] != mtarg->sessions[mtarg->session_count]) {
|
|
|
|
mtarg->sessions[i] = mtarg->sessions[mtarg->session_count];
|
|
|
|
mtarg->pfds[i] = mtarg->pfds[mtarg->session_count];
|
|
|
|
}
|
|
|
|
|
|
|
|
session->flags &= ~NC_SESSION_CLIENT_MONITORED;
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
if (lock) {
|
|
|
|
/* UNLOCK */
|
|
|
|
pthread_mutex_unlock(&mtarg->lock);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Monitoring thread for client sessions.
|
|
|
|
*
|
|
|
|
* @param[in] arg Thread context of the creating thread.
|
|
|
|
* @return NULL.
|
|
|
|
*/
|
|
|
|
static void *
|
|
|
|
nc_client_monitoring_thread(void *arg)
|
|
|
|
{
|
|
|
|
int r, locked = 0;
|
|
|
|
struct nc_client_monitoring_thread_arg *mtarg;
|
|
|
|
nfds_t i;
|
|
|
|
struct nc_session *session = NULL;
|
|
|
|
|
|
|
|
/* set this thread's context to the one from the thread that started the monitoring thread */
|
|
|
|
nc_client_set_thread_context(arg);
|
|
|
|
|
|
|
|
/* LOCK */
|
|
|
|
r = nc_client_monitoring_lock(NC_CLIENT_MONITORING_LOCK_TIMEOUT);
|
|
|
|
if (r < 1) {
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
locked = 1;
|
|
|
|
|
|
|
|
mtarg = &client_opts.monitoring_thread_data;
|
|
|
|
|
|
|
|
while (mtarg->thread_running) {
|
|
|
|
if (!mtarg->session_count) {
|
|
|
|
goto next_iter;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* poll for either POLLIN or POLLHUP events */
|
|
|
|
r = poll(mtarg->pfds, mtarg->pfd_count, 0);
|
|
|
|
if (r == -1) {
|
|
|
|
if (errno == EINTR) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
ERR(NULL, "Client monitoring thread: poll failed (%s).", strerror(errno));
|
|
|
|
goto cleanup;
|
|
|
|
} else if (!r) {
|
|
|
|
/* no events */
|
|
|
|
goto next_iter;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < mtarg->pfd_count; i++) {
|
|
|
|
if (mtarg->pfds[i].revents & (POLLHUP | POLLNVAL)) {
|
|
|
|
/* save the session and stop monitoring it, callback will be called outside of the lock */
|
|
|
|
session = mtarg->sessions[i];
|
|
|
|
session->status = NC_STATUS_INVALID;
|
|
|
|
session->term_reason = NC_SESSION_TERM_DROPPED;
|
|
|
|
nc_client_monitoring_session_stop(session, 0);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
next_iter:
|
|
|
|
/* UNLOCK */
|
|
|
|
pthread_mutex_unlock(&mtarg->lock);
|
|
|
|
locked = 0;
|
|
|
|
|
|
|
|
/* if an event occurred, call the callback */
|
|
|
|
if (session) {
|
|
|
|
mtarg->clb(session, mtarg->clb_data);
|
|
|
|
}
|
|
|
|
session = NULL;
|
|
|
|
|
|
|
|
usleep(NC_CLIENT_MONITORING_BACKOFF * 1000);
|
|
|
|
|
|
|
|
/* LOCK */
|
|
|
|
r = nc_client_monitoring_lock(NC_CLIENT_MONITORING_LOCK_TIMEOUT);
|
|
|
|
if (r < 1) {
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
locked = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
if (locked) {
|
|
|
|
/* UNLOCK */
|
|
|
|
pthread_mutex_unlock(&mtarg->lock);
|
|
|
|
}
|
|
|
|
VRB(NULL, "Client monitoring thread exit.");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
API int
|
|
|
|
nc_client_monitoring_thread_start(nc_client_monitoring_clb monitoring_clb, void *user_data, void (*free_data)(void *))
|
|
|
|
{
|
|
|
|
int ret = 0, r;
|
|
|
|
void *ctx;
|
|
|
|
|
|
|
|
NC_CHECK_ARG_RET(NULL, monitoring_clb, 1);
|
|
|
|
|
|
|
|
/* LOCK */
|
|
|
|
r = nc_client_monitoring_lock(NC_CLIENT_MONITORING_LOCK_TIMEOUT);
|
|
|
|
if (r < 1) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (client_opts.monitoring_thread_data.thread_running) {
|
|
|
|
ERR(NULL, "Client monitoring thread is already running.");
|
|
|
|
ret = 1;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
client_opts.monitoring_thread_data.clb = monitoring_clb;
|
|
|
|
client_opts.monitoring_thread_data.clb_data = user_data;
|
|
|
|
client_opts.monitoring_thread_data.clb_free_data = free_data;
|
|
|
|
|
|
|
|
/* get the current thread context, so that the monitoring thread can use it */
|
|
|
|
ctx = nc_client_get_thread_context();
|
|
|
|
if (!ctx) {
|
|
|
|
ERR(NULL, "Failed to get the client thread context.");
|
|
|
|
ret = 1;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
client_opts.monitoring_thread_data.thread_running = 1;
|
|
|
|
|
|
|
|
r = pthread_create(&client_opts.monitoring_thread_data.tid, NULL,
|
|
|
|
nc_client_monitoring_thread, ctx);
|
|
|
|
if (r) {
|
|
|
|
ERR(NULL, "Failed to create the client monitoring thread (%s).", strerror(r));
|
|
|
|
ret = 1;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
/* UNLOCK */
|
|
|
|
pthread_mutex_unlock(&client_opts.monitoring_thread_data.lock);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
API void
|
|
|
|
nc_client_monitoring_thread_stop(void)
|
|
|
|
{
|
|
|
|
int r, i;
|
|
|
|
pthread_t tid;
|
|
|
|
struct nc_client_monitoring_thread_arg *mtarg;
|
|
|
|
|
|
|
|
/* LOCK */
|
|
|
|
r = nc_client_monitoring_lock(NC_CLIENT_MONITORING_LOCK_TIMEOUT);
|
|
|
|
if (r < 1) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!client_opts.monitoring_thread_data.thread_running) {
|
|
|
|
ERR(NULL, "Client monitoring thread is not running.");
|
|
|
|
|
|
|
|
/* UNLOCK */
|
|
|
|
pthread_mutex_unlock(&client_opts.monitoring_thread_data.lock);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
mtarg = &client_opts.monitoring_thread_data;
|
|
|
|
|
|
|
|
mtarg->thread_running = 0;
|
|
|
|
tid = mtarg->tid;
|
|
|
|
|
|
|
|
/* remove the flag from the session while the lock is held */
|
|
|
|
for (i = 0; i < mtarg->session_count; i++) {
|
|
|
|
mtarg->sessions[i]->flags &= ~NC_SESSION_CLIENT_MONITORED;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* UNLOCK */
|
|
|
|
pthread_mutex_unlock(&mtarg->lock);
|
|
|
|
|
|
|
|
r = pthread_join(tid, NULL);
|
|
|
|
if (r) {
|
|
|
|
ERR(NULL, "Failed to join the client monitoring thread (%s).", strerror(r));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* LOCK */
|
|
|
|
r = nc_client_monitoring_lock(NC_CLIENT_MONITORING_LOCK_TIMEOUT);
|
|
|
|
if (r < 1) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mtarg->clb_free_data) {
|
|
|
|
mtarg->clb_free_data(mtarg->clb_data);
|
|
|
|
}
|
|
|
|
mtarg->clb = NULL;
|
|
|
|
mtarg->clb_data = NULL;
|
|
|
|
mtarg->clb_free_data = NULL;
|
|
|
|
|
|
|
|
free(mtarg->sessions);
|
|
|
|
mtarg->sessions = NULL;
|
|
|
|
|
|
|
|
free(mtarg->pfds);
|
|
|
|
mtarg->pfds = NULL;
|
|
|
|
|
|
|
|
/* UNLOCK */
|
|
|
|
pthread_mutex_unlock(&mtarg->lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
API void
|
|
|
|
nc_client_enable_tcp_keepalives(int enable)
|
|
|
|
{
|
|
|
|
client_opts.ka.enabled = enable;
|
|
|
|
}
|
|
|
|
|
|
|
|
API void
|
|
|
|
nc_client_set_tcp_keepalives(uint16_t idle_time, uint16_t max_probes, uint16_t probe_interval)
|
|
|
|
{
|
|
|
|
if (!idle_time) {
|
|
|
|
/* default */
|
|
|
|
client_opts.ka.idle_time = 1;
|
|
|
|
} else {
|
|
|
|
client_opts.ka.idle_time = idle_time;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!max_probes) {
|
|
|
|
/* default */
|
|
|
|
client_opts.ka.max_probes = 10;
|
|
|
|
} else {
|
|
|
|
client_opts.ka.max_probes = max_probes;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!probe_interval) {
|
|
|
|
/* default */
|
|
|
|
client_opts.ka.probe_interval = 5;
|
|
|
|
} else {
|
|
|
|
client_opts.ka.probe_interval = probe_interval;
|
|
|
|
}
|
|
|
|
}
|