- FOR_ALL_INTERFACES
- FOR_ALL_INTERFACES_ADDRESSES
- JSON_FOREACH
+ # libyang
+ - LY_TREE_FOR
+ - LY_TREE_DFS_BEGIN
# zebra
- RE_DEST_FOREACH_ROUTE
- RE_DEST_FOREACH_ROUTE_SAFE
nodist_noinst_DATA =
lib_LTLIBRARIES =
module_LTLIBRARIES =
+libyang_plugins_LTLIBRARIES =
pkginclude_HEADERS =
nodist_pkginclude_HEADERS =
dist_examples_DATA =
+dist_yangmodels_DATA =
man_MANS =
vtysh_scan =
$(AUTOMAKE_DUMMY)install-moduleLTLIBRARIES: install-libLTLIBRARIES
$(AUTOMAKE_DUMMY)install-binPROGRAMS: install-libLTLIBRARIES
$(AUTOMAKE_DUMMY)install-sbinPROGRAMS: install-libLTLIBRARIES
+$(AUTOMAKE_DUMMY)install-libyang_pluginsLTLIBRARIES: install-libLTLIBRARIES
include doc/subdir.am
include doc/user/subdir.am
include pbrd/subdir.am
include staticd/subdir.am
include bfdd/subdir.am
+include yang/subdir.am
+include yang/libyang_plugins/subdir.am
include vtysh/subdir.am
include tests/subdir.am
zlog_info("bgpd restarting!");
/* Reload config file. */
- vty_read_config(bgpd_di.config_file, config_default);
+ vty_read_config(NULL, bgpd_di.config_file, config_default);
/* Try to return to normal operation. */
}
])
AC_SUBST([moduledir], [$moduledir])
+yangmodelsdir="\${datarootdir}/yang"
+AC_SUBST([yangmodelsdir], [$yangmodelsdir])
+
+libyang_pluginsdir="\${libdir}/frr/libyang_plugins"
+AC_SUBST(libyang_pluginsdir)
+
AC_ARG_ENABLE(tcmalloc,
AS_HELP_STRING([--enable-tcmalloc], [Turn on tcmalloc]),
[case "${enableval}" in
AS_HELP_STRING([--disable-bgp-vnc],[turn off BGP VNC support]))
AC_ARG_ENABLE(snmp,
AS_HELP_STRING([--enable-snmp], [enable SNMP support for agentx]))
+AC_ARG_ENABLE(config_rollbacks,
+ AS_HELP_STRING([--enable-config-rollbacks], [enable configuration rollbacks (requires sqlite3)]))
AC_ARG_ENABLE(zeromq,
AS_HELP_STRING([--enable-zeromq], [enable ZeroMQ handler (libfrrzmq)]))
AC_ARG_WITH(libpam,
AC_SUBST(SNMP_LIBS)
AC_SUBST(SNMP_CFLAGS)
+dnl ---------------
+dnl libyang
+dnl ---------------
+PKG_CHECK_MODULES(libyang, [libyang >= 0.16.7], , [
+ AC_MSG_ERROR([libyang (>= 0.16.7) was not found on your system.])
+])
+
+dnl ---------------
+dnl configuration rollbacks
+dnl ---------------
+SQLITE3=false
+if test "$enable_config_rollbacks" = "yes"; then
+ PKG_CHECK_MODULES(sqlite3,[sqlite3], [
+ AC_DEFINE(HAVE_CONFIG_ROLLBACKS,1,Enable configuration rollbacks)
+ AC_DEFINE(HAVE_SQLITE3,1,Enable sqlite3 database)
+ SQLITE3=true
+ ], [
+ AC_MSG_ERROR([--enable-config-rollbacks given but sqlite3 was not found on your system.])
+ ])
+fi
+AM_CONDITIONAL(SQLITE3, $SQLITE3)
+
dnl ---------------
dnl math
dnl ---------------
AC_DEFINE_UNQUOTED(ZEBRA_SERV_PATH, "$frr_statedir/zserv.api",zebra api socket)
AC_DEFINE_UNQUOTED(BFDD_CONTROL_SOCKET, "$frr_statedir/bfdd.sock", bfdd control socket)
AC_DEFINE_UNQUOTED(DAEMON_VTY_DIR, "$frr_statedir",daemon vty directory)
+AC_DEFINE_UNQUOTED(DAEMON_DB_DIR, "$frr_statedir",daemon database directory)
dnl autoconf does this, but it does it too late...
test "x$prefix" = xNONE && prefix=$ac_default_prefix
CFG_SBIN="$sbindir"
CFG_STATE="$frr_statedir"
CFG_MODULE="$moduledir"
+CFG_YANGMODELS="$yangmodelsdir"
+CFG_LIBYANG_PLUGINS="$libyang_pluginsdir"
for I in 1 2 3 4 5 6 7 8 9 10; do
eval CFG_SYSCONF="\"$CFG_SYSCONF\""
eval CFG_SBIN="\"$CFG_SBIN\""
eval CFG_STATE="\"$CFG_STATE\""
eval CFG_MODULE="\"$CFG_MODULE\""
+ eval CFG_YANGMODELS="\"$CFG_YANGMODELS\""
+ eval CFG_LIBYANG_PLUGINS="\"$CFG_LIBYANG_PLUGINS\""
done
AC_SUBST(CFG_SYSCONF)
AC_SUBST(CFG_SBIN)
AC_SUBST(CFG_STATE)
AC_SUBST(CFG_MODULE)
+AC_SUBST(CFG_YANGMODELS)
+AC_SUBST(CFG_LIBYANG_PLUGINS)
AC_DEFINE_UNQUOTED(MODULE_PATH, "$CFG_MODULE", path to modules)
+AC_DEFINE_UNQUOTED(YANG_MODELS_PATH, "$CFG_YANGMODELS", path to YANG data models)
+AC_DEFINE_UNQUOTED(LIBYANG_PLUGINS_PATH, "$CFG_LIBYANG_PLUGINS", path to libyang plugins)
dnl ------------------------------------
dnl Enable RPKI and add librtr to libs
When initializing the daemon, allow the specification of a default
log level at startup from one of the specified levels.
+.. option:: --tcli
+
+ Enable the transactional CLI mode.
+
.. _loadable-module-support:
Loadable Module Support
the COMMIT (git hash) and TOKEN (codecov upload token) environment variables
be set.
+.. option:: --enable-config-rollbacks
+
+ Build with configuration rollback support. Requires SQLite3.
+
You may specify any combination of the above options to the configure
script. By default, the executables are placed in :file:`/usr/local/sbin`
and the configuration files in :file:`/usr/local/etc`. The :file:`/usr/local/`
* and build a new configuartion from scratch.
*/
ldp_config_reset(vty_conf);
- vty_read_config(ldpd_di.config_file, config_default);
+ vty_read_config(NULL, ldpd_di.config_file, config_default);
ldp_config_apply(NULL, vty_conf);
}
#include "jhash.h"
#include "hook.h"
#include "lib_errors.h"
+#include "northbound_cli.h"
DEFINE_MTYPE(LIB, HOST, "Host config")
DEFINE_MTYPE(LIB, COMPLETION, "Completion item")
"config", // CONFIG_NODE,
"debug", // DEBUG_NODE,
"vrf debug", // VRF_DEBUG_NODE,
+ "northbound debug", // NORTHBOUND_DEBUG_NODE,
"vnc debug", // DEBUG_VNC_NODE,
"aaa", // AAA_NODE,
"keychain", // KEYCHAIN_NODE,
if (cmd_try_do_shortcut(vty->node, vector_slot(vline, 0))) {
enum node_type onode;
+ int orig_xpath_index;
vector shifted_vline;
unsigned int index;
onode = vty->node;
+ orig_xpath_index = vty->xpath_index;
vty->node = ENABLE_NODE;
+ vty->xpath_index = 0;
/* We can try it on enable node, cos' the vty is authenticated
*/
vector_free(shifted_vline);
vty->node = onode;
+ vty->xpath_index = orig_xpath_index;
return ret;
}
{
int ret, saved_ret = 0;
enum node_type onode, try_node;
+ int orig_xpath_index;
onode = try_node = vty->node;
+ orig_xpath_index = vty->xpath_index;
if (cmd_try_do_shortcut(vty->node, vector_slot(vline, 0))) {
vector shifted_vline;
unsigned int index;
vty->node = ENABLE_NODE;
+ vty->xpath_index = 0;
/* We can try it on enable node, cos' the vty is authenticated
*/
vector_free(shifted_vline);
vty->node = onode;
+ vty->xpath_index = orig_xpath_index;
return ret;
}
while (vty->node > CONFIG_NODE) {
try_node = node_parent(try_node);
vty->node = try_node;
+ if (vty->xpath_index > 0)
+ vty->xpath_index--;
ret = cmd_execute_command_real(vline, FILTER_RELAXED,
vty, cmd);
if (ret == CMD_SUCCESS || ret == CMD_WARNING
}
/* no command succeeded, reset the vty to the original node */
vty->node = onode;
+ vty->xpath_index = orig_xpath_index;
}
/* return command status for original node */
uint32_t line_num, int use_daemon)
{
vector vline;
- int saved_node;
int ret;
vline = cmd_make_strvec(vty->buf);
&& ret != CMD_SUCCESS && ret != CMD_WARNING
&& ret != CMD_NOT_MY_INSTANCE && ret != CMD_WARNING_CONFIG_FAILED
&& vty->node != CONFIG_NODE) {
-
- saved_node = vty->node;
+ int saved_node = vty->node;
+ int saved_xpath_index = vty->xpath_index;
while (!(use_daemon && ret == CMD_SUCCESS_DAEMON)
&& !(!use_daemon && ret == CMD_ERR_NOTHING_TODO)
&& ret != CMD_SUCCESS && ret != CMD_WARNING
&& vty->node > CONFIG_NODE) {
vty->node = node_parent(vty->node);
+ if (vty->xpath_index > 0)
+ vty->xpath_index--;
ret = cmd_execute_command_strict(vline, vty, cmd);
}
&& !(!use_daemon && ret == CMD_ERR_NOTHING_TODO)
&& ret != CMD_SUCCESS && ret != CMD_WARNING) {
vty->node = saved_node;
+ vty->xpath_index = saved_xpath_index;
}
}
vty_out(vty, "VTY configuration is locked by other VTY\n");
return CMD_WARNING_CONFIG_FAILED;
}
+
+ vty->private_config = false;
+ vty->candidate_config = vty_shared_candidate_config;
+ if (frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL)
+ vty->candidate_config_base = nb_config_dup(running_config);
+
return CMD_SUCCESS;
}
default:
break;
}
+
+ if (vty->xpath_index > 0)
+ vty->xpath_index--;
}
/* ALIAS_FIXME */
default:
break;
}
+
+ vty->xpath_index = 0;
+
return CMD_SUCCESS;
}
install_element(node, &show_running_config_cmd);
install_element(node, &autocomplete_cmd);
+
+ nb_cli_install_default(node);
}
/* Initialize command interface. Install basic nodes and commands.
CONFIG_NODE, /* Config node. Default mode of config file. */
DEBUG_NODE, /* Debug node. */
VRF_DEBUG_NODE, /* Vrf Debug node. */
+ NORTHBOUND_DEBUG_NODE, /* Northbound Debug node. */
DEBUG_VNC_NODE, /* Debug VNC node. */
AAA_NODE, /* AAA node. */
KEYCHAIN_NODE, /* Key-chain node. */
--- /dev/null
+/*
+ * Copyright (c) 2018 Rafael Zalamena <rzalamena@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * Copyright (c) 2016 Rafael Zalamena <rzalamena@gmail.com>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <zebra.h>
+
+#include "db.h"
+#include "log.h"
+
+static struct sqlite3 *dbp;
+
+/*
+ * Initialize the database in path.
+ *
+ * It's possible to use in memory database with ':memory:' path.
+ */
+int db_init(const char *path_fmt, ...)
+{
+ char path[BUFSIZ];
+ va_list ap;
+
+ if (dbp)
+ return -1;
+
+ va_start(ap, path_fmt);
+ vsnprintf(path, sizeof(path), path_fmt, ap);
+ va_end(ap);
+
+ if (sqlite3_open_v2(path, &dbp,
+ (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE), NULL)
+ != SQLITE_OK) {
+ if (dbp == NULL) {
+ zlog_warn("%s: failed to open dabatase '%s'", __func__,
+ path);
+ return -1;
+ }
+
+ zlog_warn("%s: failed to open database '%s': %s", __func__,
+ path, sqlite3_errmsg(dbp));
+ if (sqlite3_close_v2(dbp) != SQLITE_OK)
+ zlog_warn("%s: failed to terminate database", __func__);
+ dbp = NULL;
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Closes the database if open. */
+int db_close(void)
+{
+ if (dbp == NULL)
+ return 0;
+
+ if (sqlite3_close_v2(dbp) != SQLITE_OK) {
+ zlog_warn("%s: failed to terminate database", __func__);
+ return -1;
+ }
+ return 0;
+}
+
+/* Helper function to handle formating. */
+static int db_vbindf(struct sqlite3_stmt *ss, const char *fmt, va_list vl)
+{
+ const char *sptr = fmt;
+ int column = 1;
+ const char *str;
+ void *blob;
+ uint64_t uinteger64;
+ uint32_t uinteger;
+ int vlen;
+
+ while (*sptr) {
+ if (*sptr != '%') {
+ sptr++;
+ continue;
+ }
+ if (sptr++ && *sptr == 0)
+ break;
+
+ switch (*sptr) {
+ case 'i':
+ uinteger = va_arg(vl, uint32_t);
+ if (sqlite3_bind_int(ss, column++, uinteger)
+ != SQLITE_OK)
+ return -1;
+ break;
+ case 'd':
+ uinteger64 = va_arg(vl, uint64_t);
+ if (sqlite3_bind_int64(ss, column++, uinteger64)
+ != SQLITE_OK)
+ return -1;
+ break;
+ case 's':
+ str = va_arg(vl, const char *);
+ vlen = va_arg(vl, int);
+ if (sqlite3_bind_text(ss, column++, str, vlen,
+ SQLITE_STATIC)
+ != SQLITE_OK)
+ return -1;
+ break;
+ case 'b':
+ blob = va_arg(vl, void *);
+ vlen = va_arg(vl, int);
+ if (sqlite3_bind_blob(ss, column++, blob, vlen,
+ SQLITE_STATIC)
+ != SQLITE_OK)
+ return -1;
+ break;
+ case 'n':
+ if (sqlite3_bind_null(ss, column++) != SQLITE_OK)
+ return -1;
+ break;
+ default:
+ zlog_warn("%s: invalid format '%c'", __func__, *sptr);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Binds values using format to the database query.
+ *
+ * Might be used to bind variables to a query, insert or update.
+ */
+int db_bindf(struct sqlite3_stmt *ss, const char *fmt, ...)
+{
+ va_list vl;
+ int result;
+
+ va_start(vl, fmt);
+ result = db_vbindf(ss, fmt, vl);
+ va_end(vl);
+
+ return result;
+}
+
+/* Prepares an statement to the database with the statement length. */
+struct sqlite3_stmt *db_prepare_len(const char *stmt, int stmtlen)
+{
+ struct sqlite3_stmt *ss;
+ int c;
+
+ if (dbp == NULL)
+ return NULL;
+
+ c = sqlite3_prepare_v2(dbp, stmt, stmtlen, &ss, NULL);
+ if (ss == NULL) {
+ zlog_warn("%s: failed to prepare (%d:%s)", __func__, c,
+ sqlite3_errmsg(dbp));
+ return NULL;
+ }
+
+ return ss;
+}
+
+/* Prepares an statement to the database. */
+struct sqlite3_stmt *db_prepare(const char *stmt)
+{
+ return db_prepare_len(stmt, strlen(stmt));
+}
+
+/* Run a prepared statement. */
+int db_run(struct sqlite3_stmt *ss)
+{
+ int result;
+
+ result = sqlite3_step(ss);
+ switch (result) {
+ case SQLITE_BUSY:
+ /* TODO handle busy database. */
+ break;
+
+ case SQLITE_OK:
+ /*
+ * SQLITE_DONE just causes confusion since it means the query went OK,
+ * but it has a different value.
+ */
+ case SQLITE_DONE:
+ result = SQLITE_OK;
+ break;
+
+ case SQLITE_ROW:
+ /* NOTHING */
+ /* It is expected to receive SQLITE_ROW on search queries. */
+ break;
+
+ default:
+ zlog_warn("%s: step failed (%d:%s)", __func__, result,
+ sqlite3_errstr(result));
+ }
+
+ return result;
+}
+
+/* Helper function to load format to variables. */
+static int db_vloadf(struct sqlite3_stmt *ss, const char *fmt, va_list vl)
+{
+ const char *sptr = fmt;
+ int column = 0;
+ const char **str;
+ void *blob;
+ const void *blobsrc;
+ uint64_t *uinteger64;
+ uint32_t *uinteger;
+ int vlen;
+ int dlen;
+ int columncount;
+
+ columncount = sqlite3_column_count(ss);
+ if (columncount == 0)
+ return -1;
+
+ while (*sptr) {
+ if (*sptr != '%') {
+ sptr++;
+ continue;
+ }
+ if (sptr++ && *sptr == 0)
+ break;
+
+ switch (*sptr) {
+ case 'i':
+ uinteger = va_arg(vl, uint32_t *);
+ *uinteger = sqlite3_column_int(ss, column);
+ break;
+ case 'd':
+ uinteger64 = va_arg(vl, uint64_t *);
+ *uinteger64 = sqlite3_column_int64(ss, column);
+ break;
+ case 's':
+ str = va_arg(vl, const char **);
+ *str = (const char *)sqlite3_column_text(ss, column);
+ break;
+ case 'b':
+ blob = va_arg(vl, void *);
+ vlen = va_arg(vl, int);
+ dlen = sqlite3_column_bytes(ss, column);
+ blobsrc = sqlite3_column_blob(ss, column);
+ memcpy(blob, blobsrc, MIN(vlen, dlen));
+ break;
+ default:
+ zlog_warn("%s: invalid format '%c'", __func__, *sptr);
+ return -1;
+ }
+
+ column++;
+ }
+
+ return 0;
+}
+
+/* Function to load format from database row. */
+int db_loadf(struct sqlite3_stmt *ss, const char *fmt, ...)
+{
+ va_list vl;
+ int result;
+
+ va_start(vl, fmt);
+ result = db_vloadf(ss, fmt, vl);
+ va_end(vl);
+
+ return result;
+}
+
+/* Finalize query and return memory. */
+void db_finalize(struct sqlite3_stmt **ss)
+{
+ sqlite3_finalize(*ss);
+ *ss = NULL;
+}
+
+/* Execute one or more statements. */
+int db_execute(const char *stmt_fmt, ...)
+{
+ char stmt[BUFSIZ];
+ va_list ap;
+
+ if (dbp == NULL)
+ return -1;
+
+ va_start(ap, stmt_fmt);
+ vsnprintf(stmt, sizeof(stmt), stmt_fmt, ap);
+ va_end(ap);
+
+ if (sqlite3_exec(dbp, stmt, NULL, 0, NULL) != SQLITE_OK) {
+ zlog_warn("%s: failed to execute statement(s): %s", __func__,
+ sqlite3_errmsg(dbp));
+ return -1;
+ }
+
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright (c) 2018 Rafael Zalamena <rzalamena@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * Copyright (c) 2016 Rafael Zalamena <rzalamena@gmail.com>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _FRR_DB_H_
+#define _FRR_DB_H_
+#ifdef HAVE_SQLITE3
+
+#include <sqlite3.h>
+
+extern int db_init(const char *path_fmt, ...);
+extern int db_close(void);
+extern int db_bindf(struct sqlite3_stmt *ss, const char *fmt, ...);
+extern struct sqlite3_stmt *db_prepare_len(const char *stmt, int stmtlen);
+extern struct sqlite3_stmt *db_prepare(const char *stmt);
+extern int db_run(struct sqlite3_stmt *ss);
+extern int db_loadf(struct sqlite3_stmt *ss, const char *fmt, ...);
+extern void db_finalize(struct sqlite3_stmt **ss);
+extern int db_execute(const char *stmt_fmt, ...);
+
+#endif /* HAVE_SQLITE3 */
+#endif /* _FRR_DB_H_ */
.description = "The VRF subsystem, during initialization, has found a parsing error with input it has received",
.suggestion = "Check the length of the vrf name and adjust accordingly",
},
+ {
+ .code = EC_LIB_YANG_DATA_TRUNCATED,
+ .title = "YANG data truncation",
+ .description = "The northbound subsystem has detected that YANG data has been truncated as the given buffer wasn't big enough",
+ .suggestion = "Gather log data and open an Issue",
+ },
+ {
+ .code = EC_LIB_YANG_UNKNOWN_DATA_PATH,
+ .title = "Unknown YANG data path",
+ .description = "The northbound subsystem has detected an unknown YANG data path",
+ .suggestion = "Gather log data and open an Issue",
+ },
+ {
+ .code = EC_LIB_YANG_TRANSLATOR_LOAD,
+ .title = "Unable to load YANG module translator",
+ .description = "The northbound subsystem has detected an error while loading a YANG module translator",
+ .suggestion = "Ensure the YANG module translator file is valid. See documentation for further information.",
+ },
+ {
+ .code = EC_LIB_YANG_TRANSLATION_ERROR,
+ .title = "YANG translation error",
+ .description = "The northbound subsystem has detected an error while performing a YANG XPath translation",
+ .suggestion = "Gather log data and open an Issue",
+ },
+ {
+ .code = EC_LIB_NB_DATABASE,
+ .title = "The northbound database wasn't initialized correctly",
+ .description = "An error occurred while initializing the northbound database. As a result, the configuration rollback feature won't work as expected.",
+ .suggestion = "Ensure permissions are correct for FRR files, users and groups are correct."
+ },
+ {
+ .code = EC_LIB_NB_CB_UNNEEDED,
+ .title = "Unneeded northbound callback",
+ .description = "The northbound subsystem, during initialization, has detected a callback that doesn't need to be implemented",
+ .suggestion = "Check if the installed FRR YANG modules are in sync with the FRR binaries",
+ },
+ {
+ .code = EC_LIB_NB_CB_CONFIG,
+ .title = "A northbound configuration callback has failed",
+ .description = "The northbound subsystem has detected that a callback used to process a configuration change has returned an error",
+ .suggestion = "The log message should contain further details on the specific error that occurred; investigate the reported error.",
+ },
+ {
+ .code = EC_LIB_NB_CB_STATE,
+ .title = "A northbound callback for operational data has failed",
+ .description = "The northbound subsystem has detected that a callback used to fetch operational data has returned an error",
+ .suggestion = "Gather log data and open an Issue",
+ },
+ {
+ .code = EC_LIB_NB_CB_RPC,
+ .title = "A northbound RPC callback has failed",
+ .description = "The northbound subsystem has detected that a callback used to process YANG RPCs/actions has returned an error",
+ .suggestion = "The log message should contain further details on the specific error that occurred; investigate the reported error.",
+ },
+ {
+ .code = EC_LIB_NB_CANDIDATE_INVALID,
+ .title = "Invalid candidate configuration",
+ .description = "The northbound subsystem failed to validate a candidate configuration",
+ .suggestion = "Check the log messages to see the validation errors and edit the candidate configuration to fix them",
+ },
+ {
+ .code = EC_LIB_NB_CANDIDATE_EDIT_ERROR,
+ .title = "Failure to edit a candidate configuration",
+ .description = "The northbound subsystem failed to edit a candidate configuration",
+ .suggestion = "This is a bug; please report it"
+ },
+ {
+ .code = EC_LIB_NB_TRANSACTION_CREATION_FAILED,
+ .title = "Failure to create a configuration transaction",
+ .description = "The northbound subsystem failed to create a configuration transaction",
+ .suggestion = "The log message should contain further details on the specific error that occurred; investigate the reported error.",
+ },
+ {
+ .code = EC_LIB_NB_TRANSACTION_RECORD_FAILED,
+ .title = "Failure to record a configuration transaction",
+ .description = "The northbound subsystem failed to record a configuration transaction in the northbound database",
+ .suggestion = "Gather log data and open an Issue",
+ },
{
.code = END_FERR,
},
.description = "FRR was not compiled with support for a particular feature, or it is not available on the current platform",
.suggestion = "Recompile FRR with the feature enabled, or find out what platforms support the feature"
},
+ {
+ .code = EC_LIB_YANG_MODULE_LOAD,
+ .title = "Unable to load YANG module from the file system",
+ .description = "The northbound subsystem has detected an error while loading a YANG module from the file system",
+ .suggestion = "Ensure all FRR YANG modules were installed correctly in the system.",
+ },
+ {
+ .code = EC_LIB_YANG_MODULE_LOADED_ALREADY,
+ .title = "Attempt to load a YANG module that is already loaded",
+ .description = "The northbound subsystem has detected an attempt to load a YANG module that is already loaded",
+ .suggestion = "This is a bug; please report it"
+ },
+ {
+ .code = EC_LIB_YANG_DATA_CONVERT,
+ .title = "YANG data conversion error",
+ .description = "An error has occurred while converting a YANG data value from string to binary representation or vice-versa",
+ .suggestion = "Open an Issue with all relevant log files and restart FRR"
+ },
+ {
+ .code = EC_LIB_YANG_DNODE_NOT_FOUND,
+ .title = "YANG data node not found",
+ .description = "The northbound subsystem failed to find a YANG data node that was supposed to exist",
+ .suggestion = "This is a bug; please report it"
+ },
+ {
+ .code = EC_LIB_NB_CB_MISSING,
+ .title = "Missing northbound callback",
+ .description = "The northbound subsystem, during initialization, has detected a missing callback for one node of the loaded YANG modules",
+ .suggestion = "Check if the installed FRR YANG modules are in sync with the FRR binaries",
+ },
+ {
+ .code = EC_LIB_NB_CB_INVALID_PRIO,
+ .title = "Norhtbound callback has an invalid priority",
+ .description = "The northbound subsystem, during initialization, has detected a callback whose priority is invalid",
+ .suggestion = "Check if the installed FRR YANG modules are in sync with the FRR binaries",
+ },
+ {
+ .code = EC_LIB_NB_CBS_VALIDATION,
+ .title = "Failure to validate the northbound callbacks",
+ .description = "The northbound subsystem, during initialization, has detected one or more errors while loading the northbound callbacks",
+ .suggestion = "Check if the installed FRR YANG modules are in sync with the FRR binaries",
+ },
+ {
+ .code = EC_LIB_LIBYANG,
+ .title = "The libyang library returned an error",
+ .description = "The northbound subsystem has detected that the libyang library returned an error",
+ .suggestion = "Open an Issue with all relevant log files and restart FRR"
+ },
+ {
+ .code = EC_LIB_LIBYANG_PLUGIN_LOAD,
+ .title = "Failure to load a libyang plugin",
+ .description = "The northbound subsystem, during initialization, has detected that a libyang plugin failed to be loaded",
+ .suggestion = "Check if the FRR libyang plugins were installed correctly in the system",
+ },
+ {
+ .code = EC_LIB_CONFD_INIT,
+ .title = "ConfD initialization error",
+ .description = "Upon startup FRR failed to properly initialize and startup the ConfD northbound plugin",
+ .suggestion = "Check if ConfD is installed correctly in the system. Also, check if the confd daemon is running.",
+ },
+ {
+ .code = EC_LIB_CONFD_DATA_CONVERT,
+ .title = "ConfD data conversion error",
+ .description = "An error has occurred while converting a ConfD data value (binary) to a string",
+ .suggestion = "Open an Issue with all relevant log files and restart FRR"
+ },
+ {
+ .code = EC_LIB_LIBCONFD,
+ .title = "libconfd error",
+ .description = "The northbound subsystem has detected that the libconfd library returned an error",
+ .suggestion = "Open an Issue with all relevant log files and restart FRR"
+ },
+ {
+ .code = EC_LIB_SYSREPO_INIT,
+ .title = "Sysrepo initialization error",
+ .description = "Upon startup FRR failed to properly initialize and startup the Sysrepo northbound plugin",
+ .suggestion = "Check if Sysrepo is installed correctly in the system",
+ },
+ {
+ .code = EC_LIB_SYSREPO_DATA_CONVERT,
+ .title = "Sysrepo data conversion error",
+ .description = "An error has occurred while converting a YANG data value to the Sysrepo format",
+ .suggestion = "Open an Issue with all relevant log files and restart FRR"
+ },
+ {
+ .code = EC_LIB_LIBSYSREPO,
+ .title = "libsysrepo error",
+ .description = "The northbound subsystem has detected that the libsysrepo library returned an error",
+ .suggestion = "Open an Issue with all relevant log files and restart FRR"
+ },
{
.code = END_FERR,
}
EC_LIB_RMAP_RECURSION_LIMIT,
EC_LIB_BACKUP_CONFIG,
EC_LIB_VRF_LENGTH,
+ EC_LIB_YANG_MODULE_LOAD,
+ EC_LIB_YANG_MODULE_LOADED_ALREADY,
+ EC_LIB_YANG_DATA_CONVERT,
+ EC_LIB_YANG_DATA_TRUNCATED,
+ EC_LIB_YANG_UNKNOWN_DATA_PATH,
+ EC_LIB_YANG_DNODE_NOT_FOUND,
+ EC_LIB_YANG_TRANSLATOR_LOAD,
+ EC_LIB_YANG_TRANSLATION_ERROR,
+ EC_LIB_NB_DATABASE,
+ EC_LIB_NB_CB_UNNEEDED,
+ EC_LIB_NB_CB_MISSING,
+ EC_LIB_NB_CB_INVALID_PRIO,
+ EC_LIB_NB_CBS_VALIDATION,
+ EC_LIB_NB_CB_CONFIG,
+ EC_LIB_NB_CB_STATE,
+ EC_LIB_NB_CB_RPC,
+ EC_LIB_NB_CANDIDATE_INVALID,
+ EC_LIB_NB_CANDIDATE_EDIT_ERROR,
+ EC_LIB_NB_TRANSACTION_CREATION_FAILED,
+ EC_LIB_NB_TRANSACTION_RECORD_FAILED,
+ EC_LIB_LIBYANG,
+ EC_LIB_LIBYANG_PLUGIN_LOAD,
+ EC_LIB_CONFD_INIT,
+ EC_LIB_CONFD_DATA_CONVERT,
+ EC_LIB_LIBCONFD,
+ EC_LIB_SYSREPO_INIT,
+ EC_LIB_SYSREPO_DATA_CONVERT,
+ EC_LIB_LIBSYSREPO,
};
extern void lib_error_init(void);
#include "module.h"
#include "network.h"
#include "lib_errors.h"
+#include "db.h"
+#include "northbound_cli.h"
DEFINE_HOOK(frr_late_init, (struct thread_master * tm), (tm))
DEFINE_KOOH(frr_early_fini, (), ())
const char frr_sysconfdir[] = SYSCONFDIR;
const char frr_vtydir[] = DAEMON_VTY_DIR;
+#ifdef HAVE_SQLITE3
+const char frr_dbdir[] = DAEMON_DB_DIR;
+#endif
const char frr_moduledir[] = MODULE_PATH;
char frr_protoname[256] = "NONE";
char config_default[512];
char frr_zclientpath[256];
static char pidfile_default[512];
+#ifdef HAVE_SQLITE3
+static char dbfile_default[512];
+#endif
static char vtypath_default[256];
bool debug_memstats_at_exit = 0;
#define OPTION_MODULEDIR 1002
#define OPTION_LOG 1003
#define OPTION_LOGLEVEL 1004
+#define OPTION_TCLI 1005
+#define OPTION_DB_FILE 1006
static const struct option lo_always[] = {
{"help", no_argument, NULL, 'h'},
{"moduledir", required_argument, NULL, OPTION_MODULEDIR},
{"log", required_argument, NULL, OPTION_LOG},
{"log-level", required_argument, NULL, OPTION_LOGLEVEL},
+ {"tcli", no_argument, NULL, OPTION_TCLI},
{NULL}};
static const struct optspec os_always = {
"hvdM:",
" --vty_socket Override vty socket path\n"
" --moduledir Override modules directory\n"
" --log Set Logging to stdout, syslog, or file:<name>\n"
- " --log-level Set Logging Level to use, debug, info, warn, etc\n",
+ " --log-level Set Logging Level to use, debug, info, warn, etc\n"
+ " --tcli Use transaction-based CLI\n",
lo_always};
static const struct option lo_cfg_pid_dry[] = {
{"pid_file", required_argument, NULL, 'i'},
{"config_file", required_argument, NULL, 'f'},
+#ifdef HAVE_SQLITE3
+ {"db_file", required_argument, NULL, OPTION_DB_FILE},
+#endif
{"pathspace", required_argument, NULL, 'N'},
{"dryrun", no_argument, NULL, 'C'},
{"terminal", no_argument, NULL, 't'},
"f:i:CtN:",
" -f, --config_file Set configuration file name\n"
" -i, --pid_file Set process identifier file name\n"
+#ifdef HAVE_SQLITE3
+ " --db_file Set database file name\n"
+#endif
" -N, --pathspace Insert prefix into config & socket paths\n"
" -C, --dryrun Check configuration for validity and exit\n"
" -t, --terminal Open terminal session on stdio\n"
frr_sysconfdir, di->name);
snprintf(pidfile_default, sizeof(pidfile_default), "%s/%s.pid",
frr_vtydir, di->name);
+#ifdef HAVE_SQLITE3
+ snprintf(dbfile_default, sizeof(dbfile_default), "%s/%s.db",
+ frr_dbdir, di->name);
+#endif
strlcpy(frr_protoname, di->logname, sizeof(frr_protoname));
strlcpy(frr_protonameinst, di->logname, sizeof(frr_protonameinst));
strlcpy(frr_zclientpath, ZEBRA_SERV_PATH, sizeof(frr_zclientpath));
+
+ di->cli_mode = FRR_CLI_CLASSIC;
}
void frr_opt_add(const char *optstr, const struct option *longopts,
}
di->pathspace = optarg;
break;
+#ifdef HAVE_SQLITE3
+ case OPTION_DB_FILE:
+ if (di->flags & FRR_NO_CFG_PID_DRY)
+ return 1;
+ di->db_file = optarg;
+ break;
+#endif
case 'C':
if (di->flags & FRR_NO_CFG_PID_DRY)
return 1;
}
di->module_path = optarg;
break;
+ case OPTION_TCLI:
+ di->cli_mode = FRR_CLI_TRANSACTIONAL;
+ break;
case 'u':
if (di->flags & FRR_NO_PRIVSEP)
return 1;
frr_sysconfdir, p_pathspace, di->name, p_instance);
snprintf(pidfile_default, sizeof(pidfile_default), "%s/%s%s%s.pid",
frr_vtydir, p_pathspace, di->name, p_instance);
+#ifdef HAVE_SQLITE3
+ snprintf(dbfile_default, sizeof(dbfile_default), "%s/%s%s%s.db",
+ frr_dbdir, p_pathspace, di->name, p_instance);
+#endif
zprivs_preinit(di->privs);
master = thread_master_create(NULL);
signal_init(master, di->n_signals, di->signals);
+#ifdef HAVE_SQLITE3
+ if (!di->db_file)
+ di->db_file = dbfile_default;
+ db_init(di->db_file);
+#endif
+
if (di->flags & FRR_LIMITED_CLI)
cmd_init(-1);
else
cmd_init(1);
+
vty_init(master);
memory_init();
log_ref_init();
lib_error_init();
+ yang_init();
+ nb_init(di->yang_modules, di->n_yang_modules);
+
return master;
}
+const char *frr_get_progname(void)
+{
+ return di ? di->progname : NULL;
+}
+
+enum frr_cli_mode frr_get_cli_mode(void)
+{
+ return di ? di->cli_mode : FRR_CLI_CLASSIC;
+}
+
static int rcvd_signal = 0;
static void rcv_signal(int signum)
*/
static int frr_config_read_in(struct thread *t)
{
- if (!vty_read_config(di->config_file, config_default) &&
+ if (!vty_read_config(NULL, di->config_file, config_default) &&
di->backup_config_file) {
char *orig = XSTRDUP(MTYPE_TMP, host_config_get());
zlog_info("Attempting to read backup config file: %s specified",
di->backup_config_file);
- vty_read_config(di->backup_config_file, config_default);
+ vty_read_config(NULL, di->backup_config_file, config_default);
host_config_set(orig);
XFREE(MTYPE_TMP, orig);
}
+
+ /*
+ * Update the shared candidate after reading the startup configuration.
+ */
+ nb_config_replace(vty_shared_candidate_config, running_config, true);
+
return 0;
}
/* memory_init -> nothing needed */
vty_terminate();
cmd_terminate();
+ nb_terminate();
+ yang_terminate();
+#ifdef HAVE_SQLITE3
+ db_close();
+#endif
log_ref_fini();
zprivs_terminate(di->privs);
/* signal_init -> nothing needed */
#include "getopt.h"
#include "module.h"
#include "hook.h"
+#include "northbound.h"
/* The following options disable specific command line options that
* are not applicable for a particular daemon.
*/
#define FRR_DETACH_LATER (1 << 5)
+enum frr_cli_mode {
+ FRR_CLI_CLASSIC = 0,
+ FRR_CLI_TRANSACTIONAL,
+};
+
struct frr_daemon_info {
unsigned flags;
bool dryrun;
bool daemon_mode;
bool terminal;
+ enum frr_cli_mode cli_mode;
struct thread *read_in;
const char *config_file;
const char *backup_config_file;
const char *pid_file;
+#ifdef HAVE_SQLITE3
+ const char *db_file;
+#endif
const char *vty_path;
const char *module_path;
const char *pathspace;
size_t n_signals;
struct zebra_privs_t *privs;
+
+ const struct frr_yang_module_info **yang_modules;
+ size_t n_yang_modules;
};
/* execname is the daemon's executable (and pidfile and configfile) name,
extern void frr_help_exit(int status);
extern struct thread_master *frr_init(void);
+extern const char *frr_get_progname(void);
+extern enum frr_cli_mode frr_get_cli_mode(void);
DECLARE_HOOK(frr_late_init, (struct thread_master * tm), (tm))
extern void frr_config_fork(void);
--- /dev/null
+/*
+ * Copyright (C) 2018 NetDEF, Inc.
+ * Renato Westphal
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <zebra.h>
+
+#include "libfrr.h"
+#include "log.h"
+#include "lib_errors.h"
+#include "command.h"
+#include "db.h"
+#include "northbound.h"
+#include "northbound_cli.h"
+#include "northbound_db.h"
+
+DEFINE_MTYPE_STATIC(LIB, NB_NODE, "Northbound Node")
+DEFINE_MTYPE_STATIC(LIB, NB_CONFIG, "Northbound Configuration")
+
+/* Running configuration - shouldn't be modified directly. */
+struct nb_config *running_config;
+
+/*
+ * Global lock used to prevent multiple configuration transactions from
+ * happening concurrently.
+ */
+static bool transaction_in_progress;
+
+static int nb_configuration_callback(const enum nb_event event,
+ struct nb_config_change *change);
+static struct nb_transaction *nb_transaction_new(struct nb_config *config,
+ struct nb_config_cbs *changes,
+ enum nb_client client,
+ const char *comment);
+static void nb_transaction_free(struct nb_transaction *transaction);
+static int nb_transaction_process(enum nb_event event,
+ struct nb_transaction *transaction);
+static void nb_transaction_apply_finish(struct nb_transaction *transaction);
+
+static void nb_node_new_cb(const struct lys_node *snode, void *arg1, void *arg2)
+{
+ struct nb_node *nb_node;
+ struct lys_node *sparent, *sparent_list;
+
+ nb_node = XCALLOC(MTYPE_NB_NODE, sizeof(*nb_node));
+ yang_snode_get_path(snode, YANG_PATH_DATA, nb_node->xpath,
+ sizeof(nb_node->xpath));
+ nb_node->priority = NB_DFLT_PRIORITY;
+ sparent = yang_snode_real_parent(snode);
+ if (sparent)
+ nb_node->parent = sparent->priv;
+ sparent_list = yang_snode_parent_list(snode);
+ if (sparent_list)
+ nb_node->parent_list = sparent_list->priv;
+
+ /*
+ * Link the northbound node and the libyang schema node with one
+ * another.
+ */
+ nb_node->snode = snode;
+ lys_set_private(snode, nb_node);
+}
+
+static void nb_node_del_cb(const struct lys_node *snode, void *arg1, void *arg2)
+{
+ struct nb_node *nb_node;
+
+ nb_node = snode->priv;
+ lys_set_private(snode, NULL);
+ XFREE(MTYPE_NB_NODE, nb_node);
+}
+
+struct nb_node *nb_node_find(const char *xpath)
+{
+ const struct lys_node *snode;
+
+ /*
+ * Use libyang to find the schema node associated to the xpath and get
+ * the northbound node from there (snode private pointer).
+ */
+ snode = ly_ctx_get_node(ly_native_ctx, NULL, xpath, 0);
+ if (!snode)
+ return NULL;
+
+ return snode->priv;
+}
+
+static int nb_node_validate_cb(const struct nb_node *nb_node,
+ enum nb_operation operation,
+ int callback_implemented, bool optional)
+{
+ bool valid;
+
+ valid = nb_operation_is_valid(operation, nb_node->snode);
+
+ if (!valid && callback_implemented)
+ flog_warn(EC_LIB_NB_CB_UNNEEDED,
+ "unneeded '%s' callback for '%s'",
+ nb_operation_name(operation), nb_node->xpath);
+
+ if (!optional && valid && !callback_implemented) {
+ flog_err(EC_LIB_NB_CB_MISSING, "missing '%s' callback for '%s'",
+ nb_operation_name(operation), nb_node->xpath);
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * Check if the required callbacks were implemented for the given northbound
+ * node.
+ */
+static unsigned int nb_node_validate_cbs(const struct nb_node *nb_node)
+
+{
+ unsigned int error = 0;
+
+ error += nb_node_validate_cb(nb_node, NB_OP_CREATE,
+ !!nb_node->cbs.create, false);
+ error += nb_node_validate_cb(nb_node, NB_OP_MODIFY,
+ !!nb_node->cbs.modify, false);
+ error += nb_node_validate_cb(nb_node, NB_OP_DELETE,
+ !!nb_node->cbs.delete, false);
+ error += nb_node_validate_cb(nb_node, NB_OP_MOVE, !!nb_node->cbs.move,
+ false);
+ error += nb_node_validate_cb(nb_node, NB_OP_APPLY_FINISH,
+ !!nb_node->cbs.apply_finish, true);
+ error += nb_node_validate_cb(nb_node, NB_OP_GET_ELEM,
+ !!nb_node->cbs.get_elem, false);
+ error += nb_node_validate_cb(nb_node, NB_OP_GET_NEXT,
+ !!nb_node->cbs.get_next, false);
+ error += nb_node_validate_cb(nb_node, NB_OP_GET_KEYS,
+ !!nb_node->cbs.get_keys, false);
+ error += nb_node_validate_cb(nb_node, NB_OP_LOOKUP_ENTRY,
+ !!nb_node->cbs.lookup_entry, false);
+ error += nb_node_validate_cb(nb_node, NB_OP_RPC, !!nb_node->cbs.rpc,
+ false);
+
+ return error;
+}
+
+static unsigned int nb_node_validate_priority(const struct nb_node *nb_node)
+{
+ /* Top-level nodes can have any priority. */
+ if (!nb_node->parent)
+ return 0;
+
+ if (nb_node->priority < nb_node->parent->priority) {
+ flog_err(EC_LIB_NB_CB_INVALID_PRIO,
+ "node has higher priority than its parent [xpath %s]",
+ nb_node->xpath);
+ return 1;
+ }
+
+ return 0;
+}
+
+static void nb_node_validate(const struct lys_node *snode, void *arg1,
+ void *arg2)
+{
+ struct nb_node *nb_node = snode->priv;
+ unsigned int *errors = arg1;
+
+ /* Validate callbacks and priority. */
+ *errors += nb_node_validate_cbs(nb_node);
+ *errors += nb_node_validate_priority(nb_node);
+}
+
+struct nb_config *nb_config_new(struct lyd_node *dnode)
+{
+ struct nb_config *config;
+
+ config = XCALLOC(MTYPE_NB_CONFIG, sizeof(*config));
+ if (dnode)
+ config->dnode = dnode;
+ else
+ config->dnode = yang_dnode_new(ly_native_ctx);
+ config->version = 0;
+
+ return config;
+}
+
+void nb_config_free(struct nb_config *config)
+{
+ if (config->dnode)
+ yang_dnode_free(config->dnode);
+ XFREE(MTYPE_NB_CONFIG, config);
+}
+
+struct nb_config *nb_config_dup(const struct nb_config *config)
+{
+ struct nb_config *dup;
+
+ dup = XCALLOC(MTYPE_NB_CONFIG, sizeof(*dup));
+ dup->dnode = yang_dnode_dup(config->dnode);
+ dup->version = config->version;
+
+ return dup;
+}
+
+int nb_config_merge(struct nb_config *config_dst, struct nb_config *config_src,
+ bool preserve_source)
+{
+ int ret;
+
+ ret = lyd_merge(config_dst->dnode, config_src->dnode, LYD_OPT_EXPLICIT);
+ if (ret != 0)
+ flog_warn(EC_LIB_LIBYANG, "%s: lyd_merge() failed", __func__);
+
+ if (!preserve_source)
+ nb_config_free(config_src);
+
+ return (ret == 0) ? NB_OK : NB_ERR;
+}
+
+void nb_config_replace(struct nb_config *config_dst,
+ struct nb_config *config_src, bool preserve_source)
+{
+ /* Update version. */
+ if (config_src->version != 0)
+ config_dst->version = config_src->version;
+
+ /* Update dnode. */
+ yang_dnode_free(config_dst->dnode);
+ if (preserve_source) {
+ config_dst->dnode = yang_dnode_dup(config_src->dnode);
+ } else {
+ config_dst->dnode = config_src->dnode;
+ config_src->dnode = NULL;
+ nb_config_free(config_src);
+ }
+}
+
+/* Generate the nb_config_cbs tree. */
+static inline int nb_config_cb_compare(const struct nb_config_cb *a,
+ const struct nb_config_cb *b)
+{
+ /* Sort by priority first. */
+ if (a->nb_node->priority < b->nb_node->priority)
+ return -1;
+ if (a->nb_node->priority > b->nb_node->priority)
+ return 1;
+
+ /*
+ * Use XPath as a tie-breaker. This will naturally sort parent nodes
+ * before their children.
+ */
+ return strcmp(a->xpath, b->xpath);
+}
+RB_GENERATE(nb_config_cbs, nb_config_cb, entry, nb_config_cb_compare);
+
+static void nb_config_diff_add_change(struct nb_config_cbs *changes,
+ enum nb_operation operation,
+ const struct lyd_node *dnode)
+{
+ struct nb_config_change *change;
+
+ change = XCALLOC(MTYPE_TMP, sizeof(*change));
+ change->cb.operation = operation;
+ change->cb.nb_node = dnode->schema->priv;
+ yang_dnode_get_path(dnode, change->cb.xpath, sizeof(change->cb.xpath));
+ change->cb.dnode = dnode;
+
+ RB_INSERT(nb_config_cbs, changes, &change->cb);
+}
+
+static void nb_config_diff_del_changes(struct nb_config_cbs *changes)
+{
+ while (!RB_EMPTY(nb_config_cbs, changes)) {
+ struct nb_config_change *change;
+
+ change = (struct nb_config_change *)RB_ROOT(nb_config_cbs,
+ changes);
+ RB_REMOVE(nb_config_cbs, changes, &change->cb);
+ XFREE(MTYPE_TMP, change);
+ }
+}
+
+/*
+ * Helper function used when calculating the delta between two different
+ * configurations. Given a new subtree, calculate all new YANG data nodes,
+ * excluding default leafs and leaf-lists. This is a recursive function.
+ */
+static void nb_config_diff_new_subtree(const struct lyd_node *dnode,
+ struct nb_config_cbs *changes)
+{
+ struct lyd_node *child;
+
+ LY_TREE_FOR (dnode->child, child) {
+ enum nb_operation operation;
+
+ switch (child->schema->nodetype) {
+ case LYS_LEAF:
+ case LYS_LEAFLIST:
+ if (lyd_wd_default((struct lyd_node_leaf_list *)child))
+ break;
+
+ if (nb_operation_is_valid(NB_OP_CREATE, child->schema))
+ operation = NB_OP_CREATE;
+ else if (nb_operation_is_valid(NB_OP_MODIFY,
+ child->schema))
+ operation = NB_OP_MODIFY;
+ else
+ continue;
+
+ nb_config_diff_add_change(changes, operation, child);
+ break;
+ case LYS_CONTAINER:
+ case LYS_LIST:
+ if (nb_operation_is_valid(NB_OP_CREATE, child->schema))
+ nb_config_diff_add_change(changes, NB_OP_CREATE,
+ child);
+ nb_config_diff_new_subtree(child, changes);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+/* Calculate the delta between two different configurations. */
+static void nb_config_diff(const struct nb_config *config1,
+ const struct nb_config *config2,
+ struct nb_config_cbs *changes)
+{
+ struct lyd_difflist *diff;
+
+ diff = lyd_diff(config1->dnode, config2->dnode,
+ LYD_DIFFOPT_WITHDEFAULTS);
+ assert(diff);
+
+ for (int i = 0; diff->type[i] != LYD_DIFF_END; i++) {
+ LYD_DIFFTYPE type;
+ struct lyd_node *dnode;
+ enum nb_operation operation;
+
+ type = diff->type[i];
+
+ switch (type) {
+ case LYD_DIFF_CREATED:
+ dnode = diff->second[i];
+
+ if (nb_operation_is_valid(NB_OP_CREATE, dnode->schema))
+ operation = NB_OP_CREATE;
+ else if (nb_operation_is_valid(NB_OP_MODIFY,
+ dnode->schema))
+ operation = NB_OP_MODIFY;
+ else
+ continue;
+ break;
+ case LYD_DIFF_DELETED:
+ dnode = diff->first[i];
+ operation = NB_OP_DELETE;
+ break;
+ case LYD_DIFF_CHANGED:
+ dnode = diff->second[i];
+ operation = NB_OP_MODIFY;
+ break;
+ case LYD_DIFF_MOVEDAFTER1:
+ case LYD_DIFF_MOVEDAFTER2:
+ default:
+ continue;
+ }
+
+ nb_config_diff_add_change(changes, operation, dnode);
+
+ if (type == LYD_DIFF_CREATED
+ && (dnode->schema->nodetype & (LYS_CONTAINER | LYS_LIST)))
+ nb_config_diff_new_subtree(dnode, changes);
+ }
+
+ lyd_free_diff(diff);
+}
+
+int nb_candidate_edit(struct nb_config *candidate,
+ const struct nb_node *nb_node,
+ enum nb_operation operation, const char *xpath,
+ const struct yang_data *previous,
+ const struct yang_data *data)
+{
+ struct lyd_node *dnode;
+ char xpath_edit[XPATH_MAXLEN];
+
+ if (!nb_operation_is_valid(operation, nb_node->snode)) {
+ flog_warn(EC_LIB_NB_CANDIDATE_EDIT_ERROR,
+ "%s: %s operation not valid for %s", __func__,
+ nb_operation_name(operation), xpath);
+ return NB_ERR;
+ }
+
+ /* Use special notation for leaf-lists (RFC 6020, section 9.13.5). */
+ if (nb_node->snode->nodetype == LYS_LEAFLIST)
+ snprintf(xpath_edit, sizeof(xpath_edit), "%s[.='%s']", xpath,
+ data->value);
+ else
+ strlcpy(xpath_edit, xpath, sizeof(xpath_edit));
+
+ switch (operation) {
+ case NB_OP_CREATE:
+ case NB_OP_MODIFY:
+ ly_errno = 0;
+ dnode = lyd_new_path(candidate->dnode, ly_native_ctx,
+ xpath_edit, (void *)data->value, 0,
+ LYD_PATH_OPT_UPDATE);
+ if (!dnode && ly_errno) {
+ flog_warn(EC_LIB_LIBYANG, "%s: lyd_new_path() failed",
+ __func__);
+ return NB_ERR;
+ }
+
+ /*
+ * If a new node was created, call lyd_validate() only to create
+ * default child nodes.
+ */
+ if (dnode) {
+ lyd_schema_sort(dnode, 0);
+ lyd_validate(&dnode, LYD_OPT_CONFIG, ly_native_ctx);
+ }
+ break;
+ case NB_OP_DELETE:
+ dnode = yang_dnode_get(candidate->dnode, xpath_edit);
+ if (!dnode)
+ /*
+ * Return a special error code so the caller can choose
+ * whether to ignore it or not.
+ */
+ return NB_ERR_NOT_FOUND;
+ lyd_free(dnode);
+ break;
+ case NB_OP_MOVE:
+ /* TODO: update configuration. */
+ break;
+ default:
+ flog_warn(EC_LIB_DEVELOPMENT,
+ "%s: unknown operation (%u) [xpath %s]", __func__,
+ operation, xpath_edit);
+ return NB_ERR;
+ }
+
+ return NB_OK;
+}
+
+bool nb_candidate_needs_update(const struct nb_config *candidate)
+{
+ if (candidate->version < running_config->version)
+ return true;
+
+ return false;
+}
+
+int nb_candidate_update(struct nb_config *candidate)
+{
+ struct nb_config *updated_config;
+
+ updated_config = nb_config_dup(running_config);
+ if (nb_config_merge(updated_config, candidate, true) != NB_OK)
+ return NB_ERR;
+
+ nb_config_replace(candidate, updated_config, false);
+
+ return NB_OK;
+}
+
+/*
+ * The northbound configuration callbacks use the 'priv' pointer present in the
+ * libyang lyd_node structure to store pointers to FRR internal variables
+ * associated to YANG lists and presence containers. Before commiting a
+ * candidate configuration, we must restore the 'priv' pointers stored in the
+ * running configuration since they might be lost while editing the candidate.
+ */
+static void nb_candidate_restore_priv_pointers(struct nb_config *candidate)
+{
+ struct lyd_node *root, *next, *dnode_iter;
+
+ LY_TREE_FOR (running_config->dnode, root) {
+ LY_TREE_DFS_BEGIN (root, next, dnode_iter) {
+ struct lyd_node *dnode_candidate;
+ char xpath[XPATH_MAXLEN];
+
+ if (!dnode_iter->priv)
+ goto next;
+
+ yang_dnode_get_path(dnode_iter, xpath, sizeof(xpath));
+ dnode_candidate =
+ yang_dnode_get(candidate->dnode, xpath);
+ if (dnode_candidate)
+ yang_dnode_set_entry(dnode_candidate,
+ dnode_iter->priv);
+
+ next:
+ LY_TREE_DFS_END(root, next, dnode_iter);
+ }
+ }
+}
+
+/*
+ * Perform YANG syntactic and semantic validation.
+ *
+ * WARNING: lyd_validate() can change the configuration as part of the
+ * validation process.
+ */
+static int nb_candidate_validate_yang(struct nb_config *candidate)
+{
+ if (lyd_validate(&candidate->dnode, LYD_OPT_STRICT | LYD_OPT_CONFIG,
+ ly_native_ctx)
+ != 0)
+ return NB_ERR_VALIDATION;
+
+ return NB_OK;
+}
+
+/* Perform code-level validation using the northbound callbacks. */
+static int nb_candidate_validate_changes(struct nb_config *candidate,
+ struct nb_config_cbs *changes)
+{
+ struct nb_config_cb *cb;
+
+ nb_candidate_restore_priv_pointers(candidate);
+ RB_FOREACH (cb, nb_config_cbs, changes) {
+ struct nb_config_change *change = (struct nb_config_change *)cb;
+ int ret;
+
+ ret = nb_configuration_callback(NB_EV_VALIDATE, change);
+ if (ret != NB_OK)
+ return NB_ERR_VALIDATION;
+ }
+
+ return NB_OK;
+}
+
+int nb_candidate_validate(struct nb_config *candidate)
+{
+ struct nb_config_cbs changes;
+ int ret;
+
+ if (nb_candidate_validate_yang(candidate) != NB_OK)
+ return NB_ERR_VALIDATION;
+
+ RB_INIT(nb_config_cbs, &changes);
+ nb_config_diff(running_config, candidate, &changes);
+ ret = nb_candidate_validate_changes(candidate, &changes);
+ nb_config_diff_del_changes(&changes);
+
+ return ret;
+}
+
+int nb_candidate_commit_prepare(struct nb_config *candidate,
+ enum nb_client client, const char *comment,
+ struct nb_transaction **transaction)
+{
+ struct nb_config_cbs changes;
+
+ if (nb_candidate_validate_yang(candidate) != NB_OK) {
+ flog_warn(EC_LIB_NB_CANDIDATE_INVALID,
+ "%s: failed to validate candidate configuration",
+ __func__);
+ return NB_ERR_VALIDATION;
+ }
+
+ RB_INIT(nb_config_cbs, &changes);
+ nb_config_diff(running_config, candidate, &changes);
+ if (RB_EMPTY(nb_config_cbs, &changes))
+ return NB_ERR_NO_CHANGES;
+
+ if (nb_candidate_validate_changes(candidate, &changes) != NB_OK) {
+ flog_warn(EC_LIB_NB_CANDIDATE_INVALID,
+ "%s: failed to validate candidate configuration",
+ __func__);
+ nb_config_diff_del_changes(&changes);
+ return NB_ERR_VALIDATION;
+ }
+
+ *transaction = nb_transaction_new(candidate, &changes, client, comment);
+ if (*transaction == NULL) {
+ flog_warn(EC_LIB_NB_TRANSACTION_CREATION_FAILED,
+ "%s: failed to create transaction", __func__);
+ nb_config_diff_del_changes(&changes);
+ return NB_ERR_LOCKED;
+ }
+
+ return nb_transaction_process(NB_EV_PREPARE, *transaction);
+}
+
+void nb_candidate_commit_abort(struct nb_transaction *transaction)
+{
+ (void)nb_transaction_process(NB_EV_ABORT, transaction);
+ nb_transaction_free(transaction);
+}
+
+void nb_candidate_commit_apply(struct nb_transaction *transaction,
+ bool save_transaction, uint32_t *transaction_id)
+{
+ (void)nb_transaction_process(NB_EV_APPLY, transaction);
+ nb_transaction_apply_finish(transaction);
+
+ /* Replace running by candidate. */
+ transaction->config->version++;
+ nb_config_replace(running_config, transaction->config, true);
+
+ /* Record transaction. */
+ if (save_transaction
+ && nb_db_transaction_save(transaction, transaction_id) != NB_OK)
+ flog_warn(EC_LIB_NB_TRANSACTION_RECORD_FAILED,
+ "%s: failed to record transaction", __func__);
+
+ nb_transaction_free(transaction);
+}
+
+int nb_candidate_commit(struct nb_config *candidate, enum nb_client client,
+ bool save_transaction, const char *comment,
+ uint32_t *transaction_id)
+{
+ struct nb_transaction *transaction = NULL;
+ int ret;
+
+ ret = nb_candidate_commit_prepare(candidate, client, comment,
+ &transaction);
+ /*
+ * Apply the changes if the preparation phase succeeded. Otherwise abort
+ * the transaction.
+ */
+ if (ret == NB_OK)
+ nb_candidate_commit_apply(transaction, save_transaction,
+ transaction_id);
+ else if (transaction != NULL)
+ nb_candidate_commit_abort(transaction);
+
+ return ret;
+}
+
+static void nb_log_callback(const enum nb_event event,
+ enum nb_operation operation, const char *xpath,
+ const char *value)
+{
+ zlog_debug(
+ "northbound callback: event [%s] op [%s] xpath [%s] value [%s]",
+ nb_event_name(event), nb_operation_name(operation), xpath,
+ value);
+}
+
+/*
+ * Call the northbound configuration callback associated to a given
+ * configuration change.
+ */
+static int nb_configuration_callback(const enum nb_event event,
+ struct nb_config_change *change)
+{
+ enum nb_operation operation = change->cb.operation;
+ const char *xpath = change->cb.xpath;
+ const struct nb_node *nb_node = change->cb.nb_node;
+ const struct lyd_node *dnode = change->cb.dnode;
+ union nb_resource *resource;
+ int ret = NB_ERR;
+
+ if (debug_northbound) {
+ const char *value = "(none)";
+
+ if (dnode && !yang_snode_is_typeless_data(dnode->schema))
+ value = yang_dnode_get_string(dnode, NULL);
+
+ nb_log_callback(event, operation, xpath, value);
+ }
+
+ if (event == NB_EV_VALIDATE)
+ resource = NULL;
+ else
+ resource = &change->resource;
+
+ switch (operation) {
+ case NB_OP_CREATE:
+ ret = (*nb_node->cbs.create)(event, dnode, resource);
+ break;
+ case NB_OP_MODIFY:
+ ret = (*nb_node->cbs.modify)(event, dnode, resource);
+ break;
+ case NB_OP_DELETE:
+ ret = (*nb_node->cbs.delete)(event, dnode);
+ break;
+ case NB_OP_MOVE:
+ ret = (*nb_node->cbs.move)(event, dnode);
+ break;
+ default:
+ break;
+ }
+
+ if (ret != NB_OK)
+ flog_warn(
+ EC_LIB_NB_CB_CONFIG,
+ "%s: error processing configuration change: error [%s] event [%s] operation [%s] xpath [%s]",
+ __func__, nb_err_name(ret), nb_event_name(event),
+ nb_operation_name(operation), xpath);
+
+ return ret;
+}
+
+static struct nb_transaction *nb_transaction_new(struct nb_config *config,
+ struct nb_config_cbs *changes,
+ enum nb_client client,
+ const char *comment)
+{
+ struct nb_transaction *transaction;
+
+ if (transaction_in_progress) {
+ flog_warn(
+ EC_LIB_NB_TRANSACTION_CREATION_FAILED,
+ "%s: error - there's already another transaction in progress",
+ __func__);
+ return NULL;
+ }
+ transaction_in_progress = true;
+
+ transaction = XCALLOC(MTYPE_TMP, sizeof(*transaction));
+ transaction->client = client;
+ if (comment)
+ strlcpy(transaction->comment, comment,
+ sizeof(transaction->comment));
+ transaction->config = config;
+ transaction->changes = *changes;
+
+ return transaction;
+}
+
+static void nb_transaction_free(struct nb_transaction *transaction)
+{
+ nb_config_diff_del_changes(&transaction->changes);
+ XFREE(MTYPE_TMP, transaction);
+ transaction_in_progress = false;
+}
+
+/* Process all configuration changes associated to a transaction. */
+static int nb_transaction_process(enum nb_event event,
+ struct nb_transaction *transaction)
+{
+ struct nb_config_cb *cb;
+
+ RB_FOREACH (cb, nb_config_cbs, &transaction->changes) {
+ struct nb_config_change *change = (struct nb_config_change *)cb;
+ int ret;
+
+ /*
+ * Only try to release resources that were allocated
+ * successfully.
+ */
+ if (event == NB_EV_ABORT && change->prepare_ok == false)
+ break;
+
+ /* Call the appropriate callback. */
+ ret = nb_configuration_callback(event, change);
+ switch (event) {
+ case NB_EV_PREPARE:
+ if (ret != NB_OK)
+ return ret;
+ change->prepare_ok = true;
+ break;
+ case NB_EV_ABORT:
+ case NB_EV_APPLY:
+ /*
+ * At this point it's not possible to reject the
+ * transaction anymore, so any failure here can lead to
+ * inconsistencies and should be treated as a bug.
+ * Operations prone to errors, like validations and
+ * resource allocations, should be performed during the
+ * 'prepare' phase.
+ */
+ break;
+ default:
+ break;
+ }
+ }
+
+ return NB_OK;
+}
+
+static struct nb_config_cb *
+nb_apply_finish_cb_new(struct nb_config_cbs *cbs, const char *xpath,
+ const struct nb_node *nb_node,
+ const struct lyd_node *dnode)
+{
+ struct nb_config_cb *cb;
+
+ cb = XCALLOC(MTYPE_TMP, sizeof(*cb));
+ strlcpy(cb->xpath, xpath, sizeof(cb->xpath));
+ cb->nb_node = nb_node;
+ cb->dnode = dnode;
+ RB_INSERT(nb_config_cbs, cbs, cb);
+
+ return cb;
+}
+
+static struct nb_config_cb *
+nb_apply_finish_cb_find(struct nb_config_cbs *cbs, const char *xpath,
+ const struct nb_node *nb_node)
+{
+ struct nb_config_cb s;
+
+ strlcpy(s.xpath, xpath, sizeof(s.xpath));
+ s.nb_node = nb_node;
+ return RB_FIND(nb_config_cbs, cbs, &s);
+}
+
+/* Call the 'apply_finish' callbacks. */
+static void nb_transaction_apply_finish(struct nb_transaction *transaction)
+{
+ struct nb_config_cbs cbs;
+ struct nb_config_cb *cb;
+
+ /* Initialize tree of 'apply_finish' callbacks. */
+ RB_INIT(nb_config_cbs, &cbs);
+
+ /* Identify the 'apply_finish' callbacks that need to be called. */
+ RB_FOREACH (cb, nb_config_cbs, &transaction->changes) {
+ struct nb_config_change *change = (struct nb_config_change *)cb;
+ const struct lyd_node *dnode = change->cb.dnode;
+
+ /*
+ * Iterate up to the root of the data tree. When a node is being
+ * deleted, skip its 'apply_finish' callback if one is defined
+ * (the 'apply_finish' callbacks from the node ancestors should
+ * be called though).
+ */
+ if (change->cb.operation == NB_OP_DELETE) {
+ char xpath[XPATH_MAXLEN];
+
+ dnode = dnode->parent;
+ if (!dnode)
+ break;
+
+ /*
+ * The dnode from 'delete' callbacks point to elements
+ * from the running configuration. Use yang_dnode_get()
+ * to get the corresponding dnode from the candidate
+ * configuration that is being committed.
+ */
+ yang_dnode_get_path(dnode, xpath, sizeof(xpath));
+ dnode = yang_dnode_get(transaction->config->dnode,
+ xpath);
+ }
+ while (dnode) {
+ char xpath[XPATH_MAXLEN];
+ struct nb_node *nb_node;
+
+ nb_node = dnode->schema->priv;
+ if (!nb_node->cbs.apply_finish)
+ goto next;
+
+ /*
+ * Don't call the callback more than once for the same
+ * data node.
+ */
+ yang_dnode_get_path(dnode, xpath, sizeof(xpath));
+ if (nb_apply_finish_cb_find(&cbs, xpath, nb_node))
+ goto next;
+
+ nb_apply_finish_cb_new(&cbs, xpath, nb_node, dnode);
+
+ next:
+ dnode = dnode->parent;
+ }
+ }
+
+ /* Call the 'apply_finish' callbacks, sorted by their priorities. */
+ RB_FOREACH (cb, nb_config_cbs, &cbs) {
+ if (debug_northbound)
+ nb_log_callback(NB_EV_APPLY, NB_OP_APPLY_FINISH,
+ cb->xpath, NULL);
+
+ (*cb->nb_node->cbs.apply_finish)(cb->dnode);
+ }
+
+ /* Release memory. */
+ while (!RB_EMPTY(nb_config_cbs, &cbs)) {
+ cb = RB_ROOT(nb_config_cbs, &cbs);
+ RB_REMOVE(nb_config_cbs, &cbs, cb);
+ XFREE(MTYPE_TMP, cb);
+ }
+}
+
+bool nb_operation_is_valid(enum nb_operation operation,
+ const struct lys_node *snode)
+{
+ struct lys_node_container *scontainer;
+ struct lys_node_leaf *sleaf;
+
+ switch (operation) {
+ case NB_OP_CREATE:
+ if (!(snode->flags & LYS_CONFIG_W))
+ return false;
+
+ switch (snode->nodetype) {
+ case LYS_LEAF:
+ sleaf = (struct lys_node_leaf *)snode;
+ if (sleaf->type.base != LY_TYPE_EMPTY)
+ return false;
+ break;
+ case LYS_CONTAINER:
+ scontainer = (struct lys_node_container *)snode;
+ if (!scontainer->presence)
+ return false;
+ break;
+ case LYS_LIST:
+ case LYS_LEAFLIST:
+ break;
+ default:
+ return false;
+ }
+ return true;
+ case NB_OP_MODIFY:
+ if (!(snode->flags & LYS_CONFIG_W))
+ return false;
+
+ switch (snode->nodetype) {
+ case LYS_LEAF:
+ sleaf = (struct lys_node_leaf *)snode;
+ if (sleaf->type.base == LY_TYPE_EMPTY)
+ return false;
+
+ /* List keys can't be modified. */
+ if (lys_is_key(sleaf, NULL))
+ return false;
+ break;
+ default:
+ return false;
+ }
+ return true;
+ case NB_OP_DELETE:
+ if (!(snode->flags & LYS_CONFIG_W))
+ return false;
+
+ switch (snode->nodetype) {
+ case LYS_LEAF:
+ sleaf = (struct lys_node_leaf *)snode;
+
+ /* List keys can't be deleted. */
+ if (lys_is_key(sleaf, NULL))
+ return false;
+
+ /*
+ * Only optional leafs can be deleted, or leafs whose
+ * parent is a case statement.
+ */
+ if (snode->parent->nodetype == LYS_CASE)
+ return true;
+ if (sleaf->when)
+ return true;
+ if ((sleaf->flags & LYS_MAND_TRUE) || sleaf->dflt)
+ return false;
+ break;
+ case LYS_CONTAINER:
+ scontainer = (struct lys_node_container *)snode;
+ if (!scontainer->presence)
+ return false;
+ break;
+ case LYS_LIST:
+ case LYS_LEAFLIST:
+ break;
+ default:
+ return false;
+ }
+ return true;
+ case NB_OP_MOVE:
+ if (!(snode->flags & LYS_CONFIG_W))
+ return false;
+
+ switch (snode->nodetype) {
+ case LYS_LIST:
+ case LYS_LEAFLIST:
+ if (!(snode->flags & LYS_USERORDERED))
+ return false;
+ break;
+ default:
+ return false;
+ }
+ return true;
+ case NB_OP_APPLY_FINISH:
+ if (!(snode->flags & LYS_CONFIG_W))
+ return false;
+ return true;
+ case NB_OP_GET_ELEM:
+ if (!(snode->flags & LYS_CONFIG_R))
+ return false;
+
+ switch (snode->nodetype) {
+ case LYS_LEAF:
+ break;
+ case LYS_CONTAINER:
+ scontainer = (struct lys_node_container *)snode;
+ if (!scontainer->presence)
+ return false;
+ break;
+ default:
+ return false;
+ }
+ return true;
+ case NB_OP_GET_NEXT:
+ case NB_OP_GET_KEYS:
+ case NB_OP_LOOKUP_ENTRY:
+ if (!(snode->flags & LYS_CONFIG_R))
+ return false;
+
+ switch (snode->nodetype) {
+ case LYS_LIST:
+ break;
+ default:
+ return false;
+ }
+ return true;
+ case NB_OP_RPC:
+ if (snode->flags & (LYS_CONFIG_W | LYS_CONFIG_R))
+ return false;
+
+ switch (snode->nodetype) {
+ case LYS_RPC:
+ case LYS_ACTION:
+ break;
+ default:
+ return false;
+ }
+ return true;
+ default:
+ return false;
+ }
+}
+
+DEFINE_HOOK(nb_notification_send, (const char *xpath, struct list *arguments),
+ (xpath, arguments));
+
+int nb_notification_send(const char *xpath, struct list *arguments)
+{
+ int ret;
+
+ ret = hook_call(nb_notification_send, xpath, arguments);
+ if (arguments)
+ list_delete(&arguments);
+
+ return ret;
+}
+
+const char *nb_event_name(enum nb_event event)
+{
+ switch (event) {
+ case NB_EV_VALIDATE:
+ return "validate";
+ case NB_EV_PREPARE:
+ return "prepare";
+ case NB_EV_ABORT:
+ return "abort";
+ case NB_EV_APPLY:
+ return "apply";
+ default:
+ return "unknown";
+ }
+}
+
+const char *nb_operation_name(enum nb_operation operation)
+{
+ switch (operation) {
+ case NB_OP_CREATE:
+ return "create";
+ case NB_OP_MODIFY:
+ return "modify";
+ case NB_OP_DELETE:
+ return "delete";
+ case NB_OP_MOVE:
+ return "move";
+ case NB_OP_APPLY_FINISH:
+ return "apply_finish";
+ case NB_OP_GET_ELEM:
+ return "get_elem";
+ case NB_OP_GET_NEXT:
+ return "get_next";
+ case NB_OP_GET_KEYS:
+ return "get_keys";
+ case NB_OP_LOOKUP_ENTRY:
+ return "lookup_entry";
+ case NB_OP_RPC:
+ return "rpc";
+ default:
+ return "unknown";
+ }
+}
+
+const char *nb_err_name(enum nb_error error)
+{
+ switch (error) {
+ case NB_OK:
+ return "ok";
+ case NB_ERR:
+ return "generic error";
+ case NB_ERR_NO_CHANGES:
+ return "no changes";
+ case NB_ERR_NOT_FOUND:
+ return "element not found";
+ case NB_ERR_LOCKED:
+ return "resource is locked";
+ case NB_ERR_VALIDATION:
+ return "validation error";
+ case NB_ERR_RESOURCE:
+ return "failed to allocate resource";
+ case NB_ERR_INCONSISTENCY:
+ return "internal inconsistency";
+ default:
+ return "unknown";
+ }
+}
+
+const char *nb_client_name(enum nb_client client)
+{
+ switch (client) {
+ case NB_CLIENT_CLI:
+ return "CLI";
+ default:
+ return "unknown";
+ }
+}
+
+static void nb_load_callbacks(const struct frr_yang_module_info *module)
+{
+ for (size_t i = 0; module->nodes[i].xpath; i++) {
+ struct nb_node *nb_node;
+ uint32_t priority;
+
+ nb_node = nb_node_find(module->nodes[i].xpath);
+ if (!nb_node) {
+ flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
+ "%s: unknown data path: %s", __func__,
+ module->nodes[i].xpath);
+ continue;
+ }
+
+ nb_node->cbs = module->nodes[i].cbs;
+ priority = module->nodes[i].priority;
+ if (priority != 0)
+ nb_node->priority = priority;
+ }
+}
+
+void nb_init(const struct frr_yang_module_info *modules[], size_t nmodules)
+{
+ unsigned int errors = 0;
+
+ /* Load YANG modules. */
+ for (size_t i = 0; i < nmodules; i++)
+ yang_module_load(modules[i]->name);
+
+ /* Create a nb_node for all YANG schema nodes. */
+ yang_all_snodes_iterate(nb_node_new_cb, 0, NULL, NULL);
+
+ /* Load northbound callbacks. */
+ for (size_t i = 0; i < nmodules; i++)
+ nb_load_callbacks(modules[i]);
+
+ /* Validate northbound callbacks. */
+ yang_all_snodes_iterate(nb_node_validate, 0, &errors, NULL);
+ if (errors > 0) {
+ flog_err(
+ EC_LIB_NB_CBS_VALIDATION,
+ "%s: failed to validate northbound callbacks: %u error(s)",
+ __func__, errors);
+ exit(1);
+ }
+
+ /* Initialize the northbound database (used for the rollback log). */
+ if (nb_db_init() != NB_OK)
+ flog_warn(EC_LIB_NB_DATABASE,
+ "%s: failed to initialize northbound database",
+ __func__);
+
+ /* Create an empty running configuration. */
+ running_config = nb_config_new(NULL);
+
+ /* Initialize the northbound CLI. */
+ nb_cli_init();
+}
+
+void nb_terminate(void)
+{
+ /* Terminate the northbound CLI. */
+ nb_cli_terminate();
+
+ /* Delete all nb_node's from all YANG modules. */
+ yang_all_snodes_iterate(nb_node_del_cb, 0, NULL, NULL);
+
+ /* Delete the running configuration. */
+ nb_config_free(running_config);
+}
--- /dev/null
+/*
+ * Copyright (C) 2018 NetDEF, Inc.
+ * Renato Westphal
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _FRR_NORTHBOUND_H_
+#define _FRR_NORTHBOUND_H_
+
+#include "hook.h"
+#include "yang.h"
+#include "linklist.h"
+#include "openbsd-tree.h"
+
+/* Forward declaration(s). */
+struct vty;
+
+/* Northbound events. */
+enum nb_event {
+ /*
+ * The configuration callback is supposed to verify that the changes are
+ * valid and can be applied.
+ */
+ NB_EV_VALIDATE,
+
+ /*
+ * The configuration callback is supposed to prepare all resources
+ * required to apply the changes.
+ */
+ NB_EV_PREPARE,
+
+ /*
+ * Transaction has failed, the configuration callback needs to release
+ * all resources previously allocated.
+ */
+ NB_EV_ABORT,
+
+ /*
+ * The configuration changes need to be applied. The changes can't be
+ * rejected at this point (errors are logged and ignored).
+ */
+ NB_EV_APPLY,
+};
+
+/*
+ * Northbound operations.
+ *
+ * Refer to the documentation comments of nb_callbacks for more details.
+ */
+enum nb_operation {
+ NB_OP_CREATE,
+ NB_OP_MODIFY,
+ NB_OP_DELETE,
+ NB_OP_MOVE,
+ NB_OP_APPLY_FINISH,
+ NB_OP_GET_ELEM,
+ NB_OP_GET_NEXT,
+ NB_OP_GET_KEYS,
+ NB_OP_LOOKUP_ENTRY,
+ NB_OP_RPC,
+};
+
+union nb_resource {
+ int fd;
+ void *ptr;
+};
+
+struct nb_callbacks {
+ /*
+ * Configuration callback.
+ *
+ * A presence container, list entry, leaf-list entry or leaf of type
+ * empty has been created.
+ *
+ * For presence-containers and list entries, the callback is supposed to
+ * initialize the default values of its children (if any) from the YANG
+ * models.
+ *
+ * event
+ * The transaction phase. Refer to the documentation comments of
+ * nb_event for more details.
+ *
+ * dnode
+ * libyang data node that is being created.
+ *
+ * resource
+ * Pointer to store resource(s) allocated during the NB_EV_PREPARE
+ * phase. The same pointer can be used during the NB_EV_ABORT and
+ * NB_EV_APPLY phases to either release or make use of the allocated
+ * resource(s). It's set to NULL when the event is NB_EV_VALIDATE.
+ *
+ * Returns:
+ * - NB_OK on success.
+ * - NB_ERR_VALIDATION when a validation error occurred.
+ * - NB_ERR_RESOURCE when the callback failed to allocate a resource.
+ * - NB_ERR_INCONSISTENCY when an inconsistency was detected.
+ * - NB_ERR for other errors.
+ */
+ int (*create)(enum nb_event event, const struct lyd_node *dnode,
+ union nb_resource *resource);
+
+ /*
+ * Configuration callback.
+ *
+ * The value of a leaf has been modified.
+ *
+ * List keys don't need to implement this callback. When a list key is
+ * modified, the northbound treats this as if the list was deleted and a
+ * new one created with the updated key value.
+ *
+ * event
+ * The transaction phase. Refer to the documentation comments of
+ * nb_event for more details.
+ *
+ * dnode
+ * libyang data node that is being modified
+ *
+ * resource
+ * Pointer to store resource(s) allocated during the NB_EV_PREPARE
+ * phase. The same pointer can be used during the NB_EV_ABORT and
+ * NB_EV_APPLY phases to either release or make use of the allocated
+ * resource(s). It's set to NULL when the event is NB_EV_VALIDATE.
+ *
+ * Returns:
+ * - NB_OK on success.
+ * - NB_ERR_VALIDATION when a validation error occurred.
+ * - NB_ERR_RESOURCE when the callback failed to allocate a resource.
+ * - NB_ERR_INCONSISTENCY when an inconsistency was detected.
+ * - NB_ERR for other errors.
+ */
+ int (*modify)(enum nb_event event, const struct lyd_node *dnode,
+ union nb_resource *resource);
+
+ /*
+ * Configuration callback.
+ *
+ * A presence container, list entry, leaf-list entry or optional leaf
+ * has been deleted.
+ *
+ * The callback is supposed to delete the entire configuration object,
+ * including its children when they exist.
+ *
+ * event
+ * The transaction phase. Refer to the documentation comments of
+ * nb_event for more details.
+ *
+ * dnode
+ * libyang data node that is being deleted.
+ *
+ * Returns:
+ * - NB_OK on success.
+ * - NB_ERR_VALIDATION when a validation error occurred.
+ * - NB_ERR_INCONSISTENCY when an inconsistency was detected.
+ * - NB_ERR for other errors.
+ */
+ int (*delete)(enum nb_event event, const struct lyd_node *dnode);
+
+ /*
+ * Configuration callback.
+ *
+ * A list entry or leaf-list entry has been moved. Only applicable when
+ * the "ordered-by user" statement is present.
+ *
+ * event
+ * The transaction phase. Refer to the documentation comments of
+ * nb_event for more details.
+ *
+ * dnode
+ * libyang data node that is being moved.
+ *
+ * Returns:
+ * - NB_OK on success.
+ * - NB_ERR_VALIDATION when a validation error occurred.
+ * - NB_ERR_INCONSISTENCY when an inconsistency was detected.
+ * - NB_ERR for other errors.
+ */
+ int (*move)(enum nb_event event, const struct lyd_node *dnode);
+
+ /*
+ * Optional configuration callback.
+ *
+ * The 'apply_finish' callbacks are called after all other callbacks
+ * during the apply phase (NB_EV_APPLY). These callbacks are called only
+ * under one of the following two cases:
+ * - The data node has been created or modified (but not deleted);
+ * - Any change was made within the descendants of the data node (e.g. a
+ * child leaf was modified, created or deleted).
+ *
+ * In the second case above, the 'apply_finish' callback is called only
+ * once even if multiple changes occurred within the descendants of the
+ * data node.
+ *
+ * dnode
+ * libyang data node associated with the 'apply_finish' callback.
+ */
+ void (*apply_finish)(const struct lyd_node *dnode);
+
+ /*
+ * Operational data callback.
+ *
+ * The callback function should return the value of a specific leaf or
+ * inform if a typeless value (presence containers or leafs of type
+ * empty) exists or not.
+ *
+ * xpath
+ * YANG data path of the data we want to get.
+ *
+ * list_entry
+ * Pointer to list entry.
+ *
+ * Returns:
+ * Pointer to newly created yang_data structure, or NULL to indicate
+ * the absence of data.
+ */
+ struct yang_data *(*get_elem)(const char *xpath,
+ const void *list_entry);
+
+ /*
+ * Operational data callback for YANG lists.
+ *
+ * The callback function should return the next entry in the list. The
+ * 'list_entry' parameter will be NULL on the first invocation.
+ *
+ * xpath
+ * Data path of the YANG list.
+ *
+ * list_entry
+ * Pointer to list entry.
+ *
+ * Returns:
+ * Pointer to the next entry in the list, or NULL to signal that the
+ * end of the list was reached.
+ */
+ const void *(*get_next)(const char *xpath, const void *list_entry);
+
+ /*
+ * Operational data callback for YANG lists.
+ *
+ * The callback function should fill the 'keys' parameter based on the
+ * given list_entry.
+ *
+ * list_entry
+ * Pointer to list entry.
+ *
+ * keys
+ * Structure to be filled based on the attributes of the provided
+ * list entry.
+ *
+ * Returns:
+ * NB_OK on success, NB_ERR otherwise.
+ */
+ int (*get_keys)(const void *list_entry, struct yang_list_keys *keys);
+
+ /*
+ * Operational data callback for YANG lists.
+ *
+ * The callback function should return a list entry based on the list
+ * keys given as a parameter.
+ *
+ * keys
+ * Structure containing the keys of the list entry.
+ *
+ * Returns:
+ * Pointer to the list entry if found, or NULL if not found.
+ */
+ const void *(*lookup_entry)(const struct yang_list_keys *keys);
+
+ /*
+ * RPC and action callback.
+ *
+ * Both 'input' and 'output' are lists of 'yang_data' structures. The
+ * callback should fetch all the input parameters from the 'input' list,
+ * and add output parameters to the 'output' list if necessary.
+ *
+ * xpath
+ * XPath of the YANG RPC or action.
+ *
+ * input
+ * Read-only list of input parameters.
+ *
+ * output
+ * List of output parameters to be populated by the callback.
+ *
+ * Returns:
+ * NB_OK on success, NB_ERR otherwise.
+ */
+ int (*rpc)(const char *xpath, const struct list *input,
+ struct list *output);
+
+ /*
+ * Optional callback to show the CLI command associated to the given
+ * YANG data node.
+ *
+ * vty
+ * The vty terminal to dump the configuration to.
+ *
+ * dnode
+ * libyang data node that should be shown in the form of a CLI
+ * command.
+ *
+ * show_defaults
+ * Specify whether to display default configuration values or not.
+ * This parameter can be ignored most of the time since the
+ * northbound doesn't call this callback for default leaves or
+ * non-presence containers that contain only default child nodes.
+ * The exception are commands associated to multiple configuration
+ * nodes, in which case it might be desirable to hide one or more
+ * parts of the command when this parameter is set to false.
+ */
+ void (*cli_show)(struct vty *vty, struct lyd_node *dnode,
+ bool show_defaults);
+};
+
+/*
+ * Northbound-specific data that is allocated for each schema node of the native
+ * YANG modules.
+ */
+struct nb_node {
+ /* Back pointer to the libyang schema node. */
+ const struct lys_node *snode;
+
+ /* Data path of this YANG node. */
+ char xpath[XPATH_MAXLEN];
+
+ /* Priority - lower priorities are processed first. */
+ uint32_t priority;
+
+ /* Callbacks implemented for this node. */
+ struct nb_callbacks cbs;
+
+ /*
+ * Pointer to the parent node (disconsidering non-presence containers).
+ */
+ struct nb_node *parent;
+
+ /* Pointer to the nearest parent list, if any. */
+ struct nb_node *parent_list;
+
+#ifdef HAVE_CONFD
+ /* ConfD hash value corresponding to this YANG path. */
+ int confd_hash;
+#endif
+};
+
+struct frr_yang_module_info {
+ /* YANG module name. */
+ const char *name;
+
+ /* Northbound callbacks. */
+ const struct {
+ /* Data path of this YANG node. */
+ const char *xpath;
+
+ /* Callbacks implemented for this node. */
+ struct nb_callbacks cbs;
+
+ /* Priority - lower priorities are processed first. */
+ uint32_t priority;
+ } nodes[];
+};
+
+/* Northbound error codes. */
+enum nb_error {
+ NB_OK = 0,
+ NB_ERR,
+ NB_ERR_NO_CHANGES,
+ NB_ERR_NOT_FOUND,
+ NB_ERR_LOCKED,
+ NB_ERR_VALIDATION,
+ NB_ERR_RESOURCE,
+ NB_ERR_INCONSISTENCY,
+};
+
+/* Default priority. */
+#define NB_DFLT_PRIORITY (UINT32_MAX / 2)
+
+/* Default maximum of configuration rollbacks to store. */
+#define NB_DLFT_MAX_CONFIG_ROLLBACKS 20
+
+/* Northbound clients. */
+enum nb_client {
+ NB_CLIENT_CLI = 0,
+};
+
+/* Northbound configuration. */
+struct nb_config {
+ struct lyd_node *dnode;
+ uint32_t version;
+};
+
+/* Northbound configuration callback. */
+struct nb_config_cb {
+ RB_ENTRY(nb_config_cb) entry;
+ enum nb_operation operation;
+ char xpath[XPATH_MAXLEN];
+ const struct nb_node *nb_node;
+ const struct lyd_node *dnode;
+};
+RB_HEAD(nb_config_cbs, nb_config_cb);
+RB_PROTOTYPE(nb_config_cbs, nb_config_cb, entry, nb_config_cb_compare);
+
+/* Northbound configuration change. */
+struct nb_config_change {
+ struct nb_config_cb cb;
+ union nb_resource resource;
+ bool prepare_ok;
+};
+
+/* Northbound configuration transaction. */
+struct nb_transaction {
+ enum nb_client client;
+ char comment[80];
+ struct nb_config *config;
+ struct nb_config_cbs changes;
+};
+
+DECLARE_HOOK(nb_notification_send, (const char *xpath, struct list *arguments),
+ (xpath, arguments))
+
+extern int debug_northbound;
+extern struct nb_config *running_config;
+
+/*
+ * Find the northbound node corresponding to a YANG data path.
+ *
+ * xpath
+ * XPath to search for (with or without predicates).
+ *
+ * Returns:
+ * Pointer to northbound node if found, NULL otherwise.
+ */
+extern struct nb_node *nb_node_find(const char *xpath);
+
+/*
+ * Create a new northbound configuration.
+ *
+ * dnode
+ * Pointer to a libyang data node containing the configuration data. If NULL
+ * is given, an empty configuration will be created.
+ *
+ * Returns:
+ * Pointer to newly created northbound configuration.
+ */
+extern struct nb_config *nb_config_new(struct lyd_node *dnode);
+
+/*
+ * Delete a northbound configuration.
+ *
+ * config
+ * Pointer to the config that is going to be deleted.
+ */
+extern void nb_config_free(struct nb_config *config);
+
+/*
+ * Duplicate a northbound configuration.
+ *
+ * config
+ * Northbound configuration to duplicate.
+ *
+ * Returns:
+ * Pointer to duplicated configuration.
+ */
+extern struct nb_config *nb_config_dup(const struct nb_config *config);
+
+/*
+ * Merge one configuration into another.
+ *
+ * config_dst
+ * Configuration to merge to.
+ *
+ * config_src
+ * Configuration to merge config_dst with.
+ *
+ * preserve_source
+ * Specify whether config_src should be deleted or not after the merge
+ * operation.
+ *
+ * Returns:
+ * NB_OK on success, NB_ERR otherwise.
+ */
+extern int nb_config_merge(struct nb_config *config_dst,
+ struct nb_config *config_src, bool preserve_source);
+
+/*
+ * Replace one configuration by another.
+ *
+ * config_dst
+ * Configuration to be replaced.
+ *
+ * config_src
+ * Configuration to replace config_dst.
+ *
+ * preserve_source
+ * Specify whether config_src should be deleted or not after the replace
+ * operation.
+ */
+extern void nb_config_replace(struct nb_config *config_dst,
+ struct nb_config *config_src,
+ bool preserve_source);
+
+/*
+ * Edit a candidate configuration.
+ *
+ * candidate
+ * Candidate configuration to edit.
+ *
+ * nb_node
+ * Northbound node associated to the configuration being edited.
+ *
+ * operation
+ * Operation to apply.
+ *
+ * xpath
+ * XPath of the configuration node being edited.
+ *
+ * previous
+ * Previous value of the configuration node. Should be used only when the
+ * operation is NB_OP_MOVE, otherwise this parameter is ignored.
+ *
+ * data
+ * New value of the configuration node.
+ *
+ * Returns:
+ * - NB_OK on success.
+ * - NB_ERR_NOT_FOUND when the element to be deleted was not found.
+ * - NB_ERR for other errors.
+ */
+extern int nb_candidate_edit(struct nb_config *candidate,
+ const struct nb_node *nb_node,
+ enum nb_operation operation, const char *xpath,
+ const struct yang_data *previous,
+ const struct yang_data *data);
+
+/*
+ * Check if a candidate configuration is outdated and needs to be updated.
+ *
+ * candidate
+ * Candidate configuration to check.
+ *
+ * Returns:
+ * true if the candidate is outdated, false otherwise.
+ */
+extern bool nb_candidate_needs_update(const struct nb_config *candidate);
+
+/*
+ * Update a candidate configuration by rebasing the changes on top of the latest
+ * running configuration. Resolve conflicts automatically by giving preference
+ * to the changes done in the candidate configuration.
+ *
+ * candidate
+ * Candidate configuration to update.
+ *
+ * Returns:
+ * NB_OK on success, NB_ERR otherwise.
+ */
+extern int nb_candidate_update(struct nb_config *candidate);
+
+/*
+ * Validate a candidate configuration. Perform both YANG syntactic/semantic
+ * validation and code-level validation using the northbound callbacks.
+ *
+ * WARNING: the candidate can be modified as part of the validation process
+ * (e.g. add default nodes).
+ *
+ * candidate
+ * Candidate configuration to validate.
+ *
+ * Returns:
+ * NB_OK on success, NB_ERR_VALIDATION otherwise.
+ */
+extern int nb_candidate_validate(struct nb_config *candidate);
+
+/*
+ * Create a new configuration transaction but do not commit it yet. Only
+ * validate the candidate and prepare all resources required to apply the
+ * configuration changes.
+ *
+ * candidate
+ * Candidate configuration to commit.
+ *
+ * client
+ * Northbound client performing the commit.
+ *
+ * comment
+ * Optional comment describing the commit.
+ *
+ * transaction
+ * Output parameter providing the created transaction when one is created
+ * successfully. In this case, it must be either aborted using
+ * nb_candidate_commit_abort() or committed using
+ * nb_candidate_commit_apply().
+ *
+ * Returns:
+ * - NB_OK on success.
+ * - NB_ERR_NO_CHANGES when the candidate is identical to the running
+ * configuration.
+ * - NB_ERR_LOCKED when there's already another transaction in progress.
+ * - NB_ERR_VALIDATION when the candidate fails the validation checks.
+ * - NB_ERR_RESOURCE when the system fails to allocate resources to apply
+ * the candidate configuration.
+ * - NB_ERR for other errors.
+ */
+extern int nb_candidate_commit_prepare(struct nb_config *candidate,
+ enum nb_client client,
+ const char *comment,
+ struct nb_transaction **transaction);
+
+/*
+ * Abort a previously created configuration transaction, releasing all resources
+ * allocated during the preparation phase.
+ *
+ * transaction
+ * Candidate configuration to abort. It's consumed by this function.
+ */
+extern void nb_candidate_commit_abort(struct nb_transaction *transaction);
+
+/*
+ * Commit a previously created configuration transaction.
+ *
+ * transaction
+ * Configuration transaction to commit. It's consumed by this function.
+ *
+ * save_transaction
+ * Specify whether the transaction should be recorded in the transactions log
+ * or not.
+ *
+ * transaction_id
+ * Optional output parameter providing the ID of the committed transaction.
+ */
+extern void nb_candidate_commit_apply(struct nb_transaction *transaction,
+ bool save_transaction,
+ uint32_t *transaction_id);
+
+/*
+ * Create a new transaction to commit a candidate configuration. This is a
+ * convenience function that performs the two-phase commit protocol
+ * transparently to the user. The cost is reduced flexibility, since
+ * network-wide and multi-daemon transactions require the network manager to
+ * take into account the results of the preparation phase of multiple managed
+ * entities.
+ *
+ * candidate
+ * Candidate configuration to commit. It's preserved regardless if the commit
+ * operation fails or not.
+ *
+ * client
+ * Northbound client performing the commit.
+ *
+ * save_transaction
+ * Specify whether the transaction should be recorded in the transactions log
+ * or not.
+ *
+ * comment
+ * Optional comment describing the commit.
+ *
+ * transaction_id
+ * Optional output parameter providing the ID of the committed transaction.
+ *
+ * Returns:
+ * - NB_OK on success.
+ * - NB_ERR_NO_CHANGES when the candidate is identical to the running
+ * configuration.
+ * - NB_ERR_LOCKED when there's already another transaction in progress.
+ * - NB_ERR_VALIDATION when the candidate fails the validation checks.
+ * - NB_ERR_RESOURCE when the system fails to allocate resources to apply
+ * the candidate configuration.
+ * - NB_ERR for other errors.
+ */
+extern int nb_candidate_commit(struct nb_config *candidate,
+ enum nb_client client, bool save_transaction,
+ const char *comment, uint32_t *transaction_id);
+
+/*
+ * Validate if the northbound operation is valid for the given node.
+ *
+ * operation
+ * Operation we want to check.
+ *
+ * snode
+ * libyang schema node we want to check.
+ *
+ * Returns:
+ * true if the operation is valid, false otherwise.
+ */
+extern bool nb_operation_is_valid(enum nb_operation operation,
+ const struct lys_node *snode);
+
+/*
+ * Send a YANG notification. This is a no-op unless the 'nb_notification_send'
+ * hook was registered by a northbound plugin.
+ *
+ * xpath
+ * XPath of the YANG notification.
+ *
+ * arguments
+ * Linked list containing the arguments that should be sent. This list is
+ * deleted after being used.
+ *
+ * Returns:
+ * NB_OK on success, NB_ERR otherwise.
+ */
+extern int nb_notification_send(const char *xpath, struct list *arguments);
+
+/*
+ * Return a human-readable string representing a northbound event.
+ *
+ * event
+ * Northbound event.
+ *
+ * Returns:
+ * String representation of the given northbound event.
+ */
+extern const char *nb_event_name(enum nb_event event);
+
+/*
+ * Return a human-readable string representing a northbound operation.
+ *
+ * operation
+ * Northbound operation.
+ *
+ * Returns:
+ * String representation of the given northbound operation.
+ */
+extern const char *nb_operation_name(enum nb_operation operation);
+
+/*
+ * Return a human-readable string representing a northbound error.
+ *
+ * error
+ * Northbound error.
+ *
+ * Returns:
+ * String representation of the given northbound error.
+ */
+extern const char *nb_err_name(enum nb_error error);
+
+/*
+ * Return a human-readable string representing a northbound client.
+ *
+ * client
+ * Northbound client.
+ *
+ * Returns:
+ * String representation of the given northbound client.
+ */
+extern const char *nb_client_name(enum nb_client client);
+
+/*
+ * Initialize the northbound layer. Should be called only once during the
+ * daemon initialization process.
+ *
+ * modules
+ * Array of YANG modules to parse and initialize.
+ *
+ * nmodules
+ * Size of the modules array.
+ */
+extern void nb_init(const struct frr_yang_module_info *modules[],
+ size_t nmodules);
+
+/*
+ * Finish the northbound layer gracefully. Should be called only when the daemon
+ * is exiting.
+ */
+extern void nb_terminate(void);
+
+#endif /* _FRR_NORTHBOUND_H_ */
--- /dev/null
+/*
+ * Copyright (C) 2018 NetDEF, Inc.
+ * Renato Westphal
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <zebra.h>
+
+#include "libfrr.h"
+#include "version.h"
+#include "log.h"
+#include "lib_errors.h"
+#include "command.h"
+#include "termtable.h"
+#include "db.h"
+#include "yang_translator.h"
+#include "northbound.h"
+#include "northbound_cli.h"
+#include "northbound_db.h"
+#ifndef VTYSH_EXTRACT_PL
+#include "lib/northbound_cli_clippy.c"
+#endif
+
+int debug_northbound;
+struct nb_config *vty_shared_candidate_config;
+
+static void vty_show_libyang_errors(struct vty *vty, struct ly_ctx *ly_ctx)
+{
+ struct ly_err_item *ei;
+ const char *path;
+
+ ei = ly_err_first(ly_ctx);
+ if (!ei)
+ return;
+
+ for (; ei; ei = ei->next)
+ vty_out(vty, "%s\n", ei->msg);
+
+ path = ly_errpath(ly_ctx);
+ if (path)
+ vty_out(vty, "YANG path: %s\n", path);
+
+ ly_err_clean(ly_ctx, NULL);
+}
+
+int nb_cli_cfg_change(struct vty *vty, char *xpath_base,
+ struct cli_config_change changes[], size_t size)
+{
+ struct nb_config *candidate_transitory;
+ bool error = false;
+ int ret;
+
+ VTY_CHECK_XPATH;
+
+ /*
+ * Create a copy of the candidate configuration. For consistency, we
+ * need to ensure that either all changes made by the command are
+ * accepted or none are.
+ */
+ candidate_transitory = nb_config_dup(vty->candidate_config);
+
+ /* Edit candidate configuration. */
+ for (size_t i = 0; i < size; i++) {
+ struct cli_config_change *change = &changes[i];
+ struct nb_node *nb_node;
+ char xpath[XPATH_MAXLEN];
+ struct yang_data *data;
+
+ /* Handle relative XPaths. */
+ memset(xpath, 0, sizeof(xpath));
+ if (vty->xpath_index > 0
+ && ((xpath_base && xpath_base[0] == '.')
+ || change->xpath[0] == '.'))
+ strlcpy(xpath, VTY_CURR_XPATH, sizeof(xpath));
+ if (xpath_base) {
+ if (xpath_base[0] == '.')
+ xpath_base++;
+ strlcat(xpath, xpath_base, sizeof(xpath));
+ }
+ if (change->xpath[0] == '.')
+ strlcat(xpath, change->xpath + 1, sizeof(xpath));
+ else
+ strlcpy(xpath, change->xpath, sizeof(xpath));
+
+ nb_node = nb_node_find(xpath);
+ if (!nb_node) {
+ flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
+ "%s: unknown data path: %s", __func__, xpath);
+ error = true;
+ break;
+ }
+
+ /* If the value is not set, get the default if it exists. */
+ if (change->value == NULL)
+ change->value = yang_snode_get_default(nb_node->snode);
+ data = yang_data_new(xpath, change->value);
+
+ /*
+ * Ignore "not found" errors when editing the candidate
+ * configuration.
+ */
+ ret = nb_candidate_edit(candidate_transitory, nb_node,
+ change->operation, xpath, NULL, data);
+ yang_data_free(data);
+ if (ret != NB_OK && ret != NB_ERR_NOT_FOUND) {
+ flog_warn(
+ EC_LIB_NB_CANDIDATE_EDIT_ERROR,
+ "%s: failed to edit candidate configuration: operation [%s] xpath [%s]",
+ __func__, nb_operation_name(change->operation),
+ xpath);
+ error = true;
+ break;
+ }
+ }
+
+ if (error) {
+ nb_config_free(candidate_transitory);
+
+ switch (frr_get_cli_mode()) {
+ case FRR_CLI_CLASSIC:
+ vty_out(vty, "%% Configuration failed.\n\n");
+ break;
+ case FRR_CLI_TRANSACTIONAL:
+ vty_out(vty,
+ "%% Failed to edit candidate configuration.\n\n");
+ break;
+ }
+ vty_show_libyang_errors(vty, ly_native_ctx);
+
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ nb_config_replace(vty->candidate_config, candidate_transitory, false);
+
+ /* Do an implicit "commit" when using the classic CLI mode. */
+ if (frr_get_cli_mode() == FRR_CLI_CLASSIC) {
+ ret = nb_candidate_commit(vty->candidate_config, NB_CLIENT_CLI,
+ false, NULL, NULL);
+ if (ret != NB_OK && ret != NB_ERR_NO_CHANGES) {
+ vty_out(vty, "%% Configuration failed: %s.\n\n",
+ nb_err_name(ret));
+ vty_out(vty,
+ "Please check the logs for more details.\n");
+
+ /* Regenerate candidate for consistency. */
+ nb_config_replace(vty->candidate_config, running_config,
+ true);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+ }
+
+ return CMD_SUCCESS;
+}
+
+int nb_cli_rpc(const char *xpath, struct list *input, struct list *output)
+{
+ struct nb_node *nb_node;
+ int ret;
+
+ nb_node = nb_node_find(xpath);
+ if (!nb_node) {
+ flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
+ "%s: unknown data path: %s", __func__, xpath);
+ return CMD_WARNING;
+ }
+
+ ret = nb_node->cbs.rpc(xpath, input, output);
+ switch (ret) {
+ case NB_OK:
+ return CMD_SUCCESS;
+ default:
+ return CMD_WARNING;
+ }
+}
+
+static int nb_cli_commit(struct vty *vty, bool force, char *comment)
+{
+ uint32_t transaction_id;
+ int ret;
+
+ if (vty_exclusive_lock != NULL && vty_exclusive_lock != vty) {
+ vty_out(vty, "%% Configuration is locked by another VTY.\n\n");
+ return CMD_WARNING;
+ }
+
+ if (!force && nb_candidate_needs_update(vty->candidate_config)) {
+ vty_out(vty,
+ "%% Candidate configuration needs to be updated before commit.\n\n");
+ vty_out(vty,
+ "Use the \"update\" command or \"commit force\".\n");
+ return CMD_WARNING;
+ }
+
+ ret = nb_candidate_commit(vty->candidate_config, NB_CLIENT_CLI, true,
+ comment, &transaction_id);
+
+ /* Map northbound return code to CLI return code. */
+ switch (ret) {
+ case NB_OK:
+ nb_config_replace(vty->candidate_config_base, running_config,
+ true);
+ vty_out(vty,
+ "%% Configuration committed successfully (Transaction ID #%u).\n\n",
+ transaction_id);
+ return CMD_SUCCESS;
+ case NB_ERR_NO_CHANGES:
+ vty_out(vty, "%% No configuration changes to commit.\n\n");
+ return CMD_SUCCESS;
+ default:
+ vty_out(vty,
+ "%% Failed to commit candidate configuration: %s.\n\n",
+ nb_err_name(ret));
+ vty_out(vty, "Please check the logs for more details.\n");
+ return CMD_WARNING;
+ }
+}
+
+static int nb_cli_candidate_load_file(struct vty *vty,
+ enum nb_cfg_format format,
+ struct yang_translator *translator,
+ const char *path, bool replace)
+{
+ struct nb_config *loaded_config = NULL;
+ struct lyd_node *dnode;
+ struct ly_ctx *ly_ctx;
+ int ly_format;
+
+ switch (format) {
+ case NB_CFG_FMT_CMDS:
+ loaded_config = nb_config_new(NULL);
+ if (!vty_read_config(loaded_config, path, config_default)) {
+ vty_out(vty, "%% Failed to load configuration.\n\n");
+ vty_out(vty,
+ "Please check the logs for more details.\n");
+ nb_config_free(loaded_config);
+ return CMD_WARNING;
+ }
+ break;
+ case NB_CFG_FMT_JSON:
+ case NB_CFG_FMT_XML:
+ ly_format = (format == NB_CFG_FMT_JSON) ? LYD_JSON : LYD_XML;
+
+ ly_ctx = translator ? translator->ly_ctx : ly_native_ctx;
+ dnode = lyd_parse_path(ly_ctx, path, ly_format, LYD_OPT_EDIT);
+ if (!dnode) {
+ flog_warn(EC_LIB_LIBYANG, "%s: lyd_parse_path() failed",
+ __func__);
+ vty_out(vty, "%% Failed to load configuration:\n\n");
+ vty_show_libyang_errors(vty, ly_ctx);
+ return CMD_WARNING;
+ }
+ if (translator
+ && yang_translate_dnode(translator,
+ YANG_TRANSLATE_TO_NATIVE, &dnode)
+ != YANG_TRANSLATE_SUCCESS) {
+ vty_out(vty, "%% Failed to translate configuration\n");
+ yang_dnode_free(dnode);
+ return CMD_WARNING;
+ }
+ loaded_config = nb_config_new(dnode);
+ break;
+ }
+
+ if (replace)
+ nb_config_replace(vty->candidate_config, loaded_config, false);
+ else if (nb_config_merge(vty->candidate_config, loaded_config, false)
+ != NB_OK) {
+ vty_out(vty,
+ "%% Failed to merge the loaded configuration:\n\n");
+ vty_show_libyang_errors(vty, ly_native_ctx);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+static int nb_cli_candidate_load_transaction(struct vty *vty,
+ uint32_t transaction_id,
+ bool replace)
+{
+ struct nb_config *loaded_config;
+
+ loaded_config = nb_db_transaction_load(transaction_id);
+ if (!loaded_config) {
+ vty_out(vty, "%% Transaction %u does not exist.\n\n",
+ transaction_id);
+ return CMD_WARNING;
+ }
+
+ if (replace)
+ nb_config_replace(vty->candidate_config, loaded_config, false);
+ else if (nb_config_merge(vty->candidate_config, loaded_config, false)
+ != NB_OK) {
+ vty_out(vty,
+ "%% Failed to merge the loaded configuration:\n\n");
+ vty_show_libyang_errors(vty, ly_native_ctx);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+void nb_cli_show_dnode_cmds(struct vty *vty, struct lyd_node *root,
+ bool with_defaults)
+{
+ struct lyd_node *next, *child;
+
+ LY_TREE_DFS_BEGIN (root, next, child) {
+ struct nb_node *nb_node;
+
+ nb_node = child->schema->priv;
+ if (!nb_node->cbs.cli_show)
+ goto next;
+
+ /* Skip default values. */
+ if (!with_defaults && yang_dnode_is_default_recursive(child))
+ goto next;
+
+ (*nb_node->cbs.cli_show)(vty, child, with_defaults);
+ next:
+ LY_TREE_DFS_END(root, next, child);
+ }
+}
+
+static void nb_cli_show_config_cmds(struct vty *vty, struct nb_config *config,
+ bool with_defaults)
+{
+ struct lyd_node *root;
+
+ vty_out(vty, "Configuration:\n");
+ vty_out(vty, "!\n");
+ vty_out(vty, "frr version %s\n", FRR_VER_SHORT);
+ vty_out(vty, "frr defaults %s\n", DFLT_NAME);
+
+ LY_TREE_FOR (config->dnode, root)
+ nb_cli_show_dnode_cmds(vty, root, with_defaults);
+
+ vty_out(vty, "!\n");
+ vty_out(vty, "end\n");
+}
+
+static int nb_cli_show_config_libyang(struct vty *vty, LYD_FORMAT format,
+ struct nb_config *config,
+ struct yang_translator *translator,
+ bool with_defaults)
+{
+ struct lyd_node *dnode;
+ char *strp;
+ int options;
+
+ dnode = yang_dnode_dup(config->dnode);
+ if (translator
+ && yang_translate_dnode(translator, YANG_TRANSLATE_FROM_NATIVE,
+ &dnode)
+ != YANG_TRANSLATE_SUCCESS) {
+ vty_out(vty, "%% Failed to translate configuration\n");
+ yang_dnode_free(dnode);
+ return CMD_WARNING;
+ }
+
+ options = LYP_FORMAT | LYP_WITHSIBLINGS;
+ if (with_defaults)
+ options |= LYP_WD_ALL;
+ else
+ options |= LYP_WD_TRIM;
+
+ if (lyd_print_mem(&strp, dnode, format, options) == 0 && strp) {
+ vty_out(vty, "%s", strp);
+ free(strp);
+ }
+
+ yang_dnode_free(dnode);
+
+ return CMD_SUCCESS;
+}
+
+static int nb_cli_show_config(struct vty *vty, struct nb_config *config,
+ enum nb_cfg_format format,
+ struct yang_translator *translator,
+ bool with_defaults)
+{
+ switch (format) {
+ case NB_CFG_FMT_CMDS:
+ nb_cli_show_config_cmds(vty, config, with_defaults);
+ break;
+ case NB_CFG_FMT_JSON:
+ return nb_cli_show_config_libyang(vty, LYD_JSON, config,
+ translator, with_defaults);
+ case NB_CFG_FMT_XML:
+ return nb_cli_show_config_libyang(vty, LYD_XML, config,
+ translator, with_defaults);
+ }
+
+ return CMD_SUCCESS;
+}
+
+static int nb_write_config(struct nb_config *config, enum nb_cfg_format format,
+ struct yang_translator *translator, char *path,
+ size_t pathlen)
+{
+ int fd;
+ struct vty *file_vty;
+ int ret = 0;
+
+ snprintf(path, pathlen, "/tmp/frr.tmp.XXXXXXXX");
+ fd = mkstemp(path);
+ if (fd < 0) {
+ flog_warn(EC_LIB_SYSTEM_CALL, "%s: mkstemp() failed: %s",
+ __func__, safe_strerror(errno));
+ return -1;
+ }
+
+ /* Make vty for configuration file. */
+ file_vty = vty_new();
+ file_vty->wfd = fd;
+ file_vty->type = VTY_FILE;
+ if (config)
+ ret = nb_cli_show_config(file_vty, config, format, translator,
+ false);
+ vty_close(file_vty);
+
+ return ret;
+}
+
+static int nb_cli_show_config_compare(struct vty *vty,
+ struct nb_config *config1,
+ struct nb_config *config2,
+ enum nb_cfg_format format,
+ struct yang_translator *translator)
+{
+ char config1_path[256];
+ char config2_path[256];
+ char command[BUFSIZ];
+ FILE *fp;
+ char line[1024];
+ int lineno = 0;
+
+ if (nb_write_config(config1, format, translator, config1_path,
+ sizeof(config1_path))
+ != 0) {
+ vty_out(vty, "%% Failed to process configurations.\n\n");
+ return CMD_WARNING;
+ }
+ if (nb_write_config(config2, format, translator, config2_path,
+ sizeof(config2_path))
+ != 0) {
+ vty_out(vty, "%% Failed to process configurations.\n\n");
+ unlink(config1_path);
+ return CMD_WARNING;
+ }
+
+ snprintf(command, sizeof(command), "diff -u %s %s", config1_path,
+ config2_path);
+ fp = popen(command, "r");
+ if (!fp) {
+ vty_out(vty, "%% Failed to generate configuration diff.\n\n");
+ unlink(config1_path);
+ unlink(config2_path);
+ return CMD_WARNING;
+ }
+ /* Print diff line by line. */
+ while (fgets(line, sizeof(line), fp) != NULL) {
+ if (lineno++ < 2)
+ continue;
+ vty_out(vty, "%s", line);
+ }
+ pclose(fp);
+
+ unlink(config1_path);
+ unlink(config2_path);
+
+ return CMD_SUCCESS;
+}
+
+/* Configure exclusively from this terminal. */
+DEFUN (config_exclusive,
+ config_exclusive_cmd,
+ "configure exclusive",
+ "Configuration from vty interface\n"
+ "Configure exclusively from this terminal\n")
+{
+ if (vty_config_exclusive_lock(vty))
+ vty->node = CONFIG_NODE;
+ else {
+ vty_out(vty, "VTY configuration is locked by other VTY\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ vty->private_config = true;
+ vty->candidate_config = nb_config_dup(running_config);
+ vty->candidate_config_base = nb_config_dup(running_config);
+ vty_out(vty,
+ "Warning: uncommitted changes will be discarded on exit.\n\n");
+
+ return CMD_SUCCESS;
+}
+
+/* Configure using a private candidate configuration. */
+DEFUN (config_private,
+ config_private_cmd,
+ "configure private",
+ "Configuration from vty interface\n"
+ "Configure using a private candidate configuration\n")
+{
+ if (vty_config_lock(vty))
+ vty->node = CONFIG_NODE;
+ else {
+ vty_out(vty, "VTY configuration is locked by other VTY\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ vty->private_config = true;
+ vty->candidate_config = nb_config_dup(running_config);
+ vty->candidate_config_base = nb_config_dup(running_config);
+ vty_out(vty,
+ "Warning: uncommitted changes will be discarded on exit.\n\n");
+
+ return CMD_SUCCESS;
+}
+
+DEFPY (config_commit,
+ config_commit_cmd,
+ "commit [force$force]",
+ "Commit changes into the running configuration\n"
+ "Force commit even if the candidate is outdated\n")
+{
+ return nb_cli_commit(vty, !!force, NULL);
+}
+
+DEFPY (config_commit_comment,
+ config_commit_comment_cmd,
+ "commit [force$force] comment LINE...",
+ "Commit changes into the running configuration\n"
+ "Force commit even if the candidate is outdated\n"
+ "Assign a comment to this commit\n"
+ "Comment for this commit (Max 80 characters)\n")
+{
+ char *comment;
+ int idx = 0;
+ int ret;
+
+ argv_find(argv, argc, "LINE", &idx);
+ comment = argv_concat(argv, argc, idx);
+ ret = nb_cli_commit(vty, !!force, comment);
+ XFREE(MTYPE_TMP, comment);
+
+ return ret;
+}
+
+DEFPY (config_commit_check,
+ config_commit_check_cmd,
+ "commit check",
+ "Commit changes into the running configuration\n"
+ "Check if the configuration changes are valid\n")
+{
+ int ret;
+
+ ret = nb_candidate_validate(vty->candidate_config);
+ if (ret != NB_OK) {
+ vty_out(vty,
+ "%% Failed to validate candidate configuration.\n\n");
+ vty_show_libyang_errors(vty, ly_native_ctx);
+ return CMD_WARNING;
+ }
+
+ vty_out(vty, "%% Candidate configuration validated successfully.\n\n");
+
+ return CMD_SUCCESS;
+}
+
+DEFPY (config_update,
+ config_update_cmd,
+ "update",
+ "Update candidate configuration\n")
+{
+ if (!nb_candidate_needs_update(vty->candidate_config)) {
+ vty_out(vty, "%% Update is not necessary.\n\n");
+ return CMD_SUCCESS;
+ }
+
+ if (nb_candidate_update(vty->candidate_config) != NB_OK) {
+ vty_out(vty,
+ "%% Failed to update the candidate configuration.\n\n");
+ vty_out(vty, "Please check the logs for more details.\n");
+ return CMD_WARNING;
+ }
+
+ nb_config_replace(vty->candidate_config_base, running_config, true);
+
+ vty_out(vty, "%% Candidate configuration updated successfully.\n\n");
+
+ return CMD_SUCCESS;
+}
+
+DEFPY (config_discard,
+ config_discard_cmd,
+ "discard",
+ "Discard changes in the candidate configuration\n")
+{
+ nb_config_replace(vty->candidate_config, vty->candidate_config_base,
+ true);
+
+ return CMD_SUCCESS;
+}
+
+DEFPY (config_load,
+ config_load_cmd,
+ "configuration load\
+ <\
+ file [<json$json|xml$xml> [translate WORD$translator_family]] FILENAME$filename\
+ |transaction (1-4294967296)$tid\
+ >\
+ [replace$replace]",
+ "Configuration related settings\n"
+ "Load configuration into candidate\n"
+ "Load configuration file into candidate\n"
+ "Load configuration file in JSON format\n"
+ "Load configuration file in XML format\n"
+ "Translate configuration file\n"
+ "YANG module translator\n"
+ "Configuration file name (full path)\n"
+ "Load configuration from transaction into candidate\n"
+ "Transaction ID\n"
+ "Replace instead of merge\n")
+{
+ if (filename) {
+ enum nb_cfg_format format;
+ struct yang_translator *translator = NULL;
+
+ if (json)
+ format = NB_CFG_FMT_JSON;
+ else if (xml)
+ format = NB_CFG_FMT_XML;
+ else
+ format = NB_CFG_FMT_CMDS;
+
+ if (translator_family) {
+ translator = yang_translator_find(translator_family);
+ if (!translator) {
+ vty_out(vty,
+ "%% Module translator \"%s\" not found\n",
+ translator_family);
+ return CMD_WARNING;
+ }
+ }
+
+ return nb_cli_candidate_load_file(vty, format, translator,
+ filename, !!replace);
+ }
+
+ return nb_cli_candidate_load_transaction(vty, tid, !!replace);
+}
+
+DEFPY (show_config_running,
+ show_config_running_cmd,
+ "show configuration running\
+ [<json$json|xml$xml> [translate WORD$translator_family]]\
+ [with-defaults$with_defaults]",
+ SHOW_STR
+ "Configuration information\n"
+ "Running configuration\n"
+ "Change output format to JSON\n"
+ "Change output format to XML\n"
+ "Translate output\n"
+ "YANG module translator\n"
+ "Show default values\n")
+
+{
+ enum nb_cfg_format format;
+ struct yang_translator *translator = NULL;
+
+ if (json)
+ format = NB_CFG_FMT_JSON;
+ else if (xml)
+ format = NB_CFG_FMT_XML;
+ else
+ format = NB_CFG_FMT_CMDS;
+
+ if (translator_family) {
+ translator = yang_translator_find(translator_family);
+ if (!translator) {
+ vty_out(vty, "%% Module translator \"%s\" not found\n",
+ translator_family);
+ return CMD_WARNING;
+ }
+ }
+
+ nb_cli_show_config(vty, running_config, format, translator,
+ !!with_defaults);
+
+ return CMD_SUCCESS;
+}
+
+DEFPY (show_config_candidate,
+ show_config_candidate_cmd,
+ "show configuration candidate\
+ [<json$json|xml$xml> [translate WORD$translator_family]]\
+ [<\
+ with-defaults$with_defaults\
+ |changes$changes\
+ >]",
+ SHOW_STR
+ "Configuration information\n"
+ "Candidate configuration\n"
+ "Change output format to JSON\n"
+ "Change output format to XML\n"
+ "Translate output\n"
+ "YANG module translator\n"
+ "Show default values\n"
+ "Show changes applied in the candidate configuration\n")
+
+{
+ enum nb_cfg_format format;
+ struct yang_translator *translator = NULL;
+
+ if (json)
+ format = NB_CFG_FMT_JSON;
+ else if (xml)
+ format = NB_CFG_FMT_XML;
+ else
+ format = NB_CFG_FMT_CMDS;
+
+ if (translator_family) {
+ translator = yang_translator_find(translator_family);
+ if (!translator) {
+ vty_out(vty, "%% Module translator \"%s\" not found\n",
+ translator_family);
+ return CMD_WARNING;
+ }
+ }
+
+ if (changes)
+ return nb_cli_show_config_compare(
+ vty, vty->candidate_config_base, vty->candidate_config,
+ format, translator);
+
+ nb_cli_show_config(vty, vty->candidate_config, format, translator,
+ !!with_defaults);
+
+ return CMD_SUCCESS;
+}
+
+DEFPY (show_config_compare,
+ show_config_compare_cmd,
+ "show configuration compare\
+ <\
+ candidate$c1_candidate\
+ |running$c1_running\
+ |transaction (1-4294967296)$c1_tid\
+ >\
+ <\
+ candidate$c2_candidate\
+ |running$c2_running\
+ |transaction (1-4294967296)$c2_tid\
+ >\
+ [<json$json|xml$xml> [translate WORD$translator_family]]",
+ SHOW_STR
+ "Configuration information\n"
+ "Compare two different configurations\n"
+ "Candidate configuration\n"
+ "Running configuration\n"
+ "Configuration transaction\n"
+ "Transaction ID\n"
+ "Candidate configuration\n"
+ "Running configuration\n"
+ "Configuration transaction\n"
+ "Transaction ID\n"
+ "Change output format to JSON\n"
+ "Change output format to XML\n"
+ "Translate output\n"
+ "YANG module translator\n")
+{
+ enum nb_cfg_format format;
+ struct yang_translator *translator = NULL;
+ struct nb_config *config1, *config_transaction1 = NULL;
+ struct nb_config *config2, *config_transaction2 = NULL;
+ int ret = CMD_WARNING;
+
+ if (c1_candidate)
+ config1 = vty->candidate_config;
+ else if (c1_running)
+ config1 = running_config;
+ else {
+ config_transaction1 = nb_db_transaction_load(c1_tid);
+ if (!config_transaction1) {
+ vty_out(vty, "%% Transaction %u does not exist\n\n",
+ (unsigned int)c1_tid);
+ goto exit;
+ }
+ config1 = config_transaction1;
+ }
+
+ if (c2_candidate)
+ config2 = vty->candidate_config;
+ else if (c2_running)
+ config2 = running_config;
+ else {
+ config_transaction2 = nb_db_transaction_load(c2_tid);
+ if (!config_transaction2) {
+ vty_out(vty, "%% Transaction %u does not exist\n\n",
+ (unsigned int)c2_tid);
+ goto exit;
+ }
+ config2 = config_transaction2;
+ }
+
+ if (json)
+ format = NB_CFG_FMT_JSON;
+ else if (xml)
+ format = NB_CFG_FMT_XML;
+ else
+ format = NB_CFG_FMT_CMDS;
+
+ if (translator_family) {
+ translator = yang_translator_find(translator_family);
+ if (!translator) {
+ vty_out(vty, "%% Module translator \"%s\" not found\n",
+ translator_family);
+ goto exit;
+ }
+ }
+
+ ret = nb_cli_show_config_compare(vty, config1, config2, format,
+ translator);
+exit:
+ if (config_transaction1)
+ nb_config_free(config_transaction1);
+ if (config_transaction2)
+ nb_config_free(config_transaction2);
+
+ return ret;
+}
+
+/*
+ * Stripped down version of the "show configuration compare" command.
+ * The "candidate" option is not present so the command can be installed in
+ * the enable node.
+ */
+ALIAS (show_config_compare,
+ show_config_compare_without_candidate_cmd,
+ "show configuration compare\
+ <\
+ running$c1_running\
+ |transaction (1-4294967296)$c1_tid\
+ >\
+ <\
+ running$c2_running\
+ |transaction (1-4294967296)$c2_tid\
+ >\
+ [<json$json|xml$xml> [translate WORD$translator_family]]",
+ SHOW_STR
+ "Configuration information\n"
+ "Compare two different configurations\n"
+ "Running configuration\n"
+ "Configuration transaction\n"
+ "Transaction ID\n"
+ "Running configuration\n"
+ "Configuration transaction\n"
+ "Transaction ID\n"
+ "Change output format to JSON\n"
+ "Change output format to XML\n"
+ "Translate output\n"
+ "YANG module translator\n")
+
+DEFPY (clear_config_transactions,
+ clear_config_transactions_cmd,
+ "clear configuration transactions oldest (1-100)$n",
+ CLEAR_STR
+ "Configuration activity\n"
+ "Delete transactions from the transactions log\n"
+ "Delete oldest <n> transactions\n"
+ "Number of transactions to delete\n")
+{
+#ifdef HAVE_CONFIG_ROLLBACKS
+ if (nb_db_clear_transactions(n) != NB_OK) {
+ vty_out(vty, "%% Failed to delete transactions.\n\n");
+ return CMD_WARNING;
+ }
+#else
+ vty_out(vty,
+ "%% FRR was compiled without --enable-config-rollbacks.\n\n");
+#endif /* HAVE_CONFIG_ROLLBACKS */
+
+ return CMD_SUCCESS;
+}
+
+DEFPY (config_database_max_transactions,
+ config_database_max_transactions_cmd,
+ "configuration database max-transactions (1-100)$max",
+ "Configuration related settings\n"
+ "Configuration database\n"
+ "Set the maximum number of transactions to store\n"
+ "Number of transactions\n")
+{
+#ifdef HAVE_CONFIG_ROLLBACKS
+ if (nb_db_set_max_transactions(max) != NB_OK) {
+ vty_out(vty,
+ "%% Failed to update the maximum number of transactions.\n\n");
+ return CMD_WARNING;
+ }
+ vty_out(vty,
+ "%% Maximum number of transactions updated successfully.\n\n");
+#else
+ vty_out(vty,
+ "%% FRR was compiled without --enable-config-rollbacks.\n\n");
+#endif /* HAVE_CONFIG_ROLLBACKS */
+
+ return CMD_SUCCESS;
+}
+
+DEFPY (yang_module_translator_load,
+ yang_module_translator_load_cmd,
+ "yang module-translator load FILENAME$filename",
+ "YANG related settings\n"
+ "YANG module translator\n"
+ "Load YANG module translator\n"
+ "File name (full path)\n")
+{
+ struct yang_translator *translator;
+
+ translator = yang_translator_load(filename);
+ if (!translator) {
+ vty_out(vty, "%% Failed to load \"%s\"\n\n", filename);
+ vty_out(vty, "Please check the logs for more details.\n");
+ return CMD_WARNING;
+ }
+
+ vty_out(vty, "%% Module translator \"%s\" loaded successfully.\n\n",
+ translator->family);
+
+ return CMD_SUCCESS;
+}
+
+DEFPY (yang_module_translator_unload_family,
+ yang_module_translator_unload_cmd,
+ "yang module-translator unload WORD$translator_family",
+ "YANG related settings\n"
+ "YANG module translator\n"
+ "Unload YANG module translator\n"
+ "Name of the module translator\n")
+{
+ struct yang_translator *translator;
+
+ translator = yang_translator_find(translator_family);
+ if (!translator) {
+ vty_out(vty, "%% Module translator \"%s\" not found\n",
+ translator_family);
+ return CMD_WARNING;
+ }
+
+ yang_translator_unload(translator);
+
+ return CMD_SUCCESS;
+}
+
+#ifdef HAVE_CONFIG_ROLLBACKS
+static void nb_cli_show_transactions_cb(void *arg, int transaction_id,
+ const char *client_name,
+ const char *date, const char *comment)
+{
+ struct ttable *tt = arg;
+
+ ttable_add_row(tt, "%d|%s|%s|%s", transaction_id, client_name, date,
+ comment);
+}
+
+static int nb_cli_show_transactions(struct vty *vty)
+{
+ struct ttable *tt;
+
+ /* Prepare table. */
+ tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
+ ttable_add_row(tt, "Transaction ID|Client|Date|Comment");
+ tt->style.cell.rpad = 2;
+ tt->style.corner = '+';
+ ttable_restyle(tt);
+ ttable_rowseps(tt, 0, BOTTOM, true, '-');
+
+ /* Fetch transactions from the northbound database. */
+ if (nb_db_transactions_iterate(nb_cli_show_transactions_cb, tt)
+ != NB_OK) {
+ vty_out(vty,
+ "%% Failed to fetch configuration transactions.\n");
+ return CMD_WARNING;
+ }
+
+ /* Dump the generated table. */
+ if (tt->nrows > 1) {
+ char *table;
+
+ table = ttable_dump(tt, "\n");
+ vty_out(vty, "%s\n", table);
+ XFREE(MTYPE_TMP, table);
+ } else
+ vty_out(vty, "No configuration transactions to display.\n\n");
+
+ ttable_del(tt);
+
+ return CMD_SUCCESS;
+}
+#endif /* HAVE_CONFIG_ROLLBACKS */
+
+DEFPY (show_config_transaction,
+ show_config_transaction_cmd,
+ "show configuration transaction\
+ [\
+ (1-4294967296)$transaction_id\
+ [<json$json|xml$xml> [translate WORD$translator_family]]\
+ [<\
+ with-defaults$with_defaults\
+ |changes$changes\
+ >]\
+ ]",
+ SHOW_STR
+ "Configuration information\n"
+ "Configuration transaction\n"
+ "Transaction ID\n"
+ "Change output format to JSON\n"
+ "Change output format to XML\n"
+ "Translate output\n"
+ "YANG module translator\n"
+ "Show default values\n"
+ "Show changes compared to the previous transaction\n")
+{
+#ifdef HAVE_CONFIG_ROLLBACKS
+ if (transaction_id) {
+ struct nb_config *config;
+ enum nb_cfg_format format;
+ struct yang_translator *translator = NULL;
+
+ if (json)
+ format = NB_CFG_FMT_JSON;
+ else if (xml)
+ format = NB_CFG_FMT_XML;
+ else
+ format = NB_CFG_FMT_CMDS;
+
+ if (translator_family) {
+ translator = yang_translator_find(translator_family);
+ if (!translator) {
+ vty_out(vty,
+ "%% Module translator \"%s\" not found\n",
+ translator_family);
+ return CMD_WARNING;
+ }
+ }
+
+ config = nb_db_transaction_load(transaction_id);
+ if (!config) {
+ vty_out(vty, "%% Transaction %u does not exist.\n\n",
+ (unsigned int)transaction_id);
+ return CMD_WARNING;
+ }
+
+ if (changes) {
+ struct nb_config *prev_config;
+ int ret;
+
+ /* NOTE: this can be NULL. */
+ prev_config =
+ nb_db_transaction_load(transaction_id - 1);
+
+ ret = nb_cli_show_config_compare(
+ vty, prev_config, config, format, translator);
+ if (prev_config)
+ nb_config_free(prev_config);
+ nb_config_free(config);
+
+ return ret;
+ }
+
+ nb_cli_show_config(vty, config, format, translator,
+ !!with_defaults);
+ nb_config_free(config);
+
+ return CMD_SUCCESS;
+ }
+
+ return nb_cli_show_transactions(vty);
+#else
+ vty_out(vty,
+ "%% FRR was compiled without --enable-config-rollbacks.\n\n");
+ return CMD_WARNING;
+#endif /* HAVE_CONFIG_ROLLBACKS */
+}
+
+DEFPY (show_yang_module,
+ show_yang_module_cmd,
+ "show yang module [module-translator WORD$translator_family]",
+ SHOW_STR
+ "YANG information\n"
+ "Show loaded modules\n"
+ "YANG module translator\n"
+ "YANG module translator\n")
+{
+ struct ly_ctx *ly_ctx;
+ struct yang_translator *translator = NULL;
+ const struct lys_module *module;
+ struct ttable *tt;
+ uint32_t idx = 0;
+
+ if (translator_family) {
+ translator = yang_translator_find(translator_family);
+ if (!translator) {
+ vty_out(vty, "%% Module translator \"%s\" not found\n",
+ translator_family);
+ return CMD_WARNING;
+ }
+ ly_ctx = translator->ly_ctx;
+ } else
+ ly_ctx = ly_native_ctx;
+
+ /* Prepare table. */
+ tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
+ ttable_add_row(tt, "Module|Version|Revision|Flags|Namespace");
+ tt->style.cell.rpad = 2;
+ tt->style.corner = '+';
+ ttable_restyle(tt);
+ ttable_rowseps(tt, 0, BOTTOM, true, '-');
+
+ while ((module = ly_ctx_get_module_iter(ly_ctx, &idx))) {
+ char flags[8];
+
+ snprintf(flags, sizeof(flags), "%c%c",
+ module->implemented ? 'I' : ' ',
+ (module->deviated == 1) ? 'D' : ' ');
+
+ ttable_add_row(tt, "%s|%s|%s|%s|%s", module->name,
+ (module->version == 2) ? "1.1" : "1.0",
+ (module->rev_size > 0) ? module->rev[0].date
+ : "-",
+ flags, module->ns);
+ }
+
+ /* Dump the generated table. */
+ if (tt->nrows > 1) {
+ char *table;
+
+ vty_out(vty, " Flags: I - Implemented, D - Deviated\n\n");
+
+ table = ttable_dump(tt, "\n");
+ vty_out(vty, "%s\n", table);
+ XFREE(MTYPE_TMP, table);
+ } else
+ vty_out(vty, "No YANG modules to display.\n\n");
+
+ ttable_del(tt);
+
+ return CMD_SUCCESS;
+}
+
+DEFPY (show_yang_module_detail,
+ show_yang_module_detail_cmd,
+ "show yang module\
+ [module-translator WORD$translator_family]\
+ WORD$module_name <summary|tree$tree|yang$yang|yin$yin>",
+ SHOW_STR
+ "YANG information\n"
+ "Show loaded modules\n"
+ "YANG module translator\n"
+ "YANG module translator\n"
+ "Module name\n"
+ "Display summary information about the module\n"
+ "Display module in the tree (RFC 8340) format\n"
+ "Display module in the YANG format\n"
+ "Display module in the YIN format\n")
+{
+ struct ly_ctx *ly_ctx;
+ struct yang_translator *translator = NULL;
+ const struct lys_module *module;
+ LYS_OUTFORMAT format;
+ char *strp;
+
+ if (translator_family) {
+ translator = yang_translator_find(translator_family);
+ if (!translator) {
+ vty_out(vty, "%% Module translator \"%s\" not found\n",
+ translator_family);
+ return CMD_WARNING;
+ }
+ ly_ctx = translator->ly_ctx;
+ } else
+ ly_ctx = ly_native_ctx;
+
+ module = ly_ctx_get_module(ly_ctx, module_name, NULL, 0);
+ if (!module) {
+ vty_out(vty, "%% Module \"%s\" not found\n", module_name);
+ return CMD_WARNING;
+ }
+
+ if (yang)
+ format = LYS_OUT_YANG;
+ else if (yin)
+ format = LYS_OUT_YIN;
+ else if (tree)
+ format = LYS_OUT_TREE;
+ else
+ format = LYS_OUT_INFO;
+
+ if (lys_print_mem(&strp, module, format, NULL, 0, 0) == 0) {
+ vty_out(vty, "%s\n", strp);
+ free(strp);
+ } else {
+ /* Unexpected. */
+ vty_out(vty, "%% Error generating module information\n");
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFPY (show_yang_module_translator,
+ show_yang_module_translator_cmd,
+ "show yang module-translator",
+ SHOW_STR
+ "YANG information\n"
+ "Show loaded YANG module translators\n")
+{
+ struct yang_translator *translator;
+ struct ttable *tt;
+
+ /* Prepare table. */
+ tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
+ ttable_add_row(tt, "Family|Module|Deviations|Coverage (%%)");
+ tt->style.cell.rpad = 2;
+ tt->style.corner = '+';
+ ttable_restyle(tt);
+ ttable_rowseps(tt, 0, BOTTOM, true, '-');
+
+ RB_FOREACH (translator, yang_translators, &yang_translators) {
+ struct yang_tmodule *tmodule;
+ struct listnode *ln;
+
+ for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) {
+ ttable_add_row(tt, "%s|%s|%s|%.2f", translator->family,
+ tmodule->module->name,
+ tmodule->deviations->name,
+ tmodule->coverage);
+ }
+ }
+
+ /* Dump the generated table. */
+ if (tt->nrows > 1) {
+ char *table;
+
+ table = ttable_dump(tt, "\n");
+ vty_out(vty, "%s\n", table);
+ XFREE(MTYPE_TMP, table);
+ } else
+ vty_out(vty, "No YANG module translators to display.\n\n");
+
+ ttable_del(tt);
+
+ return CMD_SUCCESS;
+}
+
+#ifdef HAVE_CONFIG_ROLLBACKS
+static int nb_cli_rollback_configuration(struct vty *vty,
+ uint32_t transaction_id)
+{
+ struct nb_config *candidate;
+ char comment[80];
+ int ret;
+
+ candidate = nb_db_transaction_load(transaction_id);
+ if (!candidate) {
+ vty_out(vty, "%% Transaction %u does not exist.\n\n",
+ transaction_id);
+ return CMD_WARNING;
+ }
+
+ snprintf(comment, sizeof(comment), "Rollback to transaction %u",
+ transaction_id);
+
+ ret = nb_candidate_commit(candidate, NB_CLIENT_CLI, true, comment,
+ NULL);
+ nb_config_free(candidate);
+ switch (ret) {
+ case NB_OK:
+ vty_out(vty,
+ "%% Configuration was successfully rolled back.\n\n");
+ return CMD_SUCCESS;
+ case NB_ERR_NO_CHANGES:
+ vty_out(vty,
+ "%% Aborting - no configuration changes detected.\n\n");
+ return CMD_WARNING;
+ default:
+ vty_out(vty, "%% Rollback failed.\n\n");
+ vty_out(vty, "Please check the logs for more details.\n");
+ return CMD_WARNING;
+ }
+}
+#endif /* HAVE_CONFIG_ROLLBACKS */
+
+DEFPY (rollback_config,
+ rollback_config_cmd,
+ "rollback configuration (1-4294967296)$transaction_id",
+ "Rollback to a previous state\n"
+ "Running configuration\n"
+ "Transaction ID\n")
+{
+#ifdef HAVE_CONFIG_ROLLBACKS
+ return nb_cli_rollback_configuration(vty, transaction_id);
+#else
+ vty_out(vty,
+ "%% FRR was compiled without --enable-config-rollbacks.\n\n");
+ return CMD_SUCCESS;
+#endif /* HAVE_CONFIG_ROLLBACKS */
+}
+
+/* Debug CLI commands. */
+DEFUN (debug_nb,
+ debug_nb_cmd,
+ "debug northbound",
+ DEBUG_STR
+ "Northbound Debugging\n")
+{
+ debug_northbound = 1;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_nb,
+ no_debug_nb_cmd,
+ "no debug northbound",
+ NO_STR DEBUG_STR
+ "Northbound Debugging\n")
+{
+ debug_northbound = 0;
+
+ return CMD_SUCCESS;
+}
+
+static int nb_debug_config_write(struct vty *vty)
+{
+ if (debug_northbound)
+ vty_out(vty, "debug northbound\n");
+
+ return 1;
+}
+
+static struct cmd_node nb_debug_node = {NORTHBOUND_DEBUG_NODE, "", 1};
+
+void nb_cli_install_default(int node)
+{
+ if (frr_get_cli_mode() != FRR_CLI_TRANSACTIONAL)
+ return;
+
+ install_element(node, &config_commit_cmd);
+ install_element(node, &config_commit_comment_cmd);
+ install_element(node, &config_commit_check_cmd);
+ install_element(node, &config_update_cmd);
+ install_element(node, &config_discard_cmd);
+ install_element(node, &show_config_running_cmd);
+ install_element(node, &show_config_candidate_cmd);
+ install_element(node, &show_config_compare_cmd);
+ install_element(node, &show_config_transaction_cmd);
+}
+
+/* YANG module autocomplete. */
+static void yang_module_autocomplete(vector comps, struct cmd_token *token)
+{
+ const struct lys_module *module;
+ struct yang_translator *module_tr;
+ uint32_t idx;
+
+ idx = 0;
+ while ((module = ly_ctx_get_module_iter(ly_native_ctx, &idx)))
+ vector_set(comps, XSTRDUP(MTYPE_COMPLETION, module->name));
+
+ RB_FOREACH (module_tr, yang_translators, &yang_translators) {
+ idx = 0;
+ while ((module = ly_ctx_get_module_iter(module_tr->ly_ctx,
+ &idx)))
+ vector_set(comps,
+ XSTRDUP(MTYPE_COMPLETION, module->name));
+ }
+}
+
+/* YANG module translator autocomplete. */
+static void yang_translator_autocomplete(vector comps, struct cmd_token *token)
+{
+ struct yang_translator *module_tr;
+
+ RB_FOREACH (module_tr, yang_translators, &yang_translators)
+ vector_set(comps, XSTRDUP(MTYPE_COMPLETION, module_tr->family));
+}
+
+static const struct cmd_variable_handler yang_var_handlers[] = {
+ {.varname = "module_name", .completions = yang_module_autocomplete},
+ {.varname = "translator_family",
+ .completions = yang_translator_autocomplete},
+ {.completions = NULL}};
+
+void nb_cli_init(void)
+{
+ /* Initialize the shared candidate configuration. */
+ vty_shared_candidate_config = nb_config_new(NULL);
+
+ /* Install debug commands */
+ install_node(&nb_debug_node, nb_debug_config_write);
+ install_element(ENABLE_NODE, &debug_nb_cmd);
+ install_element(ENABLE_NODE, &no_debug_nb_cmd);
+ install_element(CONFIG_NODE, &debug_nb_cmd);
+ install_element(CONFIG_NODE, &no_debug_nb_cmd);
+
+ /* Install commands specific to the transaction-base mode. */
+ if (frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL) {
+ install_element(ENABLE_NODE, &config_exclusive_cmd);
+ install_element(ENABLE_NODE, &config_private_cmd);
+ install_element(ENABLE_NODE, &show_config_running_cmd);
+ install_element(ENABLE_NODE,
+ &show_config_compare_without_candidate_cmd);
+ install_element(ENABLE_NODE, &show_config_transaction_cmd);
+ install_element(ENABLE_NODE, &rollback_config_cmd);
+ install_element(ENABLE_NODE, &clear_config_transactions_cmd);
+
+ install_element(CONFIG_NODE, &config_load_cmd);
+ install_element(CONFIG_NODE,
+ &config_database_max_transactions_cmd);
+ }
+
+ /* Other commands. */
+ install_element(CONFIG_NODE, &yang_module_translator_load_cmd);
+ install_element(CONFIG_NODE, &yang_module_translator_unload_cmd);
+ install_element(ENABLE_NODE, &show_yang_module_cmd);
+ install_element(ENABLE_NODE, &show_yang_module_detail_cmd);
+ install_element(ENABLE_NODE, &show_yang_module_translator_cmd);
+ cmd_variable_handler_register(yang_var_handlers);
+}
+
+void nb_cli_terminate(void)
+{
+ nb_config_free(vty_shared_candidate_config);
+}
--- /dev/null
+/*
+ * Copyright (C) 2018 NetDEF, Inc.
+ * Renato Westphal
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _FRR_NORTHBOUND_CLI_H_
+#define _FRR_NORTHBOUND_CLI_H_
+
+#include "northbound.h"
+
+struct cli_config_change {
+ /*
+ * XPath (absolute or relative) of the configuration option being
+ * edited.
+ */
+ char xpath[XPATH_MAXLEN];
+
+ /*
+ * Operation to apply (either NB_OP_CREATE, NB_OP_MODIFY or
+ * NB_OP_DELETE).
+ */
+ enum nb_operation operation;
+
+ /*
+ * New value of the configuration option. Should be NULL for typeless
+ * YANG data (e.g. presence-containers). For convenience, NULL can also
+ * be used to restore a leaf to its default value.
+ */
+ const char *value;
+};
+
+/* Possible formats in which a configuration can be displayed. */
+enum nb_cfg_format {
+ NB_CFG_FMT_CMDS = 0,
+ NB_CFG_FMT_JSON,
+ NB_CFG_FMT_XML,
+};
+
+extern struct nb_config *vty_shared_candidate_config;
+
+/* Prototypes. */
+extern int nb_cli_cfg_change(struct vty *vty, char *xpath_list,
+ struct cli_config_change changes[], size_t size);
+extern int nb_cli_rpc(const char *xpath, struct list *input,
+ struct list *output);
+extern void nb_cli_show_dnode_cmds(struct vty *vty, struct lyd_node *dnode,
+ bool show_defaults);
+extern void nb_cli_install_default(int node);
+extern void nb_cli_init(void);
+extern void nb_cli_terminate(void);
+
+#endif /* _FRR_NORTHBOUND_CLI_H_ */
--- /dev/null
+/*
+ * Copyright (C) 2018 NetDEF, Inc.
+ * Renato Westphal
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <zebra.h>
+
+#include "libfrr.h"
+#include "log.h"
+#include "lib_errors.h"
+#include "command.h"
+#include "db.h"
+#include "northbound.h"
+#include "northbound_db.h"
+
+int nb_db_init(void)
+{
+#ifdef HAVE_CONFIG_ROLLBACKS
+ /*
+ * NOTE: the delete_tail SQL trigger is used to implement a ring buffer
+ * where only the last N transactions are recorded in the configuration
+ * log.
+ */
+ if (db_execute(
+ "BEGIN TRANSACTION;\n"
+ " CREATE TABLE IF NOT EXISTS transactions(\n"
+ " client CHAR(32) NOT NULL,\n"
+ " date DATETIME DEFAULT CURRENT_TIMESTAMP,\n"
+ " comment CHAR(80) ,\n"
+ " configuration TEXT NOT NULL\n"
+ " );\n"
+ " CREATE TRIGGER IF NOT EXISTS delete_tail\n"
+ " AFTER INSERT ON transactions\n"
+ " FOR EACH ROW\n"
+ " BEGIN\n"
+ " DELETE\n"
+ " FROM\n"
+ " transactions\n"
+ " WHERE\n"
+ " rowid%%%u=NEW.rowid%%%u AND rowid!=NEW.rowid;\n"
+ " END;\n"
+ "COMMIT;",
+ NB_DLFT_MAX_CONFIG_ROLLBACKS, NB_DLFT_MAX_CONFIG_ROLLBACKS)
+ != 0)
+ return NB_ERR;
+#endif /* HAVE_CONFIG_ROLLBACKS */
+
+ return NB_OK;
+}
+
+int nb_db_transaction_save(const struct nb_transaction *transaction,
+ uint32_t *transaction_id)
+{
+#ifdef HAVE_CONFIG_ROLLBACKS
+ struct sqlite3_stmt *ss;
+ const char *client_name;
+ char *config_str = NULL;
+ int ret = NB_ERR;
+
+ /*
+ * Use a transaction to ensure consistency between the INSERT and SELECT
+ * queries.
+ */
+ if (db_execute("BEGIN TRANSACTION;") != 0)
+ return NB_ERR;
+
+ ss = db_prepare(
+ "INSERT INTO transactions\n"
+ " (client, comment, configuration)\n"
+ "VALUES\n"
+ " (?, ?, ?);");
+ if (!ss)
+ goto exit;
+
+ client_name = nb_client_name(transaction->client);
+ /* Always record configurations in the XML format. */
+ if (lyd_print_mem(&config_str, transaction->config->dnode, LYD_XML,
+ LYP_FORMAT | LYP_WITHSIBLINGS)
+ != 0)
+ goto exit;
+
+ if (db_bindf(ss, "%s%s%s", client_name, strlen(client_name),
+ transaction->comment, strlen(transaction->comment),
+ config_str ? config_str : "",
+ config_str ? strlen(config_str) : 0)
+ != 0)
+ goto exit;
+
+ if (db_run(ss) != SQLITE_OK)
+ goto exit;
+
+ db_finalize(&ss);
+
+ /*
+ * transaction_id is an optional output parameter that provides the ID
+ * of the recorded transaction.
+ */
+ if (transaction_id) {
+ ss = db_prepare("SELECT last_insert_rowid();");
+ if (!ss)
+ goto exit;
+
+ if (db_run(ss) != SQLITE_ROW)
+ goto exit;
+
+ if (db_loadf(ss, "%i", transaction_id) != 0)
+ goto exit;
+
+ db_finalize(&ss);
+ }
+
+ if (db_execute("COMMIT;") != 0)
+ goto exit;
+
+ ret = NB_OK;
+
+exit:
+ if (config_str)
+ free(config_str);
+ if (ss)
+ db_finalize(&ss);
+ if (ret != NB_OK)
+ (void)db_execute("ROLLBACK TRANSACTION;");
+
+ return ret;
+#else /* HAVE_CONFIG_ROLLBACKS */
+ return NB_OK;
+#endif /* HAVE_CONFIG_ROLLBACKS */
+}
+
+struct nb_config *nb_db_transaction_load(uint32_t transaction_id)
+{
+ struct nb_config *config = NULL;
+#ifdef HAVE_CONFIG_ROLLBACKS
+ struct lyd_node *dnode;
+ const char *config_str;
+ struct sqlite3_stmt *ss;
+
+ ss = db_prepare(
+ "SELECT\n"
+ " configuration\n"
+ "FROM\n"
+ " transactions\n"
+ "WHERE\n"
+ " rowid=?;");
+ if (!ss)
+ return NULL;
+
+ if (db_bindf(ss, "%d", transaction_id) != 0)
+ goto exit;
+
+ if (db_run(ss) != SQLITE_ROW)
+ goto exit;
+
+ if (db_loadf(ss, "%s", &config_str) != 0)
+ goto exit;
+
+ dnode = lyd_parse_mem(ly_native_ctx, config_str, LYD_XML,
+ LYD_OPT_CONFIG);
+ if (!dnode)
+ flog_warn(EC_LIB_LIBYANG, "%s: lyd_parse_mem() failed",
+ __func__);
+ else
+ config = nb_config_new(dnode);
+
+exit:
+ db_finalize(&ss);
+#endif /* HAVE_CONFIG_ROLLBACKS */
+
+ return config;
+}
+
+int nb_db_clear_transactions(unsigned int n_oldest)
+{
+#ifdef HAVE_CONFIG_ROLLBACKS
+ /* Delete oldest N entries. */
+ if (db_execute("DELETE\n"
+ "FROM\n"
+ " transactions\n"
+ "WHERE\n"
+ " ROWID IN (\n"
+ " SELECT\n"
+ " ROWID\n"
+ " FROM\n"
+ " transactions\n"
+ " ORDER BY ROWID ASC LIMIT %u\n"
+ " );",
+ n_oldest)
+ != 0)
+ return NB_ERR;
+#endif /* HAVE_CONFIG_ROLLBACKS */
+
+ return NB_OK;
+}
+
+int nb_db_set_max_transactions(unsigned int max)
+{
+#ifdef HAVE_CONFIG_ROLLBACKS
+ /*
+ * Delete old entries if necessary and update the SQL trigger that
+ * auto-deletes old entries.
+ */
+ if (db_execute("BEGIN TRANSACTION;\n"
+ " DELETE\n"
+ " FROM\n"
+ " transactions\n"
+ " WHERE\n"
+ " ROWID IN (\n"
+ " SELECT\n"
+ " ROWID\n"
+ " FROM\n"
+ " transactions\n"
+ " ORDER BY ROWID DESC LIMIT -1 OFFSET %u\n"
+ " );\n"
+ " DROP TRIGGER delete_tail;\n"
+ " CREATE TRIGGER delete_tail\n"
+ " AFTER INSERT ON transactions\n"
+ " FOR EACH ROW\n"
+ " BEGIN\n"
+ " DELETE\n"
+ " FROM\n"
+ " transactions\n"
+ " WHERE\n"
+ " rowid%%%u=NEW.rowid%%%u AND rowid!=NEW.rowid;\n"
+ " END;\n"
+ "COMMIT;",
+ max, max, max)
+ != 0)
+ return NB_ERR;
+#endif /* HAVE_CONFIG_ROLLBACKS */
+
+ return NB_OK;
+}
+
+int nb_db_transactions_iterate(void (*func)(void *arg, int transaction_id,
+ const char *client_name,
+ const char *date,
+ const char *comment),
+ void *arg)
+{
+#ifdef HAVE_CONFIG_ROLLBACKS
+ struct sqlite3_stmt *ss;
+
+ /* Send SQL query and parse the result. */
+ ss = db_prepare(
+ "SELECT\n"
+ " rowid, client, date, comment\n"
+ "FROM\n"
+ " transactions\n"
+ "ORDER BY\n"
+ " rowid DESC;");
+ if (!ss)
+ return NB_ERR;
+
+ while (db_run(ss) == SQLITE_ROW) {
+ int transaction_id;
+ const char *client_name;
+ const char *date;
+ const char *comment;
+ int ret;
+
+ ret = db_loadf(ss, "%i%s%s%s", &transaction_id, &client_name,
+ &date, &comment);
+ if (ret != 0)
+ continue;
+
+ (*func)(arg, transaction_id, client_name, date, comment);
+ }
+
+ db_finalize(&ss);
+#endif /* HAVE_CONFIG_ROLLBACKS */
+
+ return NB_OK;
+}
--- /dev/null
+/*
+ * Copyright (C) 2018 NetDEF, Inc.
+ * Renato Westphal
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _FRR_NORTHBOUND_DB_H_
+#define _FRR_NORTHBOUND_DB_H_
+
+#include "northbound.h"
+
+/*
+ * Initialize the northbound database.
+ *
+ * Currently the database is used only for storing and retrieving configuration
+ * transactions.
+ *
+ * Returns:
+ * NB_OK on success, NB_ERR otherwise.
+ */
+int nb_db_init(void);
+
+/*
+ * Save a configuration transaction in the northbound database.
+ *
+ * transaction
+ * Configuration transaction to be saved.
+ *
+ * transaction_id
+ * Output parameter providing the ID of the saved transaction.
+ *
+ * Returns:
+ * NB_OK on success, NB_ERR otherwise.
+ */
+int nb_db_transaction_save(const struct nb_transaction *transaction,
+ uint32_t *transaction_id);
+
+/*
+ * Load a configuration transaction from the transactions log.
+ *
+ * transaction_id
+ * ID of the transaction to be loaded.
+ *
+ * Returns:
+ * Pointer to newly created configuration or NULL in the case of an error.
+ */
+extern struct nb_config *nb_db_transaction_load(uint32_t transaction_id);
+
+/*
+ * Delete the specified number of transactions from the transactions log.
+ *
+ * n_oldest
+ * Number of transactions to delete.
+ *
+ * Returns:
+ * NB_OK on success, NB_ERR otherwise.
+ */
+extern int nb_db_clear_transactions(unsigned int n_oldest);
+
+/*
+ * Specify the maximum number of transactions we want to record in the
+ * transactions log. Note that older transactions can be removed during this
+ * operation.
+ *
+ * max
+ * New upper limit of maximum transactions to log.
+ *
+ * Returns:
+ * NB_OK on success, NB_ERR otherwise.
+ */
+extern int nb_db_set_max_transactions(unsigned int max);
+
+/*
+ * Iterate over all configuration transactions stored in the northbound
+ * database, sorted in descending order.
+ *
+ * func
+ * Function to call with each configuration transaction.
+ *
+ * arg
+ * Arbitrary argument passed as the first parameter in each call to 'func'.
+ *
+ * Returns:
+ * NB_OK on success, NB_ERR otherwise.
+ */
+extern int nb_db_transactions_iterate(
+ void (*func)(void *arg, int transaction_id, const char *client_name,
+ const char *date, const char *comment),
+ void *arg);
+
+#endif /* _FRR_NORTHBOUND_DB_H_ */
#
lib_LTLIBRARIES += lib/libfrr.la
lib_libfrr_la_LDFLAGS = -version-info 0:0:0 -Xlinker -e_libfrr_version
-lib_libfrr_la_LIBADD = @LIBCAP@ $(UNWIND_LIBS)
+lib_libfrr_la_LIBADD = @LIBCAP@ $(UNWIND_LIBS) -lyang
lib_libfrr_la_SOURCES = \
lib/agg_table.c \
lib/netns_linux.c \
lib/netns_other.c \
lib/nexthop_group.c \
+ lib/northbound.c \
+ lib/northbound_cli.c \
+ lib/northbound_db.c \
lib/openbsd-tree.c \
lib/pid_output.c \
lib/plist.c \
lib/vty.c \
lib/wheel.c \
lib/workqueue.c \
+ lib/yang.c \
+ lib/yang_translator.c \
+ lib/yang_wrappers.c \
lib/zclient.c \
lib/logicalrouter.c \
lib/lua.c \
# can be loaded as DSO - always include for vtysh
vtysh_scan += $(top_srcdir)/lib/agentx.c
+if SQLITE3
+lib_libfrr_la_LIBADD += -lsqlite3
+lib_libfrr_la_SOURCES += lib/db.c
+endif
+
lib/plist_clippy.c: $(CLIPPY_DEPS)
lib/plist.lo: lib/plist_clippy.c
lib/nexthop_group_clippy.c: $(CLIPPY_DEPS)
lib/nexthop_group.lo: lib/nexthop_group_clippy.c
+lib/northbound_cli_clippy.c: $(CLIPPY_DEPS)
+lib/northbound_cli.lo: lib/northbound_cli_clippy.c
pkginclude_HEADERS += \
lib/agg_table.h \
lib/command_match.h \
lib/compiler.h \
lib/csv.h \
+ lib/db.h \
lib/debug.h \
lib/distribute.h \
lib/event_counter.h \
lib/network.h \
lib/nexthop.h \
lib/nexthop_group.h \
+ lib/northbound.h \
+ lib/northbound_cli.h \
+ lib/northbound_db.h \
lib/ns.h \
lib/openbsd-queue.h \
lib/openbsd-tree.h \
lib/vxlan.h \
lib/wheel.h \
lib/workqueue.h \
+ lib/yang.h \
+ lib/yang_translator.h \
+ lib/yang_wrappers.h \
lib/zassert.h \
lib/zclient.h \
lib/zebra.h \
#include "libfrr.h"
#include "frrstr.h"
#include "lib_errors.h"
+#include "northbound_cli.h"
#include <arpa/telnet.h>
#include <termios.h>
static int vty_config;
static int vty_config_is_lockless = 0;
+/* Exclusive configuration lock. */
+struct vty *vty_exclusive_lock;
+
/* Login password check. */
static int no_password_check = 0;
break;
}
+ vty->xpath_index = 0;
+
vty_prompt(vty);
vty->cp = 0;
}
memset(vty->hist, 0, sizeof(vty->hist));
vty->hp = 0;
vty->hindex = 0;
+ vty->xpath_index = 0;
+ memset(vty->xpath, 0, sizeof(vty->xpath));
+ vty->private_config = false;
+ vty->candidate_config = vty_shared_candidate_config;
vector_set_index(vtyvec, vty_sock, vty);
vty->status = VTY_NORMAL;
vty->lines = -1;
}
/* Read up configuration file from file_name. */
-static void vty_read_file(FILE *confp)
+static void vty_read_file(struct nb_config *config, FILE *confp)
{
int ret;
struct vty *vty;
vty->wfd = STDERR_FILENO;
vty->type = VTY_FILE;
vty->node = CONFIG_NODE;
+ if (config)
+ vty->candidate_config = config;
+ else {
+ vty->private_config = true;
+ vty->candidate_config = nb_config_new(NULL);
+ }
/* Execute configuration file */
ret = config_from_file(vty, confp, &line_num);
}
}
+ /*
+ * Automatically commit the candidate configuration after
+ * reading the configuration file.
+ */
+ if (config == NULL && vty->candidate_config
+ && frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL) {
+ int ret;
+
+ ret = nb_candidate_commit(vty->candidate_config, NB_CLIENT_CLI,
+ true, "Read configuration file",
+ NULL);
+ if (ret != NB_OK && ret != NB_ERR_NO_CHANGES)
+ zlog_err("%s: failed to read configuration file.",
+ __func__);
+ }
+
vty_close(vty);
}
}
/* Read up configuration file from file_name. */
-bool vty_read_config(const char *config_file, char *config_default_dir)
+bool vty_read_config(struct nb_config *config, const char *config_file,
+ char *config_default_dir)
{
char cwd[MAXPATHLEN];
FILE *confp = NULL;
if (getcwd(cwd, MAXPATHLEN) == NULL) {
flog_err_sys(
EC_LIB_SYSTEM_CALL,
- "Failure to determine Current Working Directory %d!",
- errno);
- exit(1);
+ "%s: failure to determine Current Working Directory %d!",
+ __func__, errno);
+ goto tmp_free_and_out;
}
tmp = XMALLOC(MTYPE_TMP,
strlen(cwd) + strlen(config_file) + 2);
EC_LIB_BACKUP_CONFIG,
"WARNING: using backup configuration file!");
else {
- flog_err(EC_LIB_VTY,
- "can't open configuration file [%s]",
- config_file);
- exit(1);
+ flog_err(
+ EC_LIB_VTY,
+ "%s: can't open configuration file [%s]",
+ __func__, config_file);
+ goto tmp_free_and_out;
}
}
} else {
fullpath = config_default_dir;
}
- vty_read_file(confp);
+ vty_read_file(config, confp);
read_success = true;
fclose(confp);
int vty_config_unlock(struct vty *vty)
{
+ vty_config_exclusive_unlock(vty);
+
+ if (vty->candidate_config) {
+ if (vty->private_config)
+ nb_config_free(vty->candidate_config);
+ vty->candidate_config = NULL;
+ }
+ if (vty->candidate_config_base) {
+ nb_config_free(vty->candidate_config_base);
+ vty->candidate_config_base = NULL;
+ }
+
if (vty_config_is_lockless)
return 0;
if (vty_config == 1 && vty->config == 1) {
vty_config_is_lockless = 1;
}
+int vty_config_exclusive_lock(struct vty *vty)
+{
+ if (vty_exclusive_lock == NULL) {
+ vty_exclusive_lock = vty;
+ return 1;
+ }
+ return 0;
+}
+
+void vty_config_exclusive_unlock(struct vty *vty)
+{
+ if (vty_exclusive_lock == vty)
+ vty_exclusive_lock = NULL;
+}
+
/* Master of the threads. */
static struct thread_master *vty_master;
#include "sockunion.h"
#include "qobj.h"
#include "compiler.h"
+#include "northbound.h"
#define VTY_BUFSIZ 4096
#define VTY_MAXHIST 20
+#define VTY_MAXDEPTH 8
struct vty_error {
char error_buf[VTY_BUFSIZ];
/* History insert end point */
int hindex;
+ /* XPath of the current node */
+ int xpath_index;
+ char xpath[VTY_MAXDEPTH][XPATH_MAXLEN];
+
+ /* Private candidate configuration mode. */
+ bool private_config;
+
+ /* Candidate configuration. */
+ struct nb_config *candidate_config;
+
+ /* Base candidate configuration. */
+ struct nb_config *candidate_config_base;
+
/* qobj object ID (replacement for "index") */
uint64_t qobj_index;
struct structname *ptr = VTY_GET_CONTEXT(structname); \
VTY_CHECK_CONTEXT(ptr);
+/* XPath macros. */
+#define VTY_PUSH_XPATH(nodeval, value) \
+ do { \
+ if (vty->xpath_index >= VTY_MAXDEPTH) { \
+ vty_out(vty, "%% Reached maximum CLI depth (%u)\n", \
+ VTY_MAXDEPTH); \
+ return CMD_WARNING; \
+ } \
+ vty->node = nodeval; \
+ strlcpy(vty->xpath[vty->xpath_index], value, \
+ sizeof(vty->xpath[0])); \
+ vty->xpath_index++; \
+ } while (0)
+
+#define VTY_CURR_XPATH vty->xpath[vty->xpath_index - 1]
+
+#define VTY_CHECK_XPATH \
+ do { \
+ if (vty->xpath_index > 0 \
+ && !yang_dnode_exists(vty->candidate_config->dnode, \
+ VTY_CURR_XPATH)) { \
+ vty_out(vty, \
+ "Current configuration object was deleted " \
+ "by another process.\n\n"); \
+ return CMD_WARNING; \
+ } \
+ } while (0)
+
struct vty_arg {
const char *name;
const char *value;
/* Exported variables */
extern char integrate_default[];
+extern struct vty *vty_exclusive_lock;
/* Prototypes. */
extern void vty_init(struct thread_master *);
extern void vty_endframe(struct vty *, const char *);
bool vty_set_include(struct vty *vty, const char *regexp);
-extern bool vty_read_config(const char *, char *);
+extern bool vty_read_config(struct nb_config *config, const char *config_file,
+ char *config_default_dir);
extern void vty_time_print(struct vty *, int);
extern void vty_serv_sock(const char *, unsigned short, const char *);
extern void vty_close(struct vty *);
extern int vty_config_lock(struct vty *);
extern int vty_config_unlock(struct vty *);
extern void vty_config_lockless(void);
+extern int vty_config_exclusive_lock(struct vty *vty);
+extern void vty_config_exclusive_unlock(struct vty *vty);
extern int vty_shell(struct vty *);
extern int vty_shell_serv(struct vty *);
extern void vty_hello(struct vty *);
--- /dev/null
+/*
+ * Copyright (C) 2018 NetDEF, Inc.
+ * Renato Westphal
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <zebra.h>
+
+#include "log.h"
+#include "log_int.h"
+#include "lib_errors.h"
+#include "yang.h"
+#include "yang_translator.h"
+#include "northbound.h"
+
+DEFINE_MTYPE(LIB, YANG_MODULE, "YANG module")
+DEFINE_MTYPE(LIB, YANG_DATA, "YANG data structure")
+
+/* libyang container. */
+struct ly_ctx *ly_native_ctx;
+
+/* Generate the yang_modules tree. */
+static inline int yang_module_compare(const struct yang_module *a,
+ const struct yang_module *b)
+{
+ return strcmp(a->name, b->name);
+}
+RB_GENERATE(yang_modules, yang_module, entry, yang_module_compare)
+
+struct yang_modules yang_modules = RB_INITIALIZER(&yang_modules);
+
+struct yang_module *yang_module_load(const char *module_name)
+{
+ struct yang_module *module;
+ const struct lys_module *module_info;
+
+ module_info = ly_ctx_load_module(ly_native_ctx, module_name, NULL);
+ if (!module_info) {
+ flog_err(EC_LIB_YANG_MODULE_LOAD,
+ "%s: failed to load data model: %s", __func__,
+ module_name);
+ exit(1);
+ }
+
+ module = XCALLOC(MTYPE_YANG_MODULE, sizeof(*module));
+ module->name = module_name;
+ module->info = module_info;
+
+ if (RB_INSERT(yang_modules, &yang_modules, module) != NULL) {
+ flog_err(EC_LIB_YANG_MODULE_LOADED_ALREADY,
+ "%s: YANG module is loaded already: %s", __func__,
+ module_name);
+ exit(1);
+ }
+
+ return module;
+}
+
+struct yang_module *yang_module_find(const char *module_name)
+{
+ struct yang_module s;
+
+ s.name = module_name;
+ return RB_FIND(yang_modules, &yang_modules, &s);
+}
+
+/*
+ * Helper function for yang_module_snodes_iterate() and
+ * yang_all_snodes_iterate(). This is a recursive function.
+ */
+static void yang_snodes_iterate(const struct lys_node *snode,
+ void (*func)(const struct lys_node *, void *,
+ void *),
+ uint16_t flags, void *arg1, void *arg2)
+{
+ struct lys_node *child;
+
+ if (CHECK_FLAG(flags, YANG_ITER_FILTER_IMPLICIT)) {
+ switch (snode->nodetype) {
+ case LYS_CASE:
+ case LYS_INPUT:
+ case LYS_OUTPUT:
+ if (snode->flags & LYS_IMPLICIT)
+ goto next;
+ break;
+ default:
+ break;
+ }
+ }
+
+ switch (snode->nodetype) {
+ case LYS_CONTAINER:
+ if (CHECK_FLAG(flags, YANG_ITER_FILTER_NPCONTAINERS)) {
+ struct lys_node_container *scontainer;
+
+ scontainer = (struct lys_node_container *)snode;
+ if (!scontainer->presence)
+ goto next;
+ }
+ break;
+ case LYS_LEAF:
+ if (CHECK_FLAG(flags, YANG_ITER_FILTER_LIST_KEYS)) {
+ struct lys_node_leaf *sleaf;
+
+ /* Ignore list keys. */
+ sleaf = (struct lys_node_leaf *)snode;
+ if (lys_is_key(sleaf, NULL))
+ goto next;
+ }
+ break;
+ case LYS_GROUPING:
+ /* Return since we're not interested in the grouping subtree. */
+ return;
+ case LYS_USES:
+ case LYS_AUGMENT:
+ /* Always ignore nodes of these types. */
+ goto next;
+ case LYS_INPUT:
+ case LYS_OUTPUT:
+ if (CHECK_FLAG(flags, YANG_ITER_FILTER_INPUT_OUTPUT))
+ goto next;
+ break;
+ default:
+ break;
+ }
+
+ (*func)(snode, arg1, arg2);
+
+next:
+ /*
+ * YANG leafs and leaf-lists can't have child nodes, and trying to
+ * access snode->child is undefined behavior.
+ */
+ if (snode->nodetype & (LYS_LEAF | LYS_LEAFLIST))
+ return;
+
+ LY_TREE_FOR (snode->child, child) {
+ if (child->parent != snode)
+ continue;
+ yang_snodes_iterate(child, func, flags, arg1, arg2);
+ }
+}
+
+void yang_module_snodes_iterate(const struct lys_module *module,
+ void (*func)(const struct lys_node *, void *,
+ void *),
+ uint16_t flags, void *arg1, void *arg2)
+{
+ struct lys_node *snode;
+
+ LY_TREE_FOR (module->data, snode) {
+ yang_snodes_iterate(snode, func, flags, arg1, arg2);
+ }
+
+ for (uint8_t i = 0; i < module->augment_size; i++) {
+ yang_snodes_iterate(
+ (const struct lys_node *)&module->augment[i], func,
+ flags, arg1, arg2);
+ }
+}
+
+void yang_all_snodes_iterate(void (*func)(const struct lys_node *, void *,
+ void *),
+ uint16_t flags, void *arg1, void *arg2)
+{
+ struct yang_module *module;
+
+ RB_FOREACH (module, yang_modules, &yang_modules)
+ yang_module_snodes_iterate(module->info, func, flags, arg1,
+ arg2);
+}
+
+void yang_snode_get_path(const struct lys_node *snode, enum yang_path_type type,
+ char *xpath, size_t xpath_len)
+{
+ char *xpath_ptr;
+
+ switch (type) {
+ case YANG_PATH_SCHEMA:
+ xpath_ptr = lys_path(snode, 0);
+ break;
+ case YANG_PATH_DATA:
+ xpath_ptr = lys_data_path(snode);
+ break;
+ default:
+ flog_err(EC_LIB_DEVELOPMENT, "%s: unknown yang path type: %u",
+ __func__, type);
+ exit(1);
+ }
+ strlcpy(xpath, xpath_ptr, xpath_len);
+ free(xpath_ptr);
+}
+
+struct lys_node *yang_snode_real_parent(const struct lys_node *snode)
+{
+ struct lys_node *parent = snode->parent;
+
+ while (parent) {
+ struct lys_node_container *scontainer;
+
+ switch (parent->nodetype) {
+ case LYS_CONTAINER:
+ scontainer = (struct lys_node_container *)parent;
+ if (scontainer->presence)
+ return parent;
+ break;
+ case LYS_LIST:
+ return parent;
+ default:
+ break;
+ }
+ parent = parent->parent;
+ }
+
+ return NULL;
+}
+
+struct lys_node *yang_snode_parent_list(const struct lys_node *snode)
+{
+ struct lys_node *parent = snode->parent;
+
+ while (parent) {
+ switch (parent->nodetype) {
+ case LYS_LIST:
+ return parent;
+ default:
+ break;
+ }
+ parent = parent->parent;
+ }
+
+ return NULL;
+}
+
+bool yang_snode_is_typeless_data(const struct lys_node *snode)
+{
+ struct lys_node_leaf *sleaf;
+
+ switch (snode->nodetype) {
+ case LYS_LEAF:
+ sleaf = (struct lys_node_leaf *)snode;
+ if (sleaf->type.base == LY_TYPE_EMPTY)
+ return true;
+ return false;
+ case LYS_LEAFLIST:
+ return false;
+ default:
+ return true;
+ }
+}
+
+const char *yang_snode_get_default(const struct lys_node *snode)
+{
+ struct lys_node_leaf *sleaf;
+
+ switch (snode->nodetype) {
+ case LYS_LEAF:
+ sleaf = (struct lys_node_leaf *)snode;
+
+ /* NOTE: this might be null. */
+ return sleaf->dflt;
+ case LYS_LEAFLIST:
+ /* TODO: check leaf-list default values */
+ return NULL;
+ default:
+ return NULL;
+ }
+}
+
+const struct lys_type *yang_snode_get_type(const struct lys_node *snode)
+{
+ struct lys_node_leaf *sleaf = (struct lys_node_leaf *)snode;
+ struct lys_type *type;
+
+ if (!(sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST)))
+ return NULL;
+
+ type = &sleaf->type;
+ while (type->base == LY_TYPE_LEAFREF)
+ type = &type->info.lref.target->type;
+
+ return type;
+}
+
+void yang_dnode_get_path(const struct lyd_node *dnode, char *xpath,
+ size_t xpath_len)
+{
+ char *xpath_ptr;
+
+ xpath_ptr = lyd_path(dnode);
+ strlcpy(xpath, xpath_ptr, xpath_len);
+ free(xpath_ptr);
+}
+
+struct lyd_node *yang_dnode_get(const struct lyd_node *dnode,
+ const char *xpath_fmt, ...)
+{
+ va_list ap;
+ char xpath[XPATH_MAXLEN];
+ struct ly_set *set;
+ struct lyd_node *dnode_ret = NULL;
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+
+ set = lyd_find_path(dnode, xpath);
+ assert(set);
+ if (set->number == 0)
+ goto exit;
+
+ if (set->number > 1) {
+ flog_warn(EC_LIB_YANG_DNODE_NOT_FOUND,
+ "%s: found %u elements (expected 0 or 1) [xpath %s]",
+ __func__, set->number, xpath);
+ goto exit;
+ }
+
+ dnode_ret = set->set.d[0];
+
+exit:
+ ly_set_free(set);
+
+ return dnode_ret;
+}
+
+bool yang_dnode_exists(const struct lyd_node *dnode, const char *xpath_fmt, ...)
+{
+ va_list ap;
+ char xpath[XPATH_MAXLEN];
+ struct ly_set *set;
+ bool found;
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+
+ set = lyd_find_path(dnode, xpath);
+ assert(set);
+ found = (set->number > 0);
+ ly_set_free(set);
+
+ return found;
+}
+
+bool yang_dnode_is_default(const struct lyd_node *dnode, const char *xpath_fmt,
+ ...)
+{
+ struct lys_node *snode;
+ struct lys_node_leaf *sleaf;
+ struct lys_node_container *scontainer;
+
+ if (xpath_fmt) {
+ va_list ap;
+ char xpath[XPATH_MAXLEN];
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+
+ dnode = yang_dnode_get(dnode, xpath);
+ }
+
+ assert(dnode);
+ snode = dnode->schema;
+ switch (snode->nodetype) {
+ case LYS_LEAF:
+ sleaf = (struct lys_node_leaf *)snode;
+ if (sleaf->type.base == LY_TYPE_EMPTY)
+ return false;
+ return lyd_wd_default((struct lyd_node_leaf_list *)dnode);
+ case LYS_LEAFLIST:
+ /* TODO: check leaf-list default values */
+ return false;
+ case LYS_CONTAINER:
+ scontainer = (struct lys_node_container *)snode;
+ if (scontainer->presence)
+ return false;
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool yang_dnode_is_default_recursive(const struct lyd_node *dnode)
+{
+ struct lys_node *snode;
+ struct lyd_node *root, *next, *dnode_iter;
+
+ snode = dnode->schema;
+ if (snode->nodetype & (LYS_LEAF | LYS_LEAFLIST))
+ return yang_dnode_is_default(dnode, NULL);
+
+ if (!yang_dnode_is_default(dnode, NULL))
+ return false;
+
+ LY_TREE_FOR (dnode->child, root) {
+ LY_TREE_DFS_BEGIN (root, next, dnode_iter) {
+ if (!yang_dnode_is_default(dnode_iter, NULL))
+ return false;
+
+ LY_TREE_DFS_END(root, next, dnode_iter);
+ }
+ }
+
+ return true;
+}
+
+void yang_dnode_change_leaf(struct lyd_node *dnode, const char *value)
+{
+ assert(dnode->schema->nodetype == LYS_LEAF);
+ lyd_change_leaf((struct lyd_node_leaf_list *)dnode, value);
+}
+
+void yang_dnode_set_entry(const struct lyd_node *dnode, void *entry)
+{
+ assert(dnode->schema->nodetype & (LYS_LIST | LYS_CONTAINER));
+ lyd_set_private(dnode, entry);
+}
+
+void *yang_dnode_get_entry(const struct lyd_node *dnode)
+{
+ const struct lyd_node *orig_dnode = dnode;
+ char xpath[XPATH_MAXLEN];
+
+ while (dnode) {
+ switch (dnode->schema->nodetype) {
+ case LYS_CONTAINER:
+ case LYS_LIST:
+ if (dnode->priv)
+ return dnode->priv;
+ break;
+ default:
+ break;
+ }
+
+ dnode = dnode->parent;
+ }
+
+ yang_dnode_get_path(orig_dnode, xpath, sizeof(xpath));
+ flog_err(EC_LIB_YANG_DNODE_NOT_FOUND,
+ "%s: failed to find entry [xpath %s]", __func__, xpath);
+ zlog_backtrace(LOG_ERR);
+ abort();
+}
+
+struct lyd_node *yang_dnode_new(struct ly_ctx *ly_ctx)
+{
+ struct lyd_node *dnode;
+
+ dnode = NULL;
+ if (lyd_validate(&dnode, LYD_OPT_CONFIG, ly_ctx) != 0) {
+ /* Should never happen. */
+ flog_err(EC_LIB_LIBYANG, "%s: lyd_validate() failed", __func__);
+ exit(1);
+ }
+
+ return dnode;
+}
+
+struct lyd_node *yang_dnode_dup(const struct lyd_node *dnode)
+{
+ return lyd_dup_withsiblings(dnode, 1);
+}
+
+void yang_dnode_free(struct lyd_node *dnode)
+{
+ lyd_free_withsiblings(dnode);
+}
+
+struct yang_data *yang_data_new(const char *xpath, const char *value)
+{
+ const struct lys_node *snode;
+ struct yang_data *data;
+
+ snode = ly_ctx_get_node(ly_native_ctx, NULL, xpath, 0);
+ if (!snode)
+ snode = ly_ctx_get_node(ly_native_ctx, NULL, xpath, 1);
+ if (!snode) {
+ flog_err(EC_LIB_YANG_UNKNOWN_DATA_PATH,
+ "%s: unknown data path: %s", __func__, xpath);
+ zlog_backtrace(LOG_ERR);
+ abort();
+ }
+
+ data = XCALLOC(MTYPE_YANG_DATA, sizeof(*data));
+ strlcpy(data->xpath, xpath, sizeof(data->xpath));
+ data->snode = snode;
+ if (value)
+ data->value = strdup(value);
+
+ return data;
+}
+
+void yang_data_free(struct yang_data *data)
+{
+ if (data->value)
+ free(data->value);
+ XFREE(MTYPE_YANG_DATA, data);
+}
+
+struct list *yang_data_list_new(void)
+{
+ struct list *list;
+
+ list = list_new();
+ list->del = (void (*)(void *))yang_data_free;
+
+ return list;
+}
+
+static void *ly_dup_cb(const void *priv)
+{
+ /* Make a shallow copy of the priv pointer. */
+ return (void *)priv;
+}
+
+/* Make libyang log its errors using FRR logging infrastructure. */
+static void ly_log_cb(LY_LOG_LEVEL level, const char *msg, const char *path)
+{
+ int priority;
+
+ switch (level) {
+ case LY_LLERR:
+ priority = LOG_ERR;
+ break;
+ case LY_LLWRN:
+ priority = LOG_WARNING;
+ break;
+ case LY_LLVRB:
+ priority = LOG_DEBUG;
+ break;
+ default:
+ return;
+ }
+
+ if (path)
+ zlog(priority, "libyang: %s (%s)", msg, path);
+ else
+ zlog(priority, "libyang: %s", msg);
+}
+
+void yang_init(void)
+{
+ static char ly_plugin_dir[PATH_MAX];
+ const char *const *ly_loaded_plugins;
+ const char *ly_plugin;
+ bool found_ly_frr_types = false;
+
+ /* Tell libyang where to find its plugins. */
+ snprintf(ly_plugin_dir, sizeof(ly_plugin_dir), "%s=%s",
+ "LIBYANG_USER_TYPES_PLUGINS_DIR", LIBYANG_PLUGINS_PATH);
+ putenv(ly_plugin_dir);
+
+ /* Initialize libyang global parameters that affect all containers. */
+ ly_set_log_clb(ly_log_cb, 1);
+ ly_log_options(LY_LOLOG | LY_LOSTORE);
+
+ /* Initialize libyang container for native models. */
+ ly_native_ctx = ly_ctx_new(NULL, LY_CTX_DISABLE_SEARCHDIR_CWD);
+ if (!ly_native_ctx) {
+ flog_err(EC_LIB_LIBYANG, "%s: ly_ctx_new() failed", __func__);
+ exit(1);
+ }
+ ly_ctx_set_searchdir(ly_native_ctx, YANG_MODELS_PATH);
+ ly_ctx_set_priv_dup_clb(ly_native_ctx, ly_dup_cb);
+
+ /* Detect if the required libyang plugin(s) were loaded successfully. */
+ ly_loaded_plugins = ly_get_loaded_plugins();
+ for (size_t i = 0; (ly_plugin = ly_loaded_plugins[i]); i++) {
+ if (strmatch(ly_plugin, "frr_user_types")) {
+ found_ly_frr_types = true;
+ break;
+ }
+ }
+ if (!found_ly_frr_types) {
+ flog_err(EC_LIB_LIBYANG_PLUGIN_LOAD,
+ "%s: failed to load frr_user_types.so", __func__);
+ exit(1);
+ }
+
+ yang_translator_init();
+}
+
+void yang_terminate(void)
+{
+ struct yang_module *module;
+
+ yang_translator_terminate();
+
+ while (!RB_EMPTY(yang_modules, &yang_modules)) {
+ module = RB_ROOT(yang_modules, &yang_modules);
+
+ /*
+ * We shouldn't call ly_ctx_remove_module() here because this
+ * function also removes other modules that depend on it.
+ *
+ * ly_ctx_destroy() will release all memory for us.
+ */
+ RB_REMOVE(yang_modules, &yang_modules, module);
+ XFREE(MTYPE_YANG_MODULE, module);
+ }
+
+ ly_ctx_destroy(ly_native_ctx, NULL);
+}
--- /dev/null
+/*
+ * Copyright (C) 2018 NetDEF, Inc.
+ * Renato Westphal
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _FRR_YANG_H_
+#define _FRR_YANG_H_
+
+#include "memory.h"
+
+#include <libyang/libyang.h>
+#ifdef HAVE_SYSREPO
+#include <sysrepo.h>
+#endif
+
+#include "yang_wrappers.h"
+
+DECLARE_MTYPE(YANG_MODULE)
+DECLARE_MTYPE(YANG_DATA)
+
+/* Maximum XPath length. */
+#define XPATH_MAXLEN 256
+
+/* Maximum list key length. */
+#define LIST_MAXKEYS 8
+
+/* Maximum list key length. */
+#define LIST_MAXKEYLEN 128
+
+/* Maximum string length of an YANG value. */
+#define YANG_VALUE_MAXLEN 1024
+
+struct yang_module {
+ RB_ENTRY(yang_module) entry;
+ const char *name;
+ const struct lys_module *info;
+#ifdef HAVE_CONFD
+ int confd_hash;
+#endif
+#ifdef HAVE_SYSREPO
+ sr_subscription_ctx_t *sr_subscription;
+#endif
+};
+RB_HEAD(yang_modules, yang_module);
+RB_PROTOTYPE(yang_modules, yang_module, entry, yang_module_compare);
+
+struct yang_data {
+ /* XPath identifier of the data element. */
+ char xpath[XPATH_MAXLEN];
+
+ /*
+ * Schema information (necessary to interpret certain values like
+ * enums).
+ */
+ const struct lys_node *snode;
+
+ /* Value encoded as a raw string. */
+ char *value;
+};
+
+struct yang_list_keys {
+ /* Number os keys (max: LIST_MAXKEYS). */
+ uint8_t num;
+
+ struct {
+ /*
+ * Schema information (necessary to interpret certain values
+ * like enums).
+ */
+ struct lys_node *snode;
+
+ /* Value encoded as a raw string. */
+ char value[LIST_MAXKEYLEN];
+ } key[LIST_MAXKEYS];
+};
+
+enum yang_path_type {
+ YANG_PATH_SCHEMA = 0,
+ YANG_PATH_DATA,
+};
+
+/* Filter non-presence containers. */
+#define YANG_ITER_FILTER_NPCONTAINERS 0x0001
+/* Filter list keys (leafs). */
+#define YANG_ITER_FILTER_LIST_KEYS 0x0002
+/* Filter RPC input/output nodes. */
+#define YANG_ITER_FILTER_INPUT_OUTPUT 0x0004
+/* Filter implicitely created nodes. */
+#define YANG_ITER_FILTER_IMPLICIT 0x0008
+
+/* Global libyang context for native FRR models. */
+extern struct ly_ctx *ly_native_ctx;
+
+/* Tree of all loaded YANG modules. */
+extern struct yang_modules yang_modules;
+
+/*
+ * Create a new YANG module and load it using libyang. If the YANG module is not
+ * found in the YANG_MODELS_PATH directory, the program will exit with an error.
+ * Once loaded, a YANG module can't be unloaded anymore.
+ *
+ * module_name
+ * Name of the YANG module.
+ *
+ * Returns:
+ * Pointer to newly created YANG module.
+ */
+extern struct yang_module *yang_module_load(const char *module_name);
+
+/*
+ * Find a YANG module by its name.
+ *
+ * module_name
+ * Name of the YANG module.
+ *
+ * Returns:
+ * Pointer to YANG module if found, NULL otherwise.
+ */
+extern struct yang_module *yang_module_find(const char *module_name);
+
+/*
+ * Iterate over all libyang schema nodes from the given YANG module.
+ *
+ * module
+ * YANG module to operate on.
+ *
+ * func
+ * Function to call with each schema node.
+ *
+ * flags
+ * YANG_ITER_FILTER_* flags to specify node types that should be filtered.
+ *
+ * arg1
+ * Arbitrary argument passed as the second parameter in each call to 'func'.
+ *
+ * arg2
+ * Arbitrary argument passed as the third parameter in each call to 'func'.
+ */
+extern void yang_module_snodes_iterate(const struct lys_module *module,
+ void (*func)(const struct lys_node *,
+ void *, void *),
+ uint16_t flags, void *arg1, void *arg2);
+
+/*
+ * Iterate over all libyang schema nodes from all loaded YANG modules.
+ *
+ * func
+ * Function to call with each schema node.
+ *
+ * flags
+ * YANG_ITER_FILTER_* flags to specify node types that should be filtered.
+ *
+ * arg1
+ * Arbitrary argument passed as the second parameter in each call to 'func'.
+ *
+ * arg2
+ * Arbitrary argument passed as the third parameter in each call to 'func'.
+ */
+extern void yang_all_snodes_iterate(void (*func)(const struct lys_node *,
+ void *, void *),
+ uint16_t flags, void *arg1, void *arg2);
+
+/*
+ * Build schema path or data path of the schema node.
+ *
+ * snode
+ * libyang schema node to be processed.
+ *
+ * type
+ * Specify whether a schema path or a data path should be built.
+ *
+ * xpath
+ * Pointer to previously allocated buffer.
+ *
+ * xpath_len
+ * Size of the xpath buffer.
+ */
+extern void yang_snode_get_path(const struct lys_node *snode,
+ enum yang_path_type type, char *xpath,
+ size_t xpath_len);
+
+/*
+ * Find first parent schema node which is a presence-container or a list
+ * (non-presence containers are ignored).
+ *
+ * snode
+ * libyang schema node to operate on.
+ *
+ * Returns:
+ * The parent libyang schema node if found, or NULL if not found.
+ */
+extern struct lys_node *yang_snode_real_parent(const struct lys_node *snode);
+
+/*
+ * Find first parent schema node which is a list.
+ *
+ * snode
+ * libyang schema node to operate on.
+ *
+ * Returns:
+ * The parent libyang schema node (list) if found, or NULL if not found.
+ */
+extern struct lys_node *yang_snode_parent_list(const struct lys_node *snode);
+
+/*
+ * Check if the libyang schema node represents typeless data (e.g. containers,
+ * leafs of type empty, etc).
+ *
+ * snode
+ * libyang schema node to operate on.
+ *
+ * Returns:
+ * true if the schema node represents typeless data, false otherwise.
+ */
+extern bool yang_snode_is_typeless_data(const struct lys_node *snode);
+
+/*
+ * Get the default value associated to a YANG leaf or leaf-list.
+ *
+ * snode
+ * libyang schema node to operate on.
+ *
+ * Returns:
+ * The default value if it exists, NULL otherwise.
+ */
+extern const char *yang_snode_get_default(const struct lys_node *snode);
+
+/*
+ * Get the type structure of a leaf of leaf-list. If the type is a leafref, the
+ * final (if there is a chain of leafrefs) target's type is found.
+ *
+ * snode
+ * libyang schema node to operate on.
+ *
+ * Returns:
+ * The found type if the schema node represents a leaf or a leaf-list, NULL
+ * otherwise.
+ */
+extern const struct lys_type *yang_snode_get_type(const struct lys_node *snode);
+
+/*
+ * Build data path of the data node.
+ *
+ * dnode
+ * libyang data node to be processed.
+ *
+ * xpath
+ * Pointer to previously allocated buffer.
+ *
+ * xpath_len
+ * Size of the xpath buffer.
+ */
+extern void yang_dnode_get_path(const struct lyd_node *dnode, char *xpath,
+ size_t xpath_len);
+
+/*
+ * Find a libyang data node by its YANG data path.
+ *
+ * dnode
+ * Base libyang data node to operate on.
+ *
+ * xpath_fmt
+ * XPath expression (absolute or relative).
+ *
+ * Returns:
+ * The libyang data node if found, or NULL if not found.
+ */
+extern struct lyd_node *yang_dnode_get(const struct lyd_node *dnode,
+ const char *xpath_fmt, ...);
+
+/*
+ * Check if a libyang data node exists.
+ *
+ * dnode
+ * Base libyang data node to operate on.
+ *
+ * xpath_fmt
+ * XPath expression (absolute or relative).
+ *
+ * Returns:
+ * true if the libyang data node was found, false otherwise.
+ */
+extern bool yang_dnode_exists(const struct lyd_node *dnode,
+ const char *xpath_fmt, ...);
+
+/*
+ * Check if the libyang data node contains a default value. Non-presence
+ * containers are assumed to always contain a default value.
+ *
+ * dnode
+ * Base libyang data node to operate on.
+ *
+ * xpath_fmt
+ * Optional XPath expression (absolute or relative) to specify a different
+ * data node to operate on in the same data tree.
+ *
+ * Returns:
+ * true if the data node contains the default value, false otherwise.
+ */
+extern bool yang_dnode_is_default(const struct lyd_node *dnode,
+ const char *xpath_fmt, ...);
+
+/*
+ * Check if the libyang data node and all of its children contain default
+ * values. Non-presence containers are assumed to always contain a default
+ * value.
+ *
+ * dnode
+ * libyang data node to operate on.
+ *
+ * Returns:
+ * true if the data node and all of its children contain default values,
+ * false otherwise.
+ */
+extern bool yang_dnode_is_default_recursive(const struct lyd_node *dnode);
+
+/*
+ * Change the value of a libyang leaf node.
+ *
+ * dnode
+ * libyang data node to operate on.
+ *
+ * value
+ * String representing the new value.
+ */
+extern void yang_dnode_change_leaf(struct lyd_node *dnode, const char *value);
+
+/*
+ * Set the libyang private pointer to a user pointer. Can only be used on YANG
+ * lists and containers.
+ *
+ * dnode
+ * libyang data node to operate on.
+ *
+ * entry
+ * Arbitrary user-specified pointer.
+ */
+extern void yang_dnode_set_entry(const struct lyd_node *dnode, void *entry);
+
+/*
+ * Find the closest data node that contains an user pointer and return it.
+ *
+ * dnode
+ * libyang data node to operate on.
+ *
+ * Returns:
+ * User pointer if found, NULL otherwise.
+ */
+extern void *yang_dnode_get_entry(const struct lyd_node *dnode);
+
+/*
+ * Create a new libyang data node.
+ *
+ * ly_ctx
+ * libyang context to operate on.
+ *
+ * Returns:
+ * Pointer to newly created libyang data node.
+ */
+extern struct lyd_node *yang_dnode_new(struct ly_ctx *ly_ctx);
+
+/*
+ * Duplicate a libyang data node.
+ *
+ * dnode
+ * libyang data node to duplicate.
+ *
+ * Returns:
+ * Pointer to duplicated libyang data node.
+ */
+extern struct lyd_node *yang_dnode_dup(const struct lyd_node *dnode);
+
+/*
+ * Delete a libyang data node.
+ *
+ * dnode
+ * Pointer to the libyang data node that is going to be deleted.
+ */
+extern void yang_dnode_free(struct lyd_node *dnode);
+
+/*
+ * Create a new yang_data structure.
+ *
+ * xpath
+ * Data path of the YANG data.
+ *
+ * value
+ * String representing the value of the YANG data.
+ *
+ * Returns:
+ * Pointer to newly created yang_data structure.
+ */
+extern struct yang_data *yang_data_new(const char *xpath, const char *value);
+
+/*
+ * Delete a yang_data structure.
+ *
+ * data
+ * yang_data to delete.
+ */
+extern void yang_data_free(struct yang_data *data);
+
+/*
+ * Create a new linked list of yang_data structures. The list 'del' callback is
+ * initialized appropriately so that the entire list can be deleted safely with
+ * list_delete_and_null().
+ *
+ * Returns:
+ * Pointer to newly created linked list.
+ */
+extern struct list *yang_data_list_new(void);
+
+/*
+ * Initialize the YANG subsystem. Should be called only once during the
+ * daemon initialization process.
+ */
+extern void yang_init(void);
+
+/*
+ * Finish the YANG subsystem gracefully. Should be called only when the daemon
+ * is exiting.
+ */
+extern void yang_terminate(void);
+
+#endif /* _FRR_YANG_H_ */
--- /dev/null
+/*
+ * Copyright (C) 2018 NetDEF, Inc.
+ * Renato Westphal
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <zebra.h>
+
+#include "log.h"
+#include "lib_errors.h"
+#include "hash.h"
+#include "yang.h"
+#include "yang_translator.h"
+
+DEFINE_MTYPE_STATIC(LIB, YANG_TRANSLATOR, "YANG Translator")
+DEFINE_MTYPE_STATIC(LIB, YANG_TRANSLATOR_MODULE, "YANG Translator Module")
+DEFINE_MTYPE_STATIC(LIB, YANG_TRANSLATOR_MAPPING, "YANG Translator Mapping")
+
+/* Generate the yang_translators tree. */
+static inline int yang_translator_compare(const struct yang_translator *a,
+ const struct yang_translator *b)
+{
+ return strcmp(a->family, b->family);
+}
+RB_GENERATE(yang_translators, yang_translator, entry, yang_translator_compare)
+
+struct yang_translators yang_translators = RB_INITIALIZER(&yang_translators);
+
+/* Separate libyang context for the translator module. */
+static struct ly_ctx *ly_translator_ctx;
+
+static unsigned int
+yang_translator_validate(struct yang_translator *translator);
+static unsigned int yang_module_nodes_count(const struct lys_module *module);
+static void str_replace(char *o_string, const char *s_string,
+ const char *r_string);
+
+struct yang_mapping_node {
+ char xpath_from_canonical[XPATH_MAXLEN];
+ char xpath_from_fmt[XPATH_MAXLEN];
+ char xpath_to_fmt[XPATH_MAXLEN];
+};
+
+static bool yang_mapping_hash_cmp(const void *value1, const void *value2)
+{
+ const struct yang_mapping_node *c1 = value1;
+ const struct yang_mapping_node *c2 = value2;
+
+ return strmatch(c1->xpath_from_canonical, c2->xpath_from_canonical);
+}
+
+static unsigned int yang_mapping_hash_key(void *value)
+{
+ return string_hash_make(value);
+}
+
+static void *yang_mapping_hash_alloc(void *p)
+{
+ struct yang_mapping_node *new, *key = p;
+
+ new = XCALLOC(MTYPE_YANG_TRANSLATOR_MAPPING, sizeof(*new));
+ strlcpy(new->xpath_from_canonical, key->xpath_from_canonical,
+ sizeof(new->xpath_from_canonical));
+
+ return new;
+}
+
+static void yang_mapping_hash_free(void *arg)
+{
+ XFREE(MTYPE_YANG_TRANSLATOR_MAPPING, arg);
+}
+
+static struct yang_mapping_node *
+yang_mapping_lookup(const struct yang_translator *translator, int dir,
+ const char *xpath)
+{
+ struct yang_mapping_node s;
+
+ strlcpy(s.xpath_from_canonical, xpath, sizeof(s.xpath_from_canonical));
+ return hash_lookup(translator->mappings[dir], &s);
+}
+
+static void yang_mapping_add(struct yang_translator *translator, int dir,
+ const struct lys_node *snode,
+ const char *xpath_from_fmt,
+ const char *xpath_to_fmt)
+{
+ struct yang_mapping_node *mapping, s;
+
+ yang_snode_get_path(snode, YANG_PATH_DATA, s.xpath_from_canonical,
+ sizeof(s.xpath_from_canonical));
+ mapping = hash_get(translator->mappings[dir], &s,
+ yang_mapping_hash_alloc);
+ strlcpy(mapping->xpath_from_fmt, xpath_from_fmt,
+ sizeof(mapping->xpath_from_fmt));
+ strlcpy(mapping->xpath_to_fmt, xpath_to_fmt,
+ sizeof(mapping->xpath_to_fmt));
+ str_replace(mapping->xpath_from_fmt, "KEY1", "%[^']");
+ str_replace(mapping->xpath_from_fmt, "KEY2", "%[^']");
+ str_replace(mapping->xpath_from_fmt, "KEY3", "%[^']");
+ str_replace(mapping->xpath_from_fmt, "KEY4", "%[^']");
+ str_replace(mapping->xpath_to_fmt, "KEY1", "%s");
+ str_replace(mapping->xpath_to_fmt, "KEY2", "%s");
+ str_replace(mapping->xpath_to_fmt, "KEY3", "%s");
+ str_replace(mapping->xpath_to_fmt, "KEY4", "%s");
+}
+
+struct yang_translator *yang_translator_load(const char *path)
+{
+ struct yang_translator *translator;
+ struct yang_tmodule *tmodule;
+ const char *family;
+ struct lyd_node *dnode;
+ struct ly_set *set;
+ struct listnode *ln;
+
+ /* Load module translator (JSON file). */
+ dnode = lyd_parse_path(ly_translator_ctx, path, LYD_JSON,
+ LYD_OPT_CONFIG);
+ if (!dnode) {
+ flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD,
+ "%s: lyd_parse_path() failed", __func__);
+ return NULL;
+ }
+ dnode = yang_dnode_get(dnode,
+ "/frr-module-translator:frr-module-translator");
+ /*
+ * libyang guarantees the "frr-module-translator" top-level container is
+ * always present since it contains mandatory child nodes.
+ */
+ assert(dnode);
+
+ family = yang_dnode_get_string(dnode, "./family");
+ translator = yang_translator_find(family);
+ if (translator != NULL) {
+ flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD,
+ "%s: module translator \"%s\" is loaded already",
+ __func__, family);
+ return NULL;
+ }
+
+ translator = XCALLOC(MTYPE_YANG_TRANSLATOR, sizeof(*translator));
+ strlcpy(translator->family, family, sizeof(translator->family));
+ translator->modules = list_new();
+ for (size_t i = 0; i < YANG_TRANSLATE_MAX; i++)
+ translator->mappings[i] = hash_create(yang_mapping_hash_key,
+ yang_mapping_hash_cmp,
+ "YANG translation table");
+ RB_INSERT(yang_translators, &yang_translators, translator);
+
+ /* Initialize the translator libyang context. */
+ translator->ly_ctx = ly_ctx_new(NULL, LY_CTX_DISABLE_SEARCHDIR_CWD);
+ if (!translator->ly_ctx) {
+ flog_warn(EC_LIB_LIBYANG, "%s: ly_ctx_new() failed", __func__);
+ goto error;
+ }
+ ly_ctx_set_searchdir(translator->ly_ctx, YANG_MODELS_PATH);
+
+ /* Load modules and deviations. */
+ set = lyd_find_path(dnode, "./module");
+ assert(set);
+ for (size_t i = 0; i < set->number; i++) {
+ const char *module_name;
+
+ tmodule =
+ XCALLOC(MTYPE_YANG_TRANSLATOR_MODULE, sizeof(*tmodule));
+
+ module_name = yang_dnode_get_string(set->set.d[i], "./name");
+ tmodule->module = ly_ctx_load_module(translator->ly_ctx,
+ module_name, NULL);
+ if (!tmodule->module) {
+ flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD,
+ "%s: failed to load module: %s", __func__,
+ module_name);
+ ly_set_free(set);
+ goto error;
+ }
+
+ module_name =
+ yang_dnode_get_string(set->set.d[i], "./deviations");
+ tmodule->deviations = ly_ctx_load_module(translator->ly_ctx,
+ module_name, NULL);
+ if (!tmodule->deviations) {
+ flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD,
+ "%s: failed to load module: %s", __func__,
+ module_name);
+ ly_set_free(set);
+ goto error;
+ }
+ lys_set_disabled(tmodule->deviations);
+
+ listnode_add(translator->modules, tmodule);
+ }
+ ly_set_free(set);
+
+ /* Calculate the coverage. */
+ for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) {
+ tmodule->nodes_before_deviations =
+ yang_module_nodes_count(tmodule->module);
+
+ lys_set_enabled(tmodule->deviations);
+
+ tmodule->nodes_after_deviations =
+ yang_module_nodes_count(tmodule->module);
+ tmodule->coverage = ((double)tmodule->nodes_after_deviations
+ / (double)tmodule->nodes_before_deviations)
+ * 100;
+ }
+
+ /* Load mappings. */
+ set = lyd_find_path(dnode, "./module/mappings");
+ assert(set);
+ for (size_t i = 0; i < set->number; i++) {
+ const char *xpath_custom, *xpath_native;
+ const struct lys_node *snode_custom, *snode_native;
+
+ xpath_custom = yang_dnode_get_string(set->set.d[i], "./custom");
+ snode_custom = ly_ctx_get_node(translator->ly_ctx, NULL,
+ xpath_custom, 0);
+ if (!snode_custom) {
+ flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD,
+ "%s: unknown data path: %s", __func__,
+ xpath_custom);
+ ly_set_free(set);
+ goto error;
+ }
+
+ xpath_native = yang_dnode_get_string(set->set.d[i], "./native");
+ snode_native =
+ ly_ctx_get_node(ly_native_ctx, NULL, xpath_native, 0);
+ if (!snode_native) {
+ flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD,
+ "%s: unknown data path: %s", __func__,
+ xpath_native);
+ ly_set_free(set);
+ goto error;
+ }
+
+ yang_mapping_add(translator, YANG_TRANSLATE_TO_NATIVE,
+ snode_custom, xpath_custom, xpath_native);
+ yang_mapping_add(translator, YANG_TRANSLATE_FROM_NATIVE,
+ snode_native, xpath_native, xpath_custom);
+ }
+ ly_set_free(set);
+
+ /* Validate mappings. */
+ if (yang_translator_validate(translator) != 0)
+ goto error;
+
+ yang_dnode_free(dnode);
+
+ return translator;
+
+error:
+ yang_dnode_free(dnode);
+ yang_translator_unload(translator);
+
+ return NULL;
+}
+
+static void yang_tmodule_delete(struct yang_tmodule *tmodule)
+{
+ XFREE(MTYPE_YANG_TRANSLATOR_MODULE, tmodule);
+}
+
+void yang_translator_unload(struct yang_translator *translator)
+{
+ for (size_t i = 0; i < YANG_TRANSLATE_MAX; i++)
+ hash_clean(translator->mappings[i], yang_mapping_hash_free);
+ translator->modules->del = (void (*)(void *))yang_tmodule_delete;
+ list_delete(&translator->modules);
+ ly_ctx_destroy(translator->ly_ctx, NULL);
+ RB_REMOVE(yang_translators, &yang_translators, translator);
+ XFREE(MTYPE_YANG_TRANSLATOR, translator);
+}
+
+struct yang_translator *yang_translator_find(const char *family)
+{
+ struct yang_translator s;
+
+ strlcpy(s.family, family, sizeof(s.family));
+ return RB_FIND(yang_translators, &yang_translators, &s);
+}
+
+enum yang_translate_result
+yang_translate_xpath(const struct yang_translator *translator, int dir,
+ char *xpath, size_t xpath_len)
+{
+ struct ly_ctx *ly_ctx;
+ const struct lys_node *snode;
+ struct yang_mapping_node *mapping;
+ char xpath_canonical[XPATH_MAXLEN];
+ char keys[4][LIST_MAXKEYLEN];
+ int n;
+
+ if (dir == YANG_TRANSLATE_TO_NATIVE)
+ ly_ctx = translator->ly_ctx;
+ else
+ ly_ctx = ly_native_ctx;
+
+ snode = ly_ctx_get_node(ly_ctx, NULL, xpath, 0);
+ if (!snode) {
+ flog_warn(EC_LIB_YANG_TRANSLATION_ERROR,
+ "%s: unknown data path: %s", __func__, xpath);
+ return YANG_TRANSLATE_FAILURE;
+ }
+
+ yang_snode_get_path(snode, YANG_PATH_DATA, xpath_canonical,
+ sizeof(xpath_canonical));
+ mapping = yang_mapping_lookup(translator, dir, xpath_canonical);
+ if (!mapping)
+ return YANG_TRANSLATE_NOTFOUND;
+
+ n = sscanf(xpath, mapping->xpath_from_fmt, keys[0], keys[1], keys[2],
+ keys[3]);
+ if (n < 0) {
+ flog_warn(EC_LIB_YANG_TRANSLATION_ERROR,
+ "%s: sscanf() failed: %s", __func__,
+ safe_strerror(errno));
+ return YANG_TRANSLATE_FAILURE;
+ }
+
+ snprintf(xpath, xpath_len, mapping->xpath_to_fmt, keys[0], keys[1],
+ keys[2], keys[3]);
+
+ return YANG_TRANSLATE_SUCCESS;
+}
+
+int yang_translate_dnode(const struct yang_translator *translator, int dir,
+ struct lyd_node **dnode)
+{
+ struct ly_ctx *ly_ctx;
+ struct lyd_node *new;
+ struct lyd_node *root, *next, *dnode_iter;
+
+ /* Create new libyang data node to hold the translated data. */
+ if (dir == YANG_TRANSLATE_TO_NATIVE)
+ ly_ctx = ly_native_ctx;
+ else
+ ly_ctx = translator->ly_ctx;
+ new = yang_dnode_new(ly_ctx);
+
+ /* Iterate over all nodes from the data tree. */
+ LY_TREE_FOR (*dnode, root) {
+ LY_TREE_DFS_BEGIN (root, next, dnode_iter) {
+ char xpath[XPATH_MAXLEN];
+ enum yang_translate_result ret;
+
+ yang_dnode_get_path(dnode_iter, xpath, sizeof(xpath));
+ ret = yang_translate_xpath(translator, dir, xpath,
+ sizeof(xpath));
+ switch (ret) {
+ case YANG_TRANSLATE_SUCCESS:
+ break;
+ case YANG_TRANSLATE_NOTFOUND:
+ goto next;
+ case YANG_TRANSLATE_FAILURE:
+ goto error;
+ }
+
+ /* Create new node in the tree of translated data. */
+ ly_errno = 0;
+ if (!lyd_new_path(new, ly_ctx, xpath,
+ (void *)yang_dnode_get_string(
+ dnode_iter, NULL),
+ 0, LYD_PATH_OPT_UPDATE)
+ && ly_errno) {
+ flog_err(EC_LIB_LIBYANG,
+ "%s: lyd_new_path() failed", __func__);
+ goto error;
+ }
+
+ next:
+ LY_TREE_DFS_END(root, next, dnode_iter);
+ }
+ }
+
+ /* Replace dnode by the new translated dnode. */
+ yang_dnode_free(*dnode);
+ *dnode = new;
+
+ return YANG_TRANSLATE_SUCCESS;
+
+error:
+ yang_dnode_free(new);
+
+ return YANG_TRANSLATE_FAILURE;
+}
+
+static void yang_translator_validate_cb(const struct lys_node *snode_custom,
+ void *arg1, void *arg2)
+{
+ struct yang_translator *translator = arg1;
+ unsigned int *errors = arg2;
+ struct yang_mapping_node *mapping;
+ const struct lys_node *snode_native;
+ const struct lys_type *stype_custom, *stype_native;
+ char xpath[XPATH_MAXLEN];
+
+ yang_snode_get_path(snode_custom, YANG_PATH_DATA, xpath, sizeof(xpath));
+ mapping = yang_mapping_lookup(translator, YANG_TRANSLATE_TO_NATIVE,
+ xpath);
+ if (!mapping) {
+ flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD,
+ "%s: missing mapping for \"%s\"", __func__, xpath);
+ *errors += 1;
+ return;
+ }
+
+ snode_native =
+ ly_ctx_get_node(ly_native_ctx, NULL, mapping->xpath_to_fmt, 0);
+ assert(snode_native);
+
+ /* Check if the YANG types are compatible. */
+ stype_custom = yang_snode_get_type(snode_custom);
+ stype_native = yang_snode_get_type(snode_native);
+ if (stype_custom && stype_native) {
+ if (stype_custom->base != stype_native->base) {
+ flog_warn(
+ EC_LIB_YANG_TRANSLATOR_LOAD,
+ "%s: YANG types are incompatible (xpath: \"%s\")",
+ __func__, xpath);
+ *errors += 1;
+ return;
+ }
+
+ /* TODO: check if the value spaces are identical. */
+ }
+}
+
+/*
+ * Check if the modules from the translator have a mapping for all of their
+ * schema nodes (after loading the deviations).
+ */
+static unsigned int yang_translator_validate(struct yang_translator *translator)
+{
+ struct yang_tmodule *tmodule;
+ struct listnode *ln;
+ unsigned int errors = 0;
+
+ for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) {
+ yang_module_snodes_iterate(
+ tmodule->module, yang_translator_validate_cb,
+ YANG_ITER_FILTER_NPCONTAINERS
+ | YANG_ITER_FILTER_LIST_KEYS
+ | YANG_ITER_FILTER_INPUT_OUTPUT,
+ translator, &errors);
+ }
+
+ if (errors)
+ flog_warn(
+ EC_LIB_YANG_TRANSLATOR_LOAD,
+ "%s: failed to validate \"%s\" module translator: %u error(s)",
+ __func__, translator->family, errors);
+
+ return errors;
+}
+
+static void yang_module_nodes_count_cb(const struct lys_node *snode, void *arg1,
+ void *arg2)
+{
+ unsigned int *total = arg1;
+
+ *total += 1;
+}
+
+/* Calculate the number of nodes for the given module. */
+static unsigned int yang_module_nodes_count(const struct lys_module *module)
+{
+ unsigned int total = 0;
+
+ yang_module_snodes_iterate(module, yang_module_nodes_count_cb,
+ YANG_ITER_FILTER_NPCONTAINERS
+ | YANG_ITER_FILTER_LIST_KEYS
+ | YANG_ITER_FILTER_INPUT_OUTPUT,
+ &total, NULL);
+
+ return total;
+}
+
+/* TODO: rewrite this function. */
+static void str_replace(char *o_string, const char *s_string,
+ const char *r_string)
+{
+ char buffer[BUFSIZ];
+ char *ch;
+
+ ch = strstr(o_string, s_string);
+ if (!ch)
+ return;
+
+ strncpy(buffer, o_string, ch - o_string);
+ buffer[ch - o_string] = 0;
+
+ sprintf(buffer + (ch - o_string), "%s%s", r_string,
+ ch + strlen(s_string));
+
+ o_string[0] = 0;
+ strcpy(o_string, buffer);
+ return str_replace(o_string, s_string, r_string);
+}
+
+void yang_translator_init(void)
+{
+ ly_translator_ctx = ly_ctx_new(NULL, LY_CTX_DISABLE_SEARCHDIR_CWD);
+ if (!ly_translator_ctx) {
+ flog_err(EC_LIB_LIBYANG, "%s: ly_ctx_new() failed", __func__);
+ exit(1);
+ }
+ ly_ctx_set_searchdir(ly_translator_ctx, YANG_MODELS_PATH);
+
+ if (!ly_ctx_load_module(ly_translator_ctx, "frr-module-translator",
+ NULL)) {
+ flog_err(
+ EC_LIB_YANG_MODULE_LOAD,
+ "%s: failed to load the \"frr-module-translator\" module",
+ __func__);
+ exit(1);
+ }
+}
+
+void yang_translator_terminate(void)
+{
+ while (!RB_EMPTY(yang_translators, &yang_translators)) {
+ struct yang_translator *translator;
+
+ translator = RB_ROOT(yang_translators, &yang_translators);
+ yang_translator_unload(translator);
+ }
+
+ ly_ctx_destroy(ly_translator_ctx, NULL);
+}
--- /dev/null
+/*
+ * Copyright (C) 2018 NetDEF, Inc.
+ * Renato Westphal
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _FRR_YANG_TRANSLATOR_H_
+#define _FRR_YANG_TRANSLATOR_H_
+
+#define YANG_TRANSLATE_TO_NATIVE 0
+#define YANG_TRANSLATE_FROM_NATIVE 1
+#define YANG_TRANSLATE_MAX 2
+
+struct yang_tmodule {
+ const struct lys_module *module;
+ const struct lys_module *deviations;
+ uint32_t nodes_before_deviations;
+ uint32_t nodes_after_deviations;
+ double coverage;
+};
+
+struct yang_translator {
+ RB_ENTRY(yang_translator) entry;
+ char family[32];
+ struct ly_ctx *ly_ctx;
+ struct list *modules;
+ struct hash *mappings[YANG_TRANSLATE_MAX];
+};
+RB_HEAD(yang_translators, yang_translator);
+RB_PROTOTYPE(yang_translators, yang_translator, entry, yang_translator_compare);
+
+enum yang_translate_result {
+ YANG_TRANSLATE_SUCCESS,
+ YANG_TRANSLATE_NOTFOUND,
+ YANG_TRANSLATE_FAILURE,
+};
+
+/* Tree of all loaded YANG module translators. */
+extern struct yang_translators yang_translators;
+
+/*
+ * Load a YANG module translator from a JSON file.
+ *
+ * path
+ * Absolute path to the module translator file.
+ *
+ * Returns:
+ * Pointer to newly created YANG module translator, or NULL in the case of an
+ * error.
+ */
+extern struct yang_translator *yang_translator_load(const char *path);
+
+/*
+ * Unload a YANG module translator.
+ *
+ * translator
+ * Pointer to the YANG module translator.
+ */
+extern void yang_translator_unload(struct yang_translator *translator);
+
+/*
+ * Find a YANG module translator by its family name.
+ *
+ * family
+ * Family of the YANG module translator (e.g. ietf, openconfig).
+ *
+ * Returns:
+ * Pointer to the YANG module translator if found, NULL otherwise.
+ */
+extern struct yang_translator *yang_translator_find(const char *family);
+
+/*
+ * Translate an XPath expression.
+ *
+ * translator
+ * Pointer to YANG module translator.
+ *
+ * dir
+ * Direction of the translation (either YANG_TRANSLATE_TO_NATIVE or
+ * YANG_TRANSLATE_FROM_NATIVE).
+ *
+ * xpath
+ * Pointer to previously allocated buffer containing the xpath expression to
+ * be translated.
+ *
+ * xpath_len
+ * Size of the xpath buffer.
+ *
+ * Returns:
+ * - YANG_TRANSLATE_SUCCESS on success.
+ * - YANG_TRANSLATE_NOTFOUND when there's no available mapping to perform
+ * the translation.
+ * - YANG_TRANSLATE_FAILURE when an error occurred during the translation.
+ */
+extern enum yang_translate_result
+yang_translate_xpath(const struct yang_translator *translator, int dir,
+ char *xpath, size_t xpath_len);
+
+/*
+ * Translate an entire libyang data node.
+ *
+ * translator
+ * Pointer to YANG module translator.
+ *
+ * dir
+ * Direction of the translation (either YANG_TRANSLATE_TO_NATIVE or
+ * YANG_TRANSLATE_FROM_NATIVE).
+ *
+ * dnode
+ * libyang schema node we want to translate.
+ *
+ * Returns:
+ * - YANG_TRANSLATE_SUCCESS on success.
+ * - YANG_TRANSLATE_FAILURE when an error occurred during the translation.
+ */
+extern int yang_translate_dnode(const struct yang_translator *translator,
+ int dir, struct lyd_node **dnode);
+
+/*
+ * Initialize the YANG module translator subsystem. Should be called only once
+ * during the daemon initialization process.
+ */
+extern void yang_translator_init(void);
+
+/*
+ * Finish the YANG module translator subsystem gracefully. Should be called only
+ * when the daemon is exiting.
+ */
+extern void yang_translator_terminate(void);
+
+#endif /* _FRR_YANG_TRANSLATOR_H_ */
--- /dev/null
+/*
+ * Copyright (C) 2018 NetDEF, Inc.
+ * Renato Westphal
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <zebra.h>
+
+#include "log.h"
+#include "lib_errors.h"
+#include "northbound.h"
+
+static const char *yang_get_default_value(const char *xpath)
+{
+ const struct lys_node *snode;
+ const char *value;
+
+ snode = ly_ctx_get_node(ly_native_ctx, NULL, xpath, 0);
+ if (snode == NULL) {
+ flog_err(EC_LIB_YANG_UNKNOWN_DATA_PATH,
+ "%s: unknown data path: %s", __func__, xpath);
+ zlog_backtrace(LOG_ERR);
+ abort();
+ }
+
+ value = yang_snode_get_default(snode);
+ assert(value);
+
+ return value;
+}
+
+#define YANG_DNODE_GET_ASSERT(dnode, xpath) \
+ do { \
+ if ((dnode) == NULL) { \
+ flog_err(EC_LIB_YANG_DNODE_NOT_FOUND, \
+ "%s: couldn't find %s", __func__, (xpath)); \
+ zlog_backtrace(LOG_ERR); \
+ abort(); \
+ } \
+ } while (0)
+
+/*
+ * Primitive type: bool.
+ */
+bool yang_str2bool(const char *value)
+{
+ return strmatch(value, "true");
+}
+
+struct yang_data *yang_data_new_bool(const char *xpath, bool value)
+{
+ return yang_data_new(xpath, (value == true) ? "true" : "false");
+}
+
+bool yang_dnode_get_bool(const struct lyd_node *dnode, const char *xpath_fmt,
+ ...)
+{
+ const struct lyd_node_leaf_list *dleaf;
+
+ assert(dnode);
+ if (xpath_fmt) {
+ va_list ap;
+ char xpath[XPATH_MAXLEN];
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+ dnode = yang_dnode_get(dnode, xpath);
+ YANG_DNODE_GET_ASSERT(dnode, xpath);
+ }
+
+ dleaf = (const struct lyd_node_leaf_list *)dnode;
+ assert(dleaf->value_type == LY_TYPE_BOOL);
+ return dleaf->value.bln;
+}
+
+bool yang_get_default_bool(const char *xpath_fmt, ...)
+{
+ char xpath[XPATH_MAXLEN];
+ const char *value;
+ va_list ap;
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+
+ value = yang_get_default_value(xpath);
+ return yang_str2bool(value);
+}
+
+/*
+ * Primitive type: dec64.
+ */
+double yang_str2dec64(const char *xpath, const char *value)
+{
+ double dbl = 0;
+
+ if (sscanf(value, "%lf", &dbl) != 1) {
+ flog_err(EC_LIB_YANG_DATA_CONVERT,
+ "%s: couldn't convert string to decimal64 [xpath %s]",
+ __func__, xpath);
+ zlog_backtrace(LOG_ERR);
+ abort();
+ }
+
+ return dbl;
+}
+
+struct yang_data *yang_data_new_dec64(const char *xpath, double value)
+{
+ char value_str[BUFSIZ];
+
+ snprintf(value_str, sizeof(value_str), "%lf", value);
+ return yang_data_new(xpath, value_str);
+}
+
+double yang_dnode_get_dec64(const struct lyd_node *dnode, const char *xpath_fmt,
+ ...)
+{
+ const struct lyd_node_leaf_list *dleaf;
+
+ assert(dnode);
+ if (xpath_fmt) {
+ va_list ap;
+ char xpath[XPATH_MAXLEN];
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+ dnode = yang_dnode_get(dnode, xpath);
+ YANG_DNODE_GET_ASSERT(dnode, xpath);
+ }
+
+ dleaf = (const struct lyd_node_leaf_list *)dnode;
+ assert(dleaf->value_type == LY_TYPE_DEC64);
+
+ return lyd_dec64_to_double(dnode);
+}
+
+double yang_get_default_dec64(const char *xpath_fmt, ...)
+{
+ char xpath[XPATH_MAXLEN];
+ const char *value;
+ va_list ap;
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+
+ value = yang_get_default_value(xpath);
+ return yang_str2dec64(xpath, value);
+}
+
+/*
+ * Primitive type: enum.
+ */
+int yang_str2enum(const char *xpath, const char *value)
+{
+ const struct lys_node *snode;
+ const struct lys_node_leaf *sleaf;
+ const struct lys_type_info_enums *enums;
+
+ snode = ly_ctx_get_node(ly_native_ctx, NULL, xpath, 0);
+ if (snode == NULL) {
+ flog_err(EC_LIB_YANG_UNKNOWN_DATA_PATH,
+ "%s: unknown data path: %s", __func__, xpath);
+ zlog_backtrace(LOG_ERR);
+ abort();
+ }
+
+ sleaf = (const struct lys_node_leaf *)snode;
+ enums = &sleaf->type.info.enums;
+ for (unsigned int i = 0; i < enums->count; i++) {
+ const struct lys_type_enum *enm = &enums->enm[i];
+
+ if (strmatch(value, enm->name))
+ return enm->value;
+ }
+
+ flog_err(EC_LIB_YANG_DATA_CONVERT,
+ "%s: couldn't convert string to enum [xpath %s]", __func__,
+ xpath);
+ zlog_backtrace(LOG_ERR);
+ abort();
+}
+
+struct yang_data *yang_data_new_enum(const char *xpath, int value)
+{
+ const struct lys_node *snode;
+ const struct lys_node_leaf *sleaf;
+ const struct lys_type_info_enums *enums;
+
+ snode = ly_ctx_get_node(ly_native_ctx, NULL, xpath, 0);
+ if (snode == NULL) {
+ flog_err(EC_LIB_YANG_UNKNOWN_DATA_PATH,
+ "%s: unknown data path: %s", __func__, xpath);
+ zlog_backtrace(LOG_ERR);
+ abort();
+ }
+
+ sleaf = (const struct lys_node_leaf *)snode;
+ enums = &sleaf->type.info.enums;
+ for (unsigned int i = 0; i < enums->count; i++) {
+ const struct lys_type_enum *enm = &enums->enm[i];
+
+ if (value == enm->value)
+ return yang_data_new(xpath, enm->name);
+ }
+
+ flog_err(EC_LIB_YANG_DATA_CONVERT,
+ "%s: couldn't convert enum to string [xpath %s]", __func__,
+ xpath);
+ zlog_backtrace(LOG_ERR);
+ abort();
+}
+
+int yang_dnode_get_enum(const struct lyd_node *dnode, const char *xpath_fmt,
+ ...)
+{
+ const struct lyd_node_leaf_list *dleaf;
+
+ assert(dnode);
+ if (xpath_fmt) {
+ va_list ap;
+ char xpath[XPATH_MAXLEN];
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+ dnode = yang_dnode_get(dnode, xpath);
+ YANG_DNODE_GET_ASSERT(dnode, xpath);
+ }
+
+ dleaf = (const struct lyd_node_leaf_list *)dnode;
+ assert(dleaf->value_type == LY_TYPE_ENUM);
+ return dleaf->value.enm->value;
+}
+
+int yang_get_default_enum(const char *xpath_fmt, ...)
+{
+ char xpath[XPATH_MAXLEN];
+ const char *value;
+ va_list ap;
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+
+ value = yang_get_default_value(xpath);
+ return yang_str2enum(xpath, value);
+}
+
+/*
+ * Primitive type: int8.
+ */
+int8_t yang_str2int8(const char *value)
+{
+ return strtol(value, NULL, 10);
+}
+
+struct yang_data *yang_data_new_int8(const char *xpath, int8_t value)
+{
+ char value_str[BUFSIZ];
+
+ snprintf(value_str, sizeof(value_str), "%d", value);
+ return yang_data_new(xpath, value_str);
+}
+
+int8_t yang_dnode_get_int8(const struct lyd_node *dnode, const char *xpath_fmt,
+ ...)
+{
+ const struct lyd_node_leaf_list *dleaf;
+
+ assert(dnode);
+ if (xpath_fmt) {
+ va_list ap;
+ char xpath[XPATH_MAXLEN];
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+ dnode = yang_dnode_get(dnode, xpath);
+ YANG_DNODE_GET_ASSERT(dnode, xpath);
+ }
+
+ dleaf = (const struct lyd_node_leaf_list *)dnode;
+ assert(dleaf->value_type == LY_TYPE_INT8);
+ return dleaf->value.int8;
+}
+
+int8_t yang_get_default_int8(const char *xpath_fmt, ...)
+{
+ char xpath[XPATH_MAXLEN];
+ const char *value;
+ va_list ap;
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+
+ value = yang_get_default_value(xpath);
+ return yang_str2int8(value);
+}
+
+/*
+ * Primitive type: int16.
+ */
+int16_t yang_str2int16(const char *value)
+{
+ return strtol(value, NULL, 10);
+}
+
+struct yang_data *yang_data_new_int16(const char *xpath, int16_t value)
+{
+ char value_str[BUFSIZ];
+
+ snprintf(value_str, sizeof(value_str), "%d", value);
+ return yang_data_new(xpath, value_str);
+}
+
+int16_t yang_dnode_get_int16(const struct lyd_node *dnode,
+ const char *xpath_fmt, ...)
+{
+ const struct lyd_node_leaf_list *dleaf;
+
+ assert(dnode);
+ if (xpath_fmt) {
+ va_list ap;
+ char xpath[XPATH_MAXLEN];
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+ dnode = yang_dnode_get(dnode, xpath);
+ YANG_DNODE_GET_ASSERT(dnode, xpath);
+ }
+
+ dleaf = (const struct lyd_node_leaf_list *)dnode;
+ assert(dleaf->value_type == LY_TYPE_INT16);
+ return dleaf->value.int16;
+}
+
+int16_t yang_get_default_int16(const char *xpath_fmt, ...)
+{
+ char xpath[XPATH_MAXLEN];
+ const char *value;
+ va_list ap;
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+
+ value = yang_get_default_value(xpath);
+ return yang_str2int16(value);
+}
+
+/*
+ * Primitive type: int32.
+ */
+int32_t yang_str2int32(const char *value)
+{
+ return strtol(value, NULL, 10);
+}
+
+struct yang_data *yang_data_new_int32(const char *xpath, int32_t value)
+{
+ char value_str[BUFSIZ];
+
+ snprintf(value_str, sizeof(value_str), "%d", value);
+ return yang_data_new(xpath, value_str);
+}
+
+int32_t yang_dnode_get_int32(const struct lyd_node *dnode,
+ const char *xpath_fmt, ...)
+{
+ const struct lyd_node_leaf_list *dleaf;
+
+ assert(dnode);
+ if (xpath_fmt) {
+ va_list ap;
+ char xpath[XPATH_MAXLEN];
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+ dnode = yang_dnode_get(dnode, xpath);
+ YANG_DNODE_GET_ASSERT(dnode, xpath);
+ }
+
+ dleaf = (const struct lyd_node_leaf_list *)dnode;
+ assert(dleaf->value_type == LY_TYPE_INT32);
+ return dleaf->value.int32;
+}
+
+int32_t yang_get_default_int32(const char *xpath_fmt, ...)
+{
+ char xpath[XPATH_MAXLEN];
+ const char *value;
+ va_list ap;
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+
+ value = yang_get_default_value(xpath);
+ return yang_str2int32(value);
+}
+
+/*
+ * Primitive type: int64.
+ */
+int64_t yang_str2int64(const char *value)
+{
+ return strtoll(value, NULL, 10);
+}
+
+struct yang_data *yang_data_new_int64(const char *xpath, int64_t value)
+{
+ char value_str[BUFSIZ];
+
+ snprintf(value_str, sizeof(value_str), "%" PRId64, value);
+ return yang_data_new(xpath, value_str);
+}
+
+int64_t yang_dnode_get_int64(const struct lyd_node *dnode,
+ const char *xpath_fmt, ...)
+{
+ const struct lyd_node_leaf_list *dleaf;
+
+ assert(dnode);
+ if (xpath_fmt) {
+ va_list ap;
+ char xpath[XPATH_MAXLEN];
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+ dnode = yang_dnode_get(dnode, xpath);
+ YANG_DNODE_GET_ASSERT(dnode, xpath);
+ }
+
+ dleaf = (const struct lyd_node_leaf_list *)dnode;
+ assert(dleaf->value_type == LY_TYPE_INT64);
+ return dleaf->value.int64;
+}
+
+int64_t yang_get_default_int64(const char *xpath_fmt, ...)
+{
+ char xpath[XPATH_MAXLEN];
+ const char *value;
+ va_list ap;
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+
+ value = yang_get_default_value(xpath);
+ return yang_str2int64(value);
+}
+
+/*
+ * Primitive type: uint8.
+ */
+uint8_t yang_str2uint8(const char *value)
+{
+ return strtoul(value, NULL, 10);
+}
+
+struct yang_data *yang_data_new_uint8(const char *xpath, uint8_t value)
+{
+ char value_str[BUFSIZ];
+
+ snprintf(value_str, sizeof(value_str), "%u", value);
+ return yang_data_new(xpath, value_str);
+}
+
+uint8_t yang_dnode_get_uint8(const struct lyd_node *dnode,
+ const char *xpath_fmt, ...)
+{
+ const struct lyd_node_leaf_list *dleaf;
+
+ assert(dnode);
+ if (xpath_fmt) {
+ va_list ap;
+ char xpath[XPATH_MAXLEN];
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+ dnode = yang_dnode_get(dnode, xpath);
+ YANG_DNODE_GET_ASSERT(dnode, xpath);
+ }
+
+ dleaf = (const struct lyd_node_leaf_list *)dnode;
+ assert(dleaf->value_type == LY_TYPE_UINT8);
+ return dleaf->value.uint8;
+}
+
+uint8_t yang_get_default_uint8(const char *xpath_fmt, ...)
+{
+ char xpath[XPATH_MAXLEN];
+ const char *value;
+ va_list ap;
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+
+ value = yang_get_default_value(xpath);
+ return yang_str2uint8(value);
+}
+
+/*
+ * Primitive type: uint16.
+ */
+uint16_t yang_str2uint16(const char *value)
+{
+ return strtoul(value, NULL, 10);
+}
+
+struct yang_data *yang_data_new_uint16(const char *xpath, uint16_t value)
+{
+ char value_str[BUFSIZ];
+
+ snprintf(value_str, sizeof(value_str), "%u", value);
+ return yang_data_new(xpath, value_str);
+}
+
+uint16_t yang_dnode_get_uint16(const struct lyd_node *dnode,
+ const char *xpath_fmt, ...)
+{
+ const struct lyd_node_leaf_list *dleaf;
+
+ assert(dnode);
+ if (xpath_fmt) {
+ va_list ap;
+ char xpath[XPATH_MAXLEN];
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+ dnode = yang_dnode_get(dnode, xpath);
+ YANG_DNODE_GET_ASSERT(dnode, xpath);
+ }
+
+ dleaf = (const struct lyd_node_leaf_list *)dnode;
+ assert(dleaf->value_type == LY_TYPE_UINT16);
+ return dleaf->value.uint16;
+}
+
+uint16_t yang_get_default_uint16(const char *xpath_fmt, ...)
+{
+ char xpath[XPATH_MAXLEN];
+ const char *value;
+ va_list ap;
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+
+ value = yang_get_default_value(xpath);
+ return yang_str2uint16(value);
+}
+
+/*
+ * Primitive type: uint32.
+ */
+uint32_t yang_str2uint32(const char *value)
+{
+ return strtoul(value, NULL, 10);
+}
+
+struct yang_data *yang_data_new_uint32(const char *xpath, uint32_t value)
+{
+ char value_str[BUFSIZ];
+
+ snprintf(value_str, sizeof(value_str), "%u", value);
+ return yang_data_new(xpath, value_str);
+}
+
+uint32_t yang_dnode_get_uint32(const struct lyd_node *dnode,
+ const char *xpath_fmt, ...)
+{
+ const struct lyd_node_leaf_list *dleaf;
+
+ assert(dnode);
+ if (xpath_fmt) {
+ va_list ap;
+ char xpath[XPATH_MAXLEN];
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+ dnode = yang_dnode_get(dnode, xpath);
+ YANG_DNODE_GET_ASSERT(dnode, xpath);
+ }
+
+ dleaf = (const struct lyd_node_leaf_list *)dnode;
+ assert(dleaf->value_type == LY_TYPE_UINT32);
+ return dleaf->value.uint32;
+}
+
+uint32_t yang_get_default_uint32(const char *xpath_fmt, ...)
+{
+ char xpath[XPATH_MAXLEN];
+ const char *value;
+ va_list ap;
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+
+ value = yang_get_default_value(xpath);
+ return yang_str2uint32(value);
+}
+
+/*
+ * Primitive type: uint64.
+ */
+uint64_t yang_str2uint64(const char *value)
+{
+ return strtoull(value, NULL, 10);
+}
+
+struct yang_data *yang_data_new_uint64(const char *xpath, uint64_t value)
+{
+ char value_str[BUFSIZ];
+
+ snprintf(value_str, sizeof(value_str), "%" PRIu64, value);
+ return yang_data_new(xpath, value_str);
+}
+
+uint64_t yang_dnode_get_uint64(const struct lyd_node *dnode,
+ const char *xpath_fmt, ...)
+{
+ const struct lyd_node_leaf_list *dleaf;
+
+ assert(dnode);
+ if (xpath_fmt) {
+ va_list ap;
+ char xpath[XPATH_MAXLEN];
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+ dnode = yang_dnode_get(dnode, xpath);
+ YANG_DNODE_GET_ASSERT(dnode, xpath);
+ }
+
+ dleaf = (const struct lyd_node_leaf_list *)dnode;
+ assert(dleaf->value_type == LY_TYPE_UINT64);
+ return dleaf->value.uint64;
+}
+
+uint64_t yang_get_default_uint64(const char *xpath_fmt, ...)
+{
+ char xpath[XPATH_MAXLEN];
+ const char *value;
+ va_list ap;
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+
+ value = yang_get_default_value(xpath);
+ return yang_str2uint64(value);
+}
+
+/*
+ * Primitive type: string.
+ *
+ * All string wrappers can be used with non-string types.
+ */
+struct yang_data *yang_data_new_string(const char *xpath, const char *value)
+{
+ return yang_data_new(xpath, value);
+}
+
+const char *yang_dnode_get_string(const struct lyd_node *dnode,
+ const char *xpath_fmt, ...)
+{
+ const struct lyd_node_leaf_list *dleaf;
+
+ assert(dnode);
+ if (xpath_fmt) {
+ va_list ap;
+ char xpath[XPATH_MAXLEN];
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+ dnode = yang_dnode_get(dnode, xpath);
+ YANG_DNODE_GET_ASSERT(dnode, xpath);
+ }
+
+ dleaf = (const struct lyd_node_leaf_list *)dnode;
+ return dleaf->value_str;
+}
+
+void yang_dnode_get_string_buf(char *buf, size_t size,
+ const struct lyd_node *dnode,
+ const char *xpath_fmt, ...)
+{
+ const struct lyd_node_leaf_list *dleaf;
+
+ assert(dnode);
+ if (xpath_fmt) {
+ va_list ap;
+ char xpath[XPATH_MAXLEN];
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+ dnode = yang_dnode_get(dnode, xpath);
+ YANG_DNODE_GET_ASSERT(dnode, xpath);
+ }
+
+ dleaf = (const struct lyd_node_leaf_list *)dnode;
+ if (strlcpy(buf, dleaf->value_str, size) >= size) {
+ char xpath[XPATH_MAXLEN];
+
+ yang_dnode_get_path(dnode, xpath, sizeof(xpath));
+ flog_warn(EC_LIB_YANG_DATA_TRUNCATED,
+ "%s: value was truncated [xpath %s]", __func__,
+ xpath);
+ }
+}
+
+const char *yang_get_default_string(const char *xpath_fmt, ...)
+{
+ char xpath[XPATH_MAXLEN];
+ va_list ap;
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+
+ return yang_get_default_value(xpath);
+}
+
+void yang_get_default_string_buf(char *buf, size_t size, const char *xpath_fmt,
+ ...)
+{
+ char xpath[XPATH_MAXLEN];
+ const char *value;
+ va_list ap;
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+
+ value = yang_get_default_value(xpath);
+ if (strlcpy(buf, value, size) >= size)
+ flog_warn(EC_LIB_YANG_DATA_TRUNCATED,
+ "%s: value was truncated [xpath %s]", __func__,
+ xpath);
+}
+
+/*
+ * Derived type: ipv4.
+ */
+void yang_str2ipv4(const char *value, struct in_addr *addr)
+{
+ (void)inet_pton(AF_INET, value, addr);
+}
+
+struct yang_data *yang_data_new_ipv4(const char *xpath,
+ const struct in_addr *addr)
+{
+ char value_str[INET_ADDRSTRLEN];
+
+ (void)inet_ntop(AF_INET, addr, value_str, sizeof(value_str));
+ return yang_data_new(xpath, value_str);
+}
+
+void yang_dnode_get_ipv4(struct in_addr *addr, const struct lyd_node *dnode,
+ const char *xpath_fmt, ...)
+{
+ const struct lyd_node_leaf_list *dleaf;
+
+ assert(dnode);
+ if (xpath_fmt) {
+ va_list ap;
+ char xpath[XPATH_MAXLEN];
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+ dnode = yang_dnode_get(dnode, xpath);
+ YANG_DNODE_GET_ASSERT(dnode, xpath);
+ }
+
+ dleaf = (const struct lyd_node_leaf_list *)dnode;
+ assert(dleaf->value_type == LY_TYPE_STRING);
+ memcpy(addr, dleaf->value.ptr, sizeof(*addr));
+}
+
+void yang_get_default_ipv4(struct in_addr *var, const char *xpath_fmt, ...)
+{
+ char xpath[XPATH_MAXLEN];
+ const char *value;
+ va_list ap;
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+
+ value = yang_get_default_value(xpath);
+ yang_str2ipv4(value, var);
+}
+
+/*
+ * Derived type: ipv4p.
+ */
+void yang_str2ipv4p(const char *value, union prefixptr prefix)
+{
+ struct prefix_ipv4 *prefix4 = prefix.p4;
+
+ (void)str2prefix_ipv4(value, prefix4);
+ apply_mask_ipv4(prefix4);
+}
+
+struct yang_data *yang_data_new_ipv4p(const char *xpath,
+ const union prefixptr prefix)
+{
+ char value_str[PREFIX2STR_BUFFER];
+
+ (void)prefix2str(prefix.p, value_str, sizeof(value_str));
+ return yang_data_new(xpath, value_str);
+}
+
+void yang_dnode_get_ipv4p(union prefixptr prefix, const struct lyd_node *dnode,
+ const char *xpath_fmt, ...)
+{
+ const struct lyd_node_leaf_list *dleaf;
+ struct prefix_ipv4 *prefix4 = prefix.p4;
+
+ assert(dnode);
+ if (xpath_fmt) {
+ va_list ap;
+ char xpath[XPATH_MAXLEN];
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+ dnode = yang_dnode_get(dnode, xpath);
+ YANG_DNODE_GET_ASSERT(dnode, xpath);
+ }
+
+ dleaf = (const struct lyd_node_leaf_list *)dnode;
+ assert(dleaf->value_type == LY_TYPE_STRING);
+ memcpy(prefix4, dleaf->value.ptr, sizeof(*prefix4));
+}
+
+void yang_get_default_ipv4p(union prefixptr var, const char *xpath_fmt, ...)
+{
+ char xpath[XPATH_MAXLEN];
+ const char *value;
+ va_list ap;
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+
+ value = yang_get_default_value(xpath);
+ yang_str2ipv4p(value, var);
+}
+
+/*
+ * Derived type: ipv6.
+ */
+void yang_str2ipv6(const char *value, struct in6_addr *addr)
+{
+ (void)inet_pton(AF_INET6, value, addr);
+}
+
+struct yang_data *yang_data_new_ipv6(const char *xpath,
+ const struct in6_addr *addr)
+{
+ char value_str[INET6_ADDRSTRLEN];
+
+ (void)inet_ntop(AF_INET6, addr, value_str, sizeof(value_str));
+ return yang_data_new(xpath, value_str);
+}
+
+void yang_dnode_get_ipv6(struct in6_addr *addr, const struct lyd_node *dnode,
+ const char *xpath_fmt, ...)
+{
+ const struct lyd_node_leaf_list *dleaf;
+
+ assert(dnode);
+ if (xpath_fmt) {
+ va_list ap;
+ char xpath[XPATH_MAXLEN];
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+ dnode = yang_dnode_get(dnode, xpath);
+ YANG_DNODE_GET_ASSERT(dnode, xpath);
+ }
+
+ dleaf = (const struct lyd_node_leaf_list *)dnode;
+ assert(dleaf->value_type == LY_TYPE_STRING);
+ memcpy(addr, dleaf->value.ptr, sizeof(*addr));
+}
+
+void yang_get_default_ipv6(struct in6_addr *var, const char *xpath_fmt, ...)
+{
+ char xpath[XPATH_MAXLEN];
+ const char *value;
+ va_list ap;
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+
+ value = yang_get_default_value(xpath);
+ yang_str2ipv6(value, var);
+}
+
+/*
+ * Derived type: ipv6p.
+ */
+void yang_str2ipv6p(const char *value, union prefixptr prefix)
+{
+ struct prefix_ipv6 *prefix6 = prefix.p6;
+
+ (void)str2prefix_ipv6(value, prefix6);
+ apply_mask_ipv6(prefix6);
+}
+
+struct yang_data *yang_data_new_ipv6p(const char *xpath,
+ const union prefixptr prefix)
+{
+ char value_str[PREFIX2STR_BUFFER];
+
+ (void)prefix2str(prefix.p, value_str, sizeof(value_str));
+ return yang_data_new(xpath, value_str);
+}
+
+void yang_dnode_get_ipv6p(union prefixptr prefix, const struct lyd_node *dnode,
+ const char *xpath_fmt, ...)
+{
+ const struct lyd_node_leaf_list *dleaf;
+ struct prefix_ipv6 *prefix6 = prefix.p6;
+
+ assert(dnode);
+ if (xpath_fmt) {
+ va_list ap;
+ char xpath[XPATH_MAXLEN];
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+ dnode = yang_dnode_get(dnode, xpath);
+ YANG_DNODE_GET_ASSERT(dnode, xpath);
+ }
+
+ dleaf = (const struct lyd_node_leaf_list *)dnode;
+ assert(dleaf->value_type == LY_TYPE_STRING);
+ memcpy(prefix6, dleaf->value.ptr, sizeof(*prefix6));
+}
+
+void yang_get_default_ipv6p(union prefixptr var, const char *xpath_fmt, ...)
+{
+ char xpath[XPATH_MAXLEN];
+ const char *value;
+ va_list ap;
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+
+ value = yang_get_default_value(xpath);
+ yang_str2ipv6p(value, var);
+}
--- /dev/null
+/*
+ * Copyright (C) 2018 NetDEF, Inc.
+ * Renato Westphal
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _FRR_NORTHBOUND_WRAPPERS_H_
+#define _FRR_NORTHBOUND_WRAPPERS_H_
+
+#include "prefix.h"
+
+/* bool */
+extern bool yang_str2bool(const char *value);
+extern struct yang_data *yang_data_new_bool(const char *xpath, bool value);
+extern bool yang_dnode_get_bool(const struct lyd_node *dnode,
+ const char *xpath_fmt, ...);
+extern bool yang_get_default_bool(const char *xpath_fmt, ...);
+
+/* dec64 */
+extern double yang_str2dec64(const char *xpath, const char *value);
+extern struct yang_data *yang_data_new_dec64(const char *xpath, double value);
+extern double yang_dnode_get_dec64(const struct lyd_node *dnode,
+ const char *xpath_fmt, ...);
+extern double yang_get_default_dec64(const char *xpath_fmt, ...);
+
+/* enum */
+extern int yang_str2enum(const char *xpath, const char *value);
+extern struct yang_data *yang_data_new_enum(const char *xpath, int value);
+extern int yang_dnode_get_enum(const struct lyd_node *dnode,
+ const char *xpath_fmt, ...);
+extern int yang_get_default_enum(const char *xpath_fmt, ...);
+
+/* int8 */
+extern int8_t yang_str2int8(const char *value);
+extern struct yang_data *yang_data_new_int8(const char *xpath, int8_t value);
+extern int8_t yang_dnode_get_int8(const struct lyd_node *dnode,
+ const char *xpath_fmt, ...);
+extern int8_t yang_get_default_int8(const char *xpath_fmt, ...);
+
+/* int16 */
+extern int16_t yang_str2int16(const char *value);
+extern struct yang_data *yang_data_new_int16(const char *xpath, int16_t value);
+extern int16_t yang_dnode_get_int16(const struct lyd_node *dnode,
+ const char *xpath_fmt, ...);
+extern int16_t yang_get_default_int16(const char *xpath_fmt, ...);
+
+/* int32 */
+extern int32_t yang_str2int32(const char *value);
+extern struct yang_data *yang_data_new_int32(const char *xpath, int32_t value);
+extern int32_t yang_dnode_get_int32(const struct lyd_node *dnode,
+ const char *xpath_fmt, ...);
+extern int32_t yang_get_default_int32(const char *xpath_fmt, ...);
+
+/* int64 */
+extern int64_t yang_str2int64(const char *value);
+extern struct yang_data *yang_data_new_int64(const char *xpath, int64_t value);
+extern int64_t yang_dnode_get_int64(const struct lyd_node *dnode,
+ const char *xpath_fmt, ...);
+extern int64_t yang_get_default_int64(const char *xpath_fmt, ...);
+
+/* uint8 */
+extern uint8_t yang_str2uint8(const char *value);
+extern struct yang_data *yang_data_new_uint8(const char *xpath, uint8_t value);
+extern uint8_t yang_dnode_get_uint8(const struct lyd_node *dnode,
+ const char *xpath_fmt, ...);
+extern uint8_t yang_get_default_uint8(const char *xpath_fmt, ...);
+
+/* uint16 */
+extern uint16_t yang_str2uint16(const char *value);
+extern struct yang_data *yang_data_new_uint16(const char *xpath,
+ uint16_t value);
+extern uint16_t yang_dnode_get_uint16(const struct lyd_node *dnode,
+ const char *xpath_fmt, ...);
+extern uint16_t yang_get_default_uint16(const char *xpath_fmt, ...);
+
+/* uint32 */
+extern uint32_t yang_str2uint32(const char *value);
+extern struct yang_data *yang_data_new_uint32(const char *xpath,
+ uint32_t value);
+extern uint32_t yang_dnode_get_uint32(const struct lyd_node *dnode,
+ const char *xpath_fmt, ...);
+extern uint32_t yang_get_default_uint32(const char *xpath_fmt, ...);
+
+/* uint64 */
+extern uint64_t yang_str2uint64(const char *value);
+extern struct yang_data *yang_data_new_uint64(const char *xpath,
+ uint64_t value);
+extern uint64_t yang_dnode_get_uint64(const struct lyd_node *dnode,
+ const char *xpath_fmt, ...);
+extern uint64_t yang_get_default_uint64(const char *xpath_fmt, ...);
+
+/* string */
+extern struct yang_data *yang_data_new_string(const char *xpath,
+ const char *value);
+extern const char *yang_dnode_get_string(const struct lyd_node *dnode,
+ const char *xpath_fmt, ...);
+extern void yang_dnode_get_string_buf(char *buf, size_t size,
+ const struct lyd_node *dnode,
+ const char *xpath_fmt, ...);
+extern const char *yang_get_default_string(const char *xpath_fmt, ...);
+extern void yang_get_default_string_buf(char *buf, size_t size,
+ const char *xpath_fmt, ...);
+
+/* ipv4 */
+extern void yang_str2ipv4(const char *value, struct in_addr *addr);
+extern struct yang_data *yang_data_new_ipv4(const char *xpath,
+ const struct in_addr *addr);
+extern void yang_dnode_get_ipv4(struct in_addr *addr,
+ const struct lyd_node *dnode,
+ const char *xpath_fmt, ...);
+extern void yang_get_default_ipv4(struct in_addr *var, const char *xpath_fmt,
+ ...);
+
+/* ipv4p */
+extern void yang_str2ipv4p(const char *value, union prefixptr prefix);
+extern struct yang_data *yang_data_new_ipv4p(const char *xpath,
+ const union prefixptr prefix);
+extern void yang_dnode_get_ipv4p(union prefixptr prefix,
+ const struct lyd_node *dnode,
+ const char *xpath_fmt, ...);
+extern void yang_get_default_ipv4p(union prefixptr var, const char *xpath_fmt,
+ ...);
+
+/* ipv6 */
+extern void yang_str2ipv6(const char *value, struct in6_addr *addr);
+extern struct yang_data *yang_data_new_ipv6(const char *xpath,
+ const struct in6_addr *addr);
+extern void yang_dnode_get_ipv6(struct in6_addr *addr,
+ const struct lyd_node *dnode,
+ const char *xpath_fmt, ...);
+extern void yang_get_default_ipv6(struct in6_addr *var, const char *xpath_fmt,
+ ...);
+
+/* ipv6p */
+extern void yang_str2ipv6p(const char *value, union prefixptr prefix);
+extern struct yang_data *yang_data_new_ipv6p(const char *xpath,
+ const union prefixptr prefix);
+extern void yang_dnode_get_ipv6p(union prefixptr prefix,
+ const struct lyd_node *dnode,
+ const char *xpath_fmt, ...);
+extern void yang_get_default_ipv6p(union prefixptr var, const char *xpath_fmt,
+ ...);
+
+#endif /* _FRR_NORTHBOUND_WRAPPERS_H_ */
zlog_info("ripd restarting!");
/* Reload config file. */
- vty_read_config(ripd_di.config_file, config_default);
+ vty_read_config(NULL, ripd_di.config_file, config_default);
/* Try to return to normal operation. */
}
ripng_reset();
/* Reload config file. */
- vty_read_config(ripngd_di.config_file, config_default);
+ vty_read_config(NULL, ripngd_di.config_file, config_default);
/* Try to return to normal operation. */
}
LOG_DAEMON);
zprivs_preinit(&bgpd_privs);
zprivs_init(&bgpd_privs);
+ yang_init();
+ nb_init(NULL, 0);
master = thread_master_create(NULL);
bgp_master_init(master);
vty_terminate();
cmd_terminate();
+ nb_terminate();
+ yang_terminate();
zprivs_terminate(&bgpd_privs);
thread_master_free(master);
master = NULL;
cmd_init(1);
vty_init(master);
memory_init();
+ yang_init();
+ nb_init(NULL, 0);
/* OSPF vty inits. */
test_vty_init();
/* Configuration file read*/
if (!config_file)
usage(progname, 1);
- vty_read_config(config_file, NULL);
+ vty_read_config(NULL, config_file, NULL);
test_timer_init();
printf("\nend.\n");
cmd_terminate();
vty_terminate();
+ nb_terminate();
+ yang_terminate();
thread_master_free(master);
closezlog();
vty_init(master);
memory_init();
+ yang_init();
+ nb_init(NULL, 0);
test_init(argc, argv);
hostname test\r
!\r
!\r
+!\r
line vty\r
!\r
end\r
hostname foohost\r
!\r
!\r
+!\r
line vty\r
!\r
end\r
struct cmd_element *cmd;
cmd_init(1);
+ yang_init();
+ nb_init(NULL, 0);
install_node(&bgp_node, NULL);
install_node(&rip_node, NULL);
XFREE(MTYPE_TMP, vector_slot(test_cmds, i));
vector_free(test_cmds);
cmd_terminate();
+ nb_terminate();
+ yang_terminate();
}
static void test_run(struct prng *prng, struct vty *vty, const char *cmd,
/frr
+/gen_northbound_callbacks
+/gen_yang_deviations
/permutations
/ssd
--- /dev/null
+/*
+ * Copyright (C) 2018 NetDEF, Inc.
+ * Renato Westphal
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#define REALLY_NEED_PLAIN_GETOPT 1
+
+#include <zebra.h>
+
+#include <unistd.h>
+
+#include "yang.h"
+#include "northbound.h"
+
+static void __attribute__((noreturn)) usage(int status)
+{
+ fprintf(stderr, "usage: gen_northbound_callbacks [-h] MODULE\n");
+ exit(status);
+}
+
+static struct nb_callback_info {
+ int operation;
+ bool optional;
+ char return_type[32];
+ char return_value[32];
+ char arguments[128];
+} nb_callbacks[] = {
+ {
+ .operation = NB_OP_CREATE,
+ .return_type = "int ",
+ .return_value = "NB_OK",
+ .arguments =
+ "enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource",
+ },
+ {
+ .operation = NB_OP_MODIFY,
+ .return_type = "int ",
+ .return_value = "NB_OK",
+ .arguments =
+ "enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource",
+ },
+ {
+ .operation = NB_OP_DELETE,
+ .return_type = "int ",
+ .return_value = "NB_OK",
+ .arguments =
+ "enum nb_event event, const struct lyd_node *dnode",
+ },
+ {
+ .operation = NB_OP_MOVE,
+ .return_type = "int ",
+ .return_value = "NB_OK",
+ .arguments =
+ "enum nb_event event, const struct lyd_node *dnode",
+ },
+ {
+ .operation = NB_OP_APPLY_FINISH,
+ .optional = true,
+ .return_type = "void ",
+ .return_value = "",
+ .arguments = "const struct lyd_node *dnode",
+ },
+ {
+ .operation = NB_OP_GET_ELEM,
+ .return_type = "struct yang_data *",
+ .return_value = "NULL",
+ .arguments = "const char *xpath, const void *list_entry",
+ },
+ {
+ .operation = NB_OP_GET_NEXT,
+ .return_type = "const void *",
+ .return_value = "NULL",
+ .arguments = "const char *xpath, const void *list_entry",
+ },
+ {
+ .operation = NB_OP_GET_KEYS,
+ .return_type = "int ",
+ .return_value = "NB_OK",
+ .arguments = "const void *list_entry, struct yang_list_keys *keys",
+ },
+ {
+ .operation = NB_OP_LOOKUP_ENTRY,
+ .return_type = "const void *",
+ .return_value = "NULL",
+ .arguments = "const struct yang_list_keys *keys",
+ },
+ {
+ .operation = NB_OP_RPC,
+ .return_type = "int ",
+ .return_value = "NB_OK",
+ .arguments =
+ "const char *xpath, const struct list *input, struct list *output",
+ },
+ {
+ /* sentinel */
+ .operation = -1,
+ },
+};
+
+static void replace_hyphens_by_underscores(char *str)
+{
+ char *p;
+
+ p = str;
+ while ((p = strchr(p, '-')) != NULL)
+ *p++ = '_';
+}
+
+static void generate_callback_name(struct lys_node *snode,
+ enum nb_operation operation, char *buffer,
+ size_t size)
+{
+ struct list *snodes;
+ struct listnode *ln;
+
+ snodes = list_new();
+ for (; snode; snode = lys_parent(snode)) {
+ /* Skip schema-only snodes. */
+ if (snode->nodetype
+ & (LYS_USES | LYS_CHOICE | LYS_CASE | LYS_INPUT
+ | LYS_OUTPUT))
+ continue;
+
+ listnode_add_head(snodes, snode);
+ }
+
+ memset(buffer, 0, size);
+ for (ALL_LIST_ELEMENTS_RO(snodes, ln, snode)) {
+ strlcat(buffer, snode->name, size);
+ strlcat(buffer, "_", size);
+ }
+ strlcat(buffer, nb_operation_name(operation), size);
+ list_delete(&snodes);
+
+ replace_hyphens_by_underscores(buffer);
+}
+
+static void generate_callbacks(const struct lys_node *snode, void *arg1,
+ void *arg2)
+{
+ bool first = true;
+
+ switch (snode->nodetype) {
+ case LYS_CONTAINER:
+ case LYS_LEAF:
+ case LYS_LEAFLIST:
+ case LYS_LIST:
+ case LYS_NOTIF:
+ case LYS_RPC:
+ break;
+ default:
+ return;
+ }
+
+ for (struct nb_callback_info *cb = &nb_callbacks[0];
+ cb->operation != -1; cb++) {
+ char cb_name[BUFSIZ];
+
+ if (cb->optional
+ || !nb_operation_is_valid(cb->operation, snode))
+ continue;
+
+ if (first) {
+ char xpath[XPATH_MAXLEN];
+
+ yang_snode_get_path(snode, YANG_PATH_DATA, xpath,
+ sizeof(xpath));
+
+ printf("/*\n"
+ " * XPath: %s\n"
+ " */\n",
+ xpath);
+ first = false;
+ }
+
+ generate_callback_name((struct lys_node *)snode, cb->operation,
+ cb_name, sizeof(cb_name));
+ printf("static %s%s(%s)\n"
+ "{\n"
+ "\t/* TODO: implement me. */\n"
+ "\treturn %s;\n"
+ "}\n\n",
+ nb_callbacks[cb->operation].return_type, cb_name,
+ nb_callbacks[cb->operation].arguments,
+ nb_callbacks[cb->operation].return_value);
+ }
+}
+
+static void generate_nb_nodes(const struct lys_node *snode, void *arg1,
+ void *arg2)
+{
+ bool first = true;
+
+ switch (snode->nodetype) {
+ case LYS_CONTAINER:
+ case LYS_LEAF:
+ case LYS_LEAFLIST:
+ case LYS_LIST:
+ case LYS_NOTIF:
+ case LYS_RPC:
+ break;
+ default:
+ return;
+ }
+
+ for (struct nb_callback_info *cb = &nb_callbacks[0];
+ cb->operation != -1; cb++) {
+ char cb_name[BUFSIZ];
+
+ if (cb->optional
+ || !nb_operation_is_valid(cb->operation, snode))
+ continue;
+
+ if (first) {
+ char xpath[XPATH_MAXLEN];
+
+ yang_snode_get_path(snode, YANG_PATH_DATA, xpath,
+ sizeof(xpath));
+
+ printf("\t\t{\n"
+ "\t\t\t.xpath = \"%s\",\n",
+ xpath);
+ first = false;
+ }
+
+ generate_callback_name((struct lys_node *)snode, cb->operation,
+ cb_name, sizeof(cb_name));
+ printf("\t\t\t.cbs.%s = %s,\n",
+ nb_operation_name(cb->operation), cb_name);
+ }
+
+ if (!first)
+ printf("\t\t},\n");
+}
+
+int main(int argc, char *argv[])
+{
+ struct yang_module *module;
+ char module_name_underscores[64];
+ int opt;
+
+ while ((opt = getopt(argc, argv, "h")) != -1) {
+ switch (opt) {
+ case 'h':
+ usage(EXIT_SUCCESS);
+ /* NOTREACHED */
+ default:
+ usage(EXIT_FAILURE);
+ /* NOTREACHED */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc != 1)
+ usage(EXIT_FAILURE);
+
+ yang_init();
+
+ /* Load YANG module. */
+ module = yang_module_load(argv[0]);
+
+ /* Generate callback functions. */
+ yang_module_snodes_iterate(module->info, generate_callbacks, 0, NULL,
+ NULL);
+
+ strlcpy(module_name_underscores, module->name,
+ sizeof(module_name_underscores));
+ replace_hyphens_by_underscores(module_name_underscores);
+
+ /* Generate frr_yang_module_info array. */
+ printf("/* clang-format off */\n"
+ "const struct frr_yang_module_info %s_info = {\n"
+ "\t.name = \"%s\",\n"
+ "\t.nodes = {\n",
+ module_name_underscores, module->name);
+ yang_module_snodes_iterate(module->info, generate_nb_nodes, 0, NULL,
+ NULL);
+ printf("\t\t{\n"
+ "\t\t\t.xpath = NULL,\n"
+ "\t\t},\n");
+ printf("\t}\n"
+ "};\n");
+
+ /* Cleanup and exit. */
+ yang_terminate();
+
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright (C) 2018 NetDEF, Inc.
+ * Renato Westphal
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#define REALLY_NEED_PLAIN_GETOPT 1
+
+#include <zebra.h>
+
+#include <unistd.h>
+
+#include "yang.h"
+#include "northbound.h"
+
+static void __attribute__((noreturn)) usage(int status)
+{
+ fprintf(stderr, "usage: gen_yang_deviations [-h] MODULE\n");
+ exit(status);
+}
+
+static void generate_yang_deviation(const struct lys_node *snode, void *arg1,
+ void *arg2)
+{
+ char xpath[XPATH_MAXLEN];
+
+ yang_snode_get_path(snode, YANG_PATH_SCHEMA, xpath, sizeof(xpath));
+
+ printf(" deviation \"%s\" {\n", xpath);
+ printf(" deviate not-supported;\n");
+ printf(" }\n\n");
+}
+
+int main(int argc, char *argv[])
+{
+ struct yang_module *module;
+ int opt;
+
+ while ((opt = getopt(argc, argv, "h")) != -1) {
+ switch (opt) {
+ case 'h':
+ usage(EXIT_SUCCESS);
+ /* NOTREACHED */
+ default:
+ usage(EXIT_FAILURE);
+ /* NOTREACHED */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc != 1)
+ usage(EXIT_FAILURE);
+
+ yang_init();
+
+ /* Load YANG module. */
+ module = yang_module_load(argv[0]);
+
+ /* Generate deviations. */
+ yang_module_snodes_iterate(module->info, generate_yang_deviation,
+ YANG_ITER_FILTER_IMPLICIT, NULL, NULL);
+
+ /* Cleanup and exit. */
+ yang_terminate();
+
+ return 0;
+}
# tools
#
-noinst_PROGRAMS += tools/permutations
+noinst_PROGRAMS += \
+ tools/permutations \
+ tools/gen_northbound_callbacks \
+ tools/gen_yang_deviations \
+ # end
+
sbin_PROGRAMS += tools/ssd
sbin_SCRIPTS += \
tools/frr-reload \
tools_permutations_SOURCES = tools/permutations.c
tools_permutations_LDADD = lib/libfrr.la
+tools_gen_northbound_callbacks_SOURCES = tools/gen_northbound_callbacks.c
+tools_gen_northbound_callbacks_LDADD = lib/libfrr.la -lyang
+
+tools_gen_yang_deviations_SOURCES = tools/gen_yang_deviations.c
+tools_gen_yang_deviations_LDADD = lib/libfrr.la -lyang
+
tools_ssd_SOURCES = tools/start-stop-daemon.c
EXTRA_DIST += \
config = config_get(FORWARDING_NODE, line);
else if (strncmp(line, "debug vrf", strlen("debug vrf")) == 0)
config = config_get(VRF_DEBUG_NODE, line);
+ else if (strncmp(line, "debug northbound",
+ strlen("debug northbound"))
+ == 0)
+ config = config_get(NORTHBOUND_DEBUG_NODE, line);
else if (strncmp(line, "debug", strlen("debug")) == 0)
config = config_get(DEBUG_NODE, line);
else if (strncmp(line, "password", strlen("password")) == 0
|| (I) == ACCESS_IPV6_NODE || (I) == ACCESS_MAC_NODE \
|| (I) == PREFIX_IPV6_NODE || (I) == FORWARDING_NODE \
|| (I) == DEBUG_NODE || (I) == AAA_NODE || (I) == VRF_DEBUG_NODE \
- || (I) == MPLS_NODE)
+ || (I) == NORTHBOUND_DEBUG_NODE || (I) == MPLS_NODE)
/* Display configuration to file pointer. */
void vtysh_config_dump(void)
--- /dev/null
+module frr-module-translator {
+ yang-version 1.1;
+ namespace "http://frrouting.org/yang/frr-module-translator";
+ prefix frr-module-translator;
+
+ organization
+ "Free Range Routing";
+ contact
+ "FRR Users List: <mailto:frog@lists.frrouting.org>
+ FRR Development List: <mailto:dev@lists.frrouting.org>";
+ description
+ "A model for FRR YANG module translators.";
+
+ revision 2018-07-31 {
+ description
+ "Initial revision.";
+ }
+
+ container frr-module-translator {
+ leaf family {
+ type string {
+ length "0 .. 32";
+ }
+ mandatory true;
+ description
+ "Family of YANG models.";
+ }
+ list module {
+ key "name";
+ ordered-by user;
+ description
+ "YANG module.";
+
+ leaf name {
+ type string;
+ description
+ "Module name.";
+ }
+ leaf deviations {
+ type string;
+ mandatory true;
+ description
+ "Module containing the YANG deviations.";
+ }
+ list mappings {
+ key "custom";
+ description
+ "YANG mappings between the custom module and FRR native modules.";
+
+ leaf custom {
+ type string {
+ length "0 .. 256";
+ }
+ description
+ "YANG path of the custom module.";
+ }
+ leaf native {
+ type string {
+ length "0 .. 256";
+ }
+ mandatory true;
+ description
+ "Corresponding path of the native YANG modules";
+ }
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2018 NetDEF, Inc.
+ * Renato Westphal
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <zebra.h>
+
+#include "prefix.h"
+
+#include <libyang/user_types.h>
+
+static int ipv4_address_store_clb(const char *type_name, const char *value_str,
+ lyd_val *value, char **err_msg)
+{
+ value->ptr = malloc(sizeof(struct in_addr));
+ if (!value->ptr)
+ return 1;
+
+ if (inet_pton(AF_INET, value_str, value->ptr) != 1) {
+ free(value->ptr);
+ return 1;
+ }
+
+ return 0;
+}
+
+static int ipv6_address_store_clb(const char *type_name, const char *value_str,
+ lyd_val *value, char **err_msg)
+{
+ value->ptr = malloc(INET6_ADDRSTRLEN);
+ if (!value->ptr)
+ return 1;
+
+ if (inet_pton(AF_INET6, value_str, value->ptr) != 1) {
+ free(value->ptr);
+ return 1;
+ }
+
+ return 0;
+}
+
+static int ipv4_prefix_store_clb(const char *type_name, const char *value_str,
+ lyd_val *value, char **err_msg)
+{
+ value->ptr = malloc(sizeof(struct prefix_ipv4));
+ if (!value->ptr)
+ return 1;
+
+ if (str2prefix_ipv4(value_str, value->ptr) == 0) {
+ free(value->ptr);
+ return 1;
+ }
+
+ return 0;
+}
+
+static int ipv6_prefix_store_clb(const char *type_name, const char *value_str,
+ lyd_val *value, char **err_msg)
+{
+ value->ptr = malloc(sizeof(struct prefix_ipv6));
+ if (!value->ptr)
+ return 1;
+
+ if (str2prefix_ipv6(value_str, value->ptr) == 0) {
+ free(value->ptr);
+ return 1;
+ }
+
+ return 0;
+}
+
+struct lytype_plugin_list frr_user_types[] = {
+ {"ietf-inet-types", "2013-07-15", "ipv4-address",
+ ipv4_address_store_clb, free},
+ {"ietf-inet-types", "2013-07-15", "ipv4-address-no-zone",
+ ipv4_address_store_clb, free},
+ {"ietf-inet-types", "2013-07-15", "ipv6-address",
+ ipv6_address_store_clb, free},
+ {"ietf-inet-types", "2013-07-15", "ipv6-address-no-zone",
+ ipv6_address_store_clb, free},
+ {"ietf-inet-types", "2013-07-15", "ipv4-prefix", ipv4_prefix_store_clb,
+ free},
+ {"ietf-inet-types", "2013-07-15", "ipv6-prefix", ipv6_prefix_store_clb,
+ free},
+ {NULL, NULL, NULL, NULL, NULL} /* terminating item */
+};
--- /dev/null
+#
+# libyang user types
+#
+libyang_plugins_LTLIBRARIES += yang/libyang_plugins/frr_user_types.la
+
+yang_libyang_plugins_frr_user_types_la_CFLAGS = $(WERROR)
+yang_libyang_plugins_frr_user_types_la_LDFLAGS = -avoid-version -module -shared -export-dynamic
+yang_libyang_plugins_frr_user_types_la_LIBADD =
+yang_libyang_plugins_frr_user_types_la_SOURCES = yang/libyang_plugins/frr_user_types.c
--- /dev/null
+dist_yangmodels_DATA += yang/frr-module-translator.yang