| /** |
| * @file server.c |
| * @author Roman Janota <xjanot04@fit.vutbr.cz> |
| * @brief libnetconf2 server example |
| * |
| * @copyright |
| * Copyright (c) 2022 CESNET, z.s.p.o. |
| * |
| * This source code is licensed under BSD 3-Clause License (the "License"). |
| * You may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * https://opensource.org/licenses/BSD-3-Clause |
| */ |
| |
| #define _GNU_SOURCE |
| #include "example.h" |
| |
| #include <assert.h> |
| #include <getopt.h> |
| #include <signal.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include <libyang/libyang.h> |
| |
| #include "log.h" |
| #include "messages_server.h" |
| #include "netconf.h" |
| #include "server_config.h" |
| #include "session_server.h" |
| #include "session_server_ch.h" |
| |
| volatile int exit_application = 0; |
| struct lyd_node *tree; |
| |
| static void |
| sigint_handler(int signum) |
| { |
| (void) signum; |
| /* notify the main loop if we should exit */ |
| exit_application = 1; |
| } |
| |
| static struct nc_server_reply * |
| get_rpc(struct lyd_node *rpc, struct nc_session *session) |
| { |
| const struct ly_ctx *ctx; |
| const char *xpath; |
| struct lyd_node *root = NULL, *root2 = NULL, *duplicate = NULL; |
| struct lyd_node *filter, *err; |
| struct lyd_meta *m, *type = NULL, *select = NULL; |
| struct ly_set *set = NULL; |
| LY_ERR ret; |
| |
| ctx = nc_session_get_ctx(session); |
| |
| /* load the ietf-yang-library data of the session, which represent this server's state data */ |
| if (ly_ctx_get_yanglib_data(ctx, &root, "%u", ly_ctx_get_change_count(ctx))) { |
| err = nc_err(ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP); |
| goto error; |
| } |
| |
| /* search for the optional filter in the RPC */ |
| ret = lyd_find_path(rpc, "filter", 0, &filter); |
| if (ret && (ret != LY_ENOTFOUND)) { |
| err = nc_err(ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP); |
| goto error; |
| } |
| |
| if (filter) { |
| /* look for the expected filter attributes type and select */ |
| LY_LIST_FOR(filter->meta, m) { |
| if (!strcmp(m->name, "type")) { |
| type = m; |
| } |
| if (!strcmp(m->name, "select")) { |
| select = m; |
| } |
| } |
| |
| /* only XPath filter is supported */ |
| if (!type || strcmp(lyd_get_meta_value(type), "xpath") || !select) { |
| err = nc_err(ctx, NC_ERR_OP_NOT_SUPPORTED, NC_ERR_TYPE_APP); |
| goto error; |
| } |
| xpath = lyd_get_meta_value(select); |
| |
| /* find all the subtrees matching the filter */ |
| if (lyd_find_xpath(root, xpath, &set)) { |
| err = nc_err(ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP); |
| goto error; |
| } |
| |
| root2 = NULL; |
| for (uint32_t i = 0; i < set->count; i++) { |
| /* create a copy of the subtree with its parent nodes */ |
| if (lyd_dup_single(set->dnodes[i], NULL, LYD_DUP_RECURSIVE | LYD_DUP_WITH_PARENTS, &duplicate)) { |
| err = nc_err(ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP); |
| goto error; |
| } |
| |
| /* merge another top-level filtered subtree into the result */ |
| while (duplicate->parent) { |
| duplicate = lyd_parent(duplicate); |
| } |
| if (lyd_merge_tree(&root2, duplicate, LYD_MERGE_DESTRUCT)) { |
| err = nc_err(ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP); |
| goto error; |
| } |
| duplicate = NULL; |
| } |
| |
| /* replace the original full data with only the filtered data */ |
| lyd_free_siblings(root); |
| root = root2; |
| root2 = NULL; |
| } |
| |
| /* duplicate the rpc node without its input nodes so the output nodes can be appended */ |
| if (lyd_dup_single(rpc, NULL, 0, &duplicate)) { |
| err = nc_err(ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP); |
| goto error; |
| } |
| |
| /* create the get RPC anyxml "data" output node with the requested data */ |
| if (lyd_new_any(duplicate, NULL, "data", root, 1, LYD_ANYDATA_DATATREE, 1, NULL)) { |
| err = nc_err(ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP); |
| goto error; |
| } |
| |
| ly_set_free(set, NULL); |
| |
| /* send data reply with the RPC output data */ |
| return nc_server_reply_data(duplicate, NC_WD_UNKNOWN, NC_PARAMTYPE_FREE); |
| |
| error: |
| ly_set_free(set, NULL); |
| lyd_free_siblings(root); |
| lyd_free_siblings(duplicate); |
| lyd_free_siblings(root2); |
| |
| /* send error reply with the specific NETCONF error */ |
| return nc_server_reply_err(err); |
| } |
| |
| static struct nc_server_reply * |
| glob_rpc(struct lyd_node *rpc, struct nc_session *session) |
| { |
| struct lyd_node *iter; |
| struct lyd_meta *m; |
| |
| printf("Received RPC:\n"); |
| |
| /* iterate over all the nodes in the RPC */ |
| LYD_TREE_DFS_BEGIN(rpc, iter) { |
| /* if the node has a value, then print its name and value */ |
| if (iter->schema->nodetype & (LYD_NODE_TERM | LYD_NODE_ANY)) { |
| printf(" %s = \"%s\"\n", LYD_NAME(iter), lyd_get_value(iter)); |
| /* then iterate through all the metadata, which may include the XPath filter */ |
| LY_LIST_FOR(iter->meta, m) { |
| printf(" %s = \"%s\"\n", m->name, lyd_get_meta_value(m)); |
| } |
| /* else print just the name */ |
| } else if (iter->schema->nodetype == LYS_RPC) { |
| printf(" %s\n", LYD_NAME(iter)); |
| } |
| |
| LYD_TREE_DFS_END(rpc, iter); |
| } |
| |
| /* if close-session RPC is received, then call library's default function to properly close the session */ |
| if (!strcmp(LYD_NAME(rpc), "close-session") && !strcmp(lyd_owner_module(rpc)->name, "ietf-netconf")) { |
| return nc_clb_default_close_session(rpc, session); |
| } |
| |
| /* if get-schema RPC is received, then use the library implementation of this RPC */ |
| if (!strcmp(LYD_NAME(rpc), "get-schema") && !strcmp(lyd_owner_module(rpc)->name, "ietf-netconf-monitoring")) { |
| return nc_clb_default_get_schema(rpc, session); |
| } |
| |
| if (!strcmp(LYD_NAME(rpc), "get") && !strcmp(lyd_owner_module(rpc)->name, "ietf-netconf")) { |
| return get_rpc(rpc, session); |
| } |
| |
| /* return an okay reply to every other RPC */ |
| return nc_server_reply_ok(); |
| } |
| |
| static void |
| help_print() |
| { |
| printf("Example usage:\n" |
| " server -u ./unix_socket\n" |
| "\n" |
| " Available options:\n" |
| " -h, --help\t \tPrint usage help.\n" |
| " -u, --unix\t<path>\tCreate a UNIX socket at the place specified by <path>.\n" |
| " -s, --ssh\t<path>\tCreate a SSH server with the host SSH key located at <path>.\n\n"); |
| } |
| |
| static int |
| init(struct ly_ctx **context, struct nc_pollsession **ps, const char *path, NC_TRANSPORT_IMPL server_type) |
| { |
| int rc = 0; |
| const char *hostkey_path = TESTS_DIR "/data/server.key"; |
| struct lyd_node *config = NULL; |
| |
| if (path) { |
| /* if a path is supplied, then use it */ |
| hostkey_path = path; |
| } |
| |
| if (server_type == NC_TI_UNIX) { |
| ERR_MSG_CLEANUP("Only support SSH for now.\n"); |
| } |
| |
| /* create a libyang context that will determine which YANG modules will be supported by the server */ |
| rc = ly_ctx_new(MODULES_DIR, 0, context); |
| if (rc) { |
| ERR_MSG_CLEANUP("Error while creating a new context.\n"); |
| } |
| |
| /* implement the base NETCONF modules */ |
| rc = nc_server_init_ctx(context); |
| if (rc) { |
| ERR_MSG_CLEANUP("Error while initializing context.\n"); |
| } |
| |
| /* load all required modules for configuration, so the configuration of the server can be done */ |
| rc = nc_server_config_load_modules(context); |
| if (rc) { |
| ERR_MSG_CLEANUP("Error loading modules required for configuration of the server.\n"); |
| } |
| |
| /* this is where the YANG configuration data gets generated, |
| * start by creating hostkey configuration data */ |
| rc = nc_server_config_new_ssh_hostkey(hostkey_path, NULL, *context, "endpt", "hostkey", &config); |
| if (rc) { |
| ERR_MSG_CLEANUP("Error creating new hostkey configuration data.\n"); |
| } |
| |
| /* create address and port configuration data */ |
| rc = nc_server_config_new_ssh_address_port(SSH_ADDRESS, SSH_PORT, *context, "endpt", &config); |
| if (rc) { |
| ERR_MSG_CLEANUP("Error creating new address and port configuration data.\n"); |
| } |
| |
| /* create client authentication configuration data */ |
| rc = nc_server_config_new_ssh_client_auth_password(SSH_PASSWORD, *context, "endpt", SSH_USERNAME, &config); |
| if (rc) { |
| ERR_MSG_CLEANUP("Error creating client authentication configuration data.\n"); |
| } |
| |
| /* apply the created configuration data */ |
| rc = nc_server_config_setup_diff(config); |
| if (rc) { |
| ERR_MSG_CLEANUP("Application of configuration data failed.\n"); |
| } |
| |
| /* initialize the server */ |
| if (nc_server_init()) { |
| ERR_MSG_CLEANUP("Error occurred while initializing the server.\n"); |
| } |
| |
| /* create a new poll session structure, which is used for polling RPCs sent by clients */ |
| *ps = nc_ps_new(); |
| if (!*ps) { |
| ERR_MSG_CLEANUP("Couldn't create a poll session\n"); |
| } |
| |
| /* set the global RPC callback, which is called every time a new RPC is received */ |
| nc_set_global_rpc_clb(glob_rpc); |
| |
| /* upon receiving SIGINT the handler will notify the program that is should terminate */ |
| signal(SIGINT, sigint_handler); |
| |
| cleanup: |
| lyd_free_all(config); |
| return rc; |
| } |
| |
| int |
| main(int argc, char **argv) |
| { |
| int r, opt, no_new_sessions, rc = 0; |
| struct ly_ctx *context = NULL; |
| struct nc_session *session, *new_session; |
| struct nc_pollsession *ps = NULL; |
| const char *unix_socket_path = NULL, *hostkey_path = NULL; |
| |
| struct option options[] = { |
| {"help", no_argument, NULL, 'h'}, |
| {"unix", required_argument, NULL, 'u'}, |
| {"ssh", required_argument, NULL, 's'}, |
| {"debug", no_argument, NULL, 'd'}, |
| {NULL, 0, NULL, 0} |
| }; |
| |
| if (argc == 1) { |
| help_print(); |
| goto cleanup; |
| } |
| |
| opterr = 0; |
| |
| while ((opt = getopt_long(argc, argv, ":s:hu:d", options, NULL)) != -1) { |
| switch (opt) { |
| case 'h': |
| help_print(); |
| goto cleanup; |
| |
| case 'u': |
| unix_socket_path = optarg; |
| if (init(&context, &ps, unix_socket_path, NC_TI_UNIX)) { |
| ERR_MSG_CLEANUP("Failed to initialize a UNIX socket\n"); |
| } |
| printf("Using UNIX socket!\n"); |
| break; |
| |
| case 's': |
| hostkey_path = optarg; |
| if (init(&context, &ps, hostkey_path, NC_TI_LIBSSH)) { |
| ERR_MSG_CLEANUP("Failed to initialize a SSH server\n"); |
| goto cleanup; |
| } |
| printf("Using SSH!\n"); |
| break; |
| |
| case 'd': |
| nc_verbosity(NC_VERB_DEBUG); |
| break; |
| |
| case ':': |
| if (optopt == 's') { |
| if (init(&context, &ps, NULL, NC_TI_LIBSSH)) { |
| ERR_MSG_CLEANUP("Failed to initialize a SSH server\n"); |
| goto cleanup; |
| } |
| printf("Using SSH!\n"); |
| break; |
| } else { |
| ERR_MSG_CLEANUP("Invalid option or missing argument\n"); |
| } |
| |
| default: |
| ERR_MSG_CLEANUP("Invalid option or missing argument\n"); |
| } |
| } |
| |
| while (!exit_application) { |
| no_new_sessions = 0; |
| |
| /* try to accept new NETCONF sessions on all configured endpoints */ |
| r = nc_accept(0, context, &session); |
| |
| switch (r) { |
| |
| /* session accepted and its hello message received */ |
| case NC_MSG_HELLO: |
| printf("Connection established\n"); |
| |
| /* add the new session to the poll structure */ |
| if (nc_ps_add_session(ps, session)) { |
| ERR_MSG_CLEANUP("Couldn't add session to poll\n"); |
| } |
| break; |
| |
| /* there were no new sessions */ |
| case NC_MSG_WOULDBLOCK: |
| no_new_sessions = 1; |
| break; |
| |
| /* session accepted, but its hello message was invalid */ |
| case NC_MSG_BAD_HELLO: |
| printf("Parsing client hello message error.\n"); |
| break; |
| |
| /* something else went wrong */ |
| case NC_MSG_ERROR: |
| /* accepting a session failed, but the server should continue handling RPCs on established sessions */ |
| printf("Error while accepting a hello message.\n"); |
| rc = 1; |
| break; |
| } |
| |
| /* poll all the sessions in the structure and process a single event on a session which is then returned, |
| * in case it is a new RPC then the global RPC callback is also called */ |
| r = nc_ps_poll(ps, 0, &new_session); |
| |
| /* a fatal error occurred */ |
| if (r & NC_PSPOLL_ERROR) { |
| ERR_MSG_CLEANUP("Error polling RPCs\n"); |
| } |
| |
| /* a session was terminated, so remove it from the ps structure and free it */ |
| if (r & NC_PSPOLL_SESSION_TERM) { |
| r = nc_ps_del_session(ps, new_session); |
| assert(!r); |
| nc_session_free(new_session, NULL); |
| } |
| |
| /* there were no new sessions and no new events on any established sessions, |
| * prevent active waiting by sleeping for a short period of time */ |
| if (no_new_sessions && (r & (NC_PSPOLL_TIMEOUT | NC_PSPOLL_NOSESSIONS))) { |
| usleep(BACKOFF_TIMEOUT_USECS); |
| } |
| |
| /* other set bits of the return value of nc_ps_poll() are not interesting in this example */ |
| } |
| |
| cleanup: |
| /* free all the remaining sessions in the ps structure before destroying the context */ |
| if (ps) { |
| nc_ps_clear(ps, 1, NULL); |
| } |
| nc_ps_free(ps); |
| nc_server_destroy(); |
| lyd_free_all(tree); |
| ly_ctx_destroy(context); |
| return rc; |
| } |