summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:53:30 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:53:30 +0000
commit2c7cac91ed6e7db0f6937923d2b57f97dbdbc337 (patch)
treec05dc0f8e6aa3accc84e3e5cffc933ed94941383 /lib
parentInitial commit. (diff)
downloadfrr-2c7cac91ed6e7db0f6937923d2b57f97dbdbc337.tar.xz
frr-2c7cac91ed6e7db0f6937923d2b57f97dbdbc337.zip
Adding upstream version 8.4.4.upstream/8.4.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib')
-rw-r--r--lib/.gitignore13
-rw-r--r--lib/Makefile10
-rw-r--r--lib/agentx.c406
-rw-r--r--lib/agg_table.c59
-rw-r--r--lib/agg_table.h177
-rw-r--r--lib/assert/assert.h98
-rw-r--r--lib/atomlist.c350
-rw-r--r--lib/atomlist.h374
-rw-r--r--lib/base64.c197
-rw-r--r--lib/base64.h45
-rw-r--r--lib/bfd.c987
-rw-r--r--lib/bfd.h463
-rw-r--r--lib/bitfield.h275
-rw-r--r--lib/buffer.c498
-rw-r--r--lib/buffer.h110
-rw-r--r--lib/checksum.c154
-rw-r--r--lib/checksum.h74
-rw-r--r--lib/clippy.c121
-rw-r--r--lib/clippy.h38
-rw-r--r--lib/command.c2633
-rw-r--r--lib/command.h656
-rw-r--r--lib/command_graph.c562
-rw-r--r--lib/command_graph.h159
-rw-r--r--lib/command_lex.l104
-rw-r--r--lib/command_match.c1060
-rw-r--r--lib/command_match.h109
-rw-r--r--lib/command_parse.y543
-rw-r--r--lib/command_py.c363
-rw-r--r--lib/compiler.h418
-rw-r--r--lib/cspf.c646
-rw-r--r--lib/cspf.h211
-rw-r--r--lib/csv.c706
-rw-r--r--lib/csv.h196
-rw-r--r--lib/db.c325
-rw-r--r--lib/db.h60
-rw-r--r--lib/debug.c63
-rw-r--r--lib/debug.h254
-rw-r--r--lib/defaults.c230
-rw-r--r--lib/defaults.h147
-rw-r--r--lib/defun_lex.l293
-rw-r--r--lib/distribute.c488
-rw-r--r--lib/distribute.h96
-rw-r--r--lib/elf_py.c1385
-rw-r--r--lib/explicit_bzero.c39
-rw-r--r--lib/ferr.c297
-rw-r--r--lib/ferr.h267
-rw-r--r--lib/filter.c919
-rw-r--r--lib/filter.h258
-rw-r--r--lib/filter_cli.c1773
-rw-r--r--lib/filter_nb.c1865
-rw-r--r--lib/freebsd-queue.h686
-rw-r--r--lib/frr_pthread.c319
-rw-r--r--lib/frr_pthread.h270
-rw-r--r--lib/frr_zmq.c370
-rw-r--r--lib/frr_zmq.h154
-rw-r--r--lib/frratomic.h237
-rw-r--r--lib/frrcu.c527
-rw-r--r--lib/frrcu.h183
-rw-r--r--lib/frrlua.c501
-rw-r--r--lib/frrlua.h217
-rw-r--r--lib/frrscript.c438
-rw-r--r--lib/frrscript.h342
-rw-r--r--lib/frrstr.c235
-rw-r--r--lib/frrstr.h181
-rw-r--r--lib/getopt.c1024
-rw-r--r--lib/getopt.h156
-rw-r--r--lib/getopt1.c176
-rw-r--r--lib/gitversion.pl47
-rw-r--r--lib/grammar_sandbox.c565
-rw-r--r--lib/grammar_sandbox_main.c67
-rw-r--r--lib/graph.c230
-rw-r--r--lib/graph.h176
-rw-r--r--lib/hash.c464
-rw-r--r--lib/hash.h337
-rw-r--r--lib/hook.c71
-rw-r--r--lib/hook.h254
-rw-r--r--lib/iana_afi.h141
-rw-r--r--lib/id_alloc.c408
-rw-r--r--lib/id_alloc.h98
-rw-r--r--lib/if.c1803
-rw-r--r--lib/if.h615
-rw-r--r--lib/if_rmap.c332
-rw-r--r--lib/if_rmap.h69
-rw-r--r--lib/imsg-buffer.c285
-rw-r--r--lib/imsg.c327
-rw-r--r--lib/imsg.h119
-rw-r--r--lib/ipaddr.h185
-rw-r--r--lib/jhash.c187
-rw-r--r--lib/jhash.h53
-rw-r--r--lib/json.c123
-rw-r--r--lib/json.h162
-rw-r--r--lib/keychain.c1271
-rw-r--r--lib/keychain.h109
-rw-r--r--lib/ldp_sync.c91
-rw-r--r--lib/ldp_sync.h82
-rw-r--r--lib/lib_errors.c393
-rw-r--r--lib/lib_errors.h99
-rw-r--r--lib/lib_vty.c347
-rw-r--r--lib/lib_vty.h40
-rw-r--r--lib/libfrr.c1279
-rw-r--r--lib/libfrr.h196
-rw-r--r--lib/libfrr_trace.c6
-rw-r--r--lib/libfrr_trace.h240
-rw-r--r--lib/libospf.h106
-rw-r--r--lib/link_state.c2711
-rw-r--r--lib/link_state.h1131
-rw-r--r--lib/linklist.c446
-rw-r--r--lib/linklist.h366
-rw-r--r--lib/log.c662
-rw-r--r--lib/log.h178
-rw-r--r--lib/log_filter.c160
-rw-r--r--lib/log_vty.c928
-rw-r--r--lib/log_vty.h49
-rw-r--r--lib/md5.c443
-rw-r--r--lib/md5.h93
-rw-r--r--lib/memory.c192
-rw-r--r--lib/memory.h202
-rw-r--r--lib/mlag.c186
-rw-r--r--lib/mlag.h146
-rw-r--r--lib/module.c215
-rw-r--r--lib/module.h106
-rw-r--r--lib/monotime.h214
-rw-r--r--lib/mpls.c100
-rw-r--r--lib/mpls.h226
-rw-r--r--lib/netns_linux.c612
-rw-r--r--lib/netns_other.c163
-rw-r--r--lib/network.c124
-rw-r--r--lib/network.h106
-rw-r--r--lib/nexthop.c1093
-rw-r--r--lib/nexthop.h270
-rw-r--r--lib/nexthop_group.c1332
-rw-r--r--lib/nexthop_group.h160
-rw-r--r--lib/nexthop_group_private.h43
-rw-r--r--lib/northbound.c2503
-rw-r--r--lib/northbound.h1316
-rw-r--r--lib/northbound_cli.c1970
-rw-r--r--lib/northbound_cli.h159
-rw-r--r--lib/northbound_confd.c1505
-rw-r--r--lib/northbound_db.c293
-rw-r--r--lib/northbound_db.h112
-rw-r--r--lib/northbound_grpc.cpp1331
-rw-r--r--lib/northbound_sysrepo.c769
-rw-r--r--lib/ns.h194
-rw-r--r--lib/ntop.c178
-rw-r--r--lib/openbsd-queue.h583
-rw-r--r--lib/openbsd-tree.c620
-rw-r--r--lib/openbsd-tree.h580
-rw-r--r--lib/pbr.h154
-rw-r--r--lib/pid_output.c83
-rw-r--r--lib/plist.c1665
-rw-r--r--lib/plist.h107
-rw-r--r--lib/plist_int.h86
-rw-r--r--lib/prefix.c1553
-rw-r--r--lib/prefix.h686
-rw-r--r--lib/printf/README9
-rw-r--r--lib/printf/glue.c297
-rw-r--r--lib/printf/printf-pos.c791
-rw-r--r--lib/printf/printfcommon.h244
-rw-r--r--lib/printf/printflocal.h107
-rw-r--r--lib/printf/vfprintf.c821
-rw-r--r--lib/printfrr.h326
-rw-r--r--lib/privs.c762
-rw-r--r--lib/privs.h157
-rw-r--r--lib/ptm_lib.c487
-rw-r--r--lib/ptm_lib.h76
-rw-r--r--lib/pullwr.c274
-rw-r--r--lib/pullwr.h118
-rw-r--r--lib/pw.h61
-rw-r--r--lib/qobj.c116
-rw-r--r--lib/qobj.h154
-rw-r--r--lib/queue.h96
-rwxr-xr-xlib/resolver.c341
-rw-r--r--lib/resolver.h39
-rw-r--r--lib/ringbuf.c133
-rw-r--r--lib/ringbuf.h133
-rw-r--r--lib/route_opaque.h61
-rwxr-xr-xlib/route_types.pl228
-rw-r--r--lib/route_types.txt118
-rw-r--r--lib/routemap.c3400
-rw-r--r--lib/routemap.h1022
-rw-r--r--lib/routemap_cli.c1570
-rw-r--r--lib/routemap_northbound.c1439
-rw-r--r--lib/routing_nb.c42
-rw-r--r--lib/routing_nb.h39
-rw-r--r--lib/routing_nb_config.c115
-rw-r--r--lib/sbuf.c107
-rw-r--r--lib/sbuf.h87
-rw-r--r--lib/seqlock.c311
-rw-r--r--lib/seqlock.h146
-rw-r--r--lib/sha256.c413
-rw-r--r--lib/sha256.h66
-rw-r--r--lib/sigevent.c375
-rw-r--r--lib/sigevent.h67
-rw-r--r--lib/skiplist.c671
-rw-r--r--lib/skiplist.h135
-rw-r--r--lib/smux.h167
-rw-r--r--lib/snmp.c196
-rw-r--r--lib/sockopt.c695
-rw-r--r--lib/sockopt.h160
-rw-r--r--lib/sockunion.c796
-rw-r--r--lib/sockunion.h127
-rw-r--r--lib/spf_backoff.c313
-rw-r--r--lib/spf_backoff.h69
-rw-r--r--lib/srcdest_table.c334
-rw-r--r--lib/srcdest_table.h109
-rw-r--r--lib/srte.h56
-rw-r--r--lib/srv6.c295
-rw-r--r--lib/srv6.h200
-rw-r--r--lib/stream.c1389
-rw-r--r--lib/stream.h500
-rw-r--r--lib/strformat.c599
-rw-r--r--lib/strlcat.c71
-rw-r--r--lib/strlcpy.c58
-rw-r--r--lib/subdir.am608
-rw-r--r--lib/systemd.c171
-rw-r--r--lib/systemd.h57
-rw-r--r--lib/table.c796
-rw-r--r--lib/table.h343
-rw-r--r--lib/termtable.c500
-rw-r--r--lib/termtable.h297
-rw-r--r--lib/thread.c2217
-rw-r--r--lib/thread.h306
-rw-r--r--lib/trace.h80
-rw-r--r--lib/typerb.c525
-rw-r--r--lib/typerb.h230
-rw-r--r--lib/typesafe.c611
-rw-r--r--lib/typesafe.h1165
-rw-r--r--lib/vector.c227
-rw-r--r--lib/vector.h80
-rw-r--r--lib/version.h.in65
-rw-r--r--lib/vlan.h37
-rw-r--r--lib/vrf.c1041
-rw-r--r--lib/vrf.h318
-rw-r--r--lib/vrf_int.h63
-rw-r--r--lib/vty.c3236
-rw-r--r--lib/vty.h387
-rw-r--r--lib/vxlan.h58
-rw-r--r--lib/wheel.c162
-rw-r--r--lib/wheel.h126
-rw-r--r--lib/workqueue.c384
-rw-r--r--lib/workqueue.h189
-rw-r--r--lib/xref.c132
-rw-r--r--lib/xref.h310
-rw-r--r--lib/yang.c919
-rw-r--r--lib/yang.h689
-rw-r--r--lib/yang_translator.c547
-rw-r--r--lib/yang_translator.h152
-rw-r--r--lib/yang_wrappers.c1155
-rw-r--r--lib/yang_wrappers.h210
-rw-r--r--lib/zclient.c4329
-rw-r--r--lib/zclient.h1261
-rw-r--r--lib/zebra.h421
-rw-r--r--lib/zlog.c1061
-rw-r--r--lib/zlog.h297
-rw-r--r--lib/zlog_5424.c1145
-rw-r--r--lib/zlog_5424.h152
-rw-r--r--lib/zlog_5424_cli.c1006
-rw-r--r--lib/zlog_live.c277
-rw-r--r--lib/zlog_live.h83
-rw-r--r--lib/zlog_targets.c584
-rw-r--r--lib/zlog_targets.h74
261 files changed, 119242 insertions, 0 deletions
diff --git a/lib/.gitignore b/lib/.gitignore
new file mode 100644
index 0000000..6176b30
--- /dev/null
+++ b/lib/.gitignore
@@ -0,0 +1,13 @@
+/version.c
+/version.h
+/gitversion.h
+/gitversion.h.tmp
+/route_types.h
+/memtypes.h
+/command_lex.c
+/command_lex.h
+/command_parse.c
+/command_parse.h
+/grammar_sandbox
+/clippy
+/defun_lex.c
diff --git a/lib/Makefile b/lib/Makefile
new file mode 100644
index 0000000..62051ac
--- /dev/null
+++ b/lib/Makefile
@@ -0,0 +1,10 @@
+all: ALWAYS
+ @$(MAKE) -s -C .. lib/libfrr.la
+%: ALWAYS
+ @$(MAKE) -s -C .. lib/$@
+
+Makefile:
+ #nothing
+ALWAYS:
+.PHONY: ALWAYS makefiles
+.SUFFIXES:
diff --git a/lib/agentx.c b/lib/agentx.c
new file mode 100644
index 0000000..4c08721
--- /dev/null
+++ b/lib/agentx.c
@@ -0,0 +1,406 @@
+/* SNMP support
+ * Copyright (C) 2012 Vincent Bernat <bernat@luffy.cx>
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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>
+
+#ifdef SNMP_AGENTX
+#include <net-snmp/net-snmp-config.h>
+#include <net-snmp/net-snmp-includes.h>
+#include <net-snmp/agent/net-snmp-agent-includes.h>
+#include <net-snmp/agent/snmp_vars.h>
+
+#include "command.h"
+#include "smux.h"
+#include "memory.h"
+#include "linklist.h"
+#include "lib/version.h"
+#include "lib_errors.h"
+#include "xref.h"
+
+XREF_SETUP();
+
+DEFINE_HOOK(agentx_enabled, (), ());
+
+static int agentx_enabled = 0;
+
+static struct thread_master *agentx_tm;
+static struct thread *timeout_thr = NULL;
+static struct list *events = NULL;
+
+static void agentx_events_update(void);
+
+static void agentx_timeout(struct thread *t)
+{
+ timeout_thr = NULL;
+
+ snmp_timeout();
+ run_alarms();
+ netsnmp_check_outstanding_agent_requests();
+ agentx_events_update();
+}
+
+static void agentx_read(struct thread *t)
+{
+ fd_set fds;
+ int flags, new_flags = 0;
+ int nonblock = false;
+ struct listnode *ln = THREAD_ARG(t);
+ struct thread **thr = listgetdata(ln);
+ XFREE(MTYPE_TMP, thr);
+ list_delete_node(events, ln);
+
+ /* fix for non blocking socket */
+ flags = fcntl(THREAD_FD(t), F_GETFL, 0);
+ if (-1 == flags) {
+ flog_err(EC_LIB_SYSTEM_CALL, "Failed to get FD settings fcntl: %s(%d)",
+ strerror(errno), errno);
+ return;
+ }
+
+ if (flags & O_NONBLOCK)
+ nonblock = true;
+ else
+ new_flags = fcntl(THREAD_FD(t), F_SETFL, flags | O_NONBLOCK);
+
+ if (new_flags == -1)
+ flog_err(EC_LIB_SYSTEM_CALL, "Failed to set snmp fd non blocking: %s(%d)",
+ strerror(errno), errno);
+
+ FD_ZERO(&fds);
+ FD_SET(THREAD_FD(t), &fds);
+ snmp_read(&fds);
+
+ /* Reset the flag */
+ if (!nonblock) {
+ new_flags = fcntl(THREAD_FD(t), F_SETFL, flags);
+
+ if (new_flags == -1)
+ flog_err(
+ EC_LIB_SYSTEM_CALL,
+ "Failed to set snmp fd back to original settings: %s(%d)",
+ strerror(errno), errno);
+ }
+
+ netsnmp_check_outstanding_agent_requests();
+ agentx_events_update();
+}
+
+static void agentx_events_update(void)
+{
+ int maxfd = 0;
+ int block = 1;
+ struct timeval timeout = {.tv_sec = 0, .tv_usec = 0};
+ fd_set fds;
+ struct listnode *ln;
+ struct thread **thr;
+ int fd, thr_fd;
+
+ thread_cancel(&timeout_thr);
+
+ FD_ZERO(&fds);
+ snmp_select_info(&maxfd, &fds, &timeout, &block);
+
+ if (!block) {
+ thread_add_timer_tv(agentx_tm, agentx_timeout, NULL, &timeout,
+ &timeout_thr);
+ }
+
+ ln = listhead(events);
+ thr = ln ? listgetdata(ln) : NULL;
+ thr_fd = thr ? THREAD_FD(*thr) : -1;
+
+ /* "two-pointer" / two-list simultaneous iteration
+ * ln/thr/thr_fd point to the next existing event listener to hit while
+ * fd counts to catch up */
+ for (fd = 0; fd < maxfd; fd++) {
+ /* caught up */
+ if (thr_fd == fd) {
+ struct listnode *nextln = listnextnode(ln);
+ if (!FD_ISSET(fd, &fds)) {
+ thread_cancel(thr);
+ XFREE(MTYPE_TMP, thr);
+ list_delete_node(events, ln);
+ }
+ ln = nextln;
+ thr = ln ? listgetdata(ln) : NULL;
+ thr_fd = thr ? THREAD_FD(*thr) : -1;
+ }
+ /* need listener, but haven't hit one where it would be */
+ else if (FD_ISSET(fd, &fds)) {
+ struct listnode *newln;
+ thr = XCALLOC(MTYPE_TMP, sizeof(struct thread *));
+
+ newln = listnode_add_before(events, ln, thr);
+ thread_add_read(agentx_tm, agentx_read, newln, fd, thr);
+ }
+ }
+
+ /* leftover event listeners at this point have fd > maxfd, delete them
+ */
+ while (ln) {
+ struct listnode *nextln = listnextnode(ln);
+ thr = listgetdata(ln);
+ thread_cancel(thr);
+ XFREE(MTYPE_TMP, thr);
+ list_delete_node(events, ln);
+ ln = nextln;
+ }
+}
+
+/* AgentX node. */
+static int config_write_agentx(struct vty *vty);
+static struct cmd_node agentx_node = {
+ .name = "smux",
+ .node = SMUX_NODE,
+ .prompt = "",
+ .config_write = config_write_agentx,
+};
+
+/* Logging NetSNMP messages */
+static int agentx_log_callback(int major, int minor, void *serverarg,
+ void *clientarg)
+{
+ struct snmp_log_message *slm = (struct snmp_log_message *)serverarg;
+ char *msg = XSTRDUP(MTYPE_TMP, slm->msg);
+ if (msg)
+ msg[strlen(msg) - 1] = '\0';
+ switch (slm->priority) {
+ case LOG_EMERG:
+ flog_err(EC_LIB_SNMP, "snmp[emerg]: %s", msg ? msg : slm->msg);
+ break;
+ case LOG_ALERT:
+ flog_err(EC_LIB_SNMP, "snmp[alert]: %s", msg ? msg : slm->msg);
+ break;
+ case LOG_CRIT:
+ flog_err(EC_LIB_SNMP, "snmp[crit]: %s", msg ? msg : slm->msg);
+ break;
+ case LOG_ERR:
+ flog_err(EC_LIB_SNMP, "snmp[err]: %s", msg ? msg : slm->msg);
+ break;
+ case LOG_WARNING:
+ flog_warn(EC_LIB_SNMP, "snmp[warning]: %s",
+ msg ? msg : slm->msg);
+ break;
+ case LOG_NOTICE:
+ zlog_notice("snmp[notice]: %s", msg ? msg : slm->msg);
+ break;
+ case LOG_INFO:
+ zlog_info("snmp[info]: %s", msg ? msg : slm->msg);
+ break;
+ case LOG_DEBUG:
+ zlog_debug("snmp[debug]: %s", msg ? msg : slm->msg);
+ break;
+ }
+ XFREE(MTYPE_TMP, msg);
+ return SNMP_ERR_NOERROR;
+}
+
+static int config_write_agentx(struct vty *vty)
+{
+ if (agentx_enabled)
+ vty_out(vty, "agentx\n");
+ return 1;
+}
+
+DEFUN (agentx_enable,
+ agentx_enable_cmd,
+ "agentx",
+ "SNMP AgentX protocol settings\n")
+{
+ if (!agentx_enabled) {
+ init_snmp(FRR_SMUX_NAME);
+ events = list_new();
+ agentx_events_update();
+ agentx_enabled = 1;
+ hook_call(agentx_enabled);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_agentx,
+ no_agentx_cmd,
+ "no agentx",
+ NO_STR
+ "SNMP AgentX protocol settings\n")
+{
+ if (!agentx_enabled)
+ return CMD_SUCCESS;
+ vty_out(vty, "SNMP AgentX support cannot be disabled once enabled\n");
+ return CMD_WARNING_CONFIG_FAILED;
+}
+
+int smux_enabled(void)
+{
+ return agentx_enabled;
+}
+
+void smux_init(struct thread_master *tm)
+{
+ agentx_tm = tm;
+
+ netsnmp_enable_subagent();
+ snmp_disable_log();
+ snmp_enable_calllog();
+ snmp_register_callback(SNMP_CALLBACK_LIBRARY, SNMP_CALLBACK_LOGGING,
+ agentx_log_callback, NULL);
+ init_agent(FRR_SMUX_NAME);
+
+ install_node(&agentx_node);
+ install_element(CONFIG_NODE, &agentx_enable_cmd);
+ install_element(CONFIG_NODE, &no_agentx_cmd);
+}
+
+void smux_agentx_enable(void)
+{
+ if (!agentx_enabled) {
+ init_snmp(FRR_SMUX_NAME);
+ events = list_new();
+ agentx_events_update();
+ agentx_enabled = 1;
+ }
+}
+
+void smux_register_mib(const char *descr, struct variable *var, size_t width,
+ int num, oid name[], size_t namelen)
+{
+ register_mib(descr, var, width, num, name, namelen);
+}
+
+void smux_trap(struct variable *vp, size_t vp_len, const oid *ename,
+ size_t enamelen, const oid *name, size_t namelen,
+ const oid *iname, size_t inamelen,
+ const struct trap_object *trapobj, size_t trapobjlen,
+ uint8_t sptrap)
+{
+ struct index_oid trap_index[1];
+
+ /* copy the single index into the multi-index format */
+ oid_copy(trap_index[0].indexname, iname, inamelen);
+ trap_index[0].indexlen = inamelen;
+
+ smux_trap_multi_index(vp, vp_len, ename, enamelen, name, namelen,
+ trap_index, array_size(trap_index), trapobj,
+ trapobjlen, sptrap);
+}
+
+int smux_trap_multi_index(struct variable *vp, size_t vp_len, const oid *ename,
+ size_t enamelen, const oid *name, size_t namelen,
+ struct index_oid *iname, size_t index_len,
+ const struct trap_object *trapobj, size_t trapobjlen,
+ uint8_t sptrap)
+{
+ oid objid_snmptrap[] = {1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0};
+ size_t objid_snmptrap_len = sizeof(objid_snmptrap) / sizeof(oid);
+ oid notification_oid[MAX_OID_LEN];
+ size_t notification_oid_len;
+ unsigned int i;
+
+ netsnmp_variable_list *notification_vars = NULL;
+ if (!agentx_enabled)
+ return 0;
+
+ /* snmpTrapOID */
+ oid_copy(notification_oid, ename, enamelen);
+ notification_oid[enamelen] = sptrap;
+ notification_oid_len = enamelen + 1;
+ snmp_varlist_add_variable(&notification_vars, objid_snmptrap,
+ objid_snmptrap_len, ASN_OBJECT_ID,
+ (uint8_t *)notification_oid,
+ notification_oid_len * sizeof(oid));
+
+ /* Provided bindings */
+ for (i = 0; i < trapobjlen; i++) {
+ unsigned int j;
+ oid oid[MAX_OID_LEN];
+ size_t oid_len, onamelen;
+ uint8_t *val;
+ size_t val_len;
+ WriteMethod *wm = NULL;
+ struct variable cvp;
+ unsigned int iindex;
+ /*
+ * this allows the behaviour of smux_trap with a singe index
+ * for all objects to be maintained whilst allowing traps which
+ * have different indices per object to be supported
+ */
+ iindex = (index_len == 1) ? 0 : i;
+
+ /* Make OID. */
+ if (trapobj[i].namelen > 0) {
+ /* Columnar object */
+ onamelen = trapobj[i].namelen;
+ oid_copy(oid, name, namelen);
+ oid_copy(oid + namelen, trapobj[i].name, onamelen);
+ oid_copy(oid + namelen + onamelen,
+ iname[iindex].indexname,
+ iname[iindex].indexlen);
+ oid_len = namelen + onamelen + iname[iindex].indexlen;
+ } else {
+ /* Scalar object */
+ onamelen = trapobj[i].namelen * (-1);
+ oid_copy(oid, name, namelen);
+ oid_copy(oid + namelen, trapobj[i].name, onamelen);
+ oid[onamelen + namelen] = 0;
+ oid_len = namelen + onamelen + 1;
+ }
+
+ /* Locate the appropriate function and type in the MIB registry.
+ */
+ for (j = 0; j < vp_len; j++) {
+ if (oid_compare(trapobj[i].name, onamelen, vp[j].name,
+ vp[j].namelen)
+ != 0)
+ continue;
+ /* We found the appropriate variable in the MIB
+ * registry. */
+ oid_copy(cvp.name, name, namelen);
+ oid_copy(cvp.name + namelen, vp[j].name, vp[j].namelen);
+ cvp.namelen = namelen + vp[j].namelen;
+ cvp.type = vp[j].type;
+ cvp.magic = vp[j].magic;
+ cvp.acl = vp[j].acl;
+ cvp.findVar = vp[j].findVar;
+
+ /* Grab the result. */
+ val = cvp.findVar(&cvp, oid, &oid_len, 1, &val_len,
+ &wm);
+ if (!val)
+ break;
+ snmp_varlist_add_variable(&notification_vars, oid,
+ oid_len, vp[j].type, val,
+ val_len);
+ break;
+ }
+ }
+
+
+ send_v2trap(notification_vars);
+ snmp_free_varbind(notification_vars);
+ agentx_events_update();
+ return 1;
+}
+
+void smux_events_update(void)
+{
+ agentx_events_update();
+}
+
+#endif /* SNMP_AGENTX */
diff --git a/lib/agg_table.c b/lib/agg_table.c
new file mode 100644
index 0000000..22b981e
--- /dev/null
+++ b/lib/agg_table.c
@@ -0,0 +1,59 @@
+/*
+ * Aggregate Route
+ * Copyright (C) 2018 Cumulus Networks, Inc.
+ * Donald Sharp
+ *
+ * FRR 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, or (at your option) any
+ * later version.
+ *
+ * FRR 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 "agg_table.h"
+
+
+static struct route_node *agg_node_create(route_table_delegate_t *delegate,
+ struct route_table *table)
+{
+ struct agg_node *node;
+
+ node = XCALLOC(MTYPE_TMP, sizeof(struct agg_node));
+
+ return agg_node_to_rnode(node);
+}
+
+static void agg_node_destroy(route_table_delegate_t *delegate,
+ struct route_table *table, struct route_node *node)
+
+{
+ struct agg_node *anode = agg_node_from_rnode(node);
+
+ XFREE(MTYPE_TMP, anode);
+}
+
+static route_table_delegate_t agg_table_delegate = {
+ .create_node = agg_node_create,
+ .destroy_node = agg_node_destroy,
+};
+
+struct agg_table *agg_table_init(void)
+{
+ struct agg_table *at;
+
+ at = XCALLOC(MTYPE_TMP, sizeof(struct agg_table));
+
+ at->route_table = route_table_init_with_delegate(&agg_table_delegate);
+ route_table_set_info(at->route_table, at);
+
+ return at;
+}
diff --git a/lib/agg_table.h b/lib/agg_table.h
new file mode 100644
index 0000000..e0c0644
--- /dev/null
+++ b/lib/agg_table.h
@@ -0,0 +1,177 @@
+/*
+ * agg_table - Aggregate Table Header
+ * Copyright (C) 2018 Cumulus Networks, Inc.
+ * Donald Sharp
+ *
+ * FRR 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, or (at your option) any
+ * later version.
+ *
+ * FRR 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 __AGG_TABLE_H__
+#define __AGG_TABLE_H__
+
+#include "prefix.h"
+#include "table.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct agg_table {
+ struct route_table *route_table;
+
+ void *info;
+};
+
+struct agg_node {
+ /*
+ * Caution these must be the very first fields
+ * @see agg_node_to_rnode
+ * @see agg_node_from_rnode
+ */
+ ROUTE_NODE_FIELDS
+
+ /* Aggregation. */
+ void *aggregate;
+};
+
+static inline struct route_node *agg_node_to_rnode(struct agg_node *node)
+{
+ return (struct route_node *)node;
+}
+
+static inline struct agg_node *agg_node_from_rnode(struct route_node *node)
+{
+ return (struct agg_node *)node;
+}
+
+static inline struct agg_node *agg_lock_node(struct agg_node *node)
+{
+ return (struct agg_node *)route_lock_node(agg_node_to_rnode(node));
+}
+
+static inline void agg_unlock_node(struct agg_node *node)
+{
+ route_unlock_node(agg_node_to_rnode(node));
+}
+
+static inline void agg_set_table_info(struct agg_table *atable, void *data)
+{
+ atable->info = data;
+}
+
+static inline void *agg_get_table_info(struct agg_table *atable)
+{
+ return atable->info;
+}
+
+static inline struct agg_node *agg_route_top(struct agg_table *table)
+{
+ return agg_node_from_rnode(route_top(table->route_table));
+}
+
+static inline struct agg_node *agg_route_next(struct agg_node *node)
+{
+ return agg_node_from_rnode(route_next(agg_node_to_rnode(node)));
+}
+
+static inline struct agg_node *agg_node_get(struct agg_table *table,
+ const struct prefix *p)
+{
+ return agg_node_from_rnode(route_node_get(table->route_table, p));
+}
+
+static inline struct agg_node *
+agg_node_lookup(const struct agg_table *const table, const struct prefix *p)
+{
+ return agg_node_from_rnode(route_node_lookup(table->route_table, p));
+}
+
+static inline struct agg_node *agg_route_next_until(struct agg_node *node,
+ struct agg_node *limit)
+{
+ struct route_node *rnode;
+
+ rnode = route_next_until(agg_node_to_rnode(node),
+ agg_node_to_rnode(limit));
+
+ return agg_node_from_rnode(rnode);
+}
+
+static inline struct agg_node *agg_node_match(struct agg_table *table,
+ const struct prefix *p)
+{
+ return agg_node_from_rnode(route_node_match(table->route_table, p));
+}
+
+static inline struct agg_node *agg_node_parent(struct agg_node *node)
+{
+ struct route_node *rn = agg_node_to_rnode(node);
+
+ return agg_node_from_rnode(rn->parent);
+}
+
+static inline struct agg_node *agg_node_left(struct agg_node *node)
+{
+ struct route_node *rn = agg_node_to_rnode(node);
+
+ return agg_node_from_rnode(rn->l_left);
+}
+
+static inline struct agg_node *agg_node_right(struct agg_node *node)
+{
+ struct route_node *rn = agg_node_to_rnode(node);
+
+ return agg_node_from_rnode(rn->l_right);
+}
+
+extern struct agg_table *agg_table_init(void);
+
+static inline void agg_table_finish(struct agg_table *atable)
+{
+ route_table_finish(atable->route_table);
+ atable->route_table = NULL;
+
+ XFREE(MTYPE_TMP, atable);
+}
+
+static inline struct agg_node *agg_route_table_top(struct agg_node *node)
+{
+ return (struct agg_node *)route_top(node->table);
+}
+
+static inline struct agg_table *agg_get_table(struct agg_node *node)
+{
+ return (struct agg_table *)route_table_get_info(node->table);
+}
+
+static inline const struct prefix *
+agg_node_get_prefix(const struct agg_node *node)
+{
+ return &node->p;
+}
+
+static inline unsigned int agg_node_get_lock_count(const struct agg_node *node)
+{
+ return node->lock;
+}
+
+#ifdef _FRR_ATTRIBUTE_PRINTFRR
+#pragma FRR printfrr_ext "%pRN" (struct agg_node *)
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/assert/assert.h b/lib/assert/assert.h
new file mode 100644
index 0000000..fbdbd52
--- /dev/null
+++ b/lib/assert/assert.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2021 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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.
+ */
+
+/* WARNING: this file is "special" in that it overrides the system-provided
+ * assert.h by being on the include path before it. That means it should
+ * provide the functional equivalent.
+ *
+ * This is intentional because FRR extends assert() to write to the log and
+ * add backtraces. Overriding the entire file is the simplest and most
+ * reliable way to get this to work; there were problems previously with the
+ * system assert.h getting included afterwards and redefining assert() back to
+ * the system variant.
+ */
+
+#ifndef _FRR_ASSERT_H
+#define _FRR_ASSERT_H
+
+#include "xref.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef __cplusplus
+/* C++ has this built-in, but C provides it in assert.h for >=C11. Since we
+ * replace assert.h entirely, we need to provide it here too.
+ */
+#define static_assert _Static_assert
+#endif
+
+struct xref_assert {
+ struct xref xref;
+
+ const char *expr;
+ const char *extra, *args;
+};
+
+extern void _zlog_assert_failed(const struct xref_assert *xref,
+ const char *extra, ...) PRINTFRR(2, 3)
+ __attribute__((noreturn));
+
+/* the "do { } while (expr_)" is there to get a warning for assignments inside
+ * the assert expression aka "assert(x = 1)". The (necessary) braces around
+ * expr_ in the if () statement would suppress these warnings. Since
+ * _zlog_assert_failed() is noreturn, the while condition will never be
+ * checked.
+ */
+#define assert(expr_) \
+ ({ \
+ static const struct xref_assert _xref __attribute__( \
+ (used)) = { \
+ .xref = XREF_INIT(XREFT_ASSERT, NULL, __func__), \
+ .expr = #expr_, \
+ }; \
+ XREF_LINK(_xref.xref); \
+ if (__builtin_expect((expr_) ? 0 : 1, 0)) \
+ do { \
+ _zlog_assert_failed(&_xref, NULL); \
+ } while (expr_); \
+ })
+
+#define assertf(expr_, extra_, ...) \
+ ({ \
+ static const struct xref_assert _xref __attribute__( \
+ (used)) = { \
+ .xref = XREF_INIT(XREFT_ASSERT, NULL, __func__), \
+ .expr = #expr_, \
+ .extra = extra_, \
+ .args = #__VA_ARGS__, \
+ }; \
+ XREF_LINK(_xref.xref); \
+ if (__builtin_expect((expr_) ? 0 : 1, 0)) \
+ do { \
+ _zlog_assert_failed(&_xref, extra_, \
+ ##__VA_ARGS__); \
+ } while (expr_); \
+ })
+
+#define zassert assert
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRR_ASSERT_H */
diff --git a/lib/atomlist.c b/lib/atomlist.c
new file mode 100644
index 0000000..2631d4f
--- /dev/null
+++ b/lib/atomlist.c
@@ -0,0 +1,350 @@
+/*
+ * Copyright (c) 2016-2018 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+
+#include "atomlist.h"
+
+void atomlist_add_head(struct atomlist_head *h, struct atomlist_item *item)
+{
+ atomptr_t prevval;
+ atomptr_t i = atomptr_i(item);
+
+ atomic_fetch_add_explicit(&h->count, 1, memory_order_relaxed);
+
+ /* updating ->last is possible here, but makes the code considerably
+ * more complicated... let's not.
+ */
+ prevval = ATOMPTR_NULL;
+ item->next = ATOMPTR_NULL;
+
+ /* head-insert atomically
+ * release barrier: item + item->next writes must be completed
+ */
+ while (!atomic_compare_exchange_weak_explicit(&h->first, &prevval, i,
+ memory_order_release, memory_order_relaxed))
+ atomic_store_explicit(&item->next, prevval,
+ memory_order_relaxed);
+}
+
+void atomlist_add_tail(struct atomlist_head *h, struct atomlist_item *item)
+{
+ atomptr_t prevval = ATOMPTR_NULL;
+ atomptr_t i = atomptr_i(item);
+ atomptr_t hint;
+ struct atomlist_item *prevptr;
+ _Atomic atomptr_t *prev;
+
+ item->next = ATOMPTR_NULL;
+
+ atomic_fetch_add_explicit(&h->count, 1, memory_order_relaxed);
+
+ /* place new item into ->last
+ * release: item writes completed; acquire: DD barrier on hint
+ */
+ hint = atomic_exchange_explicit(&h->last, i, memory_order_acq_rel);
+
+ while (1) {
+ if (atomptr_p(hint) == NULL)
+ prev = &h->first;
+ else
+ prev = &atomlist_itemp(hint)->next;
+
+ do {
+ prevval = atomic_load_explicit(prev,
+ memory_order_consume);
+ prevptr = atomlist_itemp(prevval);
+ if (prevptr == NULL)
+ break;
+
+ prev = &prevptr->next;
+ } while (prevptr);
+
+ /* last item is being deleted - start over */
+ if (atomptr_l(prevval)) {
+ hint = ATOMPTR_NULL;
+ continue;
+ }
+
+ /* no barrier - item->next is NULL and was so in xchg above */
+ if (!atomic_compare_exchange_strong_explicit(prev, &prevval, i,
+ memory_order_consume,
+ memory_order_consume)) {
+ hint = prevval;
+ continue;
+ }
+ break;
+ }
+}
+
+static void atomlist_del_core(struct atomlist_head *h,
+ struct atomlist_item *item,
+ _Atomic atomptr_t *hint,
+ atomptr_t next)
+{
+ _Atomic atomptr_t *prev = hint ? hint : &h->first, *upd;
+ atomptr_t prevval, updval;
+ struct atomlist_item *prevptr;
+
+ /* drop us off "last" if needed. no r/w to barrier. */
+ prevval = atomptr_i(item);
+ atomic_compare_exchange_strong_explicit(&h->last, &prevval,
+ ATOMPTR_NULL,
+ memory_order_relaxed, memory_order_relaxed);
+
+ atomic_fetch_sub_explicit(&h->count, 1, memory_order_relaxed);
+
+ /* the following code should be identical (except sort<>list) to
+ * atomsort_del_hint()
+ */
+ while (1) {
+ upd = NULL;
+ updval = ATOMPTR_LOCK;
+
+ do {
+ prevval = atomic_load_explicit(prev,
+ memory_order_consume);
+
+ /* track the beginning of a chain of deleted items
+ * this is necessary to make this lock-free; we can
+ * complete deletions started by other threads.
+ */
+ if (!atomptr_l(prevval)) {
+ updval = prevval;
+ upd = prev;
+ }
+
+ prevptr = atomlist_itemp(prevval);
+ if (prevptr == item)
+ break;
+
+ prev = &prevptr->next;
+ } while (prevptr);
+
+ if (prevptr != item)
+ /* another thread completed our deletion */
+ return;
+
+ if (!upd || atomptr_l(updval)) {
+ /* failed to find non-deleted predecessor...
+ * have to try again
+ */
+ prev = &h->first;
+ continue;
+ }
+
+ if (!atomic_compare_exchange_strong_explicit(upd, &updval,
+ next, memory_order_consume,
+ memory_order_consume)) {
+ /* prev doesn't point to item anymore, something
+ * was inserted. continue at same position forward.
+ */
+ continue;
+ }
+ break;
+ }
+}
+
+void atomlist_del_hint(struct atomlist_head *h, struct atomlist_item *item,
+ _Atomic atomptr_t *hint)
+{
+ atomptr_t next;
+
+ /* mark ourselves in-delete - full barrier */
+ next = atomic_fetch_or_explicit(&item->next, ATOMPTR_LOCK,
+ memory_order_acquire);
+ assert(!atomptr_l(next)); /* delete race on same item */
+
+ atomlist_del_core(h, item, hint, next);
+}
+
+struct atomlist_item *atomlist_pop(struct atomlist_head *h)
+{
+ struct atomlist_item *item;
+ atomptr_t next;
+
+ /* grab head of the list - and remember it in replval for the
+ * actual delete below. No matter what, the head of the list is
+ * where we start deleting because either it's our item, or it's
+ * some delete-marked items and then our item.
+ */
+ next = atomic_load_explicit(&h->first, memory_order_consume);
+
+ do {
+ item = atomlist_itemp(next);
+ if (!item)
+ return NULL;
+
+ /* try to mark deletion */
+ next = atomic_fetch_or_explicit(&item->next, ATOMPTR_LOCK,
+ memory_order_acquire);
+
+ } while (atomptr_l(next));
+ /* if loop is taken: delete race on same item (another pop or del)
+ * => proceed to next item
+ * if loop exited here: we have our item selected and marked
+ */
+ atomlist_del_core(h, item, &h->first, next);
+ return item;
+}
+
+struct atomsort_item *atomsort_add(struct atomsort_head *h,
+ struct atomsort_item *item, int (*cmpfn)(
+ const struct atomsort_item *,
+ const struct atomsort_item *))
+{
+ _Atomic atomptr_t *prev;
+ atomptr_t prevval;
+ atomptr_t i = atomptr_i(item);
+ struct atomsort_item *previtem;
+ int cmpval;
+
+ do {
+ prev = &h->first;
+
+ do {
+ prevval = atomic_load_explicit(prev,
+ memory_order_acquire);
+ previtem = atomptr_p(prevval);
+
+ if (!previtem || (cmpval = cmpfn(previtem, item)) > 0)
+ break;
+ if (cmpval == 0)
+ return previtem;
+
+ prev = &previtem->next;
+ } while (1);
+
+ if (atomptr_l(prevval))
+ continue;
+
+ item->next = prevval;
+ if (atomic_compare_exchange_strong_explicit(prev, &prevval, i,
+ memory_order_release, memory_order_relaxed))
+ break;
+ } while (1);
+
+ atomic_fetch_add_explicit(&h->count, 1, memory_order_relaxed);
+ return NULL;
+}
+
+static void atomsort_del_core(struct atomsort_head *h,
+ struct atomsort_item *item, _Atomic atomptr_t *hint,
+ atomptr_t next)
+{
+ _Atomic atomptr_t *prev = hint ? hint : &h->first, *upd;
+ atomptr_t prevval, updval;
+ struct atomsort_item *prevptr;
+
+ atomic_fetch_sub_explicit(&h->count, 1, memory_order_relaxed);
+
+ /* the following code should be identical (except sort<>list) to
+ * atomlist_del_core()
+ */
+ while (1) {
+ upd = NULL;
+ updval = ATOMPTR_LOCK;
+
+ do {
+ prevval = atomic_load_explicit(prev,
+ memory_order_consume);
+
+ /* track the beginning of a chain of deleted items
+ * this is necessary to make this lock-free; we can
+ * complete deletions started by other threads.
+ */
+ if (!atomptr_l(prevval)) {
+ updval = prevval;
+ upd = prev;
+ }
+
+ prevptr = atomsort_itemp(prevval);
+ if (prevptr == item)
+ break;
+
+ prev = &prevptr->next;
+ } while (prevptr);
+
+ if (prevptr != item)
+ /* another thread completed our deletion */
+ return;
+
+ if (!upd || atomptr_l(updval)) {
+ /* failed to find non-deleted predecessor...
+ * have to try again
+ */
+ prev = &h->first;
+ continue;
+ }
+
+ if (!atomic_compare_exchange_strong_explicit(upd, &updval,
+ next, memory_order_relaxed,
+ memory_order_relaxed)) {
+ /* prev doesn't point to item anymore, something
+ * was inserted. continue at same position forward.
+ */
+ continue;
+ }
+ break;
+ }
+}
+
+void atomsort_del_hint(struct atomsort_head *h, struct atomsort_item *item,
+ _Atomic atomptr_t *hint)
+{
+ atomptr_t next;
+
+ /* mark ourselves in-delete - full barrier */
+ next = atomic_fetch_or_explicit(&item->next, ATOMPTR_LOCK,
+ memory_order_seq_cst);
+ assert(!atomptr_l(next)); /* delete race on same item */
+
+ atomsort_del_core(h, item, hint, next);
+}
+
+struct atomsort_item *atomsort_pop(struct atomsort_head *h)
+{
+ struct atomsort_item *item;
+ atomptr_t next;
+
+ /* grab head of the list - and remember it in replval for the
+ * actual delete below. No matter what, the head of the list is
+ * where we start deleting because either it's our item, or it's
+ * some delete-marked items and then our item.
+ */
+ next = atomic_load_explicit(&h->first, memory_order_consume);
+
+ do {
+ item = atomsort_itemp(next);
+ if (!item)
+ return NULL;
+
+ /* try to mark deletion */
+ next = atomic_fetch_or_explicit(&item->next, ATOMPTR_LOCK,
+ memory_order_acquire);
+
+ } while (atomptr_l(next));
+ /* if loop is taken: delete race on same item (another pop or del)
+ * => proceed to next item
+ * if loop exited here: we have our item selected and marked
+ */
+ atomsort_del_core(h, item, &h->first, next);
+ return item;
+}
diff --git a/lib/atomlist.h b/lib/atomlist.h
new file mode 100644
index 0000000..b0c4da4
--- /dev/null
+++ b/lib/atomlist.h
@@ -0,0 +1,374 @@
+/*
+ * Copyright (c) 2016-2019 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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_ATOMLIST_H
+#define _FRR_ATOMLIST_H
+
+#include "typesafe.h"
+#include "frratomic.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* pointer with lock/deleted/invalid bit in lowest bit
+ *
+ * for atomlist/atomsort, "locked" means "this pointer can't be updated, the
+ * item is being deleted". it is permissible to assume the item will indeed
+ * be deleted (as there are no replace/etc. ops in this).
+ *
+ * in general, lowest 2/3 bits on 32/64bit architectures are available for
+ * uses like this; the only thing that will really break this is putting an
+ * atomlist_item in a struct with "packed" attribute. (it'll break
+ * immediately and consistently.) -- don't do that.
+ *
+ * ATOMPTR_USER is currently unused (and available for atomic hash or skiplist
+ * implementations.)
+ */
+
+/* atomic_atomptr_t may look a bit odd, it's for the sake of C++ compat */
+typedef uintptr_t atomptr_t;
+typedef atomic_uintptr_t atomic_atomptr_t;
+
+#define ATOMPTR_MASK (UINTPTR_MAX - 3)
+#define ATOMPTR_LOCK (1)
+#define ATOMPTR_USER (2)
+#define ATOMPTR_NULL (0)
+
+static inline atomptr_t atomptr_i(void *val)
+{
+ atomptr_t atomval = (atomptr_t)val;
+
+ assert(!(atomval & ATOMPTR_LOCK));
+ return atomval;
+}
+static inline void *atomptr_p(atomptr_t val)
+{
+ return (void *)(val & ATOMPTR_MASK);
+}
+static inline bool atomptr_l(atomptr_t val)
+{
+ return (bool)(val & ATOMPTR_LOCK);
+}
+static inline bool atomptr_u(atomptr_t val)
+{
+ return (bool)(val & ATOMPTR_USER);
+}
+
+
+/* the problem with, find(), find_gteq() and find_lt() on atomic lists is that
+ * they're neither an "acquire" nor a "release" operation; the element that
+ * was found is still on the list and doesn't change ownership. Therefore,
+ * an atomic transition in ownership state can't be implemented.
+ *
+ * Contrast this with add() or pop(): both function calls atomically transfer
+ * ownership of an item to or from the list, which makes them "acquire" /
+ * "release" operations.
+ *
+ * What can be implemented atomically is a "find_pop()", i.e. try to locate an
+ * item and atomically try to remove it if found. It's not currently
+ * implemented but can be added when needed.
+ *
+ * Either way - for find(), generally speaking, if you need to use find() on
+ * a list then the whole thing probably isn't well-suited to atomic
+ * implementation and you'll need to have extra locks around to make it work
+ * correctly.
+ */
+#ifdef WNO_ATOMLIST_UNSAFE_FIND
+# define atomic_find_warn
+#else
+# define atomic_find_warn __attribute__((_DEPRECATED( \
+ "WARNING: find() on atomic lists cannot be atomic by principle; " \
+ "check code to make sure usage pattern is OK and if it is, use " \
+ "#define WNO_ATOMLIST_UNSAFE_FIND")))
+#endif
+
+
+/* single-linked list, unsorted/arbitrary.
+ * can be used as queue with add_tail / pop
+ *
+ * all operations are lock-free, but not necessarily wait-free. this means
+ * that there is no state where the system as a whole stops making process,
+ * but it *is* possible that a *particular* thread is delayed by some time.
+ *
+ * the only way for this to happen is for other threads to continuously make
+ * updates. an inactive / blocked / deadlocked other thread cannot cause such
+ * delays, and to cause such delays a thread must be heavily hitting the list -
+ * it's a rather theoretical concern.
+ */
+
+/* don't use these structs directly */
+struct atomlist_item {
+ atomic_uintptr_t next;
+};
+#define atomlist_itemp(val) ((struct atomlist_item *)atomptr_p(val))
+
+struct atomlist_head {
+ atomic_uintptr_t first, last;
+ atomic_size_t count;
+};
+
+/* use as:
+ *
+ * PREDECL_ATOMLIST(namelist);
+ * struct name {
+ * struct namelist_item nlitem;
+ * }
+ * DECLARE_ATOMLIST(namelist, struct name, nlitem);
+ */
+#define PREDECL_ATOMLIST(prefix) \
+struct prefix ## _head { struct atomlist_head ah; }; \
+struct prefix ## _item { struct atomlist_item ai; }; \
+MACRO_REQUIRE_SEMICOLON() /* end */
+
+#define INIT_ATOMLIST(var) { }
+
+#define DECLARE_ATOMLIST(prefix, type, field) \
+macro_inline void prefix ## _add_head(struct prefix##_head *h, type *item) \
+{ atomlist_add_head(&h->ah, &item->field.ai); } \
+macro_inline void prefix ## _add_tail(struct prefix##_head *h, type *item) \
+{ atomlist_add_tail(&h->ah, &item->field.ai); } \
+macro_inline void prefix ## _del_hint(struct prefix##_head *h, type *item, \
+ atomic_atomptr_t *hint) \
+{ atomlist_del_hint(&h->ah, &item->field.ai, hint); } \
+macro_inline type *prefix ## _del(struct prefix##_head *h, type *item) \
+{ atomlist_del_hint(&h->ah, &item->field.ai, NULL); \
+ /* TODO: Return NULL if not found */ \
+ return item; } \
+macro_inline type *prefix ## _pop(struct prefix##_head *h) \
+{ char *p = (char *)atomlist_pop(&h->ah); \
+ return p ? (type *)(p - offsetof(type, field)) : NULL; } \
+macro_inline type *prefix ## _first(struct prefix##_head *h) \
+{ char *p = atomptr_p(atomic_load_explicit(&h->ah.first, \
+ memory_order_acquire)); \
+ return p ? (type *)(p - offsetof(type, field)) : NULL; } \
+macro_inline type *prefix ## _next(struct prefix##_head *h, type *item) \
+{ char *p = atomptr_p(atomic_load_explicit(&item->field.ai.next, \
+ memory_order_acquire)); \
+ return p ? (type *)(p - offsetof(type, field)) : NULL; } \
+macro_inline type *prefix ## _next_safe(struct prefix##_head *h, type *item) \
+{ return item ? prefix##_next(h, item) : NULL; } \
+macro_inline size_t prefix ## _count(struct prefix##_head *h) \
+{ return atomic_load_explicit(&h->ah.count, memory_order_relaxed); } \
+macro_inline void prefix ## _init(struct prefix##_head *h) \
+{ \
+ memset(h, 0, sizeof(*h)); \
+} \
+macro_inline void prefix ## _fini(struct prefix##_head *h) \
+{ \
+ assert(prefix ## _count(h) == 0); \
+ memset(h, 0, sizeof(*h)); \
+} \
+MACRO_REQUIRE_SEMICOLON() /* end */
+
+/* add_head:
+ * - contention on ->first pointer
+ * - return implies completion
+ */
+void atomlist_add_head(struct atomlist_head *h, struct atomlist_item *item);
+
+/* add_tail:
+ * - concurrent add_tail can cause wait but has progress guarantee
+ * - return does NOT imply completion. completion is only guaranteed after
+ * all other add_tail operations that started before this add_tail have
+ * completed as well.
+ */
+void atomlist_add_tail(struct atomlist_head *h, struct atomlist_item *item);
+
+/* del/del_hint:
+ *
+ * OWNER MUST HOLD REFERENCE ON ITEM TO BE DELETED, ENSURING NO OTHER THREAD
+ * WILL TRY TO DELETE THE SAME ITEM. DELETING INCLUDES pop().
+ *
+ * as with all deletions, threads that started reading earlier may still hold
+ * pointers to the deleted item. completion is however guaranteed for all
+ * reads starting later.
+ */
+void atomlist_del_hint(struct atomlist_head *h, struct atomlist_item *item,
+ atomic_atomptr_t *hint);
+
+/* pop:
+ *
+ * as with all deletions, threads that started reading earlier may still hold
+ * pointers to the deleted item. completion is however guaranteed for all
+ * reads starting later.
+ */
+struct atomlist_item *atomlist_pop(struct atomlist_head *h);
+
+
+
+struct atomsort_item {
+ atomic_atomptr_t next;
+};
+#define atomsort_itemp(val) ((struct atomsort_item *)atomptr_p(val))
+
+struct atomsort_head {
+ atomic_atomptr_t first;
+ atomic_size_t count;
+};
+
+#define _PREDECL_ATOMSORT(prefix) \
+struct prefix ## _head { struct atomsort_head ah; }; \
+struct prefix ## _item { struct atomsort_item ai; }; \
+MACRO_REQUIRE_SEMICOLON() /* end */
+
+#define INIT_ATOMSORT_UNIQ(var) { }
+#define INIT_ATOMSORT_NONUNIQ(var) { }
+
+#define _DECLARE_ATOMSORT(prefix, type, field, cmpfn_nuq, cmpfn_uq) \
+macro_inline void prefix ## _init(struct prefix##_head *h) \
+{ \
+ memset(h, 0, sizeof(*h)); \
+} \
+macro_inline void prefix ## _fini(struct prefix##_head *h) \
+{ \
+ assert(h->ah.count == 0); \
+ memset(h, 0, sizeof(*h)); \
+} \
+macro_inline type *prefix ## _add(struct prefix##_head *h, type *item) \
+{ \
+ struct atomsort_item *p; \
+ p = atomsort_add(&h->ah, &item->field.ai, cmpfn_uq); \
+ return container_of_null(p, type, field.ai); \
+} \
+macro_inline type *prefix ## _first(struct prefix##_head *h) \
+{ \
+ struct atomsort_item *p; \
+ p = atomptr_p(atomic_load_explicit(&h->ah.first, \
+ memory_order_acquire)); \
+ return container_of_null(p, type, field.ai); \
+} \
+macro_inline type *prefix ## _next(struct prefix##_head *h, type *item) \
+{ \
+ struct atomsort_item *p; \
+ p = atomptr_p(atomic_load_explicit(&item->field.ai.next, \
+ memory_order_acquire)); \
+ return container_of_null(p, type, field.ai); \
+} \
+macro_inline type *prefix ## _next_safe(struct prefix##_head *h, type *item) \
+{ \
+ return item ? prefix##_next(h, item) : NULL; \
+} \
+atomic_find_warn \
+macro_inline type *prefix ## _find_gteq(struct prefix##_head *h, \
+ const type *item) \
+{ \
+ type *p = prefix ## _first(h); \
+ while (p && cmpfn_nuq(&p->field.ai, &item->field.ai) < 0) \
+ p = prefix ## _next(h, p); \
+ return p; \
+} \
+atomic_find_warn \
+macro_inline type *prefix ## _find_lt(struct prefix##_head *h, \
+ const type *item) \
+{ \
+ type *p = prefix ## _first(h), *prev = NULL; \
+ while (p && cmpfn_nuq(&p->field.ai, &item->field.ai) < 0) \
+ p = prefix ## _next(h, (prev = p)); \
+ return prev; \
+} \
+macro_inline void prefix ## _del_hint(struct prefix##_head *h, type *item, \
+ atomic_atomptr_t *hint) \
+{ \
+ atomsort_del_hint(&h->ah, &item->field.ai, hint); \
+} \
+macro_inline type *prefix ## _del(struct prefix##_head *h, type *item) \
+{ \
+ atomsort_del_hint(&h->ah, &item->field.ai, NULL); \
+ /* TODO: Return NULL if not found */ \
+ return item; \
+} \
+macro_inline size_t prefix ## _count(struct prefix##_head *h) \
+{ \
+ return atomic_load_explicit(&h->ah.count, memory_order_relaxed); \
+} \
+macro_inline type *prefix ## _pop(struct prefix##_head *h) \
+{ \
+ struct atomsort_item *p = atomsort_pop(&h->ah); \
+ return p ? container_of(p, type, field.ai) : NULL; \
+} \
+MACRO_REQUIRE_SEMICOLON() /* end */
+
+#define PREDECL_ATOMSORT_UNIQ(prefix) \
+ _PREDECL_ATOMSORT(prefix)
+#define DECLARE_ATOMSORT_UNIQ(prefix, type, field, cmpfn) \
+ \
+macro_inline int prefix ## __cmp(const struct atomsort_item *a, \
+ const struct atomsort_item *b) \
+{ \
+ return cmpfn(container_of(a, type, field.ai), \
+ container_of(b, type, field.ai)); \
+} \
+ \
+_DECLARE_ATOMSORT(prefix, type, field, \
+ prefix ## __cmp, prefix ## __cmp); \
+ \
+atomic_find_warn \
+macro_inline type *prefix ## _find(struct prefix##_head *h, const type *item) \
+{ \
+ type *p = prefix ## _first(h); \
+ int cmpval = 0; \
+ while (p && (cmpval = cmpfn(p, item)) < 0) \
+ p = prefix ## _next(h, p); \
+ if (!p || cmpval > 0) \
+ return NULL; \
+ return p; \
+} \
+MACRO_REQUIRE_SEMICOLON() /* end */
+
+#define PREDECL_ATOMSORT_NONUNIQ(prefix) \
+ _PREDECL_ATOMSORT(prefix)
+#define DECLARE_ATOMSORT_NONUNIQ(prefix, type, field, cmpfn) \
+ \
+macro_inline int prefix ## __cmp(const struct atomsort_item *a, \
+ const struct atomsort_item *b) \
+{ \
+ return cmpfn(container_of(a, type, field.ai), \
+ container_of(b, type, field.ai)); \
+} \
+macro_inline int prefix ## __cmp_uq(const struct atomsort_item *a, \
+ const struct atomsort_item *b) \
+{ \
+ int cmpval = cmpfn(container_of(a, type, field.ai), \
+ container_of(b, type, field.ai)); \
+ if (cmpval) \
+ return cmpval; \
+ if (a < b) \
+ return -1; \
+ if (a > b) \
+ return 1; \
+ return 0; \
+} \
+ \
+_DECLARE_ATOMSORT(prefix, type, field, \
+ prefix ## __cmp, prefix ## __cmp_uq); \
+MACRO_REQUIRE_SEMICOLON() /* end */
+
+struct atomsort_item *atomsort_add(struct atomsort_head *h,
+ struct atomsort_item *item, int (*cmpfn)(
+ const struct atomsort_item *,
+ const struct atomsort_item *));
+
+void atomsort_del_hint(struct atomsort_head *h,
+ struct atomsort_item *item, atomic_atomptr_t *hint);
+
+struct atomsort_item *atomsort_pop(struct atomsort_head *h);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRR_ATOMLIST_H */
diff --git a/lib/base64.c b/lib/base64.c
new file mode 100644
index 0000000..6f0be03
--- /dev/null
+++ b/lib/base64.c
@@ -0,0 +1,197 @@
+/*
+ * This is part of the libb64 project, and has been placed in the public domain.
+ * For details, see http://sourceforge.net/projects/libb64
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "base64.h"
+
+static const int CHARS_PER_LINE = 72;
+static const char *ENCODING =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+void base64_init_encodestate(struct base64_encodestate *state_in)
+{
+ state_in->step = step_A;
+ state_in->result = 0;
+ state_in->stepcount = 0;
+}
+
+char base64_encode_value(char value_in)
+{
+ if (value_in > 63)
+ return '=';
+ return ENCODING[(int)value_in];
+}
+
+int base64_encode_block(const char *plaintext_in, int length_in, char *code_out,
+ struct base64_encodestate *state_in)
+{
+ const char *plainchar = plaintext_in;
+ const char *const plaintextend = plaintext_in + length_in;
+ char *codechar = code_out;
+ char result;
+ char fragment;
+
+ result = state_in->result;
+
+ switch (state_in->step) {
+ while (1) {
+ case step_A:
+ if (plainchar == plaintextend) {
+ state_in->result = result;
+ state_in->step = step_A;
+ return codechar - code_out;
+ }
+ fragment = *plainchar++;
+ result = (fragment & 0x0fc) >> 2;
+ *codechar++ = base64_encode_value(result);
+ result = (fragment & 0x003) << 4;
+ /* fall through */
+ case step_B:
+ if (plainchar == plaintextend) {
+ state_in->result = result;
+ state_in->step = step_B;
+ return codechar - code_out;
+ }
+ fragment = *plainchar++;
+ result |= (fragment & 0x0f0) >> 4;
+ *codechar++ = base64_encode_value(result);
+ result = (fragment & 0x00f) << 2;
+ /* fall through */
+ case step_C:
+ if (plainchar == plaintextend) {
+ state_in->result = result;
+ state_in->step = step_C;
+ return codechar - code_out;
+ }
+ fragment = *plainchar++;
+ result |= (fragment & 0x0c0) >> 6;
+ *codechar++ = base64_encode_value(result);
+ result = (fragment & 0x03f) >> 0;
+ *codechar++ = base64_encode_value(result);
+
+ ++(state_in->stepcount);
+ if (state_in->stepcount == CHARS_PER_LINE/4) {
+ *codechar++ = '\n';
+ state_in->stepcount = 0;
+ }
+ }
+ }
+ /* control should not reach here */
+ return codechar - code_out;
+}
+
+int base64_encode_blockend(char *code_out, struct base64_encodestate *state_in)
+{
+ char *codechar = code_out;
+
+ switch (state_in->step) {
+ case step_B:
+ *codechar++ = base64_encode_value(state_in->result);
+ *codechar++ = '=';
+ *codechar++ = '=';
+ break;
+ case step_C:
+ *codechar++ = base64_encode_value(state_in->result);
+ *codechar++ = '=';
+ break;
+ case step_A:
+ break;
+ }
+ *codechar++ = '\n';
+
+ return codechar - code_out;
+}
+
+
+signed char base64_decode_value(signed char value_in)
+{
+ static const signed char decoding[] = {
+ 62, -1, -1, -1, 63, 52, 53, 54,
+ 55, 56, 57, 58, 59, 60, 61, -1,
+ -1, -1, -2, -1, -1, -1, 0, 1,
+ 2, 3, 4, 5, 6, 7, 8, 9,
+ 10, 11, 12, 13, 14, 15, 16, 17,
+ 18, 19, 20, 21, 22, 23, 24, 25,
+ -1, -1, -1, -1, -1, -1, 26, 27,
+ 28, 29, 30, 31, 32, 33, 34, 35,
+ 36, 37, 38, 39, 40, 41, 42, 43,
+ 44, 45, 46, 47, 48, 49, 50, 51
+ };
+ value_in -= 43;
+ if (value_in < 0 || value_in >= 80)
+ return -1;
+ return decoding[(int)value_in];
+}
+
+void base64_init_decodestate(struct base64_decodestate *state_in)
+{
+ state_in->step = step_a;
+ state_in->plainchar = 0;
+}
+
+int base64_decode_block(const char *code_in, int length_in, char *plaintext_out,
+ struct base64_decodestate *state_in)
+{
+ const char *codec = code_in;
+ char *plainc = plaintext_out;
+ signed char fragmt;
+
+ *plainc = state_in->plainchar;
+
+ switch (state_in->step) {
+ while (1) {
+ case step_a:
+ do {
+ if (codec == code_in+length_in) {
+ state_in->step = step_a;
+ state_in->plainchar = *plainc;
+ return plainc - plaintext_out;
+ }
+ fragmt = base64_decode_value(*codec++);
+ } while (fragmt < 0);
+ *plainc = (fragmt & 0x03f) << 2;
+ /* fall through */
+ case step_b:
+ do {
+ if (codec == code_in+length_in) {
+ state_in->step = step_b;
+ state_in->plainchar = *plainc;
+ return plainc - plaintext_out;
+ }
+ fragmt = base64_decode_value(*codec++);
+ } while (fragmt < 0);
+ *plainc++ |= (fragmt & 0x030) >> 4;
+ *plainc = (fragmt & 0x00f) << 4;
+ /* fall through */
+ case step_c:
+ do {
+ if (codec == code_in+length_in) {
+ state_in->step = step_c;
+ state_in->plainchar = *plainc;
+ return plainc - plaintext_out;
+ }
+ fragmt = base64_decode_value(*codec++);
+ } while (fragmt < 0);
+ *plainc++ |= (fragmt & 0x03c) >> 2;
+ *plainc = (fragmt & 0x003) << 6;
+ /* fall through */
+ case step_d:
+ do {
+ if (codec == code_in+length_in) {
+ state_in->step = step_d;
+ state_in->plainchar = *plainc;
+ return plainc - plaintext_out;
+ }
+ fragmt = base64_decode_value(*codec++);
+ } while (fragmt < 0);
+ *plainc++ |= (fragmt & 0x03f);
+ }
+ }
+ /* control should not reach here */
+ return plainc - plaintext_out;
+}
diff --git a/lib/base64.h b/lib/base64.h
new file mode 100644
index 0000000..3dc1559
--- /dev/null
+++ b/lib/base64.h
@@ -0,0 +1,45 @@
+/*
+ * This is part of the libb64 project, and has been placed in the public domain.
+ * For details, see http://sourceforge.net/projects/libb64
+ */
+
+#ifndef _BASE64_H_
+#define _BASE64_H_
+
+enum base64_encodestep {
+ step_A, step_B, step_C
+};
+
+struct base64_encodestate {
+ enum base64_encodestep step;
+ char result;
+ int stepcount;
+};
+
+void base64_init_encodestate(struct base64_encodestate *state_in);
+
+char base64_encode_value(char value_in);
+
+int base64_encode_block(const char *plaintext_in, int length_in, char *code_out,
+ struct base64_encodestate *state_in);
+
+int base64_encode_blockend(char *code_out, struct base64_encodestate *state_in);
+
+
+enum base64_decodestep {
+ step_a, step_b, step_c, step_d
+};
+
+struct base64_decodestate {
+ enum base64_decodestep step;
+ char plainchar;
+};
+
+void base64_init_decodestate(struct base64_decodestate *state_in);
+
+signed char base64_decode_value(signed char value_in);
+
+int base64_decode_block(const char *code_in, int length_in, char *plaintext_out,
+ struct base64_decodestate *state_in);
+
+#endif /* _BASE64_H_ */
diff --git a/lib/bfd.c b/lib/bfd.c
new file mode 100644
index 0000000..ec13524
--- /dev/null
+++ b/lib/bfd.c
@@ -0,0 +1,987 @@
+/**
+ * bfd.c: BFD handling routines
+ *
+ * @copyright Copyright (C) 2015 Cumulus Networks, Inc.
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 "command.h"
+#include "memory.h"
+#include "prefix.h"
+#include "thread.h"
+#include "stream.h"
+#include "vrf.h"
+#include "zclient.h"
+#include "table.h"
+#include "vty.h"
+#include "bfd.h"
+
+DEFINE_MTYPE_STATIC(LIB, BFD_INFO, "BFD info");
+
+/**
+ * BFD protocol integration configuration.
+ */
+
+/** Events definitions. */
+enum bfd_session_event {
+ /** Remove the BFD session configuration. */
+ BSE_UNINSTALL,
+ /** Install the BFD session configuration. */
+ BSE_INSTALL,
+};
+
+/**
+ * Data structure to do the necessary tricks to hide the BFD protocol
+ * integration internals.
+ */
+struct bfd_session_params {
+ /** Contains the session parameters and more. */
+ struct bfd_session_arg args;
+ /** Contains the session state. */
+ struct bfd_session_status bss;
+ /** Protocol implementation status update callback. */
+ bsp_status_update updatecb;
+ /** Protocol implementation custom data pointer. */
+ void *arg;
+
+ /**
+ * Next event.
+ *
+ * This variable controls what action to execute when the command batch
+ * finishes. Normally we'd use `thread_add_event` value, however since
+ * that function is going to be called multiple times and the value
+ * might be different we'll use this variable to keep track of it.
+ */
+ enum bfd_session_event lastev;
+ /**
+ * BFD session configuration event.
+ *
+ * Multiple actions might be asked during a command batch (either via
+ * configuration load or northbound batch), so we'll use this to
+ * install/uninstall the BFD session parameters only once.
+ */
+ struct thread *installev;
+
+ /** BFD session installation state. */
+ bool installed;
+
+ /** Global BFD paramaters list. */
+ TAILQ_ENTRY(bfd_session_params) entry;
+};
+
+struct bfd_sessions_global {
+ /**
+ * Global BFD session parameters list for (re)installation and update
+ * without code duplication among daemons.
+ */
+ TAILQ_HEAD(bsplist, bfd_session_params) bsplist;
+
+ /** Pointer to FRR's event manager. */
+ struct thread_master *tm;
+ /** Pointer to zebra client data structure. */
+ struct zclient *zc;
+
+ /** Debugging state. */
+ bool debugging;
+ /** Is shutting down? */
+ bool shutting_down;
+};
+
+/** Global configuration variable. */
+static struct bfd_sessions_global bsglobal;
+
+/** Global empty address for IPv4/IPv6. */
+static const struct in6_addr i6a_zero;
+
+/*
+ * bfd_get_peer_info - Extract the Peer information for which the BFD session
+ * went down from the message sent from Zebra to clients.
+ */
+static struct interface *bfd_get_peer_info(struct stream *s, struct prefix *dp,
+ struct prefix *sp, int *status,
+ int *remote_cbit, vrf_id_t vrf_id)
+{
+ unsigned int ifindex;
+ struct interface *ifp = NULL;
+ int plen;
+ int local_remote_cbit;
+
+ /*
+ * If the ifindex lookup fails the
+ * rest of the data in the stream is
+ * not read. All examples of this function
+ * call immediately use the dp->family which
+ * is not good. Ensure we are not using
+ * random data
+ */
+ memset(dp, 0, sizeof(*dp));
+ memset(sp, 0, sizeof(*sp));
+
+ /* Get interface index. */
+ STREAM_GETL(s, ifindex);
+
+ /* Lookup index. */
+ if (ifindex != 0) {
+ ifp = if_lookup_by_index(ifindex, vrf_id);
+ if (ifp == NULL) {
+ if (bsglobal.debugging)
+ zlog_debug(
+ "%s: Can't find interface by ifindex: %d ",
+ __func__, ifindex);
+ return NULL;
+ }
+ }
+
+ /* Fetch destination address. */
+ STREAM_GETC(s, dp->family);
+
+ plen = prefix_blen(dp);
+ STREAM_GET(&dp->u.prefix, s, plen);
+ STREAM_GETC(s, dp->prefixlen);
+
+ /* Get BFD status. */
+ STREAM_GETL(s, (*status));
+
+ STREAM_GETC(s, sp->family);
+
+ plen = prefix_blen(sp);
+ STREAM_GET(&sp->u.prefix, s, plen);
+ STREAM_GETC(s, sp->prefixlen);
+
+ STREAM_GETC(s, local_remote_cbit);
+ if (remote_cbit)
+ *remote_cbit = local_remote_cbit;
+ return ifp;
+
+stream_failure:
+ /*
+ * Clean dp and sp because caller
+ * will immediately check them valid or not
+ */
+ memset(dp, 0, sizeof(*dp));
+ memset(sp, 0, sizeof(*sp));
+ return NULL;
+}
+
+/*
+ * bfd_get_status_str - Convert BFD status to a display string.
+ */
+const char *bfd_get_status_str(int status)
+{
+ switch (status) {
+ case BFD_STATUS_DOWN:
+ return "Down";
+ case BFD_STATUS_UP:
+ return "Up";
+ case BFD_STATUS_ADMIN_DOWN:
+ return "Admin Down";
+ case BFD_STATUS_UNKNOWN:
+ default:
+ return "Unknown";
+ }
+}
+
+/*
+ * bfd_last_update - Calculate the last BFD update time and convert it
+ * into a dd:hh:mm:ss display format.
+ */
+static void bfd_last_update(time_t last_update, char *buf, size_t len)
+{
+ time_t curr;
+ time_t diff;
+ struct tm tm;
+ struct timeval tv;
+
+ /* If no BFD status update has ever been received, print `never'. */
+ if (last_update == 0) {
+ snprintf(buf, len, "never");
+ return;
+ }
+
+ /* Get current time. */
+ monotime(&tv);
+ curr = tv.tv_sec;
+ diff = curr - last_update;
+ gmtime_r(&diff, &tm);
+
+ snprintf(buf, len, "%d:%02d:%02d:%02d", tm.tm_yday, tm.tm_hour,
+ tm.tm_min, tm.tm_sec);
+}
+
+/*
+ * bfd_client_sendmsg - Format and send a client register
+ * command to Zebra to be forwarded to BFD
+ */
+void bfd_client_sendmsg(struct zclient *zclient, int command,
+ vrf_id_t vrf_id)
+{
+ struct stream *s;
+ enum zclient_send_status ret;
+
+ /* Check socket. */
+ if (!zclient || zclient->sock < 0) {
+ if (bsglobal.debugging)
+ zlog_debug(
+ "%s: Can't send BFD client register, Zebra client not established",
+ __func__);
+ return;
+ }
+
+ s = zclient->obuf;
+ stream_reset(s);
+ zclient_create_header(s, command, vrf_id);
+
+ stream_putl(s, getpid());
+
+ stream_putw_at(s, 0, stream_get_endp(s));
+
+ ret = zclient_send_message(zclient);
+
+ if (ret == ZCLIENT_SEND_FAILURE) {
+ if (bsglobal.debugging)
+ zlog_debug(
+ "%s: %ld: zclient_send_message() failed",
+ __func__, (long)getpid());
+ return;
+ }
+
+ return;
+}
+
+int zclient_bfd_command(struct zclient *zc, struct bfd_session_arg *args)
+{
+ struct stream *s;
+ size_t addrlen;
+
+ /* Individual reg/dereg messages are suppressed during shutdown. */
+ if (bsglobal.shutting_down) {
+ if (bsglobal.debugging)
+ zlog_debug(
+ "%s: Suppressing BFD peer reg/dereg messages",
+ __func__);
+ return -1;
+ }
+
+ /* Check socket. */
+ if (!zc || zc->sock < 0) {
+ if (bsglobal.debugging)
+ zlog_debug("%s: zclient unavailable", __func__);
+ return -1;
+ }
+
+ s = zc->obuf;
+ stream_reset(s);
+
+ /* Create new message. */
+ zclient_create_header(s, args->command, args->vrf_id);
+ stream_putl(s, getpid());
+
+ /* Encode destination address. */
+ stream_putw(s, args->family);
+ addrlen = (args->family == AF_INET) ? sizeof(struct in_addr)
+ : sizeof(struct in6_addr);
+ stream_put(s, &args->dst, addrlen);
+
+ /*
+ * For more BFD integration protocol details, see function
+ * `_ptm_msg_read` in `bfdd/ptm_adapter.c`.
+ */
+#if HAVE_BFDD > 0
+ /* Session timers. */
+ stream_putl(s, args->min_rx);
+ stream_putl(s, args->min_tx);
+ stream_putc(s, args->detection_multiplier);
+
+ /* Is multi hop? */
+ stream_putc(s, args->mhop != 0);
+
+ /* Source address. */
+ stream_putw(s, args->family);
+ stream_put(s, &args->src, addrlen);
+
+ /* Send the expected hops. */
+ stream_putc(s, args->hops);
+
+ /* Send interface name if any. */
+ if (args->mhop) {
+ /* Don't send interface. */
+ stream_putc(s, 0);
+ if (bsglobal.debugging && args->ifnamelen)
+ zlog_debug("%s: multi hop is configured, not sending interface",
+ __func__);
+ } else {
+ stream_putc(s, args->ifnamelen);
+ if (args->ifnamelen)
+ stream_put(s, args->ifname, args->ifnamelen);
+ }
+
+ /* Send the C bit indicator. */
+ stream_putc(s, args->cbit);
+
+ /* Send profile name if any. */
+ stream_putc(s, args->profilelen);
+ if (args->profilelen)
+ stream_put(s, args->profile, args->profilelen);
+#else /* PTM BFD */
+ /* Encode timers if this is a registration message. */
+ if (args->command != ZEBRA_BFD_DEST_DEREGISTER) {
+ stream_putl(s, args->min_rx);
+ stream_putl(s, args->min_tx);
+ stream_putc(s, args->detection_multiplier);
+ }
+
+ if (args->mhop) {
+ /* Multi hop indicator. */
+ stream_putc(s, 1);
+
+ /* Multi hop always sends the source address. */
+ stream_putw(s, args->family);
+ stream_put(s, &args->src, addrlen);
+
+ /* Send the expected hops. */
+ stream_putc(s, args->hops);
+ } else {
+ /* Multi hop indicator. */
+ stream_putc(s, 0);
+
+ /* Single hop only sends the source address when IPv6. */
+ if (args->family == AF_INET6) {
+ stream_putw(s, args->family);
+ stream_put(s, &args->src, addrlen);
+ }
+
+ /* Send interface name if any. */
+ stream_putc(s, args->ifnamelen);
+ if (args->ifnamelen)
+ stream_put(s, args->ifname, args->ifnamelen);
+ }
+
+ /* Send the C bit indicator. */
+ stream_putc(s, args->cbit);
+#endif /* HAVE_BFDD */
+
+ /* Finish the message by writing the size. */
+ stream_putw_at(s, 0, stream_get_endp(s));
+
+ /* Send message to zebra. */
+ if (zclient_send_message(zc) == ZCLIENT_SEND_FAILURE) {
+ if (bsglobal.debugging)
+ zlog_debug("%s: zclient_send_message failed", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+struct bfd_session_params *bfd_sess_new(bsp_status_update updatecb, void *arg)
+{
+ struct bfd_session_params *bsp;
+
+ bsp = XCALLOC(MTYPE_BFD_INFO, sizeof(*bsp));
+
+ /* Save application data. */
+ bsp->updatecb = updatecb;
+ bsp->arg = arg;
+
+ /* Set defaults. */
+ bsp->args.detection_multiplier = BFD_DEF_DETECT_MULT;
+ bsp->args.hops = 1;
+ bsp->args.min_rx = BFD_DEF_MIN_RX;
+ bsp->args.min_tx = BFD_DEF_MIN_TX;
+ bsp->args.vrf_id = VRF_DEFAULT;
+
+ /* Register in global list. */
+ TAILQ_INSERT_TAIL(&bsglobal.bsplist, bsp, entry);
+
+ return bsp;
+}
+
+static bool _bfd_sess_valid(const struct bfd_session_params *bsp)
+{
+ /* Peer/local address not configured. */
+ if (bsp->args.family == 0)
+ return false;
+
+ /* Address configured but invalid. */
+ if (bsp->args.family != AF_INET && bsp->args.family != AF_INET6) {
+ if (bsglobal.debugging)
+ zlog_debug("%s: invalid session family: %d", __func__,
+ bsp->args.family);
+ return false;
+ }
+
+ /* Invalid address. */
+ if (memcmp(&bsp->args.dst, &i6a_zero, sizeof(i6a_zero)) == 0) {
+ if (bsglobal.debugging) {
+ if (bsp->args.family == AF_INET)
+ zlog_debug("%s: invalid address: %pI4",
+ __func__,
+ (struct in_addr *)&bsp->args.dst);
+ else
+ zlog_debug("%s: invalid address: %pI6",
+ __func__, &bsp->args.dst);
+ }
+ return false;
+ }
+
+ /* Multi hop requires local address. */
+ if (bsp->args.mhop
+ && memcmp(&i6a_zero, &bsp->args.src, sizeof(i6a_zero)) == 0) {
+ if (bsglobal.debugging)
+ zlog_debug(
+ "%s: multi hop but no local address provided",
+ __func__);
+ return false;
+ }
+
+ /* Check VRF ID. */
+ if (bsp->args.vrf_id == VRF_UNKNOWN) {
+ if (bsglobal.debugging)
+ zlog_debug("%s: asked for unknown VRF", __func__);
+ return false;
+ }
+
+ return true;
+}
+
+static void _bfd_sess_send(struct thread *t)
+{
+ struct bfd_session_params *bsp = THREAD_ARG(t);
+ int rv;
+
+ /* Validate configuration before trying to send bogus data. */
+ if (!_bfd_sess_valid(bsp))
+ return;
+
+ if (bsp->lastev == BSE_INSTALL) {
+ bsp->args.command = bsp->installed ? ZEBRA_BFD_DEST_UPDATE
+ : ZEBRA_BFD_DEST_REGISTER;
+ } else
+ bsp->args.command = ZEBRA_BFD_DEST_DEREGISTER;
+
+ /* If not installed and asked for uninstall, do nothing. */
+ if (!bsp->installed && bsp->args.command == ZEBRA_BFD_DEST_DEREGISTER)
+ return;
+
+ rv = zclient_bfd_command(bsglobal.zc, &bsp->args);
+ /* Command was sent successfully. */
+ if (rv == 0) {
+ /* Update installation status. */
+ if (bsp->args.command == ZEBRA_BFD_DEST_DEREGISTER)
+ bsp->installed = false;
+ else if (bsp->args.command == ZEBRA_BFD_DEST_REGISTER)
+ bsp->installed = true;
+ } else {
+ struct ipaddr src, dst;
+
+ src.ipa_type = bsp->args.family;
+ src.ipaddr_v6 = bsp->args.src;
+ dst.ipa_type = bsp->args.family;
+ dst.ipaddr_v6 = bsp->args.dst;
+
+ zlog_err(
+ "%s: BFD session %pIA -> %pIA interface %s VRF %s(%u) was not %s",
+ __func__, &src, &dst,
+ bsp->args.ifnamelen ? bsp->args.ifname : "*",
+ vrf_id_to_name(bsp->args.vrf_id), bsp->args.vrf_id,
+ bsp->lastev == BSE_INSTALL ? "installed"
+ : "uninstalled");
+ }
+}
+
+static void _bfd_sess_remove(struct bfd_session_params *bsp)
+{
+ /* Cancel any pending installation request. */
+ THREAD_OFF(bsp->installev);
+
+ /* Not installed, nothing to do. */
+ if (!bsp->installed)
+ return;
+
+ /* Send request to remove any session. */
+ bsp->lastev = BSE_UNINSTALL;
+ thread_execute(bsglobal.tm, _bfd_sess_send, bsp, 0);
+}
+
+void bfd_sess_free(struct bfd_session_params **bsp)
+{
+ if (*bsp == NULL)
+ return;
+
+ /* Remove any installed session. */
+ _bfd_sess_remove(*bsp);
+
+ /* Remove from global list. */
+ TAILQ_REMOVE(&bsglobal.bsplist, (*bsp), entry);
+
+ /* Free the memory and point to NULL. */
+ XFREE(MTYPE_BFD_INFO, (*bsp));
+}
+
+static bool bfd_sess_address_changed(const struct bfd_session_params *bsp,
+ uint32_t family,
+ const struct in6_addr *src,
+ const struct in6_addr *dst)
+{
+ size_t addrlen;
+
+ if (bsp->args.family != family)
+ return true;
+
+ addrlen = (family == AF_INET) ? sizeof(struct in_addr)
+ : sizeof(struct in6_addr);
+ if ((src == NULL && memcmp(&bsp->args.src, &i6a_zero, addrlen))
+ || (src && memcmp(src, &bsp->args.src, addrlen))
+ || memcmp(dst, &bsp->args.dst, addrlen))
+ return true;
+
+ return false;
+}
+
+void bfd_sess_set_ipv4_addrs(struct bfd_session_params *bsp,
+ const struct in_addr *src,
+ const struct in_addr *dst)
+{
+ if (!bfd_sess_address_changed(bsp, AF_INET, (struct in6_addr *)src,
+ (struct in6_addr *)dst))
+ return;
+
+ /* If already installed, remove the old setting. */
+ _bfd_sess_remove(bsp);
+
+ bsp->args.family = AF_INET;
+
+ /* Clean memory, set zero value and avoid static analyser warnings. */
+ memset(&bsp->args.src, 0, sizeof(bsp->args.src));
+ memset(&bsp->args.dst, 0, sizeof(bsp->args.dst));
+
+ /* Copy the equivalent of IPv4 to arguments structure. */
+ if (src)
+ memcpy(&bsp->args.src, src, sizeof(struct in_addr));
+
+ assert(dst);
+ memcpy(&bsp->args.dst, dst, sizeof(struct in_addr));
+}
+
+void bfd_sess_set_ipv6_addrs(struct bfd_session_params *bsp,
+ const struct in6_addr *src,
+ const struct in6_addr *dst)
+{
+ if (!bfd_sess_address_changed(bsp, AF_INET6, src, dst))
+ return;
+
+ /* If already installed, remove the old setting. */
+ _bfd_sess_remove(bsp);
+
+ bsp->args.family = AF_INET6;
+
+ /* Clean memory, set zero value and avoid static analyser warnings. */
+ memset(&bsp->args.src, 0, sizeof(bsp->args.src));
+
+ if (src)
+ bsp->args.src = *src;
+
+ assert(dst);
+ bsp->args.dst = *dst;
+}
+
+void bfd_sess_set_interface(struct bfd_session_params *bsp, const char *ifname)
+{
+ if ((ifname == NULL && bsp->args.ifnamelen == 0)
+ || (ifname && strcmp(bsp->args.ifname, ifname) == 0))
+ return;
+
+ /* If already installed, remove the old setting. */
+ _bfd_sess_remove(bsp);
+
+ if (ifname == NULL) {
+ bsp->args.ifname[0] = 0;
+ bsp->args.ifnamelen = 0;
+ return;
+ }
+
+ if (strlcpy(bsp->args.ifname, ifname, sizeof(bsp->args.ifname))
+ > sizeof(bsp->args.ifname))
+ zlog_warn("%s: interface name truncated: %s", __func__, ifname);
+
+ bsp->args.ifnamelen = strlen(bsp->args.ifname);
+}
+
+void bfd_sess_set_profile(struct bfd_session_params *bsp, const char *profile)
+{
+ if (profile == NULL) {
+ bsp->args.profile[0] = 0;
+ bsp->args.profilelen = 0;
+ return;
+ }
+
+ if (strlcpy(bsp->args.profile, profile, sizeof(bsp->args.profile))
+ > sizeof(bsp->args.profile))
+ zlog_warn("%s: profile name truncated: %s", __func__, profile);
+
+ bsp->args.profilelen = strlen(bsp->args.profile);
+}
+
+void bfd_sess_set_vrf(struct bfd_session_params *bsp, vrf_id_t vrf_id)
+{
+ if (bsp->args.vrf_id == vrf_id)
+ return;
+
+ /* If already installed, remove the old setting. */
+ _bfd_sess_remove(bsp);
+
+ bsp->args.vrf_id = vrf_id;
+}
+
+void bfd_sess_set_hop_count(struct bfd_session_params *bsp, uint8_t hops)
+{
+ if (bsp->args.hops == hops)
+ return;
+
+ /* If already installed, remove the old setting. */
+ _bfd_sess_remove(bsp);
+
+ bsp->args.hops = hops;
+ bsp->args.mhop = (hops > 1);
+}
+
+
+void bfd_sess_set_cbit(struct bfd_session_params *bsp, bool enable)
+{
+ bsp->args.cbit = enable;
+}
+
+void bfd_sess_set_timers(struct bfd_session_params *bsp,
+ uint8_t detection_multiplier, uint32_t min_rx,
+ uint32_t min_tx)
+{
+ bsp->args.detection_multiplier = detection_multiplier;
+ bsp->args.min_rx = min_rx;
+ bsp->args.min_tx = min_tx;
+}
+
+void bfd_sess_install(struct bfd_session_params *bsp)
+{
+ bsp->lastev = BSE_INSTALL;
+ thread_add_event(bsglobal.tm, _bfd_sess_send, bsp, 0, &bsp->installev);
+}
+
+void bfd_sess_uninstall(struct bfd_session_params *bsp)
+{
+ bsp->lastev = BSE_UNINSTALL;
+ thread_add_event(bsglobal.tm, _bfd_sess_send, bsp, 0, &bsp->installev);
+}
+
+enum bfd_session_state bfd_sess_status(const struct bfd_session_params *bsp)
+{
+ return bsp->bss.state;
+}
+
+uint8_t bfd_sess_hop_count(const struct bfd_session_params *bsp)
+{
+ return bsp->args.hops;
+}
+
+const char *bfd_sess_profile(const struct bfd_session_params *bsp)
+{
+ return bsp->args.profilelen ? bsp->args.profile : NULL;
+}
+
+void bfd_sess_addresses(const struct bfd_session_params *bsp, int *family,
+ struct in6_addr *src, struct in6_addr *dst)
+{
+ *family = bsp->args.family;
+ if (src)
+ *src = bsp->args.src;
+ if (dst)
+ *dst = bsp->args.dst;
+}
+
+const char *bfd_sess_interface(const struct bfd_session_params *bsp)
+{
+ if (bsp->args.ifnamelen)
+ return bsp->args.ifname;
+
+ return NULL;
+}
+
+const char *bfd_sess_vrf(const struct bfd_session_params *bsp)
+{
+ return vrf_id_to_name(bsp->args.vrf_id);
+}
+
+vrf_id_t bfd_sess_vrf_id(const struct bfd_session_params *bsp)
+{
+ return bsp->args.vrf_id;
+}
+
+bool bfd_sess_cbit(const struct bfd_session_params *bsp)
+{
+ return bsp->args.cbit;
+}
+
+void bfd_sess_timers(const struct bfd_session_params *bsp,
+ uint8_t *detection_multiplier, uint32_t *min_rx,
+ uint32_t *min_tx)
+{
+ *detection_multiplier = bsp->args.detection_multiplier;
+ *min_rx = bsp->args.min_rx;
+ *min_tx = bsp->args.min_tx;
+}
+
+void bfd_sess_show(struct vty *vty, struct json_object *json,
+ struct bfd_session_params *bsp)
+{
+ json_object *json_bfd = NULL;
+ char time_buf[64];
+
+ if (!bsp)
+ return;
+
+ /* Show type. */
+ if (json) {
+ json_bfd = json_object_new_object();
+ if (bsp->args.mhop)
+ json_object_string_add(json_bfd, "type", "multi hop");
+ else
+ json_object_string_add(json_bfd, "type", "single hop");
+ } else
+ vty_out(vty, " BFD: Type: %s\n",
+ bsp->args.mhop ? "multi hop" : "single hop");
+
+ /* Show configuration. */
+ if (json) {
+ json_object_int_add(json_bfd, "detectMultiplier",
+ bsp->args.detection_multiplier);
+ json_object_int_add(json_bfd, "rxMinInterval",
+ bsp->args.min_rx);
+ json_object_int_add(json_bfd, "txMinInterval",
+ bsp->args.min_tx);
+ } else {
+ vty_out(vty,
+ " Detect Multiplier: %d, Min Rx interval: %d, Min Tx interval: %d\n",
+ bsp->args.detection_multiplier, bsp->args.min_rx,
+ bsp->args.min_tx);
+ }
+
+ bfd_last_update(bsp->bss.last_event, time_buf, sizeof(time_buf));
+ if (json) {
+ json_object_string_add(json_bfd, "status",
+ bfd_get_status_str(bsp->bss.state));
+ json_object_string_add(json_bfd, "lastUpdate", time_buf);
+ } else
+ vty_out(vty, " Status: %s, Last update: %s\n",
+ bfd_get_status_str(bsp->bss.state), time_buf);
+
+ if (json)
+ json_object_object_add(json, "peerBfdInfo", json_bfd);
+ else
+ vty_out(vty, "\n");
+}
+
+/*
+ * Zebra communication related.
+ */
+
+/**
+ * Callback for reinstallation of all registered BFD sessions.
+ *
+ * Use this as `zclient` `bfd_dest_replay` callback.
+ */
+int zclient_bfd_session_replay(ZAPI_CALLBACK_ARGS)
+{
+ struct bfd_session_params *bsp;
+
+ if (!zclient->bfd_integration)
+ return 0;
+
+ /* Do nothing when shutting down. */
+ if (bsglobal.shutting_down)
+ return 0;
+
+ if (bsglobal.debugging)
+ zlog_debug("%s: sending all sessions registered", __func__);
+
+ /* Send the client registration */
+ bfd_client_sendmsg(zclient, ZEBRA_BFD_CLIENT_REGISTER, vrf_id);
+
+ /* Replay all activated peers. */
+ TAILQ_FOREACH (bsp, &bsglobal.bsplist, entry) {
+ /* Skip not installed sessions. */
+ if (!bsp->installed)
+ continue;
+
+ /* We are reconnecting, so we must send installation. */
+ bsp->installed = false;
+
+ /* Cancel any pending installation request. */
+ THREAD_OFF(bsp->installev);
+
+ /* Ask for installation. */
+ bsp->lastev = BSE_INSTALL;
+ thread_execute(bsglobal.tm, _bfd_sess_send, bsp, 0);
+ }
+
+ return 0;
+}
+
+int zclient_bfd_session_update(ZAPI_CALLBACK_ARGS)
+{
+ struct bfd_session_params *bsp, *bspn;
+ size_t sessions_updated = 0;
+ struct interface *ifp;
+ int remote_cbit = false;
+ int state = BFD_STATUS_UNKNOWN;
+ time_t now;
+ size_t addrlen;
+ struct prefix dp;
+ struct prefix sp;
+ char ifstr[128], cbitstr[32];
+
+ if (!zclient->bfd_integration)
+ return 0;
+
+ /* Do nothing when shutting down. */
+ if (bsglobal.shutting_down)
+ return 0;
+
+ ifp = bfd_get_peer_info(zclient->ibuf, &dp, &sp, &state, &remote_cbit,
+ vrf_id);
+ /*
+ * When interface lookup fails or an invalid stream is read, we must
+ * not proceed otherwise it will trigger an assertion while checking
+ * family type below.
+ */
+ if (dp.family == 0 || sp.family == 0)
+ return 0;
+
+ if (bsglobal.debugging) {
+ ifstr[0] = 0;
+ if (ifp)
+ snprintf(ifstr, sizeof(ifstr), " (interface %s)",
+ ifp->name);
+
+ snprintf(cbitstr, sizeof(cbitstr), " (CPI bit %s)",
+ remote_cbit ? "yes" : "no");
+
+ zlog_debug("%s: %pFX -> %pFX%s VRF %s(%u)%s: %s", __func__, &sp,
+ &dp, ifstr, vrf_id_to_name(vrf_id), vrf_id, cbitstr,
+ bfd_get_status_str(state));
+ }
+
+ switch (dp.family) {
+ case AF_INET:
+ addrlen = sizeof(struct in_addr);
+ break;
+ case AF_INET6:
+ addrlen = sizeof(struct in6_addr);
+ break;
+
+ default:
+ /* Unexpected value. */
+ assert(0);
+ break;
+ }
+
+ /* Cache current time to avoid multiple monotime clock calls. */
+ now = monotime(NULL);
+
+ /* Notify all matching sessions about update. */
+ TAILQ_FOREACH_SAFE (bsp, &bsglobal.bsplist, entry, bspn) {
+ /* Skip not installed entries. */
+ if (!bsp->installed)
+ continue;
+ /* Skip different VRFs. */
+ if (bsp->args.vrf_id != vrf_id)
+ continue;
+ /* Skip different families. */
+ if (bsp->args.family != dp.family)
+ continue;
+ /* Skip different interface. */
+ if (bsp->args.ifnamelen && ifp
+ && strcmp(bsp->args.ifname, ifp->name) != 0)
+ continue;
+ /* Skip non matching destination addresses. */
+ if (memcmp(&bsp->args.dst, &dp.u, addrlen) != 0)
+ continue;
+ /*
+ * Source comparison test:
+ * We will only compare source if BFD daemon provided the
+ * source address and the protocol set a source address in
+ * the configuration otherwise we'll just skip it.
+ */
+ if (sp.family && memcmp(&bsp->args.src, &i6a_zero, addrlen) != 0
+ && memcmp(&sp.u, &i6a_zero, addrlen) != 0
+ && memcmp(&bsp->args.src, &sp.u, addrlen) != 0)
+ continue;
+ /* No session state change. */
+ if ((int)bsp->bss.state == state)
+ continue;
+
+ bsp->bss.last_event = now;
+ bsp->bss.previous_state = bsp->bss.state;
+ bsp->bss.state = state;
+ bsp->bss.remote_cbit = remote_cbit;
+ bsp->updatecb(bsp, &bsp->bss, bsp->arg);
+ sessions_updated++;
+ }
+
+ if (bsglobal.debugging)
+ zlog_debug("%s: sessions updated: %zu", __func__,
+ sessions_updated);
+
+ return 0;
+}
+
+void bfd_protocol_integration_init(struct zclient *zc, struct thread_master *tm)
+{
+ /* Initialize data structure. */
+ TAILQ_INIT(&bsglobal.bsplist);
+
+ /* Copy pointers. */
+ bsglobal.zc = zc;
+ bsglobal.tm = tm;
+
+ /* Enable BFD callbacks. */
+ zc->bfd_integration = true;
+
+ /* Send the client registration */
+ bfd_client_sendmsg(zc, ZEBRA_BFD_CLIENT_REGISTER, VRF_DEFAULT);
+}
+
+void bfd_protocol_integration_set_debug(bool enable)
+{
+ bsglobal.debugging = enable;
+}
+
+void bfd_protocol_integration_set_shutdown(bool enable)
+{
+ bsglobal.shutting_down = enable;
+}
+
+bool bfd_protocol_integration_debug(void)
+{
+ return bsglobal.debugging;
+}
+
+bool bfd_protocol_integration_shutting_down(void)
+{
+ return bsglobal.shutting_down;
+}
diff --git a/lib/bfd.h b/lib/bfd.h
new file mode 100644
index 0000000..344ecc2
--- /dev/null
+++ b/lib/bfd.h
@@ -0,0 +1,463 @@
+/**
+ * bfd.h: BFD definitions and structures
+ *
+ * @copyright Copyright (C) 2015 Cumulus Networks, Inc.
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 _ZEBRA_BFD_H
+#define _ZEBRA_BFD_H
+
+#include "lib/json.h"
+#include "lib/zclient.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define BFD_DEF_MIN_RX 300
+#define BFD_MIN_MIN_RX 50
+#define BFD_MAX_MIN_RX 60000
+#define BFD_DEF_MIN_TX 300
+#define BFD_MIN_MIN_TX 50
+#define BFD_MAX_MIN_TX 60000
+#define BFD_DEF_DETECT_MULT 3
+#define BFD_MIN_DETECT_MULT 2
+#define BFD_MAX_DETECT_MULT 255
+
+#define BFD_STATUS_UNKNOWN (1 << 0) /* BFD session status never received */
+#define BFD_STATUS_DOWN (1 << 1) /* BFD session status is down */
+#define BFD_STATUS_UP (1 << 2) /* BFD session status is up */
+#define BFD_STATUS_ADMIN_DOWN (1 << 3) /* BFD session is admin down */
+
+#define BFD_PROFILE_NAME_LEN 64
+
+const char *bfd_get_status_str(int status);
+
+extern void bfd_client_sendmsg(struct zclient *zclient, int command,
+ vrf_id_t vrf_id);
+
+/*
+ * BFD new API.
+ */
+
+/* Forward declaration of argument struct. */
+struct bfd_session_params;
+
+/** Session state definitions. */
+enum bfd_session_state {
+ /** Session state is unknown or not initialized. */
+ BSS_UNKNOWN = BFD_STATUS_UNKNOWN,
+ /** Local or remote peer administratively shutdown the session. */
+ BSS_ADMIN_DOWN = BFD_STATUS_ADMIN_DOWN,
+ /** Session is down. */
+ BSS_DOWN = BFD_STATUS_DOWN,
+ /** Session is up and working correctly. */
+ BSS_UP = BFD_STATUS_UP,
+};
+
+/** BFD session status information */
+struct bfd_session_status {
+ /** Current session state. */
+ enum bfd_session_state state;
+ /** Previous session state. */
+ enum bfd_session_state previous_state;
+ /** Remote Control Plane Independent bit state. */
+ bool remote_cbit;
+ /** Last event occurrence. */
+ time_t last_event;
+};
+
+/**
+ * Session status update callback.
+ *
+ * \param bsp BFD session parameters.
+ * \param bss BFD session status.
+ * \param arg application independent data.
+ */
+typedef void (*bsp_status_update)(struct bfd_session_params *bsp,
+ const struct bfd_session_status *bss,
+ void *arg);
+
+/**
+ * Allocates and initializes the session parameters.
+ *
+ * \param updatecb status update notification callback.
+ * \param args application independent data.
+ *
+ * \returns pointer to configuration storage.
+ */
+struct bfd_session_params *bfd_sess_new(bsp_status_update updatecb, void *args);
+
+/**
+ * Uninstall session if installed and free resources allocated by the
+ * parameters. Already sets pointer to `NULL` to avoid dangling references.
+ *
+ * \param bsp session parameters.
+ */
+void bfd_sess_free(struct bfd_session_params **bsp);
+
+/**
+ * Set the local and peer address of the BFD session.
+ *
+ * NOTE:
+ * If the address changed the session is removed and must be installed again
+ * with `bfd_sess_install`.
+ *
+ * \param bsp BFD session parameters.
+ * \param src local address (optional, can be `NULL`).
+ * \param dst remote address (mandatory).
+ */
+void bfd_sess_set_ipv4_addrs(struct bfd_session_params *bsp,
+ const struct in_addr *src,
+ const struct in_addr *dst);
+
+/**
+ * Set the local and peer address of the BFD session.
+ *
+ * NOTE:
+ * If the address changed the session is removed and must be installed again
+ * with `bfd_sess_install`.
+ *
+ * \param bsp BFD session parameters.
+ * \param src local address (optional, can be `NULL`).
+ * \param dst remote address (mandatory).
+ */
+void bfd_sess_set_ipv6_addrs(struct bfd_session_params *bsp,
+ const struct in6_addr *src,
+ const struct in6_addr *dst);
+
+/**
+ * Configure the BFD session interface.
+ *
+ * NOTE:
+ * If the interface changed the session is removed and must be installed again
+ * with `bfd_sess_install`.
+ *
+ * \param bsp BFD session parameters.
+ * \param ifname interface name (or `NULL` to remove it).
+ */
+void bfd_sess_set_interface(struct bfd_session_params *bsp, const char *ifname);
+
+/**
+ * Configure the BFD session profile name.
+ *
+ * NOTE:
+ * Session profile will only change after a `bfd_sess_install`.
+ *
+ * \param bsp BFD session parameters.
+ * \param profile profile name (or `NULL` to remove it).
+ */
+void bfd_sess_set_profile(struct bfd_session_params *bsp, const char *profile);
+
+/**
+ * Configure the BFD session VRF.
+ *
+ * NOTE:
+ * If the VRF changed the session is removed and must be installed again
+ * with `bfd_sess_install`.
+ *
+ * \param bsp BFD session parameters.
+ * \param vrf_id the VRF identification number.
+ */
+void bfd_sess_set_vrf(struct bfd_session_params *bsp, vrf_id_t vrf_id);
+
+/**
+ * Configure the BFD session single/multi hop setting.
+ *
+ * NOTE:
+ * If the number of hops is changed the session is removed and must be
+ * installed again with `bfd_sess_install`.
+ *
+ * \param bsp BFD session parameters.
+ * \param hops maximum amount of hops expected (1 for single hop, 2 or
+ * more for multi hop).
+ */
+void bfd_sess_set_hop_count(struct bfd_session_params *bsp, uint8_t hops);
+
+/**
+ * Configure the BFD session to set the Control Plane Independent bit.
+ *
+ * NOTE:
+ * Session CPI bit will only change after a `bfd_sess_install`.
+ *
+ * \param bsp BFD session parameters.
+ * \param enable BFD Control Plane Independent state.
+ */
+void bfd_sess_set_cbit(struct bfd_session_params *bsp, bool enable);
+
+/**
+ * DEPRECATED: please avoid using timers directly and use profiles instead.
+ *
+ * Configures the BFD session timers to use. This is specially useful with
+ * `ptm-bfd` which does not support timers.
+ *
+ * NOTE:
+ * Session timers will only apply if the session has not been created yet.
+ * If the session is already installed you must uninstall and install again
+ * to take effect.
+ *
+ * \param bsp BFD session parameters.
+ * \param detection_multiplier the detection multiplier value.
+ * \param min_rx minimum required receive period.
+ * \param min_tx minimum required transmission period.
+ */
+void bfd_sess_set_timers(struct bfd_session_params *bsp,
+ uint8_t detection_multiplier, uint32_t min_rx,
+ uint32_t min_tx);
+
+/**
+ * Installs or updates the BFD session based on the saved session arguments.
+ *
+ * NOTE:
+ * This function has a delayed effect: it will only install/update after
+ * all northbound/CLI command batch finishes.
+ *
+ * \param bsp session parameters.
+ */
+void bfd_sess_install(struct bfd_session_params *bsp);
+
+/**
+ * Uninstall the BFD session based on the saved session arguments.
+ *
+ * NOTE:
+ * This function uninstalls the session immediately (if installed) and cancels
+ * any previous `bfd_sess_install` calls.
+ *
+ * \param bsp session parameters.
+ */
+void bfd_sess_uninstall(struct bfd_session_params *bsp);
+
+/**
+ * Get BFD session current status.
+ *
+ * \param bsp session parameters.
+ *
+ * \returns BFD session status data structure.
+ */
+enum bfd_session_state bfd_sess_status(const struct bfd_session_params *bsp);
+
+/**
+ * Get BFD session amount of hops configured value.
+ *
+ * \param bsp session parameters.
+ *
+ * \returns configured amount of hops.
+ */
+uint8_t bfd_sess_hop_count(const struct bfd_session_params *bsp);
+
+/**
+ * Get BFD session profile configured value.
+ *
+ * \param bsp session parameters.
+ *
+ * \returns configured profile name (or `NULL` if empty).
+ */
+const char *bfd_sess_profile(const struct bfd_session_params *bsp);
+
+/**
+ * Get BFD session addresses.
+ *
+ * \param bsp session parameters.
+ * \param family the address family being used (AF_INET or AF_INET6).
+ * \param src source address (optional, may be `NULL`).
+ * \param dst peer address (optional, may be `NULL`).
+ */
+void bfd_sess_addresses(const struct bfd_session_params *bsp, int *family,
+ struct in6_addr *src, struct in6_addr *dst);
+/**
+ * Get BFD session interface name.
+ *
+ * \param bsp session parameters.
+ *
+ * \returns `NULL` if not set otherwise the interface name.
+ */
+const char *bfd_sess_interface(const struct bfd_session_params *bsp);
+
+/**
+ * Get BFD session VRF name.
+ *
+ * \param bsp session parameters.
+ *
+ * \returns the VRF name.
+ */
+const char *bfd_sess_vrf(const struct bfd_session_params *bsp);
+
+/**
+ * Get BFD session VRF ID.
+ *
+ * \param bsp session parameters.
+ *
+ * \returns the VRF name.
+ */
+vrf_id_t bfd_sess_vrf_id(const struct bfd_session_params *bsp);
+
+/**
+ * Get BFD session control plane independent bit configuration state.
+ *
+ * \param bsp session parameters.
+ *
+ * \returns `true` if enabled otherwise `false`.
+ */
+bool bfd_sess_cbit(const struct bfd_session_params *bsp);
+
+/**
+ * DEPRECATED: please avoid using timers directly and use profiles instead.
+ *
+ * Gets the configured timers.
+ *
+ * \param bsp BFD session parameters.
+ * \param detection_multiplier the detection multiplier value.
+ * \param min_rx minimum required receive period.
+ * \param min_tx minimum required transmission period.
+ */
+void bfd_sess_timers(const struct bfd_session_params *bsp,
+ uint8_t *detection_multiplier, uint32_t *min_rx,
+ uint32_t *min_tx);
+
+/**
+ * Show BFD session configuration and status. If `json` is provided (e.g. not
+ * `NULL`) then information will be inserted in object, otherwise printed to
+ * `vty`.
+ *
+ * \param vty Pointer to `vty` for outputting text.
+ * \param json (optional) JSON object pointer.
+ * \param bsp session parameters.
+ */
+void bfd_sess_show(struct vty *vty, struct json_object *json,
+ struct bfd_session_params *bsp);
+
+/**
+ * Initializes the BFD integration library. This function executes the
+ * following actions:
+ *
+ * - Copy the `struct thread_master` pointer to use as "thread" to execute
+ * the BFD session parameters installation.
+ * - Copy the `struct zclient` pointer to install its callbacks.
+ * - Initializes internal data structures.
+ *
+ * \param tm normally the daemon main thread event manager.
+ * \param zc the zebra client of the daemon.
+ */
+void bfd_protocol_integration_init(struct zclient *zc,
+ struct thread_master *tm);
+
+/**
+ * BFD session registration arguments.
+ */
+struct bfd_session_arg {
+ /**
+ * BFD command.
+ *
+ * Valid commands:
+ * - `ZEBRA_BFD_DEST_REGISTER`
+ * - `ZEBRA_BFD_DEST_DEREGISTER`
+ */
+ int32_t command;
+
+ /**
+ * BFD family type.
+ *
+ * Supported types:
+ * - `AF_INET`
+ * - `AF_INET6`.
+ */
+ uint32_t family;
+ /** Source address. */
+ struct in6_addr src;
+ /** Source address. */
+ struct in6_addr dst;
+
+ /** Multi hop indicator. */
+ uint8_t mhop;
+ /** Expected hops. */
+ uint8_t hops;
+ /** C bit (Control Plane Independent bit) indicator. */
+ uint8_t cbit;
+
+ /** Interface name size. */
+ uint8_t ifnamelen;
+ /** Interface name. */
+ char ifname[64];
+
+ /** Daemon or session VRF. */
+ vrf_id_t vrf_id;
+
+ /** Profile name length. */
+ uint8_t profilelen;
+ /** Profile name. */
+ char profile[BFD_PROFILE_NAME_LEN];
+
+ /*
+ * Deprecation fields: these fields should be removed once `ptm-bfd`
+ * no longer uses this interface.
+ */
+
+ /** Minimum required receive interval (in microseconds). */
+ uint32_t min_rx;
+ /** Minimum desired transmission interval (in microseconds). */
+ uint32_t min_tx;
+ /** Detection multiplier. */
+ uint32_t detection_multiplier;
+};
+
+/**
+ * Send a message to BFD daemon through the zebra client.
+ *
+ * \param zc the zebra client context.
+ * \param arg the BFD session command arguments.
+ *
+ * \returns `-1` on failure otherwise `0`.
+ *
+ * \see bfd_session_arg.
+ */
+extern int zclient_bfd_command(struct zclient *zc, struct bfd_session_arg *arg);
+
+/**
+ * Enables or disables BFD protocol integration API debugging.
+ *
+ * \param enable new API debug state.
+ */
+extern void bfd_protocol_integration_set_debug(bool enable);
+
+/**
+ * Sets shutdown mode so no more events are processed.
+ *
+ * This is useful to avoid the event storm that happens caused by network,
+ * interfaces or VRFs removal. It should also avoid some crashes due hanging
+ * pointers left overs by protocol.
+ *
+ * \param enable new API shutdown state.
+ */
+extern void bfd_protocol_integration_set_shutdown(bool enable);
+
+/**
+ * Get API debugging state.
+ */
+extern bool bfd_protocol_integration_debug(void);
+
+/**
+ * Get API shutdown state.
+ */
+extern bool bfd_protocol_integration_shutting_down(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ZEBRA_BFD_H */
diff --git a/lib/bitfield.h b/lib/bitfield.h
new file mode 100644
index 0000000..9af4053
--- /dev/null
+++ b/lib/bitfield.h
@@ -0,0 +1,275 @@
+/* Bitfields
+ * Copyright (C) 2016 Cumulus Networks, Inc.
+ *
+ * This file is part of Quagga.
+ *
+ * Quagga 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, or (at your option) any
+ * later version.
+ *
+ * Quagga 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
+ */
+/**
+ * A simple bit array implementation to allocate and free IDs. An example
+ * of its usage is in allocating link state IDs for OSPFv3 as OSPFv3 has
+ * removed all address semantics from LS ID. Another usage can be in
+ * allocating IDs for BGP neighbors (and dynamic update groups) for
+ * efficient storage of adj-rib-out.
+ *
+ * An example:
+ * #include "bitfield.h"
+ *
+ * bitfield_t bitfield;
+ *
+ * bf_init(bitfield, 32);
+ * ...
+ * bf_assign_index(bitfield, id1);
+ * bf_assign_index(bitfield, id2);
+ * ...
+ * bf_release_index(bitfield, id1);
+ */
+
+#ifndef _BITFIELD_H
+#define _BITFIELD_H
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef unsigned int word_t;
+#define WORD_MAX 0xFFFFFFFF
+#define WORD_SIZE (sizeof(word_t) * 8)
+
+/**
+ * The bitfield structure.
+ * @data: the bits to manage.
+ * @n: The current word number that is being used.
+ * @m: total number of words in 'data'
+ */
+typedef struct {word_t *data; size_t n, m; } bitfield_t;
+
+DECLARE_MTYPE(BITFIELD);
+
+/**
+ * Initialize the bits.
+ * @v: an instance of bitfield_t struct.
+ * @N: number of bits to start with, which equates to how many
+ * IDs can be allocated.
+ */
+#define bf_init(v, N) \
+ do { \
+ (v).n = 0; \
+ (v).m = ((N) / WORD_SIZE + 1); \
+ (v).data = XCALLOC(MTYPE_BITFIELD, ((v).m * sizeof(word_t))); \
+ } while (0)
+
+/**
+ * allocate and assign an id from bitfield v.
+ */
+#define bf_assign_index(v, id) \
+ do { \
+ bf_find_bit(v, id); \
+ bf_set_bit(v, id); \
+ } while (0)
+
+/*
+ * allocate and assign 0th bit in the bitfiled.
+ */
+#define bf_assign_zero_index(v) \
+ do { \
+ int id = 0; \
+ bf_assign_index(v, id); \
+ } while (0)
+
+/*
+ * return an id to bitfield v
+ */
+#define bf_release_index(v, id) \
+ (v).data[bf_index(id)] &= ~(1 << (bf_offset(id)))
+
+/* check if an id is in use */
+#define bf_test_index(v, id) \
+ ((v).data[bf_index(id)] & (1 << (bf_offset(id))))
+
+/* check if the bit field has been setup */
+#define bf_is_inited(v) ((v).data)
+
+/* compare two bitmaps of the same length */
+#define bf_cmp(v1, v2) (memcmp((v1).data, (v2).data, ((v1).m * sizeof(word_t))))
+
+/*
+ * return 0th index back to bitfield
+ */
+#define bf_release_zero_index(v) bf_release_index(v, 0)
+
+#define bf_index(b) ((b) / WORD_SIZE)
+#define bf_offset(b) ((b) % WORD_SIZE)
+
+/**
+ * Set a bit in the array. If it fills up that word and we are
+ * out of words, extend it by one more word.
+ */
+#define bf_set_bit(v, b) \
+ do { \
+ size_t w = bf_index(b); \
+ (v).data[w] |= 1 << (bf_offset(b)); \
+ (v).n += ((v).data[w] == WORD_MAX); \
+ if ((v).n == (v).m) { \
+ (v).m = (v).m + 1; \
+ (v).data = realloc((v).data, (v).m * sizeof(word_t)); \
+ } \
+ } while (0)
+
+/* Find a clear bit in v and assign it to b. */
+#define bf_find_bit(v, b) \
+ do { \
+ word_t word = 0; \
+ unsigned int w, sh; \
+ for (w = 0; w <= (v).n; w++) { \
+ if ((word = (v).data[w]) != WORD_MAX) \
+ break; \
+ } \
+ (b) = ((word & 0xFFFF) == 0xFFFF) << 4; \
+ word >>= (b); \
+ sh = ((word & 0xFF) == 0xFF) << 3; \
+ word >>= sh; \
+ (b) |= sh; \
+ sh = ((word & 0xF) == 0xF) << 2; \
+ word >>= sh; \
+ (b) |= sh; \
+ sh = ((word & 0x3) == 0x3) << 1; \
+ word >>= sh; \
+ (b) |= sh; \
+ sh = ((word & 0x1) == 0x1) << 0; \
+ word >>= sh; \
+ (b) |= sh; \
+ (b) += (w * WORD_SIZE); \
+ } while (0)
+
+/*
+ * Find a clear bit in v and return it
+ * Start looking in the word containing bit position start_index.
+ * If necessary, wrap around after bit position max_index.
+ */
+static inline unsigned int
+bf_find_next_clear_bit_wrap(bitfield_t *v, word_t start_index, word_t max_index)
+{
+ int start_bit;
+ unsigned long i, offset, scanbits, wordcount_max, index_max;
+
+ if (start_index > max_index)
+ start_index = 0;
+
+ start_bit = start_index & (WORD_SIZE - 1);
+ wordcount_max = bf_index(max_index) + 1;
+
+ scanbits = WORD_SIZE;
+ for (i = bf_index(start_index); i < v->m; ++i) {
+ if (v->data[i] == WORD_MAX) {
+ /* if the whole word is full move to the next */
+ start_bit = 0;
+ continue;
+ }
+ /* scan one word for clear bits */
+ if ((i == v->m - 1) && (v->m >= wordcount_max))
+ /* max index could be only part of word */
+ scanbits = (max_index % WORD_SIZE) + 1;
+ for (offset = start_bit; offset < scanbits; ++offset) {
+ if (!((v->data[i] >> offset) & 1))
+ return ((i * WORD_SIZE) + offset);
+ }
+ /* move to the next word */
+ start_bit = 0;
+ }
+
+ if (v->m < wordcount_max) {
+ /*
+ * We can expand bitfield, so no need to wrap.
+ * Return the index of the first bit of the next word.
+ * Assumption is that caller will call bf_set_bit which
+ * will allocate additional space.
+ */
+ v->m += 1;
+ v->data = (word_t *)realloc(v->data, v->m * sizeof(word_t));
+ v->data[v->m - 1] = 0;
+ return v->m * WORD_SIZE;
+ }
+
+ /*
+ * start looking for a clear bit at the start of the bitfield and
+ * stop when we reach start_index
+ */
+ scanbits = WORD_SIZE;
+ index_max = bf_index(start_index - 1);
+ for (i = 0; i <= index_max; ++i) {
+ if (i == index_max)
+ scanbits = ((start_index - 1) % WORD_SIZE) + 1;
+ for (offset = start_bit; offset < scanbits; ++offset) {
+ if (!((v->data[i] >> offset) & 1))
+ return ((i * WORD_SIZE) + offset);
+ }
+ /* move to the next word */
+ start_bit = 0;
+ }
+
+ return WORD_MAX;
+}
+
+static inline unsigned int bf_find_next_set_bit(bitfield_t v,
+ word_t start_index)
+{
+ int start_bit;
+ unsigned long i, offset;
+
+ start_bit = start_index & (WORD_SIZE - 1);
+
+ for (i = bf_index(start_index); i < v.m; ++i) {
+ if (v.data[i] == 0) {
+ /* if the whole word is empty move to the next */
+ start_bit = 0;
+ continue;
+ }
+ /* scan one word for set bits */
+ for (offset = start_bit; offset < WORD_SIZE; ++offset) {
+ if ((v.data[i] >> offset) & 1)
+ return ((i * WORD_SIZE) + offset);
+ }
+ /* move to the next word */
+ start_bit = 0;
+ }
+ return WORD_MAX;
+}
+
+/* iterate through all the set bits */
+#define bf_for_each_set_bit(v, b, max) \
+ for ((b) = bf_find_next_set_bit((v), 0); \
+ (b) < max; \
+ (b) = bf_find_next_set_bit((v), (b) + 1))
+
+/*
+ * Free the allocated memory for data
+ * @v: an instance of bitfield_t struct.
+ */
+#define bf_free(v) \
+ do { \
+ XFREE(MTYPE_BITFIELD, (v).data); \
+ (v).data = NULL; \
+ } while (0)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/buffer.c b/lib/buffer.c
new file mode 100644
index 0000000..e976fec
--- /dev/null
+++ b/lib/buffer.c
@@ -0,0 +1,498 @@
+/*
+ * Buffering of output and input.
+ * Copyright (C) 1998 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra 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 "memory.h"
+#include "buffer.h"
+#include "log.h"
+#include "network.h"
+#include "lib_errors.h"
+
+#include <stddef.h>
+
+DEFINE_MTYPE_STATIC(LIB, BUFFER, "Buffer");
+DEFINE_MTYPE_STATIC(LIB, BUFFER_DATA, "Buffer data");
+
+/* Buffer master. */
+struct buffer {
+ /* Data list. */
+ struct buffer_data *head;
+ struct buffer_data *tail;
+
+ /* Size of each buffer_data chunk. */
+ size_t size;
+};
+
+/* Data container. */
+struct buffer_data {
+ struct buffer_data *next;
+
+ /* Location to add new data. */
+ size_t cp;
+
+ /* Pointer to data not yet flushed. */
+ size_t sp;
+
+ /* Actual data stream (variable length). */
+ unsigned char data[]; /* real dimension is buffer->size */
+};
+
+/* It should always be true that: 0 <= sp <= cp <= size */
+
+/* Default buffer size (used if none specified). It is rounded up to the
+ next page boundary. */
+#define BUFFER_SIZE_DEFAULT 4096
+
+#define BUFFER_DATA_FREE(D) XFREE(MTYPE_BUFFER_DATA, (D))
+
+/* Make new buffer. */
+struct buffer *buffer_new(size_t size)
+{
+ struct buffer *b;
+
+ b = XCALLOC(MTYPE_BUFFER, sizeof(struct buffer));
+
+ if (size)
+ b->size = size;
+ else {
+ static size_t default_size;
+ if (!default_size) {
+ long pgsz = sysconf(_SC_PAGESIZE);
+ default_size = ((((BUFFER_SIZE_DEFAULT - 1) / pgsz) + 1)
+ * pgsz);
+ }
+ b->size = default_size;
+ }
+
+ return b;
+}
+
+/* Free buffer. */
+void buffer_free(struct buffer *b)
+{
+ buffer_reset(b);
+ XFREE(MTYPE_BUFFER, b);
+}
+
+/* Make string clone. */
+char *buffer_getstr(struct buffer *b)
+{
+ size_t totlen = 0;
+ struct buffer_data *data;
+ char *s;
+ char *p;
+
+ for (data = b->head; data; data = data->next)
+ totlen += data->cp - data->sp;
+ if (!(s = XMALLOC(MTYPE_TMP, totlen + 1)))
+ return NULL;
+ p = s;
+ for (data = b->head; data; data = data->next) {
+ memcpy(p, data->data + data->sp, data->cp - data->sp);
+ p += data->cp - data->sp;
+ }
+ *p = '\0';
+ return s;
+}
+
+/* Clear and free all allocated data. */
+void buffer_reset(struct buffer *b)
+{
+ struct buffer_data *data;
+ struct buffer_data *next;
+
+ for (data = b->head; data; data = next) {
+ next = data->next;
+ BUFFER_DATA_FREE(data);
+ }
+ b->head = b->tail = NULL;
+}
+
+/* Add buffer_data to the end of buffer. */
+static struct buffer_data *buffer_add(struct buffer *b)
+{
+ struct buffer_data *d;
+
+ d = XMALLOC(MTYPE_BUFFER_DATA,
+ offsetof(struct buffer_data, data) + b->size);
+ d->cp = d->sp = 0;
+ d->next = NULL;
+
+ if (b->tail)
+ b->tail->next = d;
+ else
+ b->head = d;
+ b->tail = d;
+
+ return d;
+}
+
+/* Write data to buffer. */
+void buffer_put(struct buffer *b, const void *p, size_t size)
+{
+ struct buffer_data *data = b->tail;
+ const char *ptr = p;
+
+ /* We use even last one byte of data buffer. */
+ while (size) {
+ size_t chunk;
+
+ /* If there is no data buffer add it. */
+ if (data == NULL || data->cp == b->size)
+ data = buffer_add(b);
+
+ chunk = ((size <= (b->size - data->cp)) ? size
+ : (b->size - data->cp));
+ memcpy((data->data + data->cp), ptr, chunk);
+ size -= chunk;
+ ptr += chunk;
+ data->cp += chunk;
+ }
+}
+
+/* Insert character into the buffer. */
+void buffer_putc(struct buffer *b, uint8_t c)
+{
+ buffer_put(b, &c, 1);
+}
+
+/* Put string to the buffer. */
+void buffer_putstr(struct buffer *b, const char *c)
+{
+ buffer_put(b, c, strlen(c));
+}
+
+/* Expand \n to \r\n */
+void buffer_put_crlf(struct buffer *b, const void *origp, size_t origsize)
+{
+ struct buffer_data *data = b->tail;
+ const char *p = origp, *end = p + origsize, *lf;
+ size_t size;
+
+ lf = memchr(p, '\n', end - p);
+
+ /* We use even last one byte of data buffer. */
+ while (p < end) {
+ size_t avail, chunk;
+
+ /* If there is no data buffer add it. */
+ if (data == NULL || data->cp == b->size)
+ data = buffer_add(b);
+
+ size = (lf ? lf : end) - p;
+ avail = b->size - data->cp;
+
+ chunk = (size <= avail) ? size : avail;
+ memcpy(data->data + data->cp, p, chunk);
+
+ p += chunk;
+ data->cp += chunk;
+
+ if (lf && size <= avail) {
+ /* we just copied up to (including) a '\n' */
+ if (data->cp == b->size)
+ data = buffer_add(b);
+ data->data[data->cp++] = '\r';
+ if (data->cp == b->size)
+ data = buffer_add(b);
+ data->data[data->cp++] = '\n';
+
+ p++;
+ lf = memchr(p, '\n', end - p);
+ }
+ }
+}
+
+/* Keep flushing data to the fd until the buffer is empty or an error is
+ encountered or the operation would block. */
+buffer_status_t buffer_flush_all(struct buffer *b, int fd)
+{
+ buffer_status_t ret;
+ struct buffer_data *head;
+ size_t head_sp;
+
+ if (!b->head)
+ return BUFFER_EMPTY;
+ head_sp = (head = b->head)->sp;
+ /* Flush all data. */
+ while ((ret = buffer_flush_available(b, fd)) == BUFFER_PENDING) {
+ if ((b->head == head) && (head_sp == head->sp)
+ && (errno != EINTR))
+ /* No data was flushed, so kernel buffer must be full.
+ */
+ return ret;
+ head_sp = (head = b->head)->sp;
+ }
+
+ return ret;
+}
+
+/* Flush enough data to fill a terminal window of the given scene (used only
+ by vty telnet interface). */
+buffer_status_t buffer_flush_window(struct buffer *b, int fd, int width,
+ int height, int erase_flag,
+ int no_more_flag)
+{
+ int nbytes;
+ int iov_alloc;
+ int iov_index;
+ struct iovec *iov;
+ struct iovec small_iov[3];
+ char more[] = " --More-- ";
+ char erase[] = {0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, ' ', ' ', ' ', ' ', ' ', ' ',
+ ' ', ' ', ' ', ' ', 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08};
+ struct buffer_data *data;
+ int column;
+
+ if (!b->head)
+ return BUFFER_EMPTY;
+
+ if (height < 1)
+ height = 1;
+ else if (height >= 2)
+ height--;
+ if (width < 1)
+ width = 1;
+
+ /* For erase and more data add two to b's buffer_data count.*/
+ if (b->head->next == NULL) {
+ iov_alloc = array_size(small_iov);
+ iov = small_iov;
+ } else {
+ iov_alloc = ((height * (width + 2)) / b->size) + 10;
+ iov = XMALLOC(MTYPE_TMP, iov_alloc * sizeof(*iov));
+ }
+ iov_index = 0;
+
+ /* Previously print out is performed. */
+ if (erase_flag) {
+ iov[iov_index].iov_base = erase;
+ iov[iov_index].iov_len = sizeof(erase);
+ iov_index++;
+ }
+
+ /* Output data. */
+ column = 1; /* Column position of next character displayed. */
+ for (data = b->head; data && (height > 0); data = data->next) {
+ size_t cp;
+
+ cp = data->sp;
+ while ((cp < data->cp) && (height > 0)) {
+ /* Calculate lines remaining and column position after
+ displaying
+ this character. */
+ if (data->data[cp] == '\r')
+ column = 1;
+ else if ((data->data[cp] == '\n')
+ || (column == width)) {
+ column = 1;
+ height--;
+ } else
+ column++;
+ cp++;
+ }
+ iov[iov_index].iov_base = (char *)(data->data + data->sp);
+ iov[iov_index++].iov_len = cp - data->sp;
+ data->sp = cp;
+
+ if (iov_index == iov_alloc)
+ /* This should not ordinarily happen. */
+ {
+ iov_alloc *= 2;
+ if (iov != small_iov) {
+ iov = XREALLOC(MTYPE_TMP, iov,
+ iov_alloc * sizeof(*iov));
+ } else {
+ /* This should absolutely never occur. */
+ flog_err_sys(
+ EC_LIB_SYSTEM_CALL,
+ "%s: corruption detected: iov_small overflowed; head %p, tail %p, head->next %p",
+ __func__, (void *)b->head,
+ (void *)b->tail, (void *)b->head->next);
+ iov = XMALLOC(MTYPE_TMP,
+ iov_alloc * sizeof(*iov));
+ memcpy(iov, small_iov, sizeof(small_iov));
+ }
+ }
+ }
+
+ /* In case of `more' display need. */
+ if (b->tail && (b->tail->sp < b->tail->cp) && !no_more_flag) {
+ iov[iov_index].iov_base = more;
+ iov[iov_index].iov_len = sizeof(more);
+ iov_index++;
+ }
+
+
+#ifdef IOV_MAX
+ /* IOV_MAX are normally defined in <sys/uio.h> , Posix.1g.
+ example: Solaris2.6 are defined IOV_MAX size at 16. */
+ {
+ struct iovec *c_iov = iov;
+ nbytes = 0; /* Make sure it's initialized. */
+
+ while (iov_index > 0) {
+ int iov_size;
+
+ iov_size =
+ ((iov_index > IOV_MAX) ? IOV_MAX : iov_index);
+ nbytes = writev(fd, c_iov, iov_size);
+ if (nbytes < 0) {
+ flog_err(EC_LIB_SOCKET,
+ "%s: writev to fd %d failed: %s",
+ __func__, fd, safe_strerror(errno));
+ break;
+ }
+
+ /* move pointer io-vector */
+ c_iov += iov_size;
+ iov_index -= iov_size;
+ }
+ }
+#else /* IOV_MAX */
+ nbytes = writev(fd, iov, iov_index);
+ if (nbytes < 0)
+ flog_err(EC_LIB_SOCKET, "%s: writev to fd %d failed: %s",
+ __func__, fd, safe_strerror(errno));
+#endif /* IOV_MAX */
+
+ /* Free printed buffer data. */
+ while (b->head && (b->head->sp == b->head->cp)) {
+ struct buffer_data *del;
+ if (!(b->head = (del = b->head)->next))
+ b->tail = NULL;
+ BUFFER_DATA_FREE(del);
+ }
+
+ if (iov != small_iov)
+ XFREE(MTYPE_TMP, iov);
+
+ return (nbytes < 0) ? BUFFER_ERROR
+ : (b->head ? BUFFER_PENDING : BUFFER_EMPTY);
+}
+
+/* This function (unlike other buffer_flush* functions above) is designed
+to work with non-blocking sockets. It does not attempt to write out
+all of the queued data, just a "big" chunk. It returns 0 if it was
+able to empty out the buffers completely, 1 if more flushing is
+required later, or -1 on a fatal write error. */
+buffer_status_t buffer_flush_available(struct buffer *b, int fd)
+{
+
+/* These are just reasonable values to make sure a significant amount of
+data is written. There's no need to go crazy and try to write it all
+in one shot. */
+#ifdef IOV_MAX
+#define MAX_CHUNKS ((IOV_MAX >= 16) ? 16 : IOV_MAX)
+#else
+#define MAX_CHUNKS 16
+#endif
+#define MAX_FLUSH 131072
+
+ struct buffer_data *d;
+ size_t written;
+ struct iovec iov[MAX_CHUNKS];
+ size_t iovcnt = 0;
+ size_t nbyte = 0;
+
+ if (fd < 0)
+ return BUFFER_ERROR;
+
+ for (d = b->head; d && (iovcnt < MAX_CHUNKS) && (nbyte < MAX_FLUSH);
+ d = d->next, iovcnt++) {
+ iov[iovcnt].iov_base = d->data + d->sp;
+ nbyte += (iov[iovcnt].iov_len = d->cp - d->sp);
+ }
+
+ if (!nbyte)
+ /* No data to flush: should we issue a warning message? */
+ return BUFFER_EMPTY;
+
+ /* only place where written should be sign compared */
+ if ((ssize_t)(written = writev(fd, iov, iovcnt)) < 0) {
+ if (ERRNO_IO_RETRY(errno))
+ /* Calling code should try again later. */
+ return BUFFER_PENDING;
+ flog_err(EC_LIB_SOCKET, "%s: write error on fd %d: %s",
+ __func__, fd, safe_strerror(errno));
+ return BUFFER_ERROR;
+ }
+
+ /* Free printed buffer data. */
+ while (written > 0) {
+ if (!(d = b->head)) {
+ flog_err(
+ EC_LIB_DEVELOPMENT,
+ "%s: corruption detected: buffer queue empty, but written is %lu",
+ __func__, (unsigned long)written);
+ break;
+ }
+ if (written < d->cp - d->sp) {
+ d->sp += written;
+ return BUFFER_PENDING;
+ }
+
+ written -= (d->cp - d->sp);
+ if (!(b->head = d->next))
+ b->tail = NULL;
+ BUFFER_DATA_FREE(d);
+ }
+
+ return b->head ? BUFFER_PENDING : BUFFER_EMPTY;
+
+#undef MAX_CHUNKS
+#undef MAX_FLUSH
+}
+
+buffer_status_t buffer_write(struct buffer *b, int fd, const void *p,
+ size_t size)
+{
+ ssize_t nbytes;
+
+ if (b->head)
+ /* Buffer is not empty, so do not attempt to write the new data.
+ */
+ nbytes = 0;
+ else {
+ nbytes = write(fd, p, size);
+ if (nbytes < 0) {
+ if (ERRNO_IO_RETRY(errno))
+ nbytes = 0;
+ else {
+ flog_err(EC_LIB_SOCKET,
+ "%s: write error on fd %d: %s",
+ __func__, fd, safe_strerror(errno));
+ return BUFFER_ERROR;
+ }
+ }
+ }
+ /* Add any remaining data to the buffer. */
+ {
+ size_t written = nbytes;
+ if (written < size)
+ buffer_put(b, ((const char *)p) + written,
+ size - written);
+ }
+ return b->head ? BUFFER_PENDING : BUFFER_EMPTY;
+}
diff --git a/lib/buffer.h b/lib/buffer.h
new file mode 100644
index 0000000..8b5a898
--- /dev/null
+++ b/lib/buffer.h
@@ -0,0 +1,110 @@
+/*
+ * Buffering to output and input.
+ * Copyright (C) 1998 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra 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 _ZEBRA_BUFFER_H
+#define _ZEBRA_BUFFER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Create a new buffer. Memory will be allocated in chunks of the given
+ size. If the argument is 0, the library will supply a reasonable
+ default size suitable for buffering socket I/O. */
+extern struct buffer *buffer_new(size_t);
+
+/* Free all data in the buffer. */
+extern void buffer_reset(struct buffer *);
+
+/* This function first calls buffer_reset to release all buffered data.
+ Then it frees the struct buffer itself. */
+extern void buffer_free(struct buffer *);
+
+/* Add the given data to the end of the buffer. */
+extern void buffer_put(struct buffer *, const void *, size_t);
+/* Add a single character to the end of the buffer. */
+extern void buffer_putc(struct buffer *, uint8_t);
+/* Add a NUL-terminated string to the end of the buffer. */
+extern void buffer_putstr(struct buffer *, const char *);
+/* Add given data, inline-expanding \n to \r\n */
+extern void buffer_put_crlf(struct buffer *b, const void *p, size_t size);
+
+/* Combine all accumulated (and unflushed) data inside the buffer into a
+ single NUL-terminated string allocated using XMALLOC(MTYPE_TMP). Note
+ that this function does not alter the state of the buffer, so the data
+ is still inside waiting to be flushed. */
+char *buffer_getstr(struct buffer *);
+
+/* Returns 1 if there is no pending data in the buffer. Otherwise returns 0. */
+int buffer_empty(struct buffer *);
+
+typedef enum {
+ /* An I/O error occurred. The buffer should be destroyed and the
+ file descriptor should be closed. */
+ BUFFER_ERROR = -1,
+
+ /* The data was written successfully, and the buffer is now empty
+ (there is no pending data waiting to be flushed). */
+ BUFFER_EMPTY = 0,
+
+ /* There is pending data in the buffer waiting to be flushed. Please
+ try flushing the buffer when select indicates that the file
+ descriptor
+ is writeable. */
+ BUFFER_PENDING = 1
+} buffer_status_t;
+
+/* Try to write this data to the file descriptor. Any data that cannot
+ be written immediately is added to the buffer queue. */
+extern buffer_status_t buffer_write(struct buffer *, int fd, const void *,
+ size_t);
+
+/* This function attempts to flush some (but perhaps not all) of
+ the queued data to the given file descriptor. */
+extern buffer_status_t buffer_flush_available(struct buffer *, int fd);
+
+/* The following 2 functions (buffer_flush_all and buffer_flush_window)
+ are for use in lib/vty.c only. They should not be used elsewhere. */
+
+/* Call buffer_flush_available repeatedly until either all data has been
+ flushed, or an I/O error has been encountered, or the operation would
+ block. */
+extern buffer_status_t buffer_flush_all(struct buffer *, int fd);
+
+/* Attempt to write enough data to the given fd to fill a window of the
+ given width and height (and remove the data written from the buffer).
+
+ If !no_more, then a message saying " --More-- " is appended.
+ If erase is true, then first overwrite the previous " --More-- " message
+ with spaces.
+
+ Any write error (including EAGAIN or EINTR) will cause this function
+ to return -1 (because the logic for handling the erase and more features
+ is too complicated to retry the write later).
+*/
+extern buffer_status_t buffer_flush_window(struct buffer *, int fd, int width,
+ int height, int erase, int no_more);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ZEBRA_BUFFER_H */
diff --git a/lib/checksum.c b/lib/checksum.c
new file mode 100644
index 0000000..6c5f06d
--- /dev/null
+++ b/lib/checksum.c
@@ -0,0 +1,154 @@
+/*
+ * Checksum routine for Internet Protocol family headers (C Version).
+ *
+ * Refer to "Computing the Internet Checksum" by R. Braden, D. Borman and
+ * C. Partridge, Computer Communication Review, Vol. 19, No. 2, April 1989,
+ * pp. 86-101, for additional details on computing this checksum.
+ */
+
+#include <zebra.h>
+#include "checksum.h"
+
+#define add_carry(dst, add) \
+ do { \
+ typeof(dst) _add = (add); \
+ dst += _add; \
+ if (dst < _add) \
+ dst++; \
+ } while (0)
+
+uint16_t in_cksumv(const struct iovec *iov, size_t iov_len)
+{
+ const struct iovec *iov_end;
+ uint32_t sum = 0;
+
+ union {
+ uint8_t bytes[2];
+ uint16_t word;
+ } wordbuf;
+ bool have_oddbyte = false;
+
+ /*
+ * Our algorithm is simple, using a 32-bit accumulator (sum),
+ * we add sequential 16-bit words to it, and at the end, fold back
+ * all the carry bits from the top 16 bits into the lower 16 bits.
+ */
+
+ for (iov_end = iov + iov_len; iov < iov_end; iov++) {
+ const uint8_t *ptr, *end;
+
+ ptr = (const uint8_t *)iov->iov_base;
+ end = ptr + iov->iov_len;
+ if (ptr == end)
+ continue;
+
+ if (have_oddbyte) {
+ have_oddbyte = false;
+ wordbuf.bytes[1] = *ptr++;
+
+ add_carry(sum, wordbuf.word);
+ }
+
+ while (ptr + 8 <= end) {
+ add_carry(sum, *(const uint32_t *)(ptr + 0));
+ add_carry(sum, *(const uint32_t *)(ptr + 4));
+ ptr += 8;
+ }
+
+ while (ptr + 2 <= end) {
+ add_carry(sum, *(const uint16_t *)ptr);
+ ptr += 2;
+ }
+
+ if (ptr + 1 <= end) {
+ wordbuf.bytes[0] = *ptr++;
+ have_oddbyte = true;
+ }
+ }
+
+ /* mop up an odd byte, if necessary */
+ if (have_oddbyte) {
+ wordbuf.bytes[1] = 0;
+ add_carry(sum, wordbuf.word);
+ }
+
+ /*
+ * Add back carry outs from top 16 bits to low 16 bits.
+ */
+
+ sum = (sum >> 16) + (sum & 0xffff); /* add high-16 to low-16 */
+ sum += (sum >> 16); /* add carry */
+ return ~sum;
+}
+
+/* Fletcher Checksum -- Refer to RFC1008. */
+#define MODX 4102U /* 5802 should be fine */
+
+/* To be consistent, offset is 0-based index, rather than the 1-based
+ index required in the specification ISO 8473, Annex C.1 */
+/* calling with offset == FLETCHER_CHECKSUM_VALIDATE will validate the checksum
+ without modifying the buffer; a valid checksum returns 0 */
+uint16_t fletcher_checksum(uint8_t *buffer, const size_t len,
+ const uint16_t offset)
+{
+ uint8_t *p;
+ int x, y, c0, c1;
+ uint16_t checksum = 0;
+ uint16_t *csum;
+ size_t partial_len, i, left = len;
+
+ if (offset != FLETCHER_CHECKSUM_VALIDATE)
+ /* Zero the csum in the packet. */
+ {
+ assert(offset
+ < (len - 1)); /* account for two bytes of checksum */
+ csum = (uint16_t *)(buffer + offset);
+ *(csum) = 0;
+ }
+
+ p = buffer;
+ c0 = 0;
+ c1 = 0;
+
+ while (left != 0) {
+ partial_len = MIN(left, MODX);
+
+ for (i = 0; i < partial_len; i++) {
+ c0 = c0 + *(p++);
+ c1 += c0;
+ }
+
+ c0 = c0 % 255;
+ c1 = c1 % 255;
+
+ left -= partial_len;
+ }
+
+ /* The cast is important, to ensure the mod is taken as a signed value.
+ */
+ x = (int)((len - offset - 1) * c0 - c1) % 255;
+
+ if (x <= 0)
+ x += 255;
+ y = 510 - c0 - x;
+ if (y > 255)
+ y -= 255;
+
+ if (offset == FLETCHER_CHECKSUM_VALIDATE) {
+ checksum = (c1 << 8) + c0;
+ } else {
+ /*
+ * Now we write this to the packet.
+ * We could skip this step too, since the checksum returned
+ * would
+ * be stored into the checksum field by the caller.
+ */
+ buffer[offset] = x;
+ buffer[offset + 1] = y;
+
+ /* Take care of the endian issue */
+ checksum = htons((x << 8) | (y & 0xFF));
+ }
+
+ return checksum;
+}
diff --git a/lib/checksum.h b/lib/checksum.h
new file mode 100644
index 0000000..508c3f3
--- /dev/null
+++ b/lib/checksum.h
@@ -0,0 +1,74 @@
+#ifndef _FRR_CHECKSUM_H
+#define _FRR_CHECKSUM_H
+
+#include <stdint.h>
+#include <netinet/in.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/* IPv4 pseudoheader */
+struct ipv4_ph {
+ struct in_addr src;
+ struct in_addr dst;
+ uint8_t rsvd;
+ uint8_t proto;
+ uint16_t len;
+} __attribute__((packed));
+
+/* IPv6 pseudoheader */
+struct ipv6_ph {
+ struct in6_addr src;
+ struct in6_addr dst;
+ uint32_t ulpl;
+ uint8_t zero[3];
+ uint8_t next_hdr;
+} __attribute__((packed));
+
+
+extern uint16_t in_cksumv(const struct iovec *iov, size_t iov_len);
+
+static inline uint16_t in_cksum(const void *data, size_t nbytes)
+{
+ struct iovec iov[1];
+
+ iov[0].iov_base = (void *)data;
+ iov[0].iov_len = nbytes;
+ return in_cksumv(iov, array_size(iov));
+}
+
+static inline uint16_t in_cksum_with_ph4(const struct ipv4_ph *ph,
+ const void *data, size_t nbytes)
+{
+ struct iovec iov[2];
+
+ iov[0].iov_base = (void *)ph;
+ iov[0].iov_len = sizeof(*ph);
+ iov[1].iov_base = (void *)data;
+ iov[1].iov_len = nbytes;
+ return in_cksumv(iov, array_size(iov));
+}
+
+static inline uint16_t in_cksum_with_ph6(const struct ipv6_ph *ph,
+ const void *data, size_t nbytes)
+{
+ struct iovec iov[2];
+
+ iov[0].iov_base = (void *)ph;
+ iov[0].iov_len = sizeof(*ph);
+ iov[1].iov_base = (void *)data;
+ iov[1].iov_len = nbytes;
+ return in_cksumv(iov, array_size(iov));
+}
+
+#define FLETCHER_CHECKSUM_VALIDATE 0xffff
+extern uint16_t fletcher_checksum(uint8_t *, const size_t len,
+ const uint16_t offset);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRR_CHECKSUM_H */
diff --git a/lib/clippy.c b/lib/clippy.c
new file mode 100644
index 0000000..7ca99c9
--- /dev/null
+++ b/lib/clippy.c
@@ -0,0 +1,121 @@
+/*
+ * clippy (CLI preparator in python) main executable
+ * Copyright (C) 2016-2017 David Lamparter for NetDEF, Inc.
+ *
+ * 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 "config.h"
+#include <Python.h>
+#include <string.h>
+#include <stdlib.h>
+#include <wchar.h>
+#include "getopt.h"
+
+#include "command_graph.h"
+#include "clippy.h"
+
+#if PY_MAJOR_VERSION >= 3
+#define pychar wchar_t
+static wchar_t *wconv(const char *s)
+{
+ size_t outlen = s ? mbstowcs(NULL, s, 0) : 0;
+ wchar_t *out = malloc((outlen + 1) * sizeof(wchar_t));
+
+ if (outlen > 0)
+ mbstowcs(out, s, outlen);
+ out[outlen] = 0;
+ return out;
+}
+#else
+#define pychar char
+#define wconv(x) x
+#endif
+
+int main(int argc, char **argv)
+{
+ pychar **wargv;
+
+#if PY_VERSION_HEX >= 0x03040000 /* 3.4 */
+ Py_SetStandardStreamEncoding("UTF-8", NULL);
+#endif
+ wchar_t *name = wconv(argv[0]);
+ Py_SetProgramName(name);
+ PyImport_AppendInittab("_clippy", command_py_init);
+
+ Py_Initialize();
+
+ wargv = malloc(argc * sizeof(pychar *));
+ for (int i = 1; i < argc; i++)
+ wargv[i - 1] = wconv(argv[i]);
+ PySys_SetArgv(argc - 1, wargv);
+
+ const char *pyfile = argc > 1 ? argv[1] : NULL;
+ FILE *fp;
+ if (pyfile) {
+ fp = fopen(pyfile, "r");
+ if (!fp) {
+ fprintf(stderr, "%s: %s\n", pyfile, strerror(errno));
+
+ free(name);
+ return 1;
+ }
+ } else {
+ fp = stdin;
+ char *ver = strdup(Py_GetVersion());
+ char *cr = strchr(ver, '\n');
+ if (cr)
+ *cr = ' ';
+ fprintf(stderr, "clippy interactive shell\n(Python %s)\n", ver);
+ free(ver);
+ PyRun_SimpleString(
+ "import rlcompleter, readline\n"
+ "readline.parse_and_bind('tab: complete')");
+ }
+
+ if (PyRun_AnyFile(fp, pyfile)) {
+ if (PyErr_Occurred())
+ PyErr_Print();
+
+ free(name);
+ return 1;
+ }
+ Py_Finalize();
+
+#if PY_MAJOR_VERSION >= 3
+ for (int i = 1; i < argc; i++)
+ free(wargv[i - 1]);
+#endif
+ free(name);
+ free(wargv);
+ return 0;
+}
+
+/* and now for the ugly part... provide simplified logging functions so we
+ * don't need to link libzebra (which would be a circular build dep) */
+
+#include "log.h"
+
+void vzlogx(const struct xref_logmsg *xref, int prio,
+ const char *format, va_list args)
+{
+ vfprintf(stderr, format, args);
+ fputs("\n", stderr);
+}
+
+void memory_oom(size_t size, const char *name)
+{
+ abort();
+}
diff --git a/lib/clippy.h b/lib/clippy.h
new file mode 100644
index 0000000..95af274
--- /dev/null
+++ b/lib/clippy.h
@@ -0,0 +1,38 @@
+/*
+ * clippy (CLI preparator in python)
+ * Copyright (C) 2016-2017 David Lamparter for NetDEF, Inc.
+ *
+ * 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_CLIPPY_H
+#define _FRR_CLIPPY_H
+
+#include <stdbool.h>
+#include <Python.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern PyObject *clippy_parse(PyObject *self, PyObject *args);
+extern PyMODINIT_FUNC command_py_init(void);
+extern bool elf_py_init(PyObject *pymod);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRR_CLIPPY_H */
diff --git a/lib/command.c b/lib/command.c
new file mode 100644
index 0000000..ca05cd6
--- /dev/null
+++ b/lib/command.c
@@ -0,0 +1,2633 @@
+/*
+ * CLI backend interface.
+ *
+ * --
+ * Copyright (C) 2016 Cumulus Networks, Inc.
+ * Copyright (C) 1997, 98, 99 Kunihiro Ishiguro
+ * Copyright (C) 2013 by Open Source Routing.
+ * Copyright (C) 2013 by Internet Systems Consortium, Inc. ("ISC")
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 <lib/version.h>
+
+#include "command.h"
+#include "frrstr.h"
+#include "memory.h"
+#include "log.h"
+#include "log_vty.h"
+#include "thread.h"
+#include "vector.h"
+#include "linklist.h"
+#include "vty.h"
+#include "workqueue.h"
+#include "vrf.h"
+#include "command_match.h"
+#include "command_graph.h"
+#include "qobj.h"
+#include "defaults.h"
+#include "libfrr.h"
+#include "jhash.h"
+#include "hook.h"
+#include "lib_errors.h"
+#include "northbound_cli.h"
+#include "network.h"
+
+#include "frrscript.h"
+
+DEFINE_MTYPE_STATIC(LIB, HOST, "Host config");
+DEFINE_MTYPE(LIB, COMPLETION, "Completion item");
+
+#define item(x) \
+ { \
+ x, #x \
+ }
+
+/* clang-format off */
+const struct message tokennames[] = {
+ item(WORD_TKN),
+ item(VARIABLE_TKN),
+ item(RANGE_TKN),
+ item(IPV4_TKN),
+ item(IPV4_PREFIX_TKN),
+ item(IPV6_TKN),
+ item(IPV6_PREFIX_TKN),
+ item(MAC_TKN),
+ item(MAC_PREFIX_TKN),
+ item(FORK_TKN),
+ item(JOIN_TKN),
+ item(START_TKN),
+ item(END_TKN),
+ item(NEG_ONLY_TKN),
+ {0},
+};
+/* clang-format on */
+
+/* Command vector which includes some level of command lists. Normally
+ each daemon maintains each own cmdvec. */
+vector cmdvec = NULL;
+
+/* Host information structure. */
+struct host host;
+
+/* for vtysh, put together CLI trees only when switching into node */
+static bool defer_cli_tree;
+
+/*
+ * Returns host.name if any, otherwise
+ * it returns the system hostname.
+ */
+const char *cmd_hostname_get(void)
+{
+ return host.name;
+}
+
+/*
+ * Returns unix domainname
+ */
+const char *cmd_domainname_get(void)
+{
+ return host.domainname;
+}
+
+const char *cmd_system_get(void)
+{
+ return host.system;
+}
+
+const char *cmd_release_get(void)
+{
+ return host.release;
+}
+
+const char *cmd_version_get(void)
+{
+ return host.version;
+}
+
+bool cmd_allow_reserved_ranges_get(void)
+{
+ return host.allow_reserved_ranges;
+}
+
+static int root_on_exit(struct vty *vty);
+
+/* Standard command node structures. */
+static struct cmd_node auth_node = {
+ .name = "auth",
+ .node = AUTH_NODE,
+ .prompt = "Password: ",
+};
+
+static struct cmd_node view_node = {
+ .name = "view",
+ .node = VIEW_NODE,
+ .prompt = "%s> ",
+ .node_exit = root_on_exit,
+};
+
+static struct cmd_node auth_enable_node = {
+ .name = "auth enable",
+ .node = AUTH_ENABLE_NODE,
+ .prompt = "Password: ",
+};
+
+static struct cmd_node enable_node = {
+ .name = "enable",
+ .node = ENABLE_NODE,
+ .prompt = "%s# ",
+ .node_exit = root_on_exit,
+};
+
+static int config_write_host(struct vty *vty);
+static struct cmd_node config_node = {
+ .name = "config",
+ .node = CONFIG_NODE,
+ .parent_node = ENABLE_NODE,
+ .prompt = "%s(config)# ",
+ .config_write = config_write_host,
+ .node_exit = vty_config_node_exit,
+};
+
+/* This is called from main when a daemon is invoked with -v or --version. */
+void print_version(const char *progname)
+{
+ printf("%s version %s\n", progname, FRR_VERSION);
+ printf("%s\n", FRR_COPYRIGHT);
+#ifdef ENABLE_VERSION_BUILD_CONFIG
+ printf("configured with:\n\t%s\n", FRR_CONFIG_ARGS);
+#endif
+}
+
+char *argv_concat(struct cmd_token **argv, int argc, int shift)
+{
+ int cnt = MAX(argc - shift, 0);
+ const char *argstr[cnt + 1];
+
+ if (!cnt)
+ return NULL;
+
+ for (int i = 0; i < cnt; i++)
+ argstr[i] = argv[i + shift]->arg;
+
+ return frrstr_join(argstr, cnt, " ");
+}
+
+vector cmd_make_strvec(const char *string)
+{
+ if (!string)
+ return NULL;
+
+ const char *copy = string;
+
+ /* skip leading whitespace */
+ while (isspace((unsigned char)*copy) && *copy != '\0')
+ copy++;
+
+ /* if the entire string was whitespace or a comment, return */
+ if (*copy == '\0' || *copy == '!' || *copy == '#')
+ return NULL;
+
+ vector result = frrstr_split_vec(copy, "\n\r\t ");
+
+ for (unsigned int i = 0; i < vector_active(result); i++) {
+ if (strlen(vector_slot(result, i)) == 0) {
+ XFREE(MTYPE_TMP, vector_slot(result, i));
+ vector_unset(result, i);
+ }
+ }
+
+ vector_compact(result);
+
+ return result;
+}
+
+void cmd_free_strvec(vector v)
+{
+ frrstr_strvec_free(v);
+}
+
+/**
+ * Convenience function for accessing argv data.
+ *
+ * @param argc
+ * @param argv
+ * @param text definition snippet of the desired token
+ * @param index the starting index, and where to store the
+ * index of the found token if it exists
+ * @return 1 if found, 0 otherwise
+ */
+int argv_find(struct cmd_token **argv, int argc, const char *text, int *index)
+{
+ int found = 0;
+ for (int i = *index; i < argc && found == 0; i++)
+ if ((found = strmatch(text, argv[i]->text)))
+ *index = i;
+ return found;
+}
+
+static unsigned int cmd_hash_key(const void *p)
+{
+ int size = sizeof(p);
+
+ return jhash(p, size, 0);
+}
+
+static bool cmd_hash_cmp(const void *a, const void *b)
+{
+ return a == b;
+}
+
+/* Install top node of command vector. */
+void install_node(struct cmd_node *node)
+{
+#define CMD_HASH_STR_SIZE 256
+ char hash_name[CMD_HASH_STR_SIZE];
+
+ vector_set_index(cmdvec, node->node, node);
+ node->cmdgraph = graph_new();
+ node->cmd_vector = vector_init(VECTOR_MIN_SIZE);
+ // add start node
+ struct cmd_token *token =
+ cmd_token_new(START_TKN, CMD_ATTR_NORMAL, NULL, NULL);
+ graph_new_node(node->cmdgraph, token,
+ (void (*)(void *)) & cmd_token_del);
+
+ snprintf(hash_name, sizeof(hash_name), "Command Hash: %s", node->name);
+ node->cmd_hash =
+ hash_create_size(16, cmd_hash_key, cmd_hash_cmp, hash_name);
+}
+
+/* Return prompt character of specified node. */
+const char *cmd_prompt(enum node_type node)
+{
+ struct cmd_node *cnode;
+
+ cnode = vector_slot(cmdvec, node);
+ return cnode->prompt;
+}
+
+void cmd_defer_tree(bool val)
+{
+ defer_cli_tree = val;
+}
+
+/* Install a command into a node. */
+void _install_element(enum node_type ntype, const struct cmd_element *cmd)
+{
+ struct cmd_node *cnode;
+
+ /* cmd_init hasn't been called */
+ if (!cmdvec) {
+ fprintf(stderr, "%s called before cmd_init, breakage likely\n",
+ __func__);
+ return;
+ }
+
+ cnode = vector_lookup(cmdvec, ntype);
+
+ if (cnode == NULL) {
+ fprintf(stderr,
+ "%s[%s]:\n"
+ "\tnode %d does not exist.\n"
+ "\tplease call install_node() before install_element()\n",
+ cmd->name, cmd->string, ntype);
+ exit(EXIT_FAILURE);
+ }
+
+ if (hash_lookup(cnode->cmd_hash, (void *)cmd) != NULL) {
+ fprintf(stderr,
+ "%s[%s]:\n"
+ "\tnode %d (%s) already has this command installed.\n"
+ "\tduplicate install_element call?\n",
+ cmd->name, cmd->string, ntype, cnode->name);
+ return;
+ }
+
+ (void)hash_get(cnode->cmd_hash, (void *)cmd, hash_alloc_intern);
+
+ if (cnode->graph_built || !defer_cli_tree) {
+ struct graph *graph = graph_new();
+ struct cmd_token *token =
+ cmd_token_new(START_TKN, CMD_ATTR_NORMAL, NULL, NULL);
+ graph_new_node(graph, token,
+ (void (*)(void *)) & cmd_token_del);
+
+ cmd_graph_parse(graph, cmd);
+ cmd_graph_names(graph);
+ cmd_graph_merge(cnode->cmdgraph, graph, +1);
+ graph_delete_graph(graph);
+
+ cnode->graph_built = true;
+ }
+
+ vector_set(cnode->cmd_vector, (void *)cmd);
+
+ if (ntype == VIEW_NODE)
+ _install_element(ENABLE_NODE, cmd);
+}
+
+static void cmd_finalize_iter(struct hash_bucket *hb, void *arg)
+{
+ struct cmd_node *cnode = arg;
+ const struct cmd_element *cmd = hb->data;
+ struct graph *graph = graph_new();
+ struct cmd_token *token =
+ cmd_token_new(START_TKN, CMD_ATTR_NORMAL, NULL, NULL);
+
+ graph_new_node(graph, token, (void (*)(void *)) & cmd_token_del);
+
+ cmd_graph_parse(graph, cmd);
+ cmd_graph_names(graph);
+ cmd_graph_merge(cnode->cmdgraph, graph, +1);
+ graph_delete_graph(graph);
+}
+
+void cmd_finalize_node(struct cmd_node *cnode)
+{
+ if (cnode->graph_built)
+ return;
+
+ hash_iterate(cnode->cmd_hash, cmd_finalize_iter, cnode);
+ cnode->graph_built = true;
+}
+
+void uninstall_element(enum node_type ntype, const struct cmd_element *cmd)
+{
+ struct cmd_node *cnode;
+
+ /* cmd_init hasn't been called */
+ if (!cmdvec) {
+ fprintf(stderr, "%s called before cmd_init, breakage likely\n",
+ __func__);
+ return;
+ }
+
+ cnode = vector_lookup(cmdvec, ntype);
+
+ if (cnode == NULL) {
+ fprintf(stderr,
+ "%s[%s]:\n"
+ "\tnode %d does not exist.\n"
+ "\tplease call install_node() before uninstall_element()\n",
+ cmd->name, cmd->string, ntype);
+ exit(EXIT_FAILURE);
+ }
+
+ if (hash_release(cnode->cmd_hash, (void *)cmd) == NULL) {
+ fprintf(stderr,
+ "%s[%s]:\n"
+ "\tnode %d (%s) does not have this command installed.\n"
+ "\tduplicate uninstall_element call?\n",
+ cmd->name, cmd->string, ntype, cnode->name);
+ return;
+ }
+
+ vector_unset_value(cnode->cmd_vector, (void *)cmd);
+
+ if (cnode->graph_built) {
+ struct graph *graph = graph_new();
+ struct cmd_token *token =
+ cmd_token_new(START_TKN, CMD_ATTR_NORMAL, NULL, NULL);
+ graph_new_node(graph, token,
+ (void (*)(void *)) & cmd_token_del);
+
+ cmd_graph_parse(graph, cmd);
+ cmd_graph_names(graph);
+ cmd_graph_merge(cnode->cmdgraph, graph, -1);
+ graph_delete_graph(graph);
+ }
+
+ if (ntype == VIEW_NODE)
+ uninstall_element(ENABLE_NODE, cmd);
+}
+
+
+static const unsigned char itoa64[] =
+ "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+static void to64(char *s, long v, int n)
+{
+ while (--n >= 0) {
+ *s++ = itoa64[v & 0x3f];
+ v >>= 6;
+ }
+}
+
+static char *zencrypt(const char *passwd)
+{
+ char salt[6];
+ struct timeval tv;
+
+ gettimeofday(&tv, 0);
+
+ to64(&salt[0], frr_weak_random(), 3);
+ to64(&salt[3], tv.tv_usec, 3);
+ salt[5] = '\0';
+
+ return crypt(passwd, salt);
+}
+
+static bool full_cli;
+
+/* This function write configuration of this host. */
+static int config_write_host(struct vty *vty)
+{
+ const char *name;
+
+ name = cmd_hostname_get();
+ if (name && name[0] != '\0')
+ vty_out(vty, "hostname %s\n", name);
+
+ name = cmd_domainname_get();
+ if (name && name[0] != '\0')
+ vty_out(vty, "domainname %s\n", name);
+
+ if (cmd_allow_reserved_ranges_get())
+ vty_out(vty, "allow-reserved-ranges\n");
+
+ /* The following are all configuration commands that are not sent to
+ * watchfrr. For instance watchfrr is hardcoded to log to syslog so
+ * we would always display 'log syslog informational' in the config
+ * which would cause other daemons to then switch to syslog when they
+ * parse frr.conf.
+ */
+ if (full_cli) {
+ if (host.encrypt) {
+ if (host.password_encrypt)
+ vty_out(vty, "password 8 %s\n",
+ host.password_encrypt);
+ if (host.enable_encrypt)
+ vty_out(vty, "enable password 8 %s\n",
+ host.enable_encrypt);
+ } else {
+ if (host.password)
+ vty_out(vty, "password %s\n", host.password);
+ if (host.enable)
+ vty_out(vty, "enable password %s\n",
+ host.enable);
+ }
+ log_config_write(vty);
+
+ /* print disable always, but enable only if default is flipped
+ * => prep for future removal of compile-time knob
+ */
+ if (!cputime_enabled)
+ vty_out(vty, "no service cputime-stats\n");
+#ifdef EXCLUDE_CPU_TIME
+ else
+ vty_out(vty, "service cputime-stats\n");
+#endif
+
+ if (!cputime_threshold)
+ vty_out(vty, "no service cputime-warning\n");
+#if defined(CONSUMED_TIME_CHECK) && CONSUMED_TIME_CHECK != 5000000
+ else /* again, always print non-default */
+#else
+ else if (cputime_threshold != 5000000)
+#endif
+ vty_out(vty, "service cputime-warning %lu\n",
+ cputime_threshold);
+
+ if (!walltime_threshold)
+ vty_out(vty, "no service walltime-warning\n");
+#if defined(CONSUMED_TIME_CHECK) && CONSUMED_TIME_CHECK != 5000000
+ else /* again, always print non-default */
+#else
+ else if (walltime_threshold != 5000000)
+#endif
+ vty_out(vty, "service walltime-warning %lu\n",
+ walltime_threshold);
+
+ if (host.advanced)
+ vty_out(vty, "service advanced-vty\n");
+
+ if (host.encrypt)
+ vty_out(vty, "service password-encryption\n");
+
+ if (host.lines >= 0)
+ vty_out(vty, "service terminal-length %d\n",
+ host.lines);
+
+ if (host.motdfile)
+ vty_out(vty, "banner motd file %s\n", host.motdfile);
+ else if (host.motd
+ && strncmp(host.motd, FRR_DEFAULT_MOTD,
+ strlen(host.motd)))
+ vty_out(vty, "banner motd line %s\n", host.motd);
+ else if (!host.motd)
+ vty_out(vty, "no banner motd\n");
+ }
+
+ if (debug_memstats_at_exit)
+ vty_out(vty, "!\ndebug memstats-at-exit\n");
+
+ return 1;
+}
+
+/* Utility function for getting command graph. */
+static struct graph *cmd_node_graph(vector v, enum node_type ntype)
+{
+ struct cmd_node *cnode = vector_slot(v, ntype);
+
+ cmd_finalize_node(cnode);
+ return cnode->cmdgraph;
+}
+
+static int cmd_try_do_shortcut(enum node_type node, char *first_word)
+{
+ if (first_word != NULL && node != AUTH_NODE && node != VIEW_NODE
+ && node != AUTH_ENABLE_NODE && 0 == strcmp("do", first_word))
+ return 1;
+ return 0;
+}
+
+/**
+ * Compare function for cmd_token.
+ * Used with qsort to sort command completions.
+ */
+static int compare_completions(const void *fst, const void *snd)
+{
+ const struct cmd_token *first = *(const struct cmd_token * const *)fst,
+ *secnd = *(const struct cmd_token * const *)snd;
+ return strcmp(first->text, secnd->text);
+}
+
+/**
+ * Takes a list of completions returned by command_complete,
+ * dedeuplicates them based on both text and description,
+ * sorts them, and returns them as a vector.
+ *
+ * @param completions linked list of cmd_token
+ * @return deduplicated and sorted vector with
+ */
+vector completions_to_vec(struct list *completions)
+{
+ vector comps = vector_init(VECTOR_MIN_SIZE);
+
+ struct listnode *ln;
+ struct cmd_token *token, *cr = NULL;
+ unsigned int i, exists;
+ for (ALL_LIST_ELEMENTS_RO(completions, ln, token)) {
+ if (token->type == END_TKN && (cr = token))
+ continue;
+
+ // linear search for token in completions vector
+ exists = 0;
+ for (i = 0; i < vector_active(comps) && !exists; i++) {
+ struct cmd_token *curr = vector_slot(comps, i);
+#ifdef VTYSH_DEBUG
+ exists = !strcmp(curr->text, token->text)
+ && !strcmp(curr->desc, token->desc);
+#else
+ exists = !strcmp(curr->text, token->text);
+#endif /* VTYSH_DEBUG */
+ }
+
+ if (!exists)
+ vector_set(comps, token);
+ }
+
+ // sort completions
+ qsort(comps->index, vector_active(comps), sizeof(void *),
+ &compare_completions);
+
+ // make <cr> the first element, if it is present
+ if (cr) {
+ vector_set_index(comps, vector_active(comps), NULL);
+ memmove(comps->index + 1, comps->index,
+ (comps->alloced - 1) * sizeof(void *));
+ vector_set_index(comps, 0, cr);
+ }
+
+ return comps;
+}
+/**
+ * Generates a vector of cmd_token representing possible completions
+ * on the current input.
+ *
+ * @param vline the vectorized input line
+ * @param vty the vty with the node to match on
+ * @param status pointer to matcher status code
+ * @return vector of struct cmd_token * with possible completions
+ */
+static vector cmd_complete_command_real(vector vline, struct vty *vty,
+ int *status)
+{
+ struct list *completions;
+ struct graph *cmdgraph = cmd_node_graph(cmdvec, vty->node);
+
+ enum matcher_rv rv = command_complete(cmdgraph, vline, &completions);
+
+ if (MATCHER_ERROR(rv)) {
+ *status = CMD_ERR_NO_MATCH;
+ return NULL;
+ }
+
+ vector comps = completions_to_vec(completions);
+ list_delete(&completions);
+
+ // set status code appropriately
+ switch (vector_active(comps)) {
+ case 0:
+ *status = CMD_ERR_NO_MATCH;
+ break;
+ case 1:
+ *status = CMD_COMPLETE_FULL_MATCH;
+ break;
+ default:
+ *status = CMD_COMPLETE_LIST_MATCH;
+ }
+
+ return comps;
+}
+
+vector cmd_describe_command(vector vline, struct vty *vty, int *status)
+{
+ vector ret;
+
+ 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
+ */
+
+ shifted_vline = vector_init(vector_count(vline));
+ /* use memcpy? */
+ for (index = 1; index < vector_active(vline); index++) {
+ vector_set_index(shifted_vline, index - 1,
+ vector_lookup(vline, index));
+ }
+
+ ret = cmd_complete_command_real(shifted_vline, vty, status);
+
+ vector_free(shifted_vline);
+ vty->node = onode;
+ vty->xpath_index = orig_xpath_index;
+ return ret;
+ }
+
+ return cmd_complete_command_real(vline, vty, status);
+}
+
+static struct list *varhandlers = NULL;
+
+void cmd_variable_complete(struct cmd_token *token, const char *arg,
+ vector comps)
+{
+ struct listnode *ln;
+ const struct cmd_variable_handler *cvh;
+ size_t i, argsz;
+ vector tmpcomps;
+
+ tmpcomps = arg ? vector_init(VECTOR_MIN_SIZE) : comps;
+
+ for (ALL_LIST_ELEMENTS_RO(varhandlers, ln, cvh)) {
+ if (cvh->tokenname && strcmp(cvh->tokenname, token->text))
+ continue;
+ if (cvh->varname && (!token->varname
+ || strcmp(cvh->varname, token->varname)))
+ continue;
+ cvh->completions(tmpcomps, token);
+ break;
+ }
+
+ if (!arg)
+ return;
+
+ argsz = strlen(arg);
+ for (i = vector_active(tmpcomps); i; i--) {
+ char *item = vector_slot(tmpcomps, i - 1);
+ if (strlen(item) >= argsz && !strncmp(item, arg, argsz))
+ vector_set(comps, item);
+ else
+ XFREE(MTYPE_COMPLETION, item);
+ }
+ vector_free(tmpcomps);
+}
+
+#define AUTOCOMP_INDENT 5
+
+char *cmd_variable_comp2str(vector comps, unsigned short cols)
+{
+ size_t bsz = 16;
+ char *buf = XCALLOC(MTYPE_TMP, bsz);
+ int lc = AUTOCOMP_INDENT;
+ size_t cs = AUTOCOMP_INDENT;
+ size_t itemlen;
+ snprintf(buf, bsz, "%*s", AUTOCOMP_INDENT, "");
+ for (size_t j = 0; j < vector_active(comps); j++) {
+ char *item = vector_slot(comps, j);
+ itemlen = strlen(item);
+
+ size_t next_sz = cs + itemlen + AUTOCOMP_INDENT + 3;
+
+ if (next_sz > bsz) {
+ /* Make sure the buf size is large enough */
+ bsz = next_sz;
+ buf = XREALLOC(MTYPE_TMP, buf, bsz);
+ }
+ if (lc + itemlen + 1 >= cols) {
+ cs += snprintf(&buf[cs], bsz - cs, "\n%*s",
+ AUTOCOMP_INDENT, "");
+ lc = AUTOCOMP_INDENT;
+ }
+
+ size_t written = snprintf(&buf[cs], bsz - cs, "%s ", item);
+ lc += written;
+ cs += written;
+ XFREE(MTYPE_COMPLETION, item);
+ vector_set_index(comps, j, NULL);
+ }
+ return buf;
+}
+
+void cmd_variable_handler_register(const struct cmd_variable_handler *cvh)
+{
+ if (!varhandlers)
+ return;
+
+ for (; cvh->completions; cvh++)
+ listnode_add(varhandlers, (void *)cvh);
+}
+
+DEFUN_HIDDEN (autocomplete,
+ autocomplete_cmd,
+ "autocomplete TYPE TEXT VARNAME",
+ "Autocompletion handler (internal, for vtysh)\n"
+ "cmd_token->type\n"
+ "cmd_token->text\n"
+ "cmd_token->varname\n")
+{
+ struct cmd_token tok;
+ vector comps = vector_init(32);
+ size_t i;
+
+ memset(&tok, 0, sizeof(tok));
+ tok.type = atoi(argv[1]->arg);
+ tok.text = argv[2]->arg;
+ tok.varname = argv[3]->arg;
+ if (!strcmp(tok.varname, "-"))
+ tok.varname = NULL;
+
+ cmd_variable_complete(&tok, NULL, comps);
+
+ for (i = 0; i < vector_active(comps); i++) {
+ char *text = vector_slot(comps, i);
+ vty_out(vty, "%s\n", text);
+ XFREE(MTYPE_COMPLETION, text);
+ }
+
+ vector_free(comps);
+ return CMD_SUCCESS;
+}
+
+/**
+ * Generate possible tab-completions for the given input. This function only
+ * returns results that would result in a valid command if used as Readline
+ * completions (as is the case in vtysh). For instance, if the passed vline ends
+ * with '4.3.2', the strings 'A.B.C.D' and 'A.B.C.D/M' will _not_ be returned.
+ *
+ * @param vline vectorized input line
+ * @param vty the vty
+ * @param status location to store matcher status code in
+ * @return set of valid strings for use with Readline as tab-completions.
+ */
+
+char **cmd_complete_command(vector vline, struct vty *vty, int *status)
+{
+ char **ret = NULL;
+ int original_node = vty->node;
+ vector input_line = vector_init(vector_count(vline));
+
+ // if the first token is 'do' we'll want to execute the command in the
+ // enable node
+ int do_shortcut = cmd_try_do_shortcut(vty->node, vector_slot(vline, 0));
+ vty->node = do_shortcut ? ENABLE_NODE : original_node;
+
+ // construct the input line we'll be matching on
+ unsigned int offset = (do_shortcut) ? 1 : 0;
+ for (unsigned index = 0; index + offset < vector_active(vline); index++)
+ vector_set_index(input_line, index,
+ vector_lookup(vline, index + offset));
+
+ // get token completions -- this is a copying operation
+ vector comps = NULL, initial_comps;
+ initial_comps = cmd_complete_command_real(input_line, vty, status);
+
+ if (!MATCHER_ERROR(*status)) {
+ assert(initial_comps);
+ // filter out everything that is not suitable for a
+ // tab-completion
+ comps = vector_init(VECTOR_MIN_SIZE);
+ for (unsigned int i = 0; i < vector_active(initial_comps);
+ i++) {
+ struct cmd_token *token = vector_slot(initial_comps, i);
+ if (token->type == WORD_TKN)
+ vector_set(comps, XSTRDUP(MTYPE_COMPLETION,
+ token->text));
+ else if (IS_VARYING_TOKEN(token->type)) {
+ const char *ref = vector_lookup(
+ vline, vector_active(vline) - 1);
+ cmd_variable_complete(token, ref, comps);
+ }
+ }
+ vector_free(initial_comps);
+
+ // since we filtered results, we need to re-set status code
+ switch (vector_active(comps)) {
+ case 0:
+ *status = CMD_ERR_NO_MATCH;
+ break;
+ case 1:
+ *status = CMD_COMPLETE_FULL_MATCH;
+ break;
+ default:
+ *status = CMD_COMPLETE_LIST_MATCH;
+ }
+
+ // copy completions text into an array of char*
+ ret = XMALLOC(MTYPE_TMP,
+ (vector_active(comps) + 1) * sizeof(char *));
+ unsigned int i;
+ for (i = 0; i < vector_active(comps); i++) {
+ ret[i] = vector_slot(comps, i);
+ }
+ // set the last element to NULL, because this array is used in
+ // a Readline completion_generator function which expects NULL
+ // as a sentinel value
+ ret[i] = NULL;
+ vector_free(comps);
+ comps = NULL;
+ } else if (initial_comps)
+ vector_free(initial_comps);
+
+ // comps should always be null here
+ assert(!comps);
+
+ // free the adjusted input line
+ vector_free(input_line);
+
+ // reset vty->node to its original value
+ vty->node = original_node;
+
+ return ret;
+}
+
+/* return parent node */
+/* MUST eventually converge on CONFIG_NODE */
+enum node_type node_parent(enum node_type node)
+{
+ struct cmd_node *cnode;
+
+ assert(node > CONFIG_NODE);
+
+ cnode = vector_lookup(cmdvec, node);
+
+ return cnode->parent_node;
+}
+
+/* Execute command by argument vline vector. */
+static int cmd_execute_command_real(vector vline, enum cmd_filter_type filter,
+ struct vty *vty,
+ const struct cmd_element **cmd,
+ unsigned int up_level)
+{
+ struct list *argv_list;
+ enum matcher_rv status;
+ const struct cmd_element *matched_element = NULL;
+ unsigned int i;
+ int xpath_index = vty->xpath_index;
+ int node = vty->node;
+
+ /* only happens for legacy split config file load; need to check for
+ * a match before calling node_exit handlers below
+ */
+ for (i = 0; i < up_level; i++) {
+ struct cmd_node *cnode;
+
+ if (node <= CONFIG_NODE)
+ return CMD_NO_LEVEL_UP;
+
+ cnode = vector_slot(cmdvec, node);
+ node = node_parent(node);
+
+ if (xpath_index > 0 && !cnode->no_xpath)
+ xpath_index--;
+ }
+
+ struct graph *cmdgraph = cmd_node_graph(cmdvec, node);
+ status = command_match(cmdgraph, vline, &argv_list, &matched_element);
+
+ if (cmd)
+ *cmd = matched_element;
+
+ // if matcher error, return corresponding CMD_ERR
+ if (MATCHER_ERROR(status)) {
+ if (argv_list)
+ list_delete(&argv_list);
+ switch (status) {
+ case MATCHER_INCOMPLETE:
+ return CMD_ERR_INCOMPLETE;
+ case MATCHER_AMBIGUOUS:
+ return CMD_ERR_AMBIGUOUS;
+ default:
+ return CMD_ERR_NO_MATCH;
+ }
+ }
+
+ for (i = 0; i < up_level; i++)
+ cmd_exit(vty);
+
+ // build argv array from argv list
+ struct cmd_token **argv = XMALLOC(
+ MTYPE_TMP, argv_list->count * sizeof(struct cmd_token *));
+ struct listnode *ln;
+ struct cmd_token *token;
+
+ i = 0;
+ for (ALL_LIST_ELEMENTS_RO(argv_list, ln, token))
+ argv[i++] = token;
+
+ int argc = argv_list->count;
+
+ int ret;
+ if (matched_element->daemon)
+ ret = CMD_SUCCESS_DAEMON;
+ else {
+ if (vty->config) {
+ /* Clear array of enqueued configuration changes. */
+ vty->num_cfg_changes = 0;
+ memset(&vty->cfg_changes, 0, sizeof(vty->cfg_changes));
+
+ /* Regenerate candidate configuration if necessary. */
+ if (frr_get_cli_mode() == FRR_CLI_CLASSIC
+ && running_config->version
+ > vty->candidate_config->version)
+ nb_config_replace(vty->candidate_config,
+ running_config, true);
+
+ /*
+ * Perform pending commit (if any) before executing
+ * non-YANG command.
+ */
+ if (matched_element->attr != CMD_ATTR_YANG)
+ (void)nb_cli_pending_commit_check(vty);
+ }
+
+ ret = matched_element->func(matched_element, vty, argc, argv);
+ }
+
+ // delete list and cmd_token's in it
+ list_delete(&argv_list);
+ XFREE(MTYPE_TMP, argv);
+
+ return ret;
+}
+
+/**
+ * Execute a given command, handling things like "do ..." and checking
+ * whether the given command might apply at a parent node if doesn't
+ * apply for the current node.
+ *
+ * @param vline Command line input, vector of char* where each element is
+ * one input token.
+ * @param vty The vty context in which the command should be executed.
+ * @param cmd Pointer where the struct cmd_element of the matched command
+ * will be stored, if any. May be set to NULL if this info is
+ * not needed.
+ * @param vtysh If set != 0, don't lookup the command at parent nodes.
+ * @return The status of the command that has been executed or an error code
+ * as to why no command could be executed.
+ */
+int cmd_execute_command(vector vline, struct vty *vty,
+ const struct cmd_element **cmd, int vtysh)
+{
+ 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
+ */
+
+ shifted_vline = vector_init(vector_count(vline));
+ /* use memcpy? */
+ for (index = 1; index < vector_active(vline); index++)
+ vector_set_index(shifted_vline, index - 1,
+ vector_lookup(vline, index));
+
+ ret = cmd_execute_command_real(shifted_vline, FILTER_RELAXED,
+ vty, cmd, 0);
+
+ vector_free(shifted_vline);
+ vty->node = onode;
+ vty->xpath_index = orig_xpath_index;
+ return ret;
+ }
+
+ saved_ret = ret =
+ cmd_execute_command_real(vline, FILTER_RELAXED, vty, cmd, 0);
+
+ if (vtysh)
+ return saved_ret;
+
+ if (ret != CMD_SUCCESS && ret != CMD_WARNING
+ && ret != CMD_ERR_AMBIGUOUS && ret != CMD_ERR_INCOMPLETE
+ && ret != CMD_NOT_MY_INSTANCE && ret != CMD_WARNING_CONFIG_FAILED) {
+ /* This assumes all nodes above CONFIG_NODE are childs of
+ * CONFIG_NODE */
+ while (vty->node > CONFIG_NODE) {
+ struct cmd_node *cnode = vector_slot(cmdvec, try_node);
+
+ try_node = node_parent(try_node);
+ vty->node = try_node;
+ if (vty->xpath_index > 0 && !cnode->no_xpath)
+ vty->xpath_index--;
+
+ ret = cmd_execute_command_real(vline, FILTER_RELAXED,
+ vty, cmd, 0);
+ if (ret == CMD_SUCCESS || ret == CMD_WARNING
+ || ret == CMD_ERR_AMBIGUOUS || ret == CMD_ERR_INCOMPLETE
+ || ret == CMD_NOT_MY_INSTANCE
+ || ret == CMD_WARNING_CONFIG_FAILED)
+ return ret;
+ }
+ /* 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 */
+ return saved_ret;
+}
+
+/**
+ * Execute a given command, matching it strictly against the current node.
+ * This mode is used when reading config files.
+ *
+ * @param vline Command line input, vector of char* where each element is
+ * one input token.
+ * @param vty The vty context in which the command should be executed.
+ * @param cmd Pointer where the struct cmd_element* of the matched command
+ * will be stored, if any. May be set to NULL if this info is
+ * not needed.
+ * @return The status of the command that has been executed or an error code
+ * as to why no command could be executed.
+ */
+int cmd_execute_command_strict(vector vline, struct vty *vty,
+ const struct cmd_element **cmd)
+{
+ return cmd_execute_command_real(vline, FILTER_STRICT, vty, cmd, 0);
+}
+
+/*
+ * Hook for preprocessing command string before executing.
+ *
+ * All subscribers are called with the raw command string that is to be
+ * executed. If any changes are to be made, a new string should be allocated
+ * with MTYPE_TMP and *cmd_out updated to point to this new string. The caller
+ * is then responsible for freeing this string.
+ *
+ * All processing functions must be mutually exclusive in their action, i.e. if
+ * one subscriber decides to modify the command, all others must not modify it
+ * when called. Feeding the output of one processing command into a subsequent
+ * one is not supported.
+ *
+ * This hook is intentionally internal to the command processing system.
+ *
+ * cmd_in
+ * The raw command string.
+ *
+ * cmd_out
+ * The result of any processing.
+ */
+DECLARE_HOOK(cmd_execute,
+ (struct vty *vty, const char *cmd_in, char **cmd_out),
+ (vty, cmd_in, cmd_out));
+DEFINE_HOOK(cmd_execute, (struct vty *vty, const char *cmd_in, char **cmd_out),
+ (vty, cmd_in, cmd_out));
+
+/* Hook executed after a CLI command. */
+DECLARE_KOOH(cmd_execute_done, (struct vty *vty, const char *cmd_exec),
+ (vty, cmd_exec));
+DEFINE_KOOH(cmd_execute_done, (struct vty *vty, const char *cmd_exec),
+ (vty, cmd_exec));
+
+/*
+ * cmd_execute hook subscriber to handle `|` actions.
+ */
+static int handle_pipe_action(struct vty *vty, const char *cmd_in,
+ char **cmd_out)
+{
+ /* look for `|` */
+ char *orig, *working, *token, *u;
+ char *pipe = strstr(cmd_in, "| ");
+ int ret = 0;
+
+ if (!pipe)
+ return 0;
+
+ /* duplicate string for processing purposes, not including pipe */
+ orig = working = XSTRDUP(MTYPE_TMP, pipe + 2);
+
+ /* retrieve action */
+ token = strsep(&working, " ");
+ assert(token);
+
+ /* match result to known actions */
+ if (strmatch(token, "include")) {
+ /* the remaining text should be a regexp */
+ char *regexp = working;
+
+ if (!regexp) {
+ vty_out(vty, "%% Need a regexp to filter with\n");
+ ret = 1;
+ goto fail;
+ }
+
+ bool succ = vty_set_include(vty, regexp);
+
+ if (!succ) {
+ vty_out(vty, "%% Bad regexp '%s'\n", regexp);
+ ret = 1;
+ goto fail;
+ }
+ *cmd_out = XSTRDUP(MTYPE_TMP, cmd_in);
+ u = *cmd_out;
+ strsep(&u, "|");
+ } else {
+ vty_out(vty, "%% Unknown action '%s'\n", token);
+ ret = 1;
+ goto fail;
+ }
+
+fail:
+ XFREE(MTYPE_TMP, orig);
+ return ret;
+}
+
+static int handle_pipe_action_done(struct vty *vty, const char *cmd_exec)
+{
+ if (vty->filter)
+ vty_set_include(vty, NULL);
+
+ return 0;
+}
+
+int cmd_execute(struct vty *vty, const char *cmd,
+ const struct cmd_element **matched, int vtysh)
+{
+ int ret;
+ char *cmd_out = NULL;
+ const char *cmd_exec = NULL;
+ vector vline;
+
+ ret = hook_call(cmd_execute, vty, cmd, &cmd_out);
+ if (ret) {
+ ret = CMD_WARNING;
+ goto free;
+ }
+
+ cmd_exec = cmd_out ? (const char *)cmd_out : cmd;
+
+ vline = cmd_make_strvec(cmd_exec);
+
+ if (vline) {
+ ret = cmd_execute_command(vline, vty, matched, vtysh);
+ cmd_free_strvec(vline);
+ } else {
+ ret = CMD_SUCCESS;
+ }
+
+free:
+ hook_call(cmd_execute_done, vty, cmd_exec);
+
+ XFREE(MTYPE_TMP, cmd_out);
+
+ return ret;
+}
+
+
+/**
+ * Parse one line of config, walking up the parse tree attempting to find a
+ * match
+ *
+ * @param vty The vty context in which the command should be executed.
+ * @param cmd Pointer where the struct cmd_element* of the match command
+ * will be stored, if any. May be set to NULL if this info is
+ * not needed.
+ * @param use_daemon Boolean to control whether or not we match on
+ * CMD_SUCCESS_DAEMON
+ * or not.
+ * @return The status of the command that has been executed or an error code
+ * as to why no command could be executed.
+ */
+int command_config_read_one_line(struct vty *vty,
+ const struct cmd_element **cmd,
+ uint32_t line_num, int use_daemon)
+{
+ vector vline;
+ int ret;
+ unsigned up_level = 0;
+
+ vline = cmd_make_strvec(vty->buf);
+
+ /* In case of comment line */
+ if (vline == NULL)
+ return CMD_SUCCESS;
+
+ /* Execute configuration command : this is strict match */
+ ret = cmd_execute_command_strict(vline, vty, cmd);
+
+ /* The logic for trying parent nodes is in cmd_execute_command_real()
+ * since calling ->node_exit() correctly is a bit involved. This is
+ * also the only reason CMD_NO_LEVEL_UP exists.
+ */
+ while (!(use_daemon && ret == CMD_SUCCESS_DAEMON)
+ && !(!use_daemon && ret == CMD_ERR_NOTHING_TODO)
+ && ret != CMD_SUCCESS && ret != CMD_WARNING
+ && ret != CMD_ERR_AMBIGUOUS && ret != CMD_ERR_INCOMPLETE
+ && ret != CMD_NOT_MY_INSTANCE && ret != CMD_WARNING_CONFIG_FAILED
+ && ret != CMD_NO_LEVEL_UP)
+ ret = cmd_execute_command_real(vline, FILTER_STRICT, vty, cmd,
+ ++up_level);
+
+ if (ret == CMD_NO_LEVEL_UP)
+ ret = CMD_ERR_NO_MATCH;
+
+ if (ret != CMD_SUCCESS &&
+ ret != CMD_WARNING &&
+ ret != CMD_SUCCESS_DAEMON) {
+ struct vty_error *ve = XCALLOC(MTYPE_TMP, sizeof(*ve));
+
+ memcpy(ve->error_buf, vty->buf, VTY_BUFSIZ);
+ ve->line_num = line_num;
+ if (!vty->error)
+ vty->error = list_new();
+
+ listnode_add(vty->error, ve);
+ }
+
+ cmd_free_strvec(vline);
+
+ return ret;
+}
+
+/* Configuration make from file. */
+int config_from_file(struct vty *vty, FILE *fp, unsigned int *line_num)
+{
+ int ret, error_ret = 0;
+ *line_num = 0;
+
+ while (fgets(vty->buf, VTY_BUFSIZ, fp)) {
+ ++(*line_num);
+
+ ret = command_config_read_one_line(vty, NULL, *line_num, 0);
+
+ if (ret != CMD_SUCCESS && ret != CMD_WARNING
+ && ret != CMD_ERR_NOTHING_TODO)
+ error_ret = ret;
+ }
+
+ if (error_ret) {
+ return error_ret;
+ }
+
+ return CMD_SUCCESS;
+}
+
+/* Configuration from terminal */
+DEFUN (config_terminal,
+ config_terminal_cmd,
+ "configure [terminal]",
+ "Configuration from vty interface\n"
+ "Configuration terminal\n")
+{
+ return vty_config_enter(vty, false, false);
+}
+
+/* Enable command */
+DEFUN (enable,
+ config_enable_cmd,
+ "enable",
+ "Turn on privileged mode command\n")
+{
+ /* If enable password is NULL, change to ENABLE_NODE */
+ if ((host.enable == NULL && host.enable_encrypt == NULL)
+ || vty->type == VTY_SHELL_SERV)
+ vty->node = ENABLE_NODE;
+ else
+ vty->node = AUTH_ENABLE_NODE;
+
+ return CMD_SUCCESS;
+}
+
+/* Disable command */
+DEFUN (disable,
+ config_disable_cmd,
+ "disable",
+ "Turn off privileged mode command\n")
+{
+ if (vty->node == ENABLE_NODE)
+ vty->node = VIEW_NODE;
+ return CMD_SUCCESS;
+}
+
+/* Down vty node level. */
+DEFUN (config_exit,
+ config_exit_cmd,
+ "exit",
+ "Exit current mode and down to previous mode\n")
+{
+ cmd_exit(vty);
+ return CMD_SUCCESS;
+}
+
+static int root_on_exit(struct vty *vty)
+{
+ if (vty_shell(vty))
+ exit(0);
+ else
+ vty->status = VTY_CLOSE;
+ return 0;
+}
+
+void cmd_exit(struct vty *vty)
+{
+ struct cmd_node *cnode = vector_lookup(cmdvec, vty->node);
+
+ if (cnode->node_exit) {
+ if (!cnode->node_exit(vty))
+ return;
+ }
+ if (cnode->parent_node)
+ vty->node = cnode->parent_node;
+ if (vty->xpath_index > 0 && !cnode->no_xpath)
+ vty->xpath_index--;
+}
+
+/* ALIAS_FIXME */
+DEFUN (config_quit,
+ config_quit_cmd,
+ "quit",
+ "Exit current mode and down to previous mode\n")
+{
+ return config_exit(self, vty, argc, argv);
+}
+
+
+/* End of configuration. */
+DEFUN (config_end,
+ config_end_cmd,
+ "end",
+ "End current mode and change to enable mode.\n")
+{
+ if (vty->config) {
+ vty_config_exit(vty);
+ vty->node = ENABLE_NODE;
+ }
+ return CMD_SUCCESS;
+}
+
+/* Show version. */
+DEFUN (show_version,
+ show_version_cmd,
+ "show version",
+ SHOW_STR
+ "Displays zebra version\n")
+{
+ vty_out(vty, "%s %s (%s) on %s(%s).\n", FRR_FULL_NAME, FRR_VERSION,
+ cmd_hostname_get() ? cmd_hostname_get() : "", cmd_system_get(),
+ cmd_release_get());
+ vty_out(vty, "%s%s\n", FRR_COPYRIGHT, GIT_INFO);
+#ifdef ENABLE_VERSION_BUILD_CONFIG
+ vty_out(vty, "configured with:\n %s\n", FRR_CONFIG_ARGS);
+#endif
+ return CMD_SUCCESS;
+}
+
+/* Help display function for all node. */
+DEFUN (config_help,
+ config_help_cmd,
+ "help",
+ "Description of the interactive help system\n")
+{
+ vty_out(vty,
+ "Quagga VTY provides advanced help feature. When you need help,\n\
+anytime at the command line please press '?'.\n\
+\n\
+If nothing matches, the help list will be empty and you must backup\n\
+ until entering a '?' shows the available options.\n\
+Two styles of help are provided:\n\
+1. Full help is available when you are ready to enter a\n\
+command argument (e.g. 'show ?') and describes each possible\n\
+argument.\n\
+2. Partial help is provided when an abbreviated argument is entered\n\
+ and you want to know what arguments match the input\n\
+ (e.g. 'show me?'.)\n\n");
+ return CMD_SUCCESS;
+}
+
+static void permute(struct graph_node *start, struct vty *vty)
+{
+ static struct list *position = NULL;
+ if (!position)
+ position = list_new();
+
+ struct cmd_token *stok = start->data;
+ struct graph_node *gnn;
+ struct listnode *ln;
+
+ // recursive dfs
+ listnode_add(position, start);
+ for (unsigned int i = 0; i < vector_active(start->to); i++) {
+ struct graph_node *gn = vector_slot(start->to, i);
+ struct cmd_token *tok = gn->data;
+ if (tok->attr == CMD_ATTR_HIDDEN
+ || tok->attr == CMD_ATTR_DEPRECATED)
+ continue;
+ else if (tok->type == END_TKN || gn == start) {
+ vty_out(vty, " ");
+ for (ALL_LIST_ELEMENTS_RO(position, ln, gnn)) {
+ struct cmd_token *tt = gnn->data;
+ if (tt->type < SPECIAL_TKN)
+ vty_out(vty, " %s", tt->text);
+ }
+ if (gn == start)
+ vty_out(vty, "...");
+ vty_out(vty, "\n");
+ } else {
+ bool skip = false;
+ if (stok->type == FORK_TKN && tok->type != FORK_TKN)
+ for (ALL_LIST_ELEMENTS_RO(position, ln, gnn))
+ if (gnn == gn) {
+ skip = true;
+ break;
+ }
+ if (!skip)
+ permute(gn, vty);
+ }
+ }
+ list_delete_node(position, listtail(position));
+}
+
+static void print_cmd(struct vty *vty, const char *cmd)
+{
+ int i, j, len = strlen(cmd);
+ char buf[len + 1];
+ bool skip = false;
+
+ j = 0;
+ for (i = 0; i < len; i++) {
+ /* skip varname */
+ if (cmd[i] == '$')
+ skip = true;
+ else if (strchr(" ()<>[]{}|", cmd[i]))
+ skip = false;
+
+ if (skip)
+ continue;
+
+ if (isspace(cmd[i])) {
+ /* skip leading whitespace */
+ if (i == 0)
+ continue;
+ /* skip trailing whitespace */
+ if (i == len - 1)
+ continue;
+ /* skip all whitespace after opening brackets or pipe */
+ if (strchr("(<[{|", cmd[i - 1])) {
+ while (isspace(cmd[i + 1]))
+ i++;
+ continue;
+ }
+ /* skip repeated whitespace */
+ if (isspace(cmd[i + 1]))
+ continue;
+ /* skip whitespace before closing brackets or pipe */
+ if (strchr(")>]}|", cmd[i + 1]))
+ continue;
+ /* convert tabs to spaces */
+ if (cmd[i] == '\t') {
+ buf[j++] = ' ';
+ continue;
+ }
+ }
+
+ buf[j++] = cmd[i];
+ }
+ buf[j] = 0;
+
+ vty_out(vty, "%s\n", buf);
+}
+
+int cmd_list_cmds(struct vty *vty, int do_permute)
+{
+ struct cmd_node *node = vector_slot(cmdvec, vty->node);
+
+ if (do_permute) {
+ cmd_finalize_node(node);
+ permute(vector_slot(node->cmdgraph->nodes, 0), vty);
+ } else {
+ /* loop over all commands at this node */
+ const struct cmd_element *element = NULL;
+ for (unsigned int i = 0; i < vector_active(node->cmd_vector);
+ i++)
+ if ((element = vector_slot(node->cmd_vector, i))
+ && element->attr != CMD_ATTR_DEPRECATED
+ && element->attr != CMD_ATTR_HIDDEN) {
+ vty_out(vty, " ");
+ print_cmd(vty, element->string);
+ }
+ }
+ return CMD_SUCCESS;
+}
+
+/* Help display function for all node. */
+DEFUN (config_list,
+ config_list_cmd,
+ "list [permutations]",
+ "Print command list\n"
+ "Print all possible command permutations\n")
+{
+ return cmd_list_cmds(vty, argc == 2);
+}
+
+DEFUN (show_commandtree,
+ show_commandtree_cmd,
+ "show commandtree [permutations]",
+ SHOW_STR
+ "Show command tree\n"
+ "Permutations that we are interested in\n")
+{
+ return cmd_list_cmds(vty, argc == 3);
+}
+
+DEFUN_HIDDEN(show_cli_graph,
+ show_cli_graph_cmd,
+ "show cli graph",
+ SHOW_STR
+ "CLI reflection\n"
+ "Dump current command space as DOT graph\n")
+{
+ struct cmd_node *cn = vector_slot(cmdvec, vty->node);
+ char *dot;
+
+ cmd_finalize_node(cn);
+ dot = cmd_graph_dump_dot(cn->cmdgraph);
+
+ vty_out(vty, "%s\n", dot);
+ XFREE(MTYPE_TMP, dot);
+ return CMD_SUCCESS;
+}
+
+static int vty_write_config(struct vty *vty)
+{
+ size_t i;
+ struct cmd_node *node;
+
+ if (host.noconfig)
+ return CMD_SUCCESS;
+
+ nb_cli_show_config_prepare(running_config, false);
+
+ if (vty->type == VTY_TERM) {
+ vty_out(vty, "\nCurrent configuration:\n");
+ vty_out(vty, "!\n");
+ }
+
+ if (strcmp(frr_defaults_version(), FRR_VER_SHORT))
+ vty_out(vty, "! loaded from %s\n", frr_defaults_version());
+ vty_out(vty, "frr version %s\n", FRR_VER_SHORT);
+ vty_out(vty, "frr defaults %s\n", frr_defaults_profile());
+ vty_out(vty, "!\n");
+
+ for (i = 0; i < vector_active(cmdvec); i++)
+ if ((node = vector_slot(cmdvec, i)) && node->config_write) {
+ if ((*node->config_write)(vty))
+ vty_out(vty, "!\n");
+ }
+
+ if (vty->type == VTY_TERM) {
+ vty_out(vty, "end\n");
+ }
+
+ return CMD_SUCCESS;
+}
+
+static int file_write_config(struct vty *vty)
+{
+ int fd, dirfd;
+ char *config_file, *slash;
+ char *config_file_tmp = NULL;
+ char *config_file_sav = NULL;
+ int ret = CMD_WARNING;
+ struct vty *file_vty;
+ struct stat conf_stat;
+
+ if (host.noconfig)
+ return CMD_SUCCESS;
+
+ /* Check and see if we are operating under vtysh configuration */
+ if (host.config == NULL) {
+ vty_out(vty,
+ "Can't save to configuration file, using vtysh.\n");
+ return CMD_WARNING;
+ }
+
+ /* Get filename. */
+ config_file = host.config;
+
+#ifndef O_DIRECTORY
+#define O_DIRECTORY 0
+#endif
+ slash = strrchr(config_file, '/');
+ if (slash) {
+ char *config_dir = XSTRDUP(MTYPE_TMP, config_file);
+ config_dir[slash - config_file] = '\0';
+ dirfd = open(config_dir, O_DIRECTORY | O_RDONLY);
+ XFREE(MTYPE_TMP, config_dir);
+ } else
+ dirfd = open(".", O_DIRECTORY | O_RDONLY);
+ /* if dirfd is invalid, directory sync fails, but we're still OK */
+
+ size_t config_file_sav_sz = strlen(config_file) + strlen(CONF_BACKUP_EXT) + 1;
+ config_file_sav = XMALLOC(MTYPE_TMP, config_file_sav_sz);
+ strlcpy(config_file_sav, config_file, config_file_sav_sz);
+ strlcat(config_file_sav, CONF_BACKUP_EXT, config_file_sav_sz);
+
+
+ config_file_tmp = XMALLOC(MTYPE_TMP, strlen(config_file) + 8);
+ snprintf(config_file_tmp, strlen(config_file) + 8, "%s.XXXXXX",
+ config_file);
+
+ /* Open file to configuration write. */
+ fd = mkstemp(config_file_tmp);
+ if (fd < 0) {
+ vty_out(vty, "Can't open configuration file %s.\n",
+ config_file_tmp);
+ goto finished;
+ }
+ if (fchmod(fd, CONFIGFILE_MASK) != 0) {
+ vty_out(vty, "Can't chmod configuration file %s: %s (%d).\n",
+ config_file_tmp, safe_strerror(errno), errno);
+ goto finished;
+ }
+
+ /* Make vty for configuration file. */
+ file_vty = vty_new();
+ file_vty->wfd = fd;
+ file_vty->type = VTY_FILE;
+
+ /* Config file header print. */
+ vty_out(file_vty, "!\n! Zebra configuration saved from vty\n! ");
+ vty_time_print(file_vty, 1);
+ vty_out(file_vty, "!\n");
+ vty_write_config(file_vty);
+ vty_close(file_vty);
+
+ if (stat(config_file, &conf_stat) >= 0) {
+ if (unlink(config_file_sav) != 0)
+ if (errno != ENOENT) {
+ vty_out(vty,
+ "Can't unlink backup configuration file %s.\n",
+ config_file_sav);
+ goto finished;
+ }
+ if (link(config_file, config_file_sav) != 0) {
+ vty_out(vty,
+ "Can't backup old configuration file %s.\n",
+ config_file_sav);
+ goto finished;
+ }
+ if (dirfd >= 0)
+ fsync(dirfd);
+ }
+ if (rename(config_file_tmp, config_file) != 0) {
+ vty_out(vty, "Can't save configuration file %s.\n",
+ config_file);
+ goto finished;
+ }
+ if (dirfd >= 0)
+ fsync(dirfd);
+
+ vty_out(vty, "Configuration saved to %s\n", config_file);
+ ret = CMD_SUCCESS;
+
+finished:
+ if (ret != CMD_SUCCESS)
+ unlink(config_file_tmp);
+ if (dirfd >= 0)
+ close(dirfd);
+ XFREE(MTYPE_TMP, config_file_tmp);
+ XFREE(MTYPE_TMP, config_file_sav);
+ return ret;
+}
+
+/* Write current configuration into file. */
+
+DEFUN (config_write,
+ config_write_cmd,
+ "write [<file|memory|terminal>]",
+ "Write running configuration to memory, network, or terminal\n"
+ "Write to configuration file\n"
+ "Write configuration currently in memory\n"
+ "Write configuration to terminal\n")
+{
+ const int idx_type = 1;
+
+ // if command was 'write terminal' or 'write memory'
+ if (argc == 2 && (!strcmp(argv[idx_type]->text, "terminal"))) {
+ return vty_write_config(vty);
+ }
+
+ return file_write_config(vty);
+}
+
+/* ALIAS_FIXME for 'write <terminal|memory>' */
+DEFUN (show_running_config,
+ show_running_config_cmd,
+ "show running-config",
+ SHOW_STR
+ "running configuration (same as write terminal)\n")
+{
+ return vty_write_config(vty);
+}
+
+/* ALIAS_FIXME for 'write file' */
+DEFUN (copy_runningconf_startupconf,
+ copy_runningconf_startupconf_cmd,
+ "copy running-config startup-config",
+ "Copy configuration\n"
+ "Copy running config to... \n"
+ "Copy running config to startup config (same as write file/memory)\n")
+{
+ return file_write_config(vty);
+}
+/** -- **/
+
+/* Write startup configuration into the terminal. */
+DEFUN (show_startup_config,
+ show_startup_config_cmd,
+ "show startup-config",
+ SHOW_STR
+ "Contents of startup configuration\n")
+{
+ char buf[BUFSIZ];
+ FILE *confp;
+
+ if (host.noconfig)
+ return CMD_SUCCESS;
+ if (host.config == NULL)
+ return CMD_WARNING;
+
+ confp = fopen(host.config, "r");
+ if (confp == NULL) {
+ vty_out(vty, "Can't open configuration file [%s] due to '%s'\n",
+ host.config, safe_strerror(errno));
+ return CMD_WARNING;
+ }
+
+ while (fgets(buf, BUFSIZ, confp)) {
+ char *cp = buf;
+
+ while (*cp != '\r' && *cp != '\n' && *cp != '\0')
+ cp++;
+ *cp = '\0';
+
+ vty_out(vty, "%s\n", buf);
+ }
+
+ fclose(confp);
+
+ return CMD_SUCCESS;
+}
+
+int cmd_domainname_set(const char *domainname)
+{
+ XFREE(MTYPE_HOST, host.domainname);
+ host.domainname = domainname ? XSTRDUP(MTYPE_HOST, domainname) : NULL;
+ return CMD_SUCCESS;
+}
+
+/* Hostname configuration */
+DEFUN(config_domainname,
+ domainname_cmd,
+ "domainname WORD",
+ "Set system's domain name\n"
+ "This system's domain name\n")
+{
+ struct cmd_token *word = argv[1];
+
+ if (!isalpha((unsigned char)word->arg[0])) {
+ vty_out(vty, "Please specify string starting with alphabet\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ return cmd_domainname_set(word->arg);
+}
+
+DEFUN(config_no_domainname,
+ no_domainname_cmd,
+ "no domainname [DOMAINNAME]",
+ NO_STR
+ "Reset system's domain name\n"
+ "domain name of this router\n")
+{
+ return cmd_domainname_set(NULL);
+}
+
+int cmd_hostname_set(const char *hostname)
+{
+ XFREE(MTYPE_HOST, host.name);
+ host.name = hostname ? XSTRDUP(MTYPE_HOST, hostname) : NULL;
+ return CMD_SUCCESS;
+}
+
+/* Hostname configuration */
+DEFUN (config_hostname,
+ hostname_cmd,
+ "hostname WORD",
+ "Set system's network name\n"
+ "This system's network name\n")
+{
+ struct cmd_token *word = argv[1];
+
+ if (!isalnum((unsigned char)word->arg[0])) {
+ vty_out(vty,
+ "Please specify string starting with alphabet or number\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ /* With reference to RFC 1123 Section 2.1 */
+ if (strlen(word->arg) > HOSTNAME_LEN) {
+ vty_out(vty, "Hostname length should be less than %d chars\n",
+ HOSTNAME_LEN);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ return cmd_hostname_set(word->arg);
+}
+
+DEFUN (config_no_hostname,
+ no_hostname_cmd,
+ "no hostname [HOSTNAME]",
+ NO_STR
+ "Reset system's network name\n"
+ "Host name of this router\n")
+{
+ return cmd_hostname_set(NULL);
+}
+
+/* VTY interface password set. */
+DEFUN (config_password,
+ password_cmd,
+ "password [(8-8)] WORD",
+ "Modify the terminal connection password\n"
+ "Specifies a HIDDEN password will follow\n"
+ "The password string\n")
+{
+ int idx_8 = 1;
+ int idx_word = 2;
+ if (argc == 3) // '8' was specified
+ {
+ if (host.password)
+ XFREE(MTYPE_HOST, host.password);
+ host.password = NULL;
+ if (host.password_encrypt)
+ XFREE(MTYPE_HOST, host.password_encrypt);
+ host.password_encrypt =
+ XSTRDUP(MTYPE_HOST, argv[idx_word]->arg);
+ return CMD_SUCCESS;
+ }
+
+ if (!isalnum((unsigned char)argv[idx_8]->arg[0])) {
+ vty_out(vty,
+ "Please specify string starting with alphanumeric\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ if (host.password)
+ XFREE(MTYPE_HOST, host.password);
+ host.password = NULL;
+
+ if (host.encrypt) {
+ if (host.password_encrypt)
+ XFREE(MTYPE_HOST, host.password_encrypt);
+ host.password_encrypt =
+ XSTRDUP(MTYPE_HOST, zencrypt(argv[idx_8]->arg));
+ } else
+ host.password = XSTRDUP(MTYPE_HOST, argv[idx_8]->arg);
+
+ return CMD_SUCCESS;
+}
+
+/* VTY interface password delete. */
+DEFUN (no_config_password,
+ no_password_cmd,
+ "no password",
+ NO_STR
+ "Modify the terminal connection password\n")
+{
+ bool warned = false;
+
+ if (host.password) {
+ if (!vty_shell_serv(vty)) {
+ vty_out(vty, NO_PASSWD_CMD_WARNING);
+ warned = true;
+ }
+ XFREE(MTYPE_HOST, host.password);
+ }
+ host.password = NULL;
+
+ if (host.password_encrypt) {
+ if (!warned && !vty_shell_serv(vty))
+ vty_out(vty, NO_PASSWD_CMD_WARNING);
+ XFREE(MTYPE_HOST, host.password_encrypt);
+ }
+ host.password_encrypt = NULL;
+
+ return CMD_SUCCESS;
+}
+
+/* VTY enable password set. */
+DEFUN (config_enable_password,
+ enable_password_cmd,
+ "enable password [(8-8)] WORD",
+ "Modify enable password parameters\n"
+ "Assign the privileged level password\n"
+ "Specifies a HIDDEN password will follow\n"
+ "The HIDDEN 'enable' password string\n")
+{
+ int idx_8 = 2;
+ int idx_word = 3;
+
+ /* Crypt type is specified. */
+ if (argc == 4) {
+ if (argv[idx_8]->arg[0] == '8') {
+ if (host.enable)
+ XFREE(MTYPE_HOST, host.enable);
+ host.enable = NULL;
+
+ if (host.enable_encrypt)
+ XFREE(MTYPE_HOST, host.enable_encrypt);
+ host.enable_encrypt =
+ XSTRDUP(MTYPE_HOST, argv[idx_word]->arg);
+
+ return CMD_SUCCESS;
+ } else {
+ vty_out(vty, "Unknown encryption type.\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+ }
+
+ if (!isalnum((unsigned char)argv[idx_8]->arg[0])) {
+ vty_out(vty,
+ "Please specify string starting with alphanumeric\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ if (host.enable)
+ XFREE(MTYPE_HOST, host.enable);
+ host.enable = NULL;
+
+ /* Plain password input. */
+ if (host.encrypt) {
+ if (host.enable_encrypt)
+ XFREE(MTYPE_HOST, host.enable_encrypt);
+ host.enable_encrypt =
+ XSTRDUP(MTYPE_HOST, zencrypt(argv[idx_8]->arg));
+ } else
+ host.enable = XSTRDUP(MTYPE_HOST, argv[idx_8]->arg);
+
+ return CMD_SUCCESS;
+}
+
+/* VTY enable password delete. */
+DEFUN (no_config_enable_password,
+ no_enable_password_cmd,
+ "no enable password",
+ NO_STR
+ "Modify enable password parameters\n"
+ "Assign the privileged level password\n")
+{
+ bool warned = false;
+
+ if (host.enable) {
+ if (!vty_shell_serv(vty)) {
+ vty_out(vty, NO_PASSWD_CMD_WARNING);
+ warned = true;
+ }
+ XFREE(MTYPE_HOST, host.enable);
+ }
+ host.enable = NULL;
+
+ if (host.enable_encrypt) {
+ if (!warned && !vty_shell_serv(vty))
+ vty_out(vty, NO_PASSWD_CMD_WARNING);
+ XFREE(MTYPE_HOST, host.enable_encrypt);
+ }
+ host.enable_encrypt = NULL;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (service_password_encrypt,
+ service_password_encrypt_cmd,
+ "service password-encryption",
+ "Set up miscellaneous service\n"
+ "Enable encrypted passwords\n")
+{
+ if (host.encrypt)
+ return CMD_SUCCESS;
+
+ host.encrypt = 1;
+
+ if (host.password) {
+ if (host.password_encrypt)
+ XFREE(MTYPE_HOST, host.password_encrypt);
+ host.password_encrypt =
+ XSTRDUP(MTYPE_HOST, zencrypt(host.password));
+ }
+ if (host.enable) {
+ if (host.enable_encrypt)
+ XFREE(MTYPE_HOST, host.enable_encrypt);
+ host.enable_encrypt =
+ XSTRDUP(MTYPE_HOST, zencrypt(host.enable));
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_service_password_encrypt,
+ no_service_password_encrypt_cmd,
+ "no service password-encryption",
+ NO_STR
+ "Set up miscellaneous service\n"
+ "Enable encrypted passwords\n")
+{
+ if (!host.encrypt)
+ return CMD_SUCCESS;
+
+ host.encrypt = 0;
+
+ if (host.password_encrypt)
+ XFREE(MTYPE_HOST, host.password_encrypt);
+ host.password_encrypt = NULL;
+
+ if (host.enable_encrypt)
+ XFREE(MTYPE_HOST, host.enable_encrypt);
+ host.enable_encrypt = NULL;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (config_terminal_length,
+ config_terminal_length_cmd,
+ "terminal length (0-512)",
+ "Set terminal line parameters\n"
+ "Set number of lines on a screen\n"
+ "Number of lines on screen (0 for no pausing)\n")
+{
+ int idx_number = 2;
+
+ vty->lines = atoi(argv[idx_number]->arg);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (config_terminal_no_length,
+ config_terminal_no_length_cmd,
+ "terminal no length",
+ "Set terminal line parameters\n"
+ NO_STR
+ "Set number of lines on a screen\n")
+{
+ vty->lines = -1;
+ return CMD_SUCCESS;
+}
+
+DEFUN (service_terminal_length,
+ service_terminal_length_cmd,
+ "service terminal-length (0-512)",
+ "Set up miscellaneous service\n"
+ "System wide terminal length configuration\n"
+ "Number of lines of VTY (0 means no line control)\n")
+{
+ int idx_number = 2;
+
+ host.lines = atoi(argv[idx_number]->arg);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_service_terminal_length,
+ no_service_terminal_length_cmd,
+ "no service terminal-length [(0-512)]",
+ NO_STR
+ "Set up miscellaneous service\n"
+ "System wide terminal length configuration\n"
+ "Number of lines of VTY (0 means no line control)\n")
+{
+ host.lines = -1;
+ return CMD_SUCCESS;
+}
+
+DEFUN_HIDDEN (do_echo,
+ echo_cmd,
+ "echo MESSAGE...",
+ "Echo a message back to the vty\n"
+ "The message to echo\n")
+{
+ char *message;
+
+ vty_out(vty, "%s\n",
+ ((message = argv_concat(argv, argc, 1)) ? message : ""));
+ if (message)
+ XFREE(MTYPE_TMP, message);
+ return CMD_SUCCESS;
+}
+
+DEFUN (config_logmsg,
+ config_logmsg_cmd,
+ "logmsg <emergencies|alerts|critical|errors|warnings|notifications|informational|debugging> MESSAGE...",
+ "Send a message to enabled logging destinations\n"
+ LOG_LEVEL_DESC
+ "The message to send\n")
+{
+ int idx_log_level = 1;
+ int idx_message = 2;
+ int level;
+ char *message;
+
+ level = log_level_match(argv[idx_log_level]->arg);
+ if (level == ZLOG_DISABLED)
+ return CMD_ERR_NO_MATCH;
+
+ zlog(level, "%s",
+ ((message = argv_concat(argv, argc, idx_message)) ? message : ""));
+ if (message)
+ XFREE(MTYPE_TMP, message);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (debug_memstats,
+ debug_memstats_cmd,
+ "[no] debug memstats-at-exit",
+ NO_STR
+ DEBUG_STR
+ "Print memory type statistics at exit\n")
+{
+ debug_memstats_at_exit = !!strcmp(argv[0]->text, "no");
+ return CMD_SUCCESS;
+}
+
+int cmd_banner_motd_file(const char *file)
+{
+ int success = CMD_SUCCESS;
+ char p[PATH_MAX];
+ char *rpath;
+ char *in;
+
+ rpath = realpath(file, p);
+ if (!rpath)
+ return CMD_ERR_NO_FILE;
+ in = strstr(rpath, SYSCONFDIR);
+ if (in == rpath) {
+ XFREE(MTYPE_HOST, host.motdfile);
+ host.motdfile = XSTRDUP(MTYPE_HOST, file);
+ } else
+ success = CMD_WARNING_CONFIG_FAILED;
+
+ return success;
+}
+
+void cmd_banner_motd_line(const char *line)
+{
+ XFREE(MTYPE_HOST, host.motd);
+ host.motd = XSTRDUP(MTYPE_HOST, line);
+}
+
+DEFUN (banner_motd_file,
+ banner_motd_file_cmd,
+ "banner motd file FILE",
+ "Set banner\n"
+ "Banner for motd\n"
+ "Banner from a file\n"
+ "Filename\n")
+{
+ int idx_file = 3;
+ const char *filename = argv[idx_file]->arg;
+ int cmd = cmd_banner_motd_file(filename);
+
+ if (cmd == CMD_ERR_NO_FILE)
+ vty_out(vty, "%s does not exist\n", filename);
+ else if (cmd == CMD_WARNING_CONFIG_FAILED)
+ vty_out(vty, "%s must be in %s\n", filename, SYSCONFDIR);
+
+ return cmd;
+}
+
+DEFUN (banner_motd_line,
+ banner_motd_line_cmd,
+ "banner motd line LINE...",
+ "Set banner\n"
+ "Banner for motd\n"
+ "Banner from an input\n"
+ "Text\n")
+{
+ int idx = 0;
+ char *motd;
+
+ argv_find(argv, argc, "LINE", &idx);
+ motd = argv_concat(argv, argc, idx);
+
+ cmd_banner_motd_line(motd);
+ XFREE(MTYPE_TMP, motd);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (banner_motd_default,
+ banner_motd_default_cmd,
+ "banner motd default",
+ "Set banner string\n"
+ "Strings for motd\n"
+ "Default string\n")
+{
+ cmd_banner_motd_line(FRR_DEFAULT_MOTD);
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_banner_motd,
+ no_banner_motd_cmd,
+ "no banner motd",
+ NO_STR
+ "Set banner string\n"
+ "Strings for motd\n")
+{
+ host.motd = NULL;
+ if (host.motdfile)
+ XFREE(MTYPE_HOST, host.motdfile);
+ host.motdfile = NULL;
+ return CMD_SUCCESS;
+}
+
+DEFUN(allow_reserved_ranges, allow_reserved_ranges_cmd, "allow-reserved-ranges",
+ "Allow using IPv4 (Class E) reserved IP space\n")
+{
+ host.allow_reserved_ranges = true;
+ return CMD_SUCCESS;
+}
+
+DEFUN(no_allow_reserved_ranges, no_allow_reserved_ranges_cmd,
+ "no allow-reserved-ranges",
+ NO_STR "Allow using IPv4 (Class E) reserved IP space\n")
+{
+ host.allow_reserved_ranges = false;
+ return CMD_SUCCESS;
+}
+
+int cmd_find_cmds(struct vty *vty, struct cmd_token **argv, int argc)
+{
+ const struct cmd_node *node;
+ const struct cmd_element *cli;
+ vector clis;
+
+ regex_t exp = {};
+
+ char *pattern = argv_concat(argv, argc, 1);
+ int cr = regcomp(&exp, pattern, REG_NOSUB | REG_EXTENDED);
+ XFREE(MTYPE_TMP, pattern);
+
+ if (cr != 0) {
+ switch (cr) {
+ case REG_BADBR:
+ vty_out(vty, "%% Invalid {...} expression\n");
+ break;
+ case REG_BADRPT:
+ vty_out(vty, "%% Bad repetition operator\n");
+ break;
+ case REG_BADPAT:
+ vty_out(vty, "%% Regex syntax error\n");
+ break;
+ case REG_ECOLLATE:
+ vty_out(vty, "%% Invalid collating element\n");
+ break;
+ case REG_ECTYPE:
+ vty_out(vty, "%% Invalid character class name\n");
+ break;
+ case REG_EESCAPE:
+ vty_out(vty,
+ "%% Regex ended with escape character (\\)\n");
+ break;
+ case REG_ESUBREG:
+ vty_out(vty,
+ "%% Invalid number in \\digit construction\n");
+ break;
+ case REG_EBRACK:
+ vty_out(vty, "%% Unbalanced square brackets\n");
+ break;
+ case REG_EPAREN:
+ vty_out(vty, "%% Unbalanced parentheses\n");
+ break;
+ case REG_EBRACE:
+ vty_out(vty, "%% Unbalanced braces\n");
+ break;
+ case REG_ERANGE:
+ vty_out(vty,
+ "%% Invalid endpoint in range expression\n");
+ break;
+ case REG_ESPACE:
+ vty_out(vty, "%% Failed to compile (out of memory)\n");
+ break;
+ }
+
+ goto done;
+ }
+
+
+ for (unsigned int i = 0; i < vector_active(cmdvec); i++) {
+ node = vector_slot(cmdvec, i);
+ if (!node)
+ continue;
+ clis = node->cmd_vector;
+ for (unsigned int j = 0; j < vector_active(clis); j++) {
+ cli = vector_slot(clis, j);
+
+ if (regexec(&exp, cli->string, 0, NULL, 0) == 0) {
+ vty_out(vty, " (%s) ", node->name);
+ print_cmd(vty, cli->string);
+ }
+ }
+ }
+
+done:
+ regfree(&exp);
+ return CMD_SUCCESS;
+}
+
+DEFUN(find,
+ find_cmd,
+ "find REGEX...",
+ "Find CLI command matching a regular expression\n"
+ "Search pattern (POSIX regex)\n")
+{
+ return cmd_find_cmds(vty, argv, argc);
+}
+
+#if defined(DEV_BUILD) && defined(HAVE_SCRIPTING)
+DEFUN(script, script_cmd, "script SCRIPT FUNCTION",
+ "Test command - execute a function in a script\n"
+ "Script name (same as filename in /etc/frr/scripts/)\n"
+ "Function name (in the script)\n")
+{
+ struct prefix p;
+
+ (void)str2prefix("1.2.3.4/24", &p);
+ struct frrscript *fs = frrscript_new(argv[1]->arg);
+
+ if (frrscript_load(fs, argv[2]->arg, NULL)) {
+ vty_out(vty,
+ "/etc/frr/scripts/%s.lua or function '%s' not found\n",
+ argv[1]->arg, argv[2]->arg);
+ }
+
+ int ret = frrscript_call(fs, argv[2]->arg, ("p", &p));
+ char buf[40];
+ prefix2str(&p, buf, sizeof(buf));
+ vty_out(vty, "p: %s\n", buf);
+ vty_out(vty, "Script result: %d\n", ret);
+
+ frrscript_delete(fs);
+
+ return CMD_SUCCESS;
+}
+#endif
+
+/* Set config filename. Called from vty.c */
+void host_config_set(const char *filename)
+{
+ XFREE(MTYPE_HOST, host.config);
+ host.config = XSTRDUP(MTYPE_HOST, filename);
+}
+
+const char *host_config_get(void)
+{
+ return host.config;
+}
+
+void install_default(enum node_type node)
+{
+ _install_element(node, &config_exit_cmd);
+ _install_element(node, &config_quit_cmd);
+ _install_element(node, &config_end_cmd);
+ _install_element(node, &config_help_cmd);
+ _install_element(node, &config_list_cmd);
+ _install_element(node, &show_cli_graph_cmd);
+ _install_element(node, &find_cmd);
+
+ _install_element(node, &config_write_cmd);
+ _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.
+ *
+ * terminal = 0 -- vtysh / no logging, no config control
+ * terminal = 1 -- normal daemon
+ * terminal = -1 -- watchfrr / no logging, but minimal config control */
+void cmd_init(int terminal)
+{
+ struct utsname names;
+
+ uname(&names);
+ qobj_init();
+
+ /* register command preprocessors */
+ hook_register(cmd_execute, handle_pipe_action);
+ hook_register(cmd_execute_done, handle_pipe_action_done);
+
+ varhandlers = list_new();
+
+ /* Allocate initial top vector of commands. */
+ cmdvec = vector_init(VECTOR_MIN_SIZE);
+
+ /* Default host value settings. */
+ host.name = XSTRDUP(MTYPE_HOST, names.nodename);
+ host.system = XSTRDUP(MTYPE_HOST, names.sysname);
+ host.release = XSTRDUP(MTYPE_HOST, names.release);
+ host.version = XSTRDUP(MTYPE_HOST, names.version);
+
+#ifdef HAVE_STRUCT_UTSNAME_DOMAINNAME
+ if ((strcmp(names.domainname, "(none)") == 0))
+ host.domainname = NULL;
+ else
+ host.domainname = XSTRDUP(MTYPE_HOST, names.domainname);
+#else
+ host.domainname = NULL;
+#endif
+ host.password = NULL;
+ host.enable = NULL;
+ host.config = NULL;
+ host.noconfig = (terminal < 0);
+ host.lines = -1;
+ cmd_banner_motd_line(FRR_DEFAULT_MOTD);
+ host.motdfile = NULL;
+ host.allow_reserved_ranges = false;
+
+ /* Install top nodes. */
+ install_node(&view_node);
+ install_node(&enable_node);
+ install_node(&auth_node);
+ install_node(&auth_enable_node);
+ install_node(&config_node);
+
+ /* Each node's basic commands. */
+ install_element(VIEW_NODE, &show_version_cmd);
+ install_element(ENABLE_NODE, &show_startup_config_cmd);
+
+ if (terminal) {
+ install_element(ENABLE_NODE, &debug_memstats_cmd);
+
+ install_element(VIEW_NODE, &config_list_cmd);
+ install_element(VIEW_NODE, &config_exit_cmd);
+ install_element(VIEW_NODE, &config_quit_cmd);
+ install_element(VIEW_NODE, &config_help_cmd);
+ install_element(VIEW_NODE, &config_enable_cmd);
+ install_element(VIEW_NODE, &config_terminal_length_cmd);
+ install_element(VIEW_NODE, &config_terminal_no_length_cmd);
+ install_element(VIEW_NODE, &show_commandtree_cmd);
+ install_element(VIEW_NODE, &echo_cmd);
+ install_element(VIEW_NODE, &autocomplete_cmd);
+ install_element(VIEW_NODE, &find_cmd);
+#if defined(DEV_BUILD) && defined(HAVE_SCRIPTING)
+ install_element(VIEW_NODE, &script_cmd);
+#endif
+
+
+ install_element(ENABLE_NODE, &config_end_cmd);
+ install_element(ENABLE_NODE, &config_disable_cmd);
+ install_element(ENABLE_NODE, &config_terminal_cmd);
+ install_element(ENABLE_NODE, &copy_runningconf_startupconf_cmd);
+ install_element(ENABLE_NODE, &config_write_cmd);
+ install_element(ENABLE_NODE, &show_running_config_cmd);
+ install_element(ENABLE_NODE, &config_logmsg_cmd);
+
+ install_default(CONFIG_NODE);
+
+ thread_cmd_init();
+ workqueue_cmd_init();
+ hash_cmd_init();
+ }
+
+ install_element(CONFIG_NODE, &hostname_cmd);
+ install_element(CONFIG_NODE, &no_hostname_cmd);
+ install_element(CONFIG_NODE, &domainname_cmd);
+ install_element(CONFIG_NODE, &no_domainname_cmd);
+
+ if (terminal > 0) {
+ full_cli = true;
+
+ install_element(CONFIG_NODE, &debug_memstats_cmd);
+
+ install_element(CONFIG_NODE, &password_cmd);
+ install_element(CONFIG_NODE, &no_password_cmd);
+ install_element(CONFIG_NODE, &enable_password_cmd);
+ install_element(CONFIG_NODE, &no_enable_password_cmd);
+
+ install_element(CONFIG_NODE, &service_password_encrypt_cmd);
+ install_element(CONFIG_NODE, &no_service_password_encrypt_cmd);
+ install_element(CONFIG_NODE, &banner_motd_default_cmd);
+ install_element(CONFIG_NODE, &banner_motd_file_cmd);
+ install_element(CONFIG_NODE, &banner_motd_line_cmd);
+ install_element(CONFIG_NODE, &no_banner_motd_cmd);
+ install_element(CONFIG_NODE, &service_terminal_length_cmd);
+ install_element(CONFIG_NODE, &no_service_terminal_length_cmd);
+ install_element(CONFIG_NODE, &allow_reserved_ranges_cmd);
+ install_element(CONFIG_NODE, &no_allow_reserved_ranges_cmd);
+
+ log_cmd_init();
+ vrf_install_commands();
+ }
+
+#ifdef DEV_BUILD
+ grammar_sandbox_init();
+#endif
+}
+
+void cmd_terminate(void)
+{
+ struct cmd_node *cmd_node;
+
+ hook_unregister(cmd_execute, handle_pipe_action);
+ hook_unregister(cmd_execute_done, handle_pipe_action_done);
+
+ if (cmdvec) {
+ for (unsigned int i = 0; i < vector_active(cmdvec); i++)
+ if ((cmd_node = vector_slot(cmdvec, i)) != NULL) {
+ // deleting the graph delets the cmd_element as
+ // well
+ graph_delete_graph(cmd_node->cmdgraph);
+ vector_free(cmd_node->cmd_vector);
+ hash_clean(cmd_node->cmd_hash, NULL);
+ hash_free(cmd_node->cmd_hash);
+ cmd_node->cmd_hash = NULL;
+ }
+
+ vector_free(cmdvec);
+ cmdvec = NULL;
+ }
+
+ XFREE(MTYPE_HOST, host.name);
+ XFREE(MTYPE_HOST, host.system);
+ XFREE(MTYPE_HOST, host.release);
+ XFREE(MTYPE_HOST, host.version);
+ XFREE(MTYPE_HOST, host.domainname);
+ XFREE(MTYPE_HOST, host.password);
+ XFREE(MTYPE_HOST, host.password_encrypt);
+ XFREE(MTYPE_HOST, host.enable);
+ XFREE(MTYPE_HOST, host.enable_encrypt);
+ XFREE(MTYPE_HOST, host.motdfile);
+ XFREE(MTYPE_HOST, host.config);
+ XFREE(MTYPE_HOST, host.motd);
+
+ list_delete(&varhandlers);
+ qobj_finish();
+}
diff --git a/lib/command.h b/lib/command.h
new file mode 100644
index 0000000..70e5270
--- /dev/null
+++ b/lib/command.h
@@ -0,0 +1,656 @@
+/*
+ * Zebra configuration command interface routine
+ * Copyright (C) 1997, 98 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra 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 _ZEBRA_COMMAND_H
+#define _ZEBRA_COMMAND_H
+
+#include "vector.h"
+#include "vty.h"
+#include "lib/route_types.h"
+#include "graph.h"
+#include "memory.h"
+#include "hash.h"
+#include "command_graph.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+DECLARE_MTYPE(COMPLETION);
+
+/*
+ * From RFC 1123 (Requirements for Internet Hosts), Section 2.1 on hostnames:
+ * One aspect of host name syntax is hereby changed: the restriction on
+ * the first character is relaxed to allow either a letter or a digit.
+ * Host software MUST support this more liberal syntax.
+ *
+ * Host software MUST handle host names of up to 63 characters and
+ * SHOULD handle host names of up to 255 characters.
+ */
+#define HOSTNAME_LEN 255
+
+/* Host configuration variable */
+struct host {
+ /* Host name of this router. */
+ char *name;
+
+ /* Domainname of this router */
+ char *domainname;
+
+ /*
+ * Some extra system data that is useful
+ */
+ char *system;
+ char *release;
+ char *version;
+
+ /* Password for vty interface. */
+ char *password;
+ char *password_encrypt;
+
+ /* Enable password */
+ char *enable;
+ char *enable_encrypt;
+
+ /* System wide terminal lines. */
+ int lines;
+
+ /* config file name of this host */
+ char *config;
+ int noconfig;
+
+ /* Flags for services */
+ int advanced;
+ int encrypt;
+
+ /* Banner configuration. */
+ char *motd;
+ char *motdfile;
+
+ /* Allow using IPv4 (Class E) reserved IP space */
+ bool allow_reserved_ranges;
+};
+
+/* List of CLI nodes. Please remember to update the name array in command.c. */
+enum node_type {
+ AUTH_NODE, /* Authentication mode of vty interface. */
+ VIEW_NODE, /* View node. Default mode of vty interface. */
+ AUTH_ENABLE_NODE, /* Authentication mode for change enable. */
+ ENABLE_NODE, /* Enable node. */
+ 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. */
+ RMAP_DEBUG_NODE, /* Route-map debug node */
+ RESOLVER_DEBUG_NODE, /* Resolver debug node */
+ AAA_NODE, /* AAA node. */
+ EXTLOG_NODE, /* RFC5424 & co. extended syslog */
+ KEYCHAIN_NODE, /* Key-chain node. */
+ KEYCHAIN_KEY_NODE, /* Key-chain key node. */
+ IP_NODE, /* Static ip route node. */
+ VRF_NODE, /* VRF mode node. */
+ INTERFACE_NODE, /* Interface mode node. */
+ NH_GROUP_NODE, /* Nexthop-Group mode node. */
+ ZEBRA_NODE, /* zebra connection node. */
+ TABLE_NODE, /* rtm_table selection node. */
+ RIP_NODE, /* RIP protocol mode node. */
+ RIPNG_NODE, /* RIPng protocol mode node. */
+ BABEL_NODE, /* BABEL protocol mode node. */
+ EIGRP_NODE, /* EIGRP protocol mode node. */
+ BGP_NODE, /* BGP protocol mode which includes BGP4+ */
+ BGP_VPNV4_NODE, /* BGP MPLS-VPN PE exchange. */
+ BGP_VPNV6_NODE, /* BGP MPLS-VPN PE exchange. */
+ BGP_IPV4_NODE, /* BGP IPv4 unicast address family. */
+ BGP_IPV4M_NODE, /* BGP IPv4 multicast address family. */
+ BGP_IPV4L_NODE, /* BGP IPv4 labeled unicast address family. */
+ BGP_IPV6_NODE, /* BGP IPv6 address family */
+ BGP_IPV6M_NODE, /* BGP IPv6 multicast address family. */
+ BGP_IPV6L_NODE, /* BGP IPv6 labeled unicast address family. */
+ BGP_VRF_POLICY_NODE, /* BGP VRF policy */
+ BGP_VNC_DEFAULTS_NODE, /* BGP VNC nve defaults */
+ BGP_VNC_NVE_GROUP_NODE, /* BGP VNC nve group */
+ BGP_VNC_L2_GROUP_NODE, /* BGP VNC L2 group */
+ RFP_DEFAULTS_NODE, /* RFP defaults node */
+ BGP_EVPN_NODE, /* BGP EVPN node. */
+ BGP_SRV6_NODE, /* BGP SRv6 node. */
+ OSPF_NODE, /* OSPF protocol mode */
+ OSPF6_NODE, /* OSPF protocol for IPv6 mode */
+ LDP_NODE, /* LDP protocol mode */
+ LDP_IPV4_NODE, /* LDP IPv4 address family */
+ LDP_IPV6_NODE, /* LDP IPv6 address family */
+ LDP_IPV4_IFACE_NODE, /* LDP IPv4 Interface */
+ LDP_IPV6_IFACE_NODE, /* LDP IPv6 Interface */
+ LDP_L2VPN_NODE, /* LDP L2VPN node */
+ LDP_PSEUDOWIRE_NODE, /* LDP Pseudowire node */
+ ISIS_NODE, /* ISIS protocol mode */
+ ACCESS_NODE, /* Access list node. */
+ PREFIX_NODE, /* Prefix list node. */
+ ACCESS_IPV6_NODE, /* Access list node. */
+ ACCESS_MAC_NODE, /* MAC access list node*/
+ PREFIX_IPV6_NODE, /* Prefix list node. */
+ AS_LIST_NODE, /* AS list node. */
+ COMMUNITY_LIST_NODE, /* Community list node. */
+ COMMUNITY_ALIAS_NODE, /* Community alias node. */
+ RMAP_NODE, /* Route map node. */
+ PBRMAP_NODE, /* PBR map node. */
+ SMUX_NODE, /* SNMP configuration node. */
+ DUMP_NODE, /* Packet dump node. */
+ FORWARDING_NODE, /* IP forwarding node. */
+ PROTOCOL_NODE, /* protocol filtering node */
+ MPLS_NODE, /* MPLS config node */
+ PW_NODE, /* Pseudowire config node */
+ SEGMENT_ROUTING_NODE, /* Segment routing root node */
+ SR_TRAFFIC_ENG_NODE, /* SR Traffic Engineering node */
+ SR_SEGMENT_LIST_NODE, /* SR segment list config node */
+ SR_POLICY_NODE, /* SR policy config node */
+ SR_CANDIDATE_DYN_NODE, /* SR dynamic candidate path config node */
+ PCEP_NODE, /* PCEP node */
+ PCEP_PCE_CONFIG_NODE, /* PCE shared configuration node */
+ PCEP_PCE_NODE, /* PCE configuration node */
+ PCEP_PCC_NODE, /* PCC configuration node */
+ SRV6_NODE, /* SRv6 node */
+ SRV6_LOCS_NODE, /* SRv6 locators node */
+ SRV6_LOC_NODE, /* SRv6 locator node */
+ VTY_NODE, /* Vty node. */
+ FPM_NODE, /* Dataplane FPM node. */
+ LINK_PARAMS_NODE, /* Link-parameters node */
+ BGP_EVPN_VNI_NODE, /* BGP EVPN VNI */
+ RPKI_NODE, /* RPKI node for configuration of RPKI cache server
+ connections.*/
+ BGP_FLOWSPECV4_NODE, /* BGP IPv4 FLOWSPEC Address-Family */
+ BGP_FLOWSPECV6_NODE, /* BGP IPv6 FLOWSPEC Address-Family */
+ BFD_NODE, /* BFD protocol mode. */
+ BFD_PEER_NODE, /* BFD peer configuration mode. */
+ BFD_PROFILE_NODE, /* BFD profile configuration mode. */
+ OPENFABRIC_NODE, /* OpenFabric router configuration node */
+ VRRP_NODE, /* VRRP node */
+ BMP_NODE, /* BMP config under router bgp */
+ NODE_TYPE_MAX, /* maximum */
+};
+
+extern vector cmdvec;
+extern const struct message tokennames[];
+
+/* for external users depending on struct layout */
+#define FRR_CMD_NODE_20200416
+
+/* Node which has some commands and prompt string and configuration
+ function pointer . */
+struct cmd_node {
+ const char *name;
+
+ /* Node index. */
+ enum node_type node;
+ enum node_type parent_node;
+
+ /* Prompt character at vty interface. */
+ const char *prompt;
+
+ /* Node's configuration write function */
+ int (*config_write)(struct vty *);
+
+ /* called when leaving the node on a VTY session.
+ * return 1 if normal exit processing should happen, 0 to suppress
+ */
+ int (*node_exit)(struct vty *);
+
+ /* Node's command graph */
+ struct graph *cmdgraph;
+
+ /* Vector of this node's command list. */
+ vector cmd_vector;
+
+ /* Hashed index of command node list, for de-dupping primarily */
+ struct hash *cmd_hash;
+
+ /* set as soon as any command is in cmdgraph */
+ bool graph_built;
+
+ /* don't decrement vty->xpath_index on leaving this node */
+ bool no_xpath;
+};
+
+/* Return value of the commands. */
+#define CMD_SUCCESS 0
+#define CMD_WARNING 1
+#define CMD_ERR_NO_MATCH 2
+#define CMD_ERR_AMBIGUOUS 3
+#define CMD_ERR_INCOMPLETE 4
+#define CMD_ERR_EXEED_ARGC_MAX 5
+#define CMD_ERR_NOTHING_TODO 6
+#define CMD_COMPLETE_FULL_MATCH 7
+#define CMD_COMPLETE_MATCH 8
+#define CMD_COMPLETE_LIST_MATCH 9
+#define CMD_SUCCESS_DAEMON 10
+#define CMD_ERR_NO_FILE 11
+#define CMD_SUSPEND 12
+#define CMD_WARNING_CONFIG_FAILED 13
+#define CMD_NOT_MY_INSTANCE 14
+#define CMD_NO_LEVEL_UP 15
+#define CMD_ERR_NO_DAEMON 16
+
+/* Argc max counts. */
+#define CMD_ARGC_MAX 256
+
+/* Turn off these macros when using cpp with extract.pl */
+#ifndef VTYSH_EXTRACT_PL
+
+/* helper defines for end-user DEFUN* macros */
+#define DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attrs, dnum) \
+ static const struct cmd_element cmdname = { \
+ .string = cmdstr, \
+ .func = funcname, \
+ .doc = helpstr, \
+ .attr = attrs, \
+ .daemon = dnum, \
+ .name = #cmdname, \
+ .xref = XREF_INIT(XREFT_DEFUN, NULL, #funcname), \
+ }; \
+ XREF_LINK(cmdname.xref); \
+ /* end */
+
+
+#define DEFUN_CMD_FUNC_DECL(funcname) \
+ static int funcname(const struct cmd_element *, struct vty *, int, \
+ struct cmd_token *[]);
+
+#define DEFUN_CMD_FUNC_TEXT(funcname) \
+ static int funcname(const struct cmd_element *self \
+ __attribute__((unused)), \
+ struct vty *vty __attribute__((unused)), \
+ int argc __attribute__((unused)), \
+ struct cmd_token *argv[] __attribute__((unused)))
+
+#define DEFPY(funcname, cmdname, cmdstr, helpstr) \
+ DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0) \
+ funcdecl_##funcname
+
+#define DEFPY_NOSH(funcname, cmdname, cmdstr, helpstr) \
+ DEFPY(funcname, cmdname, cmdstr, helpstr)
+
+#define DEFPY_ATTR(funcname, cmdname, cmdstr, helpstr, attr) \
+ DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, 0) \
+ funcdecl_##funcname
+
+#define DEFPY_HIDDEN(funcname, cmdname, cmdstr, helpstr) \
+ DEFPY_ATTR(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN)
+
+#define DEFPY_YANG(funcname, cmdname, cmdstr, helpstr) \
+ DEFPY_ATTR(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_YANG)
+
+#define DEFPY_YANG_NOSH(funcname, cmdname, cmdstr, helpstr) \
+ DEFPY_YANG(funcname, cmdname, cmdstr, helpstr)
+
+#define DEFUN(funcname, cmdname, cmdstr, helpstr) \
+ DEFUN_CMD_FUNC_DECL(funcname) \
+ DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0) \
+ DEFUN_CMD_FUNC_TEXT(funcname)
+
+#define DEFUN_ATTR(funcname, cmdname, cmdstr, helpstr, attr) \
+ DEFUN_CMD_FUNC_DECL(funcname) \
+ DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, 0) \
+ DEFUN_CMD_FUNC_TEXT(funcname)
+
+#define DEFUN_HIDDEN(funcname, cmdname, cmdstr, helpstr) \
+ DEFUN_ATTR(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN)
+
+#define DEFUN_YANG(funcname, cmdname, cmdstr, helpstr) \
+ DEFUN_ATTR(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_YANG)
+
+/* DEFUN_NOSH for commands that vtysh should ignore */
+#define DEFUN_NOSH(funcname, cmdname, cmdstr, helpstr) \
+ DEFUN(funcname, cmdname, cmdstr, helpstr)
+
+#define DEFUN_YANG_NOSH(funcname, cmdname, cmdstr, helpstr) \
+ DEFUN_YANG(funcname, cmdname, cmdstr, helpstr)
+
+/* DEFSH for vtysh. */
+#define DEFSH(daemon, cmdname, cmdstr, helpstr) \
+ DEFUN_CMD_ELEMENT(NULL, cmdname, cmdstr, helpstr, 0, daemon)
+
+#define DEFSH_HIDDEN(daemon, cmdname, cmdstr, helpstr) \
+ DEFUN_CMD_ELEMENT(NULL, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN, \
+ daemon)
+
+#define DEFSH_YANG(daemon, cmdname, cmdstr, helpstr) \
+ DEFUN_CMD_ELEMENT(NULL, cmdname, cmdstr, helpstr, CMD_ATTR_YANG, daemon)
+
+/* DEFUN + DEFSH */
+#define DEFUNSH(daemon, funcname, cmdname, cmdstr, helpstr) \
+ DEFUN_CMD_FUNC_DECL(funcname) \
+ DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, daemon) \
+ DEFUN_CMD_FUNC_TEXT(funcname)
+
+/* DEFUN + DEFSH with attributes */
+#define DEFUNSH_ATTR(daemon, funcname, cmdname, cmdstr, helpstr, attr) \
+ DEFUN_CMD_FUNC_DECL(funcname) \
+ DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, daemon) \
+ DEFUN_CMD_FUNC_TEXT(funcname)
+
+#define DEFUNSH_HIDDEN(daemon, funcname, cmdname, cmdstr, helpstr) \
+ DEFUNSH_ATTR(daemon, funcname, cmdname, cmdstr, helpstr, \
+ CMD_ATTR_HIDDEN)
+
+#define DEFUNSH_DEPRECATED(daemon, funcname, cmdname, cmdstr, helpstr) \
+ DEFUNSH_ATTR(daemon, funcname, cmdname, cmdstr, helpstr, \
+ CMD_ATTR_DEPRECATED)
+
+#define DEFUNSH_YANG(daemon, funcname, cmdname, cmdstr, helpstr) \
+ DEFUNSH_ATTR(daemon, funcname, cmdname, cmdstr, helpstr, CMD_ATTR_YANG)
+
+/* ALIAS macro which define existing command's alias. */
+#define ALIAS(funcname, cmdname, cmdstr, helpstr) \
+ DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0)
+
+#define ALIAS_ATTR(funcname, cmdname, cmdstr, helpstr, attr) \
+ DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, 0)
+
+#define ALIAS_HIDDEN(funcname, cmdname, cmdstr, helpstr) \
+ DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN, \
+ 0)
+
+#define ALIAS_DEPRECATED(funcname, cmdname, cmdstr, helpstr) \
+ DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, \
+ CMD_ATTR_DEPRECATED, 0)
+
+#define ALIAS_YANG(funcname, cmdname, cmdstr, helpstr) \
+ DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_YANG, 0)
+
+#define ALIAS_SH(daemon, funcname, cmdname, cmdstr, helpstr) \
+ DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, daemon)
+
+#define ALIAS_SH_HIDDEN(daemon, funcname, cmdname, cmdstr, helpstr) \
+ DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN, \
+ daemon)
+
+#define ALIAS_SH_DEPRECATED(daemon, funcname, cmdname, cmdstr, helpstr) \
+ DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, \
+ CMD_ATTR_DEPRECATED, daemon)
+
+#endif /* VTYSH_EXTRACT_PL */
+
+/* Some macroes */
+
+/*
+ * Sometimes #defines create maximum values that
+ * need to have strings created from them that
+ * allow the parser to match against them.
+ * These macros allow that.
+ */
+#define CMD_CREATE_STR(s) CMD_CREATE_STR_HELPER(s)
+#define CMD_CREATE_STR_HELPER(s) #s
+#define CMD_RANGE_STR(a,s) "(" CMD_CREATE_STR(a) "-" CMD_CREATE_STR(s) ")"
+
+/* Common descriptions. */
+#define SHOW_STR "Show running system information\n"
+#define IP_STR "IP information\n"
+#define IPV6_STR "IPv6 information\n"
+#define SRTE_STR "SR-TE information\n"
+#define SRTE_COLOR_STR "SR-TE Color information\n"
+#define NO_STR "Negate a command or set its defaults\n"
+#define IGNORED_IN_NO_STR "Ignored value in no form\n"
+#define REDIST_STR "Redistribute information from another routing protocol\n"
+#define CLEAR_STR "Reset functions\n"
+#define RIP_STR "RIP information\n"
+#define EIGRP_STR "EIGRP information\n"
+#define BGP_STR "BGP information\n"
+#define BGP_SOFT_STR "Soft reconfig inbound and outbound updates\n"
+#define BGP_SOFT_IN_STR "Send route-refresh unless using 'soft-reconfiguration inbound'\n"
+#define BGP_SOFT_OUT_STR "Resend all outbound updates\n"
+#define BGP_SOFT_RSCLIENT_RIB_STR "Soft reconfig for rsclient RIB\n"
+#define OSPF_STR "OSPF information\n"
+#define NEIGHBOR_STR "Specify neighbor router\n"
+#define DEBUG_STR "Debugging functions\n"
+#define UNDEBUG_STR "Disable debugging functions (see also 'debug')\n"
+#define ROUTER_STR "Enable a routing process\n"
+#define AS_STR "AS number\n"
+#define MAC_STR "MAC address\n"
+#define MBGP_STR "MBGP information\n"
+#define MATCH_STR "Match values from routing table\n"
+#define SET_STR "Set values in destination routing protocol\n"
+#define OUT_STR "Filter outgoing routing updates\n"
+#define IN_STR "Filter incoming routing updates\n"
+#define V4NOTATION_STR "specify by IPv4 address notation(e.g. 0.0.0.0)\n"
+#define OSPF6_NUMBER_STR "Specify by number\n"
+#define INTERFACE_STR "Interface information\n"
+#define IFNAME_STR "Interface name(e.g. ep0)\n"
+#define IP6_STR "IPv6 Information\n"
+#define OSPF6_STR "Open Shortest Path First (OSPF) for IPv6\n"
+#define OSPF6_INSTANCE_STR "(1-65535) Instance ID\n"
+#define SECONDS_STR "Seconds\n"
+#define ROUTE_STR "Routing Table\n"
+#define PREFIX_LIST_STR "Build a prefix list\n"
+#define OSPF6_DUMP_TYPE_LIST \
+ "<neighbor|interface|area|lsa|zebra|config|dbex|spf|route|lsdb|redistribute|hook|asbr|prefix|abr>"
+#define AREA_TAG_STR "[area tag]\n"
+#define COMMUNITY_AANN_STR "Community number where AA and NN are (0-65535)\n"
+#define COMMUNITY_VAL_STR \
+ "Community number in AA:NN format (where AA and NN are (0-65535)) or local-AS|no-advertise|no-export|internet|graceful-shutdown|accept-own-nexthop|accept-own|route-filter-translated-v4|route-filter-v4|route-filter-translated-v6|route-filter-v6|llgr-stale|no-llgr|blackhole|no-peer or additive\n"
+#define MPLS_TE_STR "MPLS-TE specific commands\n"
+#define LINK_PARAMS_STR "Configure interface link parameters\n"
+#define OSPF_RI_STR "OSPF Router Information specific commands\n"
+#define PCE_STR "PCE Router Information specific commands\n"
+#define MPLS_STR "MPLS information\n"
+#define SR_STR "Segment-Routing specific commands\n"
+#define WATCHFRR_STR "watchfrr information\n"
+#define ZEBRA_STR "Zebra information\n"
+#define FILTER_LOG_STR "Filter Logs\n"
+#define BFD_PROFILE_STR "BFD profile.\n"
+#define BFD_PROFILE_NAME_STR "BFD profile name.\n"
+#define SHARP_STR "Sharp Routing Protocol\n"
+#define OSPF_GR_STR \
+ "OSPF non-stop forwarding (NSF) also known as OSPF Graceful Restart\n"
+
+#define CMD_VNI_RANGE "(1-16777215)"
+#define CONF_BACKUP_EXT ".sav"
+#define MPLS_LDP_SYNC_STR "Enable MPLS LDP-SYNC\n"
+#define NO_MPLS_LDP_SYNC_STR "Disable MPLS LDP-SYNC\n"
+#define MPLS_LDP_SYNC_HOLDDOWN_STR \
+ "Time to wait for LDP-SYNC to occur before restoring if cost\n"
+#define NO_MPLS_LDP_SYNC_HOLDDOWN_STR "holddown timer disable\n"
+
+/* Command warnings. */
+#define NO_PASSWD_CMD_WARNING \
+ "Please be aware that removing the password is a security risk and you should think twice about this command.\n"
+
+/* IPv4 only machine should not accept IPv6 address for peer's IP
+ address. So we replace VTY command string like below. */
+#define NEIGHBOR_ADDR_STR "Neighbor address\nIPv6 address\n"
+#define NEIGHBOR_ADDR_STR2 "Neighbor address\nNeighbor IPv6 address\nInterface name or neighbor tag\n"
+#define NEIGHBOR_ADDR_STR3 "Neighbor address\nIPv6 address\nInterface name\n"
+
+/* Graceful Restart cli help strings */
+#define GR_CMD "Global Graceful Restart command\n"
+#define NO_GR_CMD "Undo Global Graceful Restart command\n"
+#define GR "Global Graceful Restart - GR Mode\n"
+#define GR_DISABLE "Global Graceful Restart - Disable Mode\n"
+#define NO_GR_DISABLE "Undo Global Graceful Restart - Disable Mode\n"
+#define GR_DEBUG "Graceful Restart - Enable Debug Logs\n"
+#define GR_SHOW "Graceful Restart - Show command for Global and all neighbor mode\n"
+#define GR_NEIGHBOR_CMD "Graceful Restart command for a neighbor\n"
+#define NO_GR_NEIGHBOR_CMD "Undo Graceful Restart command for a neighbor\n"
+#define GR_NEIGHBOR_DISABLE_CMD "Graceful Restart Disable command for a neighbor\n"
+#define NO_GR_NEIGHBOR_DISABLE_CMD "Undo Graceful Restart Disable command for a neighbor\n"
+#define GR_NEIGHBOR_HELPER_CMD "Graceful Restart Helper command for a neighbor\n"
+#define NO_GR_NEIGHBOR_HELPER_CMD "Undo Graceful Restart Helper command for a neighbor\n"
+
+/* EVPN help Strings */
+#define EVPN_RT_HELP_STR "EVPN route information\n"
+#define EVPN_RT_DIST_HELP_STR "Route Distinguisher\n"
+#define EVPN_ASN_IP_HELP_STR "ASN:XX or A.B.C.D:XX\n"
+#define EVPN_TYPE_HELP_STR "Specify Route type\n"
+#define EVPN_TYPE_1_HELP_STR "EAD (Type-1) route\n"
+#define EVPN_TYPE_2_HELP_STR "MAC-IP (Type-2) route\n"
+#define EVPN_TYPE_3_HELP_STR "Multicast (Type-3) route\n"
+#define EVPN_TYPE_4_HELP_STR "Ethernet Segment (Type-4) route\n"
+#define EVPN_TYPE_5_HELP_STR "Prefix (Type-5) route\n"
+#define EVPN_TYPE_ALL_LIST "<ead|1|macip|2|multicast|3|es|4|prefix|5>"
+#define EVPN_TYPE_ALL_LIST_HELP_STR \
+ EVPN_TYPE_1_HELP_STR EVPN_TYPE_1_HELP_STR \
+ EVPN_TYPE_2_HELP_STR EVPN_TYPE_2_HELP_STR \
+ EVPN_TYPE_3_HELP_STR EVPN_TYPE_3_HELP_STR \
+ EVPN_TYPE_4_HELP_STR EVPN_TYPE_4_HELP_STR \
+ EVPN_TYPE_5_HELP_STR EVPN_TYPE_5_HELP_STR
+
+/* Describing roles */
+#define ROLE_STR \
+ "Providing transit\nRoute server\nRS client\nUsing transit\nPublic/private peering\n"
+
+/* Prototypes. */
+extern void install_node(struct cmd_node *node);
+extern void install_default(enum node_type);
+
+struct xref_install_element {
+ struct xref xref;
+
+ const struct cmd_element *cmd_element;
+ enum node_type node_type;
+};
+
+#ifndef VTYSH_EXTRACT_PL
+#define install_element(node_type_, cmd_element_) do { \
+ static const struct xref_install_element _xref \
+ __attribute__((used)) = { \
+ .xref = XREF_INIT(XREFT_INSTALL_ELEMENT, NULL, \
+ __func__), \
+ .cmd_element = cmd_element_, \
+ .node_type = node_type_, \
+ }; \
+ XREF_LINK(_xref.xref); \
+ _install_element(node_type_, cmd_element_); \
+ } while (0)
+#endif
+
+extern void _install_element(enum node_type, const struct cmd_element *);
+
+/* known issue with uninstall_element: changes to cmd_token->attr (i.e.
+ * deprecated/hidden) are not reversed. */
+extern void uninstall_element(enum node_type, const struct cmd_element *);
+
+/* construct CLI tree only when entering nodes */
+extern void cmd_defer_tree(bool val);
+
+/* finish CLI tree for node when above is true (noop otherwise) */
+extern void cmd_finalize_node(struct cmd_node *node);
+
+/* Concatenates argv[shift] through argv[argc-1] into a single NUL-terminated
+ string with a space between each element (allocated using
+ XMALLOC(MTYPE_TMP)). Returns NULL if shift >= argc. */
+extern char *argv_concat(struct cmd_token **argv, int argc, int shift);
+
+/*
+ * It is preferred that you set the index initial value
+ * to a 0. This way in the future if you modify the
+ * cli then there is no need to modify the initial
+ * value of the index
+ */
+extern int argv_find(struct cmd_token **argv, int argc, const char *text,
+ int *index);
+
+extern vector cmd_make_strvec(const char *);
+extern void cmd_free_strvec(vector);
+extern vector cmd_describe_command(vector, struct vty *, int *status);
+extern char **cmd_complete_command(vector, struct vty *, int *status);
+extern const char *cmd_prompt(enum node_type);
+extern int command_config_read_one_line(struct vty *vty,
+ const struct cmd_element **,
+ uint32_t line_num, int use_config_node);
+extern int config_from_file(struct vty *, FILE *, unsigned int *line_num);
+extern enum node_type node_parent(enum node_type);
+/*
+ * Execute command under the given vty context.
+ *
+ * vty
+ * The vty context to execute under.
+ *
+ * cmd
+ * The command string to execute.
+ *
+ * matched
+ * If non-null and a match was found, the address of the matched command is
+ * stored here. No action otherwise.
+ *
+ * vtysh
+ * Whether or not this is being called from vtysh. If this is nonzero,
+ * XXX: then what?
+ *
+ * Returns:
+ * XXX: what does it return
+ */
+extern int cmd_execute(struct vty *vty, const char *cmd,
+ const struct cmd_element **matched, int vtysh);
+extern int cmd_execute_command(vector, struct vty *,
+ const struct cmd_element **, int);
+extern int cmd_execute_command_strict(vector, struct vty *,
+ const struct cmd_element **);
+extern void cmd_init(int terminal);
+extern void cmd_init_config_callbacks(void (*start_config_cb)(void),
+ void (*end_config_cb)(void));
+extern void cmd_terminate(void);
+extern void cmd_exit(struct vty *vty);
+extern int cmd_list_cmds(struct vty *vty, int do_permute);
+extern int cmd_find_cmds(struct vty *vty, struct cmd_token **argv, int argc);
+
+extern int cmd_domainname_set(const char *domainname);
+extern int cmd_hostname_set(const char *hostname);
+extern const char *cmd_hostname_get(void);
+extern const char *cmd_domainname_get(void);
+extern const char *cmd_system_get(void);
+extern const char *cmd_release_get(void);
+extern const char *cmd_version_get(void);
+extern bool cmd_allow_reserved_ranges_get(void);
+
+/* NOT safe for general use; call this only if DEV_BUILD! */
+extern void grammar_sandbox_init(void);
+
+extern vector completions_to_vec(struct list *completions);
+
+/* Export typical functions. */
+extern const char *host_config_get(void);
+extern void host_config_set(const char *);
+
+extern void print_version(const char *);
+
+extern int cmd_banner_motd_file(const char *);
+extern void cmd_banner_motd_line(const char *line);
+
+/* struct host global, ick */
+extern struct host host;
+
+struct cmd_variable_handler {
+ const char *tokenname, *varname;
+ void (*completions)(vector out, struct cmd_token *token);
+};
+
+extern void cmd_variable_complete(struct cmd_token *token, const char *arg,
+ vector comps);
+extern void
+cmd_variable_handler_register(const struct cmd_variable_handler *cvh);
+extern char *cmd_variable_comp2str(vector comps, unsigned short cols);
+
+extern void command_setup_early_logging(const char *dest, const char *level);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ZEBRA_COMMAND_H */
diff --git a/lib/command_graph.c b/lib/command_graph.c
new file mode 100644
index 0000000..09d802e
--- /dev/null
+++ b/lib/command_graph.c
@@ -0,0 +1,562 @@
+/*
+ * CLI graph handling
+ *
+ * --
+ * Copyright (C) 2016 Cumulus Networks, Inc.
+ * Copyright (C) 1997, 98, 99 Kunihiro Ishiguro
+ * Copyright (C) 2013 by Open Source Routing.
+ * Copyright (C) 2013 by Internet Systems Consortium, Inc. ("ISC")
+ *
+ * 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 "command_graph.h"
+
+DEFINE_MTYPE_STATIC(LIB, CMD_TOKENS, "Command Tokens");
+DEFINE_MTYPE_STATIC(LIB, CMD_DESC, "Command Token Text");
+DEFINE_MTYPE_STATIC(LIB, CMD_TEXT, "Command Token Help");
+DEFINE_MTYPE(LIB, CMD_ARG, "Command Argument");
+DEFINE_MTYPE_STATIC(LIB, CMD_VAR, "Command Argument Name");
+
+struct cmd_token *cmd_token_new(enum cmd_token_type type, uint8_t attr,
+ const char *text, const char *desc)
+{
+ struct cmd_token *token =
+ XCALLOC(MTYPE_CMD_TOKENS, sizeof(struct cmd_token));
+ token->type = type;
+ token->attr = attr;
+ token->text = text ? XSTRDUP(MTYPE_CMD_TEXT, text) : NULL;
+ token->desc = desc ? XSTRDUP(MTYPE_CMD_DESC, desc) : NULL;
+ token->refcnt = 1;
+ token->arg = NULL;
+ token->allowrepeat = false;
+ token->varname = NULL;
+
+ return token;
+}
+
+void cmd_token_del(struct cmd_token *token)
+{
+ if (!token)
+ return;
+
+ XFREE(MTYPE_CMD_TEXT, token->text);
+ XFREE(MTYPE_CMD_DESC, token->desc);
+ XFREE(MTYPE_CMD_ARG, token->arg);
+ XFREE(MTYPE_CMD_VAR, token->varname);
+
+ XFREE(MTYPE_CMD_TOKENS, token);
+}
+
+struct cmd_token *cmd_token_dup(struct cmd_token *token)
+{
+ struct cmd_token *copy =
+ cmd_token_new(token->type, token->attr, NULL, NULL);
+ copy->max = token->max;
+ copy->min = token->min;
+ copy->text = token->text ? XSTRDUP(MTYPE_CMD_TEXT, token->text) : NULL;
+ copy->desc = token->desc ? XSTRDUP(MTYPE_CMD_DESC, token->desc) : NULL;
+ copy->arg = token->arg ? XSTRDUP(MTYPE_CMD_ARG, token->arg) : NULL;
+ copy->varname =
+ token->varname ? XSTRDUP(MTYPE_CMD_VAR, token->varname) : NULL;
+
+ return copy;
+}
+
+static void cmd_token_varname_do(struct cmd_token *token, const char *varname,
+ uint8_t varname_src)
+{
+ if (token->varname_src >= varname_src)
+ return;
+
+ XFREE(MTYPE_CMD_VAR, token->varname);
+
+ size_t len = strlen(varname), i;
+ token->varname = XMALLOC(MTYPE_CMD_VAR, len + 1);
+ token->varname_src = varname_src;
+
+ for (i = 0; i < len; i++)
+ switch (varname[i]) {
+ case '-':
+ case '+':
+ case '*':
+ case ':':
+ token->varname[i] = '_';
+ break;
+ default:
+ token->varname[i] = tolower((unsigned char)varname[i]);
+ }
+ token->varname[len] = '\0';
+}
+
+void cmd_token_varname_set(struct cmd_token *token, const char *varname)
+{
+ if (varname) {
+ cmd_token_varname_do(token, varname, VARNAME_EXPLICIT);
+ return;
+ }
+ if (token->type == VARIABLE_TKN) {
+ if (strcmp(token->text, "WORD") && strcmp(token->text, "NAME"))
+ cmd_token_varname_do(token, token->text, VARNAME_TEXT);
+ }
+}
+
+static void cmd_token_varname_fork(struct graph_node *node,
+ struct cmd_token *prevtoken)
+{
+ for (size_t i = 0; i < vector_active(node->to); i++) {
+ struct graph_node *next = vector_slot(node->to, i);
+ struct cmd_token *nexttoken = next->data;
+
+ if (nexttoken->type == FORK_TKN) {
+ cmd_token_varname_fork(next, prevtoken);
+ continue;
+ }
+ if (nexttoken->varname)
+ continue;
+ if (!IS_VARYING_TOKEN(nexttoken->type))
+ continue;
+
+ cmd_token_varname_do(nexttoken, prevtoken->text, VARNAME_TEXT);
+ }
+}
+
+void cmd_token_varname_join(struct graph_node *join, const char *varname)
+{
+ if (!varname)
+ return;
+
+ for (size_t i = 0; i < vector_active(join->from); i++) {
+ struct graph_node *prev = vector_slot(join->from, i);
+ struct cmd_token *token = prev->data;
+
+ if (token->type == JOIN_TKN)
+ cmd_token_varname_join(prev, varname);
+ else if (token->type < SPECIAL_TKN)
+ cmd_token_varname_do(token, varname, VARNAME_EXPLICIT);
+ }
+}
+
+void cmd_token_varname_seqappend(struct graph_node *node)
+{
+ struct graph_node *prevnode = node;
+ struct cmd_token *token = node->data;
+ struct cmd_token *prevtoken;
+
+ if (token->type == WORD_TKN)
+ return;
+
+ do {
+ if (vector_active(prevnode->from) != 1)
+ return;
+
+ prevnode = vector_slot(prevnode->from, 0);
+ prevtoken = prevnode->data;
+ } while (prevtoken->type == FORK_TKN);
+
+ if (prevtoken->type != WORD_TKN)
+ return;
+
+ if (token->type == FORK_TKN)
+ cmd_token_varname_fork(node, prevtoken);
+ else
+ cmd_token_varname_do(token, prevtoken->text, VARNAME_TEXT);
+}
+
+static bool cmd_nodes_link(struct graph_node *from, struct graph_node *to)
+{
+ for (size_t i = 0; i < vector_active(from->to); i++)
+ if (vector_slot(from->to, i) == to)
+ return true;
+ return false;
+}
+
+static bool cmd_nodes_equal(struct graph_node *ga, struct graph_node *gb);
+
+/* returns a single node to be excluded as "next" from iteration
+ * - for JOIN_TKN, never continue back to the FORK_TKN
+ * - in all other cases, don't try the node itself (in case of "...")
+ */
+static inline struct graph_node *cmd_loopstop(struct graph_node *gn)
+{
+ struct cmd_token *tok = gn->data;
+ if (tok->type == JOIN_TKN)
+ return tok->forkjoin;
+ else
+ return gn;
+}
+
+static bool cmd_subgraph_equal(struct graph_node *ga, struct graph_node *gb,
+ struct graph_node *a_join)
+{
+ size_t i, j;
+ struct graph_node *a_fork, *b_fork;
+ a_fork = cmd_loopstop(ga);
+ b_fork = cmd_loopstop(gb);
+
+ if (vector_active(ga->to) != vector_active(gb->to))
+ return false;
+ for (i = 0; i < vector_active(ga->to); i++) {
+ struct graph_node *cga = vector_slot(ga->to, i);
+
+ for (j = 0; j < vector_active(gb->to); j++) {
+ struct graph_node *cgb = vector_slot(gb->to, i);
+
+ if (cga == a_fork && cgb != b_fork)
+ continue;
+ if (cga == a_fork && cgb == b_fork)
+ break;
+
+ if (cmd_nodes_equal(cga, cgb)) {
+ if (cga == a_join)
+ break;
+ if (cmd_subgraph_equal(cga, cgb, a_join))
+ break;
+ }
+ }
+ if (j == vector_active(gb->to))
+ return false;
+ }
+ return true;
+}
+
+/* deep compare -- for FORK_TKN, the entire subgraph is compared.
+ * this is what's needed since we're not currently trying to partially
+ * merge subgraphs */
+static bool cmd_nodes_equal(struct graph_node *ga, struct graph_node *gb)
+{
+ struct cmd_token *a = ga->data, *b = gb->data;
+
+ if (a->type != b->type || a->allowrepeat != b->allowrepeat)
+ return false;
+ if (a->type < SPECIAL_TKN && strcmp(a->text, b->text))
+ return false;
+ /* one a ..., the other not. */
+ if (cmd_nodes_link(ga, ga) != cmd_nodes_link(gb, gb))
+ return false;
+ if (!a->varname != !b->varname)
+ return false;
+ if (a->varname && strcmp(a->varname, b->varname))
+ return false;
+
+ switch (a->type) {
+ case RANGE_TKN:
+ return a->min == b->min && a->max == b->max;
+
+ case FORK_TKN:
+ /* one is keywords, the other just option or selector ... */
+ if (cmd_nodes_link(a->forkjoin, ga)
+ != cmd_nodes_link(b->forkjoin, gb))
+ return false;
+ if (cmd_nodes_link(ga, a->forkjoin)
+ != cmd_nodes_link(gb, b->forkjoin))
+ return false;
+ return cmd_subgraph_equal(ga, gb, a->forkjoin);
+
+ default:
+ return true;
+ }
+}
+
+static void cmd_fork_bump_attr(struct graph_node *gn, struct graph_node *join,
+ uint8_t attr)
+{
+ size_t i;
+ struct cmd_token *tok = gn->data;
+ struct graph_node *stop = cmd_loopstop(gn);
+
+ tok->attr = attr;
+ for (i = 0; i < vector_active(gn->to); i++) {
+ struct graph_node *next = vector_slot(gn->to, i);
+ if (next == stop || next == join)
+ continue;
+ cmd_fork_bump_attr(next, join, attr);
+ }
+}
+
+/* move an entire subtree from the temporary graph resulting from
+ * parse() into the permanent graph for the command node.
+ *
+ * this touches rather deeply into the graph code unfortunately.
+ */
+static void cmd_reparent_tree(struct graph *fromgraph, struct graph *tograph,
+ struct graph_node *node)
+{
+ struct graph_node *stop = cmd_loopstop(node);
+ size_t i;
+
+ for (i = 0; i < vector_active(fromgraph->nodes); i++)
+ if (vector_slot(fromgraph->nodes, i) == node) {
+ /* agressive iteration punching through subgraphs - may
+ * hit some
+ * nodes twice. reparent only if found on old graph */
+ vector_unset(fromgraph->nodes, i);
+ vector_set(tograph->nodes, node);
+ break;
+ }
+
+ for (i = 0; i < vector_active(node->to); i++) {
+ struct graph_node *next = vector_slot(node->to, i);
+ if (next != stop)
+ cmd_reparent_tree(fromgraph, tograph, next);
+ }
+}
+
+static void cmd_free_recur(struct graph *graph, struct graph_node *node,
+ struct graph_node *stop)
+{
+ struct graph_node *next, *nstop;
+
+ for (size_t i = vector_active(node->to); i; i--) {
+ next = vector_slot(node->to, i - 1);
+ if (next == stop)
+ continue;
+ nstop = cmd_loopstop(next);
+ if (nstop != next)
+ cmd_free_recur(graph, next, nstop);
+ cmd_free_recur(graph, nstop, stop);
+ }
+ graph_delete_node(graph, node);
+}
+
+static void cmd_free_node(struct graph *graph, struct graph_node *node)
+{
+ struct cmd_token *tok = node->data;
+ if (tok->type == JOIN_TKN)
+ cmd_free_recur(graph, tok->forkjoin, node);
+ graph_delete_node(graph, node);
+}
+
+/* recursive graph merge. call with
+ * old ~= new
+ * (which holds true for old == START_TKN, new == START_TKN)
+ */
+static void cmd_merge_nodes(struct graph *oldgraph, struct graph *newgraph,
+ struct graph_node *old, struct graph_node *new,
+ int direction)
+{
+ struct cmd_token *tok;
+ struct graph_node *old_skip, *new_skip;
+ old_skip = cmd_loopstop(old);
+ new_skip = cmd_loopstop(new);
+
+ assert(direction == 1 || direction == -1);
+
+ tok = old->data;
+ tok->refcnt += direction;
+
+ size_t j, i;
+ for (j = 0; j < vector_active(new->to); j++) {
+ struct graph_node *cnew = vector_slot(new->to, j);
+ if (cnew == new_skip)
+ continue;
+
+ for (i = 0; i < vector_active(old->to); i++) {
+ struct graph_node *cold = vector_slot(old->to, i);
+ if (cold == old_skip)
+ continue;
+
+ if (cmd_nodes_equal(cold, cnew)) {
+ struct cmd_token *told = cold->data,
+ *tnew = cnew->data;
+
+ if (told->type == END_TKN) {
+ if (direction < 0) {
+ graph_delete_node(
+ oldgraph,
+ vector_slot(cold->to,
+ 0));
+ graph_delete_node(oldgraph,
+ cold);
+ } else
+ /* force no-match handling to
+ * install END_TKN */
+ i = vector_active(old->to);
+ break;
+ }
+
+ /* the entire fork compared as equal, we
+ * continue after it. */
+ if (told->type == FORK_TKN) {
+ if (tnew->attr < told->attr
+ && direction > 0)
+ cmd_fork_bump_attr(
+ cold, told->forkjoin,
+ tnew->attr);
+ /* XXX: no reverse bump on uninstall */
+ told = (cold = told->forkjoin)->data;
+ tnew = (cnew = tnew->forkjoin)->data;
+ }
+ if (tnew->attr < told->attr)
+ told->attr = tnew->attr;
+
+ cmd_merge_nodes(oldgraph, newgraph, cold, cnew,
+ direction);
+ break;
+ }
+ }
+ /* nothing found => add new to old */
+ if (i == vector_active(old->to) && direction > 0) {
+ graph_remove_edge(new, cnew);
+
+ cmd_reparent_tree(newgraph, oldgraph, cnew);
+
+ graph_add_edge(old, cnew);
+ }
+ }
+
+ if (!tok->refcnt)
+ cmd_free_node(oldgraph, old);
+}
+
+void cmd_graph_merge(struct graph *old, struct graph *new, int direction)
+{
+ assert(vector_active(old->nodes) >= 1);
+ assert(vector_active(new->nodes) >= 1);
+
+ cmd_merge_nodes(old, new, vector_slot(old->nodes, 0),
+ vector_slot(new->nodes, 0), direction);
+}
+
+void cmd_graph_names(struct graph *graph)
+{
+ struct graph_node *start;
+
+ assert(vector_active(graph->nodes) >= 1);
+ start = vector_slot(graph->nodes, 0);
+
+ /* apply varname on initial "[no]" */
+ do {
+ if (vector_active(start->to) != 1)
+ break;
+
+ struct graph_node *first = vector_slot(start->to, 0);
+ struct cmd_token *tok = first->data;
+ /* looking for an option with 2 choices, nothing or "no" */
+ if (tok->type != FORK_TKN || vector_active(first->to) != 2)
+ break;
+
+ struct graph_node *next0 = vector_slot(first->to, 0);
+ struct graph_node *next1 = vector_slot(first->to, 1);
+ /* one needs to be empty */
+ if (next0 != tok->forkjoin && next1 != tok->forkjoin)
+ break;
+
+ struct cmd_token *tok0 = next0->data;
+ struct cmd_token *tok1 = next1->data;
+ /* the other one needs to be "no" (only one will match here) */
+ if ((tok0->type == WORD_TKN && !strcmp(tok0->text, "no")))
+ cmd_token_varname_do(tok0, "no", VARNAME_AUTO);
+ if ((tok1->type == WORD_TKN && !strcmp(tok1->text, "no")))
+ cmd_token_varname_do(tok1, "no", VARNAME_AUTO);
+ } while (0);
+}
+
+#ifndef BUILDING_CLIPPY
+
+#include "command.h"
+#include "log.h"
+
+void cmd_graph_node_print_cb(struct graph_node *gn, struct buffer *buf)
+{
+ static bool wasend;
+
+ char nbuf[512];
+ struct cmd_token *tok = gn->data;
+ const char *color;
+
+ if (wasend) {
+ wasend = false;
+ return;
+ }
+
+ if (tok->type == END_TKN) {
+ wasend = true;
+ return;
+ }
+
+ snprintf(nbuf, sizeof(nbuf), " n%p [ shape=box, label=<", gn);
+ buffer_putstr(buf, nbuf);
+ snprintf(nbuf, sizeof(nbuf), "<b>%s</b>",
+ lookup_msg(tokennames, tok->type, NULL));
+ buffer_putstr(buf, nbuf);
+ if (tok->attr == CMD_ATTR_DEPRECATED)
+ buffer_putstr(buf, " (d)");
+ else if (tok->attr == CMD_ATTR_HIDDEN)
+ buffer_putstr(buf, " (h)");
+ if (tok->text) {
+ if (tok->type == WORD_TKN)
+ snprintf(
+ nbuf, sizeof(nbuf),
+ "<br/>\"<font color=\"#0055ff\" point-size=\"11\"><b>%s</b></font>\"",
+ tok->text);
+ else
+ snprintf(nbuf, sizeof(nbuf), "<br/>%s", tok->text);
+ buffer_putstr(buf, nbuf);
+ }
+
+ switch (tok->type) {
+ case START_TKN:
+ color = "#ccffcc";
+ break;
+ case FORK_TKN:
+ color = "#aaddff";
+ break;
+ case JOIN_TKN:
+ color = "#ddaaff";
+ break;
+ case NEG_ONLY_TKN:
+ color = "#ffddaa";
+ break;
+ case WORD_TKN:
+ color = "#ffffff";
+ break;
+ default:
+ color = "#ffffff";
+ break;
+ }
+ snprintf(nbuf, sizeof(nbuf),
+ ">, style = filled, fillcolor = \"%s\" ];\n", color);
+ buffer_putstr(buf, nbuf);
+
+ for (unsigned int i = 0; i < vector_active(gn->to); i++) {
+ struct graph_node *adj = vector_slot(gn->to, i);
+
+ if (((struct cmd_token *)adj->data)->type == END_TKN) {
+ snprintf(nbuf, sizeof(nbuf), " n%p -> end%p;\n", gn,
+ adj);
+ buffer_putstr(buf, nbuf);
+ snprintf(
+ nbuf, sizeof(nbuf),
+ " end%p [ shape=box, label=<end>, style = filled, fillcolor = \"#ffddaa\" ];\n",
+ adj);
+ } else
+ snprintf(nbuf, sizeof(nbuf), " n%p -> n%p;\n", gn,
+ adj);
+
+ buffer_putstr(buf, nbuf);
+ }
+}
+
+char *cmd_graph_dump_dot(struct graph *cmdgraph)
+{
+ struct graph_node *start = vector_slot(cmdgraph->nodes, 0);
+
+ return graph_dump_dot(cmdgraph, start, cmd_graph_node_print_cb);
+}
+
+#endif /* BUILDING_CLIPPY */
diff --git a/lib/command_graph.h b/lib/command_graph.h
new file mode 100644
index 0000000..ed4da6a
--- /dev/null
+++ b/lib/command_graph.h
@@ -0,0 +1,159 @@
+/*
+ * CLI graph handling
+ *
+ * --
+ * Copyright (C) 2016 Cumulus Networks, Inc.
+ * Copyright (C) 1997, 98, 99 Kunihiro Ishiguro
+ * Copyright (C) 2013 by Open Source Routing.
+ * Copyright (C) 2013 by Internet Systems Consortium, Inc. ("ISC")
+ *
+ * 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_COMMAND_GRAPH_H
+#define _FRR_COMMAND_GRAPH_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "memory.h"
+#include "vector.h"
+#include "graph.h"
+#include "xref.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+DECLARE_MTYPE(CMD_ARG);
+
+struct vty;
+
+/**
+ * Types for tokens.
+ *
+ * The type determines what kind of data the token can match (in the
+ * matching use case) or hold (in the argv use case).
+ */
+/* clang-format off */
+enum cmd_token_type {
+ WORD_TKN, // words
+ VARIABLE_TKN, // almost anything
+ RANGE_TKN, // integer range
+ IPV4_TKN, // IPV4 addresses
+ IPV4_PREFIX_TKN, // IPV4 network prefixes
+ IPV6_TKN, // IPV6 prefixes
+ IPV6_PREFIX_TKN, // IPV6 network prefixes
+ MAC_TKN, // Ethernet address
+ MAC_PREFIX_TKN, // Ethernet address w/ CIDR mask
+
+ /* plumbing types */
+ FORK_TKN, // marks subgraph beginning
+ JOIN_TKN, // marks subgraph end
+ START_TKN, // first token in line
+ END_TKN, // last token in line
+ NEG_ONLY_TKN, // filter token, match if "no ..." command
+
+ SPECIAL_TKN = FORK_TKN,
+};
+/* clang-format on */
+
+#define IS_VARYING_TOKEN(x) ((x) >= VARIABLE_TKN && (x) < FORK_TKN)
+
+/* Command attributes */
+enum { CMD_ATTR_NORMAL,
+ CMD_ATTR_DEPRECATED,
+ CMD_ATTR_HIDDEN,
+ CMD_ATTR_YANG,
+};
+
+enum varname_src {
+ VARNAME_NONE = 0,
+ VARNAME_AUTO,
+ VARNAME_VAR,
+ VARNAME_TEXT,
+ VARNAME_EXPLICIT,
+};
+
+/* Command token struct. */
+struct cmd_token {
+ enum cmd_token_type type; // token type
+ uint8_t attr; // token attributes
+ bool allowrepeat; // matcher allowed to match token repetitively?
+ uint8_t varname_src;
+ uint32_t refcnt;
+
+ char *text; // token text
+ char *desc; // token description
+ long long min, max; // for ranges
+ char *arg; // user input that matches this token
+ char *varname;
+
+ struct graph_node *forkjoin; // paired FORK/JOIN for JOIN/FORK
+};
+
+/* Structure of command element. */
+struct cmd_element {
+ const char *string; /* Command specification by string. */
+ const char *doc; /* Documentation of this command. */
+ int daemon; /* Daemon to which this command belong. */
+ uint32_t attr; /* Command attributes */
+
+ /* handler function for command */
+ int (*func)(const struct cmd_element *, struct vty *, int,
+ struct cmd_token *[]);
+
+ const char *name; /* symbol name for debugging */
+ struct xref xref;
+};
+
+/* text for <cr> command */
+#define CMD_CR_TEXT "<cr>"
+
+/* memory management for cmd_token */
+extern struct cmd_token *cmd_token_new(enum cmd_token_type, uint8_t attr,
+ const char *text, const char *desc);
+extern struct cmd_token *cmd_token_dup(struct cmd_token *);
+extern void cmd_token_del(struct cmd_token *);
+extern void cmd_token_varname_set(struct cmd_token *token, const char *varname);
+extern void cmd_token_varname_seqappend(struct graph_node *n);
+extern void cmd_token_varname_join(struct graph_node *n, const char *varname);
+
+extern void cmd_graph_parse(struct graph *graph, const struct cmd_element *cmd);
+extern void cmd_graph_names(struct graph *graph);
+extern void cmd_graph_merge(struct graph *old, struct graph *n,
+ int direction);
+/*
+ * Print callback for DOT dumping.
+ *
+ * See graph.h for more details.
+ */
+extern void cmd_graph_node_print_cb(struct graph_node *gn, struct buffer *buf);
+/*
+ * Dump command graph to DOT.
+ *
+ * cmdgraph
+ * A command graph to dump
+ *
+ * Returns:
+ * String allocated with MTYPE_TMP representing this graph
+ */
+char *cmd_graph_dump_dot(struct graph *cmdgraph);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRR_COMMAND_GRAPH_H */
diff --git a/lib/command_lex.l b/lib/command_lex.l
new file mode 100644
index 0000000..ec366ce
--- /dev/null
+++ b/lib/command_lex.l
@@ -0,0 +1,104 @@
+/*
+ * Command format string lexer for CLI backend.
+ *
+ * --
+ * Copyright (C) 2015 Cumulus Networks, Inc.
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 GNU Zebra; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+%top{
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+}
+%{
+/* ignore flex generated code in static analyzer */
+#ifndef __clang_analyzer__
+
+/* ignore harmless bugs in old versions of flex */
+#pragma GCC diagnostic ignored "-Wsign-compare"
+#pragma GCC diagnostic ignored "-Wmissing-prototypes"
+
+#include "lib/command_parse.h"
+
+#define YY_USER_ACTION yylloc->last_column += yyleng;
+#define LOC_STEP do { if (yylloc) { \
+ yylloc->first_column = yylloc->last_column; \
+ yylloc->first_line = yylloc->last_line; \
+ } } while(0)
+%}
+
+IPV4 A\.B\.C\.D
+IPV4_PREFIX A\.B\.C\.D\/M
+IPV6 X:X::X:X
+IPV6_PREFIX X:X::X:X\/M
+MAC X:X:X:X:X:X
+MAC_PREFIX X:X:X:X:X:X\/M
+VARIABLE [A-Z][-_A-Z:0-9]+
+WORD (\-|\+)?[a-zA-Z0-9\*][-+_a-zA-Z0-9\*]*
+NUMBER (\-|\+)?[0-9]{1,20}
+RANGE \({NUMBER}[ ]?\-[ ]?{NUMBER}\)
+
+/* yytext shall be a pointer */
+%pointer
+%option noyywrap
+%option nounput
+%option noinput
+%option outfile="lib/command_lex.c"
+%option header-file="lib/command_lex.h"
+%option prefix="cmd_yy"
+%option reentrant
+%option bison-bridge
+%option bison-locations
+
+%%
+%{
+ LOC_STEP;
+%}
+
+[ \t]+ LOC_STEP /* ignore whitespace */;
+{IPV4} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return IPV4;}
+{IPV4_PREFIX} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return IPV4_PREFIX;}
+{IPV6} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return IPV6;}
+{IPV6_PREFIX} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return IPV6_PREFIX;}
+{MAC} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return MAC;}
+{MAC_PREFIX} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return MAC_PREFIX;}
+{VARIABLE} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return VARIABLE;}
+{WORD} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return WORD;}
+{RANGE} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return RANGE;}
+!\[ {yylval->string = NULL; return EXCL_BRACKET;}
+. {return yytext[0];}
+%%
+
+static YY_BUFFER_STATE buffer;
+
+void set_lexer_string (yyscan_t *scn, const char *string)
+{
+ *scn = NULL;
+ yylex_init(scn);
+ buffer = yy_scan_string (string, *scn);
+}
+
+void cleanup_lexer (yyscan_t *scn)
+{
+ // yy_delete_buffer (buffer, *scn);
+ yylex_destroy(*scn);
+}
+
+#endif /* __clang_analyzer__ */
diff --git a/lib/command_match.c b/lib/command_match.c
new file mode 100644
index 0000000..f221e0a
--- /dev/null
+++ b/lib/command_match.c
@@ -0,0 +1,1060 @@
+/*
+ * Input matching routines for CLI backend.
+ *
+ * --
+ * Copyright (C) 2016 Cumulus Networks, Inc.
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 "command_match.h"
+#include "memory.h"
+
+DEFINE_MTYPE_STATIC(LIB, CMD_MATCHSTACK, "Command Match Stack");
+
+#ifdef TRACE_MATCHER
+#define TM 1
+#else
+#define TM 0
+#endif
+
+#define trace_matcher(...) \
+ do { \
+ if (TM) \
+ fprintf(stderr, __VA_ARGS__); \
+ } while (0);
+
+/* matcher helper prototypes */
+static int add_nexthops(struct list *, struct graph_node *,
+ struct graph_node **, size_t, bool);
+
+static enum matcher_rv command_match_r(struct graph_node *, vector,
+ unsigned int, struct graph_node **,
+ struct list **);
+
+static int score_precedence(enum cmd_token_type);
+
+static enum match_type min_match_level(enum cmd_token_type);
+
+static void del_arglist(struct list *);
+
+static struct cmd_token *disambiguate_tokens(struct cmd_token *,
+ struct cmd_token *, char *);
+
+static struct list *disambiguate(struct list *, struct list *, vector,
+ unsigned int);
+
+int compare_completions(const void *, const void *);
+
+/* token matcher prototypes */
+static enum match_type match_token(struct cmd_token *, char *);
+
+static enum match_type match_ipv4(const char *);
+
+static enum match_type match_ipv4_prefix(const char *);
+
+static enum match_type match_ipv6_prefix(const char *, bool);
+
+static enum match_type match_range(struct cmd_token *, const char *);
+
+static enum match_type match_word(struct cmd_token *, const char *);
+
+static enum match_type match_variable(struct cmd_token *, const char *);
+
+static enum match_type match_mac(const char *, bool);
+
+static bool is_neg(vector vline, size_t idx)
+{
+ if (idx >= vector_active(vline) || !vector_slot(vline, idx))
+ return false;
+ return !strcmp(vector_slot(vline, idx), "no");
+}
+
+enum matcher_rv command_match(struct graph *cmdgraph, vector vline,
+ struct list **argv, const struct cmd_element **el)
+{
+ struct graph_node *stack[CMD_ARGC_MAX];
+ enum matcher_rv status;
+ *argv = NULL;
+
+ // prepend a dummy token to match that pesky start node
+ vector vvline = vector_init(vline->alloced + 1);
+ vector_set_index(vvline, 0, XSTRDUP(MTYPE_TMP, "dummy"));
+ memcpy(vvline->index + 1, vline->index,
+ sizeof(void *) * vline->alloced);
+ vvline->active = vline->active + 1;
+
+ struct graph_node *start = vector_slot(cmdgraph->nodes, 0);
+ status = command_match_r(start, vvline, 0, stack, argv);
+ if (status == MATCHER_OK) { // successful match
+ struct listnode *head = listhead(*argv);
+ struct listnode *tail = listtail(*argv);
+
+ assert(head);
+ assert(tail);
+
+ // delete dummy start node
+ cmd_token_del((struct cmd_token *)head->data);
+ list_delete_node(*argv, head);
+
+ // get cmd_element out of list tail
+ *el = listgetdata(tail);
+ list_delete_node(*argv, tail);
+
+ // now argv is an ordered list of cmd_token matching the user
+ // input, with each cmd_token->arg holding the corresponding
+ // input
+ assert(*el);
+ } else if (*argv) {
+ del_arglist(*argv);
+ *argv = NULL;
+ }
+
+ if (!*el) {
+ trace_matcher("No match\n");
+ } else {
+ trace_matcher("Matched command\n->string %s\n->desc %s\n",
+ (*el)->string, (*el)->doc);
+ }
+
+ // free the leader token we alloc'd
+ XFREE(MTYPE_TMP, vector_slot(vvline, 0));
+ // free vector
+ vector_free(vvline);
+
+ return status;
+}
+
+/**
+ * Builds an argument list given a DFA and a matching input line.
+ *
+ * First the function determines if the node it is passed matches the first
+ * token of input. If it does not, it returns NULL (MATCHER_NO_MATCH). If it
+ * does match, then it saves the input token as the head of an argument list.
+ *
+ * The next step is to see if there is further input in the input line. If
+ * there is not, the current node's children are searched to see if any of them
+ * are leaves (type END_TKN). If this is the case, then the bottom of the
+ * recursion stack has been reached, the leaf is pushed onto the argument list,
+ * the current node is pushed, and the resulting argument list is
+ * returned (MATCHER_OK). If it is not the case, NULL is returned, indicating
+ * that there is no match for the input along this path (MATCHER_INCOMPLETE).
+ *
+ * If there is further input, then the function recurses on each of the current
+ * node's children, passing them the input line minus the token that was just
+ * matched. For each child, the return value of the recursive call is
+ * inspected. If it is null, then there is no match for the input along the
+ * subgraph headed by that child. If it is not null, then there is at least one
+ * input match in that subgraph (more on this in a moment).
+ *
+ * If a recursive call on a child returns a non-null value, then it has matched
+ * the input given it on the subgraph that starts with that child. However, due
+ * to the flexibility of the grammar, it is sometimes the case that two or more
+ * child graphs match the same input (two or more of the recursive calls have
+ * non-NULL return values). This is not a valid state, since only one true
+ * match is possible. In order to resolve this conflict, the function keeps a
+ * reference to the child node that most specifically matches the input. This
+ * is done by assigning each node type a precedence. If a child is found to
+ * match the remaining input, then the precedence values of the current
+ * best-matching child and this new match are compared. The node with higher
+ * precedence is kept, and the other match is discarded. Due to the recursive
+ * nature of this function, it is only necessary to compare the precedence of
+ * immediate children, since all subsequent children will already have been
+ * disambiguated in this way.
+ *
+ * In the event that two children are found to match with the same precedence,
+ * then the input is ambiguous for the passed cmd_element and NULL is returned.
+ *
+ * @param[in] start the start node.
+ * @param[in] vline the vectorized input line.
+ * @param[in] n the index of the first input token.
+ * @return A linked list of n elements. The first n-1 elements are pointers to
+ * struct cmd_token and represent the sequence of tokens matched by the input.
+ * The ->arg field of each token points to a copy of the input matched on it.
+ * The final nth element is a pointer to struct cmd_element, which is the
+ * command that was matched.
+ *
+ * If no match was found, the return value is NULL.
+ */
+static enum matcher_rv command_match_r(struct graph_node *start, vector vline,
+ unsigned int n,
+ struct graph_node **stack,
+ struct list **currbest)
+{
+ assert(n < vector_active(vline));
+
+ enum matcher_rv status = MATCHER_NO_MATCH;
+
+ // get the minimum match level that can count as a full match
+ struct cmd_token *copy, *token = start->data;
+ enum match_type minmatch = min_match_level(token->type);
+
+ /* check history/stack of tokens
+ * this disallows matching the same one more than once if there is a
+ * circle in the graph (used for keyword arguments) */
+ if (n == CMD_ARGC_MAX)
+ return MATCHER_NO_MATCH;
+ if (!token->allowrepeat)
+ for (size_t s = 0; s < n; s++)
+ if (stack[s] == start)
+ return MATCHER_NO_MATCH;
+
+ // get the current operating input token
+ char *input_token = vector_slot(vline, n);
+
+#ifdef TRACE_MATCHER
+ fprintf(stdout, "\"%-20s\" matches \"%-30s\" ? ", input_token,
+ token->text);
+ enum match_type mt = match_token(token, input_token);
+ fprintf(stdout, "type: %d ", token->type);
+ fprintf(stdout, "min: %d - ", minmatch);
+ switch (mt) {
+ case trivial_match:
+ fprintf(stdout, "trivial_match ");
+ break;
+ case no_match:
+ fprintf(stdout, "no_match ");
+ break;
+ case partly_match:
+ fprintf(stdout, "partly_match ");
+ break;
+ case exact_match:
+ fprintf(stdout, "exact_match ");
+ break;
+ }
+ if (mt >= minmatch)
+ fprintf(stdout, " MATCH");
+ fprintf(stdout, "\n");
+#endif
+
+ // if we don't match this node, die
+ if (match_token(token, input_token) < minmatch)
+ return MATCHER_NO_MATCH;
+
+ stack[n] = start;
+
+ // pointers for iterating linklist
+ struct listnode *ln;
+ struct graph_node *gn;
+
+ // get all possible nexthops
+ struct list *next = list_new();
+ add_nexthops(next, start, NULL, 0, is_neg(vline, 1));
+
+ // determine the best match
+ for (ALL_LIST_ELEMENTS_RO(next, ln, gn)) {
+ // if we've matched all input we're looking for END_TKN
+ if (n + 1 == vector_active(vline)) {
+ struct cmd_token *tok = gn->data;
+ if (tok->type == END_TKN) {
+ // if more than one END_TKN in the follow set
+ if (*currbest) {
+ status = MATCHER_AMBIGUOUS;
+ break;
+ } else {
+ status = MATCHER_OK;
+ }
+ *currbest = list_new();
+ // node should have one child node with the
+ // element
+ struct graph_node *leaf =
+ vector_slot(gn->to, 0);
+ // last node in the list will hold the
+ // cmd_element; this is important because
+ // list_delete() expects that all nodes have
+ // the same data type, so when deleting this
+ // list the last node must be manually deleted
+ struct cmd_element *el = leaf->data;
+ listnode_add(*currbest, el);
+ (*currbest)->del =
+ (void (*)(void *)) & cmd_token_del;
+ // do not break immediately; continue walking
+ // through the follow set to ensure that there
+ // is exactly one END_TKN
+ }
+ continue;
+ }
+
+ // else recurse on candidate child node
+ struct list *result = NULL;
+ enum matcher_rv rstat =
+ command_match_r(gn, vline, n + 1, stack, &result);
+
+ // save the best match
+ if (result && *currbest) {
+ // pick the best of two matches
+ struct list *newbest =
+ disambiguate(*currbest, result, vline, n + 1);
+
+ // current best and result are ambiguous
+ if (!newbest)
+ status = MATCHER_AMBIGUOUS;
+ // current best is still the best, but ambiguous
+ else if (newbest == *currbest
+ && status == MATCHER_AMBIGUOUS)
+ status = MATCHER_AMBIGUOUS;
+ // result is better, but also ambiguous
+ else if (newbest == result
+ && rstat == MATCHER_AMBIGUOUS)
+ status = MATCHER_AMBIGUOUS;
+ // one or the other is superior and not ambiguous
+ else
+ status = MATCHER_OK;
+
+ // delete the unnecessary result
+ struct list *todelete =
+ ((newbest && newbest == result) ? *currbest
+ : result);
+ del_arglist(todelete);
+
+ *currbest = newbest ? newbest : *currbest;
+ } else if (result) {
+ status = rstat;
+ *currbest = result;
+ } else if (!*currbest) {
+ status = MAX(rstat, status);
+ }
+ }
+ if (*currbest) {
+ // copy token, set arg and prepend to currbest
+ token = start->data;
+ copy = cmd_token_dup(token);
+ copy->arg = XSTRDUP(MTYPE_CMD_ARG, input_token);
+ listnode_add_before(*currbest, (*currbest)->head, copy);
+ } else if (n + 1 == vector_active(vline) && status == MATCHER_NO_MATCH)
+ status = MATCHER_INCOMPLETE;
+
+ // cleanup
+ list_delete(&next);
+
+ return status;
+}
+
+static void stack_del(void *val)
+{
+ XFREE(MTYPE_CMD_MATCHSTACK, val);
+}
+
+enum matcher_rv command_complete(struct graph *graph, vector vline,
+ struct list **completions)
+{
+ // pointer to next input token to match
+ char *input_token;
+ bool neg = is_neg(vline, 0);
+
+ struct list *
+ current =
+ list_new(), // current nodes to match input token against
+ *next = list_new(); // possible next hops after current input
+ // token
+ current->del = next->del = stack_del;
+
+ // pointers used for iterating lists
+ struct graph_node **gstack, **newstack;
+ struct listnode *node;
+
+ // add all children of start node to list
+ struct graph_node *start = vector_slot(graph->nodes, 0);
+ add_nexthops(next, start, &start, 0, neg);
+
+ unsigned int idx;
+ for (idx = 0; idx < vector_active(vline) && next->count > 0; idx++) {
+ list_delete(&current);
+ current = next;
+ next = list_new();
+ next->del = stack_del;
+
+ input_token = vector_slot(vline, idx);
+
+ int exact_match_exists = 0;
+ for (ALL_LIST_ELEMENTS_RO(current, node, gstack))
+ if (!exact_match_exists)
+ exact_match_exists =
+ (match_token(gstack[0]->data,
+ input_token)
+ == exact_match);
+ else
+ break;
+
+ for (ALL_LIST_ELEMENTS_RO(current, node, gstack)) {
+ struct cmd_token *token = gstack[0]->data;
+
+ if (token->attr == CMD_ATTR_HIDDEN
+ || token->attr == CMD_ATTR_DEPRECATED)
+ continue;
+
+ enum match_type minmatch = min_match_level(token->type);
+ trace_matcher("\"%s\" matches \"%s\" (%d) ? ",
+ input_token, token->text, token->type);
+
+ unsigned int last_token =
+ (vector_active(vline) - 1 == idx);
+ enum match_type matchtype =
+ match_token(token, input_token);
+ switch (matchtype) {
+ // occurs when last token is whitespace
+ case trivial_match:
+ trace_matcher("trivial_match\n");
+ assert(last_token);
+ newstack = XMALLOC(MTYPE_CMD_MATCHSTACK,
+ sizeof(struct graph_node *));
+ /* we're not recursing here, just the first
+ * element is OK */
+ newstack[0] = gstack[0];
+ listnode_add(next, newstack);
+ break;
+ case partly_match:
+ trace_matcher("trivial_match\n");
+ if (exact_match_exists && !last_token)
+ break;
+ /* fallthru */
+ case exact_match:
+ trace_matcher("exact_match\n");
+ if (last_token) {
+ newstack = XMALLOC(
+ MTYPE_CMD_MATCHSTACK,
+ sizeof(struct graph_node *));
+ /* same as above, not recursing on this
+ */
+ newstack[0] = gstack[0];
+ listnode_add(next, newstack);
+ } else if (matchtype >= minmatch)
+ add_nexthops(next, gstack[0], gstack,
+ idx + 1, neg);
+ break;
+ default:
+ trace_matcher("no_match\n");
+ break;
+ }
+ }
+ }
+
+ /* Variable summary
+ * -----------------------------------------------------------------
+ * token = last input token processed
+ * idx = index in `command` of last token processed
+ * current = set of all transitions from the previous input token
+ * next = set of all nodes reachable from all nodes in `matched`
+ */
+
+ enum matcher_rv mrv = idx == vector_active(vline) && next->count
+ ? MATCHER_OK
+ : MATCHER_NO_MATCH;
+
+ *completions = NULL;
+ if (!MATCHER_ERROR(mrv)) {
+ // extract cmd_token into list
+ *completions = list_new();
+ for (ALL_LIST_ELEMENTS_RO(next, node, gstack)) {
+ listnode_add(*completions, gstack[0]->data);
+ }
+ }
+
+ list_delete(&current);
+ list_delete(&next);
+
+ return mrv;
+}
+
+/**
+ * Adds all children that are reachable by one parser hop to the given list.
+ * special tokens except END_TKN are treated as transparent.
+ *
+ * @param[in] list to add the nexthops to
+ * @param[in] node to start calculating nexthops from
+ * @param[in] stack listing previously visited nodes, if non-NULL.
+ * @param[in] stackpos how many valid entries are in stack
+ * @return the number of children added to the list
+ *
+ * NB: non-null "stack" means that new stacks will be added to "list" as
+ * output, instead of direct node pointers!
+ */
+static int add_nexthops(struct list *list, struct graph_node *node,
+ struct graph_node **stack, size_t stackpos, bool neg)
+{
+ int added = 0;
+ struct graph_node *child;
+ struct graph_node **nextstack;
+ for (unsigned int i = 0; i < vector_active(node->to); i++) {
+ child = vector_slot(node->to, i);
+ size_t j;
+ struct cmd_token *token = child->data;
+ if (!token->allowrepeat && stack) {
+ for (j = 0; j < stackpos; j++)
+ if (child == stack[j])
+ break;
+ if (j != stackpos)
+ continue;
+ }
+
+ if (token->type == NEG_ONLY_TKN && !neg)
+ continue;
+
+ if (token->type >= SPECIAL_TKN && token->type != END_TKN) {
+ added +=
+ add_nexthops(list, child, stack, stackpos, neg);
+ } else {
+ if (stack) {
+ nextstack = XMALLOC(
+ MTYPE_CMD_MATCHSTACK,
+ (stackpos + 1)
+ * sizeof(struct graph_node *));
+ nextstack[0] = child;
+ memcpy(nextstack + 1, stack,
+ stackpos * sizeof(struct graph_node *));
+
+ listnode_add(list, nextstack);
+ } else
+ listnode_add(list, child);
+ added++;
+ }
+ }
+
+ return added;
+}
+
+/**
+ * Determines the node types for which a partial match may count as a full
+ * match. Enables command abbrevations.
+ *
+ * @param[in] type node type
+ * @return minimum match level needed to for a token to fully match
+ */
+static enum match_type min_match_level(enum cmd_token_type type)
+{
+ switch (type) {
+ // anything matches a start node, for the sake of recursion
+ case START_TKN:
+ return no_match;
+ // allowing words to partly match enables command abbreviation
+ case WORD_TKN:
+ return partly_match;
+ default:
+ return exact_match;
+ }
+}
+
+/**
+ * Assigns precedence scores to node types.
+ *
+ * @param[in] type node type to score
+ * @return precedence score
+ */
+static int score_precedence(enum cmd_token_type type)
+{
+ switch (type) {
+ // some of these are mutually exclusive, so they share
+ // the same precedence value
+ case IPV4_TKN:
+ case IPV4_PREFIX_TKN:
+ case IPV6_TKN:
+ case IPV6_PREFIX_TKN:
+ case MAC_TKN:
+ case MAC_PREFIX_TKN:
+ case RANGE_TKN:
+ return 2;
+ case WORD_TKN:
+ return 3;
+ case VARIABLE_TKN:
+ return 4;
+ default:
+ return 10;
+ }
+}
+
+/**
+ * Picks the better of two possible matches for a token.
+ *
+ * @param[in] first candidate node matching token
+ * @param[in] second candidate node matching token
+ * @param[in] token the token being matched
+ * @return the best-matching node, or NULL if the two are entirely ambiguous
+ */
+static struct cmd_token *disambiguate_tokens(struct cmd_token *first,
+ struct cmd_token *second,
+ char *input_token)
+{
+ // if the types are different, simply go off of type precedence
+ if (first->type != second->type) {
+ int firstprec = score_precedence(first->type);
+ int secndprec = score_precedence(second->type);
+ if (firstprec != secndprec)
+ return firstprec < secndprec ? first : second;
+ else
+ return NULL;
+ }
+
+ // if they're the same, return the more exact match
+ enum match_type fmtype = match_token(first, input_token);
+ enum match_type smtype = match_token(second, input_token);
+ if (fmtype != smtype)
+ return fmtype > smtype ? first : second;
+
+ return NULL;
+}
+
+/**
+ * Picks the better of two possible matches for an input line.
+ *
+ * @param[in] first candidate list of cmd_token matching vline
+ * @param[in] second candidate list of cmd_token matching vline
+ * @param[in] vline the input line being matched
+ * @param[in] n index into vline to start comparing at
+ * @return the best-matching list, or NULL if the two are entirely ambiguous
+ */
+static struct list *disambiguate(struct list *first, struct list *second,
+ vector vline, unsigned int n)
+{
+ assert(first != NULL);
+ assert(second != NULL);
+ // doesn't make sense for these to be inequal length
+ assert(first->count == second->count);
+ assert(first->count == vector_active(vline) - n + 1);
+
+ struct listnode *fnode = listhead_unchecked(first),
+ *snode = listhead_unchecked(second);
+ struct cmd_token *ftok = listgetdata(fnode), *stok = listgetdata(snode),
+ *best = NULL;
+
+ // compare each token, if one matches better use that one
+ for (unsigned int i = n; i < vector_active(vline); i++) {
+ char *token = vector_slot(vline, i);
+ if ((best = disambiguate_tokens(ftok, stok, token)))
+ return best == ftok ? first : second;
+ fnode = listnextnode(fnode);
+ snode = listnextnode(snode);
+ ftok = listgetdata(fnode);
+ stok = listgetdata(snode);
+ }
+
+ return NULL;
+}
+
+/*
+ * Deletion function for arglist.
+ *
+ * Since list->del for arglists expects all listnode->data to hold cmd_token,
+ * but arglists have cmd_element as the data for the tail, this function
+ * manually deletes the tail before deleting the rest of the list as usual.
+ *
+ * The cmd_element at the end is *not* a copy. It is the one and only.
+ *
+ * @param list the arglist to delete
+ */
+static void del_arglist(struct list *list)
+{
+ // manually delete last node
+ struct listnode *tail = listtail(list);
+ tail->data = NULL;
+ list_delete_node(list, tail);
+
+ // delete the rest of the list as usual
+ list_delete(&list);
+}
+
+/*---------- token level matching functions ----------*/
+
+static enum match_type match_token(struct cmd_token *token, char *input_token)
+{
+ // nothing trivially matches everything
+ if (!input_token)
+ return trivial_match;
+
+ switch (token->type) {
+ case WORD_TKN:
+ return match_word(token, input_token);
+ case IPV4_TKN:
+ return match_ipv4(input_token);
+ case IPV4_PREFIX_TKN:
+ return match_ipv4_prefix(input_token);
+ case IPV6_TKN:
+ return match_ipv6_prefix(input_token, false);
+ case IPV6_PREFIX_TKN:
+ return match_ipv6_prefix(input_token, true);
+ case RANGE_TKN:
+ return match_range(token, input_token);
+ case VARIABLE_TKN:
+ return match_variable(token, input_token);
+ case MAC_TKN:
+ return match_mac(input_token, false);
+ case MAC_PREFIX_TKN:
+ return match_mac(input_token, true);
+ case END_TKN:
+ default:
+ return no_match;
+ }
+}
+
+#define IPV4_ADDR_STR "0123456789."
+#define IPV4_PREFIX_STR "0123456789./"
+
+static enum match_type match_ipv4(const char *str)
+{
+ const char *sp;
+ int dots = 0, nums = 0;
+ char buf[4];
+
+ for (;;) {
+ memset(buf, 0, sizeof(buf));
+ sp = str;
+ while (*str != '\0') {
+ if (*str == '.') {
+ if (dots >= 3)
+ return no_match;
+
+ if (*(str + 1) == '.')
+ return no_match;
+
+ if (*(str + 1) == '\0')
+ return partly_match;
+
+ dots++;
+ break;
+ }
+ if (!isdigit((unsigned char)*str))
+ return no_match;
+
+ str++;
+ }
+
+ if (str - sp > 3)
+ return no_match;
+
+ memcpy(buf, sp, str - sp);
+
+ int v = atoi(buf);
+
+ if (v > 255)
+ return no_match;
+ if (v > 0 && buf[0] == '0')
+ return no_match;
+
+ nums++;
+
+ if (*str == '\0')
+ break;
+
+ str++;
+ }
+
+ if (nums < 4)
+ return partly_match;
+
+ return exact_match;
+}
+
+static enum match_type match_ipv4_prefix(const char *str)
+{
+ const char *sp;
+ int dots = 0;
+ char buf[4];
+
+ for (;;) {
+ memset(buf, 0, sizeof(buf));
+ sp = str;
+ while (*str != '\0' && *str != '/') {
+ if (*str == '.') {
+ if (dots == 3)
+ return no_match;
+
+ if (*(str + 1) == '.' || *(str + 1) == '/')
+ return no_match;
+
+ if (*(str + 1) == '\0')
+ return partly_match;
+
+ dots++;
+ break;
+ }
+
+ if (!isdigit((unsigned char)*str))
+ return no_match;
+
+ str++;
+ }
+
+ if (str - sp > 3)
+ return no_match;
+
+ memcpy(buf, sp, str - sp);
+
+ int v = atoi(buf);
+
+ if (v > 255)
+ return no_match;
+ if (v > 0 && buf[0] == '0')
+ return no_match;
+
+ if (dots == 3) {
+ if (*str == '/') {
+ if (*(str + 1) == '\0')
+ return partly_match;
+
+ str++;
+ break;
+ } else if (*str == '\0')
+ return partly_match;
+ }
+
+ if (*str == '\0')
+ return partly_match;
+
+ str++;
+ }
+
+ sp = str;
+ while (*str != '\0') {
+ if (!isdigit((unsigned char)*str))
+ return no_match;
+
+ str++;
+ }
+
+ if (atoi(sp) > IPV4_MAX_BITLEN)
+ return no_match;
+
+ return exact_match;
+}
+
+
+#define IPV6_ADDR_STR "0123456789abcdefABCDEF:."
+#define IPV6_PREFIX_STR "0123456789abcdefABCDEF:./"
+#define STATE_START 1
+#define STATE_COLON 2
+#define STATE_DOUBLE 3
+#define STATE_ADDR 4
+#define STATE_DOT 5
+#define STATE_SLASH 6
+#define STATE_MASK 7
+
+static enum match_type match_ipv6_prefix(const char *str, bool prefix)
+{
+ int state = STATE_START;
+ int colons = 0, nums = 0, double_colon = 0;
+ int mask;
+ const char *sp = NULL, *start = str;
+ char *endptr = NULL;
+
+ if (str == NULL)
+ return partly_match;
+
+ if (strspn(str, prefix ? IPV6_PREFIX_STR : IPV6_ADDR_STR)
+ != strlen(str))
+ return no_match;
+
+ while (*str != '\0' && state != STATE_MASK) {
+ switch (state) {
+ case STATE_START:
+ if (*str == ':') {
+ if (*(str + 1) != ':' && *(str + 1) != '\0')
+ return no_match;
+ colons--;
+ state = STATE_COLON;
+ } else {
+ sp = str;
+ state = STATE_ADDR;
+ }
+
+ continue;
+ case STATE_COLON:
+ colons++;
+ if (*(str + 1) == '/')
+ return no_match;
+ else if (*(str + 1) == ':')
+ state = STATE_DOUBLE;
+ else {
+ sp = str + 1;
+ state = STATE_ADDR;
+ }
+ break;
+ case STATE_DOUBLE:
+ if (double_colon)
+ return no_match;
+
+ if (*(str + 1) == ':')
+ return no_match;
+ else {
+ if (*(str + 1) != '\0' && *(str + 1) != '/')
+ colons++;
+ sp = str + 1;
+
+ if (*(str + 1) == '/')
+ state = STATE_SLASH;
+ else
+ state = STATE_ADDR;
+ }
+
+ double_colon++;
+ nums += 1;
+ break;
+ case STATE_ADDR:
+ if (*(str + 1) == ':' || *(str + 1) == '.'
+ || *(str + 1) == '\0' || *(str + 1) == '/') {
+ if (str - sp > 3)
+ return no_match;
+
+ for (; sp <= str; sp++)
+ if (*sp == '/')
+ return no_match;
+
+ nums++;
+
+ if (*(str + 1) == ':')
+ state = STATE_COLON;
+ else if (*(str + 1) == '.') {
+ if (colons || double_colon)
+ state = STATE_DOT;
+ else
+ return no_match;
+ } else if (*(str + 1) == '/')
+ state = STATE_SLASH;
+ }
+ break;
+ case STATE_DOT:
+ state = STATE_ADDR;
+ break;
+ case STATE_SLASH:
+ if (*(str + 1) == '\0')
+ return partly_match;
+
+ state = STATE_MASK;
+ break;
+ default:
+ break;
+ }
+
+ if (nums > 11)
+ return no_match;
+
+ if (colons > 7)
+ return no_match;
+
+ str++;
+ }
+
+ if (!prefix) {
+ struct sockaddr_in6 sin6_dummy;
+ int ret = inet_pton(AF_INET6, start, &sin6_dummy.sin6_addr);
+ return ret == 1 ? exact_match : partly_match;
+ }
+
+ if (state < STATE_MASK)
+ return partly_match;
+
+ mask = strtol(str, &endptr, 10);
+ if (*endptr != '\0')
+ return no_match;
+
+ if (mask < 0 || mask > IPV6_MAX_BITLEN)
+ return no_match;
+
+ return exact_match;
+}
+
+static enum match_type match_range(struct cmd_token *token, const char *str)
+{
+ assert(token->type == RANGE_TKN);
+
+ char *endptr = NULL;
+ long long val;
+
+ val = strtoll(str, &endptr, 10);
+ if (*endptr != '\0')
+ return no_match;
+
+ if (val < token->min || val > token->max)
+ return no_match;
+ else
+ return exact_match;
+}
+
+static enum match_type match_word(struct cmd_token *token, const char *word)
+{
+ assert(token->type == WORD_TKN);
+
+ // if the passed token is 0 length, partly match
+ if (!strlen(word))
+ return partly_match;
+
+ // if the passed token is strictly a prefix of the full word, partly
+ // match
+ if (strlen(word) < strlen(token->text))
+ return !strncmp(token->text, word, strlen(word)) ? partly_match
+ : no_match;
+
+ // if they are the same length and exactly equal, exact match
+ else if (strlen(word) == strlen(token->text))
+ return !strncmp(token->text, word, strlen(word)) ? exact_match
+ : no_match;
+
+ return no_match;
+}
+
+static enum match_type match_variable(struct cmd_token *token, const char *word)
+{
+ assert(token->type == VARIABLE_TKN);
+ return exact_match;
+}
+
+#define MAC_CHARS "ABCDEFabcdef0123456789:"
+
+static enum match_type match_mac(const char *word, bool prefix)
+{
+ /* 6 2-digit hex numbers separated by 5 colons */
+ size_t mac_explen = 6 * 2 + 5;
+ /* '/' + 2-digit integer */
+ size_t mask_len = 1 + 2;
+ unsigned int i;
+ char *eptr;
+ unsigned int maskval;
+
+ /* length check */
+ if (strlen(word) > mac_explen + (prefix ? mask_len : 0))
+ return no_match;
+
+ /* address check */
+ for (i = 0; i < mac_explen; i++) {
+ if (word[i] == '\0' || !strchr(MAC_CHARS, word[i]))
+ break;
+ if (((i + 1) % 3 == 0) != (word[i] == ':'))
+ return no_match;
+ }
+
+ /* incomplete address */
+ if (i < mac_explen && word[i] == '\0')
+ return partly_match;
+ else if (i < mac_explen)
+ return no_match;
+
+ /* mask check */
+ if (prefix && word[i] == '/') {
+ if (word[++i] == '\0')
+ return partly_match;
+
+ maskval = strtoul(&word[i], &eptr, 10);
+ if (*eptr != '\0' || maskval > 48)
+ return no_match;
+ } else if (prefix && word[i] == '\0') {
+ return partly_match;
+ } else if (prefix) {
+ return no_match;
+ }
+
+ return exact_match;
+}
diff --git a/lib/command_match.h b/lib/command_match.h
new file mode 100644
index 0000000..0488cc1
--- /dev/null
+++ b/lib/command_match.h
@@ -0,0 +1,109 @@
+/*
+ * Input matching routines for CLI backend.
+ *
+ * --
+ * Copyright (C) 2016 Cumulus Networks, Inc.
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 _ZEBRA_COMMAND_MATCH_H
+#define _ZEBRA_COMMAND_MATCH_H
+
+#include "graph.h"
+#include "linklist.h"
+#include "command.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* These definitions exist in command.c in the current engine but should be
+ * relocated here in the new engine
+ */
+enum cmd_filter_type { FILTER_RELAXED, FILTER_STRICT };
+
+/* matcher result value */
+enum matcher_rv {
+ MATCHER_NO_MATCH,
+ MATCHER_INCOMPLETE,
+ MATCHER_AMBIGUOUS,
+ MATCHER_OK,
+};
+
+/* completion match types */
+enum match_type {
+ trivial_match, // the input is null
+ no_match, // the input does not match
+ partly_match, // the input matches but is incomplete
+ exact_match // the input matches and is complete
+};
+
+/* Defines which matcher_rv values constitute an error. Should be used with
+ * matcher_rv return values to do basic error checking.
+ */
+#define MATCHER_ERROR(matcher_rv) \
+ ((matcher_rv) == MATCHER_INCOMPLETE \
+ || (matcher_rv) == MATCHER_NO_MATCH \
+ || (matcher_rv) == MATCHER_AMBIGUOUS)
+
+/**
+ * Attempt to find an exact command match for a line of user input.
+ *
+ * @param[in] cmdgraph command graph to match against
+ * @param[in] vline vectorized input string
+ * @param[out] argv pointer to argument list if successful match, NULL
+ * otherwise. The elements of this list are pointers to struct cmd_token
+ * and represent the sequence of tokens matched by the input. The ->arg
+ * field of each token points to a copy of the input matched on it. These
+ * may be safely deleted or modified.
+ * @param[out] element pointer to matched cmd_element if successful match,
+ * or NULL when MATCHER_ERROR(rv) is true. The cmd_element may *not* be
+ * safely deleted or modified; it is the instance initialized on startup.
+ * @return matcher status
+ */
+enum matcher_rv command_match(struct graph *cmdgraph, vector vline,
+ struct list **argv,
+ const struct cmd_element **element);
+
+/**
+ * Compiles possible completions for a given line of user input.
+ *
+ * @param[in] start the start node of the DFA to match against
+ * @param[in] vline vectorized input string
+ * @param[out] completions pointer to list of cmd_token representing
+ * acceptable next inputs, or NULL when MATCHER_ERROR(rv) is true.
+ * The elements of this list are pointers to struct cmd_token and take on a
+ * variety of forms depending on the passed vline. If the last element in vline
+ * is NULL, all previous elements are considered to be complete words (the case
+ * when a space is the last token of the line) and completions are generated
+ * based on what could follow that input. If the last element in vline is not
+ * NULL and each sequential element matches the corresponding tokens of one or
+ * more commands exactly (e.g. 'encapv4' and not 'en') the same result is
+ * generated. If the last element is not NULL and the best possible match is a
+ * partial match, then the result generated will be all possible continuations
+ * of that element (e.g. 'encapv4', 'encapv6', etc for input 'en').
+ * @return matcher status
+ */
+enum matcher_rv command_complete(struct graph *cmdgraph, vector vline,
+ struct list **completions);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ZEBRA_COMMAND_MATCH_H */
diff --git a/lib/command_parse.y b/lib/command_parse.y
new file mode 100644
index 0000000..35c1196
--- /dev/null
+++ b/lib/command_parse.y
@@ -0,0 +1,543 @@
+/*
+ * Command format string parser for CLI backend.
+ *
+ * --
+ * Copyright (C) 2016 Cumulus Networks, Inc.
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 GNU Zebra; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+%{
+// compile with debugging facilities
+#define YYDEBUG 1
+%}
+
+%locations
+/* define parse.error verbose */
+%define api.pure full
+/* define api.prefix {cmd_yy} */
+
+/* names for generated header and parser files */
+%defines "lib/command_parse.h"
+%output "lib/command_parse.c"
+
+/* note: code blocks are output in order, to both .c and .h:
+ * 1. %code requires
+ * 2. %union + bison forward decls
+ * 3. %code provides
+ * command_lex.h needs to be included at 3.; it needs the union and YYSTYPE.
+ * struct parser_ctx is needed for the bison forward decls.
+ */
+%code requires {
+ #include "config.h"
+
+ #include <stdbool.h>
+ #include <stdlib.h>
+ #include <string.h>
+ #include <ctype.h>
+
+ #include "command_graph.h"
+ #include "log.h"
+
+ DECLARE_MTYPE(LEX);
+
+ #define YYSTYPE CMD_YYSTYPE
+ #define YYLTYPE CMD_YYLTYPE
+ struct parser_ctx;
+
+ /* subgraph semantic value */
+ struct subgraph {
+ struct graph_node *start, *end;
+ };
+}
+
+%union {
+ long long number;
+ char *string;
+ struct graph_node *node;
+ struct subgraph subgraph;
+}
+
+%code provides {
+ #ifndef FLEX_SCANNER
+ #include "lib/command_lex.h"
+ #endif
+
+ extern void set_lexer_string (yyscan_t *scn, const char *string);
+ extern void cleanup_lexer (yyscan_t *scn);
+
+ struct parser_ctx {
+ yyscan_t scanner;
+
+ const struct cmd_element *el;
+
+ struct graph *graph;
+ struct graph_node *currnode;
+
+ /* pointers to copy of command docstring */
+ char *docstr_start, *docstr;
+ };
+}
+
+/* union types for lexed tokens */
+%token <string> WORD
+%token <string> IPV4
+%token <string> IPV4_PREFIX
+%token <string> IPV6
+%token <string> IPV6_PREFIX
+%token <string> VARIABLE
+%token <string> RANGE
+%token <string> MAC
+%token <string> MAC_PREFIX
+
+/* special syntax, value is irrelevant */
+%token <string> EXCL_BRACKET
+
+/* union types for parsed rules */
+%type <node> start
+%type <node> literal_token
+%type <node> placeholder_token
+%type <node> placeholder_token_real
+%type <node> simple_token
+%type <subgraph> selector
+%type <subgraph> selector_token
+%type <subgraph> selector_token_seq
+%type <subgraph> selector_seq_seq
+
+%type <string> varname_token
+
+%code {
+
+ /* bison declarations */
+ void
+ cmd_yyerror (CMD_YYLTYPE *locp, struct parser_ctx *ctx, char const *msg);
+
+ /* helper functions for parser */
+ static const char *
+ doc_next (struct parser_ctx *ctx);
+
+ static struct graph_node *
+ new_token_node (struct parser_ctx *,
+ enum cmd_token_type type,
+ const char *text,
+ const char *doc);
+
+ static void
+ terminate_graph (CMD_YYLTYPE *locp, struct parser_ctx *ctx,
+ struct graph_node *);
+
+ static void
+ cleanup (struct parser_ctx *ctx);
+
+ static void loopcheck(struct parser_ctx *ctx, struct subgraph *sg);
+
+ #define scanner ctx->scanner
+}
+
+/* yyparse parameters */
+%lex-param {yyscan_t scanner}
+%parse-param {struct parser_ctx *ctx}
+
+/* called automatically before yyparse */
+%initial-action {
+ /* clear state pointers */
+ ctx->currnode = vector_slot (ctx->graph->nodes, 0);
+
+ /* copy docstring and keep a pointer to the copy */
+ if (ctx->el->doc)
+ {
+ // allocate a new buffer, making room for a flag
+ size_t length = (size_t) strlen (ctx->el->doc) + 2;
+ ctx->docstr = malloc (length);
+ memcpy (ctx->docstr, ctx->el->doc, strlen (ctx->el->doc));
+ // set the flag so doc_next knows when to print a warning
+ ctx->docstr[length - 2] = 0x03;
+ // null terminate
+ ctx->docstr[length - 1] = 0x00;
+ }
+ ctx->docstr_start = ctx->docstr;
+}
+
+%%
+
+start:
+ cmd_token_seq
+{
+ // tack on the command element
+ terminate_graph (&@1, ctx, ctx->currnode);
+}
+| cmd_token_seq placeholder_token '.' '.' '.'
+{
+ if ((ctx->currnode = graph_add_edge (ctx->currnode, $2)) != $2)
+ graph_delete_node (ctx->graph, $2);
+
+ ((struct cmd_token *)ctx->currnode->data)->allowrepeat = 1;
+
+ // adding a node as a child of itself accepts any number
+ // of the same token, which is what we want for variadics
+ graph_add_edge (ctx->currnode, ctx->currnode);
+
+ // tack on the command element
+ terminate_graph (&@1, ctx, ctx->currnode);
+}
+;
+
+varname_token: '$' WORD
+{
+ $$ = $2;
+}
+| /* empty */
+{
+ $$ = NULL;
+}
+;
+
+cmd_token_seq:
+ /* empty */
+| cmd_token_seq cmd_token
+;
+
+cmd_token:
+ simple_token
+{
+ if ((ctx->currnode = graph_add_edge (ctx->currnode, $1)) != $1)
+ graph_delete_node (ctx->graph, $1);
+ cmd_token_varname_seqappend($1);
+}
+| selector
+{
+ graph_add_edge (ctx->currnode, $1.start);
+ cmd_token_varname_seqappend($1.start);
+ ctx->currnode = $1.end;
+}
+;
+
+simple_token:
+ literal_token
+| placeholder_token
+;
+
+literal_token: WORD varname_token
+{
+ $$ = new_token_node (ctx, WORD_TKN, $1, doc_next(ctx));
+ cmd_token_varname_set ($$->data, $2);
+ XFREE (MTYPE_LEX, $2);
+ XFREE (MTYPE_LEX, $1);
+}
+;
+
+placeholder_token_real:
+ IPV4
+{
+ $$ = new_token_node (ctx, IPV4_TKN, $1, doc_next(ctx));
+ XFREE (MTYPE_LEX, $1);
+}
+| IPV4_PREFIX
+{
+ $$ = new_token_node (ctx, IPV4_PREFIX_TKN, $1, doc_next(ctx));
+ XFREE (MTYPE_LEX, $1);
+}
+| IPV6
+{
+ $$ = new_token_node (ctx, IPV6_TKN, $1, doc_next(ctx));
+ XFREE (MTYPE_LEX, $1);
+}
+| IPV6_PREFIX
+{
+ $$ = new_token_node (ctx, IPV6_PREFIX_TKN, $1, doc_next(ctx));
+ XFREE (MTYPE_LEX, $1);
+}
+| VARIABLE
+{
+ $$ = new_token_node (ctx, VARIABLE_TKN, $1, doc_next(ctx));
+ XFREE (MTYPE_LEX, $1);
+}
+| RANGE
+{
+ $$ = new_token_node (ctx, RANGE_TKN, $1, doc_next(ctx));
+ struct cmd_token *token = $$->data;
+
+ // get the numbers out
+ yylval.string++;
+ token->min = strtoll (yylval.string, &yylval.string, 10);
+ strsep (&yylval.string, "-");
+ token->max = strtoll (yylval.string, &yylval.string, 10);
+
+ // validate range
+ if (token->min > token->max) cmd_yyerror (&@1, ctx, "Invalid range.");
+
+ XFREE (MTYPE_LEX, $1);
+}
+| MAC
+{
+ $$ = new_token_node (ctx, MAC_TKN, $1, doc_next(ctx));
+ XFREE (MTYPE_LEX, $1);
+}
+| MAC_PREFIX
+{
+ $$ = new_token_node (ctx, MAC_PREFIX_TKN, $1, doc_next(ctx));
+ XFREE (MTYPE_LEX, $1);
+}
+
+placeholder_token:
+ placeholder_token_real varname_token
+{
+ $$ = $1;
+ cmd_token_varname_set ($$->data, $2);
+ XFREE (MTYPE_LEX, $2);
+};
+
+
+/* <selector|set> productions */
+selector: '<' selector_seq_seq '>' varname_token
+{
+ $$ = $2;
+ cmd_token_varname_join ($2.end, $4);
+ XFREE (MTYPE_LEX, $4);
+};
+
+selector_seq_seq:
+ selector_seq_seq '|' selector_token_seq
+{
+ $$ = $1;
+ graph_add_edge ($$.start, $3.start);
+ graph_add_edge ($3.end, $$.end);
+}
+| selector_token_seq
+{
+ $$.start = new_token_node (ctx, FORK_TKN, NULL, NULL);
+ $$.end = new_token_node (ctx, JOIN_TKN, NULL, NULL);
+ ((struct cmd_token *)$$.start->data)->forkjoin = $$.end;
+ ((struct cmd_token *)$$.end->data)->forkjoin = $$.start;
+
+ graph_add_edge ($$.start, $1.start);
+ graph_add_edge ($1.end, $$.end);
+}
+;
+
+/* {keyword} productions */
+selector: '{' selector_seq_seq '}' varname_token
+{
+ $$ = $2;
+ graph_add_edge ($$.end, $$.start);
+ /* there is intentionally no start->end link, for two reasons:
+ * 1) this allows "at least 1 of" semantics, which are otherwise impossible
+ * 2) this would add a start->end->start loop in the graph that the current
+ * loop-avoidal fails to handle
+ * just use [{a|b}] if necessary, that will work perfectly fine, and reason
+ * #1 is good enough to keep it this way. */
+
+ loopcheck(ctx, &$$);
+ cmd_token_varname_join ($2.end, $4);
+ XFREE (MTYPE_LEX, $4);
+};
+
+
+selector_token:
+ simple_token
+{
+ $$.start = $$.end = $1;
+}
+| selector
+;
+
+selector_token_seq:
+ selector_token_seq selector_token
+{
+ graph_add_edge ($1.end, $2.start);
+ cmd_token_varname_seqappend($2.start);
+ $$.start = $1.start;
+ $$.end = $2.end;
+}
+| selector_token
+;
+
+/* [option] productions */
+selector: '[' selector_seq_seq ']' varname_token
+{
+ $$ = $2;
+ graph_add_edge ($$.start, $$.end);
+ cmd_token_varname_join ($2.end, $4);
+ XFREE (MTYPE_LEX, $4);
+}
+;
+
+/* ![option] productions */
+selector: EXCL_BRACKET selector_seq_seq ']' varname_token
+{
+ struct graph_node *neg_only = new_token_node (ctx, NEG_ONLY_TKN, NULL, NULL);
+
+ $$ = $2;
+ graph_add_edge ($$.start, neg_only);
+ graph_add_edge (neg_only, $$.end);
+ cmd_token_varname_join ($2.end, $4);
+ XFREE (MTYPE_LEX, $4);
+}
+;
+
+%%
+
+#undef scanner
+
+DEFINE_MTYPE(LIB, LEX, "Lexer token (temporary)");
+
+void
+cmd_graph_parse (struct graph *graph, const struct cmd_element *cmd)
+{
+ struct parser_ctx ctx = { .graph = graph, .el = cmd };
+
+ // set to 1 to enable parser traces
+ yydebug = 0;
+
+ set_lexer_string (&ctx.scanner, cmd->string);
+
+ // parse command into DFA
+ cmd_yyparse (&ctx);
+
+ /* cleanup lexer */
+ cleanup_lexer (&ctx.scanner);
+
+ // cleanup
+ cleanup (&ctx);
+}
+
+/* parser helper functions */
+
+static bool loopcheck_inner(struct graph_node *start, struct graph_node *node,
+ struct graph_node *end, size_t depth)
+{
+ size_t i;
+ bool ret;
+
+ /* safety check */
+ if (depth++ == 64)
+ return true;
+
+ for (i = 0; i < vector_active(node->to); i++) {
+ struct graph_node *next = vector_slot(node->to, i);
+ struct cmd_token *tok = next->data;
+
+ if (next == end || next == start)
+ return true;
+ if (tok->type < SPECIAL_TKN)
+ continue;
+ ret = loopcheck_inner(start, next, end, depth);
+ if (ret)
+ return true;
+ }
+ return false;
+}
+
+static void loopcheck(struct parser_ctx *ctx, struct subgraph *sg)
+{
+ if (loopcheck_inner(sg->start, sg->start, sg->end, 0))
+ zlog_err("FATAL: '%s': {} contains an empty path! Use [{...}]",
+ ctx->el->string);
+}
+
+void
+yyerror (CMD_YYLTYPE *loc, struct parser_ctx *ctx, char const *msg)
+{
+ char *tmpstr = strdup(ctx->el->string);
+ char *line, *eol;
+ char spacing[256];
+ int lineno = 0;
+
+ zlog_notice ("%s: FATAL parse error: %s", __func__, msg);
+ zlog_notice ("%s: %d:%d-%d of this command definition:", __func__, loc->first_line, loc->first_column, loc->last_column);
+
+ line = tmpstr;
+ do {
+ lineno++;
+ eol = strchr(line, '\n');
+ if (eol)
+ *eol++ = '\0';
+
+ zlog_notice ("%s: | %s", __func__, line);
+ if (lineno == loc->first_line && lineno == loc->last_line
+ && loc->first_column < (int)sizeof(spacing) - 1
+ && loc->last_column < (int)sizeof(spacing) - 1) {
+
+ int len = loc->last_column - loc->first_column;
+ if (len == 0)
+ len = 1;
+
+ memset(spacing, ' ', loc->first_column - 1);
+ memset(spacing + loc->first_column - 1, '^', len);
+ spacing[loc->first_column - 1 + len] = '\0';
+ zlog_notice ("%s: | %s", __func__, spacing);
+ }
+ } while ((line = eol));
+ free(tmpstr);
+}
+
+static void
+cleanup (struct parser_ctx *ctx)
+{
+ /* free resources */
+ free (ctx->docstr_start);
+
+ /* clear state pointers */
+ ctx->currnode = NULL;
+ ctx->docstr_start = ctx->docstr = NULL;
+}
+
+static void
+terminate_graph (CMD_YYLTYPE *locp, struct parser_ctx *ctx,
+ struct graph_node *finalnode)
+{
+ // end of graph should look like this
+ // * -> finalnode -> END_TKN -> cmd_element
+ const struct cmd_element *element = ctx->el;
+ struct graph_node *end_token_node =
+ new_token_node (ctx, END_TKN, CMD_CR_TEXT, "");
+ struct graph_node *end_element_node =
+ graph_new_node (ctx->graph, (void *)element, NULL);
+
+ if (ctx->docstr && strlen (ctx->docstr) > 1) {
+ zlog_err ("Excessive docstring while parsing '%s'", ctx->el->string);
+ zlog_err ("----------");
+ while (ctx->docstr && ctx->docstr[1] != '\0')
+ zlog_err ("%s", strsep(&ctx->docstr, "\n"));
+ zlog_err ("----------");
+ }
+
+ graph_add_edge (finalnode, end_token_node);
+ graph_add_edge (end_token_node, end_element_node);
+}
+
+static const char *
+doc_next (struct parser_ctx *ctx)
+{
+ const char *piece = ctx->docstr ? strsep (&ctx->docstr, "\n") : "";
+ if (*piece == 0x03)
+ {
+ zlog_err ("Ran out of docstring while parsing '%s'", ctx->el->string);
+ piece = "";
+ }
+
+ return piece;
+}
+
+static struct graph_node *
+new_token_node (struct parser_ctx *ctx, enum cmd_token_type type,
+ const char *text, const char *doc)
+{
+ struct cmd_token *token = cmd_token_new (type, ctx->el->attr, text, doc);
+ return graph_new_node (ctx->graph, token, (void (*)(void *)) &cmd_token_del);
+}
diff --git a/lib/command_py.c b/lib/command_py.c
new file mode 100644
index 0000000..6301eec
--- /dev/null
+++ b/lib/command_py.c
@@ -0,0 +1,363 @@
+/*
+ * clippy (CLI preparator in python) wrapper for FRR command_graph
+ * Copyright (C) 2016-2017 David Lamparter for NetDEF, Inc.
+ *
+ * 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
+ */
+
+/* note: this wrapper is intended to be used as build-time helper. while
+ * it should be generally correct and proper, there may be the occasional
+ * memory leak or SEGV for things that haven't been well-tested.
+ */
+
+/* This file is "exempt" from having
+#include "config.h"
+ * as the first include statement because Python.h also does environment
+ * setup & these trample over each other.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <Python.h>
+#include "structmember.h"
+#include <string.h>
+#include <stdlib.h>
+
+#include "command_graph.h"
+#include "clippy.h"
+
+struct wrap_graph;
+static PyObject *graph_to_pyobj(struct wrap_graph *graph,
+ struct graph_node *gn);
+
+/*
+ * nodes are wrapped as follows:
+ * - instances can only be acquired from a graph
+ * - the same node will return the same wrapper object (they're buffered
+ * through "idx")
+ * - a reference is held onto the graph
+ * - fields are copied for easy access with PyMemberDef
+ */
+struct wrap_graph_node {
+ PyObject_HEAD
+
+ bool allowrepeat;
+ const char *type;
+
+ bool deprecated;
+ bool hidden;
+ const char *text;
+ const char *desc;
+ const char *varname;
+ long long min, max;
+
+ struct graph_node *node;
+ struct wrap_graph *wgraph;
+ size_t idx;
+};
+
+/*
+ * graphs are wrapped as follows:
+ * - they can only be created by parsing a definition string
+ * - there's a table here for the wrapped nodes (nodewrappers), indexed
+ * by "idx" (corresponds to node's position in graph's table of nodes)
+ * - graphs do NOT hold references to nodes (would be circular)
+ */
+struct wrap_graph {
+ PyObject_HEAD
+
+ char *definition;
+ struct graph *graph;
+ struct wrap_graph_node **nodewrappers;
+};
+
+static PyObject *refuse_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+ PyErr_SetString(PyExc_ValueError,
+ "cannot create instances of this type");
+ return NULL;
+}
+
+#define member(name, type) \
+ { \
+ (char *)#name, type, offsetof(struct wrap_graph_node, name), \
+ READONLY, (char *)#name " (" #type ")" \
+ }
+static PyMemberDef members_graph_node[] = {
+ member(allowrepeat, T_BOOL), member(type, T_STRING),
+ member(deprecated, T_BOOL), member(hidden, T_BOOL),
+ member(text, T_STRING), member(desc, T_STRING),
+ member(min, T_LONGLONG), member(max, T_LONGLONG),
+ member(varname, T_STRING), {},
+};
+#undef member
+
+/*
+ * node.next() -- returns list of all "next" nodes.
+ * this will include circles if the graph has them.
+ */
+static PyObject *graph_node_next(PyObject *self, PyObject *args)
+{
+ struct wrap_graph_node *wrap = (struct wrap_graph_node *)self;
+ PyObject *pylist;
+
+ if (wrap->node->data
+ && ((struct cmd_token *)wrap->node->data)->type == END_TKN)
+ return PyList_New(0);
+ pylist = PyList_New(vector_active(wrap->node->to));
+ for (size_t i = 0; i < vector_active(wrap->node->to); i++) {
+ struct graph_node *gn = vector_slot(wrap->node->to, i);
+ PyList_SetItem(pylist, i, graph_to_pyobj(wrap->wgraph, gn));
+ }
+ return pylist;
+};
+
+/*
+ * node.join() -- return FORK's JOIN node or None
+ */
+static PyObject *graph_node_join(PyObject *self, PyObject *args)
+{
+ struct wrap_graph_node *wrap = (struct wrap_graph_node *)self;
+
+ if (!wrap->node->data
+ || ((struct cmd_token *)wrap->node->data)->type == END_TKN)
+ Py_RETURN_NONE;
+
+ struct cmd_token *tok = wrap->node->data;
+ if (tok->type != FORK_TKN)
+ Py_RETURN_NONE;
+
+ return graph_to_pyobj(wrap->wgraph, tok->forkjoin);
+};
+
+static PyMethodDef methods_graph_node[] = {
+ {"next", graph_node_next, METH_NOARGS, "outbound graph edge list"},
+ {"join", graph_node_join, METH_NOARGS, "outbound join node"},
+ {}};
+
+static void graph_node_wrap_free(void *arg)
+{
+ struct wrap_graph_node *wrap = arg;
+ wrap->wgraph->nodewrappers[wrap->idx] = NULL;
+ Py_DECREF(wrap->wgraph);
+}
+
+static PyTypeObject typeobj_graph_node = {
+ PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_clippy.GraphNode",
+ .tp_basicsize = sizeof(struct wrap_graph_node),
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_doc = "struct graph_node *",
+ .tp_new = refuse_new,
+ .tp_free = graph_node_wrap_free,
+ .tp_members = members_graph_node,
+ .tp_methods = methods_graph_node,
+};
+
+static PyObject *graph_to_pyobj(struct wrap_graph *wgraph,
+ struct graph_node *gn)
+{
+ struct wrap_graph_node *wrap;
+ size_t i;
+
+ for (i = 0; i < vector_active(wgraph->graph->nodes); i++)
+ if (vector_slot(wgraph->graph->nodes, i) == gn)
+ break;
+ if (i == vector_active(wgraph->graph->nodes)) {
+ PyErr_SetString(PyExc_ValueError, "cannot find node in graph");
+ return NULL;
+ }
+ if (wgraph->nodewrappers[i]) {
+ PyObject *obj = (PyObject *)wgraph->nodewrappers[i];
+ Py_INCREF(obj);
+ return obj;
+ }
+
+ wrap = (struct wrap_graph_node *)typeobj_graph_node.tp_alloc(
+ &typeobj_graph_node, 0);
+ if (!wrap)
+ return NULL;
+ wgraph->nodewrappers[i] = wrap;
+ Py_INCREF(wgraph);
+
+ wrap->idx = i;
+ wrap->wgraph = wgraph;
+ wrap->node = gn;
+ wrap->type = "NULL";
+ wrap->allowrepeat = false;
+ if (gn->data) {
+ struct cmd_token *tok = gn->data;
+ switch (tok->type) {
+#define item(x) \
+ case x: \
+ wrap->type = #x; \
+ break /* no semicolon */
+
+ item(WORD_TKN); // words
+ item(VARIABLE_TKN); // almost anything
+ item(RANGE_TKN); // integer range
+ item(IPV4_TKN); // IPV4 addresses
+ item(IPV4_PREFIX_TKN); // IPV4 network prefixes
+ item(IPV6_TKN); // IPV6 prefixes
+ item(IPV6_PREFIX_TKN); // IPV6 network prefixes
+ item(MAC_TKN); // MAC address
+ item(MAC_PREFIX_TKN); // MAC address with mask
+
+ /* plumbing types */
+ item(FORK_TKN);
+ item(JOIN_TKN);
+ item(START_TKN);
+ item(END_TKN);
+ item(NEG_ONLY_TKN);
+#undef item
+ default:
+ wrap->type = "???";
+ }
+
+ wrap->deprecated = (tok->attr == CMD_ATTR_DEPRECATED);
+ wrap->hidden = (tok->attr == CMD_ATTR_HIDDEN);
+ wrap->text = tok->text;
+ wrap->desc = tok->desc;
+ wrap->varname = tok->varname;
+ wrap->min = tok->min;
+ wrap->max = tok->max;
+ wrap->allowrepeat = tok->allowrepeat;
+ }
+
+ return (PyObject *)wrap;
+}
+
+#define member(name, type) \
+ { \
+ (char *)#name, type, offsetof(struct wrap_graph, name), \
+ READONLY, (char *)#name " (" #type ")" \
+ }
+static PyMemberDef members_graph[] = {
+ member(definition, T_STRING),
+ {},
+};
+#undef member
+
+/* graph.first() - root node */
+static PyObject *graph_first(PyObject *self, PyObject *args)
+{
+ struct wrap_graph *gwrap = (struct wrap_graph *)self;
+ struct graph_node *gn = vector_slot(gwrap->graph->nodes, 0);
+ return graph_to_pyobj(gwrap, gn);
+};
+
+static PyMethodDef methods_graph[] = {
+ {"first", graph_first, METH_NOARGS, "first graph node"},
+ {}};
+
+static PyObject *graph_parse(PyTypeObject *type, PyObject *args,
+ PyObject *kwds);
+
+static void graph_wrap_free(void *arg)
+{
+ struct wrap_graph *wgraph = arg;
+
+ graph_delete_graph(wgraph->graph);
+ free(wgraph->nodewrappers);
+ free(wgraph->definition);
+}
+
+static PyTypeObject typeobj_graph = {
+ PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_clippy.Graph",
+ .tp_basicsize = sizeof(struct wrap_graph),
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_doc = "struct graph *",
+ .tp_new = graph_parse,
+ .tp_free = graph_wrap_free,
+ .tp_members = members_graph,
+ .tp_methods = methods_graph,
+};
+
+/* top call / entrypoint for python code */
+static PyObject *graph_parse(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+ const char *def, *doc = NULL;
+ struct wrap_graph *gwrap;
+ static const char *kwnames[] = {"cmddef", "doc", NULL};
+
+ gwrap = (struct wrap_graph *)typeobj_graph.tp_alloc(&typeobj_graph, 0);
+ if (!gwrap)
+ return NULL;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|s", (char **)kwnames,
+ &def, &doc))
+ return NULL;
+
+ struct graph *graph = graph_new();
+ struct cmd_token *token = cmd_token_new(START_TKN, 0, NULL, NULL);
+ graph_new_node(graph, token, (void (*)(void *)) & cmd_token_del);
+
+ struct cmd_element cmd = {.string = def, .doc = doc};
+ cmd_graph_parse(graph, &cmd);
+ cmd_graph_names(graph);
+
+ gwrap->graph = graph;
+ gwrap->definition = strdup(def);
+ gwrap->nodewrappers = calloc(vector_active(graph->nodes),
+ sizeof(gwrap->nodewrappers[0]));
+ return (PyObject *)gwrap;
+}
+
+static PyMethodDef clippy_methods[] = {
+ {"parse", clippy_parse, METH_VARARGS, "Parse a C file"},
+ {NULL, NULL, 0, NULL}};
+
+#if PY_MAJOR_VERSION >= 3
+static struct PyModuleDef pymoddef_clippy = {
+ PyModuleDef_HEAD_INIT,
+ "_clippy",
+ NULL, /* docstring */
+ -1,
+ clippy_methods,
+};
+#define modcreate() PyModule_Create(&pymoddef_clippy)
+#define initret(val) return val;
+#else
+#define modcreate() Py_InitModule("_clippy", clippy_methods)
+#define initret(val) \
+ do { \
+ if (!val) \
+ Py_FatalError("initialization failure"); \
+ return; \
+ } while (0)
+#endif
+
+#pragma GCC diagnostic ignored "-Wstrict-aliasing"
+PyMODINIT_FUNC command_py_init(void)
+{
+ PyObject *pymod;
+
+ if (PyType_Ready(&typeobj_graph_node) < 0)
+ initret(NULL);
+ if (PyType_Ready(&typeobj_graph) < 0)
+ initret(NULL);
+
+ pymod = modcreate();
+ if (!pymod)
+ initret(NULL);
+
+ Py_INCREF(&typeobj_graph_node);
+ PyModule_AddObject(pymod, "GraphNode", (PyObject *)&typeobj_graph_node);
+ Py_INCREF(&typeobj_graph);
+ PyModule_AddObject(pymod, "Graph", (PyObject *)&typeobj_graph);
+ if (!elf_py_init(pymod))
+ initret(NULL);
+ initret(pymod);
+}
diff --git a/lib/compiler.h b/lib/compiler.h
new file mode 100644
index 0000000..bf44390
--- /dev/null
+++ b/lib/compiler.h
@@ -0,0 +1,418 @@
+/*
+ * Copyright (c) 2015-2017 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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_COMPILER_H
+#define _FRR_COMPILER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef __cplusplus
+# if __cplusplus < 201103L
+# error FRRouting headers must be compiled in C++11 mode or newer
+# endif
+/* C++ defines static_assert(), but not _Static_assert(). C defines
+ * _Static_assert() and has static_assert() in <assert.h>. However, we mess
+ * with assert() in zassert.h so let's not include <assert.h> here.
+ */
+# define _Static_assert static_assert
+#else
+# if !defined(__STDC_VERSION__) || __STDC_VERSION__ < 201112L
+# error FRRouting must be compiled with min. -std=gnu11 (GNU ISO C11 dialect)
+# endif
+#endif
+
+/* function attributes, use like
+ * void prototype(void) __attribute__((_CONSTRUCTOR(100)));
+ */
+#if defined(__clang__)
+#if __clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >= 5)
+# define _RET_NONNULL , returns_nonnull
+#endif
+#if __has_attribute(fallthrough)
+# define _FALLTHROUGH __attribute__((fallthrough));
+#endif
+# define _CONSTRUCTOR(x) constructor(x)
+# define _DEPRECATED(x) deprecated(x)
+# if __has_builtin(assume)
+# define assume(x) __builtin_assume(x)
+# endif
+#elif defined(__GNUC__)
+#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)
+# define _RET_NONNULL , returns_nonnull
+#endif
+#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)
+# define _CONSTRUCTOR(x) constructor(x)
+# define _DESTRUCTOR(x) destructor(x)
+# define _ALLOC_SIZE(x) alloc_size(x)
+#endif
+#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)
+# define _DEPRECATED(x) deprecated(x)
+# define assume(x) do { if (!(x)) __builtin_unreachable(); } while (0)
+#endif
+#if __GNUC__ < 5
+# define __has_attribute(x) 0
+#endif
+#if __GNUC__ >= 7
+# define _FALLTHROUGH __attribute__((fallthrough));
+#endif
+#endif
+
+#if __has_attribute(hot)
+# define _OPTIMIZE_HOT __attribute__((hot))
+#else
+# define _OPTIMIZE_HOT
+#endif
+#if __has_attribute(optimize)
+# define _OPTIMIZE_O3 __attribute__((optimize("3")))
+#else
+# define _OPTIMIZE_O3
+#endif
+#define OPTIMIZE _OPTIMIZE_O3 _OPTIMIZE_HOT
+
+#if !defined(__GNUC__)
+#error module code needs GCC visibility extensions
+#elif __GNUC__ < 4
+#error module code needs GCC visibility extensions
+#else
+# define DSO_PUBLIC __attribute__ ((visibility ("default")))
+# define DSO_SELF __attribute__ ((visibility ("protected")))
+# define DSO_LOCAL __attribute__ ((visibility ("hidden")))
+#endif
+
+#ifdef __sun
+/* Solaris doesn't do constructor priorities due to linker restrictions */
+#undef _CONSTRUCTOR
+#undef _DESTRUCTOR
+#endif
+
+/* fallback versions */
+#ifndef _RET_NONNULL
+# define _RET_NONNULL
+#endif
+#ifndef _CONSTRUCTOR
+# define _CONSTRUCTOR(x) constructor
+#endif
+#ifndef _DESTRUCTOR
+# define _DESTRUCTOR(x) destructor
+#endif
+#ifndef _ALLOC_SIZE
+# define _ALLOC_SIZE(x)
+#endif
+#ifndef _FALLTHROUGH
+#define _FALLTHROUGH
+#endif
+#ifndef _DEPRECATED
+#define _DEPRECATED(x) deprecated
+#endif
+#ifndef assume
+#define assume(x)
+#endif
+
+/* for helper functions defined inside macros */
+#define macro_inline static inline __attribute__((unused))
+#define macro_pure static inline __attribute__((unused, pure))
+
+/* if the macro ends with a function definition */
+#define MACRO_REQUIRE_SEMICOLON() \
+ _Static_assert(1, "please add a semicolon after this macro")
+
+/* variadic macros, use like:
+ * #define V_0() ...
+ * #define V_1(x) ...
+ * #define V(...) MACRO_VARIANT(V, ##__VA_ARGS__)(__VA_ARGS__)
+ */
+#define _MACRO_VARIANT(A0,A1,A2,A3,A4,A5,A6,A7,A8,A9,A10, N, ...) N
+
+#define _CONCAT2(a, b) a ## b
+#define _CONCAT(a, b) _CONCAT2(a,b)
+
+#define MACRO_VARIANT(NAME, ...) \
+ _CONCAT(NAME, _MACRO_VARIANT(0, ##__VA_ARGS__, \
+ _10, _9, _8, _7, _6, _5, _4, _3, _2, _1, _0))
+
+#define NAMECTR(name) _CONCAT(name, __COUNTER__)
+
+/* per-arg repeat macros, use like:
+ * #define PERARG(n) ...n...
+ * #define FOO(...) MACRO_REPEAT(PERARG, ##__VA_ARGS__)
+ */
+
+#define _MACRO_REPEAT_0(NAME)
+#define _MACRO_REPEAT_1(NAME, A1) \
+ NAME(A1)
+#define _MACRO_REPEAT_2(NAME, A1, A2) \
+ NAME(A1) NAME(A2)
+#define _MACRO_REPEAT_3(NAME, A1, A2, A3) \
+ NAME(A1) NAME(A2) NAME(A3)
+#define _MACRO_REPEAT_4(NAME, A1, A2, A3, A4) \
+ NAME(A1) NAME(A2) NAME(A3) NAME(A4)
+#define _MACRO_REPEAT_5(NAME, A1, A2, A3, A4, A5) \
+ NAME(A1) NAME(A2) NAME(A3) NAME(A4) NAME(A5)
+#define _MACRO_REPEAT_6(NAME, A1, A2, A3, A4, A5, A6) \
+ NAME(A1) NAME(A2) NAME(A3) NAME(A4) NAME(A5) NAME(A6)
+#define _MACRO_REPEAT_7(NAME, A1, A2, A3, A4, A5, A6, A7) \
+ NAME(A1) NAME(A2) NAME(A3) NAME(A4) NAME(A5) NAME(A6) NAME(A7)
+#define _MACRO_REPEAT_8(NAME, A1, A2, A3, A4, A5, A6, A7, A8) \
+ NAME(A1) NAME(A2) NAME(A3) NAME(A4) NAME(A5) NAME(A6) NAME(A7) NAME(A8)
+
+#define MACRO_REPEAT(NAME, ...) \
+ MACRO_VARIANT(_MACRO_REPEAT, ##__VA_ARGS__)(NAME, ##__VA_ARGS__)
+
+/* per-arglist repeat macro, use like this:
+ * #define foo(...) MAP_LISTS(F, ##__VA_ARGS__)
+ * where F is a n-ary function where n is the number of args in each arglist.
+ * e.g.: MAP_LISTS(f, (a, b), (c, d))
+ * expands to: f(a, b); f(c, d)
+ */
+
+#define ESC(...) __VA_ARGS__
+#define MAP_LISTS(M, ...) \
+ _CONCAT(_MAP_LISTS_, PP_NARG(__VA_ARGS__))(M, ##__VA_ARGS__)
+#define _MAP_LISTS_0(M)
+#define _MAP_LISTS_1(M, _1) ESC(M _1)
+#define _MAP_LISTS_2(M, _1, _2) ESC(M _1; M _2)
+#define _MAP_LISTS_3(M, _1, _2, _3) ESC(M _1; M _2; M _3)
+#define _MAP_LISTS_4(M, _1, _2, _3, _4) ESC(M _1; M _2; M _3; M _4)
+#define _MAP_LISTS_5(M, _1, _2, _3, _4, _5) ESC(M _1; M _2; M _3; M _4; M _5)
+#define _MAP_LISTS_6(M, _1, _2, _3, _4, _5, _6) \
+ ESC(M _1; M _2; M _3; M _4; M _5; M _6)
+#define _MAP_LISTS_7(M, _1, _2, _3, _4, _5, _6, _7) \
+ ESC(M _1; M _2; M _3; M _4; M _5; M _6; M _7)
+#define _MAP_LISTS_8(M, _1, _2, _3, _4, _5, _6, _7, _8) \
+ ESC(M _1; M _2; M _3; M _4; M _5; M _6; M _7; M _8)
+
+/*
+ * for warnings on macros, put in the macro content like this:
+ * #define MACRO BLA CPP_WARN("MACRO has been deprecated")
+ */
+#define CPP_STR(X) #X
+
+#if defined(__ICC)
+#define CPP_NOTICE(text) _Pragma(CPP_STR(message __FILE__ ": " text))
+#define CPP_WARN(text) CPP_NOTICE(text)
+
+#elif (defined(__GNUC__) \
+ && (__GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8))) \
+ || (defined(__clang__) \
+ && (__clang_major__ >= 4 \
+ || (__clang_major__ == 3 && __clang_minor__ >= 5)))
+#define CPP_WARN(text) _Pragma(CPP_STR(GCC warning text))
+#define CPP_NOTICE(text) _Pragma(CPP_STR(message text))
+
+#else
+#define CPP_WARN(text)
+#define CPP_NOTICE(text)
+#endif
+
+/* MAX / MIN are not commonly defined, but useful */
+/* note: glibc sys/param.h has #define MIN(a,b) (((a)<(b))?(a):(b)) */
+#ifdef MAX
+#undef MAX
+#endif
+#define MAX(a, b) \
+ ({ \
+ typeof(a) _max_a = (a); \
+ typeof(b) _max_b = (b); \
+ _max_a > _max_b ? _max_a : _max_b; \
+ })
+#ifdef MIN
+#undef MIN
+#endif
+#define MIN(a, b) \
+ ({ \
+ typeof(a) _min_a = (a); \
+ typeof(b) _min_b = (b); \
+ _min_a < _min_b ? _min_a : _min_b; \
+ })
+
+#define numcmp(a, b) \
+ ({ \
+ typeof(a) _cmp_a = (a); \
+ typeof(b) _cmp_b = (b); \
+ (_cmp_a < _cmp_b) ? -1 : ((_cmp_a > _cmp_b) ? 1 : 0); \
+ })
+
+#ifndef offsetof
+#ifdef __compiler_offsetof
+#define offsetof(TYPE, MEMBER) __compiler_offsetof(TYPE,MEMBER)
+#else
+#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
+#endif
+#endif
+
+#ifdef container_of
+#undef container_of
+#endif
+
+#if !(defined(__cplusplus) || defined(test__cplusplus))
+/* this variant of container_of() retains 'const' on pointers without needing
+ * to be told to do so. The following will all work without warning:
+ *
+ * struct member *p;
+ * const struct member *cp;
+ *
+ * const struct cont *x = container_of(cp, struct cont, member);
+ * const struct cont *x = container_of(cp, const struct cont, member);
+ * const struct cont *x = container_of(p, struct cont, member);
+ * const struct cont *x = container_of(p, const struct cont, member);
+ * struct cont *x = container_of(p, struct cont, member);
+ *
+ * but the following will generate warnings about stripping const:
+ *
+ * struct cont *x = container_of(cp, struct cont, member);
+ * struct cont *x = container_of(cp, const struct cont, member);
+ * struct cont *x = container_of(p, const struct cont, member);
+ */
+#define container_of(ptr, type, member) \
+ (__builtin_choose_expr( \
+ __builtin_types_compatible_p(typeof(&((type *)0)->member), \
+ typeof(ptr)) \
+ || __builtin_types_compatible_p(void *, typeof(ptr)), \
+ ({ \
+ typeof(((type *)0)->member) *__mptr = (void *)(ptr); \
+ (type *)((char *)__mptr - offsetof(type, member)); \
+ }), \
+ ({ \
+ typeof(((const type *)0)->member) *__mptr = (ptr); \
+ (const type *)((const char *)__mptr - \
+ offsetof(type, member)); \
+ }) \
+ ))
+#else
+/* current C++ compilers don't have the builtins used above; so this version
+ * of the macro doesn't do the const check. */
+#define container_of(ptr, type, member) \
+ ({ \
+ const typeof(((type *)0)->member) *__mptr = (ptr); \
+ (type *)((char *)__mptr - offsetof(type, member)); \
+ })
+#endif
+
+#define container_of_null(ptr, type, member) \
+ ({ \
+ typeof(ptr) _tmp = (ptr); \
+ _tmp ? container_of(_tmp, type, member) : NULL; \
+ })
+
+#define array_size(ar) (sizeof(ar) / sizeof(ar[0]))
+
+/* Some insane macros to count number of varargs to a functionlike macro */
+#define PP_ARG_N( \
+ _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \
+ _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, \
+ _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, \
+ _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, \
+ _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, \
+ _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, \
+ _61, _62, _63, N, ...) N
+
+#define PP_RSEQ_N() \
+ 62, 61, 60, \
+ 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, \
+ 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, \
+ 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, \
+ 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, \
+ 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, \
+ 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
+
+#define PP_NARG_(...) PP_ARG_N(__VA_ARGS__)
+#define PP_NARG(...) PP_NARG_(_, ##__VA_ARGS__, PP_RSEQ_N())
+
+
+/* sigh. this is so ugly, it overflows and wraps to being nice again.
+ *
+ * printfrr() supports "%Ld" for <int64_t>, whatever that is typedef'd to.
+ * However, gcc & clang think that "%Ld" is <long long>, which doesn't quite
+ * match up since int64_t is <long> on a lot of 64-bit systems.
+ *
+ * If we have _FRR_ATTRIBUTE_PRINTFRR, we loaded a compiler plugin that
+ * replaces the whole format checking bits with a custom version that
+ * understands "%Ld" (along with "%pI4" and co.), so we don't need to do
+ * anything.
+ *
+ * If we don't have that attribute... we still want -Wformat to work. So,
+ * this is the "f*ck it" approach and we just redefine int64_t to always be
+ * <long long>. This should work until such a time that <long long> is
+ * something else (e.g. 128-bit integer)... let's just guard against that
+ * with the _Static_assert below and work with the world we have right now,
+ * where <long long> is always 64-bit.
+ */
+
+/* these need to be included before any of the following, so we can
+ * "overwrite" things.
+ */
+#include <stdint.h>
+#include <inttypes.h>
+
+#ifdef _FRR_ATTRIBUTE_PRINTFRR
+#define PRINTFRR(a, b) __attribute__((frr_format("frr_printf", a, b)))
+
+#undef PRIu64
+#undef PRId64
+#undef PRIx64
+#define PRIu64 "Lu"
+#define PRId64 "Ld"
+#define PRIx64 "Lx"
+
+#else /* !_FRR_ATTRIBUTE_PRINTFRR */
+#ifdef __NetBSD__
+#define PRINTFRR(a, b) __attribute__((format(gnu_syslog, a, b)))
+#else
+#define PRINTFRR(a, b) __attribute__((format(printf, a, b)))
+#endif
+
+/* frr-format plugin is C-only for now, so no point in doing these shenanigans
+ * for C++... (also they can break some C++ stuff...)
+ */
+#ifndef __cplusplus
+/* these should be typedefs, but might also be #define */
+#ifdef uint64_t
+#undef uint64_t
+#endif
+#ifdef int64_t
+#undef int64_t
+#endif
+
+/* can't overwrite the typedef, but we can replace int64_t with _int64_t */
+typedef unsigned long long _uint64_t;
+#define uint64_t _uint64_t
+typedef signed long long _int64_t;
+#define int64_t _int64_t
+
+/* if this breaks, 128-bit machines may have entered reality (or <long long>
+ * is something weird)
+ */
+_Static_assert(sizeof(_uint64_t) == 8 && sizeof(_int64_t) == 8,
+ "nobody expects the spanish intquisition");
+
+/* since we redefined int64_t, we also need to redefine PRI*64 */
+#undef PRIu64
+#undef PRId64
+#undef PRIx64
+#define PRIu64 "llu"
+#define PRId64 "lld"
+#define PRIx64 "llx"
+
+#endif /* !__cplusplus */
+#endif /* !_FRR_ATTRIBUTE_PRINTFRR */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRR_COMPILER_H */
diff --git a/lib/cspf.c b/lib/cspf.c
new file mode 100644
index 0000000..ef3e7c2
--- /dev/null
+++ b/lib/cspf.c
@@ -0,0 +1,646 @@
+/*
+ * Constraints Shortest Path First algorithms - cspf.c
+ *
+ * Author: Olivier Dugeon <olivier.dugeon@orange.com>
+ *
+ * Copyright (C) 2022 Orange http://www.orange.com
+ *
+ * This file is part of Free Range Routing (FRR).
+ *
+ * FRR 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, or (at your option) any
+ * later version.
+ *
+ * FRR 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 "if.h"
+#include "linklist.h"
+#include "log.h"
+#include "hash.h"
+#include "memory.h"
+#include "prefix.h"
+#include "table.h"
+#include "stream.h"
+#include "printfrr.h"
+#include "link_state.h"
+#include "cspf.h"
+
+/* Link State Memory allocation */
+DEFINE_MTYPE_STATIC(LIB, PCA, "Path Computation Algorithms");
+
+/**
+ * Create new Constrained Path. Memory is dynamically allocated.
+ *
+ * @param key Vertex key of the destination of this path
+ *
+ * @return Pointer to a new Constrained Path structure
+ */
+static struct c_path *cpath_new(uint64_t key)
+{
+ struct c_path *path;
+
+ /* Sanity Check */
+ if (key == 0)
+ return NULL;
+
+ path = XCALLOC(MTYPE_PCA, sizeof(struct c_path));
+ path->dst = key;
+ path->status = IN_PROGRESS;
+ path->edges = list_new();
+ path->weight = MAX_COST;
+
+ return path;
+}
+
+/**
+ * Copy src Constrained Path into dst Constrained Path. A new Constrained Path
+ * structure is dynamically allocated if dst is NULL. If src is NULL, the
+ * function return the dst disregarding if it is NULL or not.
+ *
+ * @param dest Destination Constrained Path structure
+ * @param src Source Constrained Path structure
+ *
+ * @return Pointer to the destination Constrained Path structure
+ */
+static struct c_path *cpath_copy(struct c_path *dest, const struct c_path *src)
+{
+ struct c_path *new_path;
+
+ if (!src)
+ return dest;
+
+ if (!dest) {
+ new_path = XCALLOC(MTYPE_PCA, sizeof(struct c_path));
+ } else {
+ new_path = dest;
+ if (dest->edges)
+ list_delete(&new_path->edges);
+ }
+
+ new_path->dst = src->dst;
+ new_path->weight = src->weight;
+ new_path->edges = list_dup(src->edges);
+ new_path->status = src->status;
+
+ return new_path;
+}
+
+/**
+ * Delete Constrained Path structure. Previous allocated memory is freed.
+ *
+ * @param path Constrained Path structure to be deleted
+ */
+static void cpath_del(struct c_path *path)
+{
+ if (!path)
+ return;
+
+ if (path->edges)
+ list_delete(&path->edges);
+
+ XFREE(MTYPE_PCA, path);
+ path = NULL;
+}
+
+/**
+ * Replace the list of edges in the next Constrained Path by the list of edges
+ * in the current Constrained Path.
+ *
+ * @param next_path next Constrained Path structure
+ * @param cur_path current Constrained Path structure
+ */
+static void cpath_replace(struct c_path *next_path, struct c_path *cur_path)
+{
+
+ if (next_path->edges)
+ list_delete(&next_path->edges);
+
+ next_path->edges = list_dup(cur_path->edges);
+}
+
+/**
+ * Create a new Visited Node structure from the provided Vertex. Structure is
+ * dynamically allocated.
+ *
+ * @param vertex Vertex structure
+ *
+ * @return Pointer to the new Visited Node structure
+ */
+static struct v_node *vnode_new(struct ls_vertex *vertex)
+{
+ struct v_node *vnode;
+
+ if (!vertex)
+ return NULL;
+
+ vnode = XCALLOC(MTYPE_PCA, sizeof(struct v_node));
+ vnode->vertex = vertex;
+ vnode->key = vertex->key;
+
+ return vnode;
+}
+
+/**
+ * Delete Visited Node structure. Previous allocated memory is freed.
+ *
+ * @param vnode Visited Node structure to be deleted
+ */
+static void vnode_del(struct v_node *vnode)
+{
+ if (!vnode)
+ return;
+
+ XFREE(MTYPE_PCA, vnode);
+ vnode = NULL;
+}
+
+/**
+ * Search Vertex in TED by IPv4 address. The function search vertex by browsing
+ * the subnets table. It allows to find not only vertex by router ID, but also
+ * vertex by interface IPv4 address.
+ *
+ * @param ted Traffic Engineering Database
+ * @param ipv4 IPv4 address
+ *
+ * @return Vertex if found, NULL otherwise
+ */
+static struct ls_vertex *get_vertex_by_ipv4(struct ls_ted *ted,
+ struct in_addr ipv4)
+{
+ struct ls_subnet *subnet;
+ struct prefix p;
+
+ p.family = AF_INET;
+ p.u.prefix4 = ipv4;
+
+ frr_each (subnets, &ted->subnets, subnet) {
+ if (subnet->key.family != AF_INET)
+ continue;
+ p.prefixlen = subnet->key.prefixlen;
+ if (prefix_same(&subnet->key, &p))
+ return subnet->vertex;
+ }
+
+ return NULL;
+}
+
+/**
+ * Search Vertex in TED by IPv6 address. The function search vertex by browsing
+ * the subnets table. It allows to find not only vertex by router ID, but also
+ * vertex by interface IPv6 address.
+ *
+ * @param ted Traffic Engineering Database
+ * @param ipv6 IPv6 address
+ *
+ * @return Vertex if found, NULL otherwise
+ */
+static struct ls_vertex *get_vertex_by_ipv6(struct ls_ted *ted,
+ struct in6_addr ipv6)
+{
+ struct ls_subnet *subnet;
+ struct prefix p;
+
+ p.family = AF_INET6;
+ p.u.prefix6 = ipv6;
+
+ frr_each (subnets, &ted->subnets, subnet) {
+ if (subnet->key.family != AF_INET6)
+ continue;
+ p.prefixlen = subnet->key.prefixlen;
+ if (prefix_cmp(&subnet->key, &p) == 0)
+ return subnet->vertex;
+ }
+
+ return NULL;
+}
+
+struct cspf *cspf_new(void)
+{
+ struct cspf *algo;
+
+ /* Allocate New CSPF structure */
+ algo = XCALLOC(MTYPE_PCA, sizeof(struct cspf));
+
+ /* Initialize RB-Trees */
+ processed_init(&algo->processed);
+ visited_init(&algo->visited);
+ pqueue_init(&algo->pqueue);
+
+ algo->path = NULL;
+ algo->pdst = NULL;
+
+ return algo;
+}
+
+struct cspf *cspf_init(struct cspf *algo, const struct ls_vertex *src,
+ const struct ls_vertex *dst, struct constraints *csts)
+{
+ struct cspf *new_algo;
+ struct c_path *psrc;
+
+ if (!csts)
+ return NULL;
+
+ if (!algo)
+ new_algo = cspf_new();
+ else
+ new_algo = algo;
+
+ /* Initialize Processed Path and Priority Queue with Src & Dst */
+ if (src) {
+ psrc = cpath_new(src->key);
+ psrc->weight = 0;
+ processed_add(&new_algo->processed, psrc);
+ pqueue_add(&new_algo->pqueue, psrc);
+ new_algo->path = psrc;
+ }
+ if (dst) {
+ new_algo->pdst = cpath_new(dst->key);
+ processed_add(&new_algo->processed, new_algo->pdst);
+ }
+
+ memcpy(&new_algo->csts, csts, sizeof(struct constraints));
+
+ return new_algo;
+}
+
+struct cspf *cspf_init_v4(struct cspf *algo, struct ls_ted *ted,
+ const struct in_addr src, const struct in_addr dst,
+ struct constraints *csts)
+{
+ struct ls_vertex *vsrc;
+ struct ls_vertex *vdst;
+ struct cspf *new_algo;
+
+ /* Sanity Check */
+ if (!ted)
+ return algo;
+
+ if (!algo)
+ new_algo = cspf_new();
+ else
+ new_algo = algo;
+
+ /* Got Source and Destination Vertex from TED */
+ vsrc = get_vertex_by_ipv4(ted, src);
+ vdst = get_vertex_by_ipv4(ted, dst);
+ csts->family = AF_INET;
+
+ return cspf_init(new_algo, vsrc, vdst, csts);
+}
+
+struct cspf *cspf_init_v6(struct cspf *algo, struct ls_ted *ted,
+ const struct in6_addr src, const struct in6_addr dst,
+ struct constraints *csts)
+{
+ struct ls_vertex *vsrc;
+ struct ls_vertex *vdst;
+ struct cspf *new_algo;
+
+ /* Sanity Check */
+ if (!ted)
+ return algo;
+
+ if (!algo)
+ new_algo = cspf_new();
+ else
+ new_algo = algo;
+
+ /* Got Source and Destination Vertex from TED */
+ vsrc = get_vertex_by_ipv6(ted, src);
+ vdst = get_vertex_by_ipv6(ted, dst);
+ csts->family = AF_INET6;
+
+ return cspf_init(new_algo, vsrc, vdst, csts);
+}
+
+void cspf_clean(struct cspf *algo)
+{
+ struct c_path *path;
+ struct v_node *vnode;
+
+ if (!algo)
+ return;
+
+ /* Normally, Priority Queue is empty. Clean it in case of. */
+ if (pqueue_count(&algo->pqueue)) {
+ frr_each_safe (pqueue, &algo->pqueue, path) {
+ pqueue_del(&algo->pqueue, path);
+ }
+ }
+
+ /* Empty Processed Path tree and associated Path */
+ if (processed_count(&algo->processed)) {
+ frr_each_safe (processed, &algo->processed, path) {
+ processed_del(&algo->processed, path);
+ cpath_del(path);
+ }
+ }
+
+ /* Empty visited Vertex tree and associated Node */
+ if (visited_count(&algo->visited)) {
+ frr_each_safe (visited, &algo->visited, vnode) {
+ visited_del(&algo->visited, vnode);
+ vnode_del(vnode);
+ }
+ }
+
+ memset(&algo->csts, 0, sizeof(struct constraints));
+ algo->path = NULL;
+ algo->pdst = NULL;
+}
+
+void cspf_del(struct cspf *algo)
+{
+ if (!algo)
+ return;
+
+ /* Empty Priority Queue and Processes Path */
+ cspf_clean(algo);
+
+ /* Then, reset Priority Queue, Processed Path and Visited RB-Tree */
+ pqueue_fini(&algo->pqueue);
+ processed_fini(&algo->processed);
+ visited_fini(&algo->visited);
+
+ XFREE(MTYPE_PCA, algo);
+ algo = NULL;
+}
+
+/**
+ * Prune Edge if constraints are not met by testing Edge Attributes against
+ * given constraints and cumulative cost of the given constrained path.
+ *
+ * @param path On-going Computed Path with cumulative cost constraints
+ * @param edge Edge to be validate against Constraints
+ * @param csts Constraints for this path
+ *
+ * @return True if Edge should be prune, false if Edge is valid
+ */
+static bool prune_edge(const struct c_path *path, const struct ls_edge *edge,
+ const struct constraints *csts)
+{
+ struct ls_vertex *dst;
+ struct ls_attributes *attr;
+
+ /* Check that Path, Edge and Constraints are valid */
+ if (!path || !edge || !csts)
+ return true;
+
+ /* Check that Edge has a valid destination */
+ if (!edge->destination)
+ return true;
+ dst = edge->destination;
+
+ /* Check that Edge has valid attributes */
+ if (!edge->attributes)
+ return true;
+ attr = edge->attributes;
+
+ /* Check that Edge belongs to the requested Address Family and type */
+ if (csts->family == AF_INET) {
+ if (IPV4_NET0(attr->standard.local.s_addr))
+ return true;
+ if (csts->type == SR_TE)
+ if (!CHECK_FLAG(attr->flags, LS_ATTR_ADJ_SID) ||
+ !CHECK_FLAG(dst->node->flags, LS_NODE_SR))
+ return true;
+ }
+ if (csts->family == AF_INET6) {
+ if (IN6_IS_ADDR_UNSPECIFIED(&attr->standard.local6))
+ return true;
+ if (csts->type == SR_TE)
+ if (!CHECK_FLAG(attr->flags, LS_ATTR_ADJ_SID6) ||
+ !CHECK_FLAG(dst->node->flags, LS_NODE_SR))
+ return true;
+ }
+
+ /*
+ * Check that total cost, up to this edge, respects the initial
+ * constraints
+ */
+ switch (csts->ctype) {
+ case CSPF_METRIC:
+ if (!CHECK_FLAG(attr->flags, LS_ATTR_METRIC))
+ return true;
+ if ((attr->metric + path->weight) > csts->cost)
+ return true;
+ break;
+
+ case CSPF_TE_METRIC:
+ if (!CHECK_FLAG(attr->flags, LS_ATTR_TE_METRIC))
+ return true;
+ if ((attr->standard.te_metric + path->weight) > csts->cost)
+ return true;
+ break;
+
+ case CSPF_DELAY:
+ if (!CHECK_FLAG(attr->flags, LS_ATTR_DELAY))
+ return true;
+ if ((attr->extended.delay + path->weight) > csts->cost)
+ return true;
+ break;
+ }
+
+ /* If specified, check that Edge meet Bandwidth constraint */
+ if (csts->bw > 0.0) {
+ if (attr->standard.max_bw < csts->bw ||
+ attr->standard.max_rsv_bw < csts->bw ||
+ attr->standard.unrsv_bw[csts->cos] < csts->bw)
+ return true;
+ }
+
+ /* All is fine. We can consider this Edge valid, so not to be prune */
+ return false;
+}
+
+/**
+ * Relax constraints of the current path up to the destination vertex of the
+ * provided Edge. This function progress in the network topology by validating
+ * the next vertex on the computed path. If Vertex has not already been visited,
+ * list of edges of the current path is augmented with this edge if the new cost
+ * is lower than prior path up to this vertex. Current path is re-inserted in
+ * the Priority Queue with its new cost i.e. current cost + edge cost.
+ *
+ * @param algo CSPF structure
+ * @param edge Next Edge to be added to the current computed path
+ *
+ * @return True if current path reach destination, false otherwise
+ */
+static bool relax_constraints(struct cspf *algo, struct ls_edge *edge)
+{
+
+ struct c_path pkey = {};
+ struct c_path *next_path;
+ struct v_node vnode = {};
+ uint32_t total_cost = MAX_COST;
+
+ /* Verify that we have a current computed path */
+ if (!algo->path)
+ return false;
+
+ /* Verify if we have not visited the next Vertex to avoid loop */
+ vnode.key = edge->destination->key;
+ if (visited_member(&algo->visited, &vnode)) {
+ return false;
+ }
+
+ /*
+ * Get Next Computed Path from next vertex key
+ * or create a new one if it has not yet computed.
+ */
+ pkey.dst = edge->destination->key;
+ next_path = processed_find(&algo->processed, &pkey);
+ if (!next_path) {
+ next_path = cpath_new(pkey.dst);
+ processed_add(&algo->processed, next_path);
+ }
+
+ /*
+ * Add or update the Computed Path in the Priority Queue if total cost
+ * is lower than cost associated to this next Vertex. This could occurs
+ * if we process a Vertex that as not yet been visited in the Graph
+ * or if we found a shortest path up to this Vertex.
+ */
+ switch (algo->csts.ctype) {
+ case CSPF_METRIC:
+ total_cost = edge->attributes->metric + algo->path->weight;
+ break;
+ case CSPF_TE_METRIC:
+ total_cost = edge->attributes->standard.te_metric +
+ algo->path->weight;
+ break;
+ case CSPF_DELAY:
+ total_cost =
+ edge->attributes->extended.delay + algo->path->weight;
+ break;
+ default:
+ break;
+ }
+ if (total_cost < next_path->weight) {
+ /*
+ * It is not possible to directly update the q_path in the
+ * Priority Queue. Indeed, if we modify the path weight, the
+ * Priority Queue must be re-ordered. So, we need fist to remove
+ * the q_path if it is present in the Priority Queue, then,
+ * update the Path, in particular the Weight, and finally
+ * (re-)insert it in the Priority Queue.
+ */
+ struct c_path *path;
+ frr_each_safe (pqueue, &algo->pqueue, path) {
+ if (path->dst == pkey.dst) {
+ pqueue_del(&algo->pqueue, path);
+ break;
+ }
+ }
+ next_path->weight = total_cost;
+ cpath_replace(next_path, algo->path);
+ listnode_add(next_path->edges, edge);
+ pqueue_add(&algo->pqueue, next_path);
+ }
+
+ /* Return True if we reach the destination */
+ return (next_path->dst == algo->pdst->dst);
+}
+
+struct c_path *compute_p2p_path(struct cspf *algo, struct ls_ted *ted)
+{
+ struct listnode *node;
+ struct ls_vertex *vertex;
+ struct ls_edge *edge;
+ struct c_path *optim_path;
+ struct v_node *vnode;
+ uint32_t cur_cost;
+
+ optim_path = cpath_new(0xFFFFFFFFFFFFFFFF);
+ optim_path->status = FAILED;
+
+ /* Check that all is correctly initialized */
+ if (!algo)
+ return optim_path;
+
+ if (!algo->csts.ctype)
+ return optim_path;
+
+ if (!algo->pdst) {
+ optim_path->status = NO_DESTINATION;
+ return optim_path;
+ }
+
+ if (!algo->path) {
+ optim_path->status = NO_SOURCE;
+ return optim_path;
+ }
+
+ if (algo->pdst->dst == algo->path->dst) {
+ optim_path->status = SAME_SRC_DST;
+ return optim_path;
+ }
+
+ optim_path->dst = algo->pdst->dst;
+ optim_path->status = IN_PROGRESS;
+
+ /*
+ * Process all Connected Vertex until priority queue becomes empty.
+ * Connected Vertices are added into the priority queue when
+ * processing the next Connected Vertex: see relax_constraints()
+ */
+ cur_cost = MAX_COST;
+ while (pqueue_count(&algo->pqueue) != 0) {
+ /* Got shortest current Path from the Priority Queue */
+ algo->path = pqueue_pop(&algo->pqueue);
+
+ /* Add destination Vertex of this path to the visited RB Tree */
+ vertex = ls_find_vertex_by_key(ted, algo->path->dst);
+ if (!vertex)
+ continue;
+ vnode = vnode_new(vertex);
+ visited_add(&algo->visited, vnode);
+
+ /* Process all outgoing links from this Vertex */
+ for (ALL_LIST_ELEMENTS_RO(vertex->outgoing_edges, node, edge)) {
+ /*
+ * Skip Connected Edges that must be prune i.e.
+ * Edges that not satisfy the given constraints,
+ * in particular the Bandwidth, TE Metric and Delay.
+ */
+ if (prune_edge(algo->path, edge, &algo->csts))
+ continue;
+
+ /*
+ * Relax constraints and check if we got a shorter
+ * candidate path
+ */
+ if (relax_constraints(algo, edge) &&
+ algo->pdst->weight < cur_cost) {
+ cur_cost = algo->pdst->weight;
+ cpath_copy(optim_path, algo->pdst);
+ optim_path->status = SUCCESS;
+ }
+ }
+ }
+
+ /*
+ * The priority queue is empty => all the possible (vertex, path)
+ * elements have been explored. The optim_path contains the optimal
+ * path if it exists. Otherwise an empty path with status failed is
+ * returned.
+ */
+ if (optim_path->status == IN_PROGRESS ||
+ listcount(optim_path->edges) == 0)
+ optim_path->status = FAILED;
+ cspf_clean(algo);
+
+ return optim_path;
+}
diff --git a/lib/cspf.h b/lib/cspf.h
new file mode 100644
index 0000000..6466ddb
--- /dev/null
+++ b/lib/cspf.h
@@ -0,0 +1,211 @@
+/*
+ * Constraints Shortest Path First algorithms definition - cspf.h
+ *
+ * Author: Olivier Dugeon <olivier.dugeon@orange.com>
+ *
+ * Copyright (C) 2022 Orange http://www.orange.com
+ *
+ * This file is part of Free Range Routing (FRR).
+ *
+ * FRR 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, or (at your option) any
+ * later version.
+ *
+ * FRR 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_CSPF_H_
+#define _FRR_CSPF_H_
+
+#include "typesafe.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * This file defines the different structure used for Path Computation with
+ * various constrained. Up to now, standard metric, TE metric, delay and
+ * bandwidth constraints are supported.
+ * All proposed algorithms used the same principle:
+ * - A pruning function that keeps only links that meet constraints
+ * - A priority Queue that keeps the shortest on-going computed path
+ * - A main loop over all vertices to find the shortest path
+ */
+
+#define MAX_COST 0xFFFFFFFF
+
+/* Status of the path */
+enum path_status {
+ FAILED = 0,
+ NO_SOURCE,
+ NO_DESTINATION,
+ SAME_SRC_DST,
+ IN_PROGRESS,
+ SUCCESS
+};
+enum path_type {RSVP_TE = 1, SR_TE, SRV6_TE};
+enum metric_type {CSPF_METRIC = 1, CSPF_TE_METRIC, CSPF_DELAY};
+
+/* Constrained metrics structure */
+struct constraints {
+ uint32_t cost; /* total cost (metric) of the path */
+ enum metric_type ctype; /* Metric Type: standard, TE or Delay */
+ float bw; /* bandwidth of the path */
+ uint8_t cos; /* Class of Service of the path */
+ enum path_type type; /* RSVP-TE or SR-TE path */
+ uint8_t family; /* AF_INET or AF_INET6 address family */
+};
+
+/* Priority Queue for Constrained Path Computation */
+PREDECL_RBTREE_NONUNIQ(pqueue);
+
+/* Processed Path for Constrained Path Computation */
+PREDECL_RBTREE_UNIQ(processed);
+
+/* Constrained Path structure */
+struct c_path {
+ struct pqueue_item q_itm; /* entry in the Priority Queue */
+ uint32_t weight; /* Weight to sort path in Priority Queue */
+ struct processed_item p_itm; /* entry in the Processed RB Tree */
+ uint64_t dst; /* Destination vertex key of this path */
+ struct list *edges; /* List of Edges that compose this path */
+ enum path_status status; /* status of the computed path */
+};
+
+macro_inline int q_cmp(const struct c_path *p1, const struct c_path *p2)
+{
+ return numcmp(p1->weight, p2->weight);
+}
+DECLARE_RBTREE_NONUNIQ(pqueue, struct c_path, q_itm, q_cmp);
+
+macro_inline int p_cmp(const struct c_path *p1, const struct c_path *p2)
+{
+ return numcmp(p1->dst, p2->dst);
+}
+DECLARE_RBTREE_UNIQ(processed, struct c_path, p_itm, p_cmp);
+
+/* List of visited node */
+PREDECL_RBTREE_UNIQ(visited);
+struct v_node {
+ struct visited_item item; /* entry in the Processed RB Tree */
+ uint64_t key;
+ struct ls_vertex *vertex;
+};
+
+macro_inline int v_cmp(const struct v_node *p1, const struct v_node *p2)
+{
+ return numcmp(p1->key, p2->key);
+}
+DECLARE_RBTREE_UNIQ(visited, struct v_node, item, v_cmp);
+
+/* Path Computation algorithms structure */
+struct cspf {
+ struct pqueue_head pqueue; /* Priority Queue */
+ struct processed_head processed; /* Paths that have been processed */
+ struct visited_head visited; /* Vertices that have been visited */
+ struct constraints csts; /* Constraints of the path */
+ struct c_path *path; /* Current Computed Path */
+ struct c_path *pdst; /* Computed Path to the destination */
+};
+
+/**
+ * Create a new CSPF structure. Memory is dynamically allocated.
+ *
+ * @return pointer to the new cspf structure
+ */
+extern struct cspf *cspf_new(void);
+
+/**
+ * Initialize CSPF structure prior to compute a constrained path. If CSPF
+ * structure is NULL, a new CSPF is dynamically allocated prior to the
+ * configuration itself.
+ *
+ * @param algo CSPF structure, may be null if a new CSPF must be created
+ * @param src Source vertex of the requested path
+ * @param dst Destination vertex of the requested path
+ * @param csts Constraints of the requested path
+ *
+ * @return pointer to the initialized CSPF structure
+ */
+extern struct cspf *cspf_init(struct cspf *algo, const struct ls_vertex *src,
+ const struct ls_vertex *dst,
+ struct constraints *csts);
+
+/**
+ * Initialize CSPF structure prior to compute a constrained path. If CSPF
+ * structure is NULL, a new CSPF is dynamically allocated prior to the
+ * configuration itself. This function starts by searching source and
+ * destination vertices from the IPv4 addresses in the provided TED.
+ *
+ * @param algo CSPF structure, may be null if a new CSPF must be created
+ * @param ted Traffic Engineering Database
+ * @param src Source IPv4 address of the requested path
+ * @param dst Destination IPv4 address of the requested path
+ * @param csts Constraints of the requested path
+ *
+ * @return pointer to the initialized CSPF structure
+ */
+extern struct cspf *cspf_init_v4(struct cspf *algo, struct ls_ted *ted,
+ const struct in_addr src,
+ const struct in_addr dst,
+ struct constraints *csts);
+
+/**
+ * Initialize CSPF structure prior to compute a constrained path. If CSPF
+ * structure is NULL, a new CSPF is dynamically allocated prior to the
+ * configuration itself. This function starts by searching source and
+ * destination vertices from the IPv6 addresses in the provided TED.
+ *
+ * @param algo CSPF structure, may be null if a new CSPF must be created
+ * @param ted Traffic Engineering Database
+ * @param src Source IPv6 address of the requested path
+ * @param dst Destination IPv6 address of the requested path
+ * @param csts Constraints of the requested path
+ *
+ * @return pointer to the initialized CSPF structure
+ */
+extern struct cspf *cspf_init_v6(struct cspf *algo, struct ls_ted *ted,
+ const struct in6_addr src,
+ const struct in6_addr dst,
+ struct constraints *csts);
+
+/**
+ * Clean CSPF structure. Reset all internal list and priority queue for latter
+ * initialization of the CSPF structure and new path computation.
+ *
+ * @param algo CSPF structure
+ */
+extern void cspf_clean(struct cspf *algo);
+
+/**
+ * Delete CSPF structure, internal list and priority queue.
+ *
+ * @param algo CSPF structure
+ */
+extern void cspf_del(struct cspf *algo);
+
+/**
+ * Compute point-to-point constrained path. cspf_init() function must be call
+ * prior to call this function.
+ *
+ * @param algo CSPF structure
+ * @param ted Traffic Engineering Database
+ *
+ * @return Constrained Path with status to indicate computation success
+ */
+extern struct c_path *compute_p2p_path(struct cspf *algo, struct ls_ted *ted);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRR_CSPF_H_ */
diff --git a/lib/csv.c b/lib/csv.c
new file mode 100644
index 0000000..05b9dbe
--- /dev/null
+++ b/lib/csv.c
@@ -0,0 +1,706 @@
+/* CSV
+ * Copyright (C) 2013,2020 Cumulus Networks, Inc.
+ *
+ * This file is part of Quagga.
+ *
+ * Quagga 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, or (at your option) any
+ * later version.
+ *
+ * Quagga 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
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <zebra.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <sys/queue.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include "csv.h"
+
+#define DEBUG_E 1
+#define DEBUG_V 1
+
+#define log_error(fmt, ...) \
+ do { \
+ if (DEBUG_E) \
+ fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \
+ __LINE__, __func__, ##__VA_ARGS__); \
+ } while (0)
+
+#define log_verbose(fmt, ...) \
+ do { \
+ if (DEBUG_V) \
+ fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \
+ __LINE__, __func__, __VA_ARGS__); \
+ } while (0)
+
+struct _csv_field_t_ {
+ TAILQ_ENTRY(_csv_field_t_) next_field;
+ char *field;
+ int field_len;
+};
+
+struct _csv_record_t_ {
+ TAILQ_HEAD(, _csv_field_t_) fields;
+ TAILQ_ENTRY(_csv_record_t_) next_record;
+ char *record;
+ int rec_len;
+};
+
+struct _csv_t_ {
+ TAILQ_HEAD(, _csv_record_t_) records;
+ char *buf;
+ int buflen;
+ int csv_len;
+ int pointer;
+ int num_recs;
+};
+
+
+int csvlen(csv_t *csv)
+{
+ return (csv->csv_len);
+}
+
+csv_t *csv_init(csv_t *csv, char *buf, int buflen)
+{
+ if (csv == NULL) {
+ csv = malloc(sizeof(csv_t));
+ if (csv == NULL) {
+ log_error("CSV Malloc failed\n");
+ return NULL;
+ }
+ }
+ memset(csv, 0, sizeof(csv_t));
+
+ csv->buf = buf;
+ csv->buflen = buflen;
+ TAILQ_INIT(&(csv->records));
+ return (csv);
+}
+
+void csv_clean(csv_t *csv)
+{
+ csv_record_t *rec;
+ csv_record_t *rec_n;
+
+ rec = TAILQ_FIRST(&(csv->records));
+ while (rec != NULL) {
+ rec_n = TAILQ_NEXT(rec, next_record);
+ csv_remove_record(csv, rec);
+ rec = rec_n;
+ }
+}
+
+void csv_free(csv_t *csv)
+{
+ if (csv != NULL) {
+ free(csv);
+ }
+}
+
+static void csv_init_record(csv_record_t *record)
+{
+ TAILQ_INIT(&(record->fields));
+ record->rec_len = 0;
+}
+
+csv_record_t *csv_record_iter(csv_t *csv)
+{
+ return (TAILQ_FIRST(&(csv->records)));
+}
+
+csv_record_t *csv_record_iter_next(csv_record_t *rec)
+{
+ if (!rec)
+ return NULL;
+ return (TAILQ_NEXT(rec, next_record));
+}
+
+char *csv_field_iter(csv_record_t *rec, csv_field_t **fld)
+{
+ if (!rec)
+ return NULL;
+ *fld = TAILQ_FIRST(&(rec->fields));
+ return ((*fld)->field);
+}
+
+char *csv_field_iter_next(csv_field_t **fld)
+{
+ *fld = TAILQ_NEXT(*fld, next_field);
+ if ((*fld) == NULL) {
+ return NULL;
+ }
+ return ((*fld)->field);
+}
+
+int csv_field_len(csv_field_t *fld)
+{
+ if (fld) {
+ return fld->field_len;
+ }
+ return 0;
+}
+
+static void csv_decode_record(csv_record_t *rec)
+{
+ char *curr = rec->record;
+ char *field;
+ csv_field_t *fld;
+
+ field = strpbrk(curr, ",");
+ while (field != NULL) {
+ fld = malloc(sizeof(csv_field_t));
+ if (fld) {
+ TAILQ_INSERT_TAIL(&(rec->fields), fld, next_field);
+ fld->field = curr;
+ fld->field_len = field - curr;
+ }
+ curr = field + 1;
+ field = strpbrk(curr, ",");
+ }
+ field = strstr(curr, "\n");
+ if (!field)
+ return;
+
+ fld = malloc(sizeof(csv_field_t));
+ if (fld) {
+ fld->field = curr;
+ fld->field_len = field - curr;
+ TAILQ_INSERT_TAIL(&(rec->fields), fld, next_field);
+ }
+}
+
+static csv_field_t *csv_add_field_to_record(csv_t *csv, csv_record_t *rec,
+ char *col)
+{
+ csv_field_t *fld;
+ char *str = rec->record;
+ int rlen = rec->rec_len;
+ int blen = csv->buflen;
+
+ fld = malloc(sizeof(csv_field_t));
+ if (!fld) {
+ log_error("field malloc failed\n");
+ /* more cleanup needed */
+ return NULL;
+ }
+ TAILQ_INSERT_TAIL(&(rec->fields), fld, next_field);
+ fld->field = str + rlen;
+ fld->field_len = snprintf((str + rlen), (blen - rlen), "%s", col);
+ rlen += fld->field_len;
+ rec->rec_len = rlen;
+ return fld;
+}
+
+csv_record_t *csv_encode(csv_t *csv, int count, ...)
+{
+ int tempc;
+ va_list list;
+ char *buf = csv->buf;
+ int len = csv->buflen;
+ int pointer = csv->pointer;
+ char *str = NULL;
+ char *col;
+ csv_record_t *rec;
+ csv_field_t *fld;
+
+ if (buf) {
+ str = buf + pointer;
+ } else {
+ /* allocate sufficient buffer */
+ str = (char *)malloc(csv->buflen);
+ if (!str) {
+ log_error("field str malloc failed\n");
+ return NULL;
+ }
+ }
+
+ va_start(list, count);
+ rec = malloc(sizeof(csv_record_t));
+ if (!rec) {
+ log_error("record malloc failed\n");
+ if (!buf)
+ free(str);
+ va_end(list);
+ return NULL;
+ }
+ csv_init_record(rec);
+ rec->record = str;
+ TAILQ_INSERT_TAIL(&(csv->records), rec, next_record);
+ csv->num_recs++;
+
+ /**
+ * Iterate through the fields passed as a variable list and add them
+ */
+ for (tempc = 0; tempc < count; tempc++) {
+ col = va_arg(list, char *);
+ fld = csv_add_field_to_record(csv, rec, col);
+ if (!fld) {
+ log_error("fld malloc failed\n");
+ csv_remove_record(csv, rec);
+ va_end(list);
+ return NULL;
+ }
+ if (tempc < (count - 1)) {
+ rec->rec_len += snprintf((str + rec->rec_len),
+ (len - rec->rec_len), ",");
+ }
+ }
+ rec->rec_len +=
+ snprintf((str + rec->rec_len), (len - rec->rec_len), "\n");
+ va_end(list);
+ csv->csv_len += rec->rec_len;
+ csv->pointer += rec->rec_len;
+ return (rec);
+}
+
+int csv_num_records(csv_t *csv)
+{
+ if (csv) {
+ return csv->num_recs;
+ }
+ return 0;
+}
+
+csv_record_t *csv_encode_record(csv_t *csv, csv_record_t *rec, int count, ...)
+{
+ int tempc;
+ va_list list;
+ char *str;
+ char *col;
+ csv_field_t *fld = NULL;
+ int i;
+
+ va_start(list, count);
+ str = csv_field_iter(rec, &fld);
+ if (!fld) {
+ va_end(list);
+ return NULL;
+ }
+
+ for (tempc = 0; tempc < count; tempc++) {
+ col = va_arg(list, char *);
+ for (i = 0; i < fld->field_len; i++) {
+ str[i] = col[i];
+ }
+ str = csv_field_iter_next(&fld);
+ }
+ va_end(list);
+ return (rec);
+}
+
+csv_record_t *csv_append_record(csv_t *csv, csv_record_t *rec, int count, ...)
+{
+ int tempc;
+ va_list list;
+ int len = csv->buflen, tlen;
+ char *str;
+ csv_field_t *fld;
+ char *col;
+
+ if (csv->buf) {
+ /* not only works with discrete bufs */
+ return NULL;
+ }
+
+ if (!rec) {
+ /* create a new rec */
+ rec = calloc(1, sizeof(csv_record_t));
+ if (!rec) {
+ log_error("record malloc failed\n");
+ return NULL;
+ }
+ csv_init_record(rec);
+ rec->record = calloc(1, csv->buflen);
+ if (!rec->record) {
+ log_error("field str malloc failed\n");
+ free(rec);
+ return NULL;
+ }
+ csv_insert_record(csv, rec);
+ }
+
+ str = rec->record;
+
+ va_start(list, count);
+
+ if (rec->rec_len && (str[rec->rec_len - 1] == '\n'))
+ str[rec->rec_len - 1] = ',';
+
+ /**
+ * Iterate through the fields passed as a variable list and add them
+ */
+ tlen = rec->rec_len;
+ for (tempc = 0; tempc < count; tempc++) {
+ col = va_arg(list, char *);
+ fld = csv_add_field_to_record(csv, rec, col);
+ if (!fld) {
+ log_error("fld malloc failed\n");
+ break;
+ }
+ if (tempc < (count - 1)) {
+ rec->rec_len += snprintf((str + rec->rec_len),
+ (len - rec->rec_len), ",");
+ }
+ }
+ rec->rec_len +=
+ snprintf((str + rec->rec_len), (len - rec->rec_len), "\n");
+ va_end(list);
+ csv->csv_len += (rec->rec_len - tlen);
+ csv->pointer += (rec->rec_len - tlen);
+ return (rec);
+}
+
+int csv_serialize(csv_t *csv, char *msgbuf, int msglen)
+{
+ csv_record_t *rec;
+ int offset = 0;
+
+ if (!csv || !msgbuf)
+ return -1;
+
+ rec = csv_record_iter(csv);
+ while (rec != NULL) {
+ if ((offset + rec->rec_len) >= msglen)
+ return -1;
+ offset += sprintf(&msgbuf[offset], "%s", rec->record);
+ rec = csv_record_iter_next(rec);
+ }
+
+ return 0;
+}
+
+void csv_clone_record(csv_t *csv, csv_record_t *in_rec, csv_record_t **out_rec)
+{
+ char *curr;
+ csv_record_t *rec;
+
+ /* first check if rec belongs to this csv */
+ if (!csv_is_record_valid(csv, in_rec)) {
+ log_error("rec not in this csv\n");
+ return;
+ }
+
+ /* only works with csv with discrete bufs */
+ if (csv->buf) {
+ log_error(
+ "un-supported for this csv type - single buf detected\n");
+ return;
+ }
+
+ /* create a new rec */
+ rec = calloc(1, sizeof(csv_record_t));
+ if (!rec) {
+ log_error("record malloc failed\n");
+ return;
+ }
+ csv_init_record(rec);
+ curr = calloc(1, csv->buflen);
+ if (!curr) {
+ log_error("field str malloc failed\n");
+ free(rec);
+ return;
+ }
+ rec->record = curr;
+ rec->rec_len = in_rec->rec_len;
+ strlcpy(rec->record, in_rec->record, csv->buflen);
+
+ /* decode record into fields */
+ csv_decode_record(rec);
+
+ *out_rec = rec;
+}
+
+void csv_remove_record(csv_t *csv, csv_record_t *rec)
+{
+ csv_field_t *fld = NULL, *p_fld;
+
+ /* first check if rec belongs to this csv */
+ if (!csv_is_record_valid(csv, rec)) {
+ log_error("rec not in this csv\n");
+ return;
+ }
+
+ /* remove fields */
+ csv_field_iter(rec, &fld);
+ while (fld) {
+ p_fld = fld;
+ csv_field_iter_next(&fld);
+ TAILQ_REMOVE(&(rec->fields), p_fld, next_field);
+ free(p_fld);
+ }
+
+ TAILQ_REMOVE(&(csv->records), rec, next_record);
+
+ csv->num_recs--;
+ csv->csv_len -= rec->rec_len;
+ csv->pointer -= rec->rec_len;
+ if (!csv->buf)
+ free(rec->record);
+ free(rec);
+}
+
+void csv_insert_record(csv_t *csv, csv_record_t *rec)
+{
+ /* first check if rec already in csv */
+ if (csv_is_record_valid(csv, rec)) {
+ log_error("rec already in this csv\n");
+ return;
+ }
+
+ /* we can only insert records if no buf was supplied during csv init */
+ if (csv->buf) {
+ log_error(
+ "un-supported for this csv type - single buf detected\n");
+ return;
+ }
+
+ /* do we go beyond the max buf set for this csv ?*/
+ if ((csv->csv_len + rec->rec_len) > csv->buflen) {
+ log_error("cannot insert - exceeded buf size\n");
+ return;
+ }
+
+ TAILQ_INSERT_TAIL(&(csv->records), rec, next_record);
+ csv->num_recs++;
+ csv->csv_len += rec->rec_len;
+ csv->pointer += rec->rec_len;
+}
+
+csv_record_t *csv_concat_record(csv_t *csv, csv_record_t *rec1,
+ csv_record_t *rec2)
+{
+ char *curr;
+ char *ret;
+ csv_record_t *rec;
+
+ /* first check if rec1 and rec2 belong to this csv */
+ if (!csv_is_record_valid(csv, rec1)
+ || !csv_is_record_valid(csv, rec2)) {
+ log_error("rec1 and/or rec2 invalid\n");
+ return NULL;
+ }
+
+ /* we can only concat records if no buf was supplied during csv init */
+ if (csv->buf) {
+ log_error(
+ "un-supported for this csv type - single buf detected\n");
+ return NULL;
+ }
+
+ /* create a new rec */
+ rec = calloc(1, sizeof(csv_record_t));
+ if (!rec) {
+ log_error("record malloc failed\n");
+ return NULL;
+ }
+ csv_init_record(rec);
+
+ curr = (char *)calloc(1, csv->buflen);
+ if (!curr) {
+ log_error("field str malloc failed\n");
+ goto out_rec;
+ }
+ rec->record = curr;
+
+ /* concat the record string */
+ ret = strstr(rec1->record, "\n");
+ if (!ret) {
+ log_error("rec1 str not properly formatted\n");
+ goto out_curr;
+ }
+
+ snprintf(curr, (int)(ret - rec1->record + 1), "%s", rec1->record);
+ strcat(curr, ",");
+
+ ret = strstr(rec2->record, "\n");
+ if (!ret) {
+ log_error("rec2 str not properly formatted\n");
+ goto out_curr;
+ }
+
+ snprintf((curr + strlen(curr)), (int)(ret - rec2->record + 1), "%s",
+ rec2->record);
+ strcat(curr, "\n");
+ rec->rec_len = strlen(curr);
+
+ /* paranoia */
+ assert(csv->buflen
+ > (csv->csv_len - rec1->rec_len - rec2->rec_len + rec->rec_len));
+
+ /* decode record into fields */
+ csv_decode_record(rec);
+
+ /* now remove rec1 and rec2 and insert rec into this csv */
+ csv_remove_record(csv, rec1);
+ csv_remove_record(csv, rec2);
+ csv_insert_record(csv, rec);
+
+ return rec;
+
+out_curr:
+ free(curr);
+out_rec:
+ free(rec);
+ return NULL;
+}
+
+void csv_decode(csv_t *csv, char *inbuf)
+{
+ char *buf;
+ char *pos;
+ csv_record_t *rec;
+
+ buf = (inbuf) ? inbuf : csv->buf;
+ assert(buf);
+
+ pos = strpbrk(buf, "\n");
+ while (pos != NULL) {
+ rec = calloc(1, sizeof(csv_record_t));
+ if (!rec)
+ return;
+ csv_init_record(rec);
+ TAILQ_INSERT_TAIL(&(csv->records), rec, next_record);
+ csv->num_recs++;
+ if (csv->buf)
+ rec->record = buf;
+ else {
+ rec->record = calloc(1, csv->buflen);
+ if (!rec->record) {
+ log_error("field str malloc failed\n");
+ return;
+ }
+ strncpy(rec->record, buf, pos - buf + 1);
+ }
+ rec->rec_len = pos - buf + 1;
+ /* decode record into fields */
+ csv_decode_record(rec);
+ buf = pos + 1;
+ pos = strpbrk(buf, "\n");
+ }
+}
+
+int csv_is_record_valid(csv_t *csv, csv_record_t *in_rec)
+{
+ csv_record_t *rec;
+ int valid = 0;
+
+ rec = csv_record_iter(csv);
+ while (rec) {
+ if (rec == in_rec) {
+ valid = 1;
+ break;
+ }
+ rec = csv_record_iter_next(rec);
+ }
+
+ return valid;
+}
+
+void csv_dump(csv_t *csv)
+{
+ csv_record_t *rec;
+ csv_field_t *fld;
+ char *str;
+
+ rec = csv_record_iter(csv);
+ while (rec != NULL) {
+ str = csv_field_iter(rec, &fld);
+ while (str != NULL) {
+ fprintf(stderr, "%s\n", str);
+ str = csv_field_iter_next(&fld);
+ }
+ rec = csv_record_iter_next(rec);
+ }
+}
+
+#ifdef TEST_CSV
+
+static int get_memory_usage(pid_t pid)
+{
+ int fd, data, stack;
+ char buf[4096], status_child[PATH_MAX];
+ char *vm;
+
+ snprintf(status_child, sizeof(status_child), "/proc/%d/status", pid);
+ fd = open(status_child, O_RDONLY);
+ if (fd < 0)
+ return -1;
+
+ read(fd, buf, 4095);
+ buf[4095] = '\0';
+ close(fd);
+
+ data = stack = 0;
+
+ vm = strstr(buf, "VmData:");
+ if (vm) {
+ sscanf(vm, "%*s %d", &data);
+ }
+ vm = strstr(buf, "VmStk:");
+ if (vm) {
+ sscanf(vm, "%*s %d", &stack);
+ }
+
+ return data + stack;
+}
+
+int main()
+{
+ char buf[10000];
+ csv_t csv;
+ int i;
+ csv_record_t *rec;
+ char hdr1[32], hdr2[32];
+
+ log_verbose("Mem: %d\n", get_memory_usage(getpid()));
+ csv_init(&csv, buf, 256);
+ snprintf(hdr1, sizeof(hdr1), "%4d", 0);
+ snprintf(hdr2, sizeof(hdr2), "%4d", 1);
+ log_verbose("(%zu/%zu/%d/%d)\n", strlen(hdr1), strlen(hdr2), atoi(hdr1),
+ atoi(hdr2));
+ rec = csv_encode(&csv, 2, hdr1, hdr2);
+ csv_encode(&csv, 4, "name", "age", "sex", "hei");
+ csv_encode(&csv, 3, NULL, "0", NULL);
+ csv_encode(&csv, 2, "p", "35");
+ for (i = 0; i < 50; i++) {
+ csv_encode(&csv, 2, "p", "10");
+ }
+ csv_encode(&csv, 2, "pdfadfadfadsadsaddfdfdsfdsd", "35444554545454545");
+ log_verbose("%s\n", buf);
+ snprintf(hdr1, sizeof(hdr1), "%4d", csv.csv_len);
+ snprintf(hdr2, sizeof(hdr2), "%4d", 1);
+ log_verbose("(%zu/%zu/%d/%d)\n", strlen(hdr1), strlen(hdr2), atoi(hdr1),
+ atoi(hdr2));
+ rec = csv_encode_record(&csv, rec, 2, hdr1, hdr2);
+ log_verbose("(%d/%d)\n%s\n", rec->rec_len, csv.csv_len, buf);
+
+ log_verbose("Mem: %d\n", get_memory_usage(getpid()));
+ csv_clean(&csv);
+ log_verbose("Mem: %d\n", get_memory_usage(getpid()));
+ csv_init(&csv, buf, 256);
+ csv_decode(&csv, NULL);
+ log_verbose("%s", "AFTER DECODE\n");
+ csv_dump(&csv);
+ csv_clean(&csv);
+ log_verbose("Mem: %d\n", get_memory_usage(getpid()));
+}
+#endif
diff --git a/lib/csv.h b/lib/csv.h
new file mode 100644
index 0000000..fe3599d
--- /dev/null
+++ b/lib/csv.h
@@ -0,0 +1,196 @@
+/* CSV
+ * Copyright (C) 2013 Cumulus Networks, Inc.
+ *
+ * This file is part of Quagga.
+ *
+ * Quagga 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, or (at your option) any
+ * later version.
+ *
+ * Quagga 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 __CSV_H__
+#define __CSV_H__
+
+/*
+ * CSV encoding and decoding routines.
+ *
+ * Example:
+ * Encoding side:
+ *
+ * csv_t *csv;
+ * csv_record_t *fstrec;
+ * csv_record_t *rec;
+ * char buf[BUFSIZ];
+ *
+ * csv = csv_init(csv, buf, BUFSIZ);
+ * ...
+ * fstrec = csv_encode(csv, 2, "hello", "world");
+ * rec = csv_encode(csv, 2, "foo", "bar");
+ * ...
+ * fstrec = csv_encode_record(csv, fstrec, 2, "HELLO", "WORLD");
+ * ...
+ * csv_clean(csv);
+ *
+ * Decoding side:
+ *
+ * csv_t *csv;
+ * csv_record_t *rec;
+ * csv_field_t *fld;
+ * char *rcvdbuf;
+ *
+ * csv = csv_init(csv, rcvdbuf, BUFSIZ);
+ * ...
+ * csv_decode(csv);
+ * csv_dump(csv);
+ *
+ * for (rec = csv_record_iter(csv); rec;
+ * rec = csv_record_iter_next(rec)) {
+ * ...
+ * for (str = csv_field_iter(rec, &fld); str;
+ * str = csv_field_iter_next(&fld)) {
+ * ...
+ * }
+ * }
+ * ...
+ * csv_clean(csv);
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <sys/queue.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct _csv_field_t_ csv_field_t;
+typedef struct _csv_record_t_ csv_record_t;
+typedef struct _csv_t_ csv_t;
+
+/**
+ * Initialize the CSV structure (if necessary, allocate first). Point to
+ * the passed string buffer.
+ */
+csv_t *csv_init(csv_t *csv, char *buf, int buflen);
+
+/**
+ * Encode the variable list of arguments as CSV fields. The csv structure
+ * should have been initialized (with the string buffer). The fields get
+ * concatenated into the string.
+ */
+csv_record_t *csv_encode(csv_t *csv, int count, ...);
+
+/**
+ * Encode the variable list arguments into an existing record, essentially
+ * overwriting the record. No checking is done for consistency. The number
+ * of fields should be the same as what was encoded and the length of each
+ * field should also be the same as what was encoded before. The "rec"
+ * parameter should be the same as what was returned from a previous call
+ * to csv_encode().
+ *
+ * Useful for message encoding/decoding that get passed around between
+ * processes/nodes - e.g. the message header record can be rewritten AFTER
+ * encoding all other records, with new information such as total length.
+ */
+csv_record_t *csv_encode_record(csv_t *csv, csv_record_t *rec, int count, ...);
+
+/**
+ * Decode a CSV formatted string. The csv structure should have been
+ * initialized (with the string). The function creates a LIST of records
+ * (csv_record_t structure) where each record is in turn a LIST of fields
+ * (csv_field_t structure). The record points to the string containing the
+ * list of fields. Similarly, the field points to the field string.
+ * NB: csv initialized for discrete buf , caller will pass inbuf
+ */
+void csv_decode(csv_t *csv, char *inbuf);
+
+/**
+ * Dump all fields of a decoded CSV to stderr
+ */
+void csv_dump(csv_t *csv);
+
+/**
+ * Total length of all characters encoded in the CSV.
+ */
+int csvlen(csv_t *csv);
+
+void csv_clean(csv_t *csv);
+void csv_free(csv_t *csv);
+
+/**
+ * Iterate through the records and fields of an encoded/decoded CSV.
+ */
+csv_record_t *csv_record_iter(csv_t *csv);
+csv_record_t *csv_record_iter_next(csv_record_t *rec);
+char *csv_field_iter(csv_record_t *rec, csv_field_t **fld);
+char *csv_field_iter_next(csv_field_t **fld);
+
+/**
+ * Return the length of field
+ */
+int csv_field_len(csv_field_t *fld);
+
+/**
+ * Checks to see if a record belongs to a csv
+ */
+int csv_is_record_valid(csv_t *csv, csv_record_t *in_rec);
+
+/**
+ * concat two records in a csv
+ * Returns the newly formed record which includes fields from rec1 and rec2
+ * rec1 and rec2 are removed
+ */
+csv_record_t *csv_concat_record(csv_t *csv, csv_record_t *rec1,
+ csv_record_t *rec2);
+
+/**
+ * Remove a record from csv
+ * Only works when csv has discrete record bufs
+ */
+void csv_remove_record(csv_t *csv, csv_record_t *rec);
+
+/**
+ * Insert a record into csv
+ * Only works when csv has discrete record bufs
+ */
+void csv_insert_record(csv_t *csv, csv_record_t *rec);
+
+/**
+ * append fields to a record
+ * Only works when csv has discrete record bufs
+ */
+csv_record_t *csv_append_record(csv_t *csv, csv_record_t *rec, int count, ...);
+
+/**
+ * Serialize contents of csv into string
+ * Only works when csv has discrete record bufs
+ */
+int csv_serialize(csv_t *csv, char *msgbuf, int msglen);
+
+/**
+ * Clone a record.
+ * Only works when csv has discrete record bufs
+ */
+void csv_clone_record(csv_t *csv, csv_record_t *in_rec, csv_record_t **out_rec);
+
+/**
+ * Return number of records
+ */
+int csv_num_records(csv_t *csv);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/db.c b/lib/db.c
new file mode 100644
index 0000000..b4286b8
--- /dev/null
+++ b/lib/db.c
@@ -0,0 +1,325 @@
+/*
+ * 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 database '%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;
+}
diff --git a/lib/db.h b/lib/db.h
new file mode 100644
index 0000000..884c737
--- /dev/null
+++ b/lib/db.h
@@ -0,0 +1,60 @@
+/*
+ * 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>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+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, ...);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* HAVE_SQLITE3 */
+#endif /* _FRR_DB_H_ */
diff --git a/lib/debug.c b/lib/debug.c
new file mode 100644
index 0000000..e2ba4cd
--- /dev/null
+++ b/lib/debug.c
@@ -0,0 +1,63 @@
+/*
+ * Debugging utilities.
+ * Copyright (C) 2018 Cumulus Networks, Inc.
+ * Quentin Young
+ *
+ * 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 "typesafe.h"
+#include "debug.h"
+#include "command.h"
+
+static struct debug_cb_list_head cb_head;
+
+DECLARE_LIST(debug_cb_list, struct debug_callbacks, item);
+
+/* All code in this section should be reentrant and MT-safe */
+
+DEFUN_NOSH(debug_all, debug_all_cmd, "[no] debug all",
+ NO_STR DEBUG_STR "Toggle all debugging output\n")
+{
+ struct debug_callbacks *cb;
+
+ bool set = !strmatch(argv[0]->text, "no");
+ uint32_t mode = DEBUG_NODE2MODE(vty->node);
+
+ frr_each (debug_cb_list, &cb_head, cb)
+ cb->debug_set_all(mode, set);
+
+ return CMD_SUCCESS;
+}
+
+/* ------------------------------------------------------------------------- */
+
+void debug_init(struct debug_callbacks *cb)
+{
+ static bool inited = false;
+
+ if (!inited) {
+ inited = true;
+ debug_cb_list_init(&cb_head);
+ }
+
+ debug_cb_list_add_head(&cb_head, cb);
+}
+
+void debug_init_cli(void)
+{
+ install_element(ENABLE_NODE, &debug_all_cmd);
+ install_element(CONFIG_NODE, &debug_all_cmd);
+}
diff --git a/lib/debug.h b/lib/debug.h
new file mode 100644
index 0000000..a72657b
--- /dev/null
+++ b/lib/debug.h
@@ -0,0 +1,254 @@
+/*
+ * Debugging utilities.
+ * Copyright (C) 2018 Cumulus Networks, Inc.
+ * Quentin Young
+ *
+ * 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 _FRRDEBUG_H
+#define _FRRDEBUG_H
+
+#include <zebra.h>
+#include "command.h"
+#include "frratomic.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Debugging modes.
+ *
+ * FRR's convention is that a debug statement issued under the vty CONFIG_NODE
+ * persists to the config file, whereas the same debug statement issued from
+ * the ENABLE_NODE only persists for the current session. These are mapped to
+ * DEBUG_MODE_CONF and DEBUG_MODE_TERM respectively.
+ *
+ * They are not mutually exclusive and are placed in the MSB of the flags
+ * field in a debugging record.
+ */
+#define DEBUG_MODE_TERM 0x01000000
+#define DEBUG_MODE_CONF 0x02000000
+#define DEBUG_MODE_ALL (DEBUG_MODE_TERM | DEBUG_MODE_CONF)
+#define DEBUG_MODE_NONE 0x00000000
+#define DEBUG_OPT_ALL 0x00FFFFFF
+#define DEBUG_OPT_NONE 0x00000000
+
+
+/*
+ * Debugging record.
+ *
+ * All operations on this record exposed in this header are MT-safe.
+ *
+ * flags
+ * A bitfield with the following format (bytes high to low)
+ * - [0] Debugging mode field (MSB) | Mode
+ * - [1] Arbitrary flag field | Option
+ * - [2] Arbitrary flag field | Option
+ * - [3] Arbitrary flag field (LSB) | Option
+ *
+ * ALL THESE BYTES ARE YOURS - EXCEPT MODE.
+ * ATTEMPT NO BIT OPS THERE.
+ *
+ * The MSB of this field determines the debug mode, Use the DEBUG_MODE*
+ * macros to manipulate this byte.
+ *
+ * The low 3 bytes of this field may be used to store arbitrary information.
+ * Usually they are used to store flags that tune how detailed the logging
+ * for a particular debug record is. Use the DEBUG_OPT* macros to manipulate
+ * those bytes.
+ *
+ * All operations performed on this field should be done using the macros
+ * later in this header file. They are guaranteed to be atomic operations
+ * with respect to this field. Using anything except the macros to
+ * manipulate the flags field in a multithreaded environment results in
+ * undefined behavior.
+ *
+ * desc
+ * Human-readable description of this debugging record.
+ */
+struct debug {
+ atomic_uint_fast32_t flags;
+ const char *desc;
+};
+
+PREDECL_LIST(debug_cb_list);
+/*
+ * Callback set for debugging code.
+ *
+ * debug_set_all
+ * Function pointer to call when the user requests that all debugs have a
+ * mode set.
+ */
+struct debug_callbacks {
+ /*
+ * Linked list of Callbacks to call
+ */
+ struct debug_cb_list_item item;
+
+ /*
+ * flags
+ * flags to set on debug flag fields
+ *
+ * set
+ * true: set flags
+ * false: unset flags
+ */
+ void (*debug_set_all)(uint32_t flags, bool set);
+};
+
+/*
+ * Check if a mode is set for a debug.
+ *
+ * MT-Safe
+ */
+#define DEBUG_MODE_CHECK(name, mode) \
+ CHECK_FLAG_ATOMIC(&(name)->flags, (mode)&DEBUG_MODE_ALL)
+
+/*
+ * Check if an option bit is set for a debug.
+ *
+ * MT-Safe
+ */
+#define DEBUG_OPT_CHECK(name, opt) \
+ CHECK_FLAG_ATOMIC(&(name)->flags, (opt)&DEBUG_OPT_ALL)
+
+/*
+ * Check if bits are set for a debug.
+ *
+ * MT-Safe
+ */
+#define DEBUG_FLAGS_CHECK(name, fl) CHECK_FLAG_ATOMIC(&(name)->flags, (fl))
+
+/*
+ * Set modes on a debug.
+ *
+ * MT-Safe
+ */
+#define DEBUG_MODE_SET(name, mode, onoff) \
+ do { \
+ if (onoff) \
+ SET_FLAG_ATOMIC(&(name)->flags, \
+ (mode)&DEBUG_MODE_ALL); \
+ else \
+ UNSET_FLAG_ATOMIC(&(name)->flags, \
+ (mode)&DEBUG_MODE_ALL); \
+ } while (0)
+
+/* Convenience macros for specific set operations. */
+#define DEBUG_MODE_ON(name, mode) DEBUG_MODE_SET(name, mode, true)
+#define DEBUG_MODE_OFF(name, mode) DEBUG_MODE_SET(name, mode, false)
+
+/*
+ * Set options on a debug.
+ *
+ * MT-Safe
+ */
+#define DEBUG_OPT_SET(name, opt, onoff) \
+ do { \
+ if (onoff) \
+ SET_FLAG_ATOMIC(&(name)->flags, (opt)&DEBUG_OPT_ALL); \
+ else \
+ UNSET_FLAG_ATOMIC(&(name)->flags, \
+ (opt)&DEBUG_OPT_ALL); \
+ } while (0)
+
+/* Convenience macros for specific set operations. */
+#define DEBUG_OPT_ON(name, opt) DEBUG_OPT_SET(name, opt, true)
+#define DEBUG_OPT_OFF(name, opt) DEBUG_OPT_SET(name, opt, true)
+
+/*
+ * Set bits on a debug.
+ *
+ * MT-Safe
+ */
+#define DEBUG_FLAGS_SET(name, fl, onoff) \
+ do { \
+ if (onoff) \
+ SET_FLAG_ATOMIC(&(name)->flags, (fl)); \
+ else \
+ UNSET_FLAG_ATOMIC(&(name)->flags, (fl)); \
+ } while (0)
+
+/* Convenience macros for specific set operations. */
+#define DEBUG_FLAGS_ON(name, fl) DEBUG_FLAGS_SET(&(name)->flags, (type), true)
+#define DEBUG_FLAGS_OFF(name, fl) DEBUG_FLAGS_SET(&(name)->flags, (type), false)
+
+/*
+ * Unset all modes and options on a debug.
+ *
+ * MT-Safe
+ */
+#define DEBUG_CLEAR(name) RESET_FLAG_ATOMIC(&(name)->flags)
+
+/*
+ * Set all modes and options on a debug.
+ *
+ * MT-Safe
+ */
+#define DEBUG_ON(name) \
+ SET_FLAG_ATOMIC(&(name)->flags, DEBUG_MODE_ALL | DEBUG_OPT_ALL)
+
+/*
+ * Map a vty node to the correct debugging mode flags. FRR behaves such that a
+ * debug statement issued under the config node persists to the config file,
+ * whereas the same debug statement issued from the enable node only persists
+ * for the current session.
+ *
+ * MT-Safe
+ */
+#define DEBUG_NODE2MODE(vtynode) \
+ (((vtynode) == CONFIG_NODE) ? DEBUG_MODE_ALL : DEBUG_MODE_TERM)
+
+/*
+ * Debug at the given level to the default logging destination.
+ *
+ * MT-Safe
+ */
+#define DEBUG(level, name, fmt, ...) \
+ do { \
+ if (DEBUG_MODE_CHECK(name, DEBUG_MODE_ALL)) \
+ zlog_##level(fmt, ##__VA_ARGS__); \
+ } while (0)
+
+/* Convenience macros for the various levels. */
+#define DEBUGE(name, fmt, ...) DEBUG(err, name, fmt, ##__VA_ARGS__)
+#define DEBUGW(name, fmt, ...) DEBUG(warn, name, fmt, ##__VA_ARGS__)
+#define DEBUGI(name, fmt, ...) DEBUG(info, name, fmt, ##__VA_ARGS__)
+#define DEBUGN(name, fmt, ...) DEBUG(notice, name, fmt, ##__VA_ARGS__)
+#define DEBUGD(name, fmt, ...) DEBUG(debug, name, fmt, ##__VA_ARGS__)
+
+/*
+ * Optional initializer for debugging. Highly recommended.
+ *
+ * This function installs common debugging commands and allows the caller to
+ * specify callbacks to take when these commands are issued, allowing the
+ * caller to respond to events such as a request to turn off all debugs.
+ *
+ * MT-Safe
+ */
+void debug_init(struct debug_callbacks *cb);
+
+/*
+ * Turn on the cli to turn on/off debugs.
+ * Should only be called by libfrr
+ */
+void debug_init_cli(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRRDEBUG_H */
diff --git a/lib/defaults.c b/lib/defaults.c
new file mode 100644
index 0000000..fe099b6
--- /dev/null
+++ b/lib/defaults.c
@@ -0,0 +1,230 @@
+/*
+ * FRR switchable defaults.
+ * Copyright (c) 2017-2019 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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 "defaults.h"
+#include "lib/version.h"
+
+static char df_version[128] = FRR_VER_SHORT, df_profile[128] = DFLT_NAME;
+static struct frr_default *dflt_first = NULL, **dflt_next = &dflt_first;
+
+/* these are global for all FRR daemons. they have to be, since we write an
+ * integrated config with the same value for all daemons.
+ */
+const char *frr_defaults_profiles[] = {
+ "traditional",
+ "datacenter",
+ NULL,
+};
+
+static int version_value(int ch)
+{
+ /* non-ASCII shouldn't happen */
+ if (ch < 0 || ch >= 128)
+ return 2;
+
+ /* ~foo sorts older than nothing */
+ if (ch == '~')
+ return 0;
+ if (ch == '\0')
+ return 1;
+ if (isalpha(ch))
+ return 0x100 + tolower(ch);
+
+ /* punctuation and digits (and everything else) */
+ return 0x200 + ch;
+}
+
+int frr_version_cmp(const char *aa, const char *bb)
+{
+ const char *apos = aa, *bpos = bb;
+
+ /* || is correct, we won't scan past the end of a string since that
+ * doesn't compare equal to anything else */
+ while (apos[0] || bpos[0]) {
+ if (isdigit((unsigned char)apos[0]) &&
+ isdigit((unsigned char)bpos[0])) {
+ unsigned long av, bv;
+ char *aend = NULL, *bend = NULL;
+
+ av = strtoul(apos, &aend, 10);
+ bv = strtoul(bpos, &bend, 10);
+ if (av < bv)
+ return -1;
+ if (av > bv)
+ return 1;
+
+ apos = aend;
+ bpos = bend;
+ continue;
+ }
+
+ int a = version_value(*apos++);
+ int b = version_value(*bpos++);
+
+ if (a < b)
+ return -1;
+ if (a > b)
+ return 1;
+ }
+ return 0;
+}
+
+static void frr_default_apply_one(struct frr_default *dflt, bool check);
+
+void frr_default_add(struct frr_default *dflt)
+{
+ dflt->next = NULL;
+ *dflt_next = dflt;
+ dflt_next = &dflt->next;
+
+ frr_default_apply_one(dflt, true);
+}
+
+static bool frr_match_version(const char *name, const char *vspec,
+ const char *version, bool check)
+{
+ int cmp;
+ static const struct spec {
+ const char *str;
+ int dir, eq;
+ } specs[] = {
+ {"<=", -1, 1},
+ {">=", 1, 1},
+ {"==", 0, 1},
+ {"<", -1, 0},
+ {">", 1, 0},
+ {"=", 0, 1},
+ {NULL, 0, 0},
+ };
+ const struct spec *s;
+
+ if (!vspec)
+ /* NULL = all versions */
+ return true;
+
+ for (s = specs; s->str; s++)
+ if (!strncmp(s->str, vspec, strlen(s->str)))
+ break;
+ if (!s->str) {
+ if (check)
+ fprintf(stderr, "invalid version specifier for %s: %s",
+ name, vspec);
+ /* invalid version spec, never matches */
+ return false;
+ }
+
+ vspec += strlen(s->str);
+ while (isspace((unsigned char)*vspec))
+ vspec++;
+
+ cmp = frr_version_cmp(version, vspec);
+ if (cmp == s->dir || (s->eq && cmp == 0))
+ return true;
+
+ return false;
+}
+
+static void frr_default_apply_one(struct frr_default *dflt, bool check)
+{
+ struct frr_default_entry *entry = dflt->entries;
+ struct frr_default_entry *dfltentry = NULL, *saveentry = NULL;
+
+ for (; entry->match_version || entry->match_profile; entry++) {
+ if (entry->match_profile
+ && strcmp(entry->match_profile, df_profile))
+ continue;
+
+ if (!dfltentry && frr_match_version(dflt->name,
+ entry->match_version, df_version, check))
+ dfltentry = entry;
+ if (!saveentry && frr_match_version(dflt->name,
+ entry->match_version, FRR_VER_SHORT, check))
+ saveentry = entry;
+
+ if (dfltentry && saveentry && !check)
+ break;
+ }
+ /* found default or arrived at last entry that has NULL,NULL spec */
+
+ if (!dfltentry)
+ dfltentry = entry;
+ if (!saveentry)
+ saveentry = entry;
+
+ if (dflt->dflt_bool)
+ *dflt->dflt_bool = dfltentry->val_bool;
+ if (dflt->dflt_str)
+ *dflt->dflt_str = dfltentry->val_str;
+ if (dflt->dflt_long)
+ *dflt->dflt_long = dfltentry->val_long;
+ if (dflt->dflt_ulong)
+ *dflt->dflt_ulong = dfltentry->val_ulong;
+ if (dflt->dflt_float)
+ *dflt->dflt_float = dfltentry->val_float;
+ if (dflt->save_bool)
+ *dflt->save_bool = saveentry->val_bool;
+ if (dflt->save_str)
+ *dflt->save_str = saveentry->val_str;
+ if (dflt->save_long)
+ *dflt->save_long = saveentry->val_long;
+ if (dflt->save_ulong)
+ *dflt->save_ulong = saveentry->val_ulong;
+ if (dflt->save_float)
+ *dflt->save_float = saveentry->val_float;
+}
+
+void frr_defaults_apply(void)
+{
+ struct frr_default *dflt;
+
+ for (dflt = dflt_first; dflt; dflt = dflt->next)
+ frr_default_apply_one(dflt, false);
+}
+
+bool frr_defaults_profile_valid(const char *profile)
+{
+ const char **p;
+
+ for (p = frr_defaults_profiles; *p; p++)
+ if (!strcmp(profile, *p))
+ return true;
+ return false;
+}
+
+const char *frr_defaults_version(void)
+{
+ return df_version;
+}
+
+const char *frr_defaults_profile(void)
+{
+ return df_profile;
+}
+
+void frr_defaults_version_set(const char *version)
+{
+ strlcpy(df_version, version, sizeof(df_version));
+ frr_defaults_apply();
+}
+
+void frr_defaults_profile_set(const char *profile)
+{
+ strlcpy(df_profile, profile, sizeof(df_profile));
+ frr_defaults_apply();
+}
diff --git a/lib/defaults.h b/lib/defaults.h
new file mode 100644
index 0000000..55250f0
--- /dev/null
+++ b/lib/defaults.h
@@ -0,0 +1,147 @@
+/*
+ * FRR switchable defaults.
+ * Copyright (C) 2017-2019 David Lamparter for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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_DEFAULTS_H
+#define _FRR_DEFAULTS_H
+
+#include <stdbool.h>
+
+#include "compiler.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* frr_default wraps information about a default that has different
+ * values depending on FRR version or default-set
+ *
+ * frr_default_entry describes one match rule and the resulting value;
+ * entries are evaluated in order and the first matching is used.
+ *
+ * If both match_version and match_profile are specified, they must both
+ * match. A NULL value matches everything.
+ */
+struct frr_default_entry {
+ /* syntax: "(<|<=|==|>=|>) [whitespace] version", e.g.
+ * ">= 6.1-dev" "<6.0"
+ */
+ const char *match_version;
+ /* exact profile string to compare against */
+ const char *match_profile;
+
+ /* value to use */
+ bool val_bool;
+ const char *val_str;
+ long val_long;
+ unsigned long val_ulong;
+ float val_float;
+};
+
+/* one struct frr_default exists for each malleable default value */
+struct frr_default {
+ struct frr_default *next;
+
+ /* for UI/debug use */
+ const char *name;
+
+ /* the following two sets of variables differ because the written
+ * config always targets the *current* FRR version
+ *
+ * e.g. if you load a config that has "frr version 5.0" on 6.0
+ * *dflt_long => set to the default value in 5.0
+ * *save_long => set to the default value in 6.0
+ * config save will write "frr version 6.0" with 6.0 defaults
+ */
+
+ /* variable holding the default value for reading/use */
+ bool *dflt_bool;
+ const char **dflt_str;
+ long *dflt_long;
+ unsigned long *dflt_ulong;
+ float *dflt_float;
+
+ /* variable to use when comparing for config save */
+ bool *save_bool;
+ const char **save_str;
+ long *save_long;
+ unsigned long *save_ulong;
+ float *save_float;
+
+ struct frr_default_entry entries[];
+};
+
+#define _FRR_CFG_DEFAULT(type, typname, varname, ...) \
+ static type DFLT_##varname; \
+ static type SAVE_##varname; \
+ static struct frr_default _dflt_##varname = { \
+ .name = #varname, \
+ .dflt_##typname = &DFLT_##varname, \
+ .save_##typname = &SAVE_##varname, \
+ .entries = { __VA_ARGS__ }, \
+ }; \
+ static void _dfltinit_##varname(void) \
+ __attribute__((_CONSTRUCTOR(1000))); \
+ static void _dfltinit_##varname(void) \
+ { \
+ frr_default_add(&_dflt_##varname); \
+ } \
+ MACRO_REQUIRE_SEMICOLON() /* end */
+
+/* use:
+ * FRR_CFG_DEFAULT_LONG(SHARP_BLUNTNESS,
+ * { .val_long = 2, .match_version = ">= 10.0" },
+ * { .val_long = 1, .match_profile = "datacenter" },
+ * { .val_long = 0 },
+ * )
+ *
+ * This will create DFLT_SHARP_BLUNTNESS and SAVE_SHARP_BLUNTNESS variables.
+ *
+ * Note: preprocessor defines cannot be used as variable names because they
+ * will be expanded and blow up with a compile error. Use an enum or add an
+ * extra _ at the beginning (e.g. _SHARP_BLUNTNESS => DFLT__SHARP_BLUNTNESS)
+ */
+#define FRR_CFG_DEFAULT_BOOL(varname, ...) \
+ _FRR_CFG_DEFAULT(bool, bool, varname, ## __VA_ARGS__)
+#define FRR_CFG_DEFAULT_LONG(varname, ...) \
+ _FRR_CFG_DEFAULT(long, long, varname, ## __VA_ARGS__)
+#define FRR_CFG_DEFAULT_ULONG(varname, ...) \
+ _FRR_CFG_DEFAULT(unsigned long, ulong, varname, ## __VA_ARGS__)
+#define FRR_CFG_DEFAULT_FLOAT(varname, ...) \
+ _FRR_CFG_DEFAULT(float, float, varname, ## __VA_ARGS__)
+#define FRR_CFG_DEFAULT_STR(varname, ...) \
+ _FRR_CFG_DEFAULT(const char *, str, varname, ## __VA_ARGS__)
+
+
+/* daemons don't need to call any of these, libfrr handles that */
+extern void frr_default_add(struct frr_default *dflt);
+extern void frr_defaults_version_set(const char *version);
+extern void frr_defaults_profile_set(const char *profile);
+extern const char *frr_defaults_version(void);
+extern const char *frr_defaults_profile(void);
+extern void frr_defaults_apply(void);
+
+extern const char *frr_defaults_profiles[];
+extern bool frr_defaults_profile_valid(const char *profile);
+
+/* like strcmp(), but with version ordering */
+extern int frr_version_cmp(const char *aa, const char *bb);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRR_DEFAULTS_H */
diff --git a/lib/defun_lex.l b/lib/defun_lex.l
new file mode 100644
index 0000000..af506f1
--- /dev/null
+++ b/lib/defun_lex.l
@@ -0,0 +1,293 @@
+/*
+ * clippy (CLI preparator in python) C pseudo-lexer
+ * Copyright (C) 2016-2017 David Lamparter for NetDEF, Inc.
+ *
+ * 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
+ */
+
+/* This is just enough of a lexer to make rough sense of a C source file.
+ * It handles C preprocessor directives, strings, and looks for FRR-specific
+ * idioms (aka DEFUN).
+ *
+ * There is some preliminary support for documentation comments for DEFUNs.
+ * They would look like this (note the ~): (replace \ by /)
+ *
+ * \*~ documentation for foobar_cmd
+ * * parameter does xyz
+ * *\
+ * DEFUN(foobar_cmd, ...)
+ *
+ * This is intended for user documentation / command reference. Don't put
+ * code documentation in it.
+ */
+
+%top{
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+}
+%{
+/* ignore harmless bugs in old versions of flex */
+#pragma GCC diagnostic ignored "-Wsign-compare"
+#pragma GCC diagnostic ignored "-Wunused-value"
+
+#include "config.h"
+#include <Python.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "command_graph.h"
+#include "clippy.h"
+
+#define ID 258
+#define PREPROC 259
+#define OPERATOR 260
+#define STRING 261
+#define COMMENT 262
+#define SPECIAL 263
+
+#define DEFUNNY 270
+#define INSTALL 271
+#define AUXILIARY 272
+
+int comment_link;
+char string_end;
+
+char *value;
+
+static void extendbuf(char **what, const char *arg)
+{
+ if (!*what)
+ *what = strdup(arg);
+ else {
+ size_t vall = strlen(*what), argl = strlen(arg);
+ *what = realloc(*what, vall + argl + 1);
+ memcpy(*what + vall, arg, argl);
+ (*what)[vall + argl] = '\0';
+ }
+}
+#define extend(x) extendbuf(&value, x)
+
+#ifndef __clang_analyzer__
+
+%}
+
+ID [A-Za-z0-9_]+
+OPERATOR [!%&/\[\]{}=?:^|\*.;><~'\\+-]
+SPECIAL [(),]
+
+%pointer
+%option yylineno
+%option noyywrap
+%option noinput
+%option nounput
+%option outfile="lib/defun_lex.c"
+%option prefix="def_yy"
+%option 8bit
+
+%s linestart
+%x comment
+%x linecomment
+%x preproc
+%x rstring
+%%
+ BEGIN(linestart);
+
+\n BEGIN(linestart);
+
+<INITIAL,linestart,preproc>"/*" comment_link = YY_START; extend(yytext); BEGIN(comment);
+<comment>[^*\n]* extend(yytext);
+<comment>"*"+[^*/\n]* extend(yytext);
+<comment>\n extend(yytext);
+<comment>"*"+"/" extend(yytext); BEGIN(comment_link); return COMMENT;
+
+<INITIAL,linestart,preproc>"//" comment_link = YY_START; extend(yytext); BEGIN(linecomment);
+<linecomment>[^\n]* extend(yytext);
+<linecomment>\n BEGIN((comment_link == INITIAL) ? linestart : comment_link); return COMMENT;
+
+<linestart># BEGIN(preproc);
+<preproc>\n BEGIN(INITIAL); return PREPROC;
+<preproc>[^\n\\]+ extend(yytext);
+<preproc>\\\n extend(yytext);
+<preproc>\\+[^\n] extend(yytext);
+
+[\"\'] string_end = yytext[0]; extend(yytext); BEGIN(rstring);
+<rstring>[\"\'] {
+ extend(yytext);
+ if (yytext[0] == string_end) {
+ BEGIN(INITIAL);
+ return STRING;
+ }
+ }
+<rstring>\\\n /* ignore */
+<rstring>\\. extend(yytext);
+<rstring>[^\\\"\']+ extend(yytext);
+
+"DEFUN" value = strdup(yytext); return DEFUNNY;
+"DEFUN_NOSH" value = strdup(yytext); return DEFUNNY;
+"DEFUN_HIDDEN" value = strdup(yytext); return DEFUNNY;
+"DEFPY" value = strdup(yytext); return DEFUNNY;
+"DEFPY_NOSH" value = strdup(yytext); return DEFUNNY;
+"DEFPY_ATTR" value = strdup(yytext); return DEFUNNY;
+"DEFPY_HIDDEN" value = strdup(yytext); return DEFUNNY;
+"DEFPY_YANG" value = strdup(yytext); return DEFUNNY;
+"DEFPY_YANG_NOSH" value = strdup(yytext); return DEFUNNY;
+"ALIAS" value = strdup(yytext); return DEFUNNY;
+"ALIAS_HIDDEN" value = strdup(yytext); return DEFUNNY;
+"install_element" value = strdup(yytext); return INSTALL;
+"VTYSH_TARGETS" value = strdup(yytext); return AUXILIARY;
+"VTYSH_NODESWITCH" value = strdup(yytext); return AUXILIARY;
+
+[ \t\n]+ /* ignore */
+\\ /* ignore */
+{ID} BEGIN(INITIAL); value = strdup(yytext); return ID;
+{OPERATOR} BEGIN(INITIAL); value = strdup(yytext); return OPERATOR;
+{SPECIAL} BEGIN(INITIAL); value = strdup(yytext); return SPECIAL;
+. /* printf("-- '%s' in init\n", yytext); */ BEGIN(INITIAL); return yytext[0];
+
+%%
+
+#endif /* __clang_analyzer__ */
+
+static int yylex_clr(char **retbuf)
+{
+ int rv = def_yylex();
+ *retbuf = value;
+ value = NULL;
+ return rv;
+}
+
+static PyObject *get_args(const char *filename, int lineno)
+{
+ PyObject *pyObj = PyList_New(0);
+ PyObject *pyArg = NULL;
+
+ char *tval;
+ int depth = 1;
+ int token;
+
+ while ((token = yylex_clr(&tval)) != YY_NULL) {
+ if (token == SPECIAL && tval[0] == '(') {
+ free(tval);
+ break;
+ }
+ if (token == COMMENT) {
+ free(tval);
+ continue;
+ }
+ fprintf(stderr, "invalid input!\n");
+ exit(1);
+ }
+
+ while ((token = yylex_clr(&tval)) != YY_NULL) {
+ if (token == COMMENT) {
+ free(tval);
+ continue;
+ }
+ if (token == PREPROC) {
+ free(tval);
+ Py_DECREF(pyObj);
+ return PyErr_Format(PyExc_ValueError,
+ "%s:%d: cannot process CPP directive within argument list",
+ filename, lineno);
+ }
+ if (token == SPECIAL) {
+ if (depth == 1 && (tval[0] == ',' || tval[0] == ')')) {
+ if (pyArg)
+ PyList_Append(pyObj, pyArg);
+ pyArg = NULL;
+ if (tval[0] == ')') {
+ free(tval);
+ break;
+ }
+ free(tval);
+ continue;
+ }
+ if (tval[0] == '(')
+ depth++;
+ if (tval[0] == ')')
+ depth--;
+ }
+ if (!pyArg)
+ pyArg = PyList_New(0);
+ PyList_Append(pyArg, PyUnicode_FromString(tval));
+ free(tval);
+ }
+ return pyObj;
+}
+
+/* _clippy.parse() -- read a C file, returning a list of interesting bits.
+ * note this ditches most of the actual C code. */
+PyObject *clippy_parse(PyObject *self, PyObject *args)
+{
+ const char *filename;
+ if (!PyArg_ParseTuple(args, "s", &filename))
+ return NULL;
+
+ FILE *fd = fopen(filename, "r");
+ if (!fd)
+ return PyErr_SetFromErrnoWithFilename(PyExc_IOError, filename);
+
+ char *tval;
+ int token;
+ yyin = fd;
+ value = NULL;
+
+ PyObject *pyCont = PyDict_New();
+ PyObject *pyObj = PyList_New(0);
+ PyDict_SetItemString(pyCont, "filename", PyUnicode_FromString(filename));
+ PyDict_SetItemString(pyCont, "data", pyObj);
+
+ while ((token = yylex_clr(&tval)) != YY_NULL) {
+ int lineno = yylineno;
+ PyObject *pyItem = NULL, *pyArgs;
+ switch (token) {
+ case DEFUNNY:
+ case INSTALL:
+ case AUXILIARY:
+ pyArgs = get_args(filename, lineno);
+ if (!pyArgs) {
+ free(tval);
+ Py_DECREF(pyCont);
+ return NULL;
+ }
+ pyItem = PyDict_New();
+ PyDict_SetItemString(pyItem, "type", PyUnicode_FromString(tval));
+ PyDict_SetItemString(pyItem, "args", pyArgs);
+ break;
+ case COMMENT:
+ if (strncmp(tval, "//~", 3) && strncmp(tval, "/*~", 3))
+ break;
+ pyItem = PyDict_New();
+ PyDict_SetItemString(pyItem, "type", PyUnicode_FromString("COMMENT"));
+ PyDict_SetItemString(pyItem, "line", PyUnicode_FromString(tval));
+ break;
+ case PREPROC:
+ pyItem = PyDict_New();
+ PyDict_SetItemString(pyItem, "type", PyUnicode_FromString("PREPROC"));
+ PyDict_SetItemString(pyItem, "line", PyUnicode_FromString(tval));
+ lineno--;
+ break;
+ }
+ if (pyItem) {
+ PyDict_SetItemString(pyItem, "lineno", PyLong_FromLong(lineno));
+ PyList_Append(pyObj, pyItem);
+ }
+ free(tval);
+ }
+ def_yylex_destroy();
+ fclose(fd);
+ return pyCont;
+}
diff --git a/lib/distribute.c b/lib/distribute.c
new file mode 100644
index 0000000..0f503d2
--- /dev/null
+++ b/lib/distribute.c
@@ -0,0 +1,488 @@
+/* Distribute list functions
+ * Copyright (C) 1998, 1999 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra 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 "hash.h"
+#include "if.h"
+#include "filter.h"
+#include "command.h"
+#include "distribute.h"
+#include "memory.h"
+
+DEFINE_MTYPE_STATIC(LIB, DISTRIBUTE_CTX, "Distribute ctx");
+DEFINE_MTYPE_STATIC(LIB, DISTRIBUTE, "Distribute list");
+DEFINE_MTYPE_STATIC(LIB, DISTRIBUTE_IFNAME, "Dist-list ifname");
+DEFINE_MTYPE_STATIC(LIB, DISTRIBUTE_NAME, "Dist-list name");
+
+static struct list *dist_ctx_list;
+
+static struct distribute *distribute_new(void)
+{
+ return XCALLOC(MTYPE_DISTRIBUTE, sizeof(struct distribute));
+}
+
+/* Free distribute object. */
+static void distribute_free(struct distribute *dist)
+{
+ int i = 0;
+
+ XFREE(MTYPE_DISTRIBUTE_IFNAME, dist->ifname);
+
+ for (i = 0; i < DISTRIBUTE_MAX; i++) {
+ XFREE(MTYPE_DISTRIBUTE_NAME, dist->list[i]);
+ }
+
+ for (i = 0; i < DISTRIBUTE_MAX; i++) {
+ XFREE(MTYPE_DISTRIBUTE_NAME, dist->prefix[i]);
+ }
+
+ XFREE(MTYPE_DISTRIBUTE, dist);
+}
+
+static void distribute_free_if_empty(struct distribute_ctx *ctx,
+ struct distribute *dist)
+{
+ int i;
+
+ for (i = 0; i < DISTRIBUTE_MAX; i++)
+ if (dist->list[i] != NULL || dist->prefix[i] != NULL)
+ return;
+
+ hash_release(ctx->disthash, dist);
+ distribute_free(dist);
+}
+
+/* Lookup interface's distribute list. */
+struct distribute *distribute_lookup(struct distribute_ctx *ctx,
+ const char *ifname)
+{
+ struct distribute key;
+ struct distribute *dist;
+
+ /* temporary reference */
+ key.ifname = (ifname) ? XSTRDUP(MTYPE_DISTRIBUTE_IFNAME, ifname) : NULL;
+
+ dist = hash_lookup(ctx->disthash, &key);
+
+ XFREE(MTYPE_DISTRIBUTE_IFNAME, key.ifname);
+
+ return dist;
+}
+
+void distribute_list_add_hook(struct distribute_ctx *ctx,
+ void (*func)(struct distribute_ctx *ctx,
+ struct distribute *))
+{
+ ctx->distribute_add_hook = func;
+}
+
+void distribute_list_delete_hook(struct distribute_ctx *ctx,
+ void (*func)(struct distribute_ctx *ctx,
+ struct distribute *))
+{
+ ctx->distribute_delete_hook = func;
+}
+
+static void *distribute_hash_alloc(struct distribute *arg)
+{
+ struct distribute *dist;
+
+ dist = distribute_new();
+ if (arg->ifname)
+ dist->ifname = XSTRDUP(MTYPE_DISTRIBUTE_IFNAME, arg->ifname);
+ else
+ dist->ifname = NULL;
+ return dist;
+}
+
+/* Make new distribute list and push into hash. */
+static struct distribute *distribute_get(struct distribute_ctx *ctx,
+ const char *ifname)
+{
+ struct distribute key;
+ struct distribute *ret;
+
+ /* temporary reference */
+ key.ifname = (ifname) ? XSTRDUP(MTYPE_DISTRIBUTE_IFNAME, ifname) : NULL;
+
+ ret = hash_get(ctx->disthash, &key,
+ (void *(*)(void *))distribute_hash_alloc);
+
+ XFREE(MTYPE_DISTRIBUTE_IFNAME, key.ifname);
+
+ return ret;
+}
+
+static unsigned int distribute_hash_make(const void *arg)
+{
+ const struct distribute *dist = arg;
+
+ return dist->ifname ? string_hash_make(dist->ifname) : 0;
+}
+
+/* If two distribute-list have same value then return 1 else return
+ 0. This function is used by hash package. */
+static bool distribute_cmp(const struct distribute *dist1,
+ const struct distribute *dist2)
+{
+ if (dist1->ifname && dist2->ifname)
+ if (strcmp(dist1->ifname, dist2->ifname) == 0)
+ return true;
+ if (!dist1->ifname && !dist2->ifname)
+ return true;
+ return false;
+}
+
+/* Set access-list name to the distribute list. */
+static void distribute_list_set(struct distribute_ctx *ctx,
+ const char *ifname, enum distribute_type type,
+ const char *alist_name)
+{
+ struct distribute *dist;
+
+ dist = distribute_get(ctx, ifname);
+
+ XFREE(MTYPE_DISTRIBUTE_NAME, dist->list[type]);
+ dist->list[type] = XSTRDUP(MTYPE_DISTRIBUTE_NAME, alist_name);
+
+ /* Apply this distribute-list to the interface. */
+ (ctx->distribute_add_hook)(ctx, dist);
+}
+
+/* Unset distribute-list. If matched distribute-list exist then
+ return 1. */
+static int distribute_list_unset(struct distribute_ctx *ctx,
+ const char *ifname,
+ enum distribute_type type,
+ const char *alist_name)
+{
+ struct distribute *dist;
+
+ dist = distribute_lookup(ctx, ifname);
+ if (!dist)
+ return 0;
+
+ if (!dist->list[type])
+ return 0;
+ if (strcmp(dist->list[type], alist_name) != 0)
+ return 0;
+
+ XFREE(MTYPE_DISTRIBUTE_NAME, dist->list[type]);
+
+ /* Apply this distribute-list to the interface. */
+ (ctx->distribute_delete_hook)(ctx, dist);
+
+ /* If all dist are NULL, then free distribute list. */
+ distribute_free_if_empty(ctx, dist);
+ return 1;
+}
+
+/* Set access-list name to the distribute list. */
+static void distribute_list_prefix_set(struct distribute_ctx *ctx,
+ const char *ifname,
+ enum distribute_type type,
+ const char *plist_name)
+{
+ struct distribute *dist;
+
+ dist = distribute_get(ctx, ifname);
+
+ XFREE(MTYPE_DISTRIBUTE_NAME, dist->prefix[type]);
+ dist->prefix[type] = XSTRDUP(MTYPE_DISTRIBUTE_NAME, plist_name);
+
+ /* Apply this distribute-list to the interface. */
+ (ctx->distribute_add_hook)(ctx, dist);
+}
+
+/* Unset distribute-list. If matched distribute-list exist then
+ return 1. */
+static int distribute_list_prefix_unset(struct distribute_ctx *ctx,
+ const char *ifname,
+ enum distribute_type type,
+ const char *plist_name)
+{
+ struct distribute *dist;
+
+ dist = distribute_lookup(ctx, ifname);
+ if (!dist)
+ return 0;
+
+ if (!dist->prefix[type])
+ return 0;
+ if (strcmp(dist->prefix[type], plist_name) != 0)
+ return 0;
+
+ XFREE(MTYPE_DISTRIBUTE_NAME, dist->prefix[type]);
+
+ /* Apply this distribute-list to the interface. */
+ (ctx->distribute_delete_hook)(ctx, dist);
+
+ /* If all dist are NULL, then free distribute list. */
+ distribute_free_if_empty(ctx, dist);
+ return 1;
+}
+
+static enum distribute_type distribute_direction(const char *dir, bool v4)
+{
+ if (dir[0] == 'i') {
+ if (v4)
+ return DISTRIBUTE_V4_IN;
+ else
+ return DISTRIBUTE_V6_IN;
+ } else if (dir[0] == 'o') {
+ if (v4)
+ return DISTRIBUTE_V4_OUT;
+ else
+ return DISTRIBUTE_V6_OUT;
+ }
+
+ assert(!"Expecting in or out only, fix your code");
+
+ __builtin_unreachable();
+}
+
+int distribute_list_parser(bool prefix, bool v4, const char *dir,
+ const char *list, const char *ifname)
+{
+ enum distribute_type type = distribute_direction(dir, v4);
+ struct distribute_ctx *ctx = listnode_head(dist_ctx_list);
+
+ void (*distfn)(struct distribute_ctx *, const char *,
+ enum distribute_type, const char *) =
+ prefix ? &distribute_list_prefix_set : &distribute_list_set;
+
+ distfn(ctx, ifname, type, list);
+
+ return CMD_SUCCESS;
+}
+
+int distribute_list_no_parser(struct vty *vty, bool prefix, bool v4,
+ const char *dir, const char *list,
+ const char *ifname)
+{
+ enum distribute_type type = distribute_direction(dir, v4);
+ struct distribute_ctx *ctx = listnode_head(dist_ctx_list);
+ int ret;
+
+ int (*distfn)(struct distribute_ctx *, const char *,
+ enum distribute_type, const char *) =
+ prefix ? &distribute_list_prefix_unset : &distribute_list_unset;
+
+
+ ret = distfn(ctx, ifname, type, list);
+ if (!ret) {
+ vty_out(vty, "distribute list doesn't exist\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ return CMD_SUCCESS;
+}
+
+static int distribute_print(struct vty *vty, char *tab[], int is_prefix,
+ enum distribute_type type, int has_print)
+{
+ if (tab[type]) {
+ vty_out(vty, "%s %s%s", has_print ? "," : "",
+ is_prefix ? "(prefix-list) " : "", tab[type]);
+ return 1;
+ }
+ return has_print;
+}
+
+int config_show_distribute(struct vty *vty, struct distribute_ctx *dist_ctxt)
+{
+ unsigned int i;
+ int has_print = 0;
+ struct hash_bucket *mp;
+ struct distribute *dist;
+
+ /* Output filter configuration. */
+ dist = distribute_lookup(dist_ctxt, NULL);
+ vty_out(vty, " Outgoing update filter list for all interface is");
+ has_print = 0;
+ if (dist) {
+ has_print = distribute_print(vty, dist->list, 0,
+ DISTRIBUTE_V4_OUT, has_print);
+ has_print = distribute_print(vty, dist->prefix, 1,
+ DISTRIBUTE_V4_OUT, has_print);
+ has_print = distribute_print(vty, dist->list, 0,
+ DISTRIBUTE_V6_OUT, has_print);
+ has_print = distribute_print(vty, dist->prefix, 1,
+ DISTRIBUTE_V6_OUT, has_print);
+ }
+ if (has_print)
+ vty_out(vty, "\n");
+ else
+ vty_out(vty, " not set\n");
+
+ for (i = 0; i < dist_ctxt->disthash->size; i++)
+ for (mp = dist_ctxt->disthash->index[i]; mp; mp = mp->next) {
+ dist = mp->data;
+ if (dist->ifname) {
+ vty_out(vty, " %s filtered by",
+ dist->ifname);
+ has_print = 0;
+ has_print = distribute_print(vty, dist->list, 0,
+ DISTRIBUTE_V4_OUT,
+ has_print);
+ has_print = distribute_print(
+ vty, dist->prefix, 1, DISTRIBUTE_V4_OUT,
+ has_print);
+ has_print = distribute_print(vty, dist->list, 0,
+ DISTRIBUTE_V6_OUT,
+ has_print);
+ has_print = distribute_print(
+ vty, dist->prefix, 1, DISTRIBUTE_V6_OUT,
+ has_print);
+ if (has_print)
+ vty_out(vty, "\n");
+ else
+ vty_out(vty, " nothing\n");
+ }
+ }
+
+
+ /* Input filter configuration. */
+ dist = distribute_lookup(dist_ctxt, NULL);
+ vty_out(vty, " Incoming update filter list for all interface is");
+ has_print = 0;
+ if (dist) {
+ has_print = distribute_print(vty, dist->list, 0,
+ DISTRIBUTE_V4_IN, has_print);
+ has_print = distribute_print(vty, dist->prefix, 1,
+ DISTRIBUTE_V4_IN, has_print);
+ has_print = distribute_print(vty, dist->list, 0,
+ DISTRIBUTE_V6_IN, has_print);
+ has_print = distribute_print(vty, dist->prefix, 1,
+ DISTRIBUTE_V6_IN, has_print);
+ }
+ if (has_print)
+ vty_out(vty, "\n");
+ else
+ vty_out(vty, " not set\n");
+
+ for (i = 0; i < dist_ctxt->disthash->size; i++)
+ for (mp = dist_ctxt->disthash->index[i]; mp; mp = mp->next) {
+ dist = mp->data;
+ if (dist->ifname) {
+ vty_out(vty, " %s filtered by",
+ dist->ifname);
+ has_print = 0;
+ has_print = distribute_print(vty, dist->list, 0,
+ DISTRIBUTE_V4_IN,
+ has_print);
+ has_print = distribute_print(
+ vty, dist->prefix, 1, DISTRIBUTE_V4_IN,
+ has_print);
+ has_print = distribute_print(vty, dist->list, 0,
+ DISTRIBUTE_V6_IN,
+ has_print);
+ has_print = distribute_print(
+ vty, dist->prefix, 1, DISTRIBUTE_V6_IN,
+ has_print);
+ if (has_print)
+ vty_out(vty, "\n");
+ else
+ vty_out(vty, " nothing\n");
+ }
+ }
+ return 0;
+}
+
+/* Configuration write function. */
+int config_write_distribute(struct vty *vty,
+ struct distribute_ctx *dist_ctxt)
+{
+ unsigned int i;
+ int j;
+ int output, v6;
+ struct hash_bucket *mp;
+ int write = 0;
+
+ for (i = 0; i < dist_ctxt->disthash->size; i++)
+ for (mp = dist_ctxt->disthash->index[i]; mp; mp = mp->next) {
+ struct distribute *dist;
+
+ dist = mp->data;
+
+ for (j = 0; j < DISTRIBUTE_MAX; j++)
+ if (dist->list[j]) {
+ output = j == DISTRIBUTE_V4_OUT
+ || j == DISTRIBUTE_V6_OUT;
+ v6 = j == DISTRIBUTE_V6_IN
+ || j == DISTRIBUTE_V6_OUT;
+ vty_out(vty,
+ " %sdistribute-list %s %s %s\n",
+ v6 ? "ipv6 " : "",
+ dist->list[j],
+ output ? "out" : "in",
+ dist->ifname ? dist->ifname
+ : "");
+ write++;
+ }
+
+ for (j = 0; j < DISTRIBUTE_MAX; j++)
+ if (dist->prefix[j]) {
+ output = j == DISTRIBUTE_V4_OUT
+ || j == DISTRIBUTE_V6_OUT;
+ v6 = j == DISTRIBUTE_V6_IN
+ || j == DISTRIBUTE_V6_OUT;
+ vty_out(vty,
+ " %sdistribute-list prefix %s %s %s\n",
+ v6 ? "ipv6 " : "",
+ dist->prefix[j],
+ output ? "out" : "in",
+ dist->ifname ? dist->ifname
+ : "");
+ write++;
+ }
+ }
+ return write;
+}
+
+void distribute_list_delete(struct distribute_ctx **ctx)
+{
+ if ((*ctx)->disthash) {
+ hash_clean((*ctx)->disthash, (void (*)(void *))distribute_free);
+ }
+ if (!dist_ctx_list)
+ dist_ctx_list = list_new();
+ listnode_delete(dist_ctx_list, *ctx);
+ if (list_isempty(dist_ctx_list))
+ list_delete(&dist_ctx_list);
+ XFREE(MTYPE_DISTRIBUTE_CTX, (*ctx));
+}
+
+/* Initialize distribute list container */
+struct distribute_ctx *distribute_list_ctx_create(struct vrf *vrf)
+{
+ struct distribute_ctx *ctx;
+
+ ctx = XCALLOC(MTYPE_DISTRIBUTE_CTX, sizeof(struct distribute_ctx));
+ ctx->vrf = vrf;
+ ctx->disthash = hash_create(
+ distribute_hash_make,
+ (bool (*)(const void *, const void *))distribute_cmp, NULL);
+ if (!dist_ctx_list)
+ dist_ctx_list = list_new();
+ listnode_add(dist_ctx_list, ctx);
+ return ctx;
+}
diff --git a/lib/distribute.h b/lib/distribute.h
new file mode 100644
index 0000000..6b3226e
--- /dev/null
+++ b/lib/distribute.h
@@ -0,0 +1,96 @@
+/* Distribute list functions header
+ * Copyright (C) 1999 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra 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 _ZEBRA_DISTRIBUTE_H
+#define _ZEBRA_DISTRIBUTE_H
+
+#include <zebra.h>
+#include "if.h"
+#include "filter.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Distribute list types. */
+enum distribute_type {
+ DISTRIBUTE_V4_IN,
+ DISTRIBUTE_V6_IN,
+ DISTRIBUTE_V4_OUT,
+ DISTRIBUTE_V6_OUT,
+ DISTRIBUTE_MAX
+};
+
+struct distribute {
+ /* Name of the interface. */
+ char *ifname;
+
+ /* Filter name of `in' and `out' */
+ char *list[DISTRIBUTE_MAX];
+
+ /* prefix-list name of `in' and `out' */
+ char *prefix[DISTRIBUTE_MAX];
+};
+
+struct distribute_ctx {
+ /* Hash of distribute list. */
+ struct hash *disthash;
+
+ /* Hook functions. */
+ void (*distribute_add_hook)(struct distribute_ctx *ctx,
+ struct distribute *dist);
+ void (*distribute_delete_hook)(struct distribute_ctx *ctx,
+ struct distribute *dist);
+
+ /* vrf information */
+ struct vrf *vrf;
+};
+
+/* Prototypes for distribute-list. */
+extern struct distribute_ctx *distribute_list_ctx_create(struct vrf *vrf);
+extern void distribute_list_delete(struct distribute_ctx **ctx);
+extern void distribute_list_add_hook(struct distribute_ctx *ctx,
+ void (*)(struct distribute_ctx *ctx,
+ struct distribute *));
+extern void distribute_list_delete_hook(struct distribute_ctx *ctx,
+ void (*)(struct distribute_ctx *ctx,
+ struct distribute *));
+extern struct distribute *distribute_lookup(struct distribute_ctx *ctx,
+ const char *ifname);
+extern int config_write_distribute(struct vty *vty,
+ struct distribute_ctx *ctx);
+extern int config_show_distribute(struct vty *vty,
+ struct distribute_ctx *ctx);
+
+extern enum filter_type distribute_apply_in(struct interface *,
+ struct prefix *);
+extern enum filter_type distribute_apply_out(struct interface *,
+ struct prefix *);
+
+extern int distribute_list_parser(bool prefix, bool v4, const char *dir,
+ const char *list, const char *ifname);
+extern int distribute_list_no_parser(struct vty *vty, bool prefix, bool v4,
+ const char *dir, const char *list,
+ const char *ifname);
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ZEBRA_DISTRIBUTE_H */
diff --git a/lib/elf_py.c b/lib/elf_py.c
new file mode 100644
index 0000000..7c503cf
--- /dev/null
+++ b/lib/elf_py.c
@@ -0,0 +1,1385 @@
+/*
+ * fast ELF file accessor
+ * Copyright (C) 2018-2020 David Lamparter for NetDEF, Inc.
+ *
+ * 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
+ */
+
+/* Note: this wrapper is intended to be used as build-time helper. While
+ * it should be generally correct and proper, there may be the occasional
+ * memory leak or SEGV for things that haven't been well-tested.
+ * _
+ * / \ This code is NOT SUITABLE FOR UNTRUSTED ELF FILES. It's used
+ * / ! \ in FRR to read files created by its own build. Don't take it out
+ * /_____\ of FRR and use it to parse random ELF files you found somewhere.
+ *
+ * If you're working with this code (or even reading it), you really need to
+ * read a bunch of the ELF specs. There's no way around it, things in here
+ * just represent pieces of ELF pretty much 1:1. Also, readelf & objdump are
+ * your friends.
+ *
+ * Required reading:
+ * https://refspecs.linuxfoundation.org/elf/elf.pdf
+ * https://refspecs.linuxfoundation.org/elf/x86_64-SysV-psABI.pdf
+ * Recommended reading:
+ * https://github.com/ARM-software/abi-aa/releases/download/2020Q4/aaelf64.pdf
+ *
+ * The core ELF spec is *not* enough, you should read at least one of the
+ * processor specific (psABI) docs. They define what & how relocations work.
+ * Luckily we don't need to care about the processor specifics since this only
+ * does data relocations, but without looking at the psABI, some things aren't
+ * quite clear.
+ */
+
+/* the API of this module roughly follows a very small subset of the one
+ * provided by the python elfutils package, which unfortunately is painfully
+ * slow.
+ */
+
+#define PY_SSIZE_T_CLEAN
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <Python.h>
+#include "structmember.h"
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+
+#if defined(__sun__) && (__SIZEOF_POINTER__ == 4)
+/* Solaris libelf bails otherwise ... */
+#undef _FILE_OFFSET_BITS
+#define _FILE_OFFSET_BITS 32
+#endif
+
+#include <elf.h>
+#include <libelf.h>
+#include <gelf.h>
+
+#include "typesafe.h"
+#include "jhash.h"
+#include "clippy.h"
+
+static bool debug;
+
+#define debugf(...) \
+ do { \
+ if (debug) \
+ fprintf(stderr, __VA_ARGS__); \
+ } while (0)
+
+/* Exceptions */
+static PyObject *ELFFormatError;
+static PyObject *ELFAccessError;
+
+/* most objects can only be created as return values from one of the methods */
+static PyObject *refuse_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+ PyErr_SetString(PyExc_ValueError,
+ "cannot create instances of this type");
+ return NULL;
+}
+
+struct elfreloc;
+struct elfsect;
+
+PREDECL_HASH(elfrelocs);
+
+/* ELFFile and ELFSection intentionally share some behaviour, particularly
+ * subscript[123:456] access to file data. This is because relocatables
+ * (.o files) do things section-based, but linked executables/libraries do
+ * things file-based. Having the two behave similar allows simplifying the
+ * Python code.
+ */
+
+/* class ELFFile:
+ *
+ * overall entry point, instantiated by reading in an ELF file
+ */
+struct elffile {
+ PyObject_HEAD
+
+ char *filename;
+ char *mmap, *mmend;
+ size_t len;
+ Elf *elf;
+
+ /* note from here on there are several instances of
+ *
+ * GElf_Something *x, _x;
+ *
+ * this is a pattern used by libelf's generic ELF routines; the _x
+ * field is used to create a copy of the ELF structure from the file
+ * with 32/64bit and endianness adjusted.
+ */
+
+ GElf_Ehdr *ehdr, _ehdr;
+ Elf_Scn *symtab;
+ size_t nsym, symstridx;
+ Elf_Data *symdata;
+
+ PyObject **sects;
+ size_t n_sect;
+
+ struct elfrelocs_head dynrelocs;
+
+ int elfclass;
+ bool bigendian;
+ bool has_symbols;
+};
+
+/* class ELFSection:
+ *
+ * note that executables and shared libraries can have their section headers
+ * removed, though in practice this is only used as an obfuscation technique.
+ */
+struct elfsect {
+ PyObject_HEAD
+
+ const char *name;
+ struct elffile *ef;
+
+ GElf_Shdr _shdr, *shdr;
+ Elf_Scn *scn;
+ unsigned long idx, len;
+
+ struct elfrelocs_head relocs;
+};
+
+/* class ELFReloc:
+ *
+ * note: relocations in object files (.o) are section-based while relocations
+ * in executables and shared libraries are file-based.
+ *
+ * Whenever accessing something that is a pointer in the ELF file, the Python
+ * code needs to check for a relocation; if the pointer is pointing to some
+ * unresolved symbol the file will generally contain 0 bytes. The relocation
+ * will tell what the pointer is actually pointing to.
+ *
+ * This represents both static (.o file) and dynamic (.so/exec) relocations.
+ */
+struct elfreloc {
+ PyObject_HEAD
+
+ struct elfrelocs_item elfrelocs_item;
+
+ struct elfsect *es;
+ struct elffile *ef;
+
+ /* there's also old-fashioned GElf_Rel; we're converting that to
+ * GElf_Rela in elfsect_add_relocations()
+ */
+ GElf_Rela _rela, *rela;
+ GElf_Sym _sym, *sym;
+ size_t symidx;
+ const char *symname;
+
+ /* documented below in python docstrings */
+ bool symvalid, unresolved, relative;
+ unsigned long long st_value;
+};
+
+static int elfreloc_cmp(const struct elfreloc *a, const struct elfreloc *b);
+static uint32_t elfreloc_hash(const struct elfreloc *reloc);
+
+DECLARE_HASH(elfrelocs, struct elfreloc, elfrelocs_item,
+ elfreloc_cmp, elfreloc_hash);
+
+static Elf_Scn *elf_find_addr(struct elffile *ef, uint64_t addr, size_t *idx);
+static PyObject *elffile_secbyidx(struct elffile *w, Elf_Scn *scn, size_t idx);
+static PyObject *elfreloc_getsection(PyObject *self, PyObject *args);
+static PyObject *elfreloc_getaddend(PyObject *obj, void *closure);
+
+/* --- end of declarations -------------------------------------------------- */
+
+/*
+ * class ELFReloc:
+ */
+
+static const char elfreloc_doc[] =
+ "Represents an ELF relocation record\n"
+ "\n"
+ "(struct elfreloc * in elf_py.c)";
+
+#define member(name, type, doc) \
+ { \
+ (char *)#name, type, offsetof(struct elfreloc, name), READONLY,\
+ (char *)doc "\n\n(\"" #name "\", " #type " in elf_py.c)" \
+ }
+static PyMemberDef members_elfreloc[] = {
+ member(symname, T_STRING,
+ "Name of symbol this relocation refers to.\n"
+ "\n"
+ "Will frequently be `None` in executables and shared libraries."
+ ),
+ member(symvalid, T_BOOL,
+ "Target symbol has a valid type, i.e. not STT_NOTYPE"),
+ member(unresolved, T_BOOL,
+ "Target symbol refers to an existing section"),
+ member(relative, T_BOOL,
+ "Relocation is a REL (not RELA) record and thus relative."),
+ member(st_value, T_ULONGLONG,
+ "Target symbol's value, if known\n\n"
+ "Will be zero for unresolved/external symbols."),
+ {}
+};
+#undef member
+
+static PyGetSetDef getset_elfreloc[] = {
+ { .name = (char *)"r_addend", .get = elfreloc_getaddend, .doc =
+ (char *)"Relocation addend value"},
+ {}
+};
+
+static PyMethodDef methods_elfreloc[] = {
+ {"getsection", elfreloc_getsection, METH_VARARGS,
+ "Find relocation target's ELF section\n\n"
+ "Args: address of relocatee (TODO: fix/remove?)\n"
+ "Returns: ELFSection or None\n\n"
+ "Not possible if section headers have been stripped."},
+ {}
+};
+
+static int elfreloc_cmp(const struct elfreloc *a, const struct elfreloc *b)
+{
+ if (a->rela->r_offset < b->rela->r_offset)
+ return -1;
+ if (a->rela->r_offset > b->rela->r_offset)
+ return 1;
+ return 0;
+}
+
+static uint32_t elfreloc_hash(const struct elfreloc *reloc)
+{
+ return jhash(&reloc->rela->r_offset, sizeof(reloc->rela->r_offset),
+ 0xc9a2b7f4);
+}
+
+static struct elfreloc *elfrelocs_get(struct elfrelocs_head *head,
+ GElf_Addr offset)
+{
+ struct elfreloc dummy;
+
+ dummy.rela = &dummy._rela;
+ dummy.rela->r_offset = offset;
+ return elfrelocs_find(head, &dummy);
+}
+
+static PyObject *elfreloc_getsection(PyObject *self, PyObject *args)
+{
+ struct elfreloc *w = (struct elfreloc *)self;
+ long data;
+
+ if (!PyArg_ParseTuple(args, "k", &data))
+ return NULL;
+
+ if (!w->es)
+ Py_RETURN_NONE;
+
+ if (!w->symvalid || w->symidx == 0) {
+ size_t idx = 0;
+ Elf_Scn *scn;
+
+ data = (w->relative ? data : 0) + w->rela->r_addend;
+ scn = elf_find_addr(w->es->ef, data, &idx);
+ if (!scn)
+ Py_RETURN_NONE;
+ return elffile_secbyidx(w->es->ef, scn, idx);
+ }
+ return elffile_secbyidx(w->es->ef, NULL, w->sym->st_shndx);
+}
+
+static PyObject *elfreloc_getaddend(PyObject *obj, void *closure)
+{
+ struct elfreloc *w = (struct elfreloc *)obj;
+
+ return Py_BuildValue("K", (unsigned long long)w->rela->r_addend);
+}
+
+static PyObject *elfreloc_repr(PyObject *arg)
+{
+ struct elfreloc *w = (struct elfreloc *)arg;
+
+ return PyUnicode_FromFormat("<ELFReloc @%lu %s+%lu>",
+ (unsigned long)w->rela->r_offset,
+ (w->symname && w->symname[0]) ? w->symname
+ : "[0]",
+ (unsigned long)w->rela->r_addend);
+}
+
+static void elfreloc_free(void *arg)
+{
+ struct elfreloc *w = arg;
+
+ (void)w;
+}
+
+static PyTypeObject typeobj_elfreloc = {
+ PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_clippy.ELFReloc",
+ .tp_basicsize = sizeof(struct elfreloc),
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_doc = elfreloc_doc,
+ .tp_new = refuse_new,
+ .tp_free = elfreloc_free,
+ .tp_repr = elfreloc_repr,
+ .tp_members = members_elfreloc,
+ .tp_methods = methods_elfreloc,
+ .tp_getset = getset_elfreloc,
+};
+
+/*
+ * class ELFSection:
+ */
+
+static const char elfsect_doc[] =
+ "Represents an ELF section\n"
+ "\n"
+ "To access section contents, use subscript notation, e.g.\n"
+ " section[123:456]\n"
+ "To read null terminated C strings, replace the end with str:\n"
+ " section[123:str]\n\n"
+ "(struct elfsect * in elf_py.c)";
+
+static PyObject *elfsect_getaddr(PyObject *self, void *closure);
+
+#define member(name, type, doc) \
+ { \
+ (char *)#name, type, offsetof(struct elfsect, name), READONLY, \
+ (char *)doc "\n\n(\"" #name "\", " #type " in elf_py.c)" \
+ }
+static PyMemberDef members_elfsect[] = {
+ member(name, T_STRING,
+ "Section name, e.g. \".text\""),
+ member(idx, T_ULONG,
+ "Section index in file"),
+ member(len, T_ULONG,
+ "Section length in bytes"),
+ {},
+};
+#undef member
+
+static PyGetSetDef getset_elfsect[] = {
+ { .name = (char *)"sh_addr", .get = elfsect_getaddr, .doc =
+ (char *)"Section virtual address (mapped program view)"},
+ {}
+};
+
+static PyObject *elfsect_getaddr(PyObject *self, void *closure)
+{
+ struct elfsect *w = (struct elfsect *)self;
+
+ return Py_BuildValue("K", (unsigned long long)w->shdr->sh_addr);
+}
+
+
+static PyObject *elfsect_getreloc(PyObject *self, PyObject *args)
+{
+ struct elfsect *w = (struct elfsect *)self;
+ struct elfreloc *relw;
+ unsigned long offs;
+ PyObject *ret;
+
+ if (!PyArg_ParseTuple(args, "k", &offs))
+ return NULL;
+
+ relw = elfrelocs_get(&w->relocs, offs + w->shdr->sh_addr);
+ if (!relw)
+ Py_RETURN_NONE;
+
+ ret = (PyObject *)relw;
+ Py_INCREF(ret);
+ return ret;
+}
+
+static PyMethodDef methods_elfsect[] = {
+ {"getreloc", elfsect_getreloc, METH_VARARGS,
+ "Check for / get relocation at offset into section\n\n"
+ "Args: byte offset into section to check\n"
+ "Returns: ELFReloc or None"},
+ {}
+};
+
+static PyObject *elfsect_subscript(PyObject *self, PyObject *key)
+{
+ Py_ssize_t start, stop, step, sllen;
+ struct elfsect *w = (struct elfsect *)self;
+ PySliceObject *slice;
+ unsigned long offs, len = ~0UL;
+
+ if (!PySlice_Check(key)) {
+ PyErr_SetString(PyExc_IndexError,
+ "ELFSection subscript must be slice");
+ return NULL;
+ }
+ slice = (PySliceObject *)key;
+ if (PyLong_Check(slice->stop)) {
+ if (PySlice_GetIndicesEx(key, w->shdr->sh_size,
+ &start, &stop, &step, &sllen))
+ return NULL;
+
+ if (step != 1) {
+ PyErr_SetString(PyExc_IndexError,
+ "ELFSection subscript slice step must be 1");
+ return NULL;
+ }
+ if ((GElf_Xword)stop > w->shdr->sh_size) {
+ PyErr_Format(ELFAccessError,
+ "access (%lu) beyond end of section %lu/%s (%lu)",
+ stop, w->idx, w->name, w->shdr->sh_size);
+ return NULL;
+ }
+
+ offs = start;
+ len = sllen;
+ } else {
+ if (slice->stop != (void *)&PyUnicode_Type
+ || !PyLong_Check(slice->start)) {
+ PyErr_SetString(PyExc_IndexError, "invalid slice");
+ return NULL;
+ }
+
+ offs = PyLong_AsUnsignedLongLong(slice->start);
+ len = ~0UL;
+ }
+
+ offs += w->shdr->sh_offset;
+ if (offs > w->ef->len) {
+ PyErr_Format(ELFAccessError,
+ "access (%lu) beyond end of file (%lu)",
+ offs, w->ef->len);
+ return NULL;
+ }
+ if (len == ~0UL)
+ len = strnlen(w->ef->mmap + offs, w->ef->len - offs);
+
+ Py_ssize_t pylen = len;
+
+#if PY_MAJOR_VERSION >= 3
+ return Py_BuildValue("y#", w->ef->mmap + offs, pylen);
+#else
+ return Py_BuildValue("s#", w->ef->mmap + offs, pylen);
+#endif
+}
+
+static PyMappingMethods mp_elfsect = {
+ .mp_subscript = elfsect_subscript,
+};
+
+static void elfsect_free(void *arg)
+{
+ struct elfsect *w = arg;
+
+ (void)w;
+}
+
+static PyObject *elfsect_repr(PyObject *arg)
+{
+ struct elfsect *w = (struct elfsect *)arg;
+
+ return PyUnicode_FromFormat("<ELFSection %s>", w->name);
+}
+
+static PyTypeObject typeobj_elfsect = {
+ PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_clippy.ELFSection",
+ .tp_basicsize = sizeof(struct elfsect),
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_doc = elfsect_doc,
+ .tp_new = refuse_new,
+ .tp_free = elfsect_free,
+ .tp_repr = elfsect_repr,
+ .tp_as_mapping = &mp_elfsect,
+ .tp_members = members_elfsect,
+ .tp_methods = methods_elfsect,
+ .tp_getset = getset_elfsect,
+};
+
+static void elfsect_add_relocations(struct elfsect *w, Elf_Scn *rel,
+ GElf_Shdr *relhdr)
+{
+ size_t i, entries;
+ Elf_Scn *symtab = elf_getscn(w->ef->elf, relhdr->sh_link);
+ GElf_Shdr _symhdr, *symhdr = gelf_getshdr(symtab, &_symhdr);
+ Elf_Data *symdata = elf_getdata(symtab, NULL);
+ Elf_Data *reldata = elf_getdata(rel, NULL);
+
+ entries = relhdr->sh_size / relhdr->sh_entsize;
+ for (i = 0; i < entries; i++) {
+ struct elfreloc *relw;
+ size_t symidx;
+ GElf_Rela *rela;
+ GElf_Sym *sym;
+
+ relw = (struct elfreloc *)typeobj_elfreloc.tp_alloc(
+ &typeobj_elfreloc, 0);
+ relw->es = w;
+
+ if (relhdr->sh_type == SHT_REL) {
+ GElf_Rel _rel, *rel;
+
+ rel = gelf_getrel(reldata, i, &_rel);
+ relw->rela = &relw->_rela;
+ relw->rela->r_offset = rel->r_offset;
+ relw->rela->r_info = rel->r_info;
+ relw->rela->r_addend = 0;
+ relw->relative = true;
+ } else
+ relw->rela = gelf_getrela(reldata, i, &relw->_rela);
+
+ rela = relw->rela;
+ if (rela->r_offset < w->shdr->sh_addr
+ || rela->r_offset >= w->shdr->sh_addr + w->shdr->sh_size)
+ continue;
+
+ symidx = relw->symidx = GELF_R_SYM(rela->r_info);
+ sym = relw->sym = gelf_getsym(symdata, symidx, &relw->_sym);
+ if (sym) {
+ relw->symname = elf_strptr(w->ef->elf, symhdr->sh_link,
+ sym->st_name);
+ relw->symvalid = GELF_ST_TYPE(sym->st_info)
+ != STT_NOTYPE;
+ relw->unresolved = sym->st_shndx == SHN_UNDEF;
+ relw->st_value = sym->st_value;
+ } else {
+ relw->symname = NULL;
+ relw->symvalid = false;
+ relw->unresolved = false;
+ relw->st_value = 0;
+ }
+
+ debugf("reloc @ %016llx sym %5llu %016llx %s\n",
+ (long long)rela->r_offset, (unsigned long long)symidx,
+ (long long)rela->r_addend, relw->symname);
+
+ elfrelocs_add(&w->relocs, relw);
+ }
+}
+
+/*
+ * bindings & loading code between ELFFile and ELFSection
+ */
+
+static PyObject *elfsect_wrap(struct elffile *ef, Elf_Scn *scn, size_t idx,
+ const char *name)
+{
+ struct elfsect *w;
+ size_t i;
+
+ w = (struct elfsect *)typeobj_elfsect.tp_alloc(&typeobj_elfsect, 0);
+ if (!w)
+ return NULL;
+
+ w->name = name;
+ w->ef = ef;
+ w->scn = scn;
+ w->shdr = gelf_getshdr(scn, &w->_shdr);
+ w->len = w->shdr->sh_size;
+ w->idx = idx;
+ elfrelocs_init(&w->relocs);
+
+ for (i = 0; i < ef->ehdr->e_shnum; i++) {
+ Elf_Scn *scn = elf_getscn(ef->elf, i);
+ GElf_Shdr _shdr, *shdr = gelf_getshdr(scn, &_shdr);
+
+ if (shdr->sh_type != SHT_RELA && shdr->sh_type != SHT_REL)
+ continue;
+ if (shdr->sh_info && shdr->sh_info != idx)
+ continue;
+ elfsect_add_relocations(w, scn, shdr);
+ }
+
+ return (PyObject *)w;
+}
+
+static Elf_Scn *elf_find_section(struct elffile *ef, const char *name,
+ size_t *idx)
+{
+ size_t i;
+ const char *secname;
+
+ for (i = 0; i < ef->ehdr->e_shnum; i++) {
+ Elf_Scn *scn = elf_getscn(ef->elf, i);
+ GElf_Shdr _shdr, *shdr = gelf_getshdr(scn, &_shdr);
+
+ secname = elf_strptr(ef->elf, ef->ehdr->e_shstrndx,
+ shdr->sh_name);
+ if (strcmp(secname, name))
+ continue;
+ if (idx)
+ *idx = i;
+ return scn;
+ }
+ return NULL;
+}
+
+static Elf_Scn *elf_find_addr(struct elffile *ef, uint64_t addr, size_t *idx)
+{
+ size_t i;
+
+ for (i = 0; i < ef->ehdr->e_shnum; i++) {
+ Elf_Scn *scn = elf_getscn(ef->elf, i);
+ GElf_Shdr _shdr, *shdr = gelf_getshdr(scn, &_shdr);
+
+ /* virtual address is kinda meaningless for TLS sections */
+ if (shdr->sh_flags & SHF_TLS)
+ continue;
+ if (addr < shdr->sh_addr ||
+ addr >= shdr->sh_addr + shdr->sh_size)
+ continue;
+
+ if (idx)
+ *idx = i;
+ return scn;
+ }
+ return NULL;
+}
+
+/*
+ * class ELFFile:
+ */
+
+static const char elffile_doc[] =
+ "Represents an ELF file\n"
+ "\n"
+ "Args: filename to load\n"
+ "\n"
+ "To access raw file contents, use subscript notation, e.g.\n"
+ " file[123:456]\n"
+ "To read null terminated C strings, replace the end with str:\n"
+ " file[123:str]\n\n"
+ "(struct elffile * in elf_py.c)";
+
+
+#define member(name, type, doc) \
+ { \
+ (char *)#name, type, offsetof(struct elffile, name), READONLY, \
+ (char *)doc "\n\n(\"" #name "\", " #type " in elf_py.c)" \
+ }
+static PyMemberDef members_elffile[] = {
+ member(filename, T_STRING,
+ "Original file name as given when opening"),
+ member(elfclass, T_INT,
+ "ELF class (architecture bit size)\n\n"
+ "Either 32 or 64, straight integer."),
+ member(bigendian, T_BOOL,
+ "ELF file is big-endian\n\n"
+ "All internal ELF structures are automatically converted."),
+ member(has_symbols, T_BOOL,
+ "A symbol section is present\n\n"
+ "Note: only refers to .symtab/SHT_SYMTAB section, not DT_SYMTAB"
+ ),
+ {},
+};
+#undef member
+
+static PyObject *elffile_secbyidx(struct elffile *w, Elf_Scn *scn, size_t idx)
+{
+ const char *name;
+ PyObject *ret;
+
+ if (!scn)
+ scn = elf_getscn(w->elf, idx);
+ if (!scn || idx >= w->n_sect)
+ Py_RETURN_NONE;
+
+ if (!w->sects[idx]) {
+ GElf_Shdr _shdr, *shdr = gelf_getshdr(scn, &_shdr);
+
+ name = elf_strptr(w->elf, w->ehdr->e_shstrndx, shdr->sh_name);
+ w->sects[idx] = elfsect_wrap(w, scn, idx, name);
+ }
+
+ ret = w->sects[idx];
+ Py_INCREF(ret);
+ return ret;
+}
+
+static PyObject *elffile_get_section(PyObject *self, PyObject *args)
+{
+ const char *name;
+ struct elffile *w = (struct elffile *)self;
+ Elf_Scn *scn;
+ size_t idx = 0;
+
+ if (!PyArg_ParseTuple(args, "s", &name))
+ return NULL;
+
+ scn = elf_find_section(w, name, &idx);
+ return elffile_secbyidx(w, scn, idx);
+}
+
+static PyObject *elffile_get_section_addr(PyObject *self, PyObject *args)
+{
+ unsigned long long addr;
+ struct elffile *w = (struct elffile *)self;
+ Elf_Scn *scn;
+ size_t idx = 0;
+
+ if (!PyArg_ParseTuple(args, "K", &addr))
+ return NULL;
+
+ scn = elf_find_addr(w, addr, &idx);
+ return elffile_secbyidx(w, scn, idx);
+}
+
+static PyObject *elffile_get_section_idx(PyObject *self, PyObject *args)
+{
+ unsigned long long idx;
+ struct elffile *w = (struct elffile *)self;
+
+ if (!PyArg_ParseTuple(args, "K", &idx))
+ return NULL;
+
+ return elffile_secbyidx(w, NULL, idx);
+}
+
+static PyObject *elffile_get_symbol(PyObject *self, PyObject *args)
+{
+ const char *name, *symname;
+ struct elffile *w = (struct elffile *)self;
+ GElf_Sym _sym, *sym;
+ size_t i;
+
+ if (!PyArg_ParseTuple(args, "s", &name))
+ return NULL;
+
+ for (i = 0; i < w->nsym; i++) {
+ sym = gelf_getsym(w->symdata, i, &_sym);
+ if (sym->st_name == 0)
+ continue;
+ symname = elf_strptr(w->elf, w->symstridx, sym->st_name);
+ if (strcmp(symname, name))
+ continue;
+
+ PyObject *pysect;
+ Elf_Scn *scn = elf_getscn(w->elf, sym->st_shndx);
+
+ if (scn)
+ pysect = elffile_secbyidx(w, scn, sym->st_shndx);
+ else {
+ pysect = Py_None;
+ Py_INCREF(pysect);
+ }
+ return Py_BuildValue("sKN", symname,
+ (unsigned long long)sym->st_value, pysect);
+ }
+ Py_RETURN_NONE;
+}
+
+static PyObject *elffile_getreloc(PyObject *self, PyObject *args)
+{
+ struct elffile *w = (struct elffile *)self;
+ struct elfreloc *relw;
+ unsigned long offs;
+ PyObject *ret;
+
+ if (!PyArg_ParseTuple(args, "k", &offs))
+ return NULL;
+
+ relw = elfrelocs_get(&w->dynrelocs, offs);
+ if (!relw)
+ Py_RETURN_NONE;
+
+ ret = (PyObject *)relw;
+ Py_INCREF(ret);
+ return ret;
+}
+
+static PyObject *elffile_find_note(PyObject *self, PyObject *args)
+{
+#if defined(HAVE_GELF_GETNOTE) && defined(HAVE_ELF_GETDATA_RAWCHUNK)
+ const char *owner;
+ const uint8_t *ids;
+ GElf_Word id;
+ struct elffile *w = (struct elffile *)self;
+ size_t i;
+
+ if (!PyArg_ParseTuple(args, "ss", &owner, &ids))
+ return NULL;
+
+ if (strlen((char *)ids) != 4) {
+ PyErr_SetString(PyExc_ValueError,
+ "ELF note ID must be exactly 4-byte string");
+ return NULL;
+ }
+ if (w->bigendian)
+ id = (ids[0] << 24) | (ids[1] << 16) | (ids[2] << 8) | ids[3];
+ else
+ id = (ids[3] << 24) | (ids[2] << 16) | (ids[1] << 8) | ids[0];
+
+ for (i = 0; i < w->ehdr->e_phnum; i++) {
+ GElf_Phdr _phdr, *phdr = gelf_getphdr(w->elf, i, &_phdr);
+ Elf_Data *notedata;
+ size_t offset;
+
+ if (phdr->p_type != PT_NOTE)
+ continue;
+
+ notedata = elf_getdata_rawchunk(w->elf, phdr->p_offset,
+ phdr->p_filesz, ELF_T_NHDR);
+
+ GElf_Nhdr nhdr[1];
+ size_t nameoffs, dataoffs;
+
+ offset = 0;
+ while ((offset = gelf_getnote(notedata, offset, nhdr,
+ &nameoffs, &dataoffs))) {
+ if (phdr->p_offset + nameoffs >= w->len)
+ continue;
+
+ const char *name = w->mmap + phdr->p_offset + nameoffs;
+
+ if (strcmp(name, owner))
+ continue;
+ if (id != nhdr->n_type)
+ continue;
+
+ PyObject *s, *e;
+
+ s = PyLong_FromUnsignedLongLong(
+ phdr->p_vaddr + dataoffs);
+ e = PyLong_FromUnsignedLongLong(
+ phdr->p_vaddr + dataoffs + nhdr->n_descsz);
+ return PySlice_New(s, e, NULL);
+ }
+ }
+#endif
+ Py_RETURN_NONE;
+}
+
+#ifdef HAVE_ELF_GETDATA_RAWCHUNK
+static bool elffile_virt2file(struct elffile *w, GElf_Addr virt,
+ GElf_Addr *offs)
+{
+ *offs = 0;
+
+ for (size_t i = 0; i < w->ehdr->e_phnum; i++) {
+ GElf_Phdr _phdr, *phdr = gelf_getphdr(w->elf, i, &_phdr);
+
+ if (phdr->p_type != PT_LOAD)
+ continue;
+
+ if (virt < phdr->p_vaddr
+ || virt >= phdr->p_vaddr + phdr->p_memsz)
+ continue;
+
+ if (virt >= phdr->p_vaddr + phdr->p_filesz)
+ return false;
+
+ *offs = virt - phdr->p_vaddr + phdr->p_offset;
+ return true;
+ }
+
+ return false;
+}
+#endif /* HAVE_ELF_GETDATA_RAWCHUNK */
+
+static PyObject *elffile_subscript(PyObject *self, PyObject *key)
+{
+ Py_ssize_t start, stop, step;
+ PySliceObject *slice;
+ struct elffile *w = (struct elffile *)self;
+ bool str = false;
+
+ if (!PySlice_Check(key)) {
+ PyErr_SetString(PyExc_IndexError,
+ "ELFFile subscript must be slice");
+ return NULL;
+ }
+ slice = (PySliceObject *)key;
+ stop = -1;
+ step = 1;
+ if (PyLong_Check(slice->stop)) {
+ start = PyLong_AsSsize_t(slice->start);
+ if (PyErr_Occurred())
+ return NULL;
+ if (slice->stop != Py_None) {
+ stop = PyLong_AsSsize_t(slice->stop);
+ if (PyErr_Occurred())
+ return NULL;
+ }
+ if (slice->step != Py_None) {
+ step = PyLong_AsSsize_t(slice->step);
+ if (PyErr_Occurred())
+ return NULL;
+ }
+ } else {
+ if (slice->stop != (void *)&PyUnicode_Type
+ || !PyLong_Check(slice->start)) {
+ PyErr_SetString(PyExc_IndexError, "invalid slice");
+ return NULL;
+ }
+
+ str = true;
+ start = PyLong_AsUnsignedLongLong(slice->start);
+ }
+ if (step != 1) {
+ PyErr_SetString(PyExc_IndexError,
+ "ELFFile subscript slice step must be 1");
+ return NULL;
+ }
+
+ GElf_Addr xstart = start, xstop = stop;
+
+ for (size_t i = 0; i < w->ehdr->e_phnum; i++) {
+ GElf_Phdr _phdr, *phdr = gelf_getphdr(w->elf, i, &_phdr);
+
+ if (phdr->p_type != PT_LOAD)
+ continue;
+
+ if (xstart < phdr->p_vaddr
+ || xstart >= phdr->p_vaddr + phdr->p_memsz)
+ continue;
+ if (!str && (xstop < phdr->p_vaddr
+ || xstop > phdr->p_vaddr + phdr->p_memsz)) {
+ PyErr_Format(ELFAccessError,
+ "access (%llu) beyond end of program header (%llu)",
+ (long long)xstop,
+ (long long)(phdr->p_vaddr +
+ phdr->p_memsz));
+ return NULL;
+ }
+
+ xstart = xstart - phdr->p_vaddr + phdr->p_offset;
+
+ if (str)
+ xstop = strlen(w->mmap + xstart);
+ else
+ xstop = xstop - phdr->p_vaddr + phdr->p_offset;
+
+ Py_ssize_t pylen = xstop - xstart;
+
+#if PY_MAJOR_VERSION >= 3
+ return Py_BuildValue("y#", w->mmap + xstart, pylen);
+#else
+ return Py_BuildValue("s#", w->mmap + xstart, pylen);
+#endif
+ };
+
+ return PyErr_Format(ELFAccessError,
+ "virtual address (%llu) not found in program headers",
+ (long long)start);
+}
+
+static PyMethodDef methods_elffile[] = {
+ {"find_note", elffile_find_note, METH_VARARGS,
+ "find specific note entry"},
+ {"getreloc", elffile_getreloc, METH_VARARGS,
+ "find relocation"},
+ {"get_symbol", elffile_get_symbol, METH_VARARGS,
+ "find symbol by name"},
+ {"get_section", elffile_get_section, METH_VARARGS,
+ "find section by name"},
+ {"get_section_addr", elffile_get_section_addr, METH_VARARGS,
+ "find section by address"},
+ {"get_section_idx", elffile_get_section_idx, METH_VARARGS,
+ "find section by index"},
+ {}
+};
+
+static PyObject *elffile_load(PyTypeObject *type, PyObject *args,
+ PyObject *kwds);
+
+static void elffile_free(void *arg)
+{
+ struct elffile *w = arg;
+
+ elf_end(w->elf);
+ munmap(w->mmap, w->len);
+ free(w->filename);
+}
+
+static PyMappingMethods mp_elffile = {
+ .mp_subscript = elffile_subscript,
+};
+
+static PyTypeObject typeobj_elffile = {
+ PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_clippy.ELFFile",
+ .tp_basicsize = sizeof(struct elffile),
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_doc = elffile_doc,
+ .tp_new = elffile_load,
+ .tp_free = elffile_free,
+ .tp_as_mapping = &mp_elffile,
+ .tp_members = members_elffile,
+ .tp_methods = methods_elffile,
+};
+
+#ifdef HAVE_ELF_GETDATA_RAWCHUNK
+static char *elfdata_strptr(Elf_Data *data, size_t offset)
+{
+ char *p;
+
+ if (offset >= data->d_size)
+ return NULL;
+
+ p = (char *)data->d_buf + offset;
+ if (strnlen(p, data->d_size - offset) >= data->d_size - offset)
+ return NULL;
+
+ return p;
+}
+
+static void elffile_add_dynreloc(struct elffile *w, Elf_Data *reldata,
+ size_t entries, Elf_Data *symdata,
+ Elf_Data *strdata, Elf_Type typ)
+{
+ size_t i;
+
+ for (i = 0; i < entries; i++) {
+ struct elfreloc *relw;
+ size_t symidx;
+ GElf_Rela *rela;
+ GElf_Sym *sym;
+ GElf_Addr rel_offs = 0;
+
+ relw = (struct elfreloc *)typeobj_elfreloc.tp_alloc(
+ &typeobj_elfreloc, 0);
+ relw->ef = w;
+
+ if (typ == ELF_T_REL) {
+ GElf_Rel _rel, *rel;
+ GElf_Addr offs;
+
+ rel = gelf_getrel(reldata, i, &_rel);
+ relw->rela = &relw->_rela;
+ relw->rela->r_offset = rel->r_offset;
+ relw->rela->r_info = rel->r_info;
+ relw->rela->r_addend = 0;
+ relw->relative = true;
+
+ /* REL uses the pointer contents itself instead of the
+ * RELA addend field :( ... theoretically this could
+ * be some weird platform specific encoding, but since
+ * we only care about data relocations it should
+ * always be a pointer...
+ */
+ if (elffile_virt2file(w, rel->r_offset, &offs)) {
+ Elf_Data *ptr;
+
+ /* NB: this endian-converts! */
+ ptr = elf_getdata_rawchunk(w->elf, offs,
+ w->elfclass / 8,
+ ELF_T_ADDR);
+
+ if (ptr) {
+ char *dst = (char *)&rel_offs;
+
+ /* sigh. it endian-converts. but
+ * doesn't size-convert.
+ */
+ if (BYTE_ORDER == BIG_ENDIAN &&
+ ptr->d_size < sizeof(rel_offs))
+ dst += sizeof(rel_offs) -
+ ptr->d_size;
+
+ memcpy(dst, ptr->d_buf, ptr->d_size);
+
+ relw->relative = false;
+ relw->rela->r_addend = rel_offs;
+ }
+ }
+ } else
+ relw->rela = gelf_getrela(reldata, i, &relw->_rela);
+
+ rela = relw->rela;
+ symidx = relw->symidx = GELF_R_SYM(rela->r_info);
+ sym = relw->sym = gelf_getsym(symdata, symidx, &relw->_sym);
+ if (sym) {
+ relw->symname = elfdata_strptr(strdata, sym->st_name);
+ relw->symvalid = GELF_ST_TYPE(sym->st_info)
+ != STT_NOTYPE;
+ relw->unresolved = sym->st_shndx == SHN_UNDEF;
+ relw->st_value = sym->st_value;
+ } else {
+ relw->symname = NULL;
+ relw->symvalid = false;
+ relw->unresolved = false;
+ relw->st_value = 0;
+ }
+
+ if (typ == ELF_T_RELA)
+ debugf("dynrela @ %016llx sym %5llu %016llx %s\n",
+ (long long)rela->r_offset,
+ (unsigned long long)symidx,
+ (long long)rela->r_addend, relw->symname);
+ else
+ debugf("dynrel @ %016llx sym %5llu (%016llx) %s\n",
+ (long long)rela->r_offset,
+ (unsigned long long)symidx,
+ (unsigned long long)rel_offs, relw->symname);
+
+ elfrelocs_add(&w->dynrelocs, relw);
+ }
+
+}
+#endif /* HAVE_ELF_GETDATA_RAWCHUNK */
+
+/* primary (only, really) entry point to anything in this module */
+static PyObject *elffile_load(PyTypeObject *type, PyObject *args,
+ PyObject *kwds)
+{
+ const char *filename;
+ static const char * const kwnames[] = {"filename", NULL};
+ struct elffile *w;
+ struct stat st;
+ int fd, err;
+
+ w = (struct elffile *)typeobj_elffile.tp_alloc(&typeobj_elffile, 0);
+ if (!w)
+ return NULL;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", (char **)kwnames,
+ &filename))
+ return NULL;
+
+ w->filename = strdup(filename);
+ fd = open(filename, O_RDONLY | O_NOCTTY);
+ if (fd < 0 || fstat(fd, &st)) {
+ PyErr_SetFromErrnoWithFilename(PyExc_OSError, filename);
+ close(fd);
+ goto out;
+ }
+ w->len = st.st_size;
+ w->mmap = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
+ if (!w->mmap) {
+ PyErr_SetFromErrnoWithFilename(PyExc_IOError, filename);
+ close(fd);
+ goto out;
+ }
+ close(fd);
+ w->mmend = w->mmap + st.st_size;
+
+ if (w->len < EI_NIDENT || memcmp(w->mmap, ELFMAG, SELFMAG)) {
+ PyErr_SetString(ELFFormatError, "invalid ELF signature");
+ goto out;
+ }
+
+ switch (w->mmap[EI_CLASS]) {
+ case ELFCLASS32:
+ w->elfclass = 32;
+ break;
+ case ELFCLASS64:
+ w->elfclass = 64;
+ break;
+ default:
+ PyErr_SetString(ELFFormatError, "invalid ELF class");
+ goto out;
+ }
+ switch (w->mmap[EI_DATA]) {
+ case ELFDATA2LSB:
+ w->bigendian = false;
+ break;
+ case ELFDATA2MSB:
+ w->bigendian = true;
+ break;
+ default:
+ PyErr_SetString(ELFFormatError, "invalid ELF byte order");
+ goto out;
+ }
+
+ w->elf = elf_memory(w->mmap, w->len);
+ if (!w->elf)
+ goto out_elferr;
+ w->ehdr = gelf_getehdr(w->elf, &w->_ehdr);
+ if (!w->ehdr)
+ goto out_elferr;
+
+ for (size_t i = 0; i < w->ehdr->e_shnum; i++) {
+ Elf_Scn *scn = elf_getscn(w->elf, i);
+ GElf_Shdr _shdr, *shdr = gelf_getshdr(scn, &_shdr);
+
+ if (shdr->sh_type == SHT_SYMTAB) {
+ w->symtab = scn;
+ w->nsym = shdr->sh_size / shdr->sh_entsize;
+ w->symdata = elf_getdata(scn, NULL);
+ w->symstridx = shdr->sh_link;
+ break;
+ }
+ }
+ w->has_symbols = w->symtab && w->symstridx;
+ elfrelocs_init(&w->dynrelocs);
+
+#ifdef HAVE_ELF_GETDATA_RAWCHUNK
+ for (size_t i = 0; i < w->ehdr->e_phnum; i++) {
+ GElf_Phdr _phdr, *phdr = gelf_getphdr(w->elf, i, &_phdr);
+
+ if (phdr->p_type != PT_DYNAMIC)
+ continue;
+
+ Elf_Data *dyndata = elf_getdata_rawchunk(w->elf,
+ phdr->p_offset, phdr->p_filesz, ELF_T_DYN);
+
+ GElf_Addr dynrela = 0, dynrel = 0, symtab = 0, strtab = 0;
+ size_t dynrelasz = 0, dynrelaent = 0;
+ size_t dynrelsz = 0, dynrelent = 0;
+ size_t strsz = 0;
+ GElf_Dyn _dyn, *dyn;
+
+ for (size_t j = 0;; j++) {
+ dyn = gelf_getdyn(dyndata, j, &_dyn);
+
+ if (dyn->d_tag == DT_NULL)
+ break;
+
+ switch (dyn->d_tag) {
+ case DT_SYMTAB:
+ symtab = dyn->d_un.d_ptr;
+ break;
+
+ case DT_STRTAB:
+ strtab = dyn->d_un.d_ptr;
+ break;
+ case DT_STRSZ:
+ strsz = dyn->d_un.d_val;
+ break;
+
+ case DT_RELA:
+ dynrela = dyn->d_un.d_ptr;
+ break;
+ case DT_RELASZ:
+ dynrelasz = dyn->d_un.d_val;
+ break;
+ case DT_RELAENT:
+ dynrelaent = dyn->d_un.d_val;
+ break;
+
+ case DT_REL:
+ dynrel = dyn->d_un.d_ptr;
+ break;
+ case DT_RELSZ:
+ dynrelsz = dyn->d_un.d_val;
+ break;
+ case DT_RELENT:
+ dynrelent = dyn->d_un.d_val;
+ break;
+ }
+ }
+
+ GElf_Addr offset;
+ Elf_Data *symdata = NULL, *strdata = NULL;
+
+ if (elffile_virt2file(w, symtab, &offset))
+ symdata = elf_getdata_rawchunk(w->elf, offset,
+ w->len - offset,
+ ELF_T_SYM);
+ if (elffile_virt2file(w, strtab, &offset))
+ strdata = elf_getdata_rawchunk(w->elf, offset,
+ strsz, ELF_T_BYTE);
+
+ size_t c;
+
+ if (dynrela && dynrelasz && dynrelaent
+ && elffile_virt2file(w, dynrela, &offset)) {
+ Elf_Data *reladata = NULL;
+
+ debugf("dynrela @%llx/%llx+%llx\n", (long long)dynrela,
+ (long long)offset, (long long)dynrelasz);
+
+ reladata = elf_getdata_rawchunk(w->elf, offset,
+ dynrelasz, ELF_T_RELA);
+
+ c = dynrelasz / dynrelaent;
+ elffile_add_dynreloc(w, reladata, c, symdata, strdata,
+ ELF_T_RELA);
+ }
+
+ if (dynrel && dynrelsz && dynrelent
+ && elffile_virt2file(w, dynrel, &offset)) {
+ Elf_Data *reldata = NULL;
+
+ debugf("dynrel @%llx/%llx+%llx\n", (long long)dynrel,
+ (long long)offset, (long long)dynrelsz);
+
+ reldata = elf_getdata_rawchunk(w->elf, offset, dynrelsz,
+ ELF_T_REL);
+
+ c = dynrelsz / dynrelent;
+ elffile_add_dynreloc(w, reldata, c, symdata, strdata,
+ ELF_T_REL);
+ }
+ }
+#endif
+
+ w->sects = calloc(sizeof(PyObject *), w->ehdr->e_shnum);
+ w->n_sect = w->ehdr->e_shnum;
+
+ return (PyObject *)w;
+
+out_elferr:
+ err = elf_errno();
+
+ PyErr_Format(ELFFormatError, "libelf error %d: %s",
+ err, elf_errmsg(err));
+out:
+ if (w->elf)
+ elf_end(w->elf);
+ free(w->filename);
+ return NULL;
+}
+
+static PyObject *elfpy_debug(PyObject *self, PyObject *args)
+{
+ int arg;
+
+ if (!PyArg_ParseTuple(args, "p", &arg))
+ return NULL;
+
+ debug = arg;
+
+ Py_RETURN_NONE;
+}
+
+static PyMethodDef methods_elfpy[] = {
+ {"elfpy_debug", elfpy_debug, METH_VARARGS, "switch debuging on/off"},
+ {}
+};
+
+bool elf_py_init(PyObject *pymod)
+{
+ if (PyType_Ready(&typeobj_elffile) < 0)
+ return false;
+ if (PyType_Ready(&typeobj_elfsect) < 0)
+ return false;
+ if (PyType_Ready(&typeobj_elfreloc) < 0)
+ return false;
+ if (elf_version(EV_CURRENT) == EV_NONE)
+ return false;
+
+#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 5
+ PyModule_AddFunctions(pymod, methods_elfpy);
+#else
+ (void)methods_elfpy;
+#endif
+
+ ELFFormatError = PyErr_NewException("_clippy.ELFFormatError",
+ PyExc_ValueError, NULL);
+ PyModule_AddObject(pymod, "ELFFormatError", ELFFormatError);
+ ELFAccessError = PyErr_NewException("_clippy.ELFAccessError",
+ PyExc_IndexError, NULL);
+ PyModule_AddObject(pymod, "ELFAccessError", ELFAccessError);
+
+ Py_INCREF(&typeobj_elffile);
+ PyModule_AddObject(pymod, "ELFFile", (PyObject *)&typeobj_elffile);
+ Py_INCREF(&typeobj_elfsect);
+ PyModule_AddObject(pymod, "ELFSection", (PyObject *)&typeobj_elfsect);
+ Py_INCREF(&typeobj_elfreloc);
+ PyModule_AddObject(pymod, "ELFReloc", (PyObject *)&typeobj_elfreloc);
+ return true;
+}
diff --git a/lib/explicit_bzero.c b/lib/explicit_bzero.c
new file mode 100644
index 0000000..fa64ed8
--- /dev/null
+++ b/lib/explicit_bzero.c
@@ -0,0 +1,39 @@
+/*
+ * Public domain.
+ * Written by Matthew Dempsky.
+ * Adapted for frr.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+
+#ifndef HAVE_EXPLICIT_BZERO
+#undef explicit_bzero
+
+
+void explicit_bzero(void *buf, size_t len);
+__attribute__((__weak__)) void
+__explicit_bzero_hook(void *buf, size_t len);
+
+__attribute__((__weak__)) void
+__explicit_bzero_hook(void *buf, size_t len)
+{
+}
+
+#if defined(__clang__)
+#pragma clang optimize off
+#else
+#pragma GCC optimize("00")
+#endif
+
+void
+explicit_bzero(void *buf, size_t len)
+{
+ memset(buf, 0, len);
+ __explicit_bzero_hook(buf, len);
+}
+
+#endif
diff --git a/lib/ferr.c b/lib/ferr.c
new file mode 100644
index 0000000..bef7f3b
--- /dev/null
+++ b/lib/ferr.c
@@ -0,0 +1,297 @@
+/*
+ * Copyright (c) 2015-16 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <pthread.h>
+#include <signal.h>
+#include <inttypes.h>
+
+#include "ferr.h"
+#include "vty.h"
+#include "jhash.h"
+#include "memory.h"
+#include "hash.h"
+#include "command.h"
+#include "json.h"
+#include "linklist.h"
+#include "frr_pthread.h"
+
+DEFINE_MTYPE_STATIC(LIB, ERRINFO, "error information");
+
+/*
+ * Thread-specific key for temporary storage of allocated ferr.
+ */
+static pthread_key_t errkey;
+
+static void ferr_free(void *arg)
+{
+ XFREE(MTYPE_ERRINFO, arg);
+}
+
+static void err_key_init(void) __attribute__((_CONSTRUCTOR(500)));
+static void err_key_init(void)
+{
+ pthread_key_create(&errkey, ferr_free);
+}
+
+static void err_key_fini(void) __attribute__((_DESTRUCTOR(500)));
+static void err_key_fini(void)
+{
+ pthread_key_delete(errkey);
+}
+
+/*
+ * Global shared hash table holding reference text for all defined errors.
+ */
+static pthread_mutex_t refs_mtx = PTHREAD_MUTEX_INITIALIZER;
+struct hash *refs;
+
+static bool ferr_hash_cmp(const void *a, const void *b)
+{
+ const struct log_ref *f_a = a;
+ const struct log_ref *f_b = b;
+
+ return f_a->code == f_b->code;
+}
+
+static inline unsigned int ferr_hash_key(const void *a)
+{
+ const struct log_ref *f = a;
+
+ return f->code;
+}
+
+void log_ref_add(struct log_ref *ref)
+{
+ uint32_t i = 0;
+
+ frr_with_mutex (&refs_mtx) {
+ while (ref[i].code != END_FERR) {
+ (void)hash_get(refs, &ref[i], hash_alloc_intern);
+ i++;
+ }
+ }
+}
+
+struct log_ref *log_ref_get(uint32_t code)
+{
+ struct log_ref holder;
+ struct log_ref *ref;
+
+ holder.code = code;
+ frr_with_mutex (&refs_mtx) {
+ ref = hash_lookup(refs, &holder);
+ }
+
+ return ref;
+}
+
+void log_ref_display(struct vty *vty, uint32_t code, bool json)
+{
+ struct log_ref *ref;
+ struct json_object *top = NULL, *obj = NULL;
+ struct list *errlist;
+ struct listnode *ln;
+
+ if (json)
+ top = json_object_new_object();
+
+ frr_with_mutex (&refs_mtx) {
+ errlist = code ? list_new() : hash_to_list(refs);
+ }
+
+ if (code) {
+ ref = log_ref_get(code);
+ if (!ref) {
+ if (top)
+ json_object_free(top);
+ list_delete(&errlist);
+ return;
+ }
+ listnode_add(errlist, ref);
+ }
+
+ for (ALL_LIST_ELEMENTS_RO(errlist, ln, ref)) {
+ if (json) {
+ char key[11];
+
+ snprintf(key, sizeof(key), "%u", ref->code);
+ obj = json_object_new_object();
+ json_object_string_add(obj, "title", ref->title);
+ json_object_string_add(obj, "description",
+ ref->description);
+ json_object_string_add(obj, "suggestion",
+ ref->suggestion);
+ json_object_object_add(top, key, obj);
+ } else {
+ char pbuf[256];
+ char ubuf[256];
+
+ snprintf(pbuf, sizeof(pbuf), "\nError %u - %s",
+ ref->code, ref->title);
+ memset(ubuf, '=', strlen(pbuf));
+ ubuf[strlen(pbuf)] = '\0';
+
+ vty_out(vty, "%s\n%s\n", pbuf, ubuf);
+ vty_out(vty, "Description:\n%s\n\n", ref->description);
+ vty_out(vty, "Recommendation:\n%s\n", ref->suggestion);
+ }
+ }
+
+ vty_json(vty, top);
+ list_delete(&errlist);
+}
+
+DEFUN_NOSH(show_error_code,
+ show_error_code_cmd,
+ "show error <(1-4294967295)|all> [json]",
+ SHOW_STR
+ "Information on errors\n"
+ "Error code to get info about\n"
+ "Information on all errors\n"
+ JSON_STR)
+{
+ bool json = strmatch(argv[argc-1]->text, "json");
+ uint32_t arg = 0;
+
+ if (!strmatch(argv[2]->text, "all"))
+ arg = strtoul(argv[2]->arg, NULL, 10);
+
+ log_ref_display(vty, arg, json);
+ return CMD_SUCCESS;
+}
+
+void log_ref_init(void)
+{
+ frr_with_mutex (&refs_mtx) {
+ refs = hash_create(ferr_hash_key, ferr_hash_cmp,
+ "Error Reference Texts");
+ }
+}
+
+void log_ref_fini(void)
+{
+ frr_with_mutex (&refs_mtx) {
+ hash_clean(refs, NULL);
+ hash_free(refs);
+ refs = NULL;
+ }
+}
+
+void log_ref_vty_init(void)
+{
+ install_element(VIEW_NODE, &show_error_code_cmd);
+}
+
+
+const struct ferr *ferr_get_last(ferr_r errval)
+{
+ struct ferr *last_error = pthread_getspecific(errkey);
+ if (!last_error || last_error->kind == 0)
+ return NULL;
+ return last_error;
+}
+
+ferr_r ferr_clear(void)
+{
+ struct ferr *last_error = pthread_getspecific(errkey);
+ if (last_error)
+ last_error->kind = 0;
+ return ferr_ok();
+}
+
+static ferr_r ferr_set_va(const char *file, int line, const char *func,
+ enum ferr_kind kind, const char *pathname,
+ int errno_val, const char *text, va_list va)
+{
+ struct ferr *error = pthread_getspecific(errkey);
+
+ if (!error) {
+ error = XCALLOC(MTYPE_ERRINFO, sizeof(*error));
+
+ pthread_setspecific(errkey, error);
+ }
+
+ error->file = file;
+ error->line = line;
+ error->func = func;
+ error->kind = kind;
+
+ error->unique_id = jhash(text, strlen(text),
+ jhash(file, strlen(file), 0xd4ed0298));
+
+ error->errno_val = errno_val;
+ if (pathname)
+ snprintf(error->pathname, sizeof(error->pathname), "%s",
+ pathname);
+ else
+ error->pathname[0] = '\0';
+
+ vsnprintf(error->message, sizeof(error->message), text, va);
+ return -1;
+}
+
+ferr_r ferr_set_internal(const char *file, int line, const char *func,
+ enum ferr_kind kind, const char *text, ...)
+{
+ ferr_r rv;
+ va_list va;
+ va_start(va, text);
+ rv = ferr_set_va(file, line, func, kind, NULL, 0, text, va);
+ va_end(va);
+ return rv;
+}
+
+ferr_r ferr_set_internal_ext(const char *file, int line, const char *func,
+ enum ferr_kind kind, const char *pathname,
+ int errno_val, const char *text, ...)
+{
+ ferr_r rv;
+ va_list va;
+ va_start(va, text);
+ rv = ferr_set_va(file, line, func, kind, pathname, errno_val, text, va);
+ va_end(va);
+ return rv;
+}
+
+#define REPLACE "$ERR"
+void vty_print_error(struct vty *vty, ferr_r err, const char *msg, ...)
+{
+ char tmpmsg[512], *replacepos;
+ const struct ferr *last_error = ferr_get_last(err);
+
+ va_list va;
+ va_start(va, msg);
+ vsnprintf(tmpmsg, sizeof(tmpmsg), msg, va);
+ va_end(va);
+
+ replacepos = strstr(tmpmsg, REPLACE);
+ if (!replacepos)
+ vty_out(vty, "%s\n", tmpmsg);
+ else {
+ replacepos[0] = '\0';
+ replacepos += sizeof(REPLACE) - 1;
+ vty_out(vty, "%s%s%s\n", tmpmsg,
+ last_error ? last_error->message : "(no error?)",
+ replacepos);
+ }
+}
diff --git a/lib/ferr.h b/lib/ferr.h
new file mode 100644
index 0000000..c27601f
--- /dev/null
+++ b/lib/ferr.h
@@ -0,0 +1,267 @@
+/*
+ * Copyright (c) 2015-16 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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_FERR_H
+#define _FRR_FERR_H
+
+/***********************************************************
+ * scroll down to the end of this file for a full example! *
+ ***********************************************************/
+
+#include <stdint.h>
+#include <limits.h>
+#include <errno.h>
+
+#include "vty.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* return type when this error indication stuff is used.
+ *
+ * guaranteed to have boolean evaluation to "false" when OK, "true" when error
+ * (i.e. can be changed to pointer in the future if necessary)
+ *
+ * For checking, always use "if (value)", nothing else.
+ * Do _NOT_ use any integer constant (!= 0), or sign check (< 0).
+ */
+typedef int ferr_r;
+
+/* rough category of error indication */
+enum ferr_kind {
+ /* no error */
+ FERR_OK = 0,
+
+ /* something isn't the way it's supposed to be.
+ * (things that might otherwise be asserts, really)
+ */
+ FERR_CODE_BUG,
+
+ /* user-supplied parameters don't make sense or is inconsistent
+ * if you can express a rule for it (e.g. "holdtime > 2 * keepalive"),
+ * it's this category.
+ */
+ FERR_CONFIG_INVALID,
+
+ /* user-supplied parameters don't line up with reality
+ * (IP address or interface not available, etc.)
+ * NB: these are really TODOs where the code needs to be fixed to
+ * respond to future changes!
+ */
+ FERR_CONFIG_REALITY,
+
+ /* out of some system resource (probably memory)
+ * aka "you didn't spend enough money error" */
+ FERR_RESOURCE,
+
+ /* system error (permission denied, etc.) */
+ FERR_SYSTEM,
+
+ /* error return from some external library
+ * (FERR_SYSTEM and FERR_LIBRARY are not strongly distinct) */
+ FERR_LIBRARY,
+};
+
+struct ferr {
+ /* code location */
+ const char *file;
+ const char *func;
+ int line;
+
+ enum ferr_kind kind;
+
+ /* unique_id is calculated as a checksum of source filename and error
+ * message format (*before* calling vsnprintf). Line number and
+ * function name are not used; this keeps the number reasonably static
+ * across changes.
+ */
+ uint32_t unique_id;
+
+ char message[384];
+
+ /* valid if != 0. note "errno" might be preprocessor foobar. */
+ int errno_val;
+ /* valid if pathname[0] != '\0' */
+ char pathname[PATH_MAX];
+};
+
+/* Numeric ranges assigned to daemons for use as error codes. */
+#define BABEL_FERR_START 0x01000001
+#define BABEL_FRRR_END 0x01FFFFFF
+#define BGP_FERR_START 0x02000001
+#define BGP_FERR_END 0x02FFFFFF
+#define EIGRP_FERR_START 0x03000001
+#define EIGRP_FERR_END 0x03FFFFFF
+#define ISIS_FERR_START 0x04000001
+#define ISIS_FERR_END 0x04FFFFFF
+#define LDP_FERR_START 0x05000001
+#define LDP_FERR_END 0x05FFFFFF
+#define LIB_FERR_START 0x06000001
+#define LIB_FERR_END 0x06FFFFFF
+#define NHRP_FERR_START 0x07000001
+#define NHRP_FERR_END 0x07FFFFFF
+#define OSPF_FERR_START 0x08000001
+#define OSPF_FERR_END 0x08FFFFFF
+#define OSPFV3_FERR_START 0x09000001
+#define OSPFV3_FERR_END 0x09FFFFFF
+#define PBR_FERR_START 0x0A000001
+#define PBR_FERR_END 0x0AFFFFFF
+#define PIM_FERR_START 0x0B000001
+#define PIM_FERR_STOP 0x0BFFFFFF
+#define RIP_FERR_START 0x0C000001
+#define RIP_FERR_STOP 0x0CFFFFFF
+#define RIPNG_FERR_START 0x0D000001
+#define RIPNG_FERR_STOP 0x0DFFFFFF
+#define SHARP_FERR_START 0x0E000001
+#define SHARP_FERR_END 0x0EFFFFFF
+#define VTYSH_FERR_START 0x0F000001
+#define VTYSH_FRR_END 0x0FFFFFFF
+#define WATCHFRR_FERR_START 0x10000001
+#define WATCHFRR_FERR_END 0x10FFFFFF
+#define PATH_FERR_START 0x11000001
+#define PATH_FERR_END 0x11FFFFFF
+#define ZEBRA_FERR_START 0xF1000001
+#define ZEBRA_FERR_END 0xF1FFFFFF
+#define END_FERR 0xFFFFFFFF
+
+struct log_ref {
+ /* Unique error code displayed to end user as a reference. -1 means
+ * this is an uncoded error that does not have reference material. */
+ uint32_t code;
+ /* Ultra brief title */
+ const char *title;
+ /* Brief description of error */
+ const char *description;
+ /* Remedial suggestion */
+ const char *suggestion;
+};
+
+void log_ref_add(struct log_ref *ref);
+struct log_ref *log_ref_get(uint32_t code);
+void log_ref_display(struct vty *vty, uint32_t code, bool json);
+
+/*
+ * This function should be called by the
+ * code in libfrr.c
+ */
+void log_ref_init(void);
+void log_ref_fini(void);
+void log_ref_vty_init(void);
+
+/* get error details.
+ *
+ * NB: errval/ferr_r does NOT carry the full error information. It's only
+ * passed around for future API flexibility. ferr_get_last always returns
+ * the last error set in the current thread.
+ */
+const struct ferr *ferr_get_last(ferr_r errval);
+
+/*
+ * Can optionally be called at strategic locations.
+ * Always returns 0.
+ */
+ferr_r ferr_clear(void);
+
+/* do NOT call these functions directly. only for macro use! */
+ferr_r ferr_set_internal(const char *file, int line, const char *func,
+ enum ferr_kind kind, const char *text, ...);
+ferr_r ferr_set_internal_ext(const char *file, int line, const char *func,
+ enum ferr_kind kind, const char *pathname,
+ int errno_val, const char *text, ...);
+
+#define ferr_ok() 0
+
+/* Report an error.
+ *
+ * If you need to do cleanup (free memory, etc.), save the return value in a
+ * variable of type ferr_r.
+ *
+ * Don't put a \n at the end of the error message.
+ */
+#define ferr_code_bug(...) \
+ ferr_set_internal(__FILE__, __LINE__, __func__, FERR_CODE_BUG, \
+ __VA_ARGS__)
+#define ferr_cfg_invalid(...) \
+ ferr_set_internal(__FILE__, __LINE__, __func__, FERR_CONFIG_INVALID, \
+ __VA_ARGS__)
+#define ferr_cfg_reality(...) \
+ ferr_set_internal(__FILE__, __LINE__, __func__, FERR_CONFIG_REALITY, \
+ __VA_ARGS__)
+#define ferr_cfg_resource(...) \
+ ferr_set_internal(__FILE__, __LINE__, __func__, FERR_RESOURCE, \
+ __VA_ARGS__)
+#define ferr_system(...) \
+ ferr_set_internal(__FILE__, __LINE__, __func__, FERR_SYSTEM, \
+ __VA_ARGS__)
+#define ferr_library(...) \
+ ferr_set_internal(__FILE__, __LINE__, __func__, FERR_LIBRARY, \
+ __VA_ARGS__)
+
+/* extended information variants */
+#define ferr_system_errno(...) \
+ ferr_set_internal_ext(__FILE__, __LINE__, __func__, FERR_SYSTEM, NULL, \
+ errno, __VA_ARGS__)
+#define ferr_system_path_errno(path, ...) \
+ ferr_set_internal_ext(__FILE__, __LINE__, __func__, FERR_SYSTEM, path, \
+ errno, __VA_ARGS__)
+
+#include "vty.h"
+/* print error message to vty; $ERR is replaced by the error's message */
+void vty_print_error(struct vty *vty, ferr_r err, const char *msg, ...);
+
+#define CMD_FERR_DO(func, action, ...) \
+ do { \
+ ferr_r cmd_retval = func; \
+ if (cmd_retval) { \
+ vty_print_error(vty, cmd_retval, __VA_ARGS__); \
+ action; \
+ } \
+ } while (0)
+
+#define CMD_FERR_RETURN(func, ...) \
+ CMD_FERR_DO(func, return CMD_WARNING_CONFIG_FAILED, __VA_ARGS__)
+#define CMD_FERR_GOTO(func, label, ...) \
+ CMD_FERR_DO(func, goto label, __VA_ARGS__)
+
+/* example: uses bogus #define to keep indent.py happy */
+#ifdef THIS_IS_AN_EXAMPLE
+ferr_r foo_bar_set(struct object *obj, int bar)
+{
+ if (bar < 1 || bar >= 100)
+ return ferr_config_invalid("bar setting (%d) must be 0<x<100",
+ bar);
+ obj->bar = bar;
+ if (ioctl(obj->fd, bar))
+ return ferr_system_errno("couldn't set bar to %d", bar);
+
+ return ferr_ok();
+}
+
+DEFUN("bla")
+{
+ CMD_FERR_RETURN(foo_bar_set(obj, atoi(argv[1])),
+ "command failed: $ERR\n");
+ return CMD_SUCCESS;
+}
+
+#endif /* THIS_IS_AN_EXAMPLE */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FERR_H */
diff --git a/lib/filter.c b/lib/filter.c
new file mode 100644
index 0000000..fc4b578
--- /dev/null
+++ b/lib/filter.c
@@ -0,0 +1,919 @@
+/* Route filtering function.
+ * Copyright (C) 1998, 1999 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra 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 "filter.h"
+#include "memory.h"
+#include "command.h"
+#include "sockunion.h"
+#include "buffer.h"
+#include "log.h"
+#include "routemap.h"
+#include "libfrr.h"
+#include "northbound_cli.h"
+#include "json.h"
+
+DEFINE_MTYPE_STATIC(LIB, ACCESS_LIST, "Access List");
+DEFINE_MTYPE_STATIC(LIB, ACCESS_LIST_STR, "Access List Str");
+DEFINE_MTYPE_STATIC(LIB, ACCESS_FILTER, "Access Filter");
+
+/* Static structure for mac access_list's master. */
+static struct access_master access_master_mac = {
+ {NULL, NULL},
+ NULL,
+ NULL,
+};
+
+/* Static structure for IPv4 access_list's master. */
+static struct access_master access_master_ipv4 = {
+ {NULL, NULL},
+ NULL,
+ NULL,
+};
+
+/* Static structure for IPv6 access_list's master. */
+static struct access_master access_master_ipv6 = {
+ {NULL, NULL},
+ NULL,
+ NULL,
+};
+
+static struct access_master *access_master_get(afi_t afi)
+{
+ if (afi == AFI_IP)
+ return &access_master_ipv4;
+ else if (afi == AFI_IP6)
+ return &access_master_ipv6;
+ else if (afi == AFI_L2VPN)
+ return &access_master_mac;
+ return NULL;
+}
+
+/* Allocate new filter structure. */
+struct filter *filter_new(void)
+{
+ return XCALLOC(MTYPE_ACCESS_FILTER, sizeof(struct filter));
+}
+
+static void filter_free(struct filter *filter)
+{
+ XFREE(MTYPE_ACCESS_FILTER, filter);
+}
+
+/* Return string of filter_type. */
+static const char *filter_type_str(struct filter *filter)
+{
+ switch (filter->type) {
+ case FILTER_PERMIT:
+ return "permit";
+ case FILTER_DENY:
+ return "deny";
+ case FILTER_DYNAMIC:
+ return "dynamic";
+ default:
+ return "";
+ }
+}
+
+/* If filter match to the prefix then return 1. */
+static int filter_match_cisco(struct filter *mfilter, const struct prefix *p)
+{
+ struct filter_cisco *filter;
+ struct in_addr mask;
+ uint32_t check_addr;
+ uint32_t check_mask;
+
+ filter = &mfilter->u.cfilter;
+ check_addr = p->u.prefix4.s_addr & ~filter->addr_mask.s_addr;
+
+ if (filter->extended) {
+ masklen2ip(p->prefixlen, &mask);
+ check_mask = mask.s_addr & ~filter->mask_mask.s_addr;
+
+ if (memcmp(&check_addr, &filter->addr.s_addr, IPV4_MAX_BYTELEN)
+ == 0
+ && memcmp(&check_mask, &filter->mask.s_addr,
+ IPV4_MAX_BYTELEN)
+ == 0)
+ return 1;
+ } else if (memcmp(&check_addr, &filter->addr.s_addr, IPV4_MAX_BYTELEN)
+ == 0)
+ return 1;
+
+ return 0;
+}
+
+/* If filter match to the prefix then return 1. */
+static int filter_match_zebra(struct filter *mfilter, const struct prefix *p)
+{
+ struct filter_zebra *filter = NULL;
+
+ filter = &mfilter->u.zfilter;
+
+ if (filter->prefix.family == p->family) {
+ if (filter->exact) {
+ if (filter->prefix.prefixlen == p->prefixlen)
+ return prefix_match(&filter->prefix, p);
+ else
+ return 0;
+ } else
+ return prefix_match(&filter->prefix, p);
+ } else
+ return 0;
+}
+
+/* Allocate new access list structure. */
+static struct access_list *access_list_new(void)
+{
+ return XCALLOC(MTYPE_ACCESS_LIST, sizeof(struct access_list));
+}
+
+/* Free allocated access_list. */
+static void access_list_free(struct access_list *access)
+{
+ XFREE(MTYPE_ACCESS_LIST, access);
+}
+
+/* Delete access_list from access_master and free it. */
+void access_list_delete(struct access_list *access)
+{
+ struct filter *filter;
+ struct filter *next;
+ struct access_list_list *list;
+ struct access_master *master;
+
+ for (filter = access->head; filter; filter = next) {
+ next = filter->next;
+ filter_free(filter);
+ }
+
+ master = access->master;
+
+ list = &master->str;
+
+ if (access->next)
+ access->next->prev = access->prev;
+ else
+ list->tail = access->prev;
+
+ if (access->prev)
+ access->prev->next = access->next;
+ else
+ list->head = access->next;
+
+ route_map_notify_dependencies(access->name, RMAP_EVENT_FILTER_DELETED);
+
+ if (master->delete_hook)
+ master->delete_hook(access);
+
+ XFREE(MTYPE_ACCESS_LIST_STR, access->name);
+
+ XFREE(MTYPE_TMP, access->remark);
+
+ access_list_free(access);
+}
+
+/* Insert new access list to list of access_list. Each access_list
+ is sorted by the name. */
+static struct access_list *access_list_insert(afi_t afi, const char *name)
+{
+ struct access_list *access;
+ struct access_list *point;
+ struct access_list_list *alist;
+ struct access_master *master;
+
+ master = access_master_get(afi);
+ if (master == NULL)
+ return NULL;
+
+ /* Allocate new access_list and copy given name. */
+ access = access_list_new();
+ access->name = XSTRDUP(MTYPE_ACCESS_LIST_STR, name);
+ access->master = master;
+
+ /* Set access_list to string list. */
+ alist = &master->str;
+
+ /* Set point to insertion point. */
+ for (point = alist->head; point; point = point->next)
+ if (strcmp(point->name, name) >= 0)
+ break;
+
+ /* In case of this is the first element of master. */
+ if (alist->head == NULL) {
+ alist->head = alist->tail = access;
+ return access;
+ }
+
+ /* In case of insertion is made at the tail of access_list. */
+ if (point == NULL) {
+ access->prev = alist->tail;
+ alist->tail->next = access;
+ alist->tail = access;
+ return access;
+ }
+
+ /* In case of insertion is made at the head of access_list. */
+ if (point == alist->head) {
+ access->next = alist->head;
+ alist->head->prev = access;
+ alist->head = access;
+ return access;
+ }
+
+ /* Insertion is made at middle of the access_list. */
+ access->next = point;
+ access->prev = point->prev;
+
+ if (point->prev)
+ point->prev->next = access;
+ point->prev = access;
+
+ return access;
+}
+
+/* Lookup access_list from list of access_list by name. */
+struct access_list *access_list_lookup(afi_t afi, const char *name)
+{
+ struct access_list *access;
+ struct access_master *master;
+
+ if (name == NULL)
+ return NULL;
+
+ master = access_master_get(afi);
+ if (master == NULL)
+ return NULL;
+
+ for (access = master->str.head; access; access = access->next)
+ if (strcmp(access->name, name) == 0)
+ return access;
+
+ return NULL;
+}
+
+/* Get access list from list of access_list. If there isn't matched
+ access_list create new one and return it. */
+struct access_list *access_list_get(afi_t afi, const char *name)
+{
+ struct access_list *access;
+
+ access = access_list_lookup(afi, name);
+ if (access == NULL)
+ access = access_list_insert(afi, name);
+ return access;
+}
+
+/* Apply access list to object (which should be struct prefix *). */
+enum filter_type access_list_apply(struct access_list *access,
+ const void *object)
+{
+ struct filter *filter;
+ const struct prefix *p = (const struct prefix *)object;
+
+ if (access == NULL)
+ return FILTER_DENY;
+
+ for (filter = access->head; filter; filter = filter->next) {
+ if (filter->cisco) {
+ if (filter_match_cisco(filter, p))
+ return filter->type;
+ } else {
+ if (filter_match_zebra(filter, p))
+ return filter->type;
+ }
+ }
+
+ return FILTER_DENY;
+}
+
+/* Add hook function. */
+void access_list_add_hook(void (*func)(struct access_list *access))
+{
+ access_master_ipv4.add_hook = func;
+ access_master_ipv6.add_hook = func;
+ access_master_mac.add_hook = func;
+}
+
+/* Delete hook function. */
+void access_list_delete_hook(void (*func)(struct access_list *access))
+{
+ access_master_ipv4.delete_hook = func;
+ access_master_ipv6.delete_hook = func;
+ access_master_mac.delete_hook = func;
+}
+
+/* Calculate new sequential number. */
+int64_t filter_new_seq_get(struct access_list *access)
+{
+ int64_t maxseq;
+ int64_t newseq;
+ struct filter *filter;
+
+ maxseq = 0;
+
+ for (filter = access->head; filter; filter = filter->next) {
+ if (maxseq < filter->seq)
+ maxseq = filter->seq;
+ }
+
+ newseq = ((maxseq / 5) * 5) + 5;
+
+ return (newseq > UINT_MAX) ? UINT_MAX : newseq;
+}
+
+/* Return access list entry which has same seq number. */
+static struct filter *filter_seq_check(struct access_list *access,
+ int64_t seq)
+{
+ struct filter *filter;
+
+ for (filter = access->head; filter; filter = filter->next)
+ if (filter->seq == seq)
+ return filter;
+ return NULL;
+}
+
+/* Delete filter from specified access_list. If there is hook
+ function execute it. */
+void access_list_filter_delete(struct access_list *access,
+ struct filter *filter)
+{
+ struct access_master *master;
+
+ master = access->master;
+
+ if (filter->next)
+ filter->next->prev = filter->prev;
+ else
+ access->tail = filter->prev;
+
+ if (filter->prev)
+ filter->prev->next = filter->next;
+ else
+ access->head = filter->next;
+
+ filter_free(filter);
+
+ route_map_notify_dependencies(access->name, RMAP_EVENT_FILTER_DELETED);
+ /* Run hook function. */
+ if (master->delete_hook)
+ (*master->delete_hook)(access);
+}
+
+/* Add new filter to the end of specified access_list. */
+void access_list_filter_add(struct access_list *access,
+ struct filter *filter)
+{
+ struct filter *replace;
+ struct filter *point;
+
+ /* Automatic assignment of seq no. */
+ if (filter->seq == -1)
+ filter->seq = filter_new_seq_get(access);
+
+ if (access->tail && filter->seq > access->tail->seq)
+ point = NULL;
+ else {
+ /* Is there any same seq access list filter? */
+ replace = filter_seq_check(access, filter->seq);
+ if (replace)
+ access_list_filter_delete(access, replace);
+
+ /* Check insert point. */
+ for (point = access->head; point; point = point->next)
+ if (point->seq >= filter->seq)
+ break;
+ }
+
+ /* In case of this is the first element of the list. */
+ filter->next = point;
+
+ if (point) {
+ if (point->prev)
+ point->prev->next = filter;
+ else
+ access->head = filter;
+
+ filter->prev = point->prev;
+ point->prev = filter;
+ } else {
+ if (access->tail)
+ access->tail->next = filter;
+ else
+ access->head = filter;
+
+ filter->prev = access->tail;
+ access->tail = filter;
+ }
+
+ /* Run hook function. */
+ if (access->master->add_hook)
+ (*access->master->add_hook)(access);
+ route_map_notify_dependencies(access->name, RMAP_EVENT_FILTER_ADDED);
+}
+
+/*
+ deny Specify packets to reject
+ permit Specify packets to forward
+ dynamic ?
+*/
+
+/*
+ Hostname or A.B.C.D Address to match
+ any Any source host
+ host A single host address
+*/
+
+static void config_write_access_zebra(struct vty *, struct filter *,
+ json_object *);
+static void config_write_access_cisco(struct vty *, struct filter *,
+ json_object *);
+
+static const char *filter_type2str(struct filter *filter)
+{
+ if (filter->cisco) {
+ if (filter->u.cfilter.extended)
+ return "Extended";
+ else
+ return "Standard";
+ } else
+ return "Zebra";
+}
+
+/* show access-list command. */
+static int filter_show(struct vty *vty, const char *name, afi_t afi,
+ bool use_json)
+{
+ struct access_list *access;
+ struct access_master *master;
+ struct filter *mfilter;
+ struct filter_cisco *filter;
+ bool first;
+ json_object *json = NULL;
+ json_object *json_proto = NULL;
+
+ master = access_master_get(afi);
+ if (master == NULL) {
+ if (use_json)
+ vty_out(vty, "{}\n");
+ return 0;
+ }
+
+ if (use_json)
+ json = json_object_new_object();
+
+ /* Print the name of the protocol */
+ if (json) {
+ json_proto = json_object_new_object();
+ json_object_object_add(json, frr_protoname, json_proto);
+ } else
+ vty_out(vty, "%s:\n", frr_protoname);
+
+ for (access = master->str.head; access; access = access->next) {
+ json_object *json_acl = NULL;
+ json_object *json_rules = NULL;
+
+ if (name && strcmp(access->name, name) != 0)
+ continue;
+
+ first = true;
+
+ for (mfilter = access->head; mfilter; mfilter = mfilter->next) {
+ json_object *json_rule = NULL;
+
+ filter = &mfilter->u.cfilter;
+
+ if (first) {
+ const char *type = filter_type2str(mfilter);
+
+ if (json) {
+ json_acl = json_object_new_object();
+ json_object_object_add(json_proto,
+ access->name,
+ json_acl);
+
+ json_object_string_add(json_acl, "type",
+ type);
+ json_object_string_add(json_acl,
+ "addressFamily",
+ afi2str(afi));
+ json_rules = json_object_new_array();
+ json_object_object_add(
+ json_acl, "rules", json_rules);
+ } else {
+ vty_out(vty, "%s %s access list %s\n",
+ type,
+ (afi == AFI_IP)
+ ? ("IP")
+ : ((afi == AFI_IP6)
+ ? ("IPv6 ")
+ : ("MAC ")),
+ access->name);
+ }
+
+ first = false;
+ }
+
+ if (json) {
+ json_rule = json_object_new_object();
+ json_object_array_add(json_rules, json_rule);
+
+ json_object_int_add(json_rule, "sequenceNumber",
+ mfilter->seq);
+ json_object_string_add(
+ json_rule, "filterType",
+ filter_type_str(mfilter));
+ } else {
+ vty_out(vty, " seq %" PRId64, mfilter->seq);
+ vty_out(vty, " %s%s", filter_type_str(mfilter),
+ mfilter->type == FILTER_DENY ? " "
+ : "");
+ }
+
+ if (!mfilter->cisco)
+ config_write_access_zebra(vty, mfilter,
+ json_rule);
+ else if (filter->extended)
+ config_write_access_cisco(vty, mfilter,
+ json_rule);
+ else {
+ if (json) {
+ json_object_string_addf(
+ json_rule, "address", "%pI4",
+ &filter->addr);
+ json_object_string_addf(
+ json_rule, "mask", "%pI4",
+ &filter->addr_mask);
+ } else {
+ if (filter->addr_mask.s_addr
+ == 0xffffffff)
+ vty_out(vty, " any\n");
+ else {
+ vty_out(vty, " %pI4",
+ &filter->addr);
+ if (filter->addr_mask.s_addr
+ != INADDR_ANY)
+ vty_out(vty,
+ ", wildcard bits %pI4",
+ &filter->addr_mask);
+ vty_out(vty, "\n");
+ }
+ }
+ }
+ }
+ }
+
+ return vty_json(vty, json);
+}
+
+/* show MAC access list - this only has MAC filters for now*/
+DEFUN (show_mac_access_list,
+ show_mac_access_list_cmd,
+ "show mac access-list",
+ SHOW_STR
+ "mac access lists\n"
+ "List mac access lists\n")
+{
+ return filter_show(vty, NULL, AFI_L2VPN, false);
+}
+
+DEFUN (show_mac_access_list_name,
+ show_mac_access_list_name_cmd,
+ "show mac access-list ACCESSLIST_MAC_NAME",
+ SHOW_STR
+ "mac access lists\n"
+ "List mac access lists\n"
+ "mac address\n")
+{
+ return filter_show(vty, argv[3]->arg, AFI_L2VPN, false);
+}
+
+DEFUN (show_ip_access_list,
+ show_ip_access_list_cmd,
+ "show ip access-list [json]",
+ SHOW_STR
+ IP_STR
+ "List IP access lists\n"
+ JSON_STR)
+{
+ bool uj = use_json(argc, argv);
+ return filter_show(vty, NULL, AFI_IP, uj);
+}
+
+DEFUN (show_ip_access_list_name,
+ show_ip_access_list_name_cmd,
+ "show ip access-list ACCESSLIST4_NAME [json]",
+ SHOW_STR
+ IP_STR
+ "List IP access lists\n"
+ "IP access-list name\n"
+ JSON_STR)
+{
+ bool uj = use_json(argc, argv);
+ int idx_acl = 3;
+ return filter_show(vty, argv[idx_acl]->arg, AFI_IP, uj);
+}
+
+DEFUN (show_ipv6_access_list,
+ show_ipv6_access_list_cmd,
+ "show ipv6 access-list [json]",
+ SHOW_STR
+ IPV6_STR
+ "List IPv6 access lists\n"
+ JSON_STR)
+{
+ bool uj = use_json(argc, argv);
+ return filter_show(vty, NULL, AFI_IP6, uj);
+}
+
+DEFUN (show_ipv6_access_list_name,
+ show_ipv6_access_list_name_cmd,
+ "show ipv6 access-list ACCESSLIST6_NAME [json]",
+ SHOW_STR
+ IPV6_STR
+ "List IPv6 access lists\n"
+ "IPv6 access-list name\n"
+ JSON_STR)
+{
+ bool uj = use_json(argc, argv);
+ int idx_word = 3;
+ return filter_show(vty, argv[idx_word]->arg, AFI_IP6, uj);
+}
+
+static void config_write_access_cisco(struct vty *vty, struct filter *mfilter,
+ json_object *json)
+{
+ struct filter_cisco *filter;
+
+ filter = &mfilter->u.cfilter;
+
+ if (json) {
+ json_object_boolean_add(json, "extended", !!filter->extended);
+ json_object_string_addf(json, "sourceAddress", "%pI4",
+ &filter->addr);
+ json_object_string_addf(json, "sourceMask", "%pI4",
+ &filter->addr_mask);
+ json_object_string_addf(json, "destinationAddress", "%pI4",
+ &filter->mask);
+ json_object_string_addf(json, "destinationMask", "%pI4",
+ &filter->mask_mask);
+ } else {
+ vty_out(vty, " ip");
+ if (filter->addr_mask.s_addr == 0xffffffff)
+ vty_out(vty, " any");
+ else if (filter->addr_mask.s_addr == INADDR_ANY)
+ vty_out(vty, " host %pI4", &filter->addr);
+ else {
+ vty_out(vty, " %pI4", &filter->addr);
+ vty_out(vty, " %pI4", &filter->addr_mask);
+ }
+
+ if (filter->mask_mask.s_addr == 0xffffffff)
+ vty_out(vty, " any");
+ else if (filter->mask_mask.s_addr == INADDR_ANY)
+ vty_out(vty, " host %pI4", &filter->mask);
+ else {
+ vty_out(vty, " %pI4", &filter->mask);
+ vty_out(vty, " %pI4", &filter->mask_mask);
+ }
+ vty_out(vty, "\n");
+ }
+}
+
+static void config_write_access_zebra(struct vty *vty, struct filter *mfilter,
+ json_object *json)
+{
+ struct filter_zebra *filter;
+ struct prefix *p;
+ char buf[BUFSIZ];
+
+ filter = &mfilter->u.zfilter;
+ p = &filter->prefix;
+
+ if (json) {
+ json_object_string_addf(json, "prefix", "%pFX", p);
+ json_object_boolean_add(json, "exact-match", !!filter->exact);
+ } else {
+ if (p->prefixlen == 0 && !filter->exact)
+ vty_out(vty, " any");
+ else if (p->family == AF_INET6 || p->family == AF_INET)
+ vty_out(vty, " %pFX%s", p,
+ filter->exact ? " exact-match" : "");
+ else if (p->family == AF_ETHERNET) {
+ if (p->prefixlen == 0)
+ vty_out(vty, " any");
+ else
+ vty_out(vty, " %s",
+ prefix_mac2str(&(p->u.prefix_eth), buf,
+ sizeof(buf)));
+ }
+
+ vty_out(vty, "\n");
+ }
+}
+
+static struct cmd_node access_mac_node = {
+ .name = "MAC access list",
+ .node = ACCESS_MAC_NODE,
+ .prompt = "",
+};
+
+static void access_list_reset_mac(void)
+{
+ struct access_list *access;
+ struct access_list *next;
+ struct access_master *master;
+
+ master = access_master_get(AFI_L2VPN);
+ if (master == NULL)
+ return;
+
+ for (access = master->str.head; access; access = next) {
+ next = access->next;
+ access_list_delete(access);
+ }
+
+ assert(master->str.head == NULL);
+ assert(master->str.tail == NULL);
+}
+
+/* Install vty related command. */
+static void access_list_init_mac(void)
+{
+ install_node(&access_mac_node);
+
+ install_element(ENABLE_NODE, &show_mac_access_list_cmd);
+ install_element(ENABLE_NODE, &show_mac_access_list_name_cmd);
+}
+
+/* Access-list node. */
+static int config_write_access(struct vty *vty);
+static struct cmd_node access_node = {
+ .name = "ipv4 access list",
+ .node = ACCESS_NODE,
+ .prompt = "",
+ .config_write = config_write_access,
+};
+
+static int config_write_access(struct vty *vty)
+{
+ struct lyd_node *dnode;
+ int written = 0;
+
+ dnode = yang_dnode_get(running_config->dnode, "/frr-filter:lib");
+ if (dnode) {
+ nb_cli_show_dnode_cmds(vty, dnode, false);
+ written = 1;
+ }
+
+ return written;
+}
+
+static void access_list_reset_ipv4(void)
+{
+ struct access_list *access;
+ struct access_list *next;
+ struct access_master *master;
+
+ master = access_master_get(AFI_IP);
+ if (master == NULL)
+ return;
+
+ for (access = master->str.head; access; access = next) {
+ next = access->next;
+ access_list_delete(access);
+ }
+
+ assert(master->str.head == NULL);
+ assert(master->str.tail == NULL);
+}
+
+/* Install vty related command. */
+static void access_list_init_ipv4(void)
+{
+ install_node(&access_node);
+
+ install_element(ENABLE_NODE, &show_ip_access_list_cmd);
+ install_element(ENABLE_NODE, &show_ip_access_list_name_cmd);
+}
+
+static void access_list_autocomplete_afi(afi_t afi, vector comps,
+ struct cmd_token *token)
+{
+ struct access_list *access;
+ struct access_list *next;
+ struct access_master *master;
+
+ master = access_master_get(afi);
+ if (master == NULL)
+ return;
+
+ for (access = master->str.head; access; access = next) {
+ next = access->next;
+ vector_set(comps, XSTRDUP(MTYPE_COMPLETION, access->name));
+ }
+}
+
+static struct cmd_node access_ipv6_node = {
+ .name = "ipv6 access list",
+ .node = ACCESS_IPV6_NODE,
+ .prompt = "",
+};
+
+static void access_list_autocomplete(vector comps, struct cmd_token *token)
+{
+ access_list_autocomplete_afi(AFI_IP, comps, token);
+ access_list_autocomplete_afi(AFI_IP6, comps, token);
+ access_list_autocomplete_afi(AFI_L2VPN, comps, token);
+}
+
+static void access_list4_autocomplete(vector comps, struct cmd_token *token)
+{
+ access_list_autocomplete_afi(AFI_IP, comps, token);
+}
+
+static void access_list6_autocomplete(vector comps, struct cmd_token *token)
+{
+ access_list_autocomplete_afi(AFI_IP6, comps, token);
+}
+
+static void access_list_mac_autocomplete(vector comps, struct cmd_token *token)
+{
+ access_list_autocomplete_afi(AFI_L2VPN, comps, token);
+}
+
+static const struct cmd_variable_handler access_list_handlers[] = {
+ {.tokenname = "ACCESSLIST_NAME",
+ .completions = access_list_autocomplete},
+ {.tokenname = "ACCESSLIST4_NAME",
+ .completions = access_list4_autocomplete},
+ {.tokenname = "ACCESSLIST6_NAME",
+ .completions = access_list6_autocomplete},
+ {.tokenname = "ACCESSLIST_MAC_NAME",
+ .completions = access_list_mac_autocomplete},
+ {.completions = NULL}};
+
+static void access_list_reset_ipv6(void)
+{
+ struct access_list *access;
+ struct access_list *next;
+ struct access_master *master;
+
+ master = access_master_get(AFI_IP6);
+ if (master == NULL)
+ return;
+
+ for (access = master->str.head; access; access = next) {
+ next = access->next;
+ access_list_delete(access);
+ }
+
+ assert(master->str.head == NULL);
+ assert(master->str.tail == NULL);
+}
+
+static void access_list_init_ipv6(void)
+{
+ install_node(&access_ipv6_node);
+
+ install_element(ENABLE_NODE, &show_ipv6_access_list_cmd);
+ install_element(ENABLE_NODE, &show_ipv6_access_list_name_cmd);
+}
+
+void access_list_init(void)
+{
+ cmd_variable_handler_register(access_list_handlers);
+
+ access_list_init_ipv4();
+ access_list_init_ipv6();
+ access_list_init_mac();
+
+ filter_cli_init();
+}
+
+void access_list_reset(void)
+{
+ access_list_reset_ipv4();
+ access_list_reset_ipv6();
+ access_list_reset_mac();
+}
diff --git a/lib/filter.h b/lib/filter.h
new file mode 100644
index 0000000..b378288
--- /dev/null
+++ b/lib/filter.h
@@ -0,0 +1,258 @@
+/*
+ * Route filtering function.
+ * Copyright (C) 1998 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra 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 _ZEBRA_FILTER_H
+#define _ZEBRA_FILTER_H
+
+#include "if.h"
+#include "prefix.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Maximum ACL name length */
+#define ACL_NAMSIZ 128
+
+/** Cisco host wildcard mask. */
+#define CISCO_HOST_WILDCARD_MASK "0.0.0.0"
+/** Cisco host wildcard binary mask. */
+#define CISCO_BIN_HOST_WILDCARD_MASK INADDR_ANY
+
+/** Cisco any wildcard mask. */
+#define CISCO_ANY_WILDCARD_MASK "255.255.255.255"
+/** Cisco binary any wildcard mask. */
+#define CISCO_BIN_ANY_WILDCARD_MASK INADDR_NONE
+
+/* Filter direction. */
+#define FILTER_IN 0
+#define FILTER_OUT 1
+#define FILTER_MAX 2
+
+/* Filter type is made by `permit', `deny' and `dynamic'. */
+enum filter_type { FILTER_DENY, FILTER_PERMIT, FILTER_DYNAMIC };
+
+struct filter_cisco {
+ /* Cisco access-list */
+ int extended;
+ struct in_addr addr;
+ struct in_addr addr_mask;
+ struct in_addr mask;
+ struct in_addr mask_mask;
+};
+
+struct filter_zebra {
+ /* If this filter is "exact" match then this flag is set. */
+ int exact;
+
+ /* Prefix information. */
+ struct prefix prefix;
+};
+
+/* Forward declaration of access-list struct. */
+struct access_list;
+
+/* Filter element of access list */
+struct filter {
+ /* For doubly linked list. */
+ struct filter *next;
+ struct filter *prev;
+
+ /* Parent access-list pointer. */
+ struct access_list *acl;
+
+ /* Filter type information. */
+ enum filter_type type;
+
+ /* Sequence number */
+ int64_t seq;
+
+ /* Cisco access-list */
+ int cisco;
+
+ union {
+ struct filter_cisco cfilter;
+ struct filter_zebra zfilter;
+ } u;
+};
+
+/* Access list */
+struct access_list {
+ char *name;
+ char *remark;
+
+ struct access_master *master;
+
+ struct access_list *next;
+ struct access_list *prev;
+
+ struct filter *head;
+ struct filter *tail;
+};
+
+/* List of access_list. */
+struct access_list_list {
+ struct access_list *head;
+ struct access_list *tail;
+};
+
+/* Master structure of access_list. */
+struct access_master {
+ /* List of access_list which name is string. */
+ struct access_list_list str;
+
+ /* Hook function which is executed when new access_list is added. */
+ void (*add_hook)(struct access_list *);
+
+ /* Hook function which is executed when access_list is deleted. */
+ void (*delete_hook)(struct access_list *);
+};
+
+
+/* Prototypes for access-list. */
+extern void access_list_init(void);
+extern void access_list_reset(void);
+extern void access_list_add_hook(void (*func)(struct access_list *));
+extern void access_list_delete_hook(void (*func)(struct access_list *));
+extern struct access_list *access_list_lookup(afi_t, const char *);
+extern enum filter_type access_list_apply(struct access_list *access,
+ const void *object);
+
+struct access_list *access_list_get(afi_t afi, const char *name);
+void access_list_delete(struct access_list *access);
+struct filter *filter_new(void);
+void access_list_filter_add(struct access_list *access,
+ struct filter *filter);
+void access_list_filter_delete(struct access_list *access,
+ struct filter *filter);
+int64_t filter_new_seq_get(struct access_list *access);
+
+extern const struct frr_yang_module_info frr_filter_info;
+
+
+/* filter_nb.c */
+enum yang_access_list_type {
+ YALT_IPV4 = 0,
+ YALT_IPV6 = 1,
+ YALT_MAC = 2,
+};
+
+enum yang_prefix_list_type {
+ YPLT_IPV4 = 0,
+ YPLT_IPV6 = 1,
+};
+
+enum yang_prefix_list_action {
+ YPLA_DENY = 0,
+ YPLA_PERMIT = 1,
+};
+
+struct acl_dup_args {
+ /** Access list type ("ipv4", "ipv6" or "mac"). */
+ const char *ada_type;
+ /** Access list name. */
+ const char *ada_name;
+
+ /** Entry action. */
+ const char *ada_action;
+
+#define ADA_MAX_VALUES 4
+ /** Entry XPath for value. */
+ const char *ada_xpath[ADA_MAX_VALUES];
+ /** Entry value to match. */
+ const char *ada_value[ADA_MAX_VALUES];
+
+ /** Duplicated entry found in list? */
+ bool ada_found;
+
+ /** Sequence number of the found entry */
+ int64_t ada_seq;
+
+ /** (Optional) Already existing `dnode`. */
+ const struct lyd_node *ada_entry_dnode;
+};
+
+/**
+ * Check for duplicated entries using the candidate configuration.
+ *
+ * \param vty so we can get the candidate config.
+ * \param ada the arguments to check.
+ */
+bool acl_is_dup(const struct lyd_node *dnode, struct acl_dup_args *ada);
+
+struct plist_dup_args {
+ /** Access list type ("ipv4" or "ipv6"). */
+ const char *pda_type;
+ /** Access list name. */
+ const char *pda_name;
+
+ /** Entry action. */
+ const char *pda_action;
+
+ bool any;
+ struct prefix prefix;
+ int ge;
+ int le;
+
+ /** Duplicated entry found in list? */
+ bool pda_found;
+
+ /** Sequence number of the found entry */
+ int64_t pda_seq;
+
+ /** (Optional) Already existing `dnode`. */
+ const struct lyd_node *pda_entry_dnode;
+};
+
+/**
+ * Check for duplicated entries using the candidate configuration.
+ *
+ * \param vty so we can get the candidate config.
+ * \param pda the arguments to check.
+ */
+bool plist_is_dup(const struct lyd_node *dnode, struct plist_dup_args *pda);
+
+/* filter_cli.c */
+struct lyd_node;
+struct vty;
+
+extern int access_list_cmp(const struct lyd_node *dnode1,
+ const struct lyd_node *dnode2);
+extern void access_list_show(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+extern void access_list_remark_show(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+extern int prefix_list_cmp(const struct lyd_node *dnode1,
+ const struct lyd_node *dnode2);
+extern void prefix_list_show(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+extern void prefix_list_remark_show(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+
+void filter_cli_init(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ZEBRA_FILTER_H */
diff --git a/lib/filter_cli.c b/lib/filter_cli.c
new file mode 100644
index 0000000..39b58f5
--- /dev/null
+++ b/lib/filter_cli.c
@@ -0,0 +1,1773 @@
+/*
+ * FRR filter CLI implementation.
+ *
+ * Copyright (C) 2019 Network Device Education Foundation, Inc. ("NetDEF")
+ * Rafael Zalamena
+ *
+ * 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; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301 USA.
+ */
+
+#include "zebra.h"
+#include "northbound.h"
+#include "prefix.h"
+
+#include "lib/command.h"
+#include "lib/filter.h"
+#include "lib/northbound_cli.h"
+#include "lib/plist.h"
+#include "lib/plist_int.h"
+#include "lib/printfrr.h"
+
+#ifndef VTYSH_EXTRACT_PL
+#include "lib/filter_cli_clippy.c"
+#endif /* VTYSH_EXTRACT_PL */
+
+#define ACCESS_LIST_STR "Access list entry\n"
+#define ACCESS_LIST_ZEBRA_STR "Access list name\n"
+#define ACCESS_LIST_SEQ_STR \
+ "Sequence number of an entry\n" \
+ "Sequence number\n"
+#define ACCESS_LIST_ACTION_STR \
+ "Specify packets to reject\n" \
+ "Specify packets to forward\n"
+#define ACCESS_LIST_REMARK_STR "Access list entry comment\n"
+#define ACCESS_LIST_REMARK_LINE_STR "Comment up to 100 characters\n"
+
+#define PREFIX_LIST_NAME_STR "Prefix list entry name\n"
+
+/*
+ * Helper function to generate a sequence number for legacy commands.
+ */
+static int acl_get_seq_cb(const struct lyd_node *dnode, void *arg)
+{
+ int64_t *seq = arg;
+ int64_t cur_seq = yang_dnode_get_uint32(dnode, "sequence");
+
+ if (cur_seq > *seq)
+ *seq = cur_seq;
+
+ return YANG_ITER_CONTINUE;
+}
+
+/**
+ * Helper function that iterates over the XPath `xpath` on the candidate
+ * configuration in `vty->candidate_config`.
+ *
+ * \param[in] vty shell context with the candidate configuration.
+ * \param[in] xpath the XPath to look for the sequence leaf.
+ * \returns next unused sequence number.
+ */
+static long acl_get_seq(struct vty *vty, const char *xpath)
+{
+ int64_t seq = 0;
+
+ yang_dnode_iterate(acl_get_seq_cb, &seq, vty->candidate_config->dnode,
+ "%s/entry", xpath);
+
+ return seq + 5;
+}
+
+static int acl_remove_if_empty(struct vty *vty, const char *iptype,
+ const char *name)
+{
+ char xpath[XPATH_MAXLEN];
+
+ snprintf(xpath, sizeof(xpath),
+ "/frr-filter:lib/access-list[type='%s'][name='%s']/remark",
+ iptype, name);
+ /* List is not empty if there is a remark, check that: */
+ if (yang_dnode_exists(vty->candidate_config->dnode, xpath))
+ return CMD_SUCCESS;
+
+ /* Check if we have any entries: */
+ snprintf(xpath, sizeof(xpath),
+ "/frr-filter:lib/access-list[type='%s'][name='%s']", iptype,
+ name);
+ /*
+ * NOTE: if the list is empty it will return the first sequence
+ * number: 5.
+ */
+ if (acl_get_seq(vty, xpath) != 5)
+ return CMD_SUCCESS;
+
+ /* Nobody is using this list, lets remove it. */
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+static int acl_remove(struct vty *vty, const char *iptype, const char *name,
+ int64_t sseq)
+{
+ char xpath[XPATH_MAXLEN];
+ int rv;
+
+ snprintfrr(
+ xpath, sizeof(xpath),
+ "/frr-filter:lib/access-list[type='%s'][name='%s']/entry[sequence='%" PRId64 "']",
+ iptype, name, sseq);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ rv = nb_cli_apply_changes(vty, NULL);
+ if (rv == CMD_SUCCESS)
+ return acl_remove_if_empty(vty, iptype, name);
+
+ return rv;
+}
+
+/*
+ * Cisco (legacy) access lists.
+ */
+DEFPY_YANG(
+ access_list_std, access_list_std_cmd,
+ "access-list WORD$name [seq (1-4294967295)$seq] <deny|permit>$action <[host] A.B.C.D$host|A.B.C.D$host A.B.C.D$mask>",
+ ACCESS_LIST_STR
+ ACCESS_LIST_ZEBRA_STR
+ ACCESS_LIST_SEQ_STR
+ ACCESS_LIST_ACTION_STR
+ "A single host address\n"
+ "Address to match\n"
+ "Address to match\n"
+ "Wildcard bits\n")
+{
+ int64_t sseq;
+ struct acl_dup_args ada = {};
+ char xpath[XPATH_MAXLEN];
+ char xpath_entry[XPATH_MAXLEN + 128];
+
+ /*
+ * Backward compatibility: don't complain about duplicated values,
+ * just silently accept.
+ */
+ ada.ada_type = "ipv4";
+ ada.ada_name = name;
+ ada.ada_action = action;
+ if (host_str && mask_str == NULL) {
+ ada.ada_xpath[0] = "./host";
+ ada.ada_value[0] = host_str;
+ } else if (host_str && mask_str) {
+ ada.ada_xpath[0] = "./network/address";
+ ada.ada_value[0] = host_str;
+ ada.ada_xpath[1] = "./network/mask";
+ ada.ada_value[1] = mask_str;
+ } else {
+ ada.ada_xpath[0] = "./source-any";
+ ada.ada_value[0] = "";
+ }
+
+ if (acl_is_dup(vty->candidate_config->dnode, &ada))
+ return CMD_SUCCESS;
+
+ /*
+ * Create the access-list first, so we can generate sequence if
+ * none given (backward compatibility).
+ */
+ snprintf(xpath, sizeof(xpath),
+ "/frr-filter:lib/access-list[type='ipv4'][name='%s']", name);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
+ if (seq_str == NULL) {
+ /* Use XPath to find the next sequence number. */
+ sseq = acl_get_seq(vty, xpath);
+ snprintfrr(xpath_entry, sizeof(xpath_entry),
+ "%s/entry[sequence='%" PRId64 "']", xpath, sseq);
+ } else
+ snprintfrr(xpath_entry, sizeof(xpath_entry),
+ "%s/entry[sequence='%s']", xpath, seq_str);
+
+ nb_cli_enqueue_change(vty, xpath_entry, NB_OP_CREATE, NULL);
+
+ nb_cli_enqueue_change(vty, "./action", NB_OP_MODIFY, action);
+ if (host_str != NULL && mask_str == NULL) {
+ nb_cli_enqueue_change(vty, "./host", NB_OP_MODIFY, host_str);
+ } else if (host_str != NULL && mask_str != NULL) {
+ nb_cli_enqueue_change(vty, "./network/address", NB_OP_MODIFY,
+ host_str);
+ nb_cli_enqueue_change(vty, "./network/mask", NB_OP_MODIFY,
+ mask_str);
+ } else {
+ nb_cli_enqueue_change(vty, "./source-any", NB_OP_CREATE, NULL);
+ }
+
+ return nb_cli_apply_changes(vty, xpath_entry);
+}
+
+DEFPY_YANG(
+ no_access_list_std, no_access_list_std_cmd,
+ "no access-list WORD$name [seq (1-4294967295)$seq] <deny|permit>$action <[host] A.B.C.D$host|A.B.C.D$host A.B.C.D$mask>",
+ NO_STR
+ ACCESS_LIST_STR
+ ACCESS_LIST_ZEBRA_STR
+ ACCESS_LIST_SEQ_STR
+ ACCESS_LIST_ACTION_STR
+ "A single host address\n"
+ "Address to match\n"
+ "Address to match\n"
+ "Wildcard bits\n")
+{
+ int64_t sseq;
+ struct acl_dup_args ada = {};
+
+ /* If the user provided sequence number, then just go for it. */
+ if (seq_str != NULL)
+ return acl_remove(vty, "ipv4", name, seq);
+
+ /* Otherwise, to keep compatibility, we need to figure it out. */
+ ada.ada_type = "ipv4";
+ ada.ada_name = name;
+ ada.ada_action = action;
+ if (host_str && mask_str == NULL) {
+ ada.ada_xpath[0] = "./host";
+ ada.ada_value[0] = host_str;
+ } else if (host_str && mask_str) {
+ ada.ada_xpath[0] = "./network/address";
+ ada.ada_value[0] = host_str;
+ ada.ada_xpath[1] = "./network/mask";
+ ada.ada_value[1] = mask_str;
+ } else {
+ ada.ada_xpath[0] = "./source-any";
+ ada.ada_value[0] = "";
+ }
+
+ if (acl_is_dup(vty->candidate_config->dnode, &ada))
+ sseq = ada.ada_seq;
+ else
+ return CMD_WARNING_CONFIG_FAILED;
+
+ return acl_remove(vty, "ipv4", name, sseq);
+}
+
+DEFPY_YANG(
+ access_list_ext, access_list_ext_cmd,
+ "access-list WORD$name [seq (1-4294967295)$seq] <deny|permit>$action ip <A.B.C.D$src A.B.C.D$src_mask|host A.B.C.D$src|any> <A.B.C.D$dst A.B.C.D$dst_mask|host A.B.C.D$dst|any>",
+ ACCESS_LIST_STR
+ ACCESS_LIST_ZEBRA_STR
+ ACCESS_LIST_SEQ_STR
+ ACCESS_LIST_ACTION_STR
+ "IPv4 address\n"
+ "Source address to match\n"
+ "Source address mask to apply\n"
+ "Single source host\n"
+ "Source address to match\n"
+ "Any source host\n"
+ "Destination address to match\n"
+ "Destination address mask to apply\n"
+ "Single destination host\n"
+ "Destination address to match\n"
+ "Any destination host\n")
+{
+ int idx = 0;
+ int64_t sseq;
+ struct acl_dup_args ada = {};
+ char xpath[XPATH_MAXLEN];
+ char xpath_entry[XPATH_MAXLEN + 128];
+
+ /*
+ * Backward compatibility: don't complain about duplicated values,
+ * just silently accept.
+ */
+ ada.ada_type = "ipv4";
+ ada.ada_name = name;
+ ada.ada_action = action;
+ if (src_str && src_mask_str == NULL) {
+ ada.ada_xpath[idx] = "./host";
+ ada.ada_value[idx] = src_str;
+ idx++;
+ } else if (src_str && src_mask_str) {
+ ada.ada_xpath[idx] = "./network/address";
+ ada.ada_value[idx] = src_str;
+ idx++;
+ ada.ada_xpath[idx] = "./network/mask";
+ ada.ada_value[idx] = src_mask_str;
+ idx++;
+ } else {
+ ada.ada_xpath[idx] = "./source-any";
+ ada.ada_value[idx] = "";
+ idx++;
+ }
+
+ if (dst_str && dst_mask_str == NULL) {
+ ada.ada_xpath[idx] = "./destination-host";
+ ada.ada_value[idx] = dst_str;
+ idx++;
+ } else if (dst_str && dst_mask_str) {
+ ada.ada_xpath[idx] = "./destination-network/address";
+ ada.ada_value[idx] = dst_str;
+ idx++;
+ ada.ada_xpath[idx] = "./destination-network/mask";
+ ada.ada_value[idx] = dst_mask_str;
+ idx++;
+ } else {
+ ada.ada_xpath[idx] = "./destination-any";
+ ada.ada_value[idx] = "";
+ idx++;
+ }
+
+ if (acl_is_dup(vty->candidate_config->dnode, &ada))
+ return CMD_SUCCESS;
+
+ /*
+ * Create the access-list first, so we can generate sequence if
+ * none given (backward compatibility).
+ */
+ snprintf(xpath, sizeof(xpath),
+ "/frr-filter:lib/access-list[type='ipv4'][name='%s']", name);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
+ if (seq_str == NULL) {
+ /* Use XPath to find the next sequence number. */
+ sseq = acl_get_seq(vty, xpath);
+ snprintfrr(xpath_entry, sizeof(xpath_entry),
+ "%s/entry[sequence='%" PRId64 "']", xpath, sseq);
+ } else
+ snprintfrr(xpath_entry, sizeof(xpath_entry),
+ "%s/entry[sequence='%s']", xpath, seq_str);
+
+ nb_cli_enqueue_change(vty, xpath_entry, NB_OP_CREATE, NULL);
+
+ nb_cli_enqueue_change(vty, "./action", NB_OP_MODIFY, action);
+ if (src_str != NULL && src_mask_str == NULL) {
+ nb_cli_enqueue_change(vty, "./host", NB_OP_MODIFY, src_str);
+ } else if (src_str != NULL && src_mask_str != NULL) {
+ nb_cli_enqueue_change(vty, "./network/address", NB_OP_MODIFY,
+ src_str);
+ nb_cli_enqueue_change(vty, "./network/mask", NB_OP_MODIFY,
+ src_mask_str);
+ } else {
+ nb_cli_enqueue_change(vty, "./source-any", NB_OP_CREATE, NULL);
+ }
+
+ if (dst_str != NULL && dst_mask_str == NULL) {
+ nb_cli_enqueue_change(vty, "./destination-host", NB_OP_MODIFY,
+ dst_str);
+ } else if (dst_str != NULL && dst_mask_str != NULL) {
+ nb_cli_enqueue_change(vty, "./destination-network/address",
+ NB_OP_MODIFY, dst_str);
+ nb_cli_enqueue_change(vty, "./destination-network/mask",
+ NB_OP_MODIFY, dst_mask_str);
+ } else {
+ nb_cli_enqueue_change(vty, "./destination-any", NB_OP_CREATE,
+ NULL);
+ }
+
+ return nb_cli_apply_changes(vty, xpath_entry);
+}
+
+DEFPY_YANG(
+ no_access_list_ext, no_access_list_ext_cmd,
+ "no access-list WORD$name [seq (1-4294967295)$seq] <deny|permit>$action ip <A.B.C.D$src A.B.C.D$src_mask|host A.B.C.D$src|any> <A.B.C.D$dst A.B.C.D$dst_mask|host A.B.C.D$dst|any>",
+ NO_STR
+ ACCESS_LIST_STR
+ ACCESS_LIST_ZEBRA_STR
+ ACCESS_LIST_SEQ_STR
+ ACCESS_LIST_ACTION_STR
+ "Any Internet Protocol\n"
+ "Source address to match\n"
+ "Source address mask to apply\n"
+ "Single source host\n"
+ "Source address to match\n"
+ "Any source host\n"
+ "Destination address to match\n"
+ "Destination address mask to apply\n"
+ "Single destination host\n"
+ "Destination address to match\n"
+ "Any destination host\n")
+{
+ int idx = 0;
+ int64_t sseq;
+ struct acl_dup_args ada = {};
+
+ /* If the user provided sequence number, then just go for it. */
+ if (seq_str != NULL)
+ return acl_remove(vty, "ipv4", name, seq);
+
+ /* Otherwise, to keep compatibility, we need to figure it out. */
+ ada.ada_type = "ipv4";
+ ada.ada_name = name;
+ ada.ada_action = action;
+ if (src_str && src_mask_str == NULL) {
+ ada.ada_xpath[idx] = "./host";
+ ada.ada_value[idx] = src_str;
+ idx++;
+ } else if (src_str && src_mask_str) {
+ ada.ada_xpath[idx] = "./network/address";
+ ada.ada_value[idx] = src_str;
+ idx++;
+ ada.ada_xpath[idx] = "./network/mask";
+ ada.ada_value[idx] = src_mask_str;
+ idx++;
+ } else {
+ ada.ada_xpath[idx] = "./source-any";
+ ada.ada_value[idx] = "";
+ idx++;
+ }
+
+ if (dst_str && dst_mask_str == NULL) {
+ ada.ada_xpath[idx] = "./destination-host";
+ ada.ada_value[idx] = dst_str;
+ idx++;
+ } else if (dst_str && dst_mask_str) {
+ ada.ada_xpath[idx] = "./destination-network/address";
+ ada.ada_value[idx] = dst_str;
+ idx++;
+ ada.ada_xpath[idx] = "./destination-network/mask";
+ ada.ada_value[idx] = dst_mask_str;
+ idx++;
+ } else {
+ ada.ada_xpath[idx] = "./destination-any";
+ ada.ada_value[idx] = "";
+ idx++;
+ }
+
+ if (acl_is_dup(vty->candidate_config->dnode, &ada))
+ sseq = ada.ada_seq;
+ else
+ return CMD_WARNING_CONFIG_FAILED;
+
+ return acl_remove(vty, "ipv4", name, sseq);
+}
+
+/*
+ * Zebra access lists.
+ */
+DEFPY_YANG(
+ access_list, access_list_cmd,
+ "access-list WORD$name [seq (1-4294967295)$seq] <deny|permit>$action <A.B.C.D/M$prefix [exact-match$exact]|any>",
+ ACCESS_LIST_STR
+ ACCESS_LIST_ZEBRA_STR
+ ACCESS_LIST_SEQ_STR
+ ACCESS_LIST_ACTION_STR
+ "Prefix to match. e.g. 10.0.0.0/8\n"
+ "Exact match of the prefixes\n"
+ "Match any IPv4\n")
+{
+ int64_t sseq;
+ struct acl_dup_args ada = {};
+ char xpath[XPATH_MAXLEN];
+ char xpath_entry[XPATH_MAXLEN + 128];
+
+ /*
+ * Backward compatibility: don't complain about duplicated values,
+ * just silently accept.
+ */
+ ada.ada_type = "ipv4";
+ ada.ada_name = name;
+ ada.ada_action = action;
+
+ if (prefix_str) {
+ ada.ada_xpath[0] = "./ipv4-prefix";
+ ada.ada_value[0] = prefix_str;
+ if (exact) {
+ ada.ada_xpath[1] = "./ipv4-exact-match";
+ ada.ada_value[1] = "true";
+ }
+ } else {
+ ada.ada_xpath[0] = "./any";
+ ada.ada_value[0] = "";
+ }
+
+ if (acl_is_dup(vty->candidate_config->dnode, &ada))
+ return CMD_SUCCESS;
+
+ /*
+ * Create the access-list first, so we can generate sequence if
+ * none given (backward compatibility).
+ */
+ snprintf(xpath, sizeof(xpath),
+ "/frr-filter:lib/access-list[type='ipv4'][name='%s']", name);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
+ if (seq_str == NULL) {
+ /* Use XPath to find the next sequence number. */
+ sseq = acl_get_seq(vty, xpath);
+ snprintfrr(xpath_entry, sizeof(xpath_entry),
+ "%s/entry[sequence='%" PRId64 "']", xpath, sseq);
+ } else
+ snprintfrr(xpath_entry, sizeof(xpath_entry),
+ "%s/entry[sequence='%s']", xpath, seq_str);
+
+ nb_cli_enqueue_change(vty, xpath_entry, NB_OP_CREATE, NULL);
+
+ nb_cli_enqueue_change(vty, "./action", NB_OP_MODIFY, action);
+ if (prefix_str != NULL) {
+ nb_cli_enqueue_change(vty, "./ipv4-prefix", NB_OP_MODIFY,
+ prefix_str);
+ nb_cli_enqueue_change(vty, "./ipv4-exact-match", NB_OP_MODIFY,
+ exact ? "true" : "false");
+ } else {
+ nb_cli_enqueue_change(vty, "./any", NB_OP_CREATE, NULL);
+ }
+
+ return nb_cli_apply_changes(vty, xpath_entry);
+}
+
+DEFPY_YANG(
+ no_access_list, no_access_list_cmd,
+ "no access-list WORD$name [seq (1-4294967295)$seq] <deny|permit>$action <A.B.C.D/M$prefix [exact-match$exact]|any>",
+ NO_STR
+ ACCESS_LIST_STR
+ ACCESS_LIST_ZEBRA_STR
+ ACCESS_LIST_SEQ_STR
+ ACCESS_LIST_ACTION_STR
+ "Prefix to match. e.g. 10.0.0.0/8\n"
+ "Exact match of the prefixes\n"
+ "Match any IPv4\n")
+{
+ int64_t sseq;
+ struct acl_dup_args ada = {};
+
+ /* If the user provided sequence number, then just go for it. */
+ if (seq_str != NULL)
+ return acl_remove(vty, "ipv4", name, seq);
+
+ /* Otherwise, to keep compatibility, we need to figure it out. */
+ ada.ada_type = "ipv4";
+ ada.ada_name = name;
+ ada.ada_action = action;
+
+ if (prefix_str) {
+ ada.ada_xpath[0] = "./ipv4-prefix";
+ ada.ada_value[0] = prefix_str;
+ if (exact) {
+ ada.ada_xpath[1] = "./ipv4-exact-match";
+ ada.ada_value[1] = "true";
+ }
+ } else {
+ ada.ada_xpath[0] = "./any";
+ ada.ada_value[0] = "";
+ }
+
+ if (acl_is_dup(vty->candidate_config->dnode, &ada))
+ sseq = ada.ada_seq;
+ else
+ return CMD_WARNING_CONFIG_FAILED;
+
+ return acl_remove(vty, "ipv4", name, sseq);
+}
+
+DEFPY_YANG(
+ no_access_list_all, no_access_list_all_cmd,
+ "no access-list WORD$name",
+ NO_STR
+ ACCESS_LIST_STR
+ ACCESS_LIST_ZEBRA_STR)
+{
+ char xpath[XPATH_MAXLEN];
+
+ snprintf(xpath, sizeof(xpath),
+ "/frr-filter:lib/access-list[type='ipv4'][name='%s']", name);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ access_list_remark, access_list_remark_cmd,
+ "access-list WORD$name remark LINE...",
+ ACCESS_LIST_STR
+ ACCESS_LIST_ZEBRA_STR
+ ACCESS_LIST_REMARK_STR
+ ACCESS_LIST_REMARK_LINE_STR)
+{
+ int rv;
+ char *remark;
+ char xpath[XPATH_MAXLEN];
+
+ snprintf(xpath, sizeof(xpath),
+ "/frr-filter:lib/access-list[type='ipv4'][name='%s']", name);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
+
+ remark = argv_concat(argv, argc, 3);
+ nb_cli_enqueue_change(vty, "./remark", NB_OP_CREATE, remark);
+ rv = nb_cli_apply_changes(vty, xpath);
+ XFREE(MTYPE_TMP, remark);
+
+ return rv;
+}
+
+DEFPY_YANG(
+ no_access_list_remark, no_access_list_remark_cmd,
+ "no access-list WORD$name remark",
+ NO_STR
+ ACCESS_LIST_STR
+ ACCESS_LIST_ZEBRA_STR
+ ACCESS_LIST_REMARK_STR)
+{
+ char xpath[XPATH_MAXLEN];
+ int rv;
+
+ snprintf(xpath, sizeof(xpath),
+ "/frr-filter:lib/access-list[type='ipv4'][name='%s']/remark",
+ name);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ rv = nb_cli_apply_changes(vty, NULL);
+ if (rv == CMD_SUCCESS)
+ return acl_remove_if_empty(vty, "ipv4", name);
+
+ return rv;
+}
+
+ALIAS(
+ no_access_list_remark, no_access_list_remark_line_cmd,
+ "no access-list WORD$name remark LINE...",
+ NO_STR
+ ACCESS_LIST_STR
+ ACCESS_LIST_ZEBRA_STR
+ ACCESS_LIST_REMARK_STR
+ ACCESS_LIST_REMARK_LINE_STR)
+
+DEFPY_YANG(
+ ipv6_access_list, ipv6_access_list_cmd,
+ "ipv6 access-list WORD$name [seq (1-4294967295)$seq] <deny|permit>$action <X:X::X:X/M$prefix [exact-match$exact]|any>",
+ IPV6_STR
+ ACCESS_LIST_STR
+ ACCESS_LIST_ZEBRA_STR
+ ACCESS_LIST_SEQ_STR
+ ACCESS_LIST_ACTION_STR
+ "IPv6 prefix\n"
+ "Exact match of the prefixes\n"
+ "Match any IPv6\n")
+{
+ int64_t sseq;
+ struct acl_dup_args ada = {};
+ char xpath[XPATH_MAXLEN];
+ char xpath_entry[XPATH_MAXLEN + 128];
+
+ /*
+ * Backward compatibility: don't complain about duplicated values,
+ * just silently accept.
+ */
+ ada.ada_type = "ipv6";
+ ada.ada_name = name;
+ ada.ada_action = action;
+
+ if (prefix_str) {
+ ada.ada_xpath[0] = "./ipv6-prefix";
+ ada.ada_value[0] = prefix_str;
+ if (exact) {
+ ada.ada_xpath[1] = "./ipv6-exact-match";
+ ada.ada_value[1] = "true";
+ }
+ } else {
+ ada.ada_xpath[0] = "./any";
+ ada.ada_value[0] = "";
+ }
+
+ if (acl_is_dup(vty->candidate_config->dnode, &ada))
+ return CMD_SUCCESS;
+
+ /*
+ * Create the access-list first, so we can generate sequence if
+ * none given (backward compatibility).
+ */
+ snprintf(xpath, sizeof(xpath),
+ "/frr-filter:lib/access-list[type='ipv6'][name='%s']", name);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
+ if (seq_str == NULL) {
+ /* Use XPath to find the next sequence number. */
+ sseq = acl_get_seq(vty, xpath);
+ snprintfrr(xpath_entry, sizeof(xpath_entry),
+ "%s/entry[sequence='%" PRId64 "']", xpath, sseq);
+ } else
+ snprintfrr(xpath_entry, sizeof(xpath_entry),
+ "%s/entry[sequence='%s']", xpath, seq_str);
+
+ nb_cli_enqueue_change(vty, xpath_entry, NB_OP_CREATE, NULL);
+
+ nb_cli_enqueue_change(vty, "./action", NB_OP_MODIFY, action);
+ if (prefix_str != NULL) {
+ nb_cli_enqueue_change(vty, "./ipv6-prefix", NB_OP_MODIFY,
+ prefix_str);
+ nb_cli_enqueue_change(vty, "./ipv6-exact-match", NB_OP_MODIFY,
+ exact ? "true" : "false");
+ } else {
+ nb_cli_enqueue_change(vty, "./any", NB_OP_CREATE, NULL);
+ }
+
+ return nb_cli_apply_changes(vty, xpath_entry);
+}
+
+DEFPY_YANG(
+ no_ipv6_access_list, no_ipv6_access_list_cmd,
+ "no ipv6 access-list WORD$name [seq (1-4294967295)$seq] <deny|permit>$action <X:X::X:X/M$prefix [exact-match$exact]|any>",
+ NO_STR
+ IPV6_STR
+ ACCESS_LIST_STR
+ ACCESS_LIST_ZEBRA_STR
+ ACCESS_LIST_SEQ_STR
+ ACCESS_LIST_ACTION_STR
+ "IPv6 prefix\n"
+ "Exact match of the prefixes\n"
+ "Match any IPv6\n")
+{
+ int64_t sseq;
+ struct acl_dup_args ada = {};
+
+ /* If the user provided sequence number, then just go for it. */
+ if (seq_str != NULL)
+ return acl_remove(vty, "ipv6", name, seq);
+
+ /* Otherwise, to keep compatibility, we need to figure it out. */
+ ada.ada_type = "ipv6";
+ ada.ada_name = name;
+ ada.ada_action = action;
+
+ if (prefix_str) {
+ ada.ada_xpath[0] = "./ipv6-prefix";
+ ada.ada_value[0] = prefix_str;
+ if (exact) {
+ ada.ada_xpath[1] = "./ipv6-exact-match";
+ ada.ada_value[1] = "true";
+ }
+ } else {
+ ada.ada_xpath[0] = "./any";
+ ada.ada_value[0] = "";
+ }
+
+ if (acl_is_dup(vty->candidate_config->dnode, &ada))
+ sseq = ada.ada_seq;
+ else
+ return CMD_WARNING_CONFIG_FAILED;
+
+ return acl_remove(vty, "ipv6", name, sseq);
+}
+
+DEFPY_YANG(
+ no_ipv6_access_list_all, no_ipv6_access_list_all_cmd,
+ "no ipv6 access-list WORD$name",
+ NO_STR
+ IPV6_STR
+ ACCESS_LIST_STR
+ ACCESS_LIST_ZEBRA_STR)
+{
+ char xpath[XPATH_MAXLEN];
+
+ snprintf(xpath, sizeof(xpath),
+ "/frr-filter:lib/access-list[type='ipv6'][name='%s']", name);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ ipv6_access_list_remark, ipv6_access_list_remark_cmd,
+ "ipv6 access-list WORD$name remark LINE...",
+ IPV6_STR
+ ACCESS_LIST_STR
+ ACCESS_LIST_ZEBRA_STR
+ ACCESS_LIST_REMARK_STR
+ ACCESS_LIST_REMARK_LINE_STR)
+{
+ int rv;
+ char *remark;
+ char xpath[XPATH_MAXLEN];
+
+ snprintf(xpath, sizeof(xpath),
+ "/frr-filter:lib/access-list[type='ipv6'][name='%s']", name);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
+
+ remark = argv_concat(argv, argc, 4);
+ nb_cli_enqueue_change(vty, "./remark", NB_OP_CREATE, remark);
+ rv = nb_cli_apply_changes(vty, xpath);
+ XFREE(MTYPE_TMP, remark);
+
+ return rv;
+}
+
+DEFPY_YANG(
+ no_ipv6_access_list_remark, no_ipv6_access_list_remark_cmd,
+ "no ipv6 access-list WORD$name remark",
+ NO_STR
+ IPV6_STR
+ ACCESS_LIST_STR
+ ACCESS_LIST_ZEBRA_STR
+ ACCESS_LIST_REMARK_STR)
+{
+ char xpath[XPATH_MAXLEN];
+ int rv;
+
+ snprintf(xpath, sizeof(xpath),
+ "/frr-filter:lib/access-list[type='ipv6'][name='%s']/remark",
+ name);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ rv = nb_cli_apply_changes(vty, NULL);
+ if (rv == CMD_SUCCESS)
+ return acl_remove_if_empty(vty, "ipv6", name);
+
+ return rv;
+}
+
+ALIAS(
+ no_ipv6_access_list_remark, no_ipv6_access_list_remark_line_cmd,
+ "no ipv6 access-list ACCESSLIST6_NAME$name remark LINE...",
+ NO_STR
+ IPV6_STR
+ ACCESS_LIST_STR
+ ACCESS_LIST_ZEBRA_STR
+ ACCESS_LIST_REMARK_STR
+ ACCESS_LIST_REMARK_LINE_STR)
+
+DEFPY_YANG(
+ mac_access_list, mac_access_list_cmd,
+ "mac access-list ACCESSLIST_MAC_NAME$name [seq (1-4294967295)$seq] <deny|permit>$action <X:X:X:X:X:X$mac|any>",
+ MAC_STR
+ ACCESS_LIST_STR
+ ACCESS_LIST_ZEBRA_STR
+ ACCESS_LIST_SEQ_STR
+ ACCESS_LIST_ACTION_STR
+ "MAC address\n"
+ "Match any MAC address\n")
+{
+ int64_t sseq;
+ struct acl_dup_args ada = {};
+ char xpath[XPATH_MAXLEN];
+ char xpath_entry[XPATH_MAXLEN + 128];
+
+ /*
+ * Backward compatibility: don't complain about duplicated values,
+ * just silently accept.
+ */
+ ada.ada_type = "mac";
+ ada.ada_name = name;
+ ada.ada_action = action;
+
+ if (mac_str) {
+ ada.ada_xpath[0] = "./mac";
+ ada.ada_value[0] = mac_str;
+ } else {
+ ada.ada_xpath[0] = "./any";
+ ada.ada_value[0] = "";
+ }
+
+ if (acl_is_dup(vty->candidate_config->dnode, &ada))
+ return CMD_SUCCESS;
+
+ /*
+ * Create the access-list first, so we can generate sequence if
+ * none given (backward compatibility).
+ */
+ snprintf(xpath, sizeof(xpath),
+ "/frr-filter:lib/access-list[type='mac'][name='%s']", name);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
+ if (seq_str == NULL) {
+ /* Use XPath to find the next sequence number. */
+ sseq = acl_get_seq(vty, xpath);
+ snprintfrr(xpath_entry, sizeof(xpath_entry),
+ "%s/entry[sequence='%" PRId64 "']", xpath, sseq);
+ } else
+ snprintfrr(xpath_entry, sizeof(xpath_entry),
+ "%s/entry[sequence='%s']", xpath, seq_str);
+
+ nb_cli_enqueue_change(vty, xpath_entry, NB_OP_CREATE, NULL);
+
+ nb_cli_enqueue_change(vty, "./action", NB_OP_MODIFY, action);
+ if (mac_str != NULL) {
+ nb_cli_enqueue_change(vty, "./mac", NB_OP_MODIFY, mac_str);
+ } else {
+ nb_cli_enqueue_change(vty, "./any", NB_OP_CREATE, NULL);
+ }
+
+ return nb_cli_apply_changes(vty, xpath_entry);
+}
+
+DEFPY_YANG(
+ no_mac_access_list, no_mac_access_list_cmd,
+ "no mac access-list ACCESSLIST_MAC_NAME$name [seq (1-4294967295)$seq] <deny|permit>$action <X:X:X:X:X:X$mac|any>",
+ NO_STR
+ MAC_STR
+ ACCESS_LIST_STR
+ ACCESS_LIST_ZEBRA_STR
+ ACCESS_LIST_SEQ_STR
+ ACCESS_LIST_ACTION_STR
+ "MAC address\n"
+ "Match any MAC address\n")
+{
+ int64_t sseq;
+ struct acl_dup_args ada = {};
+
+ /* If the user provided sequence number, then just go for it. */
+ if (seq_str != NULL)
+ return acl_remove(vty, "mac", name, seq);
+
+ /* Otherwise, to keep compatibility, we need to figure it out. */
+ ada.ada_type = "mac";
+ ada.ada_name = name;
+ ada.ada_action = action;
+
+ if (mac_str) {
+ ada.ada_xpath[0] = "./mac";
+ ada.ada_value[0] = mac_str;
+ } else {
+ ada.ada_xpath[0] = "./any";
+ ada.ada_value[0] = "";
+ }
+
+ if (acl_is_dup(vty->candidate_config->dnode, &ada))
+ sseq = ada.ada_seq;
+ else
+ return CMD_WARNING_CONFIG_FAILED;
+
+ return acl_remove(vty, "mac", name, sseq);
+}
+
+DEFPY_YANG(
+ no_mac_access_list_all, no_mac_access_list_all_cmd,
+ "no mac access-list ACCESSLIST_MAC_NAME$name",
+ NO_STR
+ MAC_STR
+ ACCESS_LIST_STR
+ ACCESS_LIST_ZEBRA_STR)
+{
+ char xpath[XPATH_MAXLEN];
+
+ snprintf(xpath, sizeof(xpath),
+ "/frr-filter:lib/access-list[type='mac'][name='%s']", name);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ mac_access_list_remark, mac_access_list_remark_cmd,
+ "mac access-list ACCESSLIST_MAC_NAME$name remark LINE...",
+ MAC_STR
+ ACCESS_LIST_STR
+ ACCESS_LIST_ZEBRA_STR
+ ACCESS_LIST_REMARK_STR
+ ACCESS_LIST_REMARK_LINE_STR)
+{
+ int rv;
+ char *remark;
+ char xpath[XPATH_MAXLEN];
+
+ snprintf(xpath, sizeof(xpath),
+ "/frr-filter:lib/access-list[type='mac'][name='%s']", name);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
+
+ remark = argv_concat(argv, argc, 4);
+ nb_cli_enqueue_change(vty, "./remark", NB_OP_CREATE, remark);
+ rv = nb_cli_apply_changes(vty, xpath);
+ XFREE(MTYPE_TMP, remark);
+
+ return rv;
+}
+
+DEFPY_YANG(
+ no_mac_access_list_remark, no_mac_access_list_remark_cmd,
+ "no mac access-list ACCESSLIST_MAC_NAME$name remark",
+ NO_STR
+ MAC_STR
+ ACCESS_LIST_STR
+ ACCESS_LIST_ZEBRA_STR
+ ACCESS_LIST_REMARK_STR)
+{
+ char xpath[XPATH_MAXLEN];
+ int rv;
+
+ snprintf(xpath, sizeof(xpath),
+ "/frr-filter:lib/access-list[type='mac'][name='%s']/remark",
+ name);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ rv = nb_cli_apply_changes(vty, NULL);
+ if (rv == CMD_SUCCESS)
+ return acl_remove_if_empty(vty, "mac", name);
+
+ return rv;
+}
+
+ALIAS(
+ no_mac_access_list_remark, no_mac_access_list_remark_line_cmd,
+ "no mac access-list ACCESSLIST_MAC_NAME$name remark LINE...",
+ NO_STR
+ MAC_STR
+ ACCESS_LIST_STR
+ ACCESS_LIST_ZEBRA_STR
+ ACCESS_LIST_REMARK_STR
+ ACCESS_LIST_REMARK_LINE_STR)
+
+int access_list_cmp(const struct lyd_node *dnode1,
+ const struct lyd_node *dnode2)
+{
+ uint32_t seq1 = yang_dnode_get_uint32(dnode1, "./sequence");
+ uint32_t seq2 = yang_dnode_get_uint32(dnode2, "./sequence");
+
+ return seq1 - seq2;
+}
+
+void access_list_show(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ int type = yang_dnode_get_enum(dnode, "../type");
+ struct prefix p;
+ bool is_any;
+ bool is_exact = false;
+ bool cisco_style = false;
+ bool cisco_extended = false;
+ struct in_addr addr, mask;
+ char macstr[PREFIX2STR_BUFFER];
+
+ is_any = yang_dnode_exists(dnode, "./any");
+ switch (type) {
+ case YALT_IPV4:
+ if (is_any)
+ break;
+
+ if (yang_dnode_exists(dnode, "./host")
+ || yang_dnode_exists(dnode, "./network/address")
+ || yang_dnode_exists(dnode, "./source-any")) {
+ cisco_style = true;
+ if (yang_dnode_exists(dnode, "./destination-host")
+ || yang_dnode_exists(
+ dnode, "./destination-network/address")
+ || yang_dnode_exists(dnode, "./destination-any"))
+ cisco_extended = true;
+ } else {
+ yang_dnode_get_prefix(&p, dnode, "./ipv4-prefix");
+ is_exact = yang_dnode_get_bool(dnode,
+ "./ipv4-exact-match");
+ }
+ break;
+ case YALT_IPV6: /* ipv6 */
+ vty_out(vty, "ipv6 ");
+ if (is_any)
+ break;
+
+ yang_dnode_get_prefix(&p, dnode, "./ipv6-prefix");
+ is_exact = yang_dnode_get_bool(dnode, "./ipv6-exact-match");
+ break;
+ case YALT_MAC: /* mac */
+ vty_out(vty, "mac ");
+ if (is_any)
+ break;
+
+ yang_dnode_get_prefix(&p, dnode, "./mac");
+ break;
+ }
+
+ vty_out(vty, "access-list %s seq %s %s",
+ yang_dnode_get_string(dnode, "../name"),
+ yang_dnode_get_string(dnode, "./sequence"),
+ yang_dnode_get_string(dnode, "./action"));
+
+ /* Handle Cisco style access lists. */
+ if (cisco_style) {
+ if (cisco_extended)
+ vty_out(vty, " ip");
+
+ if (yang_dnode_exists(dnode, "./network")) {
+ yang_dnode_get_ipv4(&addr, dnode, "./network/address");
+ yang_dnode_get_ipv4(&mask, dnode, "./network/mask");
+ vty_out(vty, " %pI4 %pI4", &addr, &mask);
+ } else if (yang_dnode_exists(dnode, "./host")) {
+ if (cisco_extended)
+ vty_out(vty, " host");
+
+ vty_out(vty, " %s",
+ yang_dnode_get_string(dnode, "./host"));
+ } else if (yang_dnode_exists(dnode, "./source-any"))
+ vty_out(vty, " any");
+
+ /* Not extended, exit earlier. */
+ if (!cisco_extended) {
+ vty_out(vty, "\n");
+ return;
+ }
+
+ /* Handle destination address. */
+ if (yang_dnode_exists(dnode, "./destination-network")) {
+ yang_dnode_get_ipv4(&addr, dnode,
+ "./destination-network/address");
+ yang_dnode_get_ipv4(&mask, dnode,
+ "./destination-network/mask");
+ vty_out(vty, " %pI4 %pI4", &addr, &mask);
+ } else if (yang_dnode_exists(dnode, "./destination-host"))
+ vty_out(vty, " host %s",
+ yang_dnode_get_string(dnode,
+ "./destination-host"));
+ else if (yang_dnode_exists(dnode, "./destination-any"))
+ vty_out(vty, " any");
+
+ vty_out(vty, "\n");
+ return;
+ }
+
+ /* Zebra style access list. */
+ if (!is_any) {
+ /* If type is MAC don't show '/mask'. */
+ if (type == 2 /* mac */) {
+ prefix_mac2str(&p.u.prefix_eth, macstr, sizeof(macstr));
+ vty_out(vty, " %s", macstr);
+ } else
+ vty_out(vty, " %pFX", &p);
+ } else
+ vty_out(vty, " any");
+
+ if (is_exact)
+ vty_out(vty, " exact-match");
+
+ vty_out(vty, "\n");
+}
+
+void access_list_remark_show(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ int type = yang_dnode_get_enum(dnode, "../type");
+
+ switch (type) {
+ case YALT_IPV4:
+ break;
+ case YALT_IPV6:
+ vty_out(vty, "ipv6 ");
+ break;
+ case YALT_MAC:
+ vty_out(vty, "mac ");
+ break;
+ }
+
+ vty_out(vty, "access-list %s remark %s\n",
+ yang_dnode_get_string(dnode, "../name"),
+ yang_dnode_get_string(dnode, NULL));
+}
+
+/*
+ * Prefix lists.
+ */
+
+/**
+ * Remove main data structure prefix list if there are no more entries or
+ * remark. This fixes compatibility with old CLI and tests.
+ */
+static int plist_remove_if_empty(struct vty *vty, const char *iptype,
+ const char *name)
+{
+ char xpath[XPATH_MAXLEN];
+
+ snprintf(xpath, sizeof(xpath),
+ "/frr-filter:lib/prefix-list[type='%s'][name='%s']/remark",
+ iptype, name);
+ /* List is not empty if there is a remark, check that: */
+ if (yang_dnode_exists(vty->candidate_config->dnode, xpath))
+ return CMD_SUCCESS;
+
+ /* Check if we have any entries: */
+ snprintf(xpath, sizeof(xpath),
+ "/frr-filter:lib/prefix-list[type='%s'][name='%s']", iptype,
+ name);
+ /*
+ * NOTE: if the list is empty it will return the first sequence
+ * number: 5.
+ */
+ if (acl_get_seq(vty, xpath) != 5)
+ return CMD_SUCCESS;
+
+ /* Nobody is using this list, lets remove it. */
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+static int plist_remove(struct vty *vty, const char *iptype, const char *name,
+ const char *seq, const char *action,
+ union prefixconstptr prefix, int ge, int le)
+{
+ int64_t sseq;
+ struct plist_dup_args pda = {};
+ char xpath[XPATH_MAXLEN];
+ char xpath_entry[XPATH_MAXLEN + 32];
+ int rv;
+
+ /* If the user provided sequence number, then just go for it. */
+ if (seq != NULL) {
+ snprintf(
+ xpath, sizeof(xpath),
+ "/frr-filter:lib/prefix-list[type='%s'][name='%s']/entry[sequence='%s']",
+ iptype, name, seq);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ rv = nb_cli_apply_changes(vty, NULL);
+ if (rv == CMD_SUCCESS)
+ return plist_remove_if_empty(vty, iptype, name);
+
+ return rv;
+ }
+
+ /* Otherwise, to keep compatibility, we need to figure it out. */
+ pda.pda_type = iptype;
+ pda.pda_name = name;
+ pda.pda_action = action;
+ if (prefix.p) {
+ prefix_copy(&pda.prefix, prefix);
+ apply_mask(&pda.prefix);
+ pda.ge = ge;
+ pda.le = le;
+ } else {
+ pda.any = true;
+ }
+
+ if (plist_is_dup(vty->candidate_config->dnode, &pda))
+ sseq = pda.pda_seq;
+ else
+ return CMD_WARNING_CONFIG_FAILED;
+
+ snprintfrr(
+ xpath_entry, sizeof(xpath_entry),
+ "/frr-filter:lib/prefix-list[type='%s'][name='%s']/entry[sequence='%" PRId64 "']",
+ iptype, name, sseq);
+ nb_cli_enqueue_change(vty, xpath_entry, NB_OP_DESTROY, NULL);
+
+ rv = nb_cli_apply_changes(vty, NULL);
+ if (rv == CMD_SUCCESS)
+ return plist_remove_if_empty(vty, iptype, name);
+
+ return rv;
+}
+
+DEFPY_YANG(
+ ip_prefix_list, ip_prefix_list_cmd,
+ "ip prefix-list WORD$name [seq (1-4294967295)$seq] <deny|permit>$action <any|A.B.C.D/M$prefix [{ge (0-32)$ge|le (0-32)$le}]>",
+ IP_STR
+ PREFIX_LIST_STR
+ PREFIX_LIST_NAME_STR
+ ACCESS_LIST_SEQ_STR
+ ACCESS_LIST_ACTION_STR
+ "Any prefix match. Same as \"0.0.0.0/0 le 32\"\n"
+ "IP prefix <network>/<length>, e.g., 35.0.0.0/8\n"
+ "Minimum prefix length to be matched\n"
+ "Minimum prefix length\n"
+ "Maximum prefix length to be matched\n"
+ "Maximum prefix length\n")
+{
+ int64_t sseq;
+ struct plist_dup_args pda = {};
+ char xpath[XPATH_MAXLEN];
+ char xpath_entry[XPATH_MAXLEN + 128];
+
+ /*
+ * Backward compatibility: don't complain about duplicated values,
+ * just silently accept.
+ */
+ pda.pda_type = "ipv4";
+ pda.pda_name = name;
+ pda.pda_action = action;
+ if (prefix_str) {
+ prefix_copy(&pda.prefix, prefix);
+ pda.ge = ge;
+ pda.le = le;
+ } else {
+ pda.any = true;
+ }
+
+ if (plist_is_dup(vty->candidate_config->dnode, &pda))
+ return CMD_SUCCESS;
+
+ /*
+ * Create the prefix-list first, so we can generate sequence if
+ * none given (backward compatibility).
+ */
+ snprintf(xpath, sizeof(xpath),
+ "/frr-filter:lib/prefix-list[type='ipv4'][name='%s']", name);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
+ if (seq_str == NULL) {
+ /* Use XPath to find the next sequence number. */
+ sseq = acl_get_seq(vty, xpath);
+ snprintfrr(xpath_entry, sizeof(xpath_entry),
+ "%s/entry[sequence='%" PRId64 "']", xpath, sseq);
+ } else
+ snprintfrr(xpath_entry, sizeof(xpath_entry),
+ "%s/entry[sequence='%s']", xpath, seq_str);
+
+ nb_cli_enqueue_change(vty, xpath_entry, NB_OP_CREATE, NULL);
+
+ nb_cli_enqueue_change(vty, "./action", NB_OP_MODIFY, action);
+ if (prefix_str != NULL) {
+ nb_cli_enqueue_change(vty, "./ipv4-prefix", NB_OP_MODIFY,
+ prefix_str);
+
+ if (ge_str) {
+ nb_cli_enqueue_change(
+ vty, "./ipv4-prefix-length-greater-or-equal",
+ NB_OP_MODIFY, ge_str);
+ } else {
+ /*
+ * Remove old ge if not being modified
+ */
+ nb_cli_enqueue_change(
+ vty, "./ipv4-prefix-length-greater-or-equal",
+ NB_OP_DESTROY, NULL);
+ }
+
+ if (le_str) {
+ nb_cli_enqueue_change(
+ vty, "./ipv4-prefix-length-lesser-or-equal",
+ NB_OP_MODIFY, le_str);
+ } else {
+ /*
+ * Remove old le if not being modified
+ */
+ nb_cli_enqueue_change(
+ vty, "./ipv4-prefix-length-lesser-or-equal",
+ NB_OP_DESTROY, NULL);
+ }
+ nb_cli_enqueue_change(vty, "./any", NB_OP_DESTROY, NULL);
+ } else {
+ nb_cli_enqueue_change(vty, "./any", NB_OP_CREATE, NULL);
+ }
+
+ return nb_cli_apply_changes(vty, xpath_entry);
+}
+
+DEFPY_YANG(
+ no_ip_prefix_list, no_ip_prefix_list_cmd,
+ "no ip prefix-list WORD$name [seq (1-4294967295)$seq] <deny|permit>$action <any|A.B.C.D/M$prefix [{ge (0-32)|le (0-32)}]>",
+ NO_STR
+ IP_STR
+ PREFIX_LIST_STR
+ PREFIX_LIST_NAME_STR
+ ACCESS_LIST_SEQ_STR
+ ACCESS_LIST_ACTION_STR
+ "Any prefix match. Same as \"0.0.0.0/0 le 32\"\n"
+ "IP prefix <network>/<length>, e.g., 35.0.0.0/8\n"
+ "Minimum prefix length to be matched\n"
+ "Minimum prefix length\n"
+ "Maximum prefix length to be matched\n"
+ "Maximum prefix length\n")
+{
+ return plist_remove(vty, "ipv4", name, seq_str, action,
+ prefix_str ? prefix : NULL, ge, le);
+}
+
+DEFPY_YANG(
+ no_ip_prefix_list_seq, no_ip_prefix_list_seq_cmd,
+ "no ip prefix-list WORD$name seq (1-4294967295)$seq",
+ NO_STR
+ IP_STR
+ PREFIX_LIST_STR
+ PREFIX_LIST_NAME_STR
+ ACCESS_LIST_SEQ_STR)
+{
+ return plist_remove(vty, "ipv4", name, seq_str, NULL, NULL, 0, 0);
+}
+
+DEFPY_YANG(
+ no_ip_prefix_list_all, no_ip_prefix_list_all_cmd,
+ "no ip prefix-list WORD$name",
+ NO_STR
+ IP_STR
+ PREFIX_LIST_STR
+ PREFIX_LIST_NAME_STR)
+{
+ char xpath[XPATH_MAXLEN];
+
+ snprintf(xpath, sizeof(xpath),
+ "/frr-filter:lib/prefix-list[type='ipv4'][name='%s']", name);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ ip_prefix_list_remark, ip_prefix_list_remark_cmd,
+ "ip prefix-list WORD$name description LINE...",
+ IP_STR
+ PREFIX_LIST_STR
+ PREFIX_LIST_NAME_STR
+ ACCESS_LIST_REMARK_STR
+ ACCESS_LIST_REMARK_LINE_STR)
+{
+ int rv;
+ char *remark;
+ char xpath[XPATH_MAXLEN];
+
+ snprintf(xpath, sizeof(xpath),
+ "/frr-filter:lib/prefix-list[type='ipv4'][name='%s']", name);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
+
+ remark = argv_concat(argv, argc, 4);
+ nb_cli_enqueue_change(vty, "./remark", NB_OP_CREATE, remark);
+ rv = nb_cli_apply_changes(vty, xpath);
+ XFREE(MTYPE_TMP, remark);
+
+ return rv;
+}
+
+DEFPY_YANG(
+ no_ip_prefix_list_remark, no_ip_prefix_list_remark_cmd,
+ "no ip prefix-list WORD$name description",
+ NO_STR
+ IP_STR
+ PREFIX_LIST_STR
+ PREFIX_LIST_NAME_STR
+ ACCESS_LIST_REMARK_STR)
+{
+ char xpath[XPATH_MAXLEN];
+ int rv;
+
+ snprintf(xpath, sizeof(xpath),
+ "/frr-filter:lib/prefix-list[type='ipv4'][name='%s']/remark",
+ name);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ rv = nb_cli_apply_changes(vty, NULL);
+ if (rv == CMD_SUCCESS)
+ return plist_remove_if_empty(vty, "ipv4", name);
+
+ return rv;
+}
+
+ALIAS(
+ no_ip_prefix_list_remark, no_ip_prefix_list_remark_line_cmd,
+ "no ip prefix-list WORD$name description LINE...",
+ NO_STR
+ IP_STR
+ PREFIX_LIST_STR
+ PREFIX_LIST_NAME_STR
+ ACCESS_LIST_REMARK_STR
+ ACCESS_LIST_REMARK_LINE_STR)
+
+DEFPY_YANG(
+ ipv6_prefix_list, ipv6_prefix_list_cmd,
+ "ipv6 prefix-list WORD$name [seq (1-4294967295)] <deny|permit>$action <any|X:X::X:X/M$prefix [{ge (0-128)$ge|le (0-128)$le}]>",
+ IPV6_STR
+ PREFIX_LIST_STR
+ PREFIX_LIST_NAME_STR
+ ACCESS_LIST_SEQ_STR
+ ACCESS_LIST_ACTION_STR
+ "Any prefix match. Same as \"::0/0 le 128\"\n"
+ "IPv6 prefix <network>/<length>, e.g., 3ffe::/16\n"
+ "Maximum prefix length to be matched\n"
+ "Maximum prefix length\n"
+ "Minimum prefix length to be matched\n"
+ "Minimum prefix length\n")
+{
+ int64_t sseq;
+ struct plist_dup_args pda = {};
+ char xpath[XPATH_MAXLEN];
+ char xpath_entry[XPATH_MAXLEN + 128];
+
+ /*
+ * Backward compatibility: don't complain about duplicated values,
+ * just silently accept.
+ */
+ pda.pda_type = "ipv6";
+ pda.pda_name = name;
+ pda.pda_action = action;
+ if (prefix_str) {
+ prefix_copy(&pda.prefix, prefix);
+ pda.ge = ge;
+ pda.le = le;
+ } else {
+ pda.any = true;
+ }
+
+ if (plist_is_dup(vty->candidate_config->dnode, &pda))
+ return CMD_SUCCESS;
+
+ /*
+ * Create the prefix-list first, so we can generate sequence if
+ * none given (backward compatibility).
+ */
+ snprintf(xpath, sizeof(xpath),
+ "/frr-filter:lib/prefix-list[type='ipv6'][name='%s']", name);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
+ if (seq_str == NULL) {
+ /* Use XPath to find the next sequence number. */
+ sseq = acl_get_seq(vty, xpath);
+ snprintfrr(xpath_entry, sizeof(xpath_entry),
+ "%s/entry[sequence='%" PRId64 "']", xpath, sseq);
+ } else
+ snprintfrr(xpath_entry, sizeof(xpath_entry),
+ "%s/entry[sequence='%s']", xpath, seq_str);
+
+ nb_cli_enqueue_change(vty, xpath_entry, NB_OP_CREATE, NULL);
+
+ nb_cli_enqueue_change(vty, "./action", NB_OP_MODIFY, action);
+ if (prefix_str != NULL) {
+ nb_cli_enqueue_change(vty, "./ipv6-prefix", NB_OP_MODIFY,
+ prefix_str);
+
+ if (ge_str) {
+ nb_cli_enqueue_change(
+ vty, "./ipv6-prefix-length-greater-or-equal",
+ NB_OP_MODIFY, ge_str);
+ } else {
+ /*
+ * Remove old ge if not being modified
+ */
+ nb_cli_enqueue_change(
+ vty, "./ipv6-prefix-length-greater-or-equal",
+ NB_OP_DESTROY, NULL);
+ }
+
+ if (le_str) {
+ nb_cli_enqueue_change(
+ vty, "./ipv6-prefix-length-lesser-or-equal",
+ NB_OP_MODIFY, le_str);
+ } else {
+ /*
+ * Remove old le if not being modified
+ */
+ nb_cli_enqueue_change(
+ vty, "./ipv6-prefix-length-lesser-or-equal",
+ NB_OP_DESTROY, NULL);
+ }
+ nb_cli_enqueue_change(vty, "./any", NB_OP_DESTROY, NULL);
+ } else {
+ nb_cli_enqueue_change(vty, "./any", NB_OP_CREATE, NULL);
+ }
+
+ return nb_cli_apply_changes(vty, xpath_entry);
+}
+
+DEFPY_YANG(
+ no_ipv6_prefix_list, no_ipv6_prefix_list_cmd,
+ "no ipv6 prefix-list WORD$name [seq (1-4294967295)$seq] <deny|permit>$action <any|X:X::X:X/M$prefix [{ge (0-128)$ge|le (0-128)$le}]>",
+ NO_STR
+ IPV6_STR
+ PREFIX_LIST_STR
+ PREFIX_LIST_NAME_STR
+ ACCESS_LIST_SEQ_STR
+ ACCESS_LIST_ACTION_STR
+ "Any prefix match. Same as \"::0/0 le 128\"\n"
+ "IPv6 prefix <network>/<length>, e.g., 3ffe::/16\n"
+ "Maximum prefix length to be matched\n"
+ "Maximum prefix length\n"
+ "Minimum prefix length to be matched\n"
+ "Minimum prefix length\n")
+{
+ return plist_remove(vty, "ipv6", name, seq_str, action,
+ prefix_str ? prefix : NULL, ge, le);
+}
+
+DEFPY_YANG(
+ no_ipv6_prefix_list_seq, no_ipv6_prefix_list_seq_cmd,
+ "no ipv6 prefix-list WORD$name seq (1-4294967295)$seq",
+ NO_STR
+ IPV6_STR
+ PREFIX_LIST_STR
+ PREFIX_LIST_NAME_STR
+ ACCESS_LIST_SEQ_STR)
+{
+ return plist_remove(vty, "ipv6", name, seq_str, NULL, NULL, 0, 0);
+}
+
+DEFPY_YANG(
+ no_ipv6_prefix_list_all, no_ipv6_prefix_list_all_cmd,
+ "no ipv6 prefix-list WORD$name",
+ NO_STR
+ IPV6_STR
+ PREFIX_LIST_STR
+ PREFIX_LIST_NAME_STR)
+{
+ char xpath[XPATH_MAXLEN];
+
+ snprintf(xpath, sizeof(xpath),
+ "/frr-filter:lib/prefix-list[type='ipv6'][name='%s']", name);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ ipv6_prefix_list_remark, ipv6_prefix_list_remark_cmd,
+ "ipv6 prefix-list WORD$name description LINE...",
+ IPV6_STR
+ PREFIX_LIST_STR
+ PREFIX_LIST_NAME_STR
+ ACCESS_LIST_REMARK_STR
+ ACCESS_LIST_REMARK_LINE_STR)
+{
+ int rv;
+ char *remark;
+ char xpath[XPATH_MAXLEN];
+
+ snprintf(xpath, sizeof(xpath),
+ "/frr-filter:lib/prefix-list[type='ipv6'][name='%s']", name);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
+
+ remark = argv_concat(argv, argc, 4);
+ nb_cli_enqueue_change(vty, "./remark", NB_OP_CREATE, remark);
+ rv = nb_cli_apply_changes(vty, xpath);
+ XFREE(MTYPE_TMP, remark);
+
+ return rv;
+}
+
+DEFPY_YANG(
+ no_ipv6_prefix_list_remark, no_ipv6_prefix_list_remark_cmd,
+ "no ipv6 prefix-list WORD$name description",
+ NO_STR
+ IPV6_STR
+ PREFIX_LIST_STR
+ PREFIX_LIST_NAME_STR
+ ACCESS_LIST_REMARK_STR)
+{
+ char xpath[XPATH_MAXLEN];
+ int rv;
+
+ snprintf(xpath, sizeof(xpath),
+ "/frr-filter:lib/prefix-list[type='ipv6'][name='%s']/remark",
+ name);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ rv = nb_cli_apply_changes(vty, NULL);
+ if (rv == CMD_SUCCESS)
+ return plist_remove_if_empty(vty, "ipv6", name);
+
+ return rv;
+}
+
+ALIAS(
+ no_ipv6_prefix_list_remark, no_ipv6_prefix_list_remark_line_cmd,
+ "no ipv6 prefix-list WORD$name description LINE...",
+ NO_STR
+ IPV6_STR
+ PREFIX_LIST_STR
+ PREFIX_LIST_NAME_STR
+ ACCESS_LIST_REMARK_STR
+ ACCESS_LIST_REMARK_LINE_STR)
+
+int prefix_list_cmp(const struct lyd_node *dnode1,
+ const struct lyd_node *dnode2)
+{
+ uint32_t seq1 = yang_dnode_get_uint32(dnode1, "./sequence");
+ uint32_t seq2 = yang_dnode_get_uint32(dnode2, "./sequence");
+
+ return seq1 - seq2;
+}
+
+void prefix_list_show(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ int type = yang_dnode_get_enum(dnode, "../type");
+ const char *ge_str = NULL, *le_str = NULL;
+ bool is_any;
+ struct prefix p;
+
+ is_any = yang_dnode_exists(dnode, "./any");
+ switch (type) {
+ case YPLT_IPV4:
+ if (!is_any)
+ yang_dnode_get_prefix(&p, dnode, "./ipv4-prefix");
+ if (yang_dnode_exists(dnode,
+ "./ipv4-prefix-length-greater-or-equal"))
+ ge_str = yang_dnode_get_string(
+ dnode, "./ipv4-prefix-length-greater-or-equal");
+ if (yang_dnode_exists(dnode,
+ "./ipv4-prefix-length-lesser-or-equal"))
+ le_str = yang_dnode_get_string(
+ dnode, "./ipv4-prefix-length-lesser-or-equal");
+
+ vty_out(vty, "ip ");
+ break;
+ case YPLT_IPV6:
+ if (!is_any)
+ yang_dnode_get_prefix(&p, dnode, "ipv6-prefix");
+ if (yang_dnode_exists(dnode,
+ "./ipv6-prefix-length-greater-or-equal"))
+ ge_str = yang_dnode_get_string(
+ dnode, "./ipv6-prefix-length-greater-or-equal");
+ if (yang_dnode_exists(dnode,
+ "./ipv6-prefix-length-lesser-or-equal"))
+ le_str = yang_dnode_get_string(
+ dnode, "./ipv6-prefix-length-lesser-or-equal");
+
+ vty_out(vty, "ipv6 ");
+ break;
+ }
+
+ vty_out(vty, "prefix-list %s seq %s %s",
+ yang_dnode_get_string(dnode, "../name"),
+ yang_dnode_get_string(dnode, "./sequence"),
+ yang_dnode_get_string(dnode, "./action"));
+
+ if (is_any) {
+ vty_out(vty, " any\n");
+ return;
+ }
+
+ vty_out(vty, " %pFX", &p);
+ if (ge_str)
+ vty_out(vty, " ge %s", ge_str);
+ if (le_str)
+ vty_out(vty, " le %s", le_str);
+
+ vty_out(vty, "\n");
+}
+
+void prefix_list_remark_show(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ int type = yang_dnode_get_enum(dnode, "../type");
+
+ switch (type) {
+ case YPLT_IPV4:
+ vty_out(vty, "ip ");
+ break;
+ case YPLT_IPV6:
+ vty_out(vty, "ipv6 ");
+ break;
+ }
+
+ vty_out(vty, "prefix-list %s description %s\n",
+ yang_dnode_get_string(dnode, "../name"),
+ yang_dnode_get_string(dnode, NULL));
+}
+
+void filter_cli_init(void)
+{
+ /* access-list cisco-style (legacy). */
+ install_element(CONFIG_NODE, &access_list_std_cmd);
+ install_element(CONFIG_NODE, &no_access_list_std_cmd);
+ install_element(CONFIG_NODE, &access_list_ext_cmd);
+ install_element(CONFIG_NODE, &no_access_list_ext_cmd);
+
+ /* access-list zebra-style. */
+ install_element(CONFIG_NODE, &access_list_cmd);
+ install_element(CONFIG_NODE, &no_access_list_cmd);
+ install_element(CONFIG_NODE, &no_access_list_all_cmd);
+ install_element(CONFIG_NODE, &access_list_remark_cmd);
+ install_element(CONFIG_NODE, &no_access_list_remark_cmd);
+ install_element(CONFIG_NODE, &no_access_list_remark_line_cmd);
+
+ install_element(CONFIG_NODE, &ipv6_access_list_cmd);
+ install_element(CONFIG_NODE, &no_ipv6_access_list_cmd);
+ install_element(CONFIG_NODE, &no_ipv6_access_list_all_cmd);
+ install_element(CONFIG_NODE, &ipv6_access_list_remark_cmd);
+ install_element(CONFIG_NODE, &no_ipv6_access_list_remark_cmd);
+ install_element(CONFIG_NODE, &no_ipv6_access_list_remark_line_cmd);
+
+ install_element(CONFIG_NODE, &mac_access_list_cmd);
+ install_element(CONFIG_NODE, &no_mac_access_list_cmd);
+ install_element(CONFIG_NODE, &no_mac_access_list_all_cmd);
+ install_element(CONFIG_NODE, &mac_access_list_remark_cmd);
+ install_element(CONFIG_NODE, &no_mac_access_list_remark_cmd);
+ install_element(CONFIG_NODE, &no_mac_access_list_remark_line_cmd);
+
+ /* prefix lists. */
+ install_element(CONFIG_NODE, &ip_prefix_list_cmd);
+ install_element(CONFIG_NODE, &no_ip_prefix_list_cmd);
+ install_element(CONFIG_NODE, &no_ip_prefix_list_seq_cmd);
+ install_element(CONFIG_NODE, &no_ip_prefix_list_all_cmd);
+ install_element(CONFIG_NODE, &ip_prefix_list_remark_cmd);
+ install_element(CONFIG_NODE, &no_ip_prefix_list_remark_cmd);
+ install_element(CONFIG_NODE, &no_ip_prefix_list_remark_line_cmd);
+
+ install_element(CONFIG_NODE, &ipv6_prefix_list_cmd);
+ install_element(CONFIG_NODE, &no_ipv6_prefix_list_cmd);
+ install_element(CONFIG_NODE, &no_ipv6_prefix_list_seq_cmd);
+ install_element(CONFIG_NODE, &no_ipv6_prefix_list_all_cmd);
+ install_element(CONFIG_NODE, &ipv6_prefix_list_remark_cmd);
+ install_element(CONFIG_NODE, &no_ipv6_prefix_list_remark_cmd);
+ install_element(CONFIG_NODE, &no_ipv6_prefix_list_remark_line_cmd);
+}
diff --git a/lib/filter_nb.c b/lib/filter_nb.c
new file mode 100644
index 0000000..215a33d
--- /dev/null
+++ b/lib/filter_nb.c
@@ -0,0 +1,1865 @@
+/*
+ * FRR filter northbound implementation.
+ *
+ * Copyright (C) 2019 Network Device Education Foundation, Inc. ("NetDEF")
+ * Rafael Zalamena
+ *
+ * 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; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301 USA.
+ */
+
+#include "zebra.h"
+
+#include "lib/northbound.h"
+#include "lib/prefix.h"
+#include "lib/printfrr.h"
+
+#include "lib/filter.h"
+#include "lib/plist.h"
+#include "lib/plist_int.h"
+#include "lib/routemap.h"
+
+/* Helper function. */
+static void acl_notify_route_map(struct access_list *acl, int route_map_event)
+{
+ switch (route_map_event) {
+ case RMAP_EVENT_FILTER_ADDED:
+ if (acl->master->add_hook)
+ (*acl->master->add_hook)(acl);
+ break;
+ case RMAP_EVENT_FILTER_DELETED:
+ if (acl->master->delete_hook)
+ (*acl->master->delete_hook)(acl);
+ break;
+ }
+
+ route_map_notify_dependencies(acl->name, route_map_event);
+}
+
+static enum nb_error prefix_list_length_validate(struct nb_cb_modify_args *args)
+{
+ int type = yang_dnode_get_enum(args->dnode, "../../type");
+ const char *xpath_le = NULL, *xpath_ge = NULL;
+ struct prefix p;
+ uint8_t le, ge;
+
+ if (type == YPLT_IPV4) {
+ yang_dnode_get_prefix(&p, args->dnode, "../ipv4-prefix");
+ xpath_le = "../ipv4-prefix-length-lesser-or-equal";
+ xpath_ge = "../ipv4-prefix-length-greater-or-equal";
+ } else {
+ yang_dnode_get_prefix(&p, args->dnode, "../ipv6-prefix");
+ xpath_le = "../ipv6-prefix-length-lesser-or-equal";
+ xpath_ge = "../ipv6-prefix-length-greater-or-equal";
+ }
+
+ /*
+ * Check rule:
+ * prefix length <= le.
+ */
+ if (yang_dnode_exists(args->dnode, xpath_le)) {
+ le = yang_dnode_get_uint8(args->dnode, xpath_le);
+ if (p.prefixlen > le)
+ goto log_and_fail;
+ }
+
+ /*
+ * Check rule:
+ * prefix length <= ge.
+ */
+ if (yang_dnode_exists(args->dnode, xpath_ge)) {
+ ge = yang_dnode_get_uint8(args->dnode, xpath_ge);
+ if (p.prefixlen > ge)
+ goto log_and_fail;
+ }
+
+ /*
+ * Check rule:
+ * ge <= le.
+ */
+ if (yang_dnode_exists(args->dnode, xpath_le)
+ && yang_dnode_exists(args->dnode, xpath_ge)) {
+ le = yang_dnode_get_uint8(args->dnode, xpath_le);
+ ge = yang_dnode_get_uint8(args->dnode, xpath_ge);
+ if (ge > le)
+ goto log_and_fail;
+ }
+
+ return NB_OK;
+
+log_and_fail:
+ snprintfrr(
+ args->errmsg, args->errmsg_len,
+ "Invalid prefix range for %pFX: Make sure that mask length <= ge <= le",
+ &p);
+ return NB_ERR_VALIDATION;
+}
+
+/**
+ * Sets prefix list entry to blank value.
+ *
+ * \param[out] ple prefix list entry to modify.
+ */
+static void prefix_list_entry_set_empty(struct prefix_list_entry *ple)
+{
+ ple->any = false;
+ memset(&ple->prefix, 0, sizeof(ple->prefix));
+ ple->ge = 0;
+ ple->le = 0;
+}
+
+static int
+prefix_list_nb_validate_v4_af_type(const struct lyd_node *plist_dnode,
+ char *errmsg, size_t errmsg_len)
+{
+ int af_type;
+
+ af_type = yang_dnode_get_enum(plist_dnode, "./type");
+ if (af_type != YPLT_IPV4) {
+ snprintf(errmsg, errmsg_len,
+ "prefix-list type %u is mismatched.", af_type);
+ return NB_ERR_VALIDATION;
+ }
+
+ return NB_OK;
+}
+
+static int
+prefix_list_nb_validate_v6_af_type(const struct lyd_node *plist_dnode,
+ char *errmsg, size_t errmsg_len)
+{
+ int af_type;
+
+ af_type = yang_dnode_get_enum(plist_dnode, "./type");
+ if (af_type != YPLT_IPV6) {
+ snprintf(errmsg, errmsg_len,
+ "prefix-list type %u is mismatched.", af_type);
+ return NB_ERR_VALIDATION;
+ }
+
+ return NB_OK;
+}
+
+static int lib_prefix_list_entry_prefix_length_greater_or_equal_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct prefix_list_entry *ple;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ ple = nb_running_get_entry(args->dnode, NULL, true);
+
+ /* Start prefix entry update procedure. */
+ prefix_list_entry_update_start(ple);
+
+ ple->ge = yang_dnode_get_uint8(args->dnode, NULL);
+
+ /* Finish prefix entry update procedure. */
+ prefix_list_entry_update_finish(ple);
+
+ return NB_OK;
+}
+
+static int lib_prefix_list_entry_prefix_length_lesser_or_equal_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct prefix_list_entry *ple;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ ple = nb_running_get_entry(args->dnode, NULL, true);
+
+ /* Start prefix entry update procedure. */
+ prefix_list_entry_update_start(ple);
+
+ ple->le = yang_dnode_get_uint8(args->dnode, NULL);
+
+ /* Finish prefix entry update procedure. */
+ prefix_list_entry_update_finish(ple);
+
+ return NB_OK;
+}
+
+static int lib_prefix_list_entry_prefix_length_greater_or_equal_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ struct prefix_list_entry *ple;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ ple = nb_running_get_entry(args->dnode, NULL, true);
+
+ /* Start prefix entry update procedure. */
+ prefix_list_entry_update_start(ple);
+
+ ple->ge = 0;
+
+ /* Finish prefix entry update procedure. */
+ prefix_list_entry_update_finish(ple);
+
+ return NB_OK;
+}
+
+static int lib_prefix_list_entry_prefix_length_lesser_or_equal_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ struct prefix_list_entry *ple;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ ple = nb_running_get_entry(args->dnode, NULL, true);
+
+ /* Start prefix entry update procedure. */
+ prefix_list_entry_update_start(ple);
+
+ ple->le = 0;
+
+ /* Finish prefix entry update procedure. */
+ prefix_list_entry_update_finish(ple);
+
+ return NB_OK;
+}
+
+/**
+ * Unsets the cisco style rule for addresses so it becomes disabled (the
+ * equivalent of setting: `0.0.0.0/32`).
+ *
+ * \param addr address part.
+ * \param mask mask part.
+ */
+static void cisco_unset_addr_mask(struct in_addr *addr, struct in_addr *mask)
+{
+ addr->s_addr = INADDR_ANY;
+ mask->s_addr = CISCO_BIN_HOST_WILDCARD_MASK;
+}
+
+static int _acl_is_dup(const struct lyd_node *dnode, void *arg)
+{
+ struct acl_dup_args *ada = arg;
+ int idx;
+
+ /* This entry is the caller, so skip it. */
+ if (ada->ada_entry_dnode
+ && ada->ada_entry_dnode == dnode)
+ return YANG_ITER_CONTINUE;
+
+ if (strcmp(yang_dnode_get_string(dnode, "action"), ada->ada_action))
+ return YANG_ITER_CONTINUE;
+
+ /* Check if all values match. */
+ for (idx = 0; idx < ADA_MAX_VALUES; idx++) {
+ /* No more values. */
+ if (ada->ada_xpath[idx] == NULL)
+ break;
+
+ /* Not same type, just skip it. */
+ if (!yang_dnode_exists(dnode, ada->ada_xpath[idx]))
+ return YANG_ITER_CONTINUE;
+
+ /* Check if different value. */
+ if (strcmp(yang_dnode_get_string(dnode, ada->ada_xpath[idx]),
+ ada->ada_value[idx]))
+ return YANG_ITER_CONTINUE;
+ }
+
+ ada->ada_found = true;
+ ada->ada_seq = yang_dnode_get_uint32(dnode, "sequence");
+
+ return YANG_ITER_STOP;
+}
+
+bool acl_is_dup(const struct lyd_node *dnode, struct acl_dup_args *ada)
+{
+ ada->ada_found = false;
+
+ yang_dnode_iterate(
+ _acl_is_dup, ada, dnode,
+ "/frr-filter:lib/access-list[type='%s'][name='%s']/entry",
+ ada->ada_type, ada->ada_name);
+
+ return ada->ada_found;
+}
+
+static bool acl_cisco_is_dup(const struct lyd_node *dnode)
+{
+ const struct lyd_node *entry_dnode =
+ yang_dnode_get_parent(dnode, "entry");
+ struct acl_dup_args ada = {};
+ int idx = 0, arg_idx = 0;
+ static const char *cisco_entries[] = {
+ "./host",
+ "./network/address",
+ "./network/mask",
+ "./source-any",
+ "./destination-host",
+ "./destination-network/address",
+ "./destination-network/mask",
+ "./destination-any",
+ NULL
+ };
+
+ /* Initialize. */
+ ada.ada_type = "ipv4";
+ ada.ada_name = yang_dnode_get_string(entry_dnode, "../name");
+ ada.ada_action = yang_dnode_get_string(entry_dnode, "action");
+ ada.ada_entry_dnode = entry_dnode;
+
+ /* Load all values/XPaths. */
+ while (cisco_entries[idx] != NULL) {
+ if (!yang_dnode_exists(entry_dnode, cisco_entries[idx])) {
+ idx++;
+ continue;
+ }
+
+ ada.ada_xpath[arg_idx] = cisco_entries[idx];
+ ada.ada_value[arg_idx] =
+ yang_dnode_get_string(entry_dnode, cisco_entries[idx]);
+ arg_idx++;
+ idx++;
+ }
+
+ return acl_is_dup(entry_dnode, &ada);
+}
+
+static bool acl_zebra_is_dup(const struct lyd_node *dnode,
+ enum yang_access_list_type type)
+{
+ const struct lyd_node *entry_dnode =
+ yang_dnode_get_parent(dnode, "entry");
+ struct acl_dup_args ada = {};
+ int idx = 0, arg_idx = 0;
+ static const char *zebra_entries[] = {
+ "./ipv4-prefix",
+ "./ipv4-exact-match",
+ "./ipv6-prefix",
+ "./ipv6-exact-match",
+ "./mac",
+ "./any",
+ NULL
+ };
+
+ /* Initialize. */
+ switch (type) {
+ case YALT_IPV4:
+ ada.ada_type = "ipv4";
+ break;
+ case YALT_IPV6:
+ ada.ada_type = "ipv6";
+ break;
+ case YALT_MAC:
+ ada.ada_type = "mac";
+ break;
+ }
+ ada.ada_name = yang_dnode_get_string(entry_dnode, "../name");
+ ada.ada_action = yang_dnode_get_string(entry_dnode, "action");
+ ada.ada_entry_dnode = entry_dnode;
+
+ /* Load all values/XPaths. */
+ while (zebra_entries[idx] != NULL) {
+ if (!yang_dnode_exists(entry_dnode, zebra_entries[idx])) {
+ idx++;
+ continue;
+ }
+
+ ada.ada_xpath[arg_idx] = zebra_entries[idx];
+ ada.ada_value[arg_idx] =
+ yang_dnode_get_string(entry_dnode, zebra_entries[idx]);
+ arg_idx++;
+ idx++;
+ }
+
+ return acl_is_dup(entry_dnode, &ada);
+}
+
+static void plist_dnode_to_prefix(const struct lyd_node *dnode, bool *any,
+ struct prefix *p, int *ge, int *le)
+{
+ *any = false;
+ *ge = 0;
+ *le = 0;
+
+ if (yang_dnode_exists(dnode, "./any")) {
+ *any = true;
+ return;
+ }
+
+ switch (yang_dnode_get_enum(dnode, "../type")) {
+ case YPLT_IPV4:
+ yang_dnode_get_prefix(p, dnode, "./ipv4-prefix");
+ if (yang_dnode_exists(dnode,
+ "./ipv4-prefix-length-greater-or-equal"))
+ *ge = yang_dnode_get_uint8(
+ dnode, "./ipv4-prefix-length-greater-or-equal");
+ if (yang_dnode_exists(dnode,
+ "./ipv4-prefix-length-lesser-or-equal"))
+ *le = yang_dnode_get_uint8(
+ dnode, "./ipv4-prefix-length-lesser-or-equal");
+ break;
+ case YPLT_IPV6:
+ yang_dnode_get_prefix(p, dnode, "./ipv6-prefix");
+ if (yang_dnode_exists(dnode,
+ "./ipv6-prefix-length-greater-or-equal"))
+ *ge = yang_dnode_get_uint8(
+ dnode, "./ipv6-prefix-length-greater-or-equal");
+ if (yang_dnode_exists(dnode,
+ "./ipv6-prefix-length-lesser-or-equal"))
+ *le = yang_dnode_get_uint8(
+ dnode, "./ipv6-prefix-length-lesser-or-equal");
+ break;
+ }
+}
+
+static int _plist_is_dup(const struct lyd_node *dnode, void *arg)
+{
+ struct plist_dup_args *pda = arg;
+ struct prefix p = {};
+ int ge, le;
+ bool any;
+
+ /* This entry is the caller, so skip it. */
+ if (pda->pda_entry_dnode
+ && pda->pda_entry_dnode == dnode)
+ return YANG_ITER_CONTINUE;
+
+ if (strcmp(yang_dnode_get_string(dnode, "action"), pda->pda_action))
+ return YANG_ITER_CONTINUE;
+
+ plist_dnode_to_prefix(dnode, &any, &p, &ge, &le);
+
+ if (pda->any) {
+ if (!any)
+ return YANG_ITER_CONTINUE;
+ } else {
+ if (!prefix_same(&pda->prefix, &p) || pda->ge != ge
+ || pda->le != le)
+ return YANG_ITER_CONTINUE;
+ }
+
+ pda->pda_found = true;
+ pda->pda_seq = yang_dnode_get_uint32(dnode, "sequence");
+
+ return YANG_ITER_STOP;
+}
+
+bool plist_is_dup(const struct lyd_node *dnode, struct plist_dup_args *pda)
+{
+ pda->pda_found = false;
+
+ yang_dnode_iterate(
+ _plist_is_dup, pda, dnode,
+ "/frr-filter:lib/prefix-list[type='%s'][name='%s']/entry",
+ pda->pda_type, pda->pda_name);
+
+ return pda->pda_found;
+}
+
+static bool plist_is_dup_nb(const struct lyd_node *dnode)
+{
+ const struct lyd_node *entry_dnode =
+ yang_dnode_get_parent(dnode, "entry");
+ struct plist_dup_args pda = {};
+
+ /* Initialize. */
+ pda.pda_type = yang_dnode_get_string(entry_dnode, "../type");
+ pda.pda_name = yang_dnode_get_string(entry_dnode, "../name");
+ pda.pda_action = yang_dnode_get_string(entry_dnode, "action");
+ pda.pda_entry_dnode = entry_dnode;
+
+ plist_dnode_to_prefix(entry_dnode, &pda.any, &pda.prefix, &pda.ge,
+ &pda.le);
+
+ return plist_is_dup(entry_dnode, &pda);
+}
+
+/*
+ * XPath: /frr-filter:lib/access-list
+ */
+static int lib_access_list_create(struct nb_cb_create_args *args)
+{
+ struct access_list *acl = NULL;
+ const char *acl_name;
+ int type;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ type = yang_dnode_get_enum(args->dnode, "./type");
+ acl_name = yang_dnode_get_string(args->dnode, "./name");
+
+ switch (type) {
+ case YALT_IPV4:
+ acl = access_list_get(AFI_IP, acl_name);
+ break;
+ case YALT_IPV6:
+ acl = access_list_get(AFI_IP6, acl_name);
+ break;
+ case YALT_MAC:
+ acl = access_list_get(AFI_L2VPN, acl_name);
+ break;
+ }
+
+ nb_running_set_entry(args->dnode, acl);
+
+ return NB_OK;
+}
+
+static int lib_access_list_destroy(struct nb_cb_destroy_args *args)
+{
+ struct access_list *acl;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ acl = nb_running_unset_entry(args->dnode);
+ access_list_delete(acl);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-filter:lib/access-list/remark
+ */
+static int lib_access_list_remark_modify(struct nb_cb_modify_args *args)
+{
+ struct access_list *acl;
+ const char *remark;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ acl = nb_running_get_entry(args->dnode, NULL, true);
+ if (acl->remark)
+ XFREE(MTYPE_TMP, acl->remark);
+
+ remark = yang_dnode_get_string(args->dnode, NULL);
+ acl->remark = XSTRDUP(MTYPE_TMP, remark);
+
+ return NB_OK;
+}
+
+static int
+lib_access_list_remark_destroy(struct nb_cb_destroy_args *args)
+{
+ struct access_list *acl;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ acl = nb_running_get_entry(args->dnode, NULL, true);
+ if (acl->remark)
+ XFREE(MTYPE_TMP, acl->remark);
+
+ return NB_OK;
+}
+
+
+/*
+ * XPath: /frr-filter:lib/access-list/entry
+ */
+static int lib_access_list_entry_create(struct nb_cb_create_args *args)
+{
+ struct access_list *acl;
+ struct filter *f;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ f = filter_new();
+ f->seq = yang_dnode_get_uint32(args->dnode, "./sequence");
+
+ acl = nb_running_get_entry(args->dnode, NULL, true);
+ f->acl = acl;
+ access_list_filter_add(acl, f);
+ nb_running_set_entry(args->dnode, f);
+
+ return NB_OK;
+}
+
+static int lib_access_list_entry_destroy(struct nb_cb_destroy_args *args)
+{
+ struct access_list *acl;
+ struct filter *f;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ f = nb_running_unset_entry(args->dnode);
+ acl = f->acl;
+ access_list_filter_delete(acl, f);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-filter:lib/access-list/entry/action
+ */
+static int
+lib_access_list_entry_action_modify(struct nb_cb_modify_args *args)
+{
+ const char *filter_type;
+ struct filter *f;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ f = nb_running_get_entry(args->dnode, NULL, true);
+ filter_type = yang_dnode_get_string(args->dnode, NULL);
+ if (strcmp(filter_type, "permit") == 0)
+ f->type = FILTER_PERMIT;
+ else
+ f->type = FILTER_DENY;
+
+ acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_ADDED);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-filter:lib/access-list/entry/ipv4-prefix
+ */
+static int
+lib_access_list_entry_ipv4_prefix_modify(struct nb_cb_modify_args *args)
+{
+ struct filter_zebra *fz;
+ struct filter *f;
+
+ /* Don't allow duplicated values. */
+ if (args->event == NB_EV_VALIDATE) {
+ if (acl_zebra_is_dup(
+ args->dnode,
+ yang_dnode_get_enum(args->dnode, "../../type"))) {
+ snprintfrr(args->errmsg, args->errmsg_len,
+ "duplicated access list value: %s",
+ yang_dnode_get_string(args->dnode, NULL));
+ return NB_ERR_VALIDATION;
+ }
+ return NB_OK;
+ }
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ f = nb_running_get_entry(args->dnode, NULL, true);
+ f->cisco = 0;
+ fz = &f->u.zfilter;
+ yang_dnode_get_prefix(&fz->prefix, args->dnode, NULL);
+
+ acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_ADDED);
+
+ return NB_OK;
+}
+
+static int
+lib_access_list_entry_ipv4_prefix_destroy(struct nb_cb_destroy_args *args)
+{
+ struct filter_zebra *fz;
+ struct filter *f;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ f = nb_running_get_entry(args->dnode, NULL, true);
+ fz = &f->u.zfilter;
+ memset(&fz->prefix, 0, sizeof(fz->prefix));
+
+ acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_DELETED);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-filter:lib/access-list/entry/ipv4-exact-match
+ */
+static int
+lib_access_list_entry_ipv4_exact_match_modify(struct nb_cb_modify_args *args)
+{
+ struct filter_zebra *fz;
+ struct filter *f;
+
+ /* Don't allow duplicated values. */
+ if (args->event == NB_EV_VALIDATE) {
+ if (acl_zebra_is_dup(
+ args->dnode,
+ yang_dnode_get_enum(args->dnode, "../../type"))) {
+ snprintfrr(args->errmsg, args->errmsg_len,
+ "duplicated access list value: %s",
+ yang_dnode_get_string(args->dnode, NULL));
+ return NB_ERR_VALIDATION;
+ }
+ return NB_OK;
+ }
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ f = nb_running_get_entry(args->dnode, NULL, true);
+ fz = &f->u.zfilter;
+ fz->exact = yang_dnode_get_bool(args->dnode, NULL);
+
+ acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_ADDED);
+
+ return NB_OK;
+}
+
+static int
+lib_access_list_entry_ipv4_exact_match_destroy(struct nb_cb_destroy_args *args)
+{
+ struct filter_zebra *fz;
+ struct filter *f;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ f = nb_running_get_entry(args->dnode, NULL, true);
+ fz = &f->u.zfilter;
+ fz->exact = 0;
+
+ acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_DELETED);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-filter:lib/access-list/entry/host
+ */
+static int
+lib_access_list_entry_host_modify(struct nb_cb_modify_args *args)
+{
+ struct filter_cisco *fc;
+ struct filter *f;
+
+ /* Don't allow duplicated values. */
+ if (args->event == NB_EV_VALIDATE) {
+ if (acl_cisco_is_dup(args->dnode)) {
+ snprintfrr(args->errmsg, args->errmsg_len,
+ "duplicated access list value: %s",
+ yang_dnode_get_string(args->dnode, NULL));
+ return NB_ERR_VALIDATION;
+ }
+ return NB_OK;
+ }
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ f = nb_running_get_entry(args->dnode, NULL, true);
+ f->cisco = 1;
+ fc = &f->u.cfilter;
+ yang_dnode_get_ipv4(&fc->addr, args->dnode, NULL);
+ fc->addr_mask.s_addr = CISCO_BIN_HOST_WILDCARD_MASK;
+
+ acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_ADDED);
+
+ return NB_OK;
+}
+
+static int
+lib_access_list_entry_host_destroy(struct nb_cb_destroy_args *args)
+{
+ struct filter_cisco *fc;
+ struct filter *f;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ f = nb_running_get_entry(args->dnode, NULL, true);
+ fc = &f->u.cfilter;
+ cisco_unset_addr_mask(&fc->addr, &fc->addr_mask);
+
+ acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_DELETED);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-filter:lib/access-list/entry/network/address
+ */
+static int
+lib_access_list_entry_network_address_modify(struct nb_cb_modify_args *args)
+{
+ struct filter_cisco *fc;
+ struct filter *f;
+
+ /* Don't allow duplicated values. */
+ if (args->event == NB_EV_VALIDATE) {
+ if (acl_cisco_is_dup(args->dnode)) {
+ snprintfrr(args->errmsg, args->errmsg_len,
+ "duplicated access list value: %s",
+ yang_dnode_get_string(args->dnode, NULL));
+ return NB_ERR_VALIDATION;
+ }
+ return NB_OK;
+ }
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ f = nb_running_get_entry(args->dnode, NULL, true);
+ f->cisco = 1;
+ fc = &f->u.cfilter;
+ yang_dnode_get_ipv4(&fc->addr, args->dnode, NULL);
+
+ acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_ADDED);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-filter:lib/access-list/entry/network/mask
+ */
+static int
+lib_access_list_entry_network_mask_modify(struct nb_cb_modify_args *args)
+{
+ struct filter_cisco *fc;
+ struct filter *f;
+
+ /* Don't allow duplicated values. */
+ if (args->event == NB_EV_VALIDATE) {
+ if (acl_cisco_is_dup(args->dnode)) {
+ snprintfrr(args->errmsg, args->errmsg_len,
+ "duplicated access list value: %s",
+ yang_dnode_get_string(args->dnode, NULL));
+ return NB_ERR_VALIDATION;
+ }
+ return NB_OK;
+ }
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ f = nb_running_get_entry(args->dnode, NULL, true);
+ f->cisco = 1;
+ fc = &f->u.cfilter;
+ yang_dnode_get_ipv4(&fc->addr_mask, args->dnode, NULL);
+
+ acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_ADDED);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-filter:lib/access-list/entry/source-any
+ */
+static int
+lib_access_list_entry_source_any_create(struct nb_cb_create_args *args)
+{
+ struct filter_cisco *fc;
+ struct filter *f;
+
+ /* Don't allow duplicated values. */
+ if (args->event == NB_EV_VALIDATE) {
+ if (acl_cisco_is_dup(args->dnode)) {
+ snprintfrr(args->errmsg, args->errmsg_len,
+ "duplicated access list value: %s",
+ yang_dnode_get_string(args->dnode, NULL));
+ return NB_ERR_VALIDATION;
+ }
+ return NB_OK;
+ }
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ f = nb_running_get_entry(args->dnode, NULL, true);
+ f->cisco = 1;
+ fc = &f->u.cfilter;
+ fc->addr.s_addr = INADDR_ANY;
+ fc->addr_mask.s_addr = CISCO_BIN_ANY_WILDCARD_MASK;
+
+ acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_ADDED);
+
+ return NB_OK;
+}
+
+static int
+lib_access_list_entry_source_any_destroy(struct nb_cb_destroy_args *args)
+{
+ struct filter_cisco *fc;
+ struct filter *f;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ f = nb_running_get_entry(args->dnode, NULL, true);
+ fc = &f->u.cfilter;
+ cisco_unset_addr_mask(&fc->addr, &fc->addr_mask);
+
+ acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_DELETED);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-filter:lib/access-list/entry/destination-host
+ */
+static int lib_access_list_entry_destination_host_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct filter_cisco *fc;
+ struct filter *f;
+
+ /* Don't allow duplicated values. */
+ if (args->event == NB_EV_VALIDATE) {
+ if (acl_cisco_is_dup(args->dnode)) {
+ snprintfrr(args->errmsg, args->errmsg_len,
+ "duplicated access list value: %s",
+ yang_dnode_get_string(args->dnode, NULL));
+ return NB_ERR_VALIDATION;
+ }
+ return NB_OK;
+ }
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ f = nb_running_get_entry(args->dnode, NULL, true);
+ fc = &f->u.cfilter;
+ fc->extended = 1;
+ yang_dnode_get_ipv4(&fc->mask, args->dnode, NULL);
+ fc->mask_mask.s_addr = CISCO_BIN_HOST_WILDCARD_MASK;
+
+ acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_ADDED);
+
+ return NB_OK;
+}
+
+static int lib_access_list_entry_destination_host_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ struct filter_cisco *fc;
+ struct filter *f;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ f = nb_running_get_entry(args->dnode, NULL, true);
+ fc = &f->u.cfilter;
+ fc->extended = 0;
+ cisco_unset_addr_mask(&fc->mask, &fc->mask_mask);
+
+ acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_DELETED);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-filter:lib/access-list/entry/destination-network/address
+ */
+static int lib_access_list_entry_destination_network_address_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct filter_cisco *fc;
+ struct filter *f;
+
+ /* Don't allow duplicated values. */
+ if (args->event == NB_EV_VALIDATE) {
+ if (acl_cisco_is_dup(args->dnode)) {
+ snprintfrr(args->errmsg, args->errmsg_len,
+ "duplicated access list value: %s",
+ yang_dnode_get_string(args->dnode, NULL));
+ return NB_ERR_VALIDATION;
+ }
+ return NB_OK;
+ }
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ f = nb_running_get_entry(args->dnode, NULL, true);
+ fc = &f->u.cfilter;
+ fc->extended = 1;
+ yang_dnode_get_ipv4(&fc->mask, args->dnode, NULL);
+
+ acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_ADDED);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-filter:lib/access-list/entry/destination-network/mask
+ */
+static int lib_access_list_entry_destination_network_mask_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct filter_cisco *fc;
+ struct filter *f;
+
+ /* Don't allow duplicated values. */
+ if (args->event == NB_EV_VALIDATE) {
+ if (acl_cisco_is_dup(args->dnode)) {
+ snprintfrr(args->errmsg, args->errmsg_len,
+ "duplicated access list value: %s",
+ yang_dnode_get_string(args->dnode, NULL));
+ return NB_ERR_VALIDATION;
+ }
+ return NB_OK;
+ }
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ f = nb_running_get_entry(args->dnode, NULL, true);
+ fc = &f->u.cfilter;
+ fc->extended = 1;
+ yang_dnode_get_ipv4(&fc->mask_mask, args->dnode, NULL);
+
+ acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_ADDED);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-filter:lib/access-list/entry/destination-any
+ */
+static int lib_access_list_entry_destination_any_create(
+ struct nb_cb_create_args *args)
+{
+ struct filter_cisco *fc;
+ struct filter *f;
+
+ /* Don't allow duplicated values. */
+ if (args->event == NB_EV_VALIDATE) {
+ if (acl_cisco_is_dup(args->dnode)) {
+ snprintfrr(args->errmsg, args->errmsg_len,
+ "duplicated access list value: %s",
+ yang_dnode_get_string(args->dnode, NULL));
+ return NB_ERR_VALIDATION;
+ }
+ return NB_OK;
+ }
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ f = nb_running_get_entry(args->dnode, NULL, true);
+ fc = &f->u.cfilter;
+ fc->extended = 1;
+ fc->mask.s_addr = INADDR_ANY;
+ fc->mask_mask.s_addr = CISCO_BIN_ANY_WILDCARD_MASK;
+
+ acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_ADDED);
+
+ return NB_OK;
+}
+
+static int lib_access_list_entry_destination_any_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ struct filter_cisco *fc;
+ struct filter *f;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ f = nb_running_get_entry(args->dnode, NULL, true);
+ fc = &f->u.cfilter;
+ fc->extended = 0;
+ cisco_unset_addr_mask(&fc->mask, &fc->mask_mask);
+
+ acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_DELETED);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-filter:lib/access-list/entry/any
+ */
+static int lib_access_list_entry_any_create(struct nb_cb_create_args *args)
+{
+ struct filter_zebra *fz;
+ struct filter *f;
+ int type;
+
+ /* Don't allow duplicated values. */
+ if (args->event == NB_EV_VALIDATE) {
+ if (acl_zebra_is_dup(
+ args->dnode,
+ yang_dnode_get_enum(args->dnode, "../../type"))) {
+ snprintfrr(args->errmsg, args->errmsg_len,
+ "duplicated access list value: %s",
+ yang_dnode_get_string(args->dnode, NULL));
+ return NB_ERR_VALIDATION;
+ }
+ return NB_OK;
+ }
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ f = nb_running_get_entry(args->dnode, NULL, true);
+ f->cisco = 0;
+ fz = &f->u.zfilter;
+ memset(&fz->prefix, 0, sizeof(fz->prefix));
+
+ type = yang_dnode_get_enum(args->dnode, "../../type");
+ switch (type) {
+ case YALT_IPV4:
+ fz->prefix.family = AF_INET;
+ break;
+ case YALT_IPV6:
+ fz->prefix.family = AF_INET6;
+ break;
+ case YALT_MAC:
+ fz->prefix.family = AF_ETHERNET;
+ break;
+ }
+
+ acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_ADDED);
+
+ return NB_OK;
+}
+
+static int lib_access_list_entry_any_destroy(struct nb_cb_destroy_args *args)
+{
+ struct filter_zebra *fz;
+ struct filter *f;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ f = nb_running_get_entry(args->dnode, NULL, true);
+ fz = &f->u.zfilter;
+ fz->prefix.family = AF_UNSPEC;
+
+ acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_DELETED);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-filter:lib/prefix-list
+ */
+static int lib_prefix_list_create(struct nb_cb_create_args *args)
+{
+ struct prefix_list *pl = NULL;
+ const char *name;
+ int type;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ type = yang_dnode_get_enum(args->dnode, "./type");
+ name = yang_dnode_get_string(args->dnode, "./name");
+ switch (type) {
+ case 0: /* ipv4 */
+ pl = prefix_list_get(AFI_IP, 0, name);
+ break;
+ case 1: /* ipv6 */
+ pl = prefix_list_get(AFI_IP6, 0, name);
+ break;
+ }
+
+ nb_running_set_entry(args->dnode, pl);
+
+ return NB_OK;
+}
+
+static int lib_prefix_list_destroy(struct nb_cb_destroy_args *args)
+{
+ struct prefix_list *pl;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ pl = nb_running_unset_entry(args->dnode);
+ prefix_list_delete(pl);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-filter:lib/prefix-list/remark
+ */
+static int lib_prefix_list_remark_modify(struct nb_cb_modify_args *args)
+{
+ struct prefix_list *pl;
+ const char *remark;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ pl = nb_running_get_entry(args->dnode, NULL, true);
+ if (pl->desc)
+ XFREE(MTYPE_TMP, pl->desc);
+
+ remark = yang_dnode_get_string(args->dnode, NULL);
+ pl->desc = XSTRDUP(MTYPE_TMP, remark);
+
+ return NB_OK;
+}
+
+static int lib_prefix_list_remark_destroy(struct nb_cb_destroy_args *args)
+{
+ struct prefix_list *pl;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ pl = nb_running_get_entry(args->dnode, NULL, true);
+ if (pl->desc)
+ XFREE(MTYPE_TMP, pl->desc);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-filter:lib/prefix-list/entry
+ */
+static int lib_prefix_list_entry_create(struct nb_cb_create_args *args)
+{
+ struct prefix_list_entry *ple;
+ struct prefix_list *pl;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ pl = nb_running_get_entry(args->dnode, NULL, true);
+ ple = prefix_list_entry_new();
+ ple->pl = pl;
+ ple->seq = yang_dnode_get_uint32(args->dnode, "./sequence");
+ prefix_list_entry_set_empty(ple);
+ nb_running_set_entry(args->dnode, ple);
+
+ return NB_OK;
+}
+
+static int lib_prefix_list_entry_destroy(struct nb_cb_destroy_args *args)
+{
+ struct prefix_list_entry *ple;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ ple = nb_running_unset_entry(args->dnode);
+ if (ple->installed)
+ prefix_list_entry_delete2(ple);
+ else
+ prefix_list_entry_free(ple);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-filter:lib/prefix-list/entry/action
+ */
+static int lib_prefix_list_entry_action_modify(struct nb_cb_modify_args *args)
+{
+ struct prefix_list_entry *ple;
+ int action_type;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ ple = nb_running_get_entry(args->dnode, NULL, true);
+
+ /* Start prefix entry update procedure. */
+ prefix_list_entry_update_start(ple);
+
+ action_type = yang_dnode_get_enum(args->dnode, NULL);
+ if (action_type == YPLA_PERMIT)
+ ple->type = PREFIX_PERMIT;
+ else
+ ple->type = PREFIX_DENY;
+
+ /* Finish prefix entry update procedure. */
+ prefix_list_entry_update_finish(ple);
+
+ return NB_OK;
+}
+
+static int lib_prefix_list_entry_prefix_modify(struct nb_cb_modify_args *args)
+{
+ struct prefix_list_entry *ple;
+ struct prefix p;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ ple = nb_running_get_entry(args->dnode, NULL, true);
+
+ /* Start prefix entry update procedure. */
+ prefix_list_entry_update_start(ple);
+
+ yang_dnode_get_prefix(&ple->prefix, args->dnode, NULL);
+
+ /* Apply mask and correct original address if necessary. */
+ prefix_copy(&p, &ple->prefix);
+ apply_mask(&p);
+ if (!prefix_same(&ple->prefix, &p)) {
+ zlog_info("%s: bad network %pFX correcting it to %pFX",
+ __func__, &ple->prefix, &p);
+ prefix_copy(&ple->prefix, &p);
+ }
+
+
+ /* Finish prefix entry update procedure. */
+ prefix_list_entry_update_finish(ple);
+
+ return NB_OK;
+}
+
+static int lib_prefix_list_entry_prefix_destroy(struct nb_cb_destroy_args *args)
+{
+ struct prefix_list_entry *ple;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ ple = nb_running_get_entry(args->dnode, NULL, true);
+
+ /* Start prefix entry update procedure. */
+ prefix_list_entry_update_start(ple);
+
+ memset(&ple->prefix, 0, sizeof(ple->prefix));
+
+ /* Finish prefix entry update procedure. */
+ prefix_list_entry_update_finish(ple);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-filter:lib/prefix-list/entry/ipv4-prefix
+ */
+static int
+lib_prefix_list_entry_ipv4_prefix_modify(struct nb_cb_modify_args *args)
+{
+ if (args->event == NB_EV_VALIDATE) {
+ const struct lyd_node *plist_dnode =
+ yang_dnode_get_parent(args->dnode, "prefix-list");
+
+ if (plist_is_dup_nb(args->dnode)) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "duplicated prefix list value: %s",
+ yang_dnode_get_string(args->dnode, NULL));
+ return NB_ERR_VALIDATION;
+ }
+
+ return prefix_list_nb_validate_v4_af_type(
+ plist_dnode, args->errmsg, args->errmsg_len);
+ }
+
+ return lib_prefix_list_entry_prefix_modify(args);
+}
+
+static int
+lib_prefix_list_entry_ipv4_prefix_destroy(struct nb_cb_destroy_args *args)
+{
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ return lib_prefix_list_entry_prefix_destroy(args);
+}
+
+/*
+ * XPath: /frr-filter:lib/prefix-list/entry/ipv6-prefix
+ */
+static int
+lib_prefix_list_entry_ipv6_prefix_modify(struct nb_cb_modify_args *args)
+{
+
+ if (args->event == NB_EV_VALIDATE) {
+ const struct lyd_node *plist_dnode =
+ yang_dnode_get_parent(args->dnode, "prefix-list");
+
+ if (plist_is_dup_nb(args->dnode)) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "duplicated prefix list value: %s",
+ yang_dnode_get_string(args->dnode, NULL));
+ return NB_ERR_VALIDATION;
+ }
+
+ return prefix_list_nb_validate_v6_af_type(
+ plist_dnode, args->errmsg, args->errmsg_len);
+ }
+
+ return lib_prefix_list_entry_prefix_modify(args);
+}
+
+static int
+lib_prefix_list_entry_ipv6_prefix_destroy(struct nb_cb_destroy_args *args)
+{
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ return lib_prefix_list_entry_prefix_destroy(args);
+}
+
+/*
+ * XPath: /frr-filter:lib/prefix-list/entry/ipv4-prefix-length-greater-or-equal
+ */
+static int lib_prefix_list_entry_ipv4_prefix_length_greater_or_equal_modify(
+ struct nb_cb_modify_args *args)
+{
+ if (args->event == NB_EV_VALIDATE
+ && prefix_list_length_validate(args) != NB_OK)
+ return NB_ERR_VALIDATION;
+
+ if (args->event == NB_EV_VALIDATE) {
+ const struct lyd_node *plist_dnode =
+ yang_dnode_get_parent(args->dnode, "prefix-list");
+
+ if (plist_is_dup_nb(args->dnode)) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "duplicated prefix list value: %s",
+ yang_dnode_get_string(args->dnode, NULL));
+ return NB_ERR_VALIDATION;
+ }
+
+ return prefix_list_nb_validate_v4_af_type(
+ plist_dnode, args->errmsg, args->errmsg_len);
+ }
+
+ return lib_prefix_list_entry_prefix_length_greater_or_equal_modify(
+ args);
+}
+
+static int lib_prefix_list_entry_ipv4_prefix_length_greater_or_equal_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ if (args->event == NB_EV_VALIDATE) {
+ const struct lyd_node *plist_dnode =
+ yang_dnode_get_parent(args->dnode, "prefix-list");
+
+ return prefix_list_nb_validate_v4_af_type(
+ plist_dnode, args->errmsg, args->errmsg_len);
+ }
+
+ return lib_prefix_list_entry_prefix_length_greater_or_equal_destroy(
+ args);
+}
+
+/*
+ * XPath: /frr-filter:lib/prefix-list/entry/ipv4-prefix-length-lesser-or-equal
+ */
+static int lib_prefix_list_entry_ipv4_prefix_length_lesser_or_equal_modify(
+ struct nb_cb_modify_args *args)
+{
+ if (args->event == NB_EV_VALIDATE
+ && prefix_list_length_validate(args) != NB_OK)
+ return NB_ERR_VALIDATION;
+
+ if (args->event == NB_EV_VALIDATE) {
+ const struct lyd_node *plist_dnode =
+ yang_dnode_get_parent(args->dnode, "prefix-list");
+
+ if (plist_is_dup_nb(args->dnode)) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "duplicated prefix list value: %s",
+ yang_dnode_get_string(args->dnode, NULL));
+ return NB_ERR_VALIDATION;
+ }
+
+ return prefix_list_nb_validate_v4_af_type(
+ plist_dnode, args->errmsg, args->errmsg_len);
+ }
+
+ return lib_prefix_list_entry_prefix_length_lesser_or_equal_modify(
+ args);
+}
+
+static int lib_prefix_list_entry_ipv4_prefix_length_lesser_or_equal_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ if (args->event == NB_EV_VALIDATE) {
+ const struct lyd_node *plist_dnode =
+ yang_dnode_get_parent(args->dnode, "prefix-list");
+
+ return prefix_list_nb_validate_v4_af_type(
+ plist_dnode, args->errmsg, args->errmsg_len);
+ }
+
+ return lib_prefix_list_entry_prefix_length_lesser_or_equal_destroy(
+ args);
+}
+
+/*
+ * XPath: /frr-filter:lib/prefix-list/entry/ipv6-prefix-length-greater-or-equal
+ */
+static int lib_prefix_list_entry_ipv6_prefix_length_greater_or_equal_modify(
+ struct nb_cb_modify_args *args)
+{
+ if (args->event == NB_EV_VALIDATE
+ && prefix_list_length_validate(args) != NB_OK)
+ return NB_ERR_VALIDATION;
+
+ if (args->event == NB_EV_VALIDATE) {
+ const struct lyd_node *plist_dnode =
+ yang_dnode_get_parent(args->dnode, "prefix-list");
+
+ if (plist_is_dup_nb(args->dnode)) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "duplicated prefix list value: %s",
+ yang_dnode_get_string(args->dnode, NULL));
+ return NB_ERR_VALIDATION;
+ }
+
+ return prefix_list_nb_validate_v6_af_type(
+ plist_dnode, args->errmsg, args->errmsg_len);
+ }
+
+ return lib_prefix_list_entry_prefix_length_greater_or_equal_modify(
+ args);
+}
+
+static int lib_prefix_list_entry_ipv6_prefix_length_greater_or_equal_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ if (args->event == NB_EV_VALIDATE) {
+ const struct lyd_node *plist_dnode =
+ yang_dnode_get_parent(args->dnode, "prefix-list");
+
+ return prefix_list_nb_validate_v6_af_type(
+ plist_dnode, args->errmsg, args->errmsg_len);
+ }
+
+ return lib_prefix_list_entry_prefix_length_greater_or_equal_destroy(
+ args);
+}
+
+/*
+ * XPath: /frr-filter:lib/prefix-list/entry/ipv6-prefix-length-lesser-or-equal
+ */
+static int lib_prefix_list_entry_ipv6_prefix_length_lesser_or_equal_modify(
+ struct nb_cb_modify_args *args)
+{
+ if (args->event == NB_EV_VALIDATE
+ && prefix_list_length_validate(args) != NB_OK)
+ return NB_ERR_VALIDATION;
+
+ if (args->event == NB_EV_VALIDATE) {
+ const struct lyd_node *plist_dnode =
+ yang_dnode_get_parent(args->dnode, "prefix-list");
+
+ if (plist_is_dup_nb(args->dnode)) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "duplicated prefix list value: %s",
+ yang_dnode_get_string(args->dnode, NULL));
+ return NB_ERR_VALIDATION;
+ }
+
+ return prefix_list_nb_validate_v6_af_type(
+ plist_dnode, args->errmsg, args->errmsg_len);
+ }
+
+ return lib_prefix_list_entry_prefix_length_lesser_or_equal_modify(
+ args);
+}
+
+static int lib_prefix_list_entry_ipv6_prefix_length_lesser_or_equal_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ if (args->event == NB_EV_VALIDATE) {
+ const struct lyd_node *plist_dnode =
+ yang_dnode_get_parent(args->dnode, "prefix-list");
+
+ return prefix_list_nb_validate_v6_af_type(
+ plist_dnode, args->errmsg, args->errmsg_len);
+ }
+
+ return lib_prefix_list_entry_prefix_length_lesser_or_equal_destroy(
+ args);
+}
+
+/*
+ * XPath: /frr-filter:lib/prefix-list/entry/any
+ */
+static int lib_prefix_list_entry_any_create(struct nb_cb_create_args *args)
+{
+ struct prefix_list_entry *ple;
+ int type;
+
+ if (args->event == NB_EV_VALIDATE) {
+ if (plist_is_dup_nb(args->dnode)) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "duplicated prefix list value: %s",
+ yang_dnode_get_string(args->dnode, NULL));
+ return NB_ERR_VALIDATION;
+ }
+
+ return NB_OK;
+ }
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ ple = nb_running_get_entry(args->dnode, NULL, true);
+
+ /* Start prefix entry update procedure. */
+ prefix_list_entry_update_start(ple);
+
+ ple->any = true;
+
+ /* Fill prefix struct from scratch. */
+ memset(&ple->prefix, 0, sizeof(ple->prefix));
+
+ type = yang_dnode_get_enum(args->dnode, "../../type");
+ switch (type) {
+ case YPLT_IPV4:
+ ple->prefix.family = AF_INET;
+ ple->ge = 0;
+ ple->le = IPV4_MAX_BITLEN;
+ break;
+ case YPLT_IPV6:
+ ple->prefix.family = AF_INET6;
+ ple->ge = 0;
+ ple->le = IPV6_MAX_BITLEN;
+ break;
+ }
+
+ /* Finish prefix entry update procedure. */
+ prefix_list_entry_update_finish(ple);
+
+ return NB_OK;
+}
+
+static int lib_prefix_list_entry_any_destroy(struct nb_cb_destroy_args *args)
+{
+ struct prefix_list_entry *ple;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ ple = nb_running_get_entry(args->dnode, NULL, true);
+
+ /* Start prefix entry update procedure. */
+ prefix_list_entry_update_start(ple);
+
+ ple->any = false;
+
+ /* Finish prefix entry update procedure. */
+ prefix_list_entry_update_finish(ple);
+
+ return NB_OK;
+}
+
+/* clang-format off */
+const struct frr_yang_module_info frr_filter_info = {
+ .name = "frr-filter",
+ .nodes = {
+ {
+ .xpath = "/frr-filter:lib/access-list",
+ .cbs = {
+ .create = lib_access_list_create,
+ .destroy = lib_access_list_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-filter:lib/access-list/remark",
+ .cbs = {
+ .modify = lib_access_list_remark_modify,
+ .destroy = lib_access_list_remark_destroy,
+ .cli_show = access_list_remark_show,
+ }
+ },
+ {
+ .xpath = "/frr-filter:lib/access-list/entry",
+ .cbs = {
+ .create = lib_access_list_entry_create,
+ .destroy = lib_access_list_entry_destroy,
+ .cli_cmp = access_list_cmp,
+ .cli_show = access_list_show,
+ }
+ },
+ {
+ .xpath = "/frr-filter:lib/access-list/entry/action",
+ .cbs = {
+ .modify = lib_access_list_entry_action_modify,
+ }
+ },
+ {
+ .xpath = "/frr-filter:lib/access-list/entry/ipv4-prefix",
+ .cbs = {
+ .modify = lib_access_list_entry_ipv4_prefix_modify,
+ .destroy = lib_access_list_entry_ipv4_prefix_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-filter:lib/access-list/entry/ipv4-exact-match",
+ .cbs = {
+ .modify = lib_access_list_entry_ipv4_exact_match_modify,
+ .destroy = lib_access_list_entry_ipv4_exact_match_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-filter:lib/access-list/entry/host",
+ .cbs = {
+ .modify = lib_access_list_entry_host_modify,
+ .destroy = lib_access_list_entry_host_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-filter:lib/access-list/entry/network/address",
+ .cbs = {
+ .modify = lib_access_list_entry_network_address_modify,
+ }
+ },
+ {
+ .xpath = "/frr-filter:lib/access-list/entry/network/mask",
+ .cbs = {
+ .modify = lib_access_list_entry_network_mask_modify,
+ }
+ },
+ {
+ .xpath = "/frr-filter:lib/access-list/entry/source-any",
+ .cbs = {
+ .create = lib_access_list_entry_source_any_create,
+ .destroy = lib_access_list_entry_source_any_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-filter:lib/access-list/entry/destination-host",
+ .cbs = {
+ .modify = lib_access_list_entry_destination_host_modify,
+ .destroy = lib_access_list_entry_destination_host_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-filter:lib/access-list/entry/destination-network/address",
+ .cbs = {
+ .modify = lib_access_list_entry_destination_network_address_modify,
+ }
+ },
+ {
+ .xpath = "/frr-filter:lib/access-list/entry/destination-network/mask",
+ .cbs = {
+ .modify = lib_access_list_entry_destination_network_mask_modify,
+ }
+ },
+ {
+ .xpath = "/frr-filter:lib/access-list/entry/destination-any",
+ .cbs = {
+ .create = lib_access_list_entry_destination_any_create,
+ .destroy = lib_access_list_entry_destination_any_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-filter:lib/access-list/entry/ipv6-prefix",
+ .cbs = {
+ .modify = lib_access_list_entry_ipv4_prefix_modify,
+ .destroy = lib_access_list_entry_ipv4_prefix_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-filter:lib/access-list/entry/ipv6-exact-match",
+ .cbs = {
+ .modify = lib_access_list_entry_ipv4_exact_match_modify,
+ .destroy = lib_access_list_entry_ipv4_exact_match_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-filter:lib/access-list/entry/mac",
+ .cbs = {
+ .modify = lib_access_list_entry_ipv4_prefix_modify,
+ .destroy = lib_access_list_entry_ipv4_prefix_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-filter:lib/access-list/entry/any",
+ .cbs = {
+ .create = lib_access_list_entry_any_create,
+ .destroy = lib_access_list_entry_any_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-filter:lib/prefix-list",
+ .cbs = {
+ .create = lib_prefix_list_create,
+ .destroy = lib_prefix_list_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-filter:lib/prefix-list/remark",
+ .cbs = {
+ .modify = lib_prefix_list_remark_modify,
+ .destroy = lib_prefix_list_remark_destroy,
+ .cli_show = prefix_list_remark_show,
+ }
+ },
+ {
+ .xpath = "/frr-filter:lib/prefix-list/entry",
+ .cbs = {
+ .create = lib_prefix_list_entry_create,
+ .destroy = lib_prefix_list_entry_destroy,
+ .cli_cmp = prefix_list_cmp,
+ .cli_show = prefix_list_show,
+ }
+ },
+ {
+ .xpath = "/frr-filter:lib/prefix-list/entry/action",
+ .cbs = {
+ .modify = lib_prefix_list_entry_action_modify,
+ }
+ },
+ {
+ .xpath = "/frr-filter:lib/prefix-list/entry/ipv4-prefix",
+ .cbs = {
+ .modify = lib_prefix_list_entry_ipv4_prefix_modify,
+ .destroy = lib_prefix_list_entry_ipv4_prefix_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-filter:lib/prefix-list/entry/ipv4-prefix-length-greater-or-equal",
+ .cbs = {
+ .modify = lib_prefix_list_entry_ipv4_prefix_length_greater_or_equal_modify,
+ .destroy = lib_prefix_list_entry_ipv4_prefix_length_greater_or_equal_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-filter:lib/prefix-list/entry/ipv4-prefix-length-lesser-or-equal",
+ .cbs = {
+ .modify = lib_prefix_list_entry_ipv4_prefix_length_lesser_or_equal_modify,
+ .destroy = lib_prefix_list_entry_ipv4_prefix_length_lesser_or_equal_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-filter:lib/prefix-list/entry/ipv6-prefix",
+ .cbs = {
+ .modify = lib_prefix_list_entry_ipv6_prefix_modify,
+ .destroy = lib_prefix_list_entry_ipv6_prefix_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-filter:lib/prefix-list/entry/ipv6-prefix-length-greater-or-equal",
+ .cbs = {
+ .modify = lib_prefix_list_entry_ipv6_prefix_length_greater_or_equal_modify,
+ .destroy = lib_prefix_list_entry_ipv6_prefix_length_greater_or_equal_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-filter:lib/prefix-list/entry/ipv6-prefix-length-lesser-or-equal",
+ .cbs = {
+ .modify = lib_prefix_list_entry_ipv6_prefix_length_lesser_or_equal_modify,
+ .destroy = lib_prefix_list_entry_ipv6_prefix_length_lesser_or_equal_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-filter:lib/prefix-list/entry/any",
+ .cbs = {
+ .create = lib_prefix_list_entry_any_create,
+ .destroy = lib_prefix_list_entry_any_destroy,
+ }
+ },
+ {
+ .xpath = NULL,
+ },
+ }
+};
diff --git a/lib/freebsd-queue.h b/lib/freebsd-queue.h
new file mode 100644
index 0000000..793cfff
--- /dev/null
+++ b/lib/freebsd-queue.h
@@ -0,0 +1,686 @@
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)queue.h 8.5 (Berkeley) 8/20/94
+ * $FreeBSD$
+ */
+
+#ifndef _SYS_QUEUE_H_
+#define _SYS_QUEUE_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * This file defines four types of data structures: singly-linked lists,
+ * singly-linked tail queues, lists and tail queues.
+ *
+ * A singly-linked list is headed by a single forward pointer. The elements
+ * are singly linked for minimum space and pointer manipulation overhead at
+ * the expense of O(n) removal for arbitrary elements. New elements can be
+ * added to the list after an existing element or at the head of the list.
+ * Elements being removed from the head of the list should use the explicit
+ * macro for this purpose for optimum efficiency. A singly-linked list may
+ * only be traversed in the forward direction. Singly-linked lists are ideal
+ * for applications with large datasets and few or no removals or for
+ * implementing a LIFO queue.
+ *
+ * A singly-linked tail queue is headed by a pair of pointers, one to the
+ * head of the list and the other to the tail of the list. The elements are
+ * singly linked for minimum space and pointer manipulation overhead at the
+ * expense of O(n) removal for arbitrary elements. New elements can be added
+ * to the list after an existing element, at the head of the list, or at the
+ * end of the list. Elements being removed from the head of the tail queue
+ * should use the explicit macro for this purpose for optimum efficiency.
+ * A singly-linked tail queue may only be traversed in the forward direction.
+ * Singly-linked tail queues are ideal for applications with large datasets
+ * and few or no removals or for implementing a FIFO queue.
+ *
+ * A list is headed by a single forward pointer (or an array of forward
+ * pointers for a hash table header). The elements are doubly linked
+ * so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before
+ * or after an existing element or at the head of the list. A list
+ * may only be traversed in the forward direction.
+ *
+ * A tail queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are doubly
+ * linked so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before or
+ * after an existing element, at the head of the list, or at the end of
+ * the list. A tail queue may be traversed in either direction.
+ *
+ * For details on the use of these macros, see the queue(3) manual page.
+ *
+ *
+ * SLIST LIST STAILQ TAILQ
+ * _HEAD + + + +
+ * _HEAD_INITIALIZER + + + +
+ * _ENTRY + + + +
+ * _INIT + + + +
+ * _EMPTY + + + +
+ * _FIRST + + + +
+ * _NEXT + + + +
+ * _PREV - - - +
+ * _LAST - - + +
+ * _FOREACH + + + +
+ * _FOREACH_SAFE + + + +
+ * _FOREACH_REVERSE - - - +
+ * _FOREACH_REVERSE_SAFE - - - +
+ * _INSERT_HEAD + + + +
+ * _INSERT_BEFORE - + - +
+ * _INSERT_AFTER + + + +
+ * _INSERT_TAIL - - + +
+ * _CONCAT - - + +
+ * _REMOVE_AFTER + - + -
+ * _REMOVE_HEAD + - + -
+ * _REMOVE + + + +
+ * _SWAP + + + +
+ *
+ */
+#ifdef QUEUE_MACRO_DEBUG
+/* Store the last 2 places the queue element or head was altered */
+struct qm_trace {
+ char *lastfile;
+ int lastline;
+ char *prevfile;
+ int prevline;
+};
+
+#define TRACEBUF struct qm_trace trace;
+#define TRASHIT(x) do {(x) = (void *)-1;} while (0)
+#define QMD_SAVELINK(name, link) void **name = (void *)&(link)
+
+#define QMD_TRACE_HEAD(head) \
+ do { \
+ (head)->trace.prevline = (head)->trace.lastline; \
+ (head)->trace.prevfile = (head)->trace.lastfile; \
+ (head)->trace.lastline = __LINE__; \
+ (head)->trace.lastfile = __FILE__; \
+ } while (0)
+
+#define QMD_TRACE_ELEM(elem) \
+ do { \
+ (elem)->trace.prevline = (elem)->trace.lastline; \
+ (elem)->trace.prevfile = (elem)->trace.lastfile; \
+ (elem)->trace.lastline = __LINE__; \
+ (elem)->trace.lastfile = __FILE__; \
+ } while (0)
+
+#else
+#define QMD_TRACE_ELEM(elem)
+#define QMD_TRACE_HEAD(head)
+#define QMD_SAVELINK(name, link)
+#define TRACEBUF
+#define TRASHIT(x)
+#endif /* QUEUE_MACRO_DEBUG */
+
+/*
+ * Singly-linked List declarations.
+ */
+#define SLIST_HEAD(name, type) \
+ struct name { \
+ struct type *slh_first; /* first element */ \
+ }
+
+#define SLIST_HEAD_INITIALIZER(head) \
+ { \
+ NULL \
+ }
+
+#define SLIST_ENTRY(type) \
+ struct { \
+ struct type *sle_next; /* next element */ \
+ }
+
+/*
+ * Singly-linked List functions.
+ */
+#define SLIST_EMPTY(head) ((head)->slh_first == NULL)
+
+#define SLIST_FIRST(head) ((head)->slh_first)
+
+#define SLIST_FOREACH(var, head, field) \
+ for ((var) = SLIST_FIRST((head)); (var); \
+ (var) = SLIST_NEXT((var), field))
+
+#define SLIST_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = SLIST_FIRST((head)); \
+ (var) && ((tvar) = SLIST_NEXT((var), field), 1); (var) = (tvar))
+
+#define SLIST_FOREACH_PREVPTR(var, varp, head, field) \
+ for ((varp) = &SLIST_FIRST((head)); ((var) = *(varp)) != NULL; \
+ (varp) = &SLIST_NEXT((var), field))
+
+#define SLIST_INIT(head) \
+ do { \
+ SLIST_FIRST((head)) = NULL; \
+ } while (0)
+
+#define SLIST_INSERT_AFTER(slistelm, elm, field) \
+ do { \
+ SLIST_NEXT((elm), field) = SLIST_NEXT((slistelm), field); \
+ SLIST_NEXT((slistelm), field) = (elm); \
+ } while (0)
+
+#define SLIST_INSERT_HEAD(head, elm, field) \
+ do { \
+ SLIST_NEXT((elm), field) = SLIST_FIRST((head)); \
+ SLIST_FIRST((head)) = (elm); \
+ } while (0)
+
+#define SLIST_NEXT(elm, field) ((elm)->field.sle_next)
+
+#define SLIST_REMOVE(head, elm, type, field) \
+ do { \
+ QMD_SAVELINK(oldnext, (elm)->field.sle_next); \
+ if (SLIST_FIRST((head)) == (elm)) { \
+ SLIST_REMOVE_HEAD((head), field); \
+ } else { \
+ struct type *curelm = SLIST_FIRST((head)); \
+ while (SLIST_NEXT(curelm, field) != (elm)) \
+ curelm = SLIST_NEXT(curelm, field); \
+ SLIST_REMOVE_AFTER(curelm, field); \
+ } \
+ TRASHIT(*oldnext); \
+ } while (0)
+
+#define SLIST_REMOVE_AFTER(elm, field) \
+ do { \
+ SLIST_NEXT(elm, field) = \
+ SLIST_NEXT(SLIST_NEXT(elm, field), field); \
+ } while (0)
+
+#define SLIST_REMOVE_HEAD(head, field) \
+ do { \
+ SLIST_FIRST((head)) = SLIST_NEXT(SLIST_FIRST((head)), field); \
+ } while (0)
+
+#define SLIST_SWAP(head1, head2, type) \
+ do { \
+ struct type *swap_first = SLIST_FIRST(head1); \
+ SLIST_FIRST(head1) = SLIST_FIRST(head2); \
+ SLIST_FIRST(head2) = swap_first; \
+ } while (0)
+
+/*
+ * Singly-linked Tail queue declarations.
+ */
+#define STAILQ_HEAD(name, type) \
+ struct name { \
+ struct type *stqh_first; /* first element */ \
+ struct type **stqh_last; /* addr of last next element */ \
+ }
+
+#define STAILQ_HEAD_INITIALIZER(head) \
+ { \
+ NULL, &(head).stqh_first \
+ }
+
+#define STAILQ_ENTRY(type) \
+ struct { \
+ struct type *stqe_next; /* next element */ \
+ }
+
+/*
+ * Singly-linked Tail queue functions.
+ */
+#define STAILQ_CONCAT(head1, head2) \
+ do { \
+ if (!STAILQ_EMPTY((head2))) { \
+ *(head1)->stqh_last = (head2)->stqh_first; \
+ (head1)->stqh_last = (head2)->stqh_last; \
+ STAILQ_INIT((head2)); \
+ } \
+ } while (0)
+
+#define STAILQ_EMPTY(head) ((head)->stqh_first == NULL)
+
+#define STAILQ_FIRST(head) ((head)->stqh_first)
+
+#define STAILQ_FOREACH(var, head, field) \
+ for ((var) = STAILQ_FIRST((head)); (var); \
+ (var) = STAILQ_NEXT((var), field))
+
+
+#define STAILQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = STAILQ_FIRST((head)); \
+ (var) && ((tvar) = STAILQ_NEXT((var), field), 1); (var) = (tvar))
+
+#define STAILQ_INIT(head) \
+ do { \
+ STAILQ_FIRST((head)) = NULL; \
+ (head)->stqh_last = &STAILQ_FIRST((head)); \
+ } while (0)
+
+#define STAILQ_INSERT_AFTER(head, tqelm, elm, field) \
+ do { \
+ if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) \
+ == NULL) \
+ (head)->stqh_last = &STAILQ_NEXT((elm), field); \
+ STAILQ_NEXT((tqelm), field) = (elm); \
+ } while (0)
+
+#define STAILQ_INSERT_HEAD(head, elm, field) \
+ do { \
+ if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) \
+ == NULL) \
+ (head)->stqh_last = &STAILQ_NEXT((elm), field); \
+ STAILQ_FIRST((head)) = (elm); \
+ } while (0)
+
+#define STAILQ_INSERT_TAIL(head, elm, field) \
+ do { \
+ STAILQ_NEXT((elm), field) = NULL; \
+ *(head)->stqh_last = (elm); \
+ (head)->stqh_last = &STAILQ_NEXT((elm), field); \
+ } while (0)
+
+#define STAILQ_LAST(head, type, field) \
+ (STAILQ_EMPTY((head)) \
+ ? NULL \
+ : ((struct type *)(void *)((char *)((head)->stqh_last) \
+ - offsetof(struct type, field))))
+
+#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next)
+
+#define STAILQ_REMOVE(head, elm, type, field) \
+ do { \
+ QMD_SAVELINK(oldnext, (elm)->field.stqe_next); \
+ if (STAILQ_FIRST((head)) == (elm)) { \
+ STAILQ_REMOVE_HEAD((head), field); \
+ } else { \
+ struct type *curelm = STAILQ_FIRST((head)); \
+ while (STAILQ_NEXT(curelm, field) != (elm)) \
+ curelm = STAILQ_NEXT(curelm, field); \
+ STAILQ_REMOVE_AFTER(head, curelm, field); \
+ } \
+ TRASHIT(*oldnext); \
+ } while (0)
+
+#define STAILQ_REMOVE_AFTER(head, elm, field) \
+ do { \
+ if ((STAILQ_NEXT(elm, field) = \
+ STAILQ_NEXT(STAILQ_NEXT(elm, field), field)) \
+ == NULL) \
+ (head)->stqh_last = &STAILQ_NEXT((elm), field); \
+ } while (0)
+
+#define STAILQ_REMOVE_HEAD(head, field) \
+ do { \
+ if ((STAILQ_FIRST((head)) = \
+ STAILQ_NEXT(STAILQ_FIRST((head)), field)) \
+ == NULL) \
+ (head)->stqh_last = &STAILQ_FIRST((head)); \
+ } while (0)
+
+#define STAILQ_SWAP(head1, head2, type) \
+ do { \
+ struct type *swap_first = STAILQ_FIRST(head1); \
+ struct type **swap_last = (head1)->stqh_last; \
+ STAILQ_FIRST(head1) = STAILQ_FIRST(head2); \
+ (head1)->stqh_last = (head2)->stqh_last; \
+ STAILQ_FIRST(head2) = swap_first; \
+ (head2)->stqh_last = swap_last; \
+ if (STAILQ_EMPTY(head1)) \
+ (head1)->stqh_last = &STAILQ_FIRST(head1); \
+ if (STAILQ_EMPTY(head2)) \
+ (head2)->stqh_last = &STAILQ_FIRST(head2); \
+ } while (0)
+
+
+/*
+ * List declarations.
+ */
+#define LIST_HEAD(name, type) \
+ struct name { \
+ struct type *lh_first; /* first element */ \
+ }
+
+#define LIST_HEAD_INITIALIZER(head) \
+ { \
+ NULL \
+ }
+
+#define LIST_ENTRY(type) \
+ struct { \
+ struct type *le_next; /* next element */ \
+ struct type **le_prev; /* address of previous next element */ \
+ }
+
+/*
+ * List functions.
+ */
+
+#if (defined(_KERNEL) && defined(INVARIANTS))
+#define QMD_LIST_CHECK_HEAD(head, field) \
+ do { \
+ if (LIST_FIRST((head)) != NULL \
+ && LIST_FIRST((head))->field.le_prev \
+ != &LIST_FIRST((head))) \
+ panic("Bad list head %p first->prev != head", (head)); \
+ } while (0)
+
+#define QMD_LIST_CHECK_NEXT(elm, field) \
+ do { \
+ if (LIST_NEXT((elm), field) != NULL \
+ && LIST_NEXT((elm), field)->field.le_prev \
+ != &((elm)->field.le_next)) \
+ panic("Bad link elm %p next->prev != elm", (elm)); \
+ } while (0)
+
+#define QMD_LIST_CHECK_PREV(elm, field) \
+ do { \
+ if (*(elm)->field.le_prev != (elm)) \
+ panic("Bad link elm %p prev->next != elm", (elm)); \
+ } while (0)
+#else
+#define QMD_LIST_CHECK_HEAD(head, field)
+#define QMD_LIST_CHECK_NEXT(elm, field)
+#define QMD_LIST_CHECK_PREV(elm, field)
+#endif /* (_KERNEL && INVARIANTS) */
+
+#define LIST_EMPTY(head) ((head)->lh_first == NULL)
+
+#define LIST_FIRST(head) ((head)->lh_first)
+
+#define LIST_FOREACH(var, head, field) \
+ for ((var) = LIST_FIRST((head)); (var); (var) = LIST_NEXT((var), field))
+
+#define LIST_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = LIST_FIRST((head)); \
+ (var) && ((tvar) = LIST_NEXT((var), field), 1); (var) = (tvar))
+
+#define LIST_INIT(head) \
+ do { \
+ LIST_FIRST((head)) = NULL; \
+ } while (0)
+
+#define LIST_INSERT_AFTER(listelm, elm, field) \
+ do { \
+ QMD_LIST_CHECK_NEXT(listelm, field); \
+ if ((LIST_NEXT((elm), field) = LIST_NEXT((listelm), field)) \
+ != NULL) \
+ LIST_NEXT((listelm), field)->field.le_prev = \
+ &LIST_NEXT((elm), field); \
+ LIST_NEXT((listelm), field) = (elm); \
+ (elm)->field.le_prev = &LIST_NEXT((listelm), field); \
+ } while (0)
+
+#define LIST_INSERT_BEFORE(listelm, elm, field) \
+ do { \
+ QMD_LIST_CHECK_PREV(listelm, field); \
+ (elm)->field.le_prev = (listelm)->field.le_prev; \
+ LIST_NEXT((elm), field) = (listelm); \
+ *(listelm)->field.le_prev = (elm); \
+ (listelm)->field.le_prev = &LIST_NEXT((elm), field); \
+ } while (0)
+
+#define LIST_INSERT_HEAD(head, elm, field) \
+ do { \
+ QMD_LIST_CHECK_HEAD((head), field); \
+ if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \
+ LIST_FIRST((head))->field.le_prev = \
+ &LIST_NEXT((elm), field); \
+ LIST_FIRST((head)) = (elm); \
+ (elm)->field.le_prev = &LIST_FIRST((head)); \
+ } while (0)
+
+#define LIST_NEXT(elm, field) ((elm)->field.le_next)
+
+#define LIST_REMOVE(elm, field) \
+ do { \
+ QMD_SAVELINK(oldnext, (elm)->field.le_next); \
+ QMD_SAVELINK(oldprev, (elm)->field.le_prev); \
+ QMD_LIST_CHECK_NEXT(elm, field); \
+ QMD_LIST_CHECK_PREV(elm, field); \
+ if (LIST_NEXT((elm), field) != NULL) \
+ LIST_NEXT((elm), field)->field.le_prev = \
+ (elm)->field.le_prev; \
+ *(elm)->field.le_prev = LIST_NEXT((elm), field); \
+ TRASHIT(*oldnext); \
+ TRASHIT(*oldprev); \
+ } while (0)
+
+#define LIST_SWAP(head1, head2, type, field) \
+ do { \
+ struct type *swap_tmp = LIST_FIRST((head1)); \
+ LIST_FIRST((head1)) = LIST_FIRST((head2)); \
+ LIST_FIRST((head2)) = swap_tmp; \
+ if ((swap_tmp = LIST_FIRST((head1))) != NULL) \
+ swap_tmp->field.le_prev = &LIST_FIRST((head1)); \
+ if ((swap_tmp = LIST_FIRST((head2))) != NULL) \
+ swap_tmp->field.le_prev = &LIST_FIRST((head2)); \
+ } while (0)
+
+/*
+ * Tail queue declarations.
+ */
+#define TAILQ_HEAD(name, type) \
+ struct name { \
+ struct type *tqh_first; /* first element */ \
+ struct type **tqh_last; /* addr of last next element */ \
+ TRACEBUF \
+ }
+
+#define TAILQ_HEAD_INITIALIZER(head) \
+ { \
+ NULL, &(head).tqh_first \
+ }
+
+#define TAILQ_ENTRY(type) \
+ struct { \
+ struct type *tqe_next; /* next element */ \
+ struct type **tqe_prev; /* address of previous next element */ \
+ TRACEBUF \
+ }
+
+/*
+ * Tail queue functions.
+ */
+#if (defined(_KERNEL) && defined(INVARIANTS))
+#define QMD_TAILQ_CHECK_HEAD(head, field) \
+ do { \
+ if (!TAILQ_EMPTY(head) \
+ && TAILQ_FIRST((head))->field.tqe_prev \
+ != &TAILQ_FIRST((head))) \
+ panic("Bad tailq head %p first->prev != head", \
+ (head)); \
+ } while (0)
+
+#define QMD_TAILQ_CHECK_TAIL(head, field) \
+ do { \
+ if (*(head)->tqh_last != NULL) \
+ panic("Bad tailq NEXT(%p->tqh_last) != NULL", (head)); \
+ } while (0)
+
+#define QMD_TAILQ_CHECK_NEXT(elm, field) \
+ do { \
+ if (TAILQ_NEXT((elm), field) != NULL \
+ && TAILQ_NEXT((elm), field)->field.tqe_prev \
+ != &((elm)->field.tqe_next)) \
+ panic("Bad link elm %p next->prev != elm", (elm)); \
+ } while (0)
+
+#define QMD_TAILQ_CHECK_PREV(elm, field) \
+ do { \
+ if (*(elm)->field.tqe_prev != (elm)) \
+ panic("Bad link elm %p prev->next != elm", (elm)); \
+ } while (0)
+#else
+#define QMD_TAILQ_CHECK_HEAD(head, field)
+#define QMD_TAILQ_CHECK_TAIL(head, headname)
+#define QMD_TAILQ_CHECK_NEXT(elm, field)
+#define QMD_TAILQ_CHECK_PREV(elm, field)
+#endif /* (_KERNEL && INVARIANTS) */
+
+#define TAILQ_CONCAT(head1, head2, field) \
+ do { \
+ if (!TAILQ_EMPTY(head2)) { \
+ *(head1)->tqh_last = (head2)->tqh_first; \
+ (head2)->tqh_first->field.tqe_prev = \
+ (head1)->tqh_last; \
+ (head1)->tqh_last = (head2)->tqh_last; \
+ TAILQ_INIT((head2)); \
+ QMD_TRACE_HEAD(head1); \
+ QMD_TRACE_HEAD(head2); \
+ } \
+ } while (0)
+
+#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL)
+
+#define TAILQ_FIRST(head) ((head)->tqh_first)
+
+#define TAILQ_FOREACH(var, head, field) \
+ for ((var) = TAILQ_FIRST((head)); (var); \
+ (var) = TAILQ_NEXT((var), field))
+
+#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = TAILQ_FIRST((head)); \
+ (var) && ((tvar) = TAILQ_NEXT((var), field), 1); (var) = (tvar))
+
+#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \
+ for ((var) = TAILQ_LAST((head), headname); (var); \
+ (var) = TAILQ_PREV((var), headname, field))
+
+#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \
+ for ((var) = TAILQ_LAST((head), headname); \
+ (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \
+ (var) = (tvar))
+
+#define TAILQ_INIT(head) \
+ do { \
+ TAILQ_FIRST((head)) = NULL; \
+ (head)->tqh_last = &TAILQ_FIRST((head)); \
+ QMD_TRACE_HEAD(head); \
+ } while (0)
+
+#define TAILQ_INSERT_AFTER(head, listelm, elm, field) \
+ do { \
+ QMD_TAILQ_CHECK_NEXT(listelm, field); \
+ if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) \
+ != NULL) \
+ TAILQ_NEXT((elm), field)->field.tqe_prev = \
+ &TAILQ_NEXT((elm), field); \
+ else { \
+ (head)->tqh_last = &TAILQ_NEXT((elm), field); \
+ QMD_TRACE_HEAD(head); \
+ } \
+ TAILQ_NEXT((listelm), field) = (elm); \
+ (elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field); \
+ QMD_TRACE_ELEM(&(elm)->field); \
+ QMD_TRACE_ELEM(&listelm->field); \
+ } while (0)
+
+#define TAILQ_INSERT_BEFORE(listelm, elm, field) \
+ do { \
+ QMD_TAILQ_CHECK_PREV(listelm, field); \
+ (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \
+ TAILQ_NEXT((elm), field) = (listelm); \
+ *(listelm)->field.tqe_prev = (elm); \
+ (listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field); \
+ QMD_TRACE_ELEM(&(elm)->field); \
+ QMD_TRACE_ELEM(&listelm->field); \
+ } while (0)
+
+#define TAILQ_INSERT_HEAD(head, elm, field) \
+ do { \
+ QMD_TAILQ_CHECK_HEAD(head, field); \
+ if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL) \
+ TAILQ_FIRST((head))->field.tqe_prev = \
+ &TAILQ_NEXT((elm), field); \
+ else \
+ (head)->tqh_last = &TAILQ_NEXT((elm), field); \
+ TAILQ_FIRST((head)) = (elm); \
+ (elm)->field.tqe_prev = &TAILQ_FIRST((head)); \
+ QMD_TRACE_HEAD(head); \
+ QMD_TRACE_ELEM(&(elm)->field); \
+ } while (0)
+
+#define TAILQ_INSERT_TAIL(head, elm, field) \
+ do { \
+ QMD_TAILQ_CHECK_TAIL(head, field); \
+ TAILQ_NEXT((elm), field) = NULL; \
+ (elm)->field.tqe_prev = (head)->tqh_last; \
+ *(head)->tqh_last = (elm); \
+ (head)->tqh_last = &TAILQ_NEXT((elm), field); \
+ QMD_TRACE_HEAD(head); \
+ QMD_TRACE_ELEM(&(elm)->field); \
+ } while (0)
+
+#define TAILQ_LAST(head, headname) \
+ (*(((struct headname *)((head)->tqh_last))->tqh_last))
+
+#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
+
+#define TAILQ_PREV(elm, headname, field) \
+ (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
+
+#define TAILQ_REMOVE(head, elm, field) \
+ do { \
+ QMD_SAVELINK(oldnext, (elm)->field.tqe_next); \
+ QMD_SAVELINK(oldprev, (elm)->field.tqe_prev); \
+ QMD_TAILQ_CHECK_NEXT(elm, field); \
+ QMD_TAILQ_CHECK_PREV(elm, field); \
+ if ((TAILQ_NEXT((elm), field)) != NULL) \
+ TAILQ_NEXT((elm), field)->field.tqe_prev = \
+ (elm)->field.tqe_prev; \
+ else { \
+ (head)->tqh_last = (elm)->field.tqe_prev; \
+ QMD_TRACE_HEAD(head); \
+ } \
+ *(elm)->field.tqe_prev = TAILQ_NEXT((elm), field); \
+ TRASHIT(*oldnext); \
+ TRASHIT(*oldprev); \
+ QMD_TRACE_ELEM(&(elm)->field); \
+ } while (0)
+
+#define TAILQ_SWAP(head1, head2, type, field) \
+ do { \
+ struct type *swap_first = (head1)->tqh_first; \
+ struct type **swap_last = (head1)->tqh_last; \
+ (head1)->tqh_first = (head2)->tqh_first; \
+ (head1)->tqh_last = (head2)->tqh_last; \
+ (head2)->tqh_first = swap_first; \
+ (head2)->tqh_last = swap_last; \
+ if ((swap_first = (head1)->tqh_first) != NULL) \
+ swap_first->field.tqe_prev = &(head1)->tqh_first; \
+ else \
+ (head1)->tqh_last = &(head1)->tqh_first; \
+ if ((swap_first = (head2)->tqh_first) != NULL) \
+ swap_first->field.tqe_prev = &(head2)->tqh_first; \
+ else \
+ (head2)->tqh_last = &(head2)->tqh_first; \
+ } while (0)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !_SYS_QUEUE_H_ */
diff --git a/lib/frr_pthread.c b/lib/frr_pthread.c
new file mode 100644
index 0000000..dd675bb
--- /dev/null
+++ b/lib/frr_pthread.c
@@ -0,0 +1,319 @@
+/*
+ * Utilities and interfaces for managing POSIX threads within FRR.
+ * Copyright (C) 2017 Cumulus Networks, Inc.
+ *
+ * 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 <pthread.h>
+#ifdef HAVE_PTHREAD_NP_H
+#include <pthread_np.h>
+#endif
+#include <sched.h>
+
+#include "frr_pthread.h"
+#include "memory.h"
+#include "linklist.h"
+#include "zlog.h"
+#include "libfrr.h"
+#include "libfrr_trace.h"
+
+DEFINE_MTYPE_STATIC(LIB, FRR_PTHREAD, "FRR POSIX Thread");
+DEFINE_MTYPE_STATIC(LIB, PTHREAD_PRIM, "POSIX sync primitives");
+
+/* default frr_pthread start/stop routine prototypes */
+static void *fpt_run(void *arg);
+static int fpt_halt(struct frr_pthread *fpt, void **res);
+
+/* misc sigs */
+static void frr_pthread_destroy_nolock(struct frr_pthread *fpt);
+
+/* default frr_pthread attributes */
+const struct frr_pthread_attr frr_pthread_attr_default = {
+ .start = fpt_run,
+ .stop = fpt_halt,
+};
+
+/* list to keep track of all frr_pthreads */
+static pthread_mutex_t frr_pthread_list_mtx = PTHREAD_MUTEX_INITIALIZER;
+static struct list *frr_pthread_list;
+
+/* ------------------------------------------------------------------------ */
+
+void frr_pthread_init(void)
+{
+ frr_with_mutex (&frr_pthread_list_mtx) {
+ frr_pthread_list = list_new();
+ }
+}
+
+void frr_pthread_finish(void)
+{
+ frr_pthread_stop_all();
+
+ frr_with_mutex (&frr_pthread_list_mtx) {
+ struct listnode *n, *nn;
+ struct frr_pthread *fpt;
+
+ for (ALL_LIST_ELEMENTS(frr_pthread_list, n, nn, fpt)) {
+ listnode_delete(frr_pthread_list, fpt);
+ frr_pthread_destroy_nolock(fpt);
+ }
+
+ list_delete(&frr_pthread_list);
+ }
+}
+
+struct frr_pthread *frr_pthread_new(const struct frr_pthread_attr *attr,
+ const char *name, const char *os_name)
+{
+ struct frr_pthread *fpt = NULL;
+
+ attr = attr ? attr : &frr_pthread_attr_default;
+
+ fpt = XCALLOC(MTYPE_FRR_PTHREAD, sizeof(struct frr_pthread));
+ /* initialize mutex */
+ pthread_mutex_init(&fpt->mtx, NULL);
+ /* create new thread master */
+ fpt->master = thread_master_create(name);
+ /* set attributes */
+ fpt->attr = *attr;
+ name = (name ? name : "Anonymous thread");
+ fpt->name = XSTRDUP(MTYPE_FRR_PTHREAD, name);
+ if (os_name)
+ strlcpy(fpt->os_name, os_name, OS_THREAD_NAMELEN);
+ else
+ strlcpy(fpt->os_name, name, OS_THREAD_NAMELEN);
+ /* initialize startup synchronization primitives */
+ fpt->running_cond_mtx = XCALLOC(
+ MTYPE_PTHREAD_PRIM, sizeof(pthread_mutex_t));
+ fpt->running_cond = XCALLOC(MTYPE_PTHREAD_PRIM,
+ sizeof(pthread_cond_t));
+ pthread_mutex_init(fpt->running_cond_mtx, NULL);
+ pthread_cond_init(fpt->running_cond, NULL);
+
+ frr_with_mutex (&frr_pthread_list_mtx) {
+ listnode_add(frr_pthread_list, fpt);
+ }
+
+ return fpt;
+}
+
+static void frr_pthread_destroy_nolock(struct frr_pthread *fpt)
+{
+ thread_master_free(fpt->master);
+ pthread_mutex_destroy(&fpt->mtx);
+ pthread_mutex_destroy(fpt->running_cond_mtx);
+ pthread_cond_destroy(fpt->running_cond);
+ XFREE(MTYPE_FRR_PTHREAD, fpt->name);
+ XFREE(MTYPE_PTHREAD_PRIM, fpt->running_cond_mtx);
+ XFREE(MTYPE_PTHREAD_PRIM, fpt->running_cond);
+ XFREE(MTYPE_FRR_PTHREAD, fpt);
+}
+
+void frr_pthread_destroy(struct frr_pthread *fpt)
+{
+ frr_with_mutex (&frr_pthread_list_mtx) {
+ listnode_delete(frr_pthread_list, fpt);
+ }
+
+ frr_pthread_destroy_nolock(fpt);
+}
+
+int frr_pthread_set_name(struct frr_pthread *fpt)
+{
+ int ret = 0;
+
+#ifdef HAVE_PTHREAD_SETNAME_NP
+# ifdef GNU_LINUX
+ ret = pthread_setname_np(fpt->thread, fpt->os_name);
+# elif defined(__NetBSD__)
+ ret = pthread_setname_np(fpt->thread, fpt->os_name, NULL);
+# endif
+#elif defined(HAVE_PTHREAD_SET_NAME_NP)
+ pthread_set_name_np(fpt->thread, fpt->os_name);
+#endif
+
+ return ret;
+}
+
+static void *frr_pthread_inner(void *arg)
+{
+ struct frr_pthread *fpt = arg;
+
+ rcu_thread_start(fpt->rcu_thread);
+ return fpt->attr.start(fpt);
+}
+
+int frr_pthread_run(struct frr_pthread *fpt, const pthread_attr_t *attr)
+{
+ int ret;
+ sigset_t oldsigs, blocksigs;
+
+ assert(frr_is_after_fork || !"trying to start thread before fork()");
+
+ /* Ensure we never handle signals on a background thread by blocking
+ * everything here (new thread inherits signal mask)
+ */
+ sigfillset(&blocksigs);
+ pthread_sigmask(SIG_BLOCK, &blocksigs, &oldsigs);
+
+ frrtrace(1, frr_libfrr, frr_pthread_run, fpt->name);
+
+ fpt->rcu_thread = rcu_thread_prepare();
+ ret = pthread_create(&fpt->thread, attr, frr_pthread_inner, fpt);
+
+ /* Restore caller's signals */
+ pthread_sigmask(SIG_SETMASK, &oldsigs, NULL);
+
+ /*
+ * Per pthread_create(3), the contents of fpt->thread are undefined if
+ * pthread_create() did not succeed. Reset this value to zero.
+ */
+ if (ret < 0) {
+ rcu_thread_unprepare(fpt->rcu_thread);
+ memset(&fpt->thread, 0x00, sizeof(fpt->thread));
+ }
+
+ return ret;
+}
+
+void frr_pthread_wait_running(struct frr_pthread *fpt)
+{
+ frr_with_mutex (fpt->running_cond_mtx) {
+ while (!fpt->running)
+ pthread_cond_wait(fpt->running_cond,
+ fpt->running_cond_mtx);
+ }
+}
+
+void frr_pthread_notify_running(struct frr_pthread *fpt)
+{
+ frr_with_mutex (fpt->running_cond_mtx) {
+ fpt->running = true;
+ pthread_cond_signal(fpt->running_cond);
+ }
+}
+
+int frr_pthread_stop(struct frr_pthread *fpt, void **result)
+{
+ frrtrace(1, frr_libfrr, frr_pthread_stop, fpt->name);
+
+ int ret = (*fpt->attr.stop)(fpt, result);
+ memset(&fpt->thread, 0x00, sizeof(fpt->thread));
+ return ret;
+}
+
+void frr_pthread_stop_all(void)
+{
+ frr_with_mutex (&frr_pthread_list_mtx) {
+ struct listnode *n;
+ struct frr_pthread *fpt;
+ for (ALL_LIST_ELEMENTS_RO(frr_pthread_list, n, fpt)) {
+ if (atomic_load_explicit(&fpt->running,
+ memory_order_relaxed))
+ frr_pthread_stop(fpt, NULL);
+ }
+ }
+}
+
+/*
+ * ----------------------------------------------------------------------------
+ * Default Event Loop
+ * ----------------------------------------------------------------------------
+ */
+
+/* dummy task for sleeper pipe */
+static void fpt_dummy(struct thread *thread)
+{
+}
+
+/* poison pill task to end event loop */
+static void fpt_finish(struct thread *thread)
+{
+ struct frr_pthread *fpt = THREAD_ARG(thread);
+
+ atomic_store_explicit(&fpt->running, false, memory_order_relaxed);
+}
+
+/* stop function, called from other threads to halt this one */
+static int fpt_halt(struct frr_pthread *fpt, void **res)
+{
+ thread_add_event(fpt->master, &fpt_finish, fpt, 0, NULL);
+ pthread_join(fpt->thread, res);
+
+ return 0;
+}
+
+/*
+ * Entry pthread function & main event loop.
+ *
+ * Upon thread start the following actions occur:
+ *
+ * - frr_pthread's owner field is set to pthread ID.
+ * - All signals are blocked (except for unblockable signals).
+ * - Pthread's threadmaster is set to never handle pending signals
+ * - Poker pipe for poll() is created and queued as I/O source
+ * - The frr_pthread->running_cond condition variable is signalled to indicate
+ * that the previous actions have completed. It is not safe to assume any of
+ * the above have occurred before receiving this signal.
+ *
+ * After initialization is completed, the event loop begins running. Each tick,
+ * the following actions are performed before running the usual event system
+ * tick function:
+ *
+ * - Verify that the running boolean is set
+ * - Verify that there are no pending cancellation requests
+ * - Verify that there are tasks scheduled
+ *
+ * So long as the conditions are met, the event loop tick is run and the
+ * returned task is executed.
+ *
+ * If any of these conditions are not met, the event loop exits, closes the
+ * pipes and dies without running any cleanup functions.
+ */
+static void *fpt_run(void *arg)
+{
+ struct frr_pthread *fpt = arg;
+ fpt->master->owner = pthread_self();
+
+ zlog_tls_buffer_init();
+
+ int sleeper[2];
+ pipe(sleeper);
+ thread_add_read(fpt->master, &fpt_dummy, NULL, sleeper[0], NULL);
+
+ fpt->master->handle_signals = false;
+
+ frr_pthread_set_name(fpt);
+
+ frr_pthread_notify_running(fpt);
+
+ struct thread task;
+ while (atomic_load_explicit(&fpt->running, memory_order_relaxed)) {
+ pthread_testcancel();
+ if (thread_fetch(fpt->master, &task)) {
+ thread_call(&task);
+ }
+ }
+
+ close(sleeper[1]);
+ close(sleeper[0]);
+
+ zlog_tls_buffer_fini();
+
+ return NULL;
+}
diff --git a/lib/frr_pthread.h b/lib/frr_pthread.h
new file mode 100644
index 0000000..89519ab
--- /dev/null
+++ b/lib/frr_pthread.h
@@ -0,0 +1,270 @@
+/*
+ * Utilities and interfaces for managing POSIX threads within FRR.
+ * Copyright (C) 2017 Cumulus Networks, Inc.
+ *
+ * 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_PTHREAD_H
+#define _FRR_PTHREAD_H
+
+#include <pthread.h>
+#include "frratomic.h"
+#include "memory.h"
+#include "frrcu.h"
+#include "thread.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define OS_THREAD_NAMELEN 16
+
+struct frr_pthread;
+struct frr_pthread_attr;
+
+struct frr_pthread_attr {
+ void *(*start)(void *);
+ int (*stop)(struct frr_pthread *, void **);
+};
+
+struct frr_pthread {
+
+ /*
+ * Mutex protecting this structure. Must be taken for reading some
+ * fields, denoted by a 'Requires: mtx'.
+ */
+ pthread_mutex_t mtx;
+
+ /* pthread id */
+ pthread_t thread;
+
+ struct rcu_thread *rcu_thread;
+
+ /* thread master for this pthread's thread.c event loop */
+ struct thread_master *master;
+
+ /* caller-specified data; start & stop funcs, name, id */
+ struct frr_pthread_attr attr;
+
+ /*
+ * Notification mechanism for allowing pthreads to notify their parents
+ * when they are ready to do work. This mechanism has two associated
+ * functions:
+ *
+ * - frr_pthread_wait_running()
+ * This function should be called by the spawning thread after
+ * frr_pthread_run(). It safely waits until the spawned thread
+ * indicates that is ready to do work by posting to the condition
+ * variable.
+ *
+ * - frr_pthread_notify_running()
+ * This function should be called by the spawned thread when it is
+ * ready to do work. It will wake up any threads waiting on the
+ * previously described condition.
+ */
+ pthread_cond_t *running_cond;
+ pthread_mutex_t *running_cond_mtx;
+ atomic_bool running;
+
+ /*
+ * Fake thread-specific storage. No constraints on usage. Helpful when
+ * creating reentrant pthread implementations. Can be used to pass
+ * argument to pthread entry function.
+ *
+ * Requires: mtx
+ */
+ void *data;
+
+ /*
+ * Human-readable thread name.
+ *
+ * Requires: mtx
+ */
+ char *name;
+
+ /* Used in pthread_set_name max 16 characters */
+ char os_name[OS_THREAD_NAMELEN];
+};
+
+extern const struct frr_pthread_attr frr_pthread_attr_default;
+
+/*
+ * Initializes this module.
+ *
+ * Must be called before using any of the other functions.
+ */
+void frr_pthread_init(void);
+
+/*
+ * Uninitializes this module.
+ *
+ * Destroys all registered frr_pthread's and internal data structures.
+ *
+ * It is safe to call frr_pthread_init() after this function to reinitialize
+ * the module.
+ */
+void frr_pthread_finish(void);
+
+/*
+ * Creates a new frr_pthread with the given attributes.
+ *
+ * The 'attr' argument should be filled out with the desired attributes,
+ * including ID, start and stop functions and the desired name. Alternatively,
+ * if attr is NULL, the default attributes will be used. The pthread will be
+ * set up to run a basic threadmaster loop and the name will be "Anonymous".
+ * Scheduling tasks onto the threadmaster in the 'master' field of the returned
+ * frr_pthread will cause them to run on that pthread.
+ *
+ * @param attr - the thread attributes
+ * @param name - Human-readable name
+ * @param os_name - 16 characters (including '\0') thread name to set in os,
+ * @return the created frr_pthread upon success, or NULL upon failure
+ */
+struct frr_pthread *frr_pthread_new(const struct frr_pthread_attr *attr,
+ const char *name, const char *os_name);
+
+/*
+ * Changes the name of the frr_pthread as reported by the operating
+ * system.
+ *
+ * @param fpt - the frr_pthread to operate on
+ * @return - on success returns 0 otherwise nonzero error number.
+ */
+int frr_pthread_set_name(struct frr_pthread *fpt);
+
+/*
+ * Destroys an frr_pthread.
+ *
+ * Assumes that the associated pthread, if any, has already terminated.
+ *
+ * @param fpt - the frr_pthread to destroy
+ */
+void frr_pthread_destroy(struct frr_pthread *fpt);
+
+/*
+ * Creates a new pthread and binds it to a frr_pthread.
+ *
+ * This function is a wrapper for pthread_create. The first parameter is the
+ * frr_pthread to bind the created pthread to. All subsequent arguments are
+ * passed unmodified to pthread_create(). The frr_pthread * provided will be
+ * used as the argument to the pthread entry function. If it is necessary to
+ * pass additional data, the 'data' field in the frr_pthread may be used.
+ *
+ * This function returns the same code as pthread_create(). If the value is
+ * zero, the provided frr_pthread is bound to a running POSIX thread. If the
+ * value is less than zero, the provided frr_pthread is guaranteed to be a
+ * clean instance that may be susbsequently passed to frr_pthread_run().
+ *
+ * @param fpt - frr_pthread * to run
+ * @param attr - see pthread_create(3)
+ *
+ * @return see pthread_create(3)
+ */
+int frr_pthread_run(struct frr_pthread *fpt, const pthread_attr_t *attr);
+
+/*
+ * Waits until the specified pthread has finished setting up and is ready to
+ * begin work.
+ *
+ * If the pthread's code makes use of the startup synchronization mechanism,
+ * this function should be called before attempting to use the functionality
+ * exposed by the pthread. It waits until the 'running' condition is satisfied
+ * (see struct definition of frr_pthread).
+ *
+ * @param fpt - the frr_pthread * to wait on
+ */
+void frr_pthread_wait_running(struct frr_pthread *fpt);
+
+/*
+ * Notifies other pthreads that the calling thread has finished setting up and
+ * is ready to begin work.
+ *
+ * This will allow any other pthreads waiting in 'frr_pthread_wait_running' to
+ * proceed.
+ *
+ * @param fpt - the frr_pthread * that has finished setting up
+ */
+void frr_pthread_notify_running(struct frr_pthread *fpt);
+
+/*
+ * Stops a frr_pthread with a result.
+ *
+ * @param fpt - frr_pthread * to stop
+ * @param result - where to store the thread's result, if any. May be NULL if a
+ * result is not needed.
+ */
+int frr_pthread_stop(struct frr_pthread *fpt, void **result);
+
+/* Stops all frr_pthread's. */
+void frr_pthread_stop_all(void);
+
+#ifndef HAVE_PTHREAD_CONDATTR_SETCLOCK
+#define pthread_condattr_setclock(A, B)
+#endif
+
+/* mutex auto-lock/unlock */
+
+/* variant 1:
+ * (for short blocks, multiple mutexes supported)
+ * break & return can be used for aborting the block
+ *
+ * frr_with_mutex(&mtx, &mtx2) {
+ * if (error)
+ * break;
+ * ...
+ * }
+ */
+#define _frr_with_mutex(mutex) \
+ *NAMECTR(_mtx_) __attribute__(( \
+ unused, cleanup(_frr_mtx_unlock))) = _frr_mtx_lock(mutex), \
+ /* end */
+
+#define frr_with_mutex(...) \
+ for (pthread_mutex_t MACRO_REPEAT(_frr_with_mutex, ##__VA_ARGS__) \
+ *_once = NULL; _once == NULL; _once = (void *)1) \
+ /* end */
+
+/* variant 2:
+ * (more suitable for long blocks, no extra indentation)
+ *
+ * frr_mutex_lock_autounlock(&mtx);
+ * ...
+ */
+#define frr_mutex_lock_autounlock(mutex) \
+ pthread_mutex_t *NAMECTR(_mtx_) \
+ __attribute__((unused, cleanup(_frr_mtx_unlock))) = \
+ _frr_mtx_lock(mutex) \
+ /* end */
+
+static inline pthread_mutex_t *_frr_mtx_lock(pthread_mutex_t *mutex)
+{
+ pthread_mutex_lock(mutex);
+ return mutex;
+}
+
+static inline void _frr_mtx_unlock(pthread_mutex_t **mutex)
+{
+ if (!*mutex)
+ return;
+ pthread_mutex_unlock(*mutex);
+ *mutex = NULL;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRR_PTHREAD_H */
diff --git a/lib/frr_zmq.c b/lib/frr_zmq.c
new file mode 100644
index 0000000..db5c4c9
--- /dev/null
+++ b/lib/frr_zmq.c
@@ -0,0 +1,370 @@
+/*
+ * libzebra ZeroMQ bindings
+ * Copyright (C) 2015 David Lamparter
+ *
+ * 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
+ */
+
+/*
+ * IF YOU MODIFY THIS FILE PLEASE RUN `make check` and ensure that
+ * the test_zmq.c unit test is still working. There are dependencies
+ * between the two that are extremely fragile. My understanding
+ * is that there is specialized ownership of the cb pointer based
+ * upon what is happening. Those assumptions are supposed to be
+ * tested in the test_zmq.c
+ */
+#include <zebra.h>
+#include <zmq.h>
+
+#include "thread.h"
+#include "memory.h"
+#include "frr_zmq.h"
+#include "log.h"
+#include "lib_errors.h"
+
+DEFINE_MTYPE_STATIC(LIB, ZEROMQ_CB, "ZeroMQ callback");
+
+/* libzmq's context */
+void *frrzmq_context = NULL;
+static unsigned frrzmq_initcount = 0;
+
+void frrzmq_init(void)
+{
+ if (frrzmq_initcount++ == 0) {
+ frrzmq_context = zmq_ctx_new();
+ zmq_ctx_set(frrzmq_context, ZMQ_IPV6, 1);
+ }
+}
+
+void frrzmq_finish(void)
+{
+ if (--frrzmq_initcount == 0) {
+ zmq_ctx_term(frrzmq_context);
+ frrzmq_context = NULL;
+ }
+}
+
+static void frrzmq_read_msg(struct thread *t)
+{
+ struct frrzmq_cb **cbp = THREAD_ARG(t);
+ struct frrzmq_cb *cb;
+ zmq_msg_t msg;
+ unsigned partno;
+ unsigned char read = 0;
+ int ret, more;
+ size_t moresz;
+
+ if (!cbp)
+ return;
+ cb = (*cbp);
+ if (!cb || !cb->zmqsock)
+ return;
+
+ while (1) {
+ zmq_pollitem_t polli = {.socket = cb->zmqsock,
+ .events = ZMQ_POLLIN};
+ ret = zmq_poll(&polli, 1, 0);
+
+ if (ret < 0)
+ goto out_err;
+
+ if (!(polli.revents & ZMQ_POLLIN))
+ break;
+
+ if (cb->read.cb_msg) {
+ cb->in_cb = true;
+ cb->read.cb_msg(cb->read.arg, cb->zmqsock);
+ cb->in_cb = false;
+
+ read = 1;
+
+ if (cb->read.cancelled) {
+ frrzmq_check_events(cbp, &cb->write,
+ ZMQ_POLLOUT);
+ cb->read.thread = NULL;
+ if (cb->write.cancelled && !cb->write.thread)
+ XFREE(MTYPE_ZEROMQ_CB, *cbp);
+
+ return;
+ }
+ continue;
+ }
+
+ partno = 0;
+ if (zmq_msg_init(&msg))
+ goto out_err;
+ do {
+ ret = zmq_msg_recv(&msg, cb->zmqsock, ZMQ_NOBLOCK);
+ if (ret < 0) {
+ if (errno == EAGAIN)
+ break;
+
+ zmq_msg_close(&msg);
+ goto out_err;
+ }
+ read = 1;
+
+ cb->in_cb = true;
+ cb->read.cb_part(cb->read.arg, cb->zmqsock, &msg,
+ partno);
+ cb->in_cb = false;
+
+ if (cb->read.cancelled) {
+ zmq_msg_close(&msg);
+ frrzmq_check_events(cbp, &cb->write,
+ ZMQ_POLLOUT);
+ cb->read.thread = NULL;
+ if (cb->write.cancelled && !cb->write.thread)
+ XFREE(MTYPE_ZEROMQ_CB, *cbp);
+
+ return;
+ }
+
+ /* cb_part may have read additional parts of the
+ * message; don't use zmq_msg_more here */
+ moresz = sizeof(more);
+ more = 0;
+ ret = zmq_getsockopt(cb->zmqsock, ZMQ_RCVMORE, &more,
+ &moresz);
+ if (ret < 0) {
+ zmq_msg_close(&msg);
+ goto out_err;
+ }
+
+ partno++;
+ } while (more);
+ zmq_msg_close(&msg);
+ }
+
+ if (read)
+ frrzmq_check_events(cbp, &cb->write, ZMQ_POLLOUT);
+
+ thread_add_read(t->master, frrzmq_read_msg, cbp,
+ cb->fd, &cb->read.thread);
+ return;
+
+out_err:
+ flog_err(EC_LIB_ZMQ, "ZeroMQ read error: %s(%d)", strerror(errno),
+ errno);
+ if (cb->read.cb_error)
+ cb->read.cb_error(cb->read.arg, cb->zmqsock);
+}
+
+int _frrzmq_thread_add_read(const struct xref_threadsched *xref,
+ struct thread_master *master,
+ void (*msgfunc)(void *arg, void *zmqsock),
+ void (*partfunc)(void *arg, void *zmqsock,
+ zmq_msg_t *msg, unsigned partnum),
+ void (*errfunc)(void *arg, void *zmqsock),
+ void *arg, void *zmqsock,
+ struct frrzmq_cb **cbp)
+{
+ int fd, events;
+ size_t len;
+ struct frrzmq_cb *cb;
+
+ if (!cbp)
+ return -1;
+ if (!(msgfunc || partfunc) || (msgfunc && partfunc))
+ return -1;
+ len = sizeof(fd);
+ if (zmq_getsockopt(zmqsock, ZMQ_FD, &fd, &len))
+ return -1;
+ len = sizeof(events);
+ if (zmq_getsockopt(zmqsock, ZMQ_EVENTS, &events, &len))
+ return -1;
+
+ if (*cbp)
+ cb = *cbp;
+ else {
+ cb = XCALLOC(MTYPE_ZEROMQ_CB, sizeof(struct frrzmq_cb));
+ cb->write.cancelled = true;
+ *cbp = cb;
+ }
+
+ cb->zmqsock = zmqsock;
+ cb->fd = fd;
+ cb->read.arg = arg;
+ cb->read.cb_msg = msgfunc;
+ cb->read.cb_part = partfunc;
+ cb->read.cb_error = errfunc;
+ cb->read.cancelled = false;
+ cb->in_cb = false;
+
+ if (events & ZMQ_POLLIN) {
+ thread_cancel(&cb->read.thread);
+
+ thread_add_event(master, frrzmq_read_msg, cbp, fd,
+ &cb->read.thread);
+ } else
+ thread_add_read(master, frrzmq_read_msg, cbp, fd,
+ &cb->read.thread);
+ return 0;
+}
+
+static void frrzmq_write_msg(struct thread *t)
+{
+ struct frrzmq_cb **cbp = THREAD_ARG(t);
+ struct frrzmq_cb *cb;
+ unsigned char written = 0;
+ int ret;
+
+ if (!cbp)
+ return;
+ cb = (*cbp);
+ if (!cb || !cb->zmqsock)
+ return;
+
+ while (1) {
+ zmq_pollitem_t polli = {.socket = cb->zmqsock,
+ .events = ZMQ_POLLOUT};
+ ret = zmq_poll(&polli, 1, 0);
+
+ if (ret < 0)
+ goto out_err;
+
+ if (!(polli.revents & ZMQ_POLLOUT))
+ break;
+
+ if (cb->write.cb_msg) {
+ cb->in_cb = true;
+ cb->write.cb_msg(cb->write.arg, cb->zmqsock);
+ cb->in_cb = false;
+
+ written = 1;
+
+ if (cb->write.cancelled) {
+ frrzmq_check_events(cbp, &cb->read, ZMQ_POLLIN);
+ cb->write.thread = NULL;
+ if (cb->read.cancelled && !cb->read.thread)
+ XFREE(MTYPE_ZEROMQ_CB, *cbp);
+
+ return;
+ }
+ continue;
+ }
+ }
+
+ if (written)
+ frrzmq_check_events(cbp, &cb->read, ZMQ_POLLIN);
+
+ thread_add_write(t->master, frrzmq_write_msg, cbp,
+ cb->fd, &cb->write.thread);
+ return;
+
+out_err:
+ flog_err(EC_LIB_ZMQ, "ZeroMQ write error: %s(%d)", strerror(errno),
+ errno);
+ if (cb->write.cb_error)
+ cb->write.cb_error(cb->write.arg, cb->zmqsock);
+}
+
+int _frrzmq_thread_add_write(const struct xref_threadsched *xref,
+ struct thread_master *master,
+ void (*msgfunc)(void *arg, void *zmqsock),
+ void (*errfunc)(void *arg, void *zmqsock),
+ void *arg, void *zmqsock, struct frrzmq_cb **cbp)
+{
+ int fd, events;
+ size_t len;
+ struct frrzmq_cb *cb;
+
+ if (!cbp)
+ return -1;
+ if (!msgfunc)
+ return -1;
+ len = sizeof(fd);
+ if (zmq_getsockopt(zmqsock, ZMQ_FD, &fd, &len))
+ return -1;
+ len = sizeof(events);
+ if (zmq_getsockopt(zmqsock, ZMQ_EVENTS, &events, &len))
+ return -1;
+
+ if (*cbp)
+ cb = *cbp;
+ else {
+ cb = XCALLOC(MTYPE_ZEROMQ_CB, sizeof(struct frrzmq_cb));
+ cb->read.cancelled = true;
+ *cbp = cb;
+ }
+
+ cb->zmqsock = zmqsock;
+ cb->fd = fd;
+ cb->write.arg = arg;
+ cb->write.cb_msg = msgfunc;
+ cb->write.cb_part = NULL;
+ cb->write.cb_error = errfunc;
+ cb->write.cancelled = false;
+ cb->in_cb = false;
+
+ if (events & ZMQ_POLLOUT) {
+ thread_cancel(&cb->write.thread);
+
+ _thread_add_event(xref, master, frrzmq_write_msg, cbp, fd,
+ &cb->write.thread);
+ } else
+ thread_add_write(master, frrzmq_write_msg, cbp, fd,
+ &cb->write.thread);
+ return 0;
+}
+
+void frrzmq_thread_cancel(struct frrzmq_cb **cb, struct cb_core *core)
+{
+ if (!cb || !*cb)
+ return;
+ core->cancelled = true;
+ thread_cancel(&core->thread);
+
+ /* If cancelled from within a callback, don't try to free memory
+ * in this path.
+ */
+ if ((*cb)->in_cb)
+ return;
+
+ /* Ok to free the callback context if no more ... context. */
+ if ((*cb)->read.cancelled && !(*cb)->read.thread
+ && (*cb)->write.cancelled && ((*cb)->write.thread == NULL))
+ XFREE(MTYPE_ZEROMQ_CB, *cb);
+}
+
+void frrzmq_check_events(struct frrzmq_cb **cbp, struct cb_core *core,
+ int event)
+{
+ struct frrzmq_cb *cb;
+ int events;
+ size_t len;
+
+ if (!cbp)
+ return;
+ cb = (*cbp);
+ if (!cb || !cb->zmqsock)
+ return;
+
+ len = sizeof(events);
+ if (zmq_getsockopt(cb->zmqsock, ZMQ_EVENTS, &events, &len))
+ return;
+ if ((events & event) && core->thread && !core->cancelled) {
+ struct thread_master *tm = core->thread->master;
+
+ thread_cancel(&core->thread);
+
+ if (event == ZMQ_POLLIN)
+ thread_add_event(tm, frrzmq_read_msg,
+ cbp, cb->fd, &core->thread);
+ else
+ thread_add_event(tm, frrzmq_write_msg,
+ cbp, cb->fd, &core->thread);
+ }
+}
diff --git a/lib/frr_zmq.h b/lib/frr_zmq.h
new file mode 100644
index 0000000..b3be78c
--- /dev/null
+++ b/lib/frr_zmq.h
@@ -0,0 +1,154 @@
+/*
+ * libzebra ZeroMQ bindings
+ * Copyright (C) 2015 David Lamparter
+ *
+ * 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 _FRRZMQ_H
+#define _FRRZMQ_H
+
+#include "thread.h"
+#include <zmq.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* linking/packaging note: this is a separate library that needs to be
+ * linked into any daemon/library/module that wishes to use its
+ * functionality. The purpose of this is to encapsulate the libzmq
+ * dependency and not make libfrr/FRR itself depend on libzmq.
+ *
+ * libfrrzmq should be put in LDFLAGS/LIBADD *before* either libfrr or
+ * libzmq, and both of these should always be listed, e.g.
+ * foo_LDFLAGS = libfrrzmq.la libfrr.la $(ZEROMQ_LIBS)
+ */
+
+/* callback integration */
+struct cb_core {
+ struct thread *thread;
+ void *arg;
+
+ bool cancelled;
+
+ void (*cb_msg)(void *arg, void *zmqsock);
+ void (*cb_part)(void *arg, void *zmqsock, zmq_msg_t *msg,
+ unsigned partnum);
+ void (*cb_error)(void *arg, void *zmqsock);
+};
+
+struct frrzmq_cb {
+ void *zmqsock;
+ int fd;
+
+ bool in_cb; /* This context is in a read or write callback. */
+
+ struct cb_core read;
+ struct cb_core write;
+};
+
+/* libzmq's context
+ *
+ * this is mostly here as a convenience, it has IPv6 enabled but nothing
+ * else is tied to it; you can use a separate context without problems
+ */
+extern void *frrzmq_context;
+
+extern void frrzmq_init(void);
+extern void frrzmq_finish(void);
+
+#define _xref_zmq_a(type, f, d, call) \
+ ({ \
+ static const struct xref_threadsched _xref \
+ __attribute__((used)) = { \
+ .xref = XREF_INIT(XREFT_THREADSCHED, NULL, __func__), \
+ .funcname = #f, \
+ .dest = #d, \
+ .thread_type = THREAD_ ## type, \
+ }; \
+ XREF_LINK(_xref.xref); \
+ call; \
+ }) \
+ /* end */
+
+/* core event registration, one of these 2 macros should be used */
+#define frrzmq_thread_add_read_msg(m, f, e, a, z, d) \
+ _xref_zmq_a(READ, f, d, \
+ _frrzmq_thread_add_read(&_xref, m, f, NULL, e, a, z, d))
+
+#define frrzmq_thread_add_read_part(m, f, e, a, z, d) \
+ _xref_zmq_a(READ, f, d, \
+ _frrzmq_thread_add_read(&_xref, m, NULL, f, e, a, z, d))
+
+#define frrzmq_thread_add_write_msg(m, f, e, a, z, d) \
+ _xref_zmq_a(WRITE, f, d, \
+ _frrzmq_thread_add_write(&_xref, m, f, e, a, z, d))
+
+struct cb_core;
+struct frrzmq_cb;
+
+/* Set up a POLLIN or POLLOUT notification to be called from the libfrr main
+ * loop. This has the following properties:
+ *
+ * - since ZeroMQ works with edge triggered notifications, it will loop and
+ * dispatch as many events as ZeroMQ has pending at the time libfrr calls
+ * into this code
+ * - due to this looping (which means it non-single-issue), the callback is
+ * also persistent. Do _NOT_ re-register the event inside of your
+ * callback function.
+ * - either msgfunc or partfunc will be called (only one can be specified)
+ * - msgfunc is called once for each incoming message
+ * - if partfunc is specified, the message is read and partfunc is called
+ * for each ZeroMQ multi-part subpart. Note that you can't send replies
+ * before all parts have been read because that violates the ZeroMQ FSM.
+ * - write version doesn't allow for partial callback, you must handle the
+ * whole message (all parts) in msgfunc callback
+ * - you can safely cancel the callback from within itself
+ * - installing a callback will check for pending events (ZMQ_EVENTS) and
+ * may schedule the event to run as soon as libfrr is back in its main
+ * loop.
+ */
+extern int _frrzmq_thread_add_read(
+ const struct xref_threadsched *xref, struct thread_master *master,
+ void (*msgfunc)(void *arg, void *zmqsock),
+ void (*partfunc)(void *arg, void *zmqsock, zmq_msg_t *msg,
+ unsigned partnum),
+ void (*errfunc)(void *arg, void *zmqsock), void *arg, void *zmqsock,
+ struct frrzmq_cb **cb);
+extern int _frrzmq_thread_add_write(
+ const struct xref_threadsched *xref, struct thread_master *master,
+ void (*msgfunc)(void *arg, void *zmqsock),
+ void (*errfunc)(void *arg, void *zmqsock), void *arg, void *zmqsock,
+ struct frrzmq_cb **cb);
+
+extern void frrzmq_thread_cancel(struct frrzmq_cb **cb, struct cb_core *core);
+
+/*
+ * http://api.zeromq.org/4-2:zmq-getsockopt#toc10
+ *
+ * As the descriptor is edge triggered, applications must update the state of
+ * ZMQ_EVENTS after each invocation of zmq_send or zmq_recv.To be more explicit:
+ * after calling zmq_send the socket may become readable (and vice versa)
+ * without triggering a read event on the file descriptor.
+ */
+extern void frrzmq_check_events(struct frrzmq_cb **cbp, struct cb_core *core,
+ int event);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRRZMQ_H */
diff --git a/lib/frratomic.h b/lib/frratomic.h
new file mode 100644
index 0000000..bafc644
--- /dev/null
+++ b/lib/frratomic.h
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 2015-16 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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 _FRRATOMIC_H
+#define _FRRATOMIC_H
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifndef FRR_AUTOCONF_ATOMIC
+#error autoconf checks for atomic functions were not properly run
+#endif
+
+/* ISO C11 */
+#ifdef __cplusplus
+#include <stdint.h>
+#include <atomic>
+using std::atomic_int;
+using std::memory_order;
+using std::memory_order_relaxed;
+using std::memory_order_acquire;
+using std::memory_order_release;
+using std::memory_order_acq_rel;
+using std::memory_order_consume;
+using std::memory_order_seq_cst;
+
+typedef std::atomic<bool> atomic_bool;
+typedef std::atomic<size_t> atomic_size_t;
+typedef std::atomic<uint_fast32_t> atomic_uint_fast32_t;
+typedef std::atomic<uintptr_t> atomic_uintptr_t;
+
+#elif defined(HAVE_STDATOMIC_H)
+#include <stdatomic.h>
+
+/* These are available in gcc, but not in stdatomic */
+#define atomic_add_fetch_explicit __atomic_add_fetch
+#define atomic_sub_fetch_explicit __atomic_sub_fetch
+#define atomic_and_fetch_explicit __atomic_and_fetch
+#define atomic_or_fetch_explicit __atomic_or_fetch
+
+/* gcc 4.7 and newer */
+#elif defined(HAVE___ATOMIC)
+
+#define _Atomic volatile
+#define _ATOMIC_WANT_TYPEDEFS
+
+#define memory_order_relaxed __ATOMIC_RELAXED
+#define memory_order_consume __ATOMIC_CONSUME
+#define memory_order_acquire __ATOMIC_ACQUIRE
+#define memory_order_release __ATOMIC_RELEASE
+#define memory_order_acq_rel __ATOMIC_ACQ_REL
+#define memory_order_seq_cst __ATOMIC_SEQ_CST
+
+#define atomic_load_explicit __atomic_load_n
+#define atomic_store_explicit __atomic_store_n
+#define atomic_exchange_explicit __atomic_exchange_n
+#define atomic_fetch_add_explicit __atomic_fetch_add
+#define atomic_fetch_sub_explicit __atomic_fetch_sub
+#define atomic_fetch_and_explicit __atomic_fetch_and
+#define atomic_fetch_or_explicit __atomic_fetch_or
+
+#define atomic_add_fetch_explicit __atomic_add_fetch
+#define atomic_sub_fetch_explicit __atomic_sub_fetch
+#define atomic_and_fetch_explicit __atomic_and_fetch
+#define atomic_or_fetch_explicit __atomic_or_fetch
+
+#define atomic_compare_exchange_weak_explicit(atom, expect, desire, mem1, \
+ mem2) \
+ __atomic_compare_exchange_n(atom, expect, desire, 1, mem1, mem2)
+#define atomic_compare_exchange_strong_explicit(atom, expect, desire, mem1, \
+ mem2) \
+ __atomic_compare_exchange_n(atom, expect, desire, 0, mem1, mem2)
+
+/* gcc 4.1 and newer,
+ * clang 3.3 (possibly older)
+ *
+ * __sync_swap isn't in gcc's documentation, but clang has it
+ *
+ * note __sync_synchronize()
+ */
+#elif defined(HAVE___SYNC)
+
+#define _Atomic volatile
+#define _ATOMIC_WANT_TYPEDEFS
+
+#define memory_order_relaxed 0
+#define memory_order_consume 0
+#define memory_order_acquire 0
+#define memory_order_release 0
+#define memory_order_acq_rel 0
+#define memory_order_seq_cst 0
+
+#define atomic_load_explicit(ptr, mem) \
+ ({ \
+ __sync_synchronize(); \
+ typeof(*ptr) rval = __sync_fetch_and_add((ptr), 0); \
+ __sync_synchronize(); \
+ rval; \
+ })
+#define atomic_store_explicit(ptr, val, mem) \
+ ({ \
+ __sync_synchronize(); \
+ *(ptr) = (val); \
+ __sync_synchronize(); \
+ (void)0; \
+ })
+#ifdef HAVE___SYNC_SWAP
+#define atomic_exchange_explicit(ptr, val, mem) \
+ ({ \
+ __sync_synchronize(); \
+ typeof(*ptr) rval = __sync_swap((ptr, val), 0); \
+ __sync_synchronize(); \
+ rval; \
+ })
+#else /* !HAVE___SYNC_SWAP */
+#define atomic_exchange_explicit(ptr, val, mem) \
+ ({ \
+ typeof(ptr) _ptr = (ptr); \
+ typeof(val) _val = (val); \
+ __sync_synchronize(); \
+ typeof(*ptr) old1, old2 = __sync_fetch_and_add(_ptr, 0); \
+ do { \
+ old1 = old2; \
+ old2 = __sync_val_compare_and_swap(_ptr, old1, _val); \
+ } while (old1 != old2); \
+ __sync_synchronize(); \
+ old2; \
+ })
+#endif /* !HAVE___SYNC_SWAP */
+#define atomic_fetch_add_explicit(ptr, val, mem) \
+ ({ \
+ __sync_synchronize(); \
+ typeof(*ptr) rval = __sync_fetch_and_add((ptr), (val)); \
+ __sync_synchronize(); \
+ rval; \
+ })
+#define atomic_fetch_sub_explicit(ptr, val, mem) \
+ ({ \
+ __sync_synchronize(); \
+ typeof(*ptr) rval = __sync_fetch_and_sub((ptr), (val)); \
+ __sync_synchronize(); \
+ rval; \
+ })
+
+#define atomic_compare_exchange_strong_explicit(atom, expect, desire, mem1, \
+ mem2) \
+ ({ \
+ typeof(atom) _atom = (atom); \
+ typeof(expect) _expect = (expect); \
+ typeof(desire) _desire = (desire); \
+ __sync_synchronize(); \
+ typeof(*atom) rval = \
+ __sync_val_compare_and_swap(_atom, *_expect, _desire); \
+ __sync_synchronize(); \
+ bool ret = (rval == *_expect); \
+ *_expect = rval; \
+ ret; \
+ })
+#define atomic_compare_exchange_weak_explicit \
+ atomic_compare_exchange_strong_explicit
+
+#define atomic_fetch_and_explicit(ptr, val, mem) \
+ ({ \
+ __sync_synchronize(); \
+ typeof(*ptr) rval = __sync_fetch_and_and(ptr, val); \
+ __sync_synchronize(); \
+ rval; \
+ })
+#define atomic_fetch_or_explicit(ptr, val, mem) \
+ ({ \
+ __sync_synchronize(); \
+ typeof(*ptr) rval = __sync_fetch_and_or(ptr, val); \
+ __sync_synchronize(); \
+ rval; \
+ })
+
+#define atomic_add_fetch_explicit(ptr, val, mem) \
+ ({ \
+ __sync_synchronize(); \
+ typeof(*ptr) rval = __sync_add_and_fetch((ptr), (val)); \
+ __sync_synchronize(); \
+ rval; \
+ })
+#define atomic_sub_fetch_explicit(ptr, val, mem) \
+ ({ \
+ __sync_synchronize(); \
+ typeof(*ptr) rval = __sync_sub_and_fetch((ptr), (val)); \
+ __sync_synchronize(); \
+ rval; \
+ })
+
+#define atomic_and_fetch_explicit(ptr, val, mem) \
+ ({ \
+ __sync_synchronize(); \
+ typeof(*ptr) rval = __sync_and_and_fetch(ptr, val); \
+ __sync_synchronize(); \
+ rval; \
+ })
+#define atomic_or_fetch_explicit(ptr, val, mem) \
+ ({ \
+ __sync_synchronize(); \
+ typeof(*ptr) rval = __sync_or_and_fetch(ptr, val); \
+ __sync_synchronize(); \
+ rval; \
+ })
+
+#else /* !HAVE___ATOMIC && !HAVE_STDATOMIC_H */
+#error no atomic functions...
+#endif
+
+#ifdef _ATOMIC_WANT_TYPEDEFS
+#undef _ATOMIC_WANT_TYPEDEFS
+
+#include <stdint.h>
+#include <stdbool.h>
+
+typedef _Atomic bool atomic_bool;
+typedef _Atomic size_t atomic_size_t;
+typedef _Atomic uint_fast32_t atomic_uint_fast32_t;
+typedef _Atomic uintptr_t atomic_uintptr_t;
+#endif
+
+#endif /* _FRRATOMIC_H */
diff --git a/lib/frrcu.c b/lib/frrcu.c
new file mode 100644
index 0000000..0e717a9
--- /dev/null
+++ b/lib/frrcu.c
@@ -0,0 +1,527 @@
+/*
+ * Copyright (c) 2017-19 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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.
+ */
+
+/* implementation notes: this is an epoch-based RCU implementation. rcu_seq
+ * (global variable) counts the current epoch. Threads hold a specific epoch
+ * in rcu_read_lock(). This is the oldest epoch a thread might be accessing
+ * data from.
+ *
+ * The rcu_seq global is only pushed forward on rcu_read_lock() and
+ * rcu_read_unlock() calls. This makes things a tad more efficient since
+ * those are the only places it matters:
+ * - on rcu_read_lock, we don't want to hold an old epoch pointlessly
+ * - on rcu_read_unlock, we want to make sure we're not stuck on an old epoch
+ * when heading into a long idle period where no thread holds RCU
+ *
+ * rcu_thread structures themselves are RCU-free'd.
+ *
+ * rcu_head structures are the most iffy; normally for an ATOMLIST we would
+ * need to make sure we use rcu_free or pthread_rwlock to deallocate old items
+ * to prevent ABA or use-after-free problems. However, our ATOMLIST code
+ * guarantees that if the list remains non-empty in all cases, we only need
+ * the "last" pointer to do an "add_tail()", i.e. we can't run into ABA/UAF
+ * issues - but we do need to keep at least 1 item on the list.
+ *
+ * (Search the atomlist code for all uses of "last")
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <pthread.h>
+#ifdef HAVE_PTHREAD_NP_H
+#include <pthread_np.h>
+#endif
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+
+#include "frrcu.h"
+#include "seqlock.h"
+#include "atomlist.h"
+
+DEFINE_MTYPE_STATIC(LIB, RCU_THREAD, "RCU thread");
+DEFINE_MTYPE_STATIC(LIB, RCU_NEXT, "RCU sequence barrier");
+
+DECLARE_ATOMLIST(rcu_heads, struct rcu_head, head);
+
+PREDECL_ATOMLIST(rcu_threads);
+struct rcu_thread {
+ struct rcu_threads_item head;
+
+ struct rcu_head rcu_head;
+
+ struct seqlock rcu;
+
+ /* only accessed by thread itself, not atomic */
+ unsigned depth;
+};
+DECLARE_ATOMLIST(rcu_threads, struct rcu_thread, head);
+
+static const struct rcu_action rcua_next = { .type = RCUA_NEXT };
+static const struct rcu_action rcua_end = { .type = RCUA_END };
+static const struct rcu_action rcua_close = { .type = RCUA_CLOSE };
+
+struct rcu_next {
+ struct rcu_head head_free;
+ struct rcu_head head_next;
+};
+
+#define rcu_free_internal(mtype, ptr, field) \
+ do { \
+ typeof(ptr) _ptr = (ptr); \
+ struct rcu_head *_rcu_head = &_ptr->field; \
+ static const struct rcu_action _rcu_action = { \
+ .type = RCUA_FREE, \
+ .u.free = { \
+ .mt = mtype, \
+ .offset = offsetof(typeof(*_ptr), field), \
+ }, \
+ }; \
+ _rcu_head->action = &_rcu_action; \
+ rcu_heads_add_tail(&rcu_heads, _rcu_head); \
+ } while (0)
+
+/* primary global RCU position */
+static struct seqlock rcu_seq;
+/* this is set to rcu_seq whenever something is added on the RCU queue.
+ * rcu_read_lock() and rcu_read_unlock() will then bump rcu_seq up one step.
+ */
+static _Atomic seqlock_val_t rcu_dirty;
+
+static struct rcu_threads_head rcu_threads;
+static struct rcu_heads_head rcu_heads;
+
+/* main thread & RCU sweeper have pre-setup rcu_thread structures. The
+ * reasons are different:
+ *
+ * - rcu_thread_main is there because the main thread isn't started like
+ * other threads, it's implicitly created when the program is started. So
+ * rcu_thread_main matches up implicitly.
+ *
+ * - rcu_thread_rcu isn't actually put on the rcu_threads list (makes no
+ * sense really), it only exists so we can call RCU-using functions from
+ * the RCU thread without special handling in rcu_read_lock/unlock.
+ */
+static struct rcu_thread rcu_thread_main;
+static struct rcu_thread rcu_thread_rcu;
+
+static pthread_t rcu_pthread;
+static pthread_key_t rcu_thread_key;
+static bool rcu_active;
+
+static void rcu_start(void);
+static void rcu_bump(void);
+
+/*
+ * preinitialization for main thread
+ */
+static void rcu_thread_end(void *rcu_thread);
+
+static void rcu_preinit(void) __attribute__((constructor));
+static void rcu_preinit(void)
+{
+ struct rcu_thread *rt;
+
+ rt = &rcu_thread_main;
+ rt->depth = 1;
+ seqlock_init(&rt->rcu);
+ seqlock_acquire_val(&rt->rcu, SEQLOCK_STARTVAL);
+
+ pthread_key_create(&rcu_thread_key, rcu_thread_end);
+ pthread_setspecific(rcu_thread_key, rt);
+
+ rcu_threads_add_tail(&rcu_threads, rt);
+
+ /* RCU sweeper's rcu_thread is a dummy, NOT added to rcu_threads */
+ rt = &rcu_thread_rcu;
+ rt->depth = 1;
+
+ seqlock_init(&rcu_seq);
+ seqlock_acquire_val(&rcu_seq, SEQLOCK_STARTVAL);
+}
+
+static struct rcu_thread *rcu_self(void)
+{
+ return (struct rcu_thread *)pthread_getspecific(rcu_thread_key);
+}
+
+/*
+ * thread management (for the non-main thread)
+ */
+struct rcu_thread *rcu_thread_prepare(void)
+{
+ struct rcu_thread *rt, *cur;
+
+ rcu_assert_read_locked();
+
+ if (!rcu_active)
+ rcu_start();
+
+ cur = rcu_self();
+ assert(cur->depth);
+
+ /* new thread always starts with rcu_read_lock held at depth 1, and
+ * holding the same epoch as the parent (this makes it possible to
+ * use RCU for things passed into the thread through its arg)
+ */
+ rt = XCALLOC(MTYPE_RCU_THREAD, sizeof(*rt));
+ rt->depth = 1;
+
+ seqlock_init(&rt->rcu);
+ seqlock_acquire(&rt->rcu, &cur->rcu);
+
+ rcu_threads_add_tail(&rcu_threads, rt);
+
+ return rt;
+}
+
+void rcu_thread_start(struct rcu_thread *rt)
+{
+ pthread_setspecific(rcu_thread_key, rt);
+}
+
+void rcu_thread_unprepare(struct rcu_thread *rt)
+{
+ if (rt == &rcu_thread_rcu)
+ return;
+
+ rt->depth = 1;
+ seqlock_acquire(&rt->rcu, &rcu_seq);
+
+ rcu_bump();
+ if (rt != &rcu_thread_main)
+ /* this free() happens after seqlock_release() below */
+ rcu_free_internal(MTYPE_RCU_THREAD, rt, rcu_head);
+
+ rcu_threads_del(&rcu_threads, rt);
+ seqlock_release(&rt->rcu);
+}
+
+static void rcu_thread_end(void *rtvoid)
+{
+ struct rcu_thread *rt = rtvoid;
+ rcu_thread_unprepare(rt);
+}
+
+/*
+ * main RCU control aspects
+ */
+
+static void rcu_bump(void)
+{
+ struct rcu_next *rn;
+
+ rn = XMALLOC(MTYPE_RCU_NEXT, sizeof(*rn));
+
+ /* note: each RCUA_NEXT item corresponds to exactly one seqno bump.
+ * This means we don't need to communicate which seqno is which
+ * RCUA_NEXT, since we really don't care.
+ */
+
+ /*
+ * Important race condition: while rcu_heads_add_tail is executing,
+ * there is an intermediate point where the rcu_heads "last" pointer
+ * already points to rn->head_next, but rn->head_next isn't added to
+ * the list yet. That means any other "add_tail" calls append to this
+ * item, which isn't fully on the list yet. Freeze this thread at
+ * that point and look at another thread doing a rcu_bump. It adds
+ * these two items and then does a seqlock_bump. But the rcu_heads
+ * list is still "interrupted" and there's no RCUA_NEXT on the list
+ * yet (from either the frozen thread or the second thread). So
+ * rcu_main() might actually hit the end of the list at the
+ * "interrupt".
+ *
+ * This situation is prevented by requiring that rcu_read_lock is held
+ * for any calls to rcu_bump, since if we're holding the current RCU
+ * epoch, that means rcu_main can't be chewing on rcu_heads and hit
+ * that interruption point. Only by the time the thread has continued
+ * to rcu_read_unlock() - and therefore completed the add_tail - the
+ * RCU sweeper gobbles up the epoch and can be sure to find at least
+ * the RCUA_NEXT and RCUA_FREE items on rcu_heads.
+ */
+ rn->head_next.action = &rcua_next;
+ rcu_heads_add_tail(&rcu_heads, &rn->head_next);
+
+ /* free rn that we allocated above.
+ *
+ * This is INTENTIONALLY not built into the RCUA_NEXT action. This
+ * ensures that after the action above is popped off the queue, there
+ * is still at least 1 item on the RCU queue. This means we never
+ * delete the last item, which is extremely important since it keeps
+ * the atomlist ->last pointer alive and well.
+ *
+ * If we were to "run dry" on the RCU queue, add_tail may run into the
+ * "last item is being deleted - start over" case, and then we may end
+ * up accessing old RCU queue items that are already free'd.
+ */
+ rcu_free_internal(MTYPE_RCU_NEXT, rn, head_free);
+
+ /* Only allow the RCU sweeper to run after these 2 items are queued.
+ *
+ * If another thread enqueues some RCU action in the intermediate
+ * window here, nothing bad happens - the queued action is associated
+ * with a larger seq# than strictly necessary. Thus, it might get
+ * executed a bit later, but that's not a problem.
+ *
+ * If another thread acquires the read lock in this window, it holds
+ * the previous epoch, but its RCU queue actions will be in the next
+ * epoch. This isn't a problem either, just a tad inefficient.
+ */
+ seqlock_bump(&rcu_seq);
+}
+
+static void rcu_bump_maybe(void)
+{
+ seqlock_val_t dirty;
+
+ dirty = atomic_load_explicit(&rcu_dirty, memory_order_relaxed);
+ /* no problem if we race here and multiple threads bump rcu_seq;
+ * bumping too much causes no issues while not bumping enough will
+ * result in delayed cleanup
+ */
+ if (dirty == seqlock_cur(&rcu_seq))
+ rcu_bump();
+}
+
+void rcu_read_lock(void)
+{
+ struct rcu_thread *rt = rcu_self();
+
+ assert(rt);
+ if (rt->depth++ > 0)
+ return;
+
+ seqlock_acquire(&rt->rcu, &rcu_seq);
+ /* need to hold RCU for bump ... */
+ rcu_bump_maybe();
+ /* ... but no point in holding the old epoch if we just bumped */
+ seqlock_acquire(&rt->rcu, &rcu_seq);
+}
+
+void rcu_read_unlock(void)
+{
+ struct rcu_thread *rt = rcu_self();
+
+ assert(rt && rt->depth);
+ if (--rt->depth > 0)
+ return;
+ rcu_bump_maybe();
+ seqlock_release(&rt->rcu);
+}
+
+void rcu_assert_read_locked(void)
+{
+ struct rcu_thread *rt = rcu_self();
+ assert(rt && rt->depth && seqlock_held(&rt->rcu));
+}
+
+void rcu_assert_read_unlocked(void)
+{
+ struct rcu_thread *rt = rcu_self();
+ assert(rt && !rt->depth && !seqlock_held(&rt->rcu));
+}
+
+/*
+ * RCU resource-release thread
+ */
+
+static void *rcu_main(void *arg);
+
+static void rcu_start(void)
+{
+ /* ensure we never handle signals on the RCU thread by blocking
+ * everything here (new thread inherits signal mask)
+ */
+ sigset_t oldsigs, blocksigs;
+
+ sigfillset(&blocksigs);
+ pthread_sigmask(SIG_BLOCK, &blocksigs, &oldsigs);
+
+ rcu_active = true;
+
+ assert(!pthread_create(&rcu_pthread, NULL, rcu_main, NULL));
+
+ pthread_sigmask(SIG_SETMASK, &oldsigs, NULL);
+
+#ifdef HAVE_PTHREAD_SETNAME_NP
+# ifdef GNU_LINUX
+ pthread_setname_np(rcu_pthread, "RCU sweeper");
+# elif defined(__NetBSD__)
+ pthread_setname_np(rcu_pthread, "RCU sweeper", NULL);
+# endif
+#elif defined(HAVE_PTHREAD_SET_NAME_NP)
+ pthread_set_name_np(rcu_pthread, "RCU sweeper");
+#endif
+}
+
+static void rcu_do(struct rcu_head *rh)
+{
+ struct rcu_head_close *rhc;
+ void *p;
+
+ switch (rh->action->type) {
+ case RCUA_FREE:
+ p = (char *)rh - rh->action->u.free.offset;
+ if (rh->action->u.free.mt)
+ qfree(rh->action->u.free.mt, p);
+ else
+ free(p);
+ break;
+ case RCUA_CLOSE:
+ rhc = container_of(rh, struct rcu_head_close,
+ rcu_head);
+ close(rhc->fd);
+ break;
+ case RCUA_CALL:
+ p = (char *)rh - rh->action->u.call.offset;
+ rh->action->u.call.fptr(p);
+ break;
+
+ case RCUA_INVALID:
+ case RCUA_NEXT:
+ case RCUA_END:
+ default:
+ assert(0);
+ }
+}
+
+static void rcu_watchdog(struct rcu_thread *rt)
+{
+#if 0
+ /* future work: print a backtrace for the thread that's holding up
+ * RCU. The only (good) way of doing that is to send a signal to the
+ * other thread, save away the backtrace in the signal handler, and
+ * block here until the signal is done processing.
+ *
+ * Just haven't implemented that yet.
+ */
+ fprintf(stderr, "RCU watchdog %p\n", rt);
+#endif
+}
+
+static void *rcu_main(void *arg)
+{
+ struct rcu_thread *rt;
+ struct rcu_head *rh = NULL;
+ bool end = false;
+ struct timespec maxwait;
+
+ seqlock_val_t rcuval = SEQLOCK_STARTVAL;
+
+ pthread_setspecific(rcu_thread_key, &rcu_thread_rcu);
+
+ while (!end) {
+ seqlock_wait(&rcu_seq, rcuval);
+
+ /* RCU watchdog timeout, TODO: configurable value */
+ clock_gettime(CLOCK_MONOTONIC, &maxwait);
+ maxwait.tv_nsec += 100 * 1000 * 1000;
+ if (maxwait.tv_nsec >= 1000000000) {
+ maxwait.tv_sec++;
+ maxwait.tv_nsec -= 1000000000;
+ }
+
+ frr_each (rcu_threads, &rcu_threads, rt)
+ if (!seqlock_timedwait(&rt->rcu, rcuval, &maxwait)) {
+ rcu_watchdog(rt);
+ seqlock_wait(&rt->rcu, rcuval);
+ }
+
+ while ((rh = rcu_heads_pop(&rcu_heads))) {
+ if (rh->action->type == RCUA_NEXT)
+ break;
+ else if (rh->action->type == RCUA_END)
+ end = true;
+ else
+ rcu_do(rh);
+ }
+
+ rcuval += SEQLOCK_INCR;
+ }
+
+ /* rcu_shutdown can only be called singlethreaded, and it does a
+ * pthread_join, so it should be impossible that anything ended up
+ * on the queue after RCUA_END
+ */
+#if 1
+ assert(!rcu_heads_first(&rcu_heads));
+#else
+ while ((rh = rcu_heads_pop(&rcu_heads)))
+ if (rh->action->type >= RCUA_FREE)
+ rcu_do(rh);
+#endif
+ return NULL;
+}
+
+void rcu_shutdown(void)
+{
+ static struct rcu_head rcu_head_end;
+ struct rcu_thread *rt = rcu_self();
+ void *retval;
+
+ if (!rcu_active)
+ return;
+
+ rcu_assert_read_locked();
+ assert(rcu_threads_count(&rcu_threads) == 1);
+
+ rcu_enqueue(&rcu_head_end, &rcua_end);
+
+ rt->depth = 0;
+ seqlock_release(&rt->rcu);
+ seqlock_release(&rcu_seq);
+ rcu_active = false;
+
+ /* clearing rcu_active is before pthread_join in case we hang in
+ * pthread_join & get a SIGTERM or something - in that case, just
+ * ignore the maybe-still-running RCU thread
+ */
+ if (pthread_join(rcu_pthread, &retval) == 0) {
+ seqlock_acquire_val(&rcu_seq, SEQLOCK_STARTVAL);
+ seqlock_acquire_val(&rt->rcu, SEQLOCK_STARTVAL);
+ rt->depth = 1;
+ }
+}
+
+/*
+ * RCU'd free functions
+ */
+
+void rcu_enqueue(struct rcu_head *rh, const struct rcu_action *action)
+{
+ /* refer to rcu_bump() for why we need to hold RCU when adding items
+ * to rcu_heads
+ */
+ rcu_assert_read_locked();
+
+ rh->action = action;
+
+ if (!rcu_active) {
+ rcu_do(rh);
+ return;
+ }
+ rcu_heads_add_tail(&rcu_heads, rh);
+ atomic_store_explicit(&rcu_dirty, seqlock_cur(&rcu_seq),
+ memory_order_relaxed);
+}
+
+void rcu_close(struct rcu_head_close *rhc, int fd)
+{
+ rhc->fd = fd;
+ rcu_enqueue(&rhc->rcu_head, &rcua_close);
+}
diff --git a/lib/frrcu.h b/lib/frrcu.h
new file mode 100644
index 0000000..ae84092
--- /dev/null
+++ b/lib/frrcu.h
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2017-19 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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 _FRRCU_H
+#define _FRRCU_H
+
+#include <assert.h>
+
+#include "memory.h"
+#include "atomlist.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* quick RCU primer:
+ * There's a global sequence counter. Whenever a thread does a
+ * rcu_read_lock(), it is marked as holding the current sequence counter.
+ * When something is cleaned with RCU, the global sequence counter is
+ * increased and the item is queued for cleanup - *after* all threads are
+ * at a more recent sequence counter (or no sequence counter / unheld).
+ *
+ * So, by delaying resource cleanup, RCU ensures that things don't go away
+ * while another thread may hold a (stale) reference.
+ *
+ * Note that even if a thread is in rcu_read_lock(), it is invalid for that
+ * thread to access bits after rcu_free() & co on them. This is a design
+ * choice to allow no-op'ing out the entire RCU mechanism if we're running
+ * singlethreaded. (Also allows some optimization on the counter bumping.)
+ *
+ * differences from Linux Kernel RCU:
+ * - there's no rcu_synchronize(), if you really need to defer something
+ * use rcu_call() (and double check it's really necessary)
+ * - rcu_dereference() and rcu_assign_pointer() don't exist, use atomic_*
+ * instead (ATOM* list structures do the right thing)
+ */
+
+/* opaque */
+struct rcu_thread;
+
+/* called before new thread creation, sets up rcu thread info for new thread
+ * before it actually exits. This ensures possible RCU references are held
+ * for thread startup.
+ *
+ * return value must be passed into the new thread's call to rcu_thread_start()
+ */
+extern struct rcu_thread *rcu_thread_prepare(void);
+
+/* cleanup in case pthread_create() fails */
+extern void rcu_thread_unprepare(struct rcu_thread *rcu_thread);
+
+/* called early in the new thread, with the return value from the above.
+ * NB: new thread is initially in RCU-held state! (at depth 1)
+ *
+ * TBD: maybe inherit RCU state from rcu_thread_prepare()?
+ */
+extern void rcu_thread_start(struct rcu_thread *rcu_thread);
+
+/* thread exit is handled through pthread_key_create's destructor function */
+
+/* global RCU shutdown - must be called with only 1 active thread left. waits
+ * until remaining RCU actions are done & RCU thread has exited.
+ *
+ * This is mostly here to get a clean exit without memleaks.
+ */
+extern void rcu_shutdown(void);
+
+/* enter / exit RCU-held state. counter-based, so can be called nested. */
+extern void rcu_read_lock(void);
+extern void rcu_read_unlock(void);
+
+/* for debugging / safety checks */
+extern void rcu_assert_read_locked(void);
+extern void rcu_assert_read_unlocked(void);
+
+enum rcu_action_type {
+ RCUA_INVALID = 0,
+ /* used internally by the RCU code, shouldn't ever show up outside */
+ RCUA_NEXT,
+ RCUA_END,
+ /* normal RCU actions, for outside use */
+ RCUA_FREE,
+ RCUA_CLOSE,
+ RCUA_CALL,
+};
+
+/* since rcu_head is intended to be embedded into structs which may exist
+ * with lots of copies, rcu_head is shrunk down to its absolute minimum -
+ * the atomlist pointer + a pointer to this action struct.
+ */
+struct rcu_action {
+ enum rcu_action_type type;
+
+ union {
+ struct {
+ struct memtype *mt;
+ ptrdiff_t offset;
+ } free;
+
+ struct {
+ void (*fptr)(void *arg);
+ ptrdiff_t offset;
+ } call;
+ } u;
+};
+
+/* RCU cleanup function queue item */
+PREDECL_ATOMLIST(rcu_heads);
+struct rcu_head {
+ struct rcu_heads_item head;
+ const struct rcu_action *action;
+};
+
+/* special RCU head for delayed fd-close */
+struct rcu_head_close {
+ struct rcu_head rcu_head;
+ int fd;
+};
+
+/* enqueue RCU action - use the macros below to get the rcu_action set up */
+extern void rcu_enqueue(struct rcu_head *head, const struct rcu_action *action);
+
+/* RCU free() and file close() operations.
+ *
+ * freed memory / closed fds become _immediately_ unavailable to the calling
+ * thread, but will remain available for other threads until they have passed
+ * into RCU-released state.
+ */
+
+/* may be called with NULL mt to do non-MTYPE free() */
+#define rcu_free(mtype, ptr, field) \
+ do { \
+ typeof(ptr) _ptr = (ptr); \
+ if (!_ptr) \
+ break; \
+ struct rcu_head *_rcu_head = &_ptr->field; \
+ static const struct rcu_action _rcu_action = { \
+ .type = RCUA_FREE, \
+ .u.free = { \
+ .mt = mtype, \
+ .offset = offsetof(typeof(*_ptr), field), \
+ }, \
+ }; \
+ rcu_enqueue(_rcu_head, &_rcu_action); \
+ } while (0)
+
+/* use this sparingly, it runs on (and blocks) the RCU thread */
+#define rcu_call(func, ptr, field) \
+ do { \
+ typeof(ptr) _ptr = (ptr); \
+ void (*fptype)(typeof(ptr)); \
+ struct rcu_head *_rcu_head = &_ptr->field; \
+ static const struct rcu_action _rcu_action = { \
+ .type = RCUA_CALL, \
+ .u.call = { \
+ .fptr = (void *)func, \
+ .offset = offsetof(typeof(*_ptr), field), \
+ }, \
+ }; \
+ (void)(_fptype = func); \
+ rcu_enqueue(_rcu_head, &_rcu_action); \
+ } while (0)
+
+extern void rcu_close(struct rcu_head_close *head, int fd);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRRCU_H */
diff --git a/lib/frrlua.c b/lib/frrlua.c
new file mode 100644
index 0000000..535649e
--- /dev/null
+++ b/lib/frrlua.c
@@ -0,0 +1,501 @@
+/*
+ * This file defines the lua interface into
+ * FRRouting.
+ *
+ * Copyright (C) 2016-2019 Cumulus Networks, Inc.
+ * Donald Sharp, Quentin Young
+ *
+ * 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>
+
+#ifdef HAVE_SCRIPTING
+
+#include "prefix.h"
+#include "frrlua.h"
+#include "log.h"
+#include "buffer.h"
+
+DEFINE_MTYPE(LIB, SCRIPT_RES, "Scripting results");
+
+/* Lua stuff */
+
+/*
+ * FRR convenience functions.
+ *
+ * This section has convenience functions used to make interacting with the Lua
+ * stack easier.
+ */
+
+int frrlua_table_get_integer(lua_State *L, const char *key)
+{
+ int result;
+
+ lua_pushstring(L, key);
+ lua_gettable(L, -2);
+
+ result = lua_tointeger(L, -1);
+ lua_pop(L, 1);
+
+ return result;
+}
+
+/*
+ * This section has functions that convert internal FRR datatypes into Lua
+ * datatypes: one encoder function and two decoder functions for each type.
+ *
+ */
+
+void lua_pushprefix(lua_State *L, const struct prefix *prefix)
+{
+ char buffer[PREFIX_STRLEN];
+
+ lua_newtable(L);
+ lua_pushstring(L, prefix2str(prefix, buffer, PREFIX_STRLEN));
+ lua_setfield(L, -2, "network");
+ lua_pushinteger(L, prefix->prefixlen);
+ lua_setfield(L, -2, "length");
+ lua_pushinteger(L, prefix->family);
+ lua_setfield(L, -2, "family");
+}
+
+void lua_decode_prefix(lua_State *L, int idx, struct prefix *prefix)
+{
+ lua_getfield(L, idx, "network");
+ (void)str2prefix(lua_tostring(L, -1), prefix);
+ lua_pop(L, 1);
+ /* pop the table */
+ lua_pop(L, 1);
+}
+
+void *lua_toprefix(lua_State *L, int idx)
+{
+ struct prefix *p = XCALLOC(MTYPE_SCRIPT_RES, sizeof(struct prefix));
+ lua_decode_prefix(L, idx, p);
+ return p;
+}
+
+void lua_pushinterface(lua_State *L, const struct interface *ifp)
+{
+ lua_newtable(L);
+ lua_pushstring(L, ifp->name);
+ lua_setfield(L, -2, "name");
+ lua_pushinteger(L, ifp->ifindex);
+ lua_setfield(L, -2, "ifindex");
+ lua_pushinteger(L, ifp->status);
+ lua_setfield(L, -2, "status");
+ lua_pushinteger(L, ifp->flags);
+ lua_setfield(L, -2, "flags");
+ lua_pushinteger(L, ifp->metric);
+ lua_setfield(L, -2, "metric");
+ lua_pushinteger(L, ifp->speed);
+ lua_setfield(L, -2, "speed");
+ lua_pushinteger(L, ifp->mtu);
+ lua_setfield(L, -2, "mtu");
+ lua_pushinteger(L, ifp->mtu6);
+ lua_setfield(L, -2, "mtu6");
+ lua_pushinteger(L, ifp->bandwidth);
+ lua_setfield(L, -2, "bandwidth");
+ lua_pushinteger(L, ifp->link_ifindex);
+ lua_setfield(L, -2, "link_ifindex");
+ lua_pushinteger(L, ifp->ll_type);
+ lua_setfield(L, -2, "linklayer_type");
+}
+
+void lua_decode_interface(lua_State *L, int idx, struct interface *ifp)
+{
+ lua_getfield(L, idx, "name");
+ strlcpy(ifp->name, lua_tostring(L, -1), sizeof(ifp->name));
+ lua_pop(L, 1);
+ lua_getfield(L, idx, "ifindex");
+ ifp->ifindex = lua_tointeger(L, -1);
+ lua_pop(L, 1);
+ lua_getfield(L, idx, "status");
+ ifp->status = lua_tointeger(L, -1);
+ lua_pop(L, 1);
+ lua_getfield(L, idx, "flags");
+ ifp->flags = lua_tointeger(L, -1);
+ lua_pop(L, 1);
+ lua_getfield(L, idx, "metric");
+ ifp->metric = lua_tointeger(L, -1);
+ lua_pop(L, 1);
+ lua_getfield(L, idx, "speed");
+ ifp->speed = lua_tointeger(L, -1);
+ lua_pop(L, 1);
+ lua_getfield(L, idx, "mtu");
+ ifp->mtu = lua_tointeger(L, -1);
+ lua_pop(L, 1);
+ lua_getfield(L, idx, "mtu6");
+ ifp->mtu6 = lua_tointeger(L, -1);
+ lua_pop(L, 1);
+ lua_getfield(L, idx, "bandwidth");
+ ifp->bandwidth = lua_tointeger(L, -1);
+ lua_pop(L, 1);
+ lua_getfield(L, idx, "link_ifindex");
+ ifp->link_ifindex = lua_tointeger(L, -1);
+ lua_pop(L, 1);
+ lua_getfield(L, idx, "linklayer_type");
+ ifp->ll_type = lua_tointeger(L, -1);
+ lua_pop(L, 1);
+ /* pop the table */
+ lua_pop(L, 1);
+}
+void *lua_tointerface(lua_State *L, int idx)
+{
+ struct interface *ifp =
+ XCALLOC(MTYPE_SCRIPT_RES, sizeof(struct interface));
+
+ lua_decode_interface(L, idx, ifp);
+ return ifp;
+}
+
+void lua_pushinaddr(lua_State *L, const struct in_addr *addr)
+{
+ char buf[INET_ADDRSTRLEN];
+
+ inet_ntop(AF_INET, addr, buf, sizeof(buf));
+
+ lua_newtable(L);
+ lua_pushinteger(L, addr->s_addr);
+ lua_setfield(L, -2, "value");
+ lua_pushstring(L, buf);
+ lua_setfield(L, -2, "string");
+}
+
+void lua_decode_inaddr(lua_State *L, int idx, struct in_addr *inaddr)
+{
+ lua_getfield(L, idx, "value");
+ inaddr->s_addr = lua_tointeger(L, -1);
+ lua_pop(L, 1);
+ /* pop the table */
+ lua_pop(L, 1);
+}
+
+void *lua_toinaddr(lua_State *L, int idx)
+{
+ struct in_addr *inaddr =
+ XCALLOC(MTYPE_SCRIPT_RES, sizeof(struct in_addr));
+ lua_decode_inaddr(L, idx, inaddr);
+ return inaddr;
+}
+
+
+void lua_pushin6addr(lua_State *L, const struct in6_addr *addr)
+{
+ char buf[INET6_ADDRSTRLEN];
+
+ inet_ntop(AF_INET6, addr, buf, sizeof(buf));
+
+ lua_newtable(L);
+ lua_pushlstring(L, (const char *)addr->s6_addr, 16);
+ lua_setfield(L, -2, "value");
+ lua_pushstring(L, buf);
+ lua_setfield(L, -2, "string");
+}
+
+void lua_decode_in6addr(lua_State *L, int idx, struct in6_addr *in6addr)
+{
+ lua_getfield(L, idx, "string");
+ inet_pton(AF_INET6, lua_tostring(L, -1), in6addr);
+ lua_pop(L, 1);
+ /* pop the table */
+ lua_pop(L, 1);
+}
+
+void *lua_toin6addr(lua_State *L, int idx)
+{
+ struct in6_addr *in6addr =
+ XCALLOC(MTYPE_SCRIPT_RES, sizeof(struct in6_addr));
+ lua_decode_in6addr(L, idx, in6addr);
+ return in6addr;
+}
+
+void lua_pushipaddr(lua_State *L, const struct ipaddr *addr)
+{
+ if (IS_IPADDR_V4(addr))
+ lua_pushinaddr(L, &addr->ipaddr_v4);
+ else
+ lua_pushin6addr(L, &addr->ipaddr_v6);
+}
+
+void lua_pushethaddr(lua_State *L, const struct ethaddr *addr)
+{
+ lua_newtable(L);
+ lua_pushinteger(L, *(addr->octet));
+ lua_setfield(L, -2, "octet");
+}
+
+void lua_pushsockunion(lua_State *L, const union sockunion *su)
+{
+ char buf[SU_ADDRSTRLEN];
+
+ sockunion2str(su, buf, sizeof(buf));
+
+ lua_newtable(L);
+ lua_pushlstring(L, (const char *)sockunion_get_addr(su),
+ sockunion_get_addrlen(su));
+ lua_setfield(L, -2, "value");
+ lua_pushstring(L, buf);
+ lua_setfield(L, -2, "string");
+}
+
+void lua_decode_sockunion(lua_State *L, int idx, union sockunion *su)
+{
+ lua_getfield(L, idx, "string");
+ if (str2sockunion(lua_tostring(L, -1), su) < 0)
+ zlog_err("Lua hook call: Failed to decode sockunion");
+
+ lua_pop(L, 1);
+ /* pop the table */
+ lua_pop(L, 1);
+}
+
+void *lua_tosockunion(lua_State *L, int idx)
+{
+ union sockunion *su =
+ XCALLOC(MTYPE_SCRIPT_RES, sizeof(union sockunion));
+
+ lua_decode_sockunion(L, idx, su);
+ return su;
+}
+
+void lua_pushtimet(lua_State *L, const time_t *time)
+{
+ lua_pushinteger(L, *time);
+}
+
+void lua_decode_timet(lua_State *L, int idx, time_t *t)
+{
+ *t = lua_tointeger(L, idx);
+ lua_pop(L, 1);
+}
+
+void *lua_totimet(lua_State *L, int idx)
+{
+ time_t *t = XCALLOC(MTYPE_SCRIPT_RES, sizeof(time_t));
+
+ lua_decode_timet(L, idx, t);
+ return t;
+}
+
+void lua_pushintegerp(lua_State *L, const long long *num)
+{
+ lua_pushinteger(L, *num);
+}
+
+void lua_decode_integerp(lua_State *L, int idx, long long *num)
+{
+ int isnum;
+ *num = lua_tonumberx(L, idx, &isnum);
+ lua_pop(L, 1);
+ assert(isnum);
+}
+
+void *lua_tointegerp(lua_State *L, int idx)
+{
+ long long *num = XCALLOC(MTYPE_SCRIPT_RES, sizeof(long long));
+
+ lua_decode_integerp(L, idx, num);
+ return num;
+}
+
+void lua_pushnexthop(lua_State *L, const struct nexthop *nexthop)
+{
+ lua_newtable(L);
+ lua_pushinteger(L, nexthop->vrf_id);
+ lua_setfield(L, -2, "vrf_id");
+ lua_pushinteger(L, nexthop->ifindex);
+ lua_setfield(L, -2, "ifindex");
+ lua_pushinteger(L, nexthop->type);
+ lua_setfield(L, -2, "type");
+ lua_pushinteger(L, nexthop->flags);
+ lua_setfield(L, -2, "flags");
+ if (nexthop->type == NEXTHOP_TYPE_BLACKHOLE) {
+ lua_pushinteger(L, nexthop->bh_type);
+ lua_setfield(L, -2, "bh_type");
+ } else if (nexthop->type == NEXTHOP_TYPE_IPV4) {
+ lua_pushinaddr(L, &nexthop->gate.ipv4);
+ lua_setfield(L, -2, "gate");
+ } else if (nexthop->type == NEXTHOP_TYPE_IPV6) {
+ lua_pushin6addr(L, &nexthop->gate.ipv6);
+ lua_setfield(L, -2, "gate");
+ }
+ lua_pushinteger(L, nexthop->nh_label_type);
+ lua_setfield(L, -2, "nh_label_type");
+ lua_pushinteger(L, nexthop->weight);
+ lua_setfield(L, -2, "weight");
+ lua_pushinteger(L, nexthop->backup_num);
+ lua_setfield(L, -2, "backup_num");
+ lua_pushinteger(L, *(nexthop->backup_idx));
+ lua_setfield(L, -2, "backup_idx");
+ if (nexthop->nh_encap_type == NET_VXLAN) {
+ lua_pushinteger(L, nexthop->nh_encap.vni);
+ lua_setfield(L, -2, "vni");
+ }
+ lua_pushinteger(L, nexthop->nh_encap_type);
+ lua_setfield(L, -2, "nh_encap_type");
+ lua_pushinteger(L, nexthop->srte_color);
+ lua_setfield(L, -2, "srte_color");
+}
+
+void lua_pushnexthop_group(lua_State *L, const struct nexthop_group *ng)
+{
+ lua_newtable(L);
+ struct nexthop *nexthop;
+ int i = 0;
+
+ for (ALL_NEXTHOPS_PTR(ng, nexthop)) {
+ lua_pushnexthop(L, nexthop);
+ lua_seti(L, -2, i);
+ i++;
+ }
+}
+
+void lua_decode_stringp(lua_State *L, int idx, char *str)
+{
+ strlcpy(str, lua_tostring(L, idx), strlen(str) + 1);
+ lua_pop(L, 1);
+}
+
+void *lua_tostringp(lua_State *L, int idx)
+{
+ char *string = XSTRDUP(MTYPE_SCRIPT_RES, lua_tostring(L, idx));
+
+ return string;
+}
+
+/*
+ * Decoder for const values, since we cannot modify them.
+ */
+void lua_decode_noop(lua_State *L, int idx, const void *ptr)
+{
+}
+
+
+/*
+ * Noop decoder for int.
+ */
+void lua_decode_integer_noop(lua_State *L, int idx, int i)
+{
+}
+
+/*
+ * Logging.
+ *
+ * Lua-compatible wrappers for FRR logging functions.
+ */
+static const char *frrlua_log_thunk(lua_State *L)
+{
+ int nargs;
+
+ nargs = lua_gettop(L);
+ assert(nargs == 1);
+
+ return lua_tostring(L, 1);
+}
+
+static int frrlua_log_debug(lua_State *L)
+{
+ zlog_debug("%s", frrlua_log_thunk(L));
+ return 0;
+}
+
+static int frrlua_log_info(lua_State *L)
+{
+ zlog_info("%s", frrlua_log_thunk(L));
+ return 0;
+}
+
+static int frrlua_log_notice(lua_State *L)
+{
+ zlog_notice("%s", frrlua_log_thunk(L));
+ return 0;
+}
+
+static int frrlua_log_warn(lua_State *L)
+{
+ zlog_warn("%s", frrlua_log_thunk(L));
+ return 0;
+}
+
+static int frrlua_log_error(lua_State *L)
+{
+ zlog_err("%s", frrlua_log_thunk(L));
+ return 0;
+}
+
+static const luaL_Reg log_funcs[] = {
+ {"debug", frrlua_log_debug},
+ {"info", frrlua_log_info},
+ {"notice", frrlua_log_notice},
+ {"warn", frrlua_log_warn},
+ {"error", frrlua_log_error},
+ {},
+};
+
+void frrlua_export_logging(lua_State *L)
+{
+ lua_newtable(L);
+ luaL_setfuncs(L, log_funcs, 0);
+ lua_setglobal(L, "log");
+}
+
+/*
+ * Debugging.
+ */
+
+char *frrlua_stackdump(lua_State *L)
+{
+ int top = lua_gettop(L);
+
+ char tmpbuf[64];
+ struct buffer *buf = buffer_new(4098);
+
+ for (int i = 1; i <= top; i++) {
+ int t = lua_type(L, i);
+
+ switch (t) {
+ case LUA_TSTRING: /* strings */
+ snprintf(tmpbuf, sizeof(tmpbuf), "\"%s\"\n",
+ lua_tostring(L, i));
+ buffer_putstr(buf, tmpbuf);
+ break;
+ case LUA_TBOOLEAN: /* booleans */
+ snprintf(tmpbuf, sizeof(tmpbuf), "%s\n",
+ lua_toboolean(L, i) ? "true" : "false");
+ buffer_putstr(buf, tmpbuf);
+ break;
+ case LUA_TNUMBER: /* numbers */
+ snprintf(tmpbuf, sizeof(tmpbuf), "%g\n",
+ lua_tonumber(L, i));
+ buffer_putstr(buf, tmpbuf);
+ break;
+ default: /* other values */
+ snprintf(tmpbuf, sizeof(tmpbuf), "%s\n",
+ lua_typename(L, t));
+ buffer_putstr(buf, tmpbuf);
+ break;
+ }
+ }
+
+ char *result = XSTRDUP(MTYPE_TMP, buffer_getstr(buf));
+
+ buffer_free(buf);
+
+ return result;
+}
+
+#endif /* HAVE_SCRIPTING */
diff --git a/lib/frrlua.h b/lib/frrlua.h
new file mode 100644
index 0000000..a82009a
--- /dev/null
+++ b/lib/frrlua.h
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2016-2019 Cumulus Networks, Inc.
+ * Donald Sharp, Quentin Young
+ *
+ * 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 __FRRLUA_H__
+#define __FRRLUA_H__
+
+#include <zebra.h>
+
+#ifdef HAVE_SCRIPTING
+
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include "prefix.h"
+#include "frrscript.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+DECLARE_MTYPE(SCRIPT_RES);
+
+/*
+ * gcc-10 is complaining about the wrapper function
+ * not being compatible with lua_pushstring returning
+ * a char *. Let's wrapper it here to make our life
+ * easier
+ */
+static inline void lua_pushstring_wrapper(lua_State *L, const char *str)
+{
+ (void)lua_pushstring(L, str);
+}
+
+/*
+ * Converts a prefix to a Lua value and pushes it on the stack.
+ */
+void lua_pushprefix(lua_State *L, const struct prefix *prefix);
+
+void lua_decode_prefix(lua_State *L, int idx, struct prefix *prefix);
+
+/*
+ * Converts the Lua value at idx to a prefix.
+ *
+ * Returns:
+ * struct prefix allocated with MTYPE_TMP
+ */
+void *lua_toprefix(lua_State *L, int idx);
+
+/*
+ * Converts an interface to a Lua value and pushes it on the stack.
+ */
+void lua_pushinterface(lua_State *L, const struct interface *ifp);
+
+void lua_decode_interface(lua_State *L, int idx, struct interface *ifp);
+
+/*
+ * Converts the Lua value at idx to an interface.
+ *
+ * Returns:
+ * struct interface allocated with MTYPE_TMP. This interface is not hooked
+ * to anything, nor is it inserted in the global interface tree.
+ */
+void *lua_tointerface(lua_State *L, int idx);
+
+/*
+ * Converts an in_addr to a Lua value and pushes it on the stack.
+ */
+void lua_pushinaddr(lua_State *L, const struct in_addr *addr);
+
+void lua_decode_inaddr(lua_State *L, int idx, struct in_addr *addr);
+
+/*
+ * Converts the Lua value at idx to an in_addr.
+ *
+ * Returns:
+ * struct in_addr allocated with MTYPE_TMP.
+ */
+void *lua_toinaddr(lua_State *L, int idx);
+
+/*
+ * Converts an in6_addr to a Lua value and pushes it on the stack.
+ */
+void lua_pushin6addr(lua_State *L, const struct in6_addr *addr);
+
+void lua_decode_in6addr(lua_State *L, int idx, struct in6_addr *addr);
+
+void lua_pushipaddr(lua_State *L, const struct ipaddr *addr);
+
+void lua_pushethaddr(lua_State *L, const struct ethaddr *addr);
+
+/*
+ * Converts the Lua value at idx to an in6_addr.
+ *
+ * Returns:
+ * struct in6_addr allocated with MTYPE_TMP.
+ */
+void *lua_toin6addr(lua_State *L, int idx);
+
+/*
+ * Converts a time_t to a Lua value and pushes it on the stack.
+ */
+void lua_pushtimet(lua_State *L, const time_t *time);
+
+void lua_decode_timet(lua_State *L, int idx, time_t *time);
+
+/*
+ * Converts the Lua value at idx to a time_t.
+ *
+ * Returns:
+ * time_t allocated with MTYPE_TMP.
+ */
+void *lua_totimet(lua_State *L, int idx);
+
+/*
+ * Converts a sockunion to a Lua value and pushes it on the stack.
+ */
+void lua_pushsockunion(lua_State *L, const union sockunion *su);
+
+void lua_decode_sockunion(lua_State *L, int idx, union sockunion *su);
+
+/*
+ * Converts the Lua value at idx to a sockunion.
+ *
+ * Returns:
+ * sockunion allocated with MTYPE_TMP.
+ */
+void *lua_tosockunion(lua_State *L, int idx);
+
+void lua_pushnexthop_group(lua_State *L, const struct nexthop_group *ng);
+
+void lua_pushnexthop(lua_State *L, const struct nexthop *nexthop);
+
+/*
+ * Converts an int to a Lua value and pushes it on the stack.
+ */
+void lua_pushintegerp(lua_State *L, const long long *num);
+
+void lua_decode_integerp(lua_State *L, int idx, long long *num);
+
+/*
+ * Converts the Lua value at idx to an int.
+ *
+ * Returns:
+ * int allocated with MTYPE_TMP.
+ */
+void *lua_tointegerp(lua_State *L, int idx);
+
+void lua_decode_stringp(lua_State *L, int idx, char *str);
+
+/*
+ * Pop string.
+ *
+ * Sets *string to a copy of the string at the top of the stack. The copy is
+ * allocated with MTYPE_TMP and the caller is responsible for freeing it.
+ */
+void *lua_tostringp(lua_State *L, int idx);
+
+/*
+ * No-op decoders
+ */
+void lua_decode_noop(lua_State *L, int idx, const void *ptr);
+
+void lua_decode_integer_noop(lua_State *L, int idx, int i);
+
+/*
+ * Retrieve an integer from table on the top of the stack.
+ *
+ * key
+ * Key of string value in table
+ */
+int frrlua_table_get_integer(lua_State *L, const char *key);
+
+/*
+ * Exports a new table containing bindings to FRR zlog functions into the
+ * global namespace.
+ *
+ * From Lua, these functions may be accessed as:
+ *
+ * - log.debug()
+ * - log.info()
+ * - log.warn()
+ * - log.error()
+ *
+ * They take a single string argument.
+ */
+void frrlua_export_logging(lua_State *L);
+
+/*
+ * Dump Lua stack to a string.
+ *
+ * Return value must be freed with XFREE(MTYPE_TMP, ...);
+ */
+char *frrlua_stackdump(lua_State *L);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* HAVE_SCRIPTING */
+
+#endif /* __FRRLUA_H__ */
diff --git a/lib/frrscript.c b/lib/frrscript.c
new file mode 100644
index 0000000..2e56932
--- /dev/null
+++ b/lib/frrscript.c
@@ -0,0 +1,438 @@
+/* Scripting foo
+ * Copyright (C) 2020 NVIDIA Corporation
+ * Quentin Young
+ *
+ * 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>
+
+#ifdef HAVE_SCRIPTING
+
+#include <stdarg.h>
+#include <lua.h>
+
+#include "frrscript.h"
+#include "frrlua.h"
+#include "memory.h"
+#include "hash.h"
+#include "log.h"
+
+
+DEFINE_MTYPE_STATIC(LIB, SCRIPT, "Scripting");
+
+/*
+ * Script name hash utilities
+ */
+
+struct frrscript_names_head frrscript_names_hash;
+
+/*
+ * Wrapper for frrscript_names_add
+ * Use this to register hook calls when a daemon starts up
+ */
+int frrscript_names_add_function_name(const char *function_name)
+{
+ struct frrscript_names_entry *insert =
+ XCALLOC(MTYPE_SCRIPT, sizeof(*insert));
+ strlcpy(insert->function_name, function_name,
+ sizeof(insert->function_name));
+
+ if (frrscript_names_add(&frrscript_names_hash, insert)) {
+ zlog_warn(
+ "Failed to add hook call function name to script_names");
+ return 1;
+ }
+ return 0;
+}
+
+void frrscript_names_destroy(void)
+{
+ struct frrscript_names_entry *ne;
+
+ while ((ne = frrscript_names_pop(&frrscript_names_hash)))
+ XFREE(MTYPE_SCRIPT, ne);
+}
+
+/*
+ * Given a function_name, set its script_name. function_names and script_names
+ * are one-to-one. Each set will wipe the previous script_name.
+ * Return 0 if set was successful, else 1.
+ *
+ * script_name is the base name of the file, without .lua.
+ */
+int frrscript_names_set_script_name(const char *function_name,
+ const char *script_name)
+{
+ struct frrscript_names_entry lookup;
+
+ strlcpy(lookup.function_name, function_name,
+ sizeof(lookup.function_name));
+ struct frrscript_names_entry *snhe =
+ frrscript_names_find(&frrscript_names_hash, &lookup);
+ if (!snhe)
+ return 1;
+ strlcpy(snhe->script_name, script_name, sizeof(snhe->script_name));
+ return 0;
+}
+
+/*
+ * Given a function_name, get its script_name.
+ * Return NULL if function_name not found.
+ *
+ * script_name is the base name of the file, without .lua.
+ */
+char *frrscript_names_get_script_name(const char *function_name)
+{
+ struct frrscript_names_entry lookup;
+
+ strlcpy(lookup.function_name, function_name,
+ sizeof(lookup.function_name));
+ struct frrscript_names_entry *snhe =
+ frrscript_names_find(&frrscript_names_hash, &lookup);
+ if (!snhe)
+ return NULL;
+
+ if (snhe->script_name[0] == '\0')
+ return NULL;
+
+ return snhe->script_name;
+}
+
+uint32_t frrscript_names_hash_key(const struct frrscript_names_entry *snhe)
+{
+ return string_hash_make(snhe->function_name);
+}
+
+int frrscript_names_hash_cmp(const struct frrscript_names_entry *snhe1,
+ const struct frrscript_names_entry *snhe2)
+{
+ return strncmp(snhe1->function_name, snhe2->function_name,
+ sizeof(snhe1->function_name));
+}
+
+/* Codecs */
+
+struct frrscript_codec frrscript_codecs_lib[] = {
+ {.typename = "integer",
+ .encoder = (encoder_func)lua_pushintegerp,
+ .decoder = lua_tointegerp},
+ {.typename = "string",
+ .encoder = (encoder_func)lua_pushstring_wrapper,
+ .decoder = lua_tostringp},
+ {.typename = "prefix",
+ .encoder = (encoder_func)lua_pushprefix,
+ .decoder = lua_toprefix},
+ {.typename = "interface",
+ .encoder = (encoder_func)lua_pushinterface,
+ .decoder = lua_tointerface},
+ {.typename = "in_addr",
+ .encoder = (encoder_func)lua_pushinaddr,
+ .decoder = lua_toinaddr},
+ {.typename = "in6_addr",
+ .encoder = (encoder_func)lua_pushin6addr,
+ .decoder = lua_toin6addr},
+ {.typename = "sockunion",
+ .encoder = (encoder_func)lua_pushsockunion,
+ .decoder = lua_tosockunion},
+ {.typename = "time_t",
+ .encoder = (encoder_func)lua_pushtimet,
+ .decoder = lua_totimet},
+ {}};
+
+/* Type codecs */
+
+struct hash *codec_hash;
+char scriptdir[MAXPATHLEN];
+
+static unsigned int codec_hash_key(const void *data)
+{
+ const struct frrscript_codec *c = data;
+
+ return string_hash_make(c->typename);
+}
+
+static bool codec_hash_cmp(const void *d1, const void *d2)
+{
+ const struct frrscript_codec *e1 = d1;
+ const struct frrscript_codec *e2 = d2;
+
+ return strmatch(e1->typename, e2->typename);
+}
+
+static void *codec_alloc(void *arg)
+{
+ struct frrscript_codec *tmp = arg;
+
+ struct frrscript_codec *e =
+ XCALLOC(MTYPE_SCRIPT, sizeof(struct frrscript_codec));
+ e->typename = XSTRDUP(MTYPE_SCRIPT, tmp->typename);
+ e->encoder = tmp->encoder;
+ e->decoder = tmp->decoder;
+
+ return e;
+}
+
+static void codec_free(void *data)
+{
+ struct frrscript_codec *c = data;
+ char *constworkaroundandihateit = (char *)c->typename;
+
+ XFREE(MTYPE_SCRIPT, constworkaroundandihateit);
+ XFREE(MTYPE_SCRIPT, c);
+}
+
+/* Lua function hash utils */
+
+unsigned int lua_function_hash_key(const void *data)
+{
+ const struct lua_function_state *lfs = data;
+
+ return string_hash_make(lfs->name);
+}
+
+bool lua_function_hash_cmp(const void *d1, const void *d2)
+{
+ const struct lua_function_state *lfs1 = d1;
+ const struct lua_function_state *lfs2 = d2;
+
+ return strmatch(lfs1->name, lfs2->name);
+}
+
+void *lua_function_alloc(void *arg)
+{
+ struct lua_function_state *tmp = arg;
+ struct lua_function_state *lfs =
+ XCALLOC(MTYPE_SCRIPT, sizeof(struct lua_function_state));
+
+ lfs->name = tmp->name;
+ lfs->L = tmp->L;
+ return lfs;
+}
+
+static void lua_function_free(void *data)
+{
+ struct lua_function_state *lfs = data;
+
+ lua_close(lfs->L);
+ XFREE(MTYPE_SCRIPT, lfs);
+}
+
+/* internal frrscript APIs */
+
+int _frrscript_call_lua(struct lua_function_state *lfs, int nargs)
+{
+
+ int ret;
+ ret = lua_pcall(lfs->L, nargs, 1, 0);
+
+ switch (ret) {
+ case LUA_OK:
+ break;
+ case LUA_ERRRUN:
+ zlog_err("Lua hook call '%s' : runtime error: %s", lfs->name,
+ lua_tostring(lfs->L, -1));
+ break;
+ case LUA_ERRMEM:
+ zlog_err("Lua hook call '%s' : memory error: %s", lfs->name,
+ lua_tostring(lfs->L, -1));
+ break;
+ case LUA_ERRERR:
+ zlog_err("Lua hook call '%s' : error handler error: %s",
+ lfs->name, lua_tostring(lfs->L, -1));
+ break;
+ case LUA_ERRGCMM:
+ zlog_err("Lua hook call '%s' : garbage collector error: %s",
+ lfs->name, lua_tostring(lfs->L, -1));
+ break;
+ default:
+ zlog_err("Lua hook call '%s' : unknown error: %s", lfs->name,
+ lua_tostring(lfs->L, -1));
+ break;
+ }
+
+ if (ret != LUA_OK) {
+ lua_pop(lfs->L, 1);
+ goto done;
+ }
+
+ if (lua_gettop(lfs->L) != 1) {
+ zlog_err(
+ "Lua hook call '%s': Lua function should return only 1 result",
+ lfs->name);
+ ret = 1;
+ goto done;
+ }
+
+ if (lua_istable(lfs->L, 1) != 1) {
+ zlog_err(
+ "Lua hook call '%s': Lua function should return a Lua table",
+ lfs->name);
+ ret = 1;
+ }
+
+done:
+ /* LUA_OK is 0, so we can just return lua_pcall's result directly */
+ return ret;
+}
+
+void *frrscript_get_result(struct frrscript *fs, const char *function_name,
+ const char *name,
+ void *(*lua_to)(lua_State *L, int idx))
+{
+ void *p;
+ struct lua_function_state *lfs;
+ struct lua_function_state lookup = {.name = function_name};
+
+ lfs = hash_lookup(fs->lua_function_hash, &lookup);
+
+ if (lfs == NULL)
+ return NULL;
+
+ /* At this point, the Lua state should have only the returned table.
+ * We will then search the table for the key/value we're interested in.
+ * Then if the value is present (i.e. non-nil), call the lua_to*
+ * decoder.
+ */
+ assert(lua_gettop(lfs->L) == 1);
+ assert(lua_istable(lfs->L, -1) == 1);
+ lua_getfield(lfs->L, -1, name);
+ if (lua_isnil(lfs->L, -1)) {
+ lua_pop(lfs->L, 1);
+ zlog_warn(
+ "frrscript: '%s.lua': '%s': tried to decode '%s' as result but failed",
+ fs->name, function_name, name);
+ return NULL;
+ }
+ p = lua_to(lfs->L, 2);
+
+ /* At the end, the Lua state should be same as it was at the start
+ * i.e. containing solely the returned table.
+ */
+ assert(lua_gettop(lfs->L) == 1);
+ assert(lua_istable(lfs->L, -1) == 1);
+
+ return p;
+}
+
+void frrscript_register_type_codec(struct frrscript_codec *codec)
+{
+ struct frrscript_codec c = *codec;
+
+ if (hash_lookup(codec_hash, &c)) {
+ zlog_backtrace(LOG_ERR);
+ assert(!"Type codec double-registered.");
+ }
+
+ (void)hash_get(codec_hash, &c, codec_alloc);
+}
+
+void frrscript_register_type_codecs(struct frrscript_codec *codecs)
+{
+ for (int i = 0; codecs[i].typename != NULL; i++)
+ frrscript_register_type_codec(&codecs[i]);
+}
+
+struct frrscript *frrscript_new(const char *name)
+{
+ struct frrscript *fs = XCALLOC(MTYPE_SCRIPT, sizeof(struct frrscript));
+
+ fs->name = XSTRDUP(MTYPE_SCRIPT, name);
+ fs->lua_function_hash =
+ hash_create(lua_function_hash_key, lua_function_hash_cmp,
+ "Lua function state hash");
+ return fs;
+}
+
+int frrscript_load(struct frrscript *fs, const char *function_name,
+ int (*load_cb)(struct frrscript *))
+{
+
+ /* Set up the Lua script */
+ lua_State *L = luaL_newstate();
+
+ frrlua_export_logging(L);
+
+ char script_name[MAXPATHLEN];
+
+ if (snprintf(script_name, sizeof(script_name), "%s/%s.lua", scriptdir,
+ fs->name)
+ >= (int)sizeof(script_name)) {
+ zlog_err("frrscript: path to script %s/%s.lua is too long",
+ scriptdir, fs->name);
+ goto fail;
+ }
+
+ if (luaL_dofile(L, script_name) != 0) {
+ zlog_err("frrscript: failed loading script '%s': error: %s",
+ script_name, lua_tostring(L, -1));
+ goto fail;
+ }
+
+ /* To check the Lua function, we get it from the global table */
+ lua_getglobal(L, function_name);
+ if (lua_isfunction(L, lua_gettop(L)) == 0) {
+ zlog_err("frrscript: loaded script '%s' but %s not found",
+ script_name, function_name);
+ goto fail;
+ }
+ /* Then pop the function (frrscript_call will push it when it needs it)
+ */
+ lua_pop(L, 1);
+
+ if (load_cb && (*load_cb)(fs) != 0) {
+ zlog_err(
+ "frrscript: '%s': %s: loaded but callback returned non-zero exit code",
+ script_name, function_name);
+ goto fail;
+ }
+
+ /* Add the Lua function state to frrscript */
+ struct lua_function_state key = {.name = function_name, .L = L};
+
+ (void)hash_get(fs->lua_function_hash, &key, lua_function_alloc);
+
+ return 0;
+fail:
+ lua_close(L);
+ return 1;
+}
+
+void frrscript_delete(struct frrscript *fs)
+{
+ hash_clean(fs->lua_function_hash, lua_function_free);
+ hash_free(fs->lua_function_hash);
+ XFREE(MTYPE_SCRIPT, fs->name);
+ XFREE(MTYPE_SCRIPT, fs);
+}
+
+void frrscript_init(const char *sd)
+{
+ codec_hash = hash_create(codec_hash_key, codec_hash_cmp,
+ "Lua type encoders");
+
+ strlcpy(scriptdir, sd, sizeof(scriptdir));
+
+ /* Register core library types */
+ frrscript_register_type_codecs(frrscript_codecs_lib);
+}
+
+void frrscript_fini(void)
+{
+ hash_clean(codec_hash, codec_free);
+ hash_free(codec_hash);
+
+ frrscript_names_destroy();
+}
+#endif /* HAVE_SCRIPTING */
diff --git a/lib/frrscript.h b/lib/frrscript.h
new file mode 100644
index 0000000..7fa01f7
--- /dev/null
+++ b/lib/frrscript.h
@@ -0,0 +1,342 @@
+/* Scripting foo
+ * Copyright (C) 2020 NVIDIA Corporation
+ * Quentin Young
+ *
+ * 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 __FRRSCRIPT_H__
+#define __FRRSCRIPT_H__
+
+#include <zebra.h>
+
+#ifdef HAVE_SCRIPTING
+
+#include <lua.h>
+#include <nexthop.h>
+#include <nexthop_group.h>
+#include "frrlua.h"
+#include "bgpd/bgp_script.h" // for peer and attr encoders/decoders
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Forward declarations */
+extern struct zebra_dplane_ctx ctx;
+extern void lua_pushzebra_dplane_ctx(lua_State *L,
+ const struct zebra_dplane_ctx *ctx);
+extern void lua_decode_zebra_dplane_ctx(lua_State *L, int idx,
+ struct zebra_dplane_ctx *ctx);
+
+/*
+ * Script name hash
+ */
+PREDECL_HASH(frrscript_names);
+
+struct frrscript_names_entry {
+ /* Name of a Lua hook call */
+ char function_name[MAXPATHLEN];
+
+ /* Lua script in which to look for it */
+ char script_name[MAXPATHLEN];
+
+ struct frrscript_names_item item;
+};
+
+extern struct frrscript_names_head frrscript_names_hash;
+
+int frrscript_names_hash_cmp(const struct frrscript_names_entry *snhe1,
+ const struct frrscript_names_entry *snhe2);
+uint32_t frrscript_names_hash_key(const struct frrscript_names_entry *snhe);
+
+DECLARE_HASH(frrscript_names, struct frrscript_names_entry, item,
+ frrscript_names_hash_cmp, frrscript_names_hash_key);
+
+int frrscript_names_add_function_name(const char *function_name);
+void frrscript_names_destroy(void);
+int frrscript_names_set_script_name(const char *function_name,
+ const char *script_name);
+char *frrscript_names_get_script_name(const char *function_name);
+
+typedef void (*encoder_func)(lua_State *, const void *);
+typedef void *(*decoder_func)(lua_State *, int);
+
+struct frrscript_codec {
+ const char *typename;
+ encoder_func encoder;
+ decoder_func decoder;
+};
+
+struct lua_function_state {
+ const char *name;
+ lua_State *L;
+};
+
+struct frrscript {
+ /* Script name */
+ char *name;
+
+ /* Hash of Lua function name to Lua function state */
+ struct hash *lua_function_hash;
+};
+
+
+/*
+ * Hash related functions for lua_function_hash
+ */
+
+void *lua_function_alloc(void *arg);
+
+unsigned int lua_function_hash_key(const void *data);
+
+bool lua_function_hash_cmp(const void *d1, const void *d2);
+
+struct frrscript_env {
+ /* Value type */
+ const char *typename;
+
+ /* Binding name */
+ const char *name;
+
+ /* Value */
+ const void *val;
+};
+
+/*
+ * Create new struct frrscript for a Lua script.
+ * This will hold the states for the Lua functions in this script.
+ *
+ * scriptname
+ * Name of the Lua script file, without the .lua
+ */
+struct frrscript *frrscript_new(const char *scriptname);
+
+/*
+ * Load a function into frrscript, run callback if any
+ */
+int frrscript_load(struct frrscript *fs, const char *function_name,
+ int (*load_cb)(struct frrscript *));
+
+/*
+ * Delete Lua function states and frrscript
+ */
+void frrscript_delete(struct frrscript *fs);
+
+/*
+ * Register a Lua codec for a type.
+ *
+ * tname
+ * Name of type; e.g., "peer", "ospf_interface", etc. Chosen at will.
+ *
+ * codec(s)
+ * Function pointer to codec struct. Encoder function should push a Lua
+ * table representing the passed argument - which will have the C type
+ * associated with the chosen 'tname' to the provided stack. The decoder
+ * function should pop a value from the top of the stack and return a heap
+ * chunk containing that value. Allocations should be made with MTYPE_TMP.
+ *
+ * If using the plural function variant, pass a NULL-terminated array.
+ *
+ */
+void frrscript_register_type_codec(struct frrscript_codec *codec);
+void frrscript_register_type_codecs(struct frrscript_codec *codecs);
+
+/*
+ * Initialize scripting subsystem. Call this before anything else.
+ *
+ * scriptdir
+ * Directory in which to look for scripts
+ */
+void frrscript_init(const char *scriptdir);
+
+/*
+ * On shutdown clean up memory associated with the scripting subsystem
+ */
+void frrscript_fini(void);
+
+/*
+ * This macro is mapped to every (name, value) in frrscript_call,
+ * so this in turn maps them onto their encoders
+ */
+#define ENCODE_ARGS(name, value) ENCODE_ARGS_WITH_STATE(lfs->L, (value))
+
+/*
+ * This macro is also mapped to every (name, value) in frrscript_call, but
+ * not every value can be mapped to its decoder - only those that appear
+ * in the returned table will. To find out if they appear in the returned
+ * table, first pop the value and check if its nil. Only call the decoder
+ * if non-nil.
+ *
+ * At the end, the only thing left on the stack should be the
+ * returned table.
+ */
+#define DECODE_ARGS(name, value) \
+ do { \
+ lua_getfield(lfs->L, 1, (name)); \
+ if (lua_isnil(lfs->L, 2)) { \
+ lua_pop(lfs->L, 1); \
+ } else { \
+ DECODE_ARGS_WITH_STATE(lfs->L, (value)); \
+ } \
+ assert(lua_gettop(lfs->L) == 1); \
+ } while (0)
+
+/*
+ * Maps the type of value to its encoder/decoder.
+ * Add new mappings here.
+ *
+ * L
+ * Lua state
+ * scriptdir
+ * Directory in which to look for scripts
+ */
+#define ENCODE_ARGS_WITH_STATE(L, value) \
+ _Generic((value), \
+int : lua_pushinteger, \
+long long * : lua_pushintegerp, \
+struct prefix * : lua_pushprefix, \
+struct interface * : lua_pushinterface, \
+struct in_addr * : lua_pushinaddr, \
+struct in6_addr * : lua_pushin6addr, \
+union sockunion * : lua_pushsockunion, \
+time_t * : lua_pushtimet, \
+char * : lua_pushstring_wrapper, \
+struct attr * : lua_pushattr, \
+struct peer * : lua_pushpeer, \
+const struct prefix * : lua_pushprefix, \
+const struct ipaddr * : lua_pushipaddr, \
+const struct ethaddr * : lua_pushethaddr, \
+const struct nexthop_group * : lua_pushnexthop_group, \
+const struct nexthop * : lua_pushnexthop, \
+struct zebra_dplane_ctx * : lua_pushzebra_dplane_ctx \
+)((L), (value))
+
+#define DECODE_ARGS_WITH_STATE(L, value) \
+ _Generic((value), \
+int : lua_decode_integer_noop, \
+long long * : lua_decode_integerp, \
+struct prefix * : lua_decode_prefix, \
+struct interface * : lua_decode_interface, \
+struct in_addr * : lua_decode_inaddr, \
+struct in6_addr * : lua_decode_in6addr, \
+union sockunion * : lua_decode_sockunion, \
+time_t * : lua_decode_timet, \
+char * : lua_decode_stringp, \
+struct attr * : lua_decode_attr, \
+struct peer * : lua_decode_noop, \
+const struct prefix * : lua_decode_noop, \
+const struct ipaddr * : lua_decode_noop, \
+const struct ethaddr * : lua_decode_noop, \
+const struct nexthop_group * : lua_decode_noop, \
+const struct nexthop * : lua_decode_noop, \
+struct zebra_dplane_ctx * : lua_decode_noop \
+)((L), -1, (value))
+
+/*
+ * Call Lua function state (abstraction for a single Lua function)
+ *
+ * lfs
+ * The Lua function to call; this should have been loaded in by
+ * frrscript_load(). nargs Number of arguments the function accepts
+ *
+ * Returns:
+ * 0 if the script ran successfully, nonzero otherwise.
+ */
+int _frrscript_call_lua(struct lua_function_state *lfs, int nargs);
+
+/*
+ * Wrapper for calling Lua function state.
+ *
+ * The Lua function name (f) to run should have already been checked by
+ * frrscript_load. So this wrapper will:
+ * 1) Find the Lua function state, which contains the Lua state
+ * 2) Clear the Lua state (there may be leftovers items from previous call)
+ * 3) Push the Lua function (f)
+ * 4) Map frrscript_call arguments onto their encoder and decoders, push those
+ * 5) Call _frrscript_call_lua (Lua execution takes place)
+ * 6) Write back to frrscript_call arguments using their decoders
+ *
+ * This wrapper can be called multiple times (after one frrscript_load).
+ *
+ * fs
+ * The struct frrscript in which the Lua fuction was loaded into
+ * f
+ * Name of the Lua function.
+ *
+ * Returns:
+ * 0 if the script ran successfully, nonzero otherwise.
+ */
+#define frrscript_call(fs, f, ...) \
+ ({ \
+ struct lua_function_state lookup = {.name = (f)}; \
+ struct lua_function_state *lfs; \
+ lfs = hash_lookup((fs)->lua_function_hash, &lookup); \
+ lfs == NULL ? ({ \
+ zlog_err( \
+ "frrscript: '%s.lua': '%s': tried to call this function but it was not loaded", \
+ (fs)->name, (f)); \
+ 1; \
+ }) \
+ : ({ \
+ lua_settop(lfs->L, 0); \
+ lua_getglobal(lfs->L, f); \
+ MAP_LISTS(ENCODE_ARGS, ##__VA_ARGS__); \
+ _frrscript_call_lua( \
+ lfs, PP_NARG(__VA_ARGS__)); \
+ }) != 0 \
+ ? ({ \
+ zlog_err( \
+ "frrscript: '%s.lua': '%s': this function called but returned non-zero exit code. No variables modified.", \
+ (fs)->name, (f)); \
+ 1; \
+ }) \
+ : ({ \
+ MAP_LISTS(DECODE_ARGS, \
+ ##__VA_ARGS__); \
+ 0; \
+ }); \
+ })
+
+/*
+ * Get result from finished function
+ *
+ * fs
+ * The script. This script must have been run already.
+ * function_name
+ * Name of the Lua function.
+ * name
+ * Name of the result.
+ * This will be used as a string key to retrieve from the table that the
+ * Lua function returns.
+ * The name here should *not* appear in frrscript_call.
+ * lua_to
+ * Function pointer to a lua_to decoder function.
+ * This function should allocate and decode a value from the Lua state.
+ *
+ * Returns:
+ * A pointer to the decoded value from the Lua state, or NULL if no such
+ * value.
+ */
+void *frrscript_get_result(struct frrscript *fs, const char *function_name,
+ const char *name,
+ void *(*lua_to)(lua_State *L, int idx));
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* HAVE_SCRIPTING */
+
+#endif /* __FRRSCRIPT_H__ */
diff --git a/lib/frrstr.c b/lib/frrstr.c
new file mode 100644
index 0000000..1b98b22
--- /dev/null
+++ b/lib/frrstr.c
@@ -0,0 +1,235 @@
+/*
+ * FRR string processing utilities.
+ * Copyright (C) 2018 Cumulus Networks, Inc.
+ * Quentin Young
+ *
+ * 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 <string.h>
+#include <ctype.h>
+#include <sys/types.h>
+#ifdef HAVE_LIBPCREPOSIX
+#include <pcreposix.h>
+#else
+#include <regex.h>
+#endif /* HAVE_LIBPCREPOSIX */
+
+#include "frrstr.h"
+#include "memory.h"
+#include "vector.h"
+
+void frrstr_split(const char *string, const char *delimiter, char ***result,
+ int *argc)
+{
+ if (!string)
+ return;
+
+ unsigned int sz = 4, idx = 0;
+ char *copy, *copystart;
+ *result = XCALLOC(MTYPE_TMP, sizeof(char *) * sz);
+ copystart = copy = XSTRDUP(MTYPE_TMP, string);
+ *argc = 0;
+
+ const char *tok = NULL;
+
+ while (copy) {
+ tok = strsep(&copy, delimiter);
+ (*result)[idx] = XSTRDUP(MTYPE_TMP, tok);
+ if (++idx == sz)
+ *result = XREALLOC(MTYPE_TMP, *result,
+ (sz *= 2) * sizeof(char *));
+ (*argc)++;
+ }
+
+ XFREE(MTYPE_TMP, copystart);
+}
+
+vector frrstr_split_vec(const char *string, const char *delimiter)
+{
+ char **result;
+ int argc;
+
+ if (!string)
+ return NULL;
+
+ frrstr_split(string, delimiter, &result, &argc);
+
+ vector v = array_to_vector((void **)result, argc);
+
+ XFREE(MTYPE_TMP, result);
+
+ return v;
+}
+
+char *frrstr_join(const char **parts, int argc, const char *join)
+{
+ int i;
+ char *str;
+ char *p;
+ size_t len = 0;
+ size_t joinlen = join ? strlen(join) : 0;
+
+ if (!argc)
+ return NULL;
+
+ for (i = 0; i < argc; i++)
+ len += strlen(parts[i]);
+ len += argc * joinlen + 1;
+
+ if (!len)
+ return NULL;
+
+ p = str = XMALLOC(MTYPE_TMP, len);
+
+ for (i = 0; i < argc; i++) {
+ size_t arglen = strlen(parts[i]);
+
+ memcpy(p, parts[i], arglen);
+ p += arglen;
+ if (i + 1 != argc && join) {
+ memcpy(p, join, joinlen);
+ p += joinlen;
+ }
+ }
+
+ *p = '\0';
+
+ return str;
+}
+
+char *frrstr_join_vec(vector v, const char *join)
+{
+ char **argv;
+ int argc;
+
+ vector_to_array(v, (void ***)&argv, &argc);
+
+ char *ret = frrstr_join((const char **)argv, argc, join);
+
+ XFREE(MTYPE_TMP, argv);
+
+ return ret;
+}
+
+void frrstr_filter_vec(vector v, regex_t *filter)
+{
+ regmatch_t ignored[1];
+
+ for (unsigned int i = 0; i < vector_active(v); i++) {
+ if (regexec(filter, vector_slot(v, i), 0, ignored, 0)) {
+ XFREE(MTYPE_TMP, vector_slot(v, i));
+ vector_unset(v, i);
+ }
+ }
+}
+
+void frrstr_strvec_free(vector v)
+{
+ unsigned int i;
+ char *cp;
+
+ if (!v)
+ return;
+
+ for (i = 0; i < vector_active(v); i++) {
+ cp = vector_slot(v, i);
+ XFREE(MTYPE_TMP, cp);
+ }
+
+ vector_free(v);
+}
+
+char *frrstr_replace(const char *str, const char *find, const char *replace)
+{
+ char *ch;
+ char *nustr = XSTRDUP(MTYPE_TMP, str);
+
+ size_t findlen = strlen(find);
+ size_t repllen = strlen(replace);
+
+ while ((ch = strstr(nustr, find))) {
+ if (repllen > findlen) {
+ size_t nusz = strlen(nustr) + repllen - findlen + 1;
+ nustr = XREALLOC(MTYPE_TMP, nustr, nusz);
+ ch = strstr(nustr, find);
+ }
+
+ size_t nustrlen = strlen(nustr);
+ size_t taillen = (nustr + nustrlen) - (ch + findlen);
+
+ memmove(ch + findlen + (repllen - findlen), ch + findlen,
+ taillen + 1);
+ memcpy(ch, replace, repllen);
+ }
+
+ return nustr;
+}
+
+bool frrstr_startswith(const char *str, const char *prefix)
+{
+ if (!str || !prefix)
+ return false;
+
+ size_t lenstr = strlen(str);
+ size_t lenprefix = strlen(prefix);
+
+ if (lenprefix > lenstr)
+ return false;
+
+ return strncmp(str, prefix, lenprefix) == 0;
+}
+
+bool frrstr_endswith(const char *str, const char *suffix)
+{
+ if (!str || !suffix)
+ return false;
+
+ size_t lenstr = strlen(str);
+ size_t lensuffix = strlen(suffix);
+
+ if (lensuffix > lenstr)
+ return false;
+
+ return strncmp(&str[lenstr - lensuffix], suffix, lensuffix) == 0;
+}
+
+int all_digit(const char *str)
+{
+ for (; *str != '\0'; str++)
+ if (!isdigit((unsigned char)*str))
+ return 0;
+ return 1;
+}
+
+
+char *frrstr_hex(char *buff, size_t bufsiz, const uint8_t *str, size_t num)
+{
+ if (bufsiz == 0)
+ return buff;
+
+ char tmp[3];
+
+ buff[0] = '\0';
+
+ for (size_t i = 0; i < num; i++) {
+ snprintf(tmp, sizeof(tmp), "%02x", (unsigned char)str[i]);
+ strlcat(buff, tmp, bufsiz);
+ }
+
+ return buff;
+}
diff --git a/lib/frrstr.h b/lib/frrstr.h
new file mode 100644
index 0000000..d52d6a4
--- /dev/null
+++ b/lib/frrstr.h
@@ -0,0 +1,181 @@
+/*
+ * FRR string processing utilities.
+ * Copyright (C) 2018 Cumulus Networks, Inc.
+ * Quentin Young
+ *
+ * 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 _FRRSTR_H_
+#define _FRRSTR_H_
+
+#include <sys/types.h>
+#include <sys/types.h>
+#ifdef HAVE_LIBPCREPOSIX
+#include <pcreposix.h>
+#else
+#include <regex.h>
+#endif /* HAVE_LIBPCREPOSIX */
+#include <stdbool.h>
+
+#include "vector.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Tokenizes a string, storing tokens in a vector. Whitespace is ignored.
+ * Delimiter characters are not included.
+ *
+ * string
+ * The string to split
+ *
+ * delimiter
+ * Delimiter string, as used in strsep()
+ *
+ * Returns:
+ * The split string. Each token is allocated with MTYPE_TMP.
+ */
+void frrstr_split(const char *string, const char *delimiter, char ***result,
+ int *argc);
+vector frrstr_split_vec(const char *string, const char *delimiter);
+
+/*
+ * Concatenate string array into a single string.
+ *
+ * argv
+ * array of string pointers to concatenate
+ *
+ * argc
+ * array length
+ *
+ * join
+ * string to insert between each part, or NULL for nothing
+ *
+ * Returns:
+ * the joined string, allocated with MTYPE_TMP
+ */
+char *frrstr_join(const char **parts, int argc, const char *join);
+char *frrstr_join_vec(vector v, const char *join);
+
+/*
+ * Filter string vector.
+ * Removes lines that do not contain a match for the provided regex.
+ *
+ * v
+ * The vector to filter.
+ *
+ * filter
+ * Regex to filter with.
+ */
+void frrstr_filter_vec(vector v, regex_t *filter);
+
+/*
+ * Free allocated string vector.
+ * Assumes each item is allocated with MTYPE_TMP.
+ *
+ * v
+ * the vector to free
+ */
+void frrstr_strvec_free(vector v);
+
+/*
+ * Given a string, replaces all occurrences of a substring with a different
+ * string. The result is a new string. The original string is not modified.
+ *
+ * If 'replace' is longer than 'find', this function performs N+1 allocations,
+ * where N is the number of times 'find' occurs in 'str'. If 'replace' is equal
+ * in length or shorter than 'find', only 1 allocation is performed.
+ *
+ * str
+ * String to perform replacement on.
+ *
+ * find
+ * Substring to replace.
+ *
+ * replace
+ * String to replace 'find' with.
+ *
+ * Returns:
+ * A new string, allocated with MTYPE_TMP, that is the result of performing
+ * the replacement on 'str'. This must be freed by the caller.
+ */
+char *frrstr_replace(const char *str, const char *find, const char *replace);
+
+/*
+ * Prefix match for string.
+ *
+ * str
+ * string to check for prefix match
+ *
+ * prefix
+ * prefix to look for
+ *
+ * Returns:
+ * true if str starts with prefix, false otherwise
+ */
+bool frrstr_startswith(const char *str, const char *prefix);
+
+/*
+ * Suffix match for string.
+ *
+ * str
+ * string to check for suffix match
+ *
+ * suffix
+ * suffix to look for
+ *
+ * Returns:
+ * true if str ends with suffix, false otherwise
+ */
+bool frrstr_endswith(const char *str, const char *suffix);
+
+/*
+ * Check the string only contains digit characters.
+ *
+ * str
+ * string to check for digits
+ *
+ * Returns:
+ * 1 str only contains digit characters, 0 otherwise
+ */
+int all_digit(const char *str);
+
+/*
+ * Copy the hexadecimal representation of the string to a buffer.
+ *
+ * buff
+ * Buffer to copy result into with size of at least (2 * num) + 1.
+ *
+ * bufsiz
+ * Size of destination buffer.
+ *
+ * str
+ * String to represent as hexadecimal.
+ *
+ * num
+ * Number of characters to copy.
+ *
+ * Returns:
+ * Pointer to buffer containing resulting hexadecimal representation.
+ */
+char *frrstr_hex(char *buff, size_t bufsiz, const uint8_t *str, size_t num);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRRSTR_H_ */
diff --git a/lib/getopt.c b/lib/getopt.c
new file mode 100644
index 0000000..a33d196
--- /dev/null
+++ b/lib/getopt.c
@@ -0,0 +1,1024 @@
+/* Getopt for GNU.
+ * NOTE: getopt is now part of the C library, so if you don't know what
+ * "Keep this file name-space clean" means, talk to drepper@gnu.org
+ * before changing it!
+ *
+ * Copyright (C) 1987, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98
+ * Free Software Foundation, Inc.
+ *
+ * NOTE: The canonical source of this file is maintained with the GNU C Library.
+ * Bugs can be reported to bug-glibc@gnu.org.
+ *
+ * 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, 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
+ */
+
+/* This tells Alpha OSF/1 not to define a getopt prototype in <stdio.h>.
+ Ditto for AIX 3.2 and <stdlib.h>. */
+#ifndef _NO_PROTO
+# define _NO_PROTO
+#endif
+
+#include <zebra.h>
+
+#if !defined __STDC__ || !__STDC__
+/* This is a separate conditional since some stdc systems
+ reject `defined (const)'. */
+#ifndef const
+# define const
+#endif
+#endif
+
+#include <stdio.h>
+
+/* Comment out all this code if we are using the GNU C Library, and are not
+ actually compiling the library itself. This code is part of the GNU C
+ Library, but also included in many other GNU distributions. Compiling
+ and linking in this code is a waste when using the GNU C library
+ (especially if it is a shared library). Rather than having every GNU
+ program understand `configure --with-gnu-libc' and omit the object files,
+ it is simpler to just do this in the source for each such file. */
+
+#define GETOPT_INTERFACE_VERSION 2
+#if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2
+#include <gnu-versions.h>
+#if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION
+# define ELIDE_CODE
+#endif
+#endif
+
+#ifndef ELIDE_CODE
+
+
+/* This needs to come after some library #include
+ to get __GNU_LIBRARY__ defined. */
+#ifdef __GNU_LIBRARY__
+/* Don't include stdlib.h for non-GNU C libraries because some of them
+ contain conflicting prototypes for getopt. */
+#include <stdlib.h>
+#include <unistd.h>
+#endif /* GNU C library. */
+
+#ifdef VMS
+#include <unixlib.h>
+#if HAVE_STRING_H - 0
+#include <string.h>
+#endif
+#endif
+
+#ifndef _
+/* This is for other GNU distributions with internationalized messages.
+ When compiling libc, the _ macro is predefined. */
+#ifdef HAVE_LIBINTL_H
+#include <libintl.h>
+# define _(msgid) gettext (msgid)
+#else
+# define _(msgid) (msgid)
+#endif
+#endif
+
+/* This version of `getopt' appears to the caller like standard Unix `getopt'
+ but it behaves differently for the user, since it allows the user
+ to intersperse the options with the other arguments.
+
+ As `getopt' works, it permutes the elements of ARGV so that,
+ when it is done, all the options precede everything else. Thus
+ all application programs are extended to handle flexible argument order.
+
+ Setting the environment variable POSIXLY_CORRECT disables permutation.
+ Then the behavior is completely standard.
+
+ GNU application programs can use a third alternative mode in which
+ they can distinguish the relative order of options and other arguments. */
+
+#include "getopt.h"
+
+/* For communication from `getopt' to the caller.
+ When `getopt' finds an option that takes an argument,
+ the argument value is returned here.
+ Also, when `ordering' is RETURN_IN_ORDER,
+ each non-option ARGV-element is returned here. */
+
+char *optarg = NULL;
+
+/* Index in ARGV of the next element to be scanned.
+ This is used for communication to and from the caller
+ and for communication between successive calls to `getopt'.
+
+ On entry to `getopt', zero means this is the first call; initialize.
+
+ When `getopt' returns -1, this is the index of the first of the
+ non-option elements that the caller should itself scan.
+
+ Otherwise, `optind' communicates from one call to the next
+ how much of ARGV has been scanned so far. */
+
+/* 1003.2 says this must be 1 before any call. */
+int optind = 1;
+
+/* Formerly, initialization of getopt depended on optind==0, which
+ causes problems with re-calling getopt as programs generally don't
+ know that. */
+
+int __getopt_initialized = 0;
+
+/* The next char to be scanned in the option-element
+ in which the last option character we returned was found.
+ This allows us to pick up the scan where we left off.
+
+ If this is zero, or a null string, it means resume the scan
+ by advancing to the next ARGV-element. */
+
+static char *nextchar;
+
+/* Callers store zero here to inhibit the error message
+ for unrecognized options. */
+
+int opterr = 1;
+
+/* Set to an option character which was unrecognized.
+ This must be initialized on some systems to avoid linking in the
+ system's own getopt implementation. */
+
+int optopt = '?';
+
+/* Describe how to deal with options that follow non-option ARGV-elements.
+
+ If the caller did not specify anything,
+ the default is REQUIRE_ORDER if the environment variable
+ POSIXLY_CORRECT is defined, PERMUTE otherwise.
+
+ REQUIRE_ORDER means don't recognize them as options;
+ stop option processing when the first non-option is seen.
+ This is what Unix does.
+ This mode of operation is selected by either setting the environment
+ variable POSIXLY_CORRECT, or using `+' as the first character
+ of the list of option characters.
+
+ PERMUTE is the default. We permute the contents of ARGV as we scan,
+ so that eventually all the non-options are at the end. This allows options
+ to be given in any order, even with programs that were not written to
+ expect this.
+
+ RETURN_IN_ORDER is an option available to programs that were written
+ to expect options and other ARGV-elements in any order and that care about
+ the ordering of the two. We describe each non-option ARGV-element
+ as if it were the argument of an option with character code 1.
+ Using `-' as the first character of the list of option characters
+ selects this mode of operation.
+
+ The special argument `--' forces an end of option-scanning regardless
+ of the value of `ordering'. In the case of RETURN_IN_ORDER, only
+ `--' can cause `getopt' to return -1 with `optind' != ARGC. */
+
+static enum { REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER } ordering;
+
+/* Value of POSIXLY_CORRECT environment variable. */
+static char *posixly_correct;
+
+#ifdef __GNU_LIBRARY__
+/* We want to avoid inclusion of string.h with non-GNU libraries
+ because there are many ways it can cause trouble.
+ On some systems, it contains special magic macros that don't work
+ in GCC. */
+#include <string.h>
+# define my_index strchr
+#else
+
+#if HAVE_STRING_H
+#include <string.h>
+#else
+#include <strings.h>
+#endif
+
+/* Avoid depending on library functions or files
+ whose names are inconsistent. */
+
+#ifndef getenv
+extern char *getenv(const char *);
+#endif
+
+static char *my_index(const char *str, int chr)
+{
+ while (*str) {
+ if (*str == chr)
+ return (char *)str;
+ str++;
+ }
+ return 0;
+}
+
+/* If using GCC, we can safely declare strlen this way.
+ If not using GCC, it is ok not to declare it. */
+#ifdef __GNUC__
+/* Note that Motorola Delta 68k R3V7 comes with GCC but not stddef.h.
+ That was relevant to code that was here before. */
+#if (!defined __STDC__ || !__STDC__) && !defined strlen
+/* gcc with -traditional declares the built-in strlen to return int,
+ and has done so at least since version 2.4.5. -- rms. */
+extern int strlen(const char *);
+#endif /* not __STDC__ */
+#endif /* __GNUC__ */
+
+#endif /* not __GNU_LIBRARY__ */
+
+/* Handle permutation of arguments. */
+
+/* Describe the part of ARGV that contains non-options that have
+ been skipped. `first_nonopt' is the index in ARGV of the first of them;
+ `last_nonopt' is the index after the last of them. */
+
+static int first_nonopt;
+static int last_nonopt;
+
+#ifdef _LIBC
+/* Bash 2.0 gives us an environment variable containing flags
+ indicating ARGV elements that should not be considered arguments. */
+
+/* Defined in getopt_init.c */
+extern char *__getopt_nonoption_flags;
+
+static int nonoption_flags_max_len;
+static int nonoption_flags_len;
+
+static int original_argc;
+static char *const *original_argv;
+
+/* Make sure the environment variable bash 2.0 puts in the environment
+ is valid for the getopt call we must make sure that the ARGV passed
+ to getopt is that one passed to the process. */
+static void __attribute__((unused))
+store_args_and_env(int argc, char *const *argv)
+{
+ /* XXX This is no good solution. We should rather copy the args so
+ that we can compare them later. But we must not use malloc(3). */
+ original_argc = argc;
+ original_argv = argv;
+}
+#ifdef text_set_element
+text_set_element(__libc_subinit, store_args_and_env);
+#endif /* text_set_element */
+
+#define SWAP_FLAGS(ch1, ch2) \
+ if (nonoption_flags_len > 0) { \
+ char __tmp = __getopt_nonoption_flags[ch1]; \
+ __getopt_nonoption_flags[ch1] = __getopt_nonoption_flags[ch2]; \
+ __getopt_nonoption_flags[ch2] = __tmp; \
+ }
+#else /* !_LIBC */
+# define SWAP_FLAGS(ch1, ch2)
+#endif /* _LIBC */
+
+/* Exchange two adjacent subsequences of ARGV.
+ One subsequence is elements [first_nonopt,last_nonopt)
+ which contains all the non-options that have been skipped so far.
+ The other is elements [last_nonopt,optind), which contains all
+ the options processed since those non-options were skipped.
+
+ `first_nonopt' and `last_nonopt' are relocated so that they describe
+ the new indices of the non-options in ARGV after they are moved. */
+
+#if defined __STDC__ && __STDC__
+static void exchange(char **);
+#endif
+
+static void exchange(argv) char **argv;
+{
+ int bottom = first_nonopt;
+ int middle = last_nonopt;
+ int top = optind;
+ char *tem;
+
+/* Exchange the shorter segment with the far end of the longer segment.
+ That puts the shorter segment into the right place.
+ It leaves the longer segment in the right place overall,
+ but it consists of two parts that need to be swapped next. */
+
+#ifdef _LIBC
+ /* First make sure the handling of the `__getopt_nonoption_flags'
+ string can work normally. Our top argument must be in the range
+ of the string. */
+ if (nonoption_flags_len > 0 && top >= nonoption_flags_max_len) {
+ /* We must extend the array. The user plays games with us and
+ presents new arguments. */
+ char *new_str = malloc(top + 1);
+ if (new_str == NULL)
+ nonoption_flags_len = nonoption_flags_max_len = 0;
+ else {
+ memset(__mempcpy(new_str, __getopt_nonoption_flags,
+ nonoption_flags_max_len),
+ '\0', top + 1 - nonoption_flags_max_len);
+ nonoption_flags_max_len = top + 1;
+ __getopt_nonoption_flags = new_str;
+ }
+ }
+#endif
+
+ while (top > middle && middle > bottom) {
+ if (top - middle > middle - bottom) {
+ /* Bottom segment is the short one. */
+ int len = middle - bottom;
+ register int i;
+
+ /* Swap it with the top part of the top segment. */
+ for (i = 0; i < len; i++) {
+ tem = argv[bottom + i];
+ argv[bottom + i] =
+ argv[top - (middle - bottom) + i];
+ argv[top - (middle - bottom) + i] = tem;
+ SWAP_FLAGS(bottom + i,
+ top - (middle - bottom) + i);
+ }
+ /* Exclude the moved bottom segment from further
+ * swapping. */
+ top -= len;
+ } else {
+ /* Top segment is the short one. */
+ int len = top - middle;
+ register int i;
+
+ /* Swap it with the bottom part of the bottom segment.
+ */
+ for (i = 0; i < len; i++) {
+ tem = argv[bottom + i];
+ argv[bottom + i] = argv[middle + i];
+ argv[middle + i] = tem;
+ SWAP_FLAGS(bottom + i, middle + i);
+ }
+ /* Exclude the moved top segment from further swapping.
+ */
+ bottom += len;
+ }
+ }
+
+ /* Update records for the slots the non-options now occupy. */
+
+ first_nonopt += (optind - last_nonopt);
+ last_nonopt = optind;
+}
+
+/* Initialize the internal data when the first call is made. */
+
+#if defined __STDC__ && __STDC__
+static const char *_getopt_initialize(int, char *const *, const char *);
+#endif
+static const char *_getopt_initialize(argc, argv, optstring) int argc;
+char *const *argv;
+const char *optstring;
+{
+ /* Start processing options with ARGV-element 1 (since ARGV-element 0
+ is the program name); the sequence of previously skipped
+ non-option ARGV-elements is empty. */
+
+ first_nonopt = last_nonopt = optind;
+
+ nextchar = NULL;
+
+ posixly_correct = getenv("POSIXLY_CORRECT");
+
+ /* Determine how to handle the ordering of options and nonoptions. */
+
+ if (optstring[0] == '-') {
+ ordering = RETURN_IN_ORDER;
+ ++optstring;
+ } else if (optstring[0] == '+') {
+ ordering = REQUIRE_ORDER;
+ ++optstring;
+ } else if (posixly_correct != NULL)
+ ordering = REQUIRE_ORDER;
+ else
+ ordering = PERMUTE;
+
+#ifdef _LIBC
+ if (posixly_correct == NULL && argc == original_argc
+ && argv == original_argv) {
+ if (nonoption_flags_max_len == 0) {
+ if (__getopt_nonoption_flags == NULL
+ || __getopt_nonoption_flags[0] == '\0')
+ nonoption_flags_max_len = -1;
+ else {
+ const char *orig_str = __getopt_nonoption_flags;
+ int len = nonoption_flags_max_len =
+ strlen(orig_str);
+ if (nonoption_flags_max_len < argc)
+ nonoption_flags_max_len = argc;
+ __getopt_nonoption_flags =
+ (char *)malloc(nonoption_flags_max_len);
+ if (__getopt_nonoption_flags == NULL)
+ nonoption_flags_max_len = -1;
+ else
+ memset(__mempcpy(
+ __getopt_nonoption_flags,
+ orig_str, len),
+ '\0',
+ nonoption_flags_max_len - len);
+ }
+ }
+ nonoption_flags_len = nonoption_flags_max_len;
+ } else
+ nonoption_flags_len = 0;
+#endif
+
+ return optstring;
+}
+
+/* Scan elements of ARGV (whose length is ARGC) for option characters
+ given in OPTSTRING.
+
+ If an element of ARGV starts with '-', and is not exactly "-" or "--",
+ then it is an option element. The characters of this element
+ (aside from the initial '-') are option characters. If `getopt'
+ is called repeatedly, it returns successively each of the option characters
+ from each of the option elements.
+
+ If `getopt' finds another option character, it returns that character,
+ updating `optind' and `nextchar' so that the next call to `getopt' can
+ resume the scan with the following option character or ARGV-element.
+
+ If there are no more option characters, `getopt' returns -1.
+ Then `optind' is the index in ARGV of the first ARGV-element
+ that is not an option. (The ARGV-elements have been permuted
+ so that those that are not options now come last.)
+
+ OPTSTRING is a string containing the legitimate option characters.
+ If an option character is seen that is not listed in OPTSTRING,
+ return '?' after printing an error message. If you set `opterr' to
+ zero, the error message is suppressed but we still return '?'.
+
+ If a char in OPTSTRING is followed by a colon, that means it wants an arg,
+ so the following text in the same ARGV-element, or the text of the following
+ ARGV-element, is returned in `optarg'. Two colons mean an option that
+ wants an optional arg; if there is text in the current ARGV-element,
+ it is returned in `optarg', otherwise `optarg' is set to zero.
+
+ If OPTSTRING starts with `-' or `+', it requests different methods of
+ handling the non-option ARGV-elements.
+ See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above.
+
+ Long-named options begin with `--' instead of `-'.
+ Their names may be abbreviated as long as the abbreviation is unique
+ or is an exact match for some defined option. If they have an
+ argument, it follows the option name in the same ARGV-element, separated
+ from the option name by a `=', or else the in next ARGV-element.
+ When `getopt' finds a long-named option, it returns 0 if that option's
+ `flag' field is nonzero, the value of the option's `val' field
+ if the `flag' field is zero.
+
+ The elements of ARGV aren't really const, because we permute them.
+ But we pretend they're const in the prototype to be compatible
+ with other systems.
+
+ LONGOPTS is a vector of `struct option' terminated by an
+ element containing a name which is zero.
+
+ LONGIND returns the index in LONGOPT of the long-named option found.
+ It is only valid when a long-named option has been found by the most
+ recent call.
+
+ If LONG_ONLY is nonzero, '-' as well as '--' can introduce
+ long-named options. */
+
+int _getopt_internal(argc, argv, optstring, longopts, longind,
+ long_only) int argc;
+char *const *argv;
+const char *optstring;
+const struct option *longopts;
+int *longind;
+int long_only;
+{
+ optarg = NULL;
+
+ if (optind == 0 || !__getopt_initialized) {
+ if (optind == 0)
+ optind = 1; /* Don't scan ARGV[0], the program name. */
+ optstring = _getopt_initialize(argc, argv, optstring);
+ __getopt_initialized = 1;
+ }
+
+/* Test whether ARGV[optind] points to a non-option argument.
+ Either it does not have option syntax, or there is an environment flag
+ from the shell indicating it is not an option. The later information
+ is only used when the used in the GNU libc. */
+#ifdef _LIBC
+#define NONOPTION_P \
+ (argv[optind][0] != '-' || argv[optind][1] == '\0' \
+ || (optind < nonoption_flags_len \
+ && __getopt_nonoption_flags[optind] == '1'))
+#else
+# define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0')
+#endif
+
+ if (nextchar == NULL || *nextchar == '\0') {
+ /* Advance to the next ARGV-element. */
+
+ /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has
+ been
+ moved back by the user (who may also have changed the
+ arguments). */
+ if (last_nonopt > optind)
+ last_nonopt = optind;
+ if (first_nonopt > optind)
+ first_nonopt = optind;
+
+ if (ordering == PERMUTE) {
+ /* If we have just processed some options following some
+ non-options,
+ exchange them so that the options come first. */
+
+ if (first_nonopt != last_nonopt
+ && last_nonopt != optind)
+ exchange((char **)argv);
+ else if (last_nonopt != optind)
+ first_nonopt = optind;
+
+ /* Skip any additional non-options
+ and extend the range of non-options previously
+ skipped. */
+
+ while (optind < argc && NONOPTION_P)
+ optind++;
+ last_nonopt = optind;
+ }
+
+ /* The special ARGV-element `--' means premature end of options.
+ Skip it like a null option,
+ then exchange with previous non-options as if it were an
+ option,
+ then skip everything else like a non-option. */
+
+ if (optind != argc && !strcmp(argv[optind], "--")) {
+ optind++;
+
+ if (first_nonopt != last_nonopt
+ && last_nonopt != optind)
+ exchange((char **)argv);
+ else if (first_nonopt == last_nonopt)
+ first_nonopt = optind;
+ last_nonopt = argc;
+
+ optind = argc;
+ }
+
+ /* If we have done all the ARGV-elements, stop the scan
+ and back over any non-options that we skipped and permuted.
+ */
+
+ if (optind == argc) {
+ /* Set the next-arg-index to point at the non-options
+ that we previously skipped, so the caller will digest
+ them. */
+ if (first_nonopt != last_nonopt)
+ optind = first_nonopt;
+ return -1;
+ }
+
+ /* If we have come to a non-option and did not permute it,
+ either stop the scan or describe it to the caller and pass it
+ by. */
+
+ if (NONOPTION_P) {
+ if (ordering == REQUIRE_ORDER)
+ return -1;
+ optarg = argv[optind++];
+ return 1;
+ }
+
+ /* We have found another option-ARGV-element.
+ Skip the initial punctuation. */
+
+ nextchar = (argv[optind] + 1
+ + (longopts != NULL && argv[optind][1] == '-'));
+ }
+
+ /* Decode the current option-ARGV-element. */
+
+ /* Check whether the ARGV-element is a long option.
+
+ If long_only and the ARGV-element has the form "-f", where f is
+ a valid short option, don't consider it an abbreviated form of
+ a long option that starts with f. Otherwise there would be no
+ way to give the -f short option.
+
+ On the other hand, if there's a long option "fubar" and
+ the ARGV-element is "-fu", do consider that an abbreviation of
+ the long option, just like "--fu", and not "-f" with arg "u".
+
+ This distinction seems to be the most useful approach. */
+
+ if (longopts != NULL
+ && (argv[optind][1] == '-'
+ || (long_only && (argv[optind][2]
+ || !my_index(optstring, argv[optind][1]))))) {
+ char *nameend;
+ const struct option *p;
+ const struct option *pfound = NULL;
+ int exact = 0;
+ int ambig = 0;
+ int indfound = -1;
+ int option_index;
+
+ for (nameend = nextchar; *nameend && *nameend != '='; nameend++)
+ /* Do nothing. */;
+
+ /* Test all long options for either exact match
+ or abbreviated matches. */
+ for (p = longopts, option_index = 0; p->name;
+ p++, option_index++)
+ if (!strncmp(p->name, nextchar, nameend - nextchar)) {
+ if ((unsigned int)(nameend - nextchar)
+ == (unsigned int)strlen(p->name)) {
+ /* Exact match found. */
+ pfound = p;
+ indfound = option_index;
+ exact = 1;
+ break;
+ } else if (pfound == NULL) {
+ /* First nonexact match found. */
+ pfound = p;
+ indfound = option_index;
+ } else
+ /* Second or later nonexact match found.
+ */
+ ambig = 1;
+ }
+
+ if (ambig && !exact) {
+ if (opterr)
+ fprintf(stderr,
+ _("%s: option `%s' is ambiguous\n"),
+ argv[0], argv[optind]);
+ nextchar += strlen(nextchar);
+ optind++;
+ optopt = 0;
+ return '?';
+ }
+
+ if (pfound != NULL) {
+ option_index = indfound;
+ optind++;
+ if (*nameend) {
+ /* Don't test has_arg with >, because some C
+ compilers don't
+ allow it to be used on enums. */
+ if (pfound->has_arg)
+ optarg = nameend + 1;
+ else {
+ if (opterr) {
+ if (argv[optind - 1][1] == '-')
+ /* --option */
+ fprintf(stderr,
+ _("%s: option `--%s' doesn't allow an argument\n"),
+ argv[0],
+ pfound->name);
+ else
+ /* +option or -option */
+ fprintf(stderr,
+ _("%s: option `%c%s' doesn't allow an argument\n"),
+ argv[0],
+ argv[optind - 1]
+ [0],
+ pfound->name);
+ }
+
+ nextchar += strlen(nextchar);
+
+ optopt = pfound->val;
+ return '?';
+ }
+ } else if (pfound->has_arg == 1) {
+ if (optind < argc)
+ optarg = argv[optind++];
+ else {
+ if (opterr)
+ fprintf(stderr,
+ _("%s: option `%s' requires an argument\n"),
+ argv[0],
+ argv[optind - 1]);
+ nextchar += strlen(nextchar);
+ optopt = pfound->val;
+ return optstring[0] == ':' ? ':' : '?';
+ }
+ }
+ nextchar += strlen(nextchar);
+ if (longind != NULL)
+ *longind = option_index;
+ if (pfound->flag) {
+ *(pfound->flag) = pfound->val;
+ return 0;
+ }
+ return pfound->val;
+ }
+
+ /* Can't find it as a long option. If this is not
+ getopt_long_only,
+ or the option starts with '--' or is not a valid short
+ option, then it's an error.
+ Otherwise interpret it as a short option. */
+ if (!long_only || argv[optind][1] == '-'
+ || my_index(optstring, *nextchar) == NULL) {
+ if (opterr) {
+ if (argv[optind][1] == '-')
+ /* --option */
+ fprintf(stderr,
+ _("%s: unrecognized option `--%s'\n"),
+ argv[0], nextchar);
+ else
+ /* +option or -option */
+ fprintf(stderr,
+ _("%s: unrecognized option `%c%s'\n"),
+ argv[0], argv[optind][0],
+ nextchar);
+ }
+ nextchar = (char *)"";
+ optind++;
+ optopt = 0;
+ return '?';
+ }
+ }
+
+ /* Look at and handle the next short option-character. */
+
+ {
+ char c = *nextchar++;
+ char *temp = my_index(optstring, c);
+
+ /* Increment `optind' when we start to process its last
+ * character. */
+ if (*nextchar == '\0')
+ ++optind;
+
+ if (temp == NULL || c == ':') {
+ if (opterr) {
+ if (posixly_correct)
+ /* 1003.2 specifies the format of this
+ * message. */
+ fprintf(stderr,
+ _("%s: illegal option -- %c\n"),
+ argv[0], c);
+ else
+ fprintf(stderr,
+ _("%s: invalid option -- %c\n"),
+ argv[0], c);
+ }
+ optopt = c;
+ return '?';
+ }
+ /* Convenience. Treat POSIX -W foo same as long option --foo */
+ if (temp[0] == 'W' && temp[1] == ';') {
+ char *nameend;
+ const struct option *p;
+ const struct option *pfound = NULL;
+ int exact = 0;
+ int ambig = 0;
+ int indfound = 0;
+ int option_index;
+
+ /* This is an option that requires an argument. */
+ if (*nextchar != '\0') {
+ optarg = nextchar;
+ /* If we end this ARGV-element by taking the
+ rest as an arg,
+ we must advance to the next element now. */
+ optind++;
+ } else if (optind == argc) {
+ if (opterr) {
+ /* 1003.2 specifies the format of this
+ * message. */
+ fprintf(stderr,
+ _("%s: option requires an argument -- %c\n"),
+ argv[0], c);
+ }
+ optopt = c;
+ if (optstring[0] == ':')
+ c = ':';
+ else
+ c = '?';
+ return c;
+ } else
+ /* We already incremented `optind' once;
+ increment it again when taking next ARGV-elt
+ as argument. */
+ optarg = argv[optind++];
+
+ /* optarg is now the argument, see if it's in the
+ table of longopts. */
+
+ for (nextchar = nameend = optarg;
+ *nameend && *nameend != '='; nameend++)
+ /* Do nothing. */;
+
+ /* Test all long options for either exact match
+ or abbreviated matches. */
+ for (p = longopts, option_index = 0; p->name;
+ p++, option_index++)
+ if (!strncmp(p->name, nextchar,
+ nameend - nextchar)) {
+ if ((unsigned int)(nameend - nextchar)
+ == strlen(p->name)) {
+ /* Exact match found. */
+ pfound = p;
+ indfound = option_index;
+ exact = 1;
+ break;
+ } else if (pfound == NULL) {
+ /* First nonexact match found.
+ */
+ pfound = p;
+ indfound = option_index;
+ } else
+ /* Second or later nonexact
+ * match found. */
+ ambig = 1;
+ }
+ if (ambig && !exact) {
+ if (opterr)
+ fprintf(stderr,
+ _("%s: option `-W %s' is ambiguous\n"),
+ argv[0], argv[optind]);
+ nextchar += strlen(nextchar);
+ optind++;
+ return '?';
+ }
+ if (pfound != NULL) {
+ option_index = indfound;
+ if (*nameend) {
+ /* Don't test has_arg with >, because
+ some C compilers don't
+ allow it to be used on enums. */
+ if (pfound->has_arg)
+ optarg = nameend + 1;
+ else {
+ if (opterr)
+ fprintf(stderr, _("\
+%s: option `-W %s' doesn't allow an argument\n"),
+ argv[0],
+ pfound->name);
+
+ nextchar += strlen(nextchar);
+ return '?';
+ }
+ } else if (pfound->has_arg == 1) {
+ if (optind < argc)
+ optarg = argv[optind++];
+ else {
+ if (opterr)
+ fprintf(stderr,
+ _("%s: option `%s' requires an argument\n"),
+ argv[0],
+ argv[optind
+ - 1]);
+ nextchar += strlen(nextchar);
+ return optstring[0] == ':'
+ ? ':'
+ : '?';
+ }
+ }
+ nextchar += strlen(nextchar);
+ if (longind != NULL)
+ *longind = option_index;
+ if (pfound->flag) {
+ *(pfound->flag) = pfound->val;
+ return 0;
+ }
+ return pfound->val;
+ }
+ nextchar = NULL;
+ return 'W'; /* Let the application handle it. */
+ }
+ if (temp[1] == ':') {
+ if (temp[2] == ':') {
+ /* This is an option that accepts an argument
+ * optionally. */
+ if (*nextchar != '\0') {
+ optarg = nextchar;
+ optind++;
+ } else
+ optarg = NULL;
+ nextchar = NULL;
+ } else {
+ /* This is an option that requires an argument.
+ */
+ if (*nextchar != '\0') {
+ optarg = nextchar;
+ /* If we end this ARGV-element by taking
+ the rest as an arg,
+ we must advance to the next element
+ now. */
+ optind++;
+ } else if (optind == argc) {
+ if (opterr) {
+ /* 1003.2 specifies the format
+ * of this message. */
+ fprintf(stderr,
+ _("%s: option requires an argument -- %c\n"),
+ argv[0], c);
+ }
+ optopt = c;
+ if (optstring[0] == ':')
+ c = ':';
+ else
+ c = '?';
+ } else
+ /* We already incremented `optind' once;
+ increment it again when taking next
+ ARGV-elt as argument. */
+ optarg = argv[optind++];
+ nextchar = NULL;
+ }
+ }
+ return c;
+ }
+}
+
+#ifdef REALLY_NEED_PLAIN_GETOPT
+
+int getopt(argc, argv, optstring) int argc;
+char *const *argv;
+const char *optstring;
+{
+ return _getopt_internal(argc, argv, optstring, (const struct option *)0,
+ (int *)0, 0);
+}
+
+#endif /* REALLY_NEED_PLAIN_GETOPT */
+
+#endif /* Not ELIDE_CODE. */
+
+#ifdef TEST
+
+/* Compile with -DTEST to make an executable for use in testing
+ the above definition of `getopt'. */
+
+int main(argc, argv) int argc;
+char **argv;
+{
+ int c;
+ int digit_optind = 0;
+
+ while (1) {
+ int this_option_optind = optind ? optind : 1;
+
+ c = getopt(argc, argv, "abc:d:0123456789");
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if (digit_optind != 0
+ && digit_optind != this_option_optind)
+ printf("digits occur in two different argv-elements.\n");
+ digit_optind = this_option_optind;
+ printf("option %c\n", c);
+ break;
+
+ case 'a':
+ printf("option a\n");
+ break;
+
+ case 'b':
+ printf("option b\n");
+ break;
+
+ case 'c':
+ printf("option c with value `%s'\n", optarg);
+ break;
+
+ case '?':
+ break;
+
+ default:
+ printf("?? getopt returned character code 0%o ??\n", c);
+ }
+ }
+
+ if (optind < argc) {
+ printf("non-option ARGV-elements: ");
+ while (optind < argc)
+ printf("%s ", argv[optind++]);
+ printf("\n");
+ }
+
+ exit(0);
+}
+
+#endif /* TEST */
diff --git a/lib/getopt.h b/lib/getopt.h
new file mode 100644
index 0000000..63e12e9
--- /dev/null
+++ b/lib/getopt.h
@@ -0,0 +1,156 @@
+/* Declarations for getopt.
+ * Copyright (C) 1989,90,91,92,93,94,96,97 Free Software Foundation, Inc.
+ *
+ * NOTE: The canonical source of this file is maintained with the GNU C Library.
+ * Bugs can be reported to bug-glibc@gnu.org.
+ *
+ * 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, 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 _GETOPT_H
+#define _GETOPT_H 1
+
+/*
+ * The operating system may or may not provide getopt_long(), and if
+ * so it may or may not be a version we are willing to use. Our
+ * strategy is to declare getopt here, and then provide code unless
+ * the supplied version is adequate. The difficult case is when a
+ * declaration for getopt is provided, as our declaration must match.
+ *
+ * XXX Arguably this version should be named differently, and the
+ * local names defined to refer to the system version when we choose
+ * to use the system version.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* For communication from `getopt' to the caller.
+ When `getopt' finds an option that takes an argument,
+ the argument value is returned here.
+ Also, when `ordering' is RETURN_IN_ORDER,
+ each non-option ARGV-element is returned here. */
+
+extern char *optarg;
+
+/* Index in ARGV of the next element to be scanned.
+ This is used for communication to and from the caller
+ and for communication between successive calls to `getopt'.
+
+ On entry to `getopt', zero means this is the first call; initialize.
+
+ When `getopt' returns -1, this is the index of the first of the
+ non-option elements that the caller should itself scan.
+
+ Otherwise, `optind' communicates from one call to the next
+ how much of ARGV has been scanned so far. */
+
+extern int optind;
+
+/* Callers store zero here to inhibit the error message `getopt' prints
+ for unrecognized options. */
+
+extern int opterr;
+
+/* Set to an option character which was unrecognized. */
+
+extern int optopt;
+
+/* Describe the long-named options requested by the application.
+ The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector
+ of `struct option' terminated by an element containing a name which is
+ zero.
+
+ The field `has_arg' is:
+ no_argument (or 0) if the option does not take an argument,
+ required_argument (or 1) if the option requires an argument,
+ optional_argument (or 2) if the option takes an optional argument.
+
+ If the field `flag' is not NULL, it points to a variable that is set
+ to the value given in the field `val' when the option is found, but
+ left unchanged if the option is not found.
+
+ To have a long-named option do something other than set an `int' to
+ a compiled-in constant, such as set a value from `optarg', set the
+ option's `flag' field to zero and its `val' field to a nonzero
+ value (the equivalent single-letter option character, if there is
+ one). For long options that have a zero `flag' field, `getopt'
+ returns the contents of the `val' field. */
+
+struct option {
+#if defined(__STDC__) && __STDC__
+ const char *name;
+#else
+ char *name;
+#endif
+ /* has_arg can't be an enum because some compilers complain about
+ type mismatches in all the code that assumes it is an int. */
+ int has_arg;
+ int *flag;
+ int val;
+};
+
+/* Names for the values of the `has_arg' field of `struct option'. */
+
+#define no_argument 0
+#define required_argument 1
+#define optional_argument 2
+
+#if defined(__STDC__) && __STDC__
+
+#ifdef REALLY_NEED_PLAIN_GETOPT
+
+/*
+ * getopt is defined in POSIX.2. Assume that if the system defines
+ * getopt that it complies with POSIX.2. If not, an autoconf test
+ * should be written to define NONPOSIX_GETOPT_DEFINITION.
+ */
+#ifndef NONPOSIX_GETOPT_DEFINITION
+extern int getopt(int argc, char *const *argv, const char *shortopts);
+#else /* NONPOSIX_GETOPT_DEFINITION */
+extern int getopt(void);
+#endif /* NONPOSIX_GETOPT_DEFINITION */
+
+#endif
+
+
+extern int getopt_long(int argc, char *const *argv, const char *shortopts,
+ const struct option *longopts, int *longind);
+extern int getopt_long_only(int argc, char *const *argv, const char *shortopts,
+ const struct option *longopts, int *longind);
+
+/* Internal only. Users should not call this directly. */
+extern int _getopt_internal(int argc, char *const *argv, const char *shortopts,
+ const struct option *longopts, int *longind,
+ int long_only);
+#else /* not __STDC__ */
+
+#ifdef REALLY_NEED_PLAIN_GETOPT
+extern int getopt();
+#endif /* REALLY_NEED_PLAIN_GETOPT */
+
+extern int getopt_long();
+extern int getopt_long_only();
+
+extern int _getopt_internal();
+
+#endif /* __STDC__ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* getopt.h */
diff --git a/lib/getopt1.c b/lib/getopt1.c
new file mode 100644
index 0000000..a7fe253
--- /dev/null
+++ b/lib/getopt1.c
@@ -0,0 +1,176 @@
+/* getopt_long and getopt_long_only entry points for GNU getopt.
+ * Copyright (C) 1987,88,89,90,91,92,93,94,96,97,98
+ * Free Software Foundation, Inc.
+ *
+ * NOTE: The canonical source of this file is maintained with the GNU C Library.
+ * Bugs can be reported to bug-glibc@gnu.org.
+ *
+ * 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, 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 "getopt.h"
+
+#if !defined __STDC__ || !__STDC__
+/* This is a separate conditional since some stdc systems
+ reject `defined (const)'. */
+#ifndef const
+#define const
+#endif
+#endif
+
+#include <stdio.h>
+
+/* Comment out all this code if we are using the GNU C Library, and are not
+ actually compiling the library itself. This code is part of the GNU C
+ Library, but also included in many other GNU distributions. Compiling
+ and linking in this code is a waste when using the GNU C library
+ (especially if it is a shared library). Rather than having every GNU
+ program understand `configure --with-gnu-libc' and omit the object files,
+ it is simpler to just do this in the source for each such file. */
+
+#define GETOPT_INTERFACE_VERSION 2
+#if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2
+#include <gnu-versions.h>
+#if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION
+#define ELIDE_CODE
+#endif
+#endif
+
+#ifndef ELIDE_CODE
+
+
+/* This needs to come after some library #include
+ to get __GNU_LIBRARY__ defined. */
+#ifdef __GNU_LIBRARY__
+#include <stdlib.h>
+#endif
+
+#ifndef NULL
+#define NULL 0
+#endif
+
+int getopt_long(argc, argv, options, long_options, opt_index) int argc;
+char *const *argv;
+const char *options;
+const struct option *long_options;
+int *opt_index;
+{
+ return _getopt_internal(argc, argv, options, long_options, opt_index,
+ 0);
+}
+
+/* Like getopt_long, but '-' as well as '--' can indicate a long option.
+ If an option that starts with '-' (not '--') doesn't match a long option,
+ but does match a short option, it is parsed as a short option
+ instead. */
+
+int getopt_long_only(argc, argv, options, long_options, opt_index) int argc;
+char *const *argv;
+const char *options;
+const struct option *long_options;
+int *opt_index;
+{
+ return _getopt_internal(argc, argv, options, long_options, opt_index,
+ 1);
+}
+
+
+#endif /* Not ELIDE_CODE. */
+
+#ifdef TEST
+
+#include <stdio.h>
+
+int main(argc, argv) int argc;
+char **argv;
+{
+ int c;
+ int digit_optind = 0;
+
+ while (1) {
+ int this_option_optind = optind ? optind : 1;
+ int option_index = 0;
+ static struct option long_options[] = {
+ {"add", 1, 0, 0}, {"append", 0, 0, 0},
+ {"delete", 1, 0, 0}, {"verbose", 0, 0, 0},
+ {"create", 0, 0, 0}, {"file", 1, 0, 0},
+ {0, 0, 0, 0}};
+
+ c = getopt_long(argc, argv, "abc:d:0123456789", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 0:
+ printf("option %s", long_options[option_index].name);
+ if (optarg)
+ printf(" with arg %s", optarg);
+ printf("\n");
+ break;
+
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if (digit_optind != 0
+ && digit_optind != this_option_optind)
+ printf("digits occur in two different argv-elements.\n");
+ digit_optind = this_option_optind;
+ printf("option %c\n", c);
+ break;
+
+ case 'a':
+ printf("option a\n");
+ break;
+
+ case 'b':
+ printf("option b\n");
+ break;
+
+ case 'c':
+ printf("option c with value `%s'\n", optarg);
+ break;
+
+ case 'd':
+ printf("option d with value `%s'\n", optarg);
+ break;
+
+ case '?':
+ break;
+
+ default:
+ printf("?? getopt returned character code 0%o ??\n", c);
+ }
+ }
+
+ if (optind < argc) {
+ printf("non-option ARGV-elements: ");
+ while (optind < argc)
+ printf("%s ", argv[optind++]);
+ printf("\n");
+ }
+
+ exit(0);
+}
+
+#endif /* TEST */
diff --git a/lib/gitversion.pl b/lib/gitversion.pl
new file mode 100644
index 0000000..dd25c89
--- /dev/null
+++ b/lib/gitversion.pl
@@ -0,0 +1,47 @@
+#!/usr/bin/perl -w
+use strict;
+
+my $dir = shift;
+chdir $dir || die "$dir: $!\n";
+
+my $gitdesc = `git describe --always --first-parent --tags --dirty --match 'frr-*' || echo -- \"0-gUNKNOWN\"`;
+chomp $gitdesc;
+my $gitsuffix = ($gitdesc =~ /-g([0-9a-fA-F]+(-dirty)?)$/) ? "-g$1" : "-gUNKNOWN";
+
+printf STDERR "git suffix: %s\n", $gitsuffix;
+printf "#define GIT_SUFFIX \"%s\"\n", $gitsuffix;
+
+my $gitcommit = `git log -1 --format=\"%H\" || echo DEADBEEF`;
+chomp $gitcommit;
+open(BRANCHES, "git branch -a -v --abbrev=40|") || die "git branch: $!\n";
+my @names = ();
+while (<BRANCHES>) {
+ chomp $_;
+ if (/\s+(.*?)\s+$gitcommit/) {
+ my $branch = $1;
+ if ($branch =~ /^remotes\/(.*?)(\/.*)$/) {
+ my $path = $2;
+ my $url = `git config --get "remote.$1.url"`;
+ chomp $url;
+ $url =~ s/^(git:|https?:|git@)\/\/github\.com/github/i;
+ $url =~ s/^(ssh|git):\/\/git\.sv\.gnu\.org\/srv\/git\//savannah:/i;
+ $url =~ s/^(ssh|git):\/\/git\.savannah\.nongnu\.org\//savannah:/i;
+
+ push @names, $url.$path;
+ } else {
+ push @names, 'local:'.$branch;
+ }
+ }
+}
+
+printf STDERR "git branches: %s\n", join(", ", @names);
+
+my $cr = "\\r\\n\\";
+printf <<EOF, $gitdesc, join($cr."\n\\t", @names);
+#define GIT_INFO "$cr
+This is a git build of %s$cr
+Associated branch(es):$cr
+\\t%s$cr
+"
+EOF
+
diff --git a/lib/grammar_sandbox.c b/lib/grammar_sandbox.c
new file mode 100644
index 0000000..f9778c5
--- /dev/null
+++ b/lib/grammar_sandbox.c
@@ -0,0 +1,565 @@
+/*
+ * Testing shim and API examples for the new CLI backend.
+ *
+ * This unit defines a number of commands in the old engine that can
+ * be used to test and interact with the new engine.
+ * --
+ * Copyright (C) 2016 Cumulus Networks, Inc.
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "command.h"
+#include "graph.h"
+#include "linklist.h"
+#include "command_match.h"
+
+#define GRAMMAR_STR "CLI grammar sandbox\n"
+
+DEFINE_MTYPE_STATIC(LIB, CMD_TOKENS, "Command desc");
+
+/** headers **/
+void grammar_sandbox_init(void);
+static void pretty_print_graph(struct vty *vty, struct graph_node *, int, int,
+ struct graph_node **, size_t);
+static void init_cmdgraph(struct vty *, struct graph **);
+
+/** shim interface commands **/
+static struct graph *nodegraph = NULL, *nodegraph_free = NULL;
+
+#define check_nodegraph() \
+ do { \
+ if (!nodegraph) { \
+ vty_out(vty, "nodegraph not initialized\n"); \
+ return CMD_WARNING; \
+ } \
+ } while (0)
+
+DEFUN (grammar_test,
+ grammar_test_cmd,
+ "grammar parse LINE...",
+ GRAMMAR_STR
+ "parse a command\n"
+ "command to pass to new parser\n")
+{
+ check_nodegraph();
+
+ int idx_command = 2;
+ // make a string from tokenized command line
+ char *command = argv_concat(argv, argc, idx_command);
+
+ // create cmd_element for parser
+ struct cmd_element *cmd =
+ XCALLOC(MTYPE_CMD_TOKENS, sizeof(struct cmd_element));
+ cmd->string = command;
+ cmd->doc =
+ "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n";
+ cmd->func = NULL;
+
+ // parse the command and install it into the command graph
+ struct graph *graph = graph_new();
+ struct cmd_token *token =
+ cmd_token_new(START_TKN, CMD_ATTR_NORMAL, NULL, NULL);
+ graph_new_node(graph, token, (void (*)(void *)) & cmd_token_del);
+
+ cmd_graph_parse(graph, cmd);
+ cmd_graph_merge(nodegraph, graph, +1);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (grammar_test_complete,
+ grammar_test_complete_cmd,
+ "grammar complete COMMAND...",
+ GRAMMAR_STR
+ "attempt to complete input on DFA\n"
+ "command to complete\n")
+{
+ check_nodegraph();
+
+ int idx_command = 2;
+ char *cmdstr = argv_concat(argv, argc, idx_command);
+ if (!cmdstr)
+ return CMD_SUCCESS;
+
+ vector command = cmd_make_strvec(cmdstr);
+ if (!command) {
+ XFREE(MTYPE_TMP, cmdstr);
+ return CMD_SUCCESS;
+ }
+
+ // generate completions of user input
+ struct list *completions;
+ enum matcher_rv result =
+ command_complete(nodegraph, command, &completions);
+
+ // print completions or relevant error message
+ if (!MATCHER_ERROR(result)) {
+ vector comps = completions_to_vec(completions);
+ struct cmd_token *tkn;
+
+ // calculate length of longest tkn->text in completions
+ unsigned int width = 0, i = 0;
+ for (i = 0; i < vector_active(comps); i++) {
+ tkn = vector_slot(comps, i);
+ unsigned int len = strlen(tkn->text);
+ width = len > width ? len : width;
+ }
+
+ // print completions
+ for (i = 0; i < vector_active(comps); i++) {
+ tkn = vector_slot(comps, i);
+ vty_out(vty, " %-*s %s\n", width, tkn->text,
+ tkn->desc);
+ }
+
+ for (i = 0; i < vector_active(comps); i++)
+ cmd_token_del(
+ (struct cmd_token *)vector_slot(comps, i));
+ vector_free(comps);
+ } else
+ vty_out(vty, "%% No match\n");
+
+ // free resources
+ list_delete(&completions);
+ cmd_free_strvec(command);
+ XFREE(MTYPE_TMP, cmdstr);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (grammar_test_match,
+ grammar_test_match_cmd,
+ "grammar match COMMAND...",
+ GRAMMAR_STR
+ "attempt to match input on DFA\n"
+ "command to match\n")
+{
+ check_nodegraph();
+
+ int idx_command = 2;
+ if (argv[2]->arg[0] == '#')
+ return CMD_SUCCESS;
+
+ char *cmdstr = argv_concat(argv, argc, idx_command);
+ if (!cmdstr)
+ return CMD_SUCCESS;
+ vector command = cmd_make_strvec(cmdstr);
+ if (!command) {
+ XFREE(MTYPE_TMP, cmdstr);
+ return CMD_SUCCESS;
+ }
+
+ struct list *argvv = NULL;
+ const struct cmd_element *element = NULL;
+ enum matcher_rv result =
+ command_match(nodegraph, command, &argvv, &element);
+
+ // print completions or relevant error message
+ if (element) {
+ vty_out(vty, "Matched: %s\n", element->string);
+ struct listnode *ln;
+ struct cmd_token *token;
+ for (ALL_LIST_ELEMENTS_RO(argvv, ln, token))
+ vty_out(vty, "%s -- %s\n", token->text, token->arg);
+
+ vty_out(vty, "func: %p\n", element->func);
+
+ list_delete(&argvv);
+ } else {
+ assert(MATCHER_ERROR(result));
+ switch (result) {
+ case MATCHER_NO_MATCH:
+ vty_out(vty, "%% Unknown command\n");
+ break;
+ case MATCHER_INCOMPLETE:
+ vty_out(vty, "%% Incomplete command\n");
+ break;
+ case MATCHER_AMBIGUOUS:
+ vty_out(vty, "%% Ambiguous command\n");
+ break;
+ default:
+ vty_out(vty, "%% Unknown error\n");
+ break;
+ }
+ }
+
+ // free resources
+ cmd_free_strvec(command);
+ XFREE(MTYPE_TMP, cmdstr);
+
+ return CMD_SUCCESS;
+}
+
+/**
+ * Testing shim to test docstrings
+ */
+DEFUN (grammar_test_doc,
+ grammar_test_doc_cmd,
+ "grammar test docstring",
+ GRAMMAR_STR
+ "Test function for docstring\n"
+ "Command end\n")
+{
+ check_nodegraph();
+
+ // create cmd_element with docstring
+ struct cmd_element *cmd =
+ XCALLOC(MTYPE_CMD_TOKENS, sizeof(struct cmd_element));
+ cmd->string = XSTRDUP(
+ MTYPE_CMD_TOKENS,
+ "test docstring <example|selector follow> (1-255) end VARIABLE [OPTION|set lol] . VARARG");
+ cmd->doc = XSTRDUP(MTYPE_CMD_TOKENS,
+ "Test stuff\n"
+ "docstring thing\n"
+ "first example\n"
+ "second example\n"
+ "follow\n"
+ "random range\n"
+ "end thingy\n"
+ "variable\n"
+ "optional variable\n"
+ "optional set\n"
+ "optional lol\n"
+ "vararg!\n");
+ cmd->func = NULL;
+
+ // parse element
+ cmd_graph_parse(nodegraph, cmd);
+
+ return CMD_SUCCESS;
+}
+
+/**
+ * Debugging command to print command graph
+ */
+DEFUN (grammar_test_show,
+ grammar_test_show_cmd,
+ "grammar show [doc]",
+ GRAMMAR_STR
+ "print current accumulated DFA\n"
+ "include docstrings\n")
+{
+ check_nodegraph();
+
+ struct graph_node *stack[CMD_ARGC_MAX];
+ pretty_print_graph(vty, vector_slot(nodegraph->nodes, 0), 0, argc >= 3,
+ stack, 0);
+ return CMD_SUCCESS;
+}
+
+DEFUN (grammar_test_dot,
+ grammar_test_dot_cmd,
+ "grammar dotfile OUTNAME",
+ GRAMMAR_STR
+ "print current graph for dot\n"
+ ".dot filename\n")
+{
+ check_nodegraph();
+ FILE *ofd = fopen(argv[2]->arg, "w");
+
+ if (!ofd) {
+ vty_out(vty, "%s: %s\r\n", argv[2]->arg, strerror(errno));
+ return CMD_SUCCESS;
+ }
+
+ char *dot = cmd_graph_dump_dot(nodegraph);
+
+ fprintf(ofd, "%s", dot);
+ fclose(ofd);
+ XFREE(MTYPE_TMP, dot);
+
+ return CMD_SUCCESS;
+}
+
+struct cmd_permute_item {
+ char *cmd;
+ struct cmd_element *el;
+};
+
+static void cmd_permute_free(void *arg)
+{
+ struct cmd_permute_item *i = arg;
+ XFREE(MTYPE_TMP, i->cmd);
+ XFREE(MTYPE_TMP, i);
+}
+
+static int cmd_permute_cmp(void *a, void *b)
+{
+ struct cmd_permute_item *aa = a, *bb = b;
+ return strcmp(aa->cmd, bb->cmd);
+}
+
+static void cmd_graph_permute(struct list *out, struct graph_node **stack,
+ size_t stackpos, char *cmd)
+{
+ struct graph_node *gn = stack[stackpos];
+ struct cmd_token *tok = gn->data;
+ char *appendp = cmd + strlen(cmd);
+ size_t j;
+
+ if (tok->type < SPECIAL_TKN) {
+ sprintf(appendp, "%s ", tok->text);
+ appendp += strlen(appendp);
+ } else if (tok->type == END_TKN) {
+ struct cmd_permute_item *i = XMALLOC(MTYPE_TMP, sizeof(*i));
+ i->el = ((struct graph_node *)vector_slot(gn->to, 0))->data;
+ i->cmd = XSTRDUP(MTYPE_TMP, cmd);
+ i->cmd[strlen(cmd) - 1] = '\0';
+ listnode_add_sort(out, i);
+ return;
+ }
+
+ if (++stackpos == CMD_ARGC_MAX)
+ return;
+
+ for (size_t i = 0; i < vector_active(gn->to); i++) {
+ struct graph_node *gnext = vector_slot(gn->to, i);
+ for (j = 0; j < stackpos; j++)
+ if (stack[j] == gnext)
+ break;
+ if (j != stackpos)
+ continue;
+
+ stack[stackpos] = gnext;
+ *appendp = '\0';
+ cmd_graph_permute(out, stack, stackpos, cmd);
+ }
+}
+
+static struct list *cmd_graph_permutations(struct graph *graph)
+{
+ char accumulate[2048] = "";
+ struct graph_node *stack[CMD_ARGC_MAX];
+
+ struct list *rv = list_new();
+ rv->cmp = cmd_permute_cmp;
+ rv->del = cmd_permute_free;
+ stack[0] = vector_slot(graph->nodes, 0);
+ cmd_graph_permute(rv, stack, 0, accumulate);
+ return rv;
+}
+
+extern vector cmdvec;
+
+DEFUN (grammar_findambig,
+ grammar_findambig_cmd,
+ "grammar find-ambiguous [{printall|nodescan}]",
+ GRAMMAR_STR
+ "Find ambiguous commands\n"
+ "Print all permutations\n"
+ "Scan all nodes\n")
+{
+ struct list *commands;
+ struct cmd_permute_item *prev = NULL, *cur = NULL;
+ struct listnode *ln;
+ int i, printall, scan, scannode = 0;
+ int ambig = 0;
+
+ i = 0;
+ printall = argv_find(argv, argc, "printall", &i);
+ i = 0;
+ scan = argv_find(argv, argc, "nodescan", &i);
+
+ if (scan && nodegraph_free) {
+ graph_delete_graph(nodegraph_free);
+ nodegraph_free = NULL;
+ }
+
+ if (!scan && !nodegraph) {
+ vty_out(vty, "nodegraph uninitialized\r\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ do {
+ if (scan) {
+ struct cmd_node *cnode =
+ vector_slot(cmdvec, scannode++);
+ if (!cnode)
+ continue;
+ cmd_finalize_node(cnode);
+ nodegraph = cnode->cmdgraph;
+ if (!nodegraph)
+ continue;
+ vty_out(vty, "scanning node %d (%s)\n", scannode - 1,
+ cnode->name);
+ }
+
+ commands = cmd_graph_permutations(nodegraph);
+ prev = NULL;
+ for (ALL_LIST_ELEMENTS_RO(commands, ln, cur)) {
+ int same = prev && !strcmp(prev->cmd, cur->cmd);
+ if (printall && !same)
+ vty_out(vty, "'%s' [%x]\n", cur->cmd,
+ cur->el->daemon);
+ if (same) {
+ vty_out(vty, "'%s' AMBIGUOUS:\n", cur->cmd);
+ vty_out(vty, " %s\n '%s'\n", prev->el->name,
+ prev->el->string);
+ vty_out(vty, " %s\n '%s'\n", cur->el->name,
+ cur->el->string);
+ vty_out(vty, "\n");
+ ambig++;
+ }
+ prev = cur;
+ }
+ list_delete(&commands);
+
+ vty_out(vty, "\n");
+ } while (scan && scannode < LINK_PARAMS_NODE);
+
+ vty_out(vty, "%d ambiguous commands found.\n", ambig);
+
+ if (scan)
+ nodegraph = NULL;
+ return ambig == 0 ? CMD_SUCCESS : CMD_WARNING_CONFIG_FAILED;
+}
+
+DEFUN (grammar_init_graph,
+ grammar_init_graph_cmd,
+ "grammar init",
+ GRAMMAR_STR
+ "(re)initialize graph\n")
+{
+ if (nodegraph_free)
+ graph_delete_graph(nodegraph_free);
+ nodegraph_free = NULL;
+
+ init_cmdgraph(vty, &nodegraph);
+ return CMD_SUCCESS;
+}
+
+DEFUN (grammar_access,
+ grammar_access_cmd,
+ "grammar access (0-65535)",
+ GRAMMAR_STR
+ "access node graph\n"
+ "node number\n")
+{
+ if (nodegraph_free)
+ graph_delete_graph(nodegraph_free);
+ nodegraph_free = NULL;
+
+ struct cmd_node *cnode;
+
+ cnode = vector_slot(cmdvec, atoi(argv[2]->arg));
+ if (!cnode) {
+ vty_out(vty, "%% no such node\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ vty_out(vty, "node %d\n", (int)cnode->node);
+ cmd_finalize_node(cnode);
+ nodegraph = cnode->cmdgraph;
+ return CMD_SUCCESS;
+}
+
+/* this is called in vtysh.c to set up the testing shim */
+void grammar_sandbox_init(void)
+{
+ // install all enable elements
+ install_element(ENABLE_NODE, &grammar_test_cmd);
+ install_element(ENABLE_NODE, &grammar_test_show_cmd);
+ install_element(ENABLE_NODE, &grammar_test_dot_cmd);
+ install_element(ENABLE_NODE, &grammar_test_match_cmd);
+ install_element(ENABLE_NODE, &grammar_test_complete_cmd);
+ install_element(ENABLE_NODE, &grammar_test_doc_cmd);
+ install_element(ENABLE_NODE, &grammar_findambig_cmd);
+ install_element(ENABLE_NODE, &grammar_init_graph_cmd);
+ install_element(ENABLE_NODE, &grammar_access_cmd);
+}
+
+/**
+ * Pretty-prints a graph, assuming it is a tree.
+ *
+ * @param start the node to take as the root
+ * @param level indent level for recursive calls, always pass 0
+ */
+static void pretty_print_graph(struct vty *vty, struct graph_node *start,
+ int level, int desc, struct graph_node **stack,
+ size_t stackpos)
+{
+ // print this node
+ char tokennum[32];
+ struct cmd_token *tok = start->data;
+
+ snprintf(tokennum, sizeof(tokennum), "%d?", tok->type);
+ vty_out(vty, "%s", lookup_msg(tokennames, tok->type, NULL));
+ if (tok->text)
+ vty_out(vty, ":\"%s\"", tok->text);
+ if (tok->varname)
+ vty_out(vty, " => %s", tok->varname);
+ if (desc)
+ vty_out(vty, " ?'%s'", tok->desc);
+ vty_out(vty, " ");
+
+ if (stackpos == CMD_ARGC_MAX) {
+ vty_out(vty, " -aborting! (depth limit)\n");
+ return;
+ }
+ stack[stackpos++] = start;
+
+ int numto = desc ? 2 : vector_active(start->to);
+ if (numto) {
+ if (numto > 1)
+ vty_out(vty, "\n");
+ for (unsigned int i = 0; i < vector_active(start->to); i++) {
+ struct graph_node *adj = vector_slot(start->to, i);
+ // if we're listing multiple children, indent!
+ if (numto > 1)
+ for (int j = 0; j < level + 1; j++)
+ vty_out(vty, " ");
+ // if this node is a vararg, just print *
+ if (adj == start)
+ vty_out(vty, "*");
+ else if (((struct cmd_token *)adj->data)->type
+ == END_TKN)
+ vty_out(vty, "--END\n");
+ else {
+ size_t k;
+ for (k = 0; k < stackpos; k++)
+ if (stack[k] == adj) {
+ vty_out(vty, "<<loop@%zu \n",
+ k);
+ break;
+ }
+ if (k == stackpos)
+ pretty_print_graph(
+ vty, adj,
+ numto > 1 ? level + 1 : level,
+ desc, stack, stackpos);
+ }
+ }
+ } else
+ vty_out(vty, "\n");
+}
+
+/** stuff that should go in command.c + command.h */
+static void init_cmdgraph(struct vty *vty, struct graph **graph)
+{
+ // initialize graph, add start noe
+ *graph = graph_new();
+ nodegraph_free = *graph;
+ struct cmd_token *token = cmd_token_new(START_TKN, 0, NULL, NULL);
+ graph_new_node(*graph, token, (void (*)(void *)) & cmd_token_del);
+ if (vty)
+ vty_out(vty, "initialized graph\n");
+}
diff --git a/lib/grammar_sandbox_main.c b/lib/grammar_sandbox_main.c
new file mode 100644
index 0000000..6469b49
--- /dev/null
+++ b/lib/grammar_sandbox_main.c
@@ -0,0 +1,67 @@
+/*
+ * Testing shim and API examples for the new CLI backend.
+ *
+ * Minimal main() to run grammar_sandbox standalone.
+ * [split off grammar_sandbox.c 2017-01-23]
+ * --
+ * Copyright (C) 2016 Cumulus Networks, Inc.
+ * Copyright (C) 2017 David Lamparter for NetDEF, Inc.
+ *
+ * This file is part of FRRouting (FRR).
+ *
+ * FRR 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, or (at your option) any later version.
+ *
+ * FRR 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
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "command.h"
+#include "lib_vty.h"
+
+static void vty_do_exit(int isexit)
+{
+ printf("\nend.\n");
+ if (!isexit)
+ exit(0);
+}
+
+struct thread_master *master;
+
+int main(int argc, char **argv)
+{
+ struct thread thread;
+
+ master = thread_master_create(NULL);
+
+ zlog_aux_init("NONE: ", LOG_DEBUG);
+
+ /* Library inits. */
+ cmd_init(1);
+ host.name = strdup("test");
+ host.domainname = strdup("testdomainname");
+
+ vty_init(master, true);
+ lib_cmd_init();
+ nb_init(master, NULL, 0, false);
+
+ vty_stdio(vty_do_exit);
+
+ /* Fetch next active thread. */
+ while (thread_fetch(master, &thread))
+ thread_call(&thread);
+
+ /* Not reached. */
+ exit(0);
+}
diff --git a/lib/graph.c b/lib/graph.c
new file mode 100644
index 0000000..ba7314f
--- /dev/null
+++ b/lib/graph.c
@@ -0,0 +1,230 @@
+/*
+ * Graph data structure.
+ *
+ * --
+ * Copyright (C) 2016 Cumulus Networks, Inc.
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 "graph.h"
+#include "memory.h"
+#include "buffer.h"
+
+DEFINE_MTYPE_STATIC(LIB, GRAPH, "Graph");
+DEFINE_MTYPE_STATIC(LIB, GRAPH_NODE, "Graph Node");
+struct graph *graph_new(void)
+{
+ struct graph *graph = XCALLOC(MTYPE_GRAPH, sizeof(struct graph));
+ graph->nodes = vector_init(VECTOR_MIN_SIZE);
+
+ return graph;
+}
+
+void graph_delete_graph(struct graph *graph)
+{
+ for (unsigned int i = vector_active(graph->nodes); i--; /**/)
+ graph_delete_node(graph, vector_slot(graph->nodes, i));
+
+ vector_free(graph->nodes);
+ XFREE(MTYPE_GRAPH, graph);
+}
+
+struct graph_node *graph_new_node(struct graph *graph, void *data,
+ void (*del)(void *))
+{
+ struct graph_node *node =
+ XCALLOC(MTYPE_GRAPH_NODE, sizeof(struct graph_node));
+
+ node->from = vector_init(VECTOR_MIN_SIZE);
+ node->to = vector_init(VECTOR_MIN_SIZE);
+ node->data = data;
+ node->del = del;
+
+ vector_set(graph->nodes, node);
+
+ return node;
+}
+
+static void graph_vector_remove(vector v, unsigned int ix)
+{
+ if (ix >= v->active)
+ return;
+
+ /* v->active is guaranteed >= 1 because ix can't be lower than 0
+ * and v->active is > ix. */
+ v->active--;
+ /* if ix == v->active--, we set the item to itself, then to NULL...
+ * still correct, no check necessary. */
+ v->index[ix] = v->index[v->active];
+ v->index[v->active] = NULL;
+}
+
+void graph_delete_node(struct graph *graph, struct graph_node *node)
+{
+ if (!node)
+ return;
+
+ // an adjacent node
+ struct graph_node *adj;
+
+ // remove all edges from other nodes to us
+ for (unsigned int i = vector_active(node->from); i--; /**/) {
+ adj = vector_slot(node->from, i);
+ graph_remove_edge(adj, node);
+ }
+
+ // remove all edges from us to other nodes
+ for (unsigned int i = vector_active(node->to); i--; /**/) {
+ adj = vector_slot(node->to, i);
+ graph_remove_edge(node, adj);
+ }
+
+ // if there is a deletion callback, call it
+ if (node->del && node->data)
+ (*node->del)(node->data);
+
+ // free adjacency lists
+ vector_free(node->to);
+ vector_free(node->from);
+
+ // remove node from graph->nodes
+ for (unsigned int i = vector_active(graph->nodes); i--; /**/)
+ if (vector_slot(graph->nodes, i) == node) {
+ graph_vector_remove(graph->nodes, i);
+ break;
+ }
+
+ // free the node itself
+ XFREE(MTYPE_GRAPH_NODE, node);
+}
+
+struct graph_node *graph_add_edge(struct graph_node *from,
+ struct graph_node *to)
+{
+ vector_set(from->to, to);
+ vector_set(to->from, from);
+ return to;
+}
+
+void graph_remove_edge(struct graph_node *from, struct graph_node *to)
+{
+ // remove from from to->from
+ for (unsigned int i = vector_active(to->from); i--; /**/)
+ if (vector_slot(to->from, i) == from) {
+ graph_vector_remove(to->from, i);
+ break;
+ }
+ // remove to from from->to
+ for (unsigned int i = vector_active(from->to); i--; /**/)
+ if (vector_slot(from->to, i) == to) {
+ graph_vector_remove(from->to, i);
+ break;
+ }
+}
+
+struct graph_node *graph_find_node(struct graph *graph, void *data)
+{
+ struct graph_node *g;
+
+ for (unsigned int i = vector_active(graph->nodes); i--; /**/) {
+ g = vector_slot(graph->nodes, i);
+ if (g->data == data)
+ return g;
+ }
+
+ return NULL;
+}
+
+bool graph_has_edge(struct graph_node *from, struct graph_node *to)
+{
+ for (unsigned int i = vector_active(from->to); i--; /**/)
+ if (vector_slot(from->to, i) == to)
+ return true;
+
+ return false;
+}
+
+static void _graph_dfs(struct graph *graph, struct graph_node *start,
+ vector visited,
+ void (*dfs_cb)(struct graph_node *, void *), void *arg)
+{
+ /* check that we have not visited this node */
+ for (unsigned int i = 0; i < vector_active(visited); i++) {
+ if (start == vector_slot(visited, i))
+ return;
+ }
+
+ /* put this node in visited stack */
+ vector_ensure(visited, vector_active(visited));
+ vector_set_index(visited, vector_active(visited), start);
+
+ /* callback */
+ dfs_cb(start, arg);
+
+ /* recurse into children */
+ for (unsigned int i = vector_active(start->to); i--; /**/) {
+ struct graph_node *c = vector_slot(start->to, i);
+
+ _graph_dfs(graph, c, visited, dfs_cb, arg);
+ }
+}
+
+void graph_dfs(struct graph *graph, struct graph_node *start,
+ void (*dfs_cb)(struct graph_node *, void *), void *arg)
+{
+ vector visited = vector_init(VECTOR_MIN_SIZE);
+
+ _graph_dfs(graph, start, visited, dfs_cb, arg);
+ vector_free(visited);
+}
+
+#ifndef BUILDING_CLIPPY
+
+void graph_dump_dot_default_print_cb(struct graph_node *gn, struct buffer *buf)
+{
+ char nbuf[64];
+
+ for (unsigned int i = 0; i < vector_active(gn->to); i++) {
+ struct graph_node *adj = vector_slot(gn->to, i);
+
+ snprintf(nbuf, sizeof(nbuf), " n%p -> n%p;\n", gn, adj);
+ buffer_putstr(buf, nbuf);
+ }
+}
+
+char *graph_dump_dot(struct graph *graph, struct graph_node *start,
+ void (*pcb)(struct graph_node *, struct buffer *))
+{
+ struct buffer *buf = buffer_new(0);
+ char *ret;
+
+ pcb = (pcb) ? pcb : graph_dump_dot_default_print_cb;
+ buffer_putstr(buf, "digraph {\n");
+
+ graph_dfs(graph, start, (void (*)(struct graph_node *, void *))pcb,
+ buf);
+
+ buffer_putstr(buf, "}\n");
+
+ ret = buffer_getstr(buf);
+ buffer_free(buf);
+
+ return ret;
+}
+
+#endif /* BUILDING_CLIPPY */
diff --git a/lib/graph.h b/lib/graph.h
new file mode 100644
index 0000000..8e126e6
--- /dev/null
+++ b/lib/graph.h
@@ -0,0 +1,176 @@
+/*
+ * Graph data structure.
+ *
+ * --
+ * Copyright (C) 2016 Cumulus Networks, Inc.
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 _ZEBRA_COMMAND_GRAPH_H
+#define _ZEBRA_COMMAND_GRAPH_H
+
+#include <stdbool.h>
+#include "vector.h"
+#include "buffer.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct graph {
+ vector nodes;
+};
+
+struct graph_node {
+ vector from; // nodes which have edges to this node
+ vector to; // nodes which this node has edges to
+
+ void *data; // node data
+ void (*del)(void *data); // deletion callback
+};
+
+struct graph *graph_new(void);
+
+/**
+ * Creates a new node.
+ *
+ * @struct graph the graph this node exists in
+ * @param[in] data this node's data
+ * @param[in] del data deletion callback
+ * @return the new node
+ */
+struct graph_node *graph_new_node(struct graph *graph, void *data,
+ void (*del)(void *));
+
+/**
+ * Deletes a node.
+ *
+ * Before deletion, this function removes all edges to and from this node from
+ * any neighbor nodes.
+ *
+ * If *data and *del are non-null, the following call is made:
+ * (*node->del) (node->data);
+ *
+ * @param[in] graph the graph this node belongs to
+ * @param[out] node pointer to node to delete
+ */
+void graph_delete_node(struct graph *graph, struct graph_node *node);
+
+/**
+ * Makes a directed edge between two nodes.
+ *
+ * @param[in] from
+ * @param[in] to
+ * @return to
+ */
+struct graph_node *graph_add_edge(struct graph_node *from,
+ struct graph_node *to);
+
+/**
+ * Removes a directed edge between two nodes.
+ *
+ * @param[in] from
+ * @param[in] to
+ */
+void graph_remove_edge(struct graph_node *from, struct graph_node *to);
+
+/**
+ * Deletes a graph.
+ * Calls graph_delete_node on each node before freeing the graph struct itself.
+ *
+ * @param graph the graph to delete
+ */
+void graph_delete_graph(struct graph *graph);
+
+/*
+ * Finds a node in the graph.
+ *
+ * @param[in] graph the graph to search in
+ * @param[in] data the data to key off
+ * @return the first graph node whose data pointer matches `data`
+ */
+struct graph_node *graph_find_node(struct graph *graph, void *data);
+
+
+/*
+ * Determines whether two nodes have a directed edge between them.
+ *
+ * @param from
+ * @param to
+ * @return whether there is a directed edge from `from` to `to`.
+ */
+bool graph_has_edge(struct graph_node *from, struct graph_node *to);
+
+/*
+ * Depth-first search.
+ *
+ * Performs a depth-first traversal of the given graph, visiting each node
+ * exactly once and calling the user-provided callback for each visit.
+ *
+ * @param graph the graph to operate on
+ * @param start the node to take as the root
+ * @param dfs_cb callback called for each node visited in the traversal
+ * @param arg argument to provide to dfs_cb
+ */
+void graph_dfs(struct graph *graph, struct graph_node *start,
+ void (*dfs_cb)(struct graph_node *, void *), void *arg);
+
+#ifndef BUILDING_CLIPPY
+/*
+ * Clippy relies on a small subset of sources in lib/, but it cannot link
+ * libfrr since clippy itself is required to build libfrr. Instead it directly
+ * includes the sources it needs. One of these is the command graph
+ * implementation, which wraps this graph implementation. Since we need to use
+ * the buffer.[ch] sources here, which indirectly rely on most of libfrr, we
+ * have to ignore them when compiling clippy to avoid build dependency issues.
+ *
+ * TODO: Fix clippy build.
+ */
+
+/*
+ * Default node printer for use with graph_dump_dot.
+ *
+ * @param gn the node to print
+ * @param buf the buffer to print into
+ */
+void graph_dump_dot_default_print_cb(struct graph_node *gn, struct buffer *buf);
+
+/*
+ * Prints a graph in the DOT language.
+ *
+ * The generated output is produced from a depth-first traversal of the graph.
+ *
+ * @param graph the graph to print
+ * @param start the node to take as the root
+ * @param pcb callback called for each node in the traversal that should
+ * print the node in the DOT language. Passing NULL for this argument
+ * will use the default printer. See graph_dump_dot_default_print_cb for
+ * an example.
+ * @return representation of graph in DOT language, allocated with MTYPE_TMP.
+ * Caller is responsible for freeing this string.
+ */
+char *graph_dump_dot(struct graph *graph, struct graph_node *start,
+ void (*pcb)(struct graph_node *, struct buffer *buf));
+
+#endif /* BUILDING_CLIPPY */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ZEBRA_COMMAND_GRAPH_H */
diff --git a/lib/hash.c b/lib/hash.c
new file mode 100644
index 0000000..4b371b4
--- /dev/null
+++ b/lib/hash.c
@@ -0,0 +1,464 @@
+/* Hash routine.
+ * Copyright (C) 1998 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra 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 <math.h>
+
+#include "hash.h"
+#include "memory.h"
+#include "linklist.h"
+#include "termtable.h"
+#include "vty.h"
+#include "command.h"
+#include "libfrr.h"
+#include "frr_pthread.h"
+#include "libfrr_trace.h"
+
+DEFINE_MTYPE_STATIC(LIB, HASH, "Hash");
+DEFINE_MTYPE_STATIC(LIB, HASH_BUCKET, "Hash Bucket");
+DEFINE_MTYPE_STATIC(LIB, HASH_INDEX, "Hash Index");
+
+static pthread_mutex_t _hashes_mtx = PTHREAD_MUTEX_INITIALIZER;
+static struct list *_hashes;
+
+struct hash *hash_create_size(unsigned int size,
+ unsigned int (*hash_key)(const void *),
+ bool (*hash_cmp)(const void *, const void *),
+ const char *name)
+{
+ struct hash *hash;
+
+ assert((size & (size - 1)) == 0);
+ hash = XCALLOC(MTYPE_HASH, sizeof(struct hash));
+ hash->index =
+ XCALLOC(MTYPE_HASH_INDEX, sizeof(struct hash_bucket *) * size);
+ hash->size = size;
+ hash->hash_key = hash_key;
+ hash->hash_cmp = hash_cmp;
+ hash->count = 0;
+ hash->name = name ? XSTRDUP(MTYPE_HASH, name) : NULL;
+ hash->stats.empty = hash->size;
+
+ frr_with_mutex (&_hashes_mtx) {
+ if (!_hashes)
+ _hashes = list_new();
+
+ listnode_add(_hashes, hash);
+ }
+
+ return hash;
+}
+
+struct hash *hash_create(unsigned int (*hash_key)(const void *),
+ bool (*hash_cmp)(const void *, const void *),
+ const char *name)
+{
+ return hash_create_size(HASH_INITIAL_SIZE, hash_key, hash_cmp, name);
+}
+
+void *hash_alloc_intern(void *arg)
+{
+ return arg;
+}
+
+/*
+ * ssq = ssq + (new^2 - old^2)
+ * = ssq + ((new + old) * (new - old))
+ */
+#define hash_update_ssq(hz, old, new) \
+ do { \
+ int _adjust = (new + old) * (new - old); \
+ if (_adjust < 0) \
+ atomic_fetch_sub_explicit(&hz->stats.ssq, -_adjust, \
+ memory_order_relaxed); \
+ else \
+ atomic_fetch_add_explicit(&hz->stats.ssq, _adjust, \
+ memory_order_relaxed); \
+ } while (0)
+
+/* Expand hash if the chain length exceeds the threshold. */
+static void hash_expand(struct hash *hash)
+{
+ unsigned int i, new_size;
+ struct hash_bucket *hb, *hbnext, **new_index;
+
+ new_size = hash->size * 2;
+
+ if (hash->max_size && new_size > hash->max_size)
+ return;
+
+ new_index = XCALLOC(MTYPE_HASH_INDEX,
+ sizeof(struct hash_bucket *) * new_size);
+
+ hash->stats.empty = new_size;
+
+ for (i = 0; i < hash->size; i++)
+ for (hb = hash->index[i]; hb; hb = hbnext) {
+ unsigned int h = hb->key & (new_size - 1);
+
+ hbnext = hb->next;
+ hb->next = new_index[h];
+
+ int oldlen = hb->next ? hb->next->len : 0;
+ int newlen = oldlen + 1;
+
+ if (newlen == 1)
+ hash->stats.empty--;
+ else
+ hb->next->len = 0;
+
+ hb->len = newlen;
+
+ hash_update_ssq(hash, oldlen, newlen);
+
+ new_index[h] = hb;
+ }
+
+ /* Switch to new table */
+ XFREE(MTYPE_HASH_INDEX, hash->index);
+ hash->size = new_size;
+ hash->index = new_index;
+}
+
+void *hash_get(struct hash *hash, void *data, void *(*alloc_func)(void *))
+{
+ frrtrace(2, frr_libfrr, hash_get, hash, data);
+
+ unsigned int key;
+ unsigned int index;
+ void *newdata;
+ struct hash_bucket *bucket;
+
+ if (!alloc_func && !hash->count)
+ return NULL;
+
+ key = (*hash->hash_key)(data);
+ index = key & (hash->size - 1);
+
+ for (bucket = hash->index[index]; bucket != NULL;
+ bucket = bucket->next) {
+ if (bucket->key == key && (*hash->hash_cmp)(bucket->data, data))
+ return bucket->data;
+ }
+
+ if (alloc_func) {
+ newdata = (*alloc_func)(data);
+ if (newdata == NULL)
+ return NULL;
+
+ if (HASH_THRESHOLD(hash->count + 1, hash->size)) {
+ hash_expand(hash);
+ index = key & (hash->size - 1);
+ }
+
+ bucket = XCALLOC(MTYPE_HASH_BUCKET, sizeof(struct hash_bucket));
+ bucket->data = newdata;
+ bucket->key = key;
+ bucket->next = hash->index[index];
+ hash->index[index] = bucket;
+ hash->count++;
+
+ frrtrace(3, frr_libfrr, hash_insert, hash, data, key);
+
+ int oldlen = bucket->next ? bucket->next->len : 0;
+ int newlen = oldlen + 1;
+
+ if (newlen == 1)
+ hash->stats.empty--;
+ else
+ bucket->next->len = 0;
+
+ bucket->len = newlen;
+
+ hash_update_ssq(hash, oldlen, newlen);
+
+ return bucket->data;
+ }
+ return NULL;
+}
+
+void *hash_lookup(struct hash *hash, void *data)
+{
+ return hash_get(hash, data, NULL);
+}
+
+unsigned int string_hash_make(const char *str)
+{
+ unsigned int hash = 0;
+
+ while (*str)
+ hash = (hash * 33) ^ (unsigned int)*str++;
+
+ return hash;
+}
+
+void *hash_release(struct hash *hash, void *data)
+{
+ void *ret = NULL;
+ unsigned int key;
+ unsigned int index;
+ struct hash_bucket *bucket;
+ struct hash_bucket *pp;
+
+ key = (*hash->hash_key)(data);
+ index = key & (hash->size - 1);
+
+ for (bucket = pp = hash->index[index]; bucket; bucket = bucket->next) {
+ if (bucket->key == key
+ && (*hash->hash_cmp)(bucket->data, data)) {
+ int oldlen = hash->index[index]->len;
+ int newlen = oldlen - 1;
+
+ if (bucket == pp)
+ hash->index[index] = bucket->next;
+ else
+ pp->next = bucket->next;
+
+ if (hash->index[index])
+ hash->index[index]->len = newlen;
+ else
+ hash->stats.empty++;
+
+ hash_update_ssq(hash, oldlen, newlen);
+
+ ret = bucket->data;
+ XFREE(MTYPE_HASH_BUCKET, bucket);
+ hash->count--;
+ break;
+ }
+ pp = bucket;
+ }
+
+ frrtrace(3, frr_libfrr, hash_release, hash, data, ret);
+
+ return ret;
+}
+
+void hash_iterate(struct hash *hash, void (*func)(struct hash_bucket *, void *),
+ void *arg)
+{
+ unsigned int i;
+ struct hash_bucket *hb;
+ struct hash_bucket *hbnext;
+
+ for (i = 0; i < hash->size; i++)
+ for (hb = hash->index[i]; hb; hb = hbnext) {
+ /* get pointer to next hash bucket here, in case (*func)
+ * decides to delete hb by calling hash_release
+ */
+ hbnext = hb->next;
+ (*func)(hb, arg);
+ }
+}
+
+void hash_walk(struct hash *hash, int (*func)(struct hash_bucket *, void *),
+ void *arg)
+{
+ unsigned int i;
+ struct hash_bucket *hb;
+ struct hash_bucket *hbnext;
+ int ret = HASHWALK_CONTINUE;
+
+ for (i = 0; i < hash->size; i++) {
+ for (hb = hash->index[i]; hb; hb = hbnext) {
+ /* get pointer to next hash bucket here, in case (*func)
+ * decides to delete hb by calling hash_release
+ */
+ hbnext = hb->next;
+ ret = (*func)(hb, arg);
+ if (ret == HASHWALK_ABORT)
+ return;
+ }
+ }
+}
+
+void hash_clean(struct hash *hash, void (*free_func)(void *))
+{
+ unsigned int i;
+ struct hash_bucket *hb;
+ struct hash_bucket *next;
+
+ for (i = 0; i < hash->size; i++) {
+ for (hb = hash->index[i]; hb; hb = next) {
+ next = hb->next;
+
+ if (free_func)
+ (*free_func)(hb->data);
+
+ XFREE(MTYPE_HASH_BUCKET, hb);
+ hash->count--;
+ }
+ hash->index[i] = NULL;
+ }
+
+ hash->stats.ssq = 0;
+ hash->stats.empty = hash->size;
+}
+
+static void hash_to_list_iter(struct hash_bucket *hb, void *arg)
+{
+ struct list *list = arg;
+
+ listnode_add(list, hb->data);
+}
+
+struct list *hash_to_list(struct hash *hash)
+{
+ struct list *list = list_new();
+
+ hash_iterate(hash, hash_to_list_iter, list);
+ return list;
+}
+
+void hash_free(struct hash *hash)
+{
+ frr_with_mutex (&_hashes_mtx) {
+ if (_hashes) {
+ listnode_delete(_hashes, hash);
+ if (_hashes->count == 0) {
+ list_delete(&_hashes);
+ }
+ }
+ }
+
+ XFREE(MTYPE_HASH, hash->name);
+
+ XFREE(MTYPE_HASH_INDEX, hash->index);
+ XFREE(MTYPE_HASH, hash);
+}
+
+
+/* CLI commands ------------------------------------------------------------ */
+
+DEFUN_NOSH(show_hash_stats,
+ show_hash_stats_cmd,
+ "show debugging hashtable [statistics]",
+ SHOW_STR
+ DEBUG_STR
+ "Statistics about hash tables\n"
+ "Statistics about hash tables\n")
+{
+ struct hash *h;
+ struct listnode *ln;
+ struct ttable *tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
+
+ ttable_add_row(tt, "Hash table|Buckets|Entries|Empty|LF|SD|FLF|SD");
+ tt->style.cell.lpad = 2;
+ tt->style.cell.rpad = 1;
+ tt->style.corner = '+';
+ ttable_restyle(tt);
+ ttable_rowseps(tt, 0, BOTTOM, true, '-');
+
+ /* Summary statistics calculated are:
+ *
+ * - Load factor: This is the number of elements in the table divided
+ * by the number of buckets. Since this hash table implementation
+ * uses chaining, this value can be greater than 1.
+ * This number provides information on how 'full' the table is, but
+ * does not provide information on how evenly distributed the
+ * elements are.
+ * Notably, a load factor >= 1 does not imply that every bucket has
+ * an element; with a pathological hash function, all elements could
+ * be in a single bucket.
+ *
+ * - Full load factor: this is the number of elements in the table
+ * divided by the number of buckets that have some elements in them.
+ *
+ * - Std. Dev.: This is the standard deviation calculated from the
+ * relevant load factor. If the load factor is the mean of number of
+ * elements per bucket, the standard deviation measures how much any
+ * particular bucket is likely to deviate from the mean.
+ * As a rule of thumb this number should be less than 2, and ideally
+ * <= 1 for optimal performance. A number larger than 3 generally
+ * indicates a poor hash function.
+ */
+
+ double lf; // load factor
+ double flf; // full load factor
+ double var; // overall variance
+ double fvar; // full variance
+ double stdv; // overall stddev
+ double fstdv; // full stddev
+
+ long double x2; // h->count ^ 2
+ long double ldc; // (long double) h->count
+ long double full; // h->size - h->stats.empty
+ long double ssq; // ssq casted to long double
+
+ pthread_mutex_lock(&_hashes_mtx);
+ if (!_hashes) {
+ pthread_mutex_unlock(&_hashes_mtx);
+ ttable_del(tt);
+ vty_out(vty, "No hash tables in use.\n");
+ return CMD_SUCCESS;
+ }
+
+ for (ALL_LIST_ELEMENTS_RO(_hashes, ln, h)) {
+ if (!h->name)
+ continue;
+
+ ssq = (long double)h->stats.ssq;
+ x2 = h->count * h->count;
+ ldc = (long double)h->count;
+ full = h->size - h->stats.empty;
+ lf = h->count / (double)h->size;
+ flf = full ? h->count / (double)(full) : 0;
+ var = ldc ? (1.0 / ldc) * (ssq - x2 / ldc) : 0;
+ fvar = full ? (1.0 / full) * (ssq - x2 / full) : 0;
+ var = (var < .0001) ? 0 : var;
+ fvar = (fvar < .0001) ? 0 : fvar;
+ stdv = sqrt(var);
+ fstdv = sqrt(fvar);
+
+ ttable_add_row(tt, "%s|%d|%ld|%.0f%%|%.2lf|%.2lf|%.2lf|%.2lf",
+ h->name, h->size, h->count,
+ (h->stats.empty / (double)h->size) * 100, lf,
+ stdv, flf, fstdv);
+ }
+ pthread_mutex_unlock(&_hashes_mtx);
+
+ /* display header */
+ char header[] = "Showing hash table statistics for ";
+ char underln[sizeof(header) + strlen(frr_protonameinst)];
+ memset(underln, '-', sizeof(underln));
+ underln[sizeof(underln) - 1] = '\0';
+ vty_out(vty, "%s%s\n", header, frr_protonameinst);
+ vty_out(vty, "%s\n", underln);
+
+ vty_out(vty, "# allocated: %d\n", _hashes->count);
+ vty_out(vty, "# named: %d\n\n", tt->nrows - 1);
+
+ if (tt->nrows > 1) {
+ ttable_colseps(tt, 0, RIGHT, true, '|');
+ char *table = ttable_dump(tt, "\n");
+ vty_out(vty, "%s\n", table);
+ XFREE(MTYPE_TMP, table);
+ } else
+ vty_out(vty, "No named hash tables to display.\n");
+
+ ttable_del(tt);
+
+ return CMD_SUCCESS;
+}
+
+void hash_cmd_init(void)
+{
+ install_element(ENABLE_NODE, &show_hash_stats_cmd);
+}
diff --git a/lib/hash.h b/lib/hash.h
new file mode 100644
index 0000000..91770d1
--- /dev/null
+++ b/lib/hash.h
@@ -0,0 +1,337 @@
+/* Hash routine.
+ * Copyright (C) 1998 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra 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 _ZEBRA_HASH_H
+#define _ZEBRA_HASH_H
+
+#include "memory.h"
+#include "frratomic.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Default hash table size. */
+#define HASH_INITIAL_SIZE 256
+/* Expansion threshold */
+#define HASH_THRESHOLD(used, size) ((used) > (size))
+
+#define HASHWALK_CONTINUE 0
+#define HASHWALK_ABORT -1
+
+struct hash_bucket {
+ /*
+ * if this bucket is the head of the linked listed, len denotes the
+ * number of elements in the list
+ */
+ int len;
+
+ /* Linked list. */
+ struct hash_bucket *next;
+
+ /* Hash key. */
+ unsigned int key;
+
+ /* Data. */
+ void *data;
+};
+
+struct hashstats {
+ /* number of empty hash buckets */
+ atomic_uint_fast32_t empty;
+ /* sum of squares of bucket length */
+ atomic_uint_fast32_t ssq;
+};
+
+struct hash {
+ /* Hash bucket. */
+ struct hash_bucket **index;
+
+ /* Hash table size. Must be power of 2 */
+ unsigned int size;
+
+ /* If max_size is 0 there is no limit */
+ unsigned int max_size;
+
+ /* Key make function. */
+ unsigned int (*hash_key)(const void *);
+
+ /* Data compare function. */
+ bool (*hash_cmp)(const void *, const void *);
+
+ /* Bucket alloc. */
+ unsigned long count;
+
+ struct hashstats stats;
+
+ /* hash name */
+ char *name;
+};
+
+#define hashcount(X) ((X)->count)
+
+/*
+ * Create a hash table.
+ *
+ * The created hash table uses chaining and a user-provided comparator function
+ * to resolve collisions. For best performance use a perfect hash function.
+ * Worst case lookup time is O(N) when using a constant hash function. Best
+ * case lookup time is O(1) when using a perfect hash function.
+ *
+ * The initial size of the created hash table is HASH_INITIAL_SIZE.
+ *
+ * hash_key
+ * hash function to use; should return a unique unsigned integer when called
+ * with a data item. Collisions are acceptable.
+ *
+ * hash_cmp
+ * comparison function used for resolving collisions; when called with two
+ * data items, should return true if the two items are equal and false
+ * otherwise
+ *
+ * name
+ * optional name for the hashtable; this is used when displaying global
+ * hashtable statistics. If this parameter is NULL the hash's name will be
+ * set to NULL and the default name will be displayed when showing
+ * statistics.
+ *
+ * Returns:
+ * a new hash table
+ */
+extern struct hash *hash_create(unsigned int (*hash_key)(const void *),
+ bool (*hash_cmp)(const void *, const void *),
+ const char *name);
+
+/*
+ * Create a hash table.
+ *
+ * The created hash table uses chaining and a user-provided comparator function
+ * to resolve collisions. For best performance use a perfect hash function.
+ * Worst case lookup time is O(N) when using a constant hash function. Best
+ * case lookup time is O(1) when using a perfect hash function.
+ *
+ * size
+ * initial number of hash buckets to allocate; must be a power of 2 or the
+ * program will assert
+ *
+ * hash_key
+ * hash function to use; should return a unique unsigned integer when called
+ * with a data item. Collisions are acceptable.
+ *
+ * hash_cmp
+ * comparison function used for resolving collisions; when called with two
+ * data items, should return true if the two items are equal and false
+ * otherwise
+ *
+ * name
+ * optional name for the hashtable; this is used when displaying global
+ * hashtable statistics. If this parameter is NULL the hash's name will be
+ * set to NULL and the default name will be displayed when showing
+ * statistics.
+ *
+ * Returns:
+ * a new hash table
+ */
+extern struct hash *
+hash_create_size(unsigned int size, unsigned int (*hash_key)(const void *),
+ bool (*hash_cmp)(const void *, const void *),
+ const char *name);
+
+/*
+ * Retrieve or insert data from / into a hash table.
+ *
+ * This function is somewhat counterintuitive in its usage. In order to look up
+ * an element from its key, you must provide the data item itself, with the
+ * portions used in the hash function set to the same values as the data item
+ * to retrieve. To insert a data element, either provide the key as just
+ * described and provide alloc_func as described below to allocate the full
+ * data element, or provide the full data element and pass 'hash_alloc_intern'
+ * to alloc_func.
+ *
+ * hash
+ * hash table to operate on
+ *
+ * data
+ * data to insert or retrieve - A hash bucket will not be created if
+ * the alloc_func returns a NULL pointer and nothing will be added to
+ * the hash. As such bucket->data will always be non-NULL.
+ *
+ * alloc_func
+ * function to call if the item is not found in the hash table. This
+ * function is called with the value of 'data' and should create the data
+ * item to insert and return a pointer to it. If the data has already been
+ * completely created and provided in the 'data' parameter, passing
+ * 'hash_alloc_intern' to this parameter will cause 'data' to be inserted.
+ * If this parameter is NULL, then this call to hash_get is equivalent to
+ * hash_lookup.
+ *
+ * Returns:
+ * the data item found or inserted, or NULL if alloc_func is NULL and the
+ * data is not found
+ */
+extern void *hash_get(struct hash *hash, void *data,
+ void *(*alloc_func)(void *));
+
+/*
+ * Dummy element allocation function.
+ *
+ * See hash_get for details.
+ *
+ * data
+ * data to insert into the hash table
+ *
+ * Returns:
+ * data
+ */
+extern void *hash_alloc_intern(void *data);
+
+/*
+ * Retrieve an item from a hash table.
+ *
+ * This function is equivalent to calling hash_get with alloc_func set to NULL.
+ *
+ * hash
+ * hash table to operate on
+ *
+ * data
+ * data element with values used for key computation set
+ *
+ * Returns:
+ * the data element if found, or NULL if not found
+ */
+extern void *hash_lookup(struct hash *hash, void *data);
+
+/*
+ * Remove an element from a hash table.
+ *
+ * hash
+ * hash table to operate on
+ *
+ * data
+ * data element to remove with values used for key computation set
+ *
+ * Returns:
+ * the removed element if found, or NULL if not found
+ */
+extern void *hash_release(struct hash *hash, void *data);
+
+/*
+ * Iterate over the elements in a hash table.
+ *
+ * The passed in arg to the handler function is the only safe
+ * item to delete from the hash.
+ *
+ * Please note that adding entries to the hash
+ * during the walk will cause undefined behavior in that some new entries
+ * will be walked and some will not. So do not do this.
+ *
+ * The bucket passed to func will have a non-NULL data pointer.
+ *
+ * hash
+ * hash table to operate on
+ *
+ * func
+ * function to call with each data item
+ *
+ * arg
+ * arbitrary argument passed as the second parameter in each call to 'func'
+ */
+extern void hash_iterate(struct hash *hash,
+ void (*func)(struct hash_bucket *, void *), void *arg);
+
+/*
+ * Iterate over the elements in a hash table, stopping on condition.
+ *
+ * The passed in arg to the handler function is the only safe item
+ * to delete from the hash.
+ *
+ * Please note that adding entries to the hash
+ * during the walk will cause undefined behavior in that some new entries
+ * will be walked and some will not. So do not do this.
+ *
+ * The bucket passed to func will have a non-NULL data pointer.
+ *
+ * hash
+ * hash table to operate on
+ *
+ * func
+ * function to call with each data item. If this function returns
+ * HASHWALK_ABORT then the iteration stops.
+ *
+ * arg
+ * arbitrary argument passed as the second parameter in each call to 'func'
+ */
+extern void hash_walk(struct hash *hash,
+ int (*func)(struct hash_bucket *, void *), void *arg);
+
+/*
+ * Remove all elements from a hash table.
+ *
+ * hash
+ * hash table to operate on
+ *
+ * free_func
+ * function to call with each removed item; intended to free the data
+ */
+extern void hash_clean(struct hash *hash, void (*free_func)(void *));
+
+/*
+ * Delete a hash table.
+ *
+ * This function assumes the table is empty. Call hash_clean to delete the
+ * hashtable contents if necessary.
+ *
+ * hash
+ * hash table to delete
+ */
+extern void hash_free(struct hash *hash);
+
+/*
+ * Converts a hash table to an unsorted linked list.
+ * Does not modify the hash table in any way.
+ *
+ * hash
+ * hash table to convert
+ */
+extern struct list *hash_to_list(struct hash *hash);
+
+/*
+ * Hash a string using the modified Bernstein hash.
+ *
+ * This is not a perfect hash function.
+ *
+ * str
+ * string to hash
+ *
+ * Returns:
+ * modified Bernstein hash of the string
+ */
+extern unsigned int string_hash_make(const char *);
+
+/*
+ * Install CLI commands for viewing global hash table statistics.
+ */
+extern void hash_cmd_init(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ZEBRA_HASH_H */
diff --git a/lib/hook.c b/lib/hook.c
new file mode 100644
index 0000000..895243a
--- /dev/null
+++ b/lib/hook.c
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2016 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+
+#include "memory.h"
+#include "hook.h"
+
+DEFINE_MTYPE_STATIC(LIB, HOOK_ENTRY, "Hook entry");
+
+void _hook_register(struct hook *hook, struct hookent *stackent, void *funcptr,
+ void *arg, bool has_arg, struct frrmod_runtime *module,
+ const char *funcname, int priority)
+{
+ struct hookent *he, **pos;
+
+ if (!stackent->ent_used)
+ he = stackent;
+ else {
+ he = XCALLOC(MTYPE_HOOK_ENTRY, sizeof(*he));
+ he->ent_on_heap = true;
+ }
+ he->ent_used = true;
+ he->hookfn = funcptr;
+ he->hookarg = arg;
+ he->has_arg = has_arg;
+ he->module = module;
+ he->fnname = funcname;
+ he->priority = priority;
+
+ for (pos = &hook->entries; *pos; pos = &(*pos)->next)
+ if (hook->reverse ? (*pos)->priority < priority
+ : (*pos)->priority >= priority)
+ break;
+
+ he->next = *pos;
+ *pos = he;
+}
+
+void _hook_unregister(struct hook *hook, void *funcptr, void *arg, bool has_arg)
+{
+ struct hookent *he, **prev;
+
+ for (prev = &hook->entries; (he = *prev) != NULL; prev = &he->next)
+ if (he->hookfn == funcptr && he->hookarg == arg
+ && he->has_arg == has_arg) {
+ *prev = he->next;
+ if (he->ent_on_heap)
+ XFREE(MTYPE_HOOK_ENTRY, he);
+ else
+ memset(he, 0, sizeof(*he));
+ break;
+ }
+}
diff --git a/lib/hook.h b/lib/hook.h
new file mode 100644
index 0000000..d75e623
--- /dev/null
+++ b/lib/hook.h
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) 2016 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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_HOOK_H
+#define _FRR_HOOK_H
+
+#include <stdbool.h>
+
+#include "module.h"
+#include "memory.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* type-safe subscribable hook points
+ *
+ * where "type-safe" applies to the function pointers used for subscriptions
+ *
+ * overall usage:
+ * - to create a hook:
+ *
+ * mydaemon.h:
+ * #include "hook.h"
+ * DECLARE_HOOK (some_update_event, (struct eventinfo *info), (info));
+ *
+ * mydaemon.c:
+ * DEFINE_HOOK (some_update_event, (struct eventinfo *info), (info));
+ * ...
+ * hook_call (some_update_event, info)
+ *
+ * Note: the second and third macro args must be the hook function's
+ * parameter list, with the same names for each parameter. The second
+ * macro arg is with types (used for defining things), the third arg is
+ * just the names (used for passing along parameters).
+ *
+ * Do not use parameter names starting with "hook", these can collide with
+ * names used by the hook code itself.
+ *
+ * The return value is always "int" for now; hook_call will sum up the
+ * return values from each registered user. Default is 0.
+ *
+ * There are no pre-defined semantics for the value, in most cases it is
+ * ignored. For success/failure indication, 0 should be success, and
+ * handlers should make sure to only return 0 or 1 (not -1 or other values).
+ *
+ *
+ * - to use a hook / create a handler:
+ *
+ * #include "mydaemon.h"
+ * int event_handler (struct eventinfo *info) { ... }
+ * hook_register (some_update_event, event_handler);
+ *
+ * or, if you need an argument to be passed along (addonptr will be added
+ * as first argument when calling the handler):
+ *
+ * #include "mydaemon.h"
+ * int event_handler (void *addonptr, struct eventinfo *info) { ... }
+ * hook_register_arg (some_update_event, event_handler, addonptr);
+ *
+ * (addonptr isn't typesafe, but that should be manageable.)
+ *
+ * Hooks also support a "priority" value for ordering registered calls
+ * relative to each other. The priority is a signed integer where lower
+ * values are called earlier. There is also "Koohs", which is hooks with
+ * reverse priority ordering (for cleanup/deinit hooks, so you can use the
+ * same priority value).
+ *
+ * Recommended priority value ranges are:
+ *
+ * -999 ... 0 ... 999 - main executable / daemon, or library
+ * -1999 ... -1000 - modules registering calls that should run before
+ * the daemon's bits
+ * 1000 ... 1999 - modules calls that should run after daemon's
+ *
+ * Note: the default value is 1000, based on the following 2 expectations:
+ * - most hook_register() usage will be in loadable modules
+ * - usage of hook_register() in the daemon itself may need relative ordering
+ * to itself, making an explicit value the expected case
+ *
+ * The priority value is passed as extra argument on hook_register_prio() /
+ * hook_register_arg_prio(). Whether a hook runs in reverse is determined
+ * solely by the code defining / calling the hook. (DECLARE_KOOH is actually
+ * the same thing as DECLARE_HOOK, it's just there to make it obvious.)
+ */
+
+/* TODO:
+ * - hook_unregister_all_module()
+ * - introspection / CLI / debug
+ * - testcases ;)
+ *
+ * For loadable modules, the idea is that hooks could be automatically
+ * unregistered when a module is unloaded.
+ *
+ * It's also possible to add a constructor (MTYPE style) to DEFINE_HOOK,
+ * which would make it possible for the CLI to show all hooks and all
+ * registered handlers.
+ */
+
+struct hookent {
+ struct hookent *next;
+ void *hookfn; /* actually a function pointer */
+ void *hookarg;
+ bool has_arg : 1;
+ bool ent_on_heap : 1;
+ bool ent_used : 1;
+ int priority;
+ struct frrmod_runtime *module;
+ const char *fnname;
+};
+
+struct hook {
+ const char *name;
+ struct hookent *entries;
+ bool reverse;
+};
+
+#define HOOK_DEFAULT_PRIORITY 1000
+
+/* subscribe/add callback function to a hook
+ *
+ * always use hook_register(), which uses the static inline helper from
+ * DECLARE_HOOK in order to get type safety
+ */
+extern void _hook_register(struct hook *hook, struct hookent *stackent,
+ void *funcptr, void *arg, bool has_arg,
+ struct frrmod_runtime *module,
+ const char *funcname, int priority);
+
+/* most hook_register calls are not in a loop or similar and can use a
+ * statically allocated "struct hookent" from the data segment
+ */
+#define _hook_reg_svar(hook, funcptr, arg, has_arg, module, funcname, prio) \
+ do { \
+ static struct hookent stack_hookent = {}; \
+ _hook_register(hook, &stack_hookent, funcptr, arg, has_arg, \
+ module, funcname, prio); \
+ } while (0)
+
+#define hook_register(hookname, func) \
+ _hook_reg_svar(&_hook_##hookname, _hook_typecheck_##hookname(func), \
+ NULL, false, THIS_MODULE, #func, HOOK_DEFAULT_PRIORITY)
+#define hook_register_arg(hookname, func, arg) \
+ _hook_reg_svar(&_hook_##hookname, \
+ _hook_typecheck_arg_##hookname(func), arg, true, \
+ THIS_MODULE, #func, HOOK_DEFAULT_PRIORITY)
+#define hook_register_prio(hookname, prio, func) \
+ _hook_reg_svar(&_hook_##hookname, _hook_typecheck_##hookname(func), \
+ NULL, false, THIS_MODULE, #func, prio)
+#define hook_register_arg_prio(hookname, prio, func, arg) \
+ _hook_reg_svar(&_hook_##hookname, \
+ _hook_typecheck_arg_##hookname(func), arg, true, \
+ THIS_MODULE, #func, prio)
+
+extern void _hook_unregister(struct hook *hook, void *funcptr, void *arg,
+ bool has_arg);
+#define hook_unregister(hookname, func) \
+ _hook_unregister(&_hook_##hookname, _hook_typecheck_##hookname(func), \
+ NULL, false)
+#define hook_unregister_arg(hookname, func, arg) \
+ _hook_unregister(&_hook_##hookname, \
+ _hook_typecheck_arg_##hookname(func), arg, true)
+
+/* invoke hooks
+ * this is private (static) to the file that has the DEFINE_HOOK statement
+ */
+#define hook_call(hookname, ...) hook_call_##hookname(__VA_ARGS__)
+
+/* helpers to add the void * arg */
+#define HOOK_ADDDEF(...) (void *hookarg , ## __VA_ARGS__)
+#define HOOK_ADDARG(...) (hookarg , ## __VA_ARGS__)
+
+/* and another helper to convert () into (void) to get a proper prototype */
+#define _SKIP_10(x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, ret, ...) ret
+#define _MAKE_VOID(...) _SKIP_10(, ##__VA_ARGS__, , , , , , , , , , void)
+
+#define HOOK_VOIDIFY(...) (_MAKE_VOID(__VA_ARGS__) __VA_ARGS__)
+
+/* use in header file - declares the hook and its arguments
+ * usage: DECLARE_HOOK(my_hook, (int arg1, struct foo *arg2), (arg1, arg2));
+ * as above, "passlist" must use the same order and same names as "arglist"
+ *
+ * theoretically passlist is not necessary, but let's keep things simple and
+ * use exact same args on DECLARE and DEFINE.
+ */
+#define DECLARE_HOOK(hookname, arglist, passlist) \
+ extern struct hook _hook_##hookname; \
+ __attribute__((unused)) static inline void * \
+ _hook_typecheck_##hookname(int(*funcptr) HOOK_VOIDIFY arglist) \
+ { \
+ return (void *)funcptr; \
+ } \
+ __attribute__((unused)) static inline void \
+ *_hook_typecheck_arg_##hookname(int(*funcptr) \
+ HOOK_ADDDEF arglist) \
+ { \
+ return (void *)funcptr; \
+ } \
+ MACRO_REQUIRE_SEMICOLON() /* end */
+
+#define DECLARE_KOOH(hookname, arglist, passlist) \
+ DECLARE_HOOK(hookname, arglist, passlist)
+
+/* use in source file - contains hook-related definitions.
+ */
+#define DEFINE_HOOK_INT(hookname, arglist, passlist, rev) \
+ struct hook _hook_##hookname = { \
+ .name = #hookname, .entries = NULL, .reverse = rev, \
+ }; \
+ static int hook_call_##hookname HOOK_VOIDIFY arglist \
+ { \
+ int hooksum = 0; \
+ struct hookent *he = _hook_##hookname.entries; \
+ void *hookarg; \
+ union { \
+ void *voidptr; \
+ int(*fptr) HOOK_VOIDIFY arglist; \
+ int(*farg) HOOK_ADDDEF arglist; \
+ } hookp; \
+ for (; he; he = he->next) { \
+ hookarg = he->hookarg; \
+ hookp.voidptr = he->hookfn; \
+ if (!he->has_arg) \
+ hooksum += hookp.fptr passlist; \
+ else \
+ hooksum += hookp.farg HOOK_ADDARG passlist; \
+ } \
+ return hooksum; \
+ } \
+ MACRO_REQUIRE_SEMICOLON() /* end */
+
+#define DEFINE_HOOK(hookname, arglist, passlist) \
+ DEFINE_HOOK_INT(hookname, arglist, passlist, false)
+#define DEFINE_KOOH(hookname, arglist, passlist) \
+ DEFINE_HOOK_INT(hookname, arglist, passlist, true)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRR_HOOK_H */
diff --git a/lib/iana_afi.h b/lib/iana_afi.h
new file mode 100644
index 0000000..56e8a24
--- /dev/null
+++ b/lib/iana_afi.h
@@ -0,0 +1,141 @@
+/*
+ * iana_afi and safi definitions.
+ * Copyright (C) 2018-2019 Cumulus Networks, Inc.
+ * Donald Sharp
+ *
+ * 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 __IANA_AFI_H__
+
+#include <prefix.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * The above AFI and SAFI definitions are for internal use. The protocol
+ * definitions (IANA values) as for example used in BGP protocol packets
+ * are defined below and these will get mapped to/from the internal values
+ * in the appropriate places.
+ * The rationale is that the protocol (IANA) values may be sparse and are
+ * not optimal for use in data-structure sizing.
+ * Note: Only useful (i.e., supported) values are defined below.
+ */
+typedef enum {
+ IANA_AFI_RESERVED = 0,
+ IANA_AFI_IPV4 = 1,
+ IANA_AFI_IPV6 = 2,
+ IANA_AFI_L2VPN = 25,
+} iana_afi_t;
+
+typedef enum {
+ IANA_SAFI_RESERVED = 0,
+ IANA_SAFI_UNICAST = 1,
+ IANA_SAFI_MULTICAST = 2,
+ IANA_SAFI_LABELED_UNICAST = 4,
+ IANA_SAFI_ENCAP = 7,
+ IANA_SAFI_EVPN = 70,
+ IANA_SAFI_MPLS_VPN = 128,
+ IANA_SAFI_FLOWSPEC = 133
+} iana_safi_t;
+
+static inline afi_t afi_iana2int(iana_afi_t afi)
+{
+ switch (afi) {
+ case IANA_AFI_IPV4:
+ return AFI_IP;
+ case IANA_AFI_IPV6:
+ return AFI_IP6;
+ case IANA_AFI_L2VPN:
+ return AFI_L2VPN;
+ default:
+ return AFI_MAX;
+ }
+}
+
+static inline iana_afi_t afi_int2iana(afi_t afi)
+{
+ switch (afi) {
+ case AFI_IP:
+ return IANA_AFI_IPV4;
+ case AFI_IP6:
+ return IANA_AFI_IPV6;
+ case AFI_L2VPN:
+ return IANA_AFI_L2VPN;
+ default:
+ return IANA_AFI_RESERVED;
+ }
+}
+
+static inline const char *iana_afi2str(iana_afi_t afi)
+{
+ return afi2str(afi_iana2int(afi));
+}
+
+static inline safi_t safi_iana2int(iana_safi_t safi)
+{
+ switch (safi) {
+ case IANA_SAFI_UNICAST:
+ return SAFI_UNICAST;
+ case IANA_SAFI_MULTICAST:
+ return SAFI_MULTICAST;
+ case IANA_SAFI_MPLS_VPN:
+ return SAFI_MPLS_VPN;
+ case IANA_SAFI_ENCAP:
+ return SAFI_ENCAP;
+ case IANA_SAFI_EVPN:
+ return SAFI_EVPN;
+ case IANA_SAFI_LABELED_UNICAST:
+ return SAFI_LABELED_UNICAST;
+ case IANA_SAFI_FLOWSPEC:
+ return SAFI_FLOWSPEC;
+ default:
+ return SAFI_MAX;
+ }
+}
+
+static inline iana_safi_t safi_int2iana(safi_t safi)
+{
+ switch (safi) {
+ case SAFI_UNICAST:
+ return IANA_SAFI_UNICAST;
+ case SAFI_MULTICAST:
+ return IANA_SAFI_MULTICAST;
+ case SAFI_MPLS_VPN:
+ return IANA_SAFI_MPLS_VPN;
+ case SAFI_ENCAP:
+ return IANA_SAFI_ENCAP;
+ case SAFI_EVPN:
+ return IANA_SAFI_EVPN;
+ case SAFI_LABELED_UNICAST:
+ return IANA_SAFI_LABELED_UNICAST;
+ case SAFI_FLOWSPEC:
+ return IANA_SAFI_FLOWSPEC;
+ default:
+ return IANA_SAFI_RESERVED;
+ }
+}
+
+static inline const char *iana_safi2str(iana_safi_t safi)
+{
+ return safi2str(safi_iana2int(safi));
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/id_alloc.c b/lib/id_alloc.c
new file mode 100644
index 0000000..9179dc4
--- /dev/null
+++ b/lib/id_alloc.c
@@ -0,0 +1,408 @@
+/*
+ * FRR ID Number Allocator
+ * Copyright (C) 2018 Amazon.com, Inc. or its affiliates
+ *
+ * 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
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "id_alloc.h"
+
+#include "log.h"
+#include "lib_errors.h"
+#include "memory.h"
+
+#include <inttypes.h>
+
+DEFINE_MTYPE_STATIC(LIB, IDALLOC_ALLOCATOR, "ID Number Allocator");
+DEFINE_MTYPE_STATIC(LIB, IDALLOC_ALLOCATOR_NAME, "ID Number Allocator Name");
+DEFINE_MTYPE_STATIC(LIB, IDALLOC_DIRECTORY, "ID Number Allocator Directory");
+DEFINE_MTYPE_STATIC(LIB, IDALLOC_SUBDIRECTORY,
+ "ID Number Allocator Subdirectory");
+DEFINE_MTYPE_STATIC(LIB, IDALLOC_PAGE, "ID Number Allocator Page");
+DEFINE_MTYPE_STATIC(LIB, IDALLOC_POOL,
+ "ID Number temporary holding pool entry");
+
+#if UINT_MAX >= UINT32_MAX
+#define FFS32(x) ffs(x)
+#else
+/* ints less than 32 bits? Yikes. */
+#define FFS32(x) ffsl(x)
+#endif
+
+#define DIR_MASK ((1<<IDALLOC_DIR_BITS)-1)
+#define SUBDIR_MASK ((1<<IDALLOC_SUBDIR_BITS)-1)
+#define FRR_ID_PAGE_MASK ((1<<IDALLOC_PAGE_BITS)-1)
+#define WORD_MASK ((1<<IDALLOC_WORD_BITS)-1)
+#define OFFSET_MASK ((1<<IDALLOC_OFFSET_BITS)-1)
+
+#define DIR_SHIFT (IDALLOC_OFFSET_BITS + IDALLOC_WORD_BITS + \
+ IDALLOC_PAGE_BITS + IDALLOC_SUBDIR_BITS)
+#define SUBDIR_SHIFT (IDALLOC_OFFSET_BITS + IDALLOC_WORD_BITS + \
+ IDALLOC_PAGE_BITS)
+#define FRR_ID_PAGE_SHIFT (IDALLOC_OFFSET_BITS + IDALLOC_WORD_BITS)
+#define WORD_SHIFT (IDALLOC_OFFSET_BITS)
+#define OFFSET_SHIFT (0)
+
+#define ID_DIR(id) ((id >> DIR_SHIFT) & DIR_MASK)
+#define ID_SUBDIR(id) ((id >> SUBDIR_SHIFT) & SUBDIR_MASK)
+#define ID_PAGE(id) ((id >> FRR_ID_PAGE_SHIFT) & FRR_ID_PAGE_MASK)
+#define ID_WORD(id) ((id >> WORD_SHIFT) & WORD_MASK)
+#define ID_OFFSET(id) ((id >> OFFSET_SHIFT) & OFFSET_MASK)
+
+/*
+ * Find the page that an ID number belongs to in an allocator.
+ * Optionally create the page if it doesn't exist.
+ */
+static struct id_alloc_page *find_or_create_page(struct id_alloc *alloc,
+ uint32_t id, int create)
+{
+ struct id_alloc_dir *dir = NULL;
+ struct id_alloc_subdir *subdir = NULL;
+ struct id_alloc_page *page = NULL;
+
+ dir = alloc->sublevels[ID_DIR(id)];
+ if (dir == NULL) {
+ if (create) {
+ dir = XCALLOC(MTYPE_IDALLOC_DIRECTORY, sizeof(*dir));
+ alloc->sublevels[ID_DIR(id)] = dir;
+ } else {
+ return NULL;
+ }
+ }
+
+ subdir = dir->sublevels[ID_SUBDIR(id)];
+ if (subdir == NULL) {
+ if (create) {
+ subdir = XCALLOC(MTYPE_IDALLOC_SUBDIRECTORY,
+ sizeof(*subdir));
+ dir->sublevels[ID_SUBDIR(id)] = subdir;
+ } else {
+ return NULL;
+ }
+ }
+
+ page = subdir->sublevels[ID_PAGE(id)];
+ if (page == NULL && create) {
+ page = XCALLOC(MTYPE_IDALLOC_PAGE, sizeof(*page));
+ page->base_value = id;
+ subdir->sublevels[ID_PAGE(id)] = page;
+
+ alloc->capacity += 1 << FRR_ID_PAGE_SHIFT;
+ page->next_has_free = alloc->has_free;
+ alloc->has_free = page;
+ } else if (page != NULL && create) {
+ flog_err(
+ EC_LIB_ID_CONSISTENCY,
+ "ID Allocator %s attempt to re-create page at %u",
+ alloc->name, id);
+ }
+
+ return page;
+}
+
+/*
+ * Return an ID number back to the allocator.
+ * While this ID can be re-assigned through idalloc_allocate, the underlying
+ * memory will not be freed. If this is the first free ID in the page, the page
+ * will be added to the allocator's list of pages with free IDs.
+ */
+void idalloc_free(struct id_alloc *alloc, uint32_t id)
+{
+ struct id_alloc_page *page = NULL;
+
+ int word, offset;
+ uint32_t old_word, old_word_mask;
+
+ page = find_or_create_page(alloc, id, 0);
+ if (!page) {
+ flog_err(EC_LIB_ID_CONSISTENCY,
+ "ID Allocator %s cannot free #%u. ID Block does not exist.",
+ alloc->name, id);
+ return;
+ }
+
+ word = ID_WORD(id);
+ offset = ID_OFFSET(id);
+
+ if ((page->allocated_mask[word] & (1 << offset)) == 0) {
+ flog_err(EC_LIB_ID_CONSISTENCY,
+ "ID Allocator %s cannot free #%u. ID was not allocated at the time of free.",
+ alloc->name, id);
+ return;
+ }
+
+ old_word = page->allocated_mask[word];
+ page->allocated_mask[word] &= ~(((uint32_t)1) << offset);
+ alloc->allocated -= 1;
+
+ if (old_word == UINT32_MAX) {
+ /* first bit in this block of 32 to be freed.*/
+
+ old_word_mask = page->full_word_mask;
+ page->full_word_mask &= ~(((uint32_t)1) << word);
+
+ if (old_word_mask == UINT32_MAX) {
+ /* first bit in page freed, add this to the allocator's
+ * list of pages with free space
+ */
+ page->next_has_free = alloc->has_free;
+ alloc->has_free = page;
+ }
+ }
+}
+
+/*
+ * Add a allocation page to the end of the allocator's current range.
+ * Returns null if the allocator has had all possible pages allocated already.
+ */
+static struct id_alloc_page *create_next_page(struct id_alloc *alloc)
+{
+ if (alloc->capacity == 0 && alloc->sublevels[0])
+ return NULL; /* All IDs allocated and the capacity looped. */
+
+ return find_or_create_page(alloc, alloc->capacity, 1);
+}
+
+/*
+ * Marks an ID within an allocator page as in use.
+ * If the ID was the last free ID in the page, the page is removed from the
+ * allocator's list of free IDs. In the typical allocation case, this page is
+ * the first page in the list, and removing the page is fast. If instead an ID
+ * is being reserved by number, this may end up scanning the whole single linked
+ * list of pages in order to remove it.
+ */
+static void reserve_bit(struct id_alloc *alloc, struct id_alloc_page *page,
+ int word, int offset)
+{
+ struct id_alloc_page *itr;
+
+ page->allocated_mask[word] |= ((uint32_t)1) << offset;
+ alloc->allocated += 1;
+
+ if (page->allocated_mask[word] == UINT32_MAX) {
+ page->full_word_mask |= ((uint32_t)1) << word;
+ if (page->full_word_mask == UINT32_MAX) {
+ if (alloc->has_free == page) {
+ /* allocate always pulls from alloc->has_free */
+ alloc->has_free = page->next_has_free;
+ } else {
+ /* reserve could pull from any page with free
+ * bits
+ */
+ itr = alloc->has_free;
+ while (itr) {
+ if (itr->next_has_free == page) {
+ itr->next_has_free =
+ page->next_has_free;
+ return;
+ }
+
+ itr = itr->next_has_free;
+ }
+ }
+ }
+ }
+}
+
+/*
+ * Reserve an ID number from the allocator. Returns IDALLOC_INVALID (0) if the
+ * allocator has no more IDs available.
+ */
+uint32_t idalloc_allocate(struct id_alloc *alloc)
+{
+ struct id_alloc_page *page;
+ int word, offset;
+ uint32_t return_value;
+
+ if (alloc->has_free == NULL)
+ create_next_page(alloc);
+
+ if (alloc->has_free == NULL) {
+ flog_err(EC_LIB_ID_EXHAUST,
+ "ID Allocator %s has run out of IDs.", alloc->name);
+ return IDALLOC_INVALID;
+ }
+
+ page = alloc->has_free;
+ word = FFS32(~(page->full_word_mask)) - 1;
+
+ if (word < 0 || word >= 32) {
+ flog_err(EC_LIB_ID_CONSISTENCY,
+ "ID Allocator %s internal error. Page starting at %d is inconsistent.",
+ alloc->name, page->base_value);
+ return IDALLOC_INVALID;
+ }
+
+ offset = FFS32(~(page->allocated_mask[word])) - 1;
+ if (offset < 0 || offset >= 32) {
+ flog_err(EC_LIB_ID_CONSISTENCY,
+ "ID Allocator %s internal error. Page starting at %d is inconsistent on word %d",
+ alloc->name, page->base_value, word);
+ return IDALLOC_INVALID;
+ }
+ return_value = page->base_value + word * 32 + offset;
+
+ reserve_bit(alloc, page, word, offset);
+
+ return return_value;
+}
+
+/*
+ * Tries to allocate a specific ID from the allocator. Returns IDALLOC_INVALID
+ * when the ID being "reserved" has allready been assigned/reserved. This should
+ * only be done with low numbered IDs, as the allocator needs to reserve bit-map
+ * pages in order
+ */
+uint32_t idalloc_reserve(struct id_alloc *alloc, uint32_t id)
+{
+ struct id_alloc_page *page;
+ int word, offset;
+
+ while (alloc->capacity <= id)
+ create_next_page(alloc);
+
+ word = ID_WORD(id);
+ offset = ID_OFFSET(id);
+ page = find_or_create_page(alloc, id, 0);
+ /* page can't be null because the loop above ensured it was created. */
+
+ if (page->allocated_mask[word] & (((uint32_t)1) << offset)) {
+ flog_err(EC_LIB_ID_CONSISTENCY,
+ "ID Allocator %s could not reserve %u because it is already allocated.",
+ alloc->name, id);
+ return IDALLOC_INVALID;
+ }
+
+ reserve_bit(alloc, page, word, offset);
+ return id;
+}
+
+/*
+ * Set up an empty ID allocator, with IDALLOC_INVALID pre-reserved.
+ */
+struct id_alloc *idalloc_new(const char *name)
+{
+ struct id_alloc *ret;
+
+ ret = XCALLOC(MTYPE_IDALLOC_ALLOCATOR, sizeof(*ret));
+ ret->name = XSTRDUP(MTYPE_IDALLOC_ALLOCATOR_NAME, name);
+
+ idalloc_reserve(ret, IDALLOC_INVALID);
+
+ return ret;
+}
+
+/*
+ * Free a subdir, and all pages below it.
+ */
+static void idalloc_destroy_subdir(struct id_alloc_subdir *subdir)
+{
+ int i;
+
+ for (i = 0; i < IDALLOC_PAGE_COUNT; i++) {
+ if (subdir->sublevels[i])
+ XFREE(MTYPE_IDALLOC_PAGE, subdir->sublevels[i]);
+ else
+ break;
+ }
+ XFREE(MTYPE_IDALLOC_SUBDIRECTORY, subdir);
+}
+
+/*
+ * Free a dir, and all subdirs/pages below it.
+ */
+static void idalloc_destroy_dir(struct id_alloc_dir *dir)
+{
+ int i;
+
+ for (i = 0; i < IDALLOC_SUBDIR_COUNT; i++) {
+ if (dir->sublevels[i])
+ idalloc_destroy_subdir(dir->sublevels[i]);
+ else
+ break;
+ }
+ XFREE(MTYPE_IDALLOC_DIRECTORY, dir);
+}
+
+/*
+ * Free all memory associated with an ID allocator.
+ */
+void idalloc_destroy(struct id_alloc *alloc)
+{
+ int i;
+
+ for (i = 0; i < IDALLOC_DIR_COUNT; i++) {
+ if (alloc->sublevels[i])
+ idalloc_destroy_dir(alloc->sublevels[i]);
+ else
+ break;
+ }
+
+ XFREE(MTYPE_IDALLOC_ALLOCATOR_NAME, alloc->name);
+ XFREE(MTYPE_IDALLOC_ALLOCATOR, alloc);
+}
+
+/*
+ * Give an ID number to temporary holding pool.
+ */
+void idalloc_free_to_pool(struct id_alloc_pool **pool_ptr, uint32_t id)
+{
+ struct id_alloc_pool *new_pool;
+
+ new_pool = XMALLOC(MTYPE_IDALLOC_POOL, sizeof(*new_pool));
+ new_pool->id = id;
+ new_pool->next = *pool_ptr;
+ *pool_ptr = new_pool;
+}
+
+/*
+ * Free all ID numbers held in a holding pool back to the main allocator.
+ */
+void idalloc_drain_pool(struct id_alloc *alloc, struct id_alloc_pool **pool_ptr)
+{
+ struct id_alloc_pool *current, *next;
+
+ while (*pool_ptr) {
+ current = *pool_ptr;
+ next = current->next;
+ idalloc_free(alloc, current->id);
+ XFREE(MTYPE_IDALLOC_POOL, current);
+ *pool_ptr = next;
+ }
+}
+
+/*
+ * Allocate an ID from either a holding pool, or the main allocator. IDs will
+ * only be pulled form the main allocator when the pool is empty.
+ */
+uint32_t idalloc_allocate_prefer_pool(struct id_alloc *alloc,
+ struct id_alloc_pool **pool_ptr)
+{
+ uint32_t ret;
+ struct id_alloc_pool *pool_head = *pool_ptr;
+
+ if (pool_head) {
+ ret = pool_head->id;
+ *pool_ptr = pool_head->next;
+ XFREE(MTYPE_IDALLOC_POOL, pool_head);
+ return ret;
+ } else {
+ return idalloc_allocate(alloc);
+ }
+}
diff --git a/lib/id_alloc.h b/lib/id_alloc.h
new file mode 100644
index 0000000..8705ffb
--- /dev/null
+++ b/lib/id_alloc.h
@@ -0,0 +1,98 @@
+/*
+ * FRR ID Number Allocator
+ * Copyright (C) 2018 Amazon.com, Inc. or its affiliates
+ *
+ * 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 _ZEBRA_ID_ALLOC_H
+#define _ZEBRA_ID_ALLOC_H
+
+#include <strings.h>
+#include <limits.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define IDALLOC_INVALID 0
+
+#define IDALLOC_DIR_BITS 8
+#define IDALLOC_SUBDIR_BITS 7
+#define IDALLOC_PAGE_BITS 7
+#define IDALLOC_WORD_BITS 5
+#define IDALLOC_OFFSET_BITS 5
+
+#define IDALLOC_DIR_COUNT (1 << IDALLOC_DIR_BITS)
+#define IDALLOC_SUBDIR_COUNT (1 << IDALLOC_SUBDIR_BITS)
+#define IDALLOC_PAGE_COUNT (1 << IDALLOC_PAGE_BITS)
+#define IDALLOC_WORD_COUNT (1 << IDALLOC_WORD_BITS)
+
+struct id_alloc_page {
+ /* Bitmask of allocations. 1s indicates the ID is already allocated. */
+ uint32_t allocated_mask[IDALLOC_WORD_COUNT];
+
+ /* Bitmask for free space in allocated_mask. 1s indicate whole 32 bit
+ * section is full.
+ */
+ uint32_t full_word_mask;
+
+ /* The ID that bit 0 in allocated_mask corresponds to. */
+ uint32_t base_value;
+
+ struct id_alloc_page
+ *next_has_free; /* Next page with at least one bit open */
+};
+
+struct id_alloc_subdir {
+ struct id_alloc_page *sublevels[IDALLOC_PAGE_COUNT];
+};
+
+struct id_alloc_dir {
+ struct id_alloc_subdir *sublevels[IDALLOC_SUBDIR_COUNT];
+};
+
+struct id_alloc {
+ struct id_alloc_dir *sublevels[IDALLOC_DIR_COUNT];
+
+ struct id_alloc_page *has_free;
+
+ char *name;
+
+ uint32_t allocated, capacity;
+};
+
+struct id_alloc_pool {
+ struct id_alloc_pool *next;
+ uint32_t id;
+};
+
+void idalloc_free(struct id_alloc *alloc, uint32_t id);
+void idalloc_free_to_pool(struct id_alloc_pool **pool_ptr, uint32_t id);
+void idalloc_drain_pool(struct id_alloc *alloc,
+ struct id_alloc_pool **pool_ptr);
+uint32_t idalloc_allocate(struct id_alloc *alloc);
+uint32_t idalloc_allocate_prefer_pool(struct id_alloc *alloc,
+ struct id_alloc_pool **pool_ptr);
+uint32_t idalloc_reserve(struct id_alloc *alloc, uint32_t id);
+struct id_alloc *idalloc_new(const char *name);
+void idalloc_destroy(struct id_alloc *alloc);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/if.c b/lib/if.c
new file mode 100644
index 0000000..fa4fdb8
--- /dev/null
+++ b/lib/if.c
@@ -0,0 +1,1803 @@
+/*
+ * Interface functions.
+ * Copyright (C) 1997, 98 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra 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 "linklist.h"
+#include "vector.h"
+#include "lib_errors.h"
+#include "vty.h"
+#include "command.h"
+#include "vrf.h"
+#include "if.h"
+#include "sockunion.h"
+#include "prefix.h"
+#include "memory.h"
+#include "table.h"
+#include "buffer.h"
+#include "log.h"
+#include "northbound_cli.h"
+#ifndef VTYSH_EXTRACT_PL
+#include "lib/if_clippy.c"
+#endif
+
+DEFINE_MTYPE_STATIC(LIB, IF, "Interface");
+DEFINE_MTYPE_STATIC(LIB, CONNECTED, "Connected");
+DEFINE_MTYPE_STATIC(LIB, NBR_CONNECTED, "Neighbor Connected");
+DEFINE_MTYPE(LIB, CONNECTED_LABEL, "Connected interface label");
+DEFINE_MTYPE_STATIC(LIB, IF_LINK_PARAMS, "Informational Link Parameters");
+
+static void if_set_name(struct interface *ifp, const char *name);
+static struct interface *if_lookup_by_ifindex(ifindex_t ifindex,
+ vrf_id_t vrf_id);
+static struct interface *if_lookup_by_index_all_vrf(ifindex_t ifindex);
+static int if_cmp_func(const struct interface *, const struct interface *);
+static int if_cmp_index_func(const struct interface *ifp1,
+ const struct interface *ifp2);
+RB_GENERATE(if_name_head, interface, name_entry, if_cmp_func);
+RB_GENERATE(if_index_head, interface, index_entry, if_cmp_index_func);
+
+DEFINE_QOBJ_TYPE(interface);
+
+DEFINE_HOOK(if_add, (struct interface * ifp), (ifp));
+DEFINE_KOOH(if_del, (struct interface * ifp), (ifp));
+
+static struct interface_master{
+ int (*create_hook)(struct interface *ifp);
+ int (*up_hook)(struct interface *ifp);
+ int (*down_hook)(struct interface *ifp);
+ int (*destroy_hook)(struct interface *ifp);
+} ifp_master = { 0, };
+
+/* Compare interface names, returning an integer greater than, equal to, or
+ * less than 0, (following the strcmp convention), according to the
+ * relationship between ifp1 and ifp2. Interface names consist of an
+ * alphabetic prefix and a numeric suffix. The primary sort key is
+ * lexicographic by name, and then numeric by number. No number sorts
+ * before all numbers. Examples: de0 < de1, de100 < fxp0 < xl0, devpty <
+ * devpty0, de0 < del0
+ */
+int if_cmp_name_func(const char *p1, const char *p2)
+{
+ unsigned int l1, l2;
+ long int x1, x2;
+ int res;
+
+ while (*p1 && *p2) {
+ char *tmp1, *tmp2;
+
+ /* look up to any number */
+ l1 = strcspn(p1, "0123456789");
+ l2 = strcspn(p2, "0123456789");
+
+ /* name lengths are different -> compare names */
+ if (l1 != l2)
+ return (strcmp(p1, p2));
+
+ /* Note that this relies on all numbers being less than all
+ * letters, so
+ * that de0 < del0.
+ */
+ res = strncmp(p1, p2, l1);
+
+ /* names are different -> compare them */
+ if (res)
+ return res;
+
+ /* with identical name part, go to numeric part */
+ p1 += l1;
+ p2 += l1;
+
+ if (!*p1 && !*p2)
+ return 0;
+ if (!*p1)
+ return -1;
+ if (!*p2)
+ return 1;
+
+ x1 = strtol(p1, (char **)&tmp1, 10);
+ x2 = strtol(p2, (char **)&tmp2, 10);
+
+ /* let's compare numbers now */
+ if (x1 < x2)
+ return -1;
+ if (x1 > x2)
+ return 1;
+
+ /* Compare string if numbers are equal (distinguish foo-1 from foo-001) */
+ l1 = strspn(p1, "0123456789");
+ l2 = strspn(p2, "0123456789");
+ if (l1 != l2)
+ return (strcmp(p1, p2));
+
+ /* Continue to parse the rest of the string */
+ p1 = (const char *)tmp1;
+ p2 = (const char *)tmp2;
+
+ /* numbers were equal, lets do it again..
+ (it happens with name like "eth123.456:789") */
+ }
+ if (*p1)
+ return 1;
+ if (*p2)
+ return -1;
+ return 0;
+}
+
+static int if_cmp_func(const struct interface *ifp1,
+ const struct interface *ifp2)
+{
+ return if_cmp_name_func(ifp1->name, ifp2->name);
+}
+
+static int if_cmp_index_func(const struct interface *ifp1,
+ const struct interface *ifp2)
+{
+ if (ifp1->ifindex == ifp2->ifindex)
+ return 0;
+ else if (ifp1->ifindex > ifp2->ifindex)
+ return 1;
+ else
+ return -1;
+}
+
+static void ifp_connected_free(void *arg)
+{
+ struct connected *c = arg;
+
+ connected_free(&c);
+}
+
+/* Create new interface structure. */
+static struct interface *if_new(struct vrf *vrf)
+{
+ struct interface *ifp;
+
+ assert(vrf);
+
+ ifp = XCALLOC(MTYPE_IF, sizeof(struct interface));
+
+ ifp->ifindex = IFINDEX_INTERNAL;
+ ifp->name[0] = '\0';
+
+ ifp->vrf = vrf;
+
+ ifp->connected = list_new();
+ ifp->connected->del = ifp_connected_free;
+
+ ifp->nbr_connected = list_new();
+ ifp->nbr_connected->del = (void (*)(void *))nbr_connected_free;
+
+ /* Enable Link-detection by default */
+ SET_FLAG(ifp->status, ZEBRA_INTERFACE_LINKDETECTION);
+
+ QOBJ_REG(ifp, interface);
+ return ifp;
+}
+
+void if_new_via_zapi(struct interface *ifp)
+{
+ if (ifp_master.create_hook)
+ (*ifp_master.create_hook)(ifp);
+}
+
+void if_destroy_via_zapi(struct interface *ifp)
+{
+ if (ifp_master.destroy_hook)
+ (*ifp_master.destroy_hook)(ifp);
+
+ ifp->oldifindex = ifp->ifindex;
+ if_set_index(ifp, IFINDEX_INTERNAL);
+
+ if (!ifp->configured)
+ if_delete(&ifp);
+}
+
+void if_up_via_zapi(struct interface *ifp)
+{
+ if (ifp_master.up_hook)
+ (*ifp_master.up_hook)(ifp);
+}
+
+void if_down_via_zapi(struct interface *ifp)
+{
+ if (ifp_master.down_hook)
+ (*ifp_master.down_hook)(ifp);
+}
+
+static struct interface *if_create_name(const char *name, struct vrf *vrf)
+{
+ struct interface *ifp;
+
+ ifp = if_new(vrf);
+
+ if_set_name(ifp, name);
+
+ hook_call(if_add, ifp);
+ return ifp;
+}
+
+/* Create new interface structure. */
+void if_update_to_new_vrf(struct interface *ifp, vrf_id_t vrf_id)
+{
+ struct vrf *old_vrf, *vrf;
+
+ /* remove interface from old master vrf list */
+ old_vrf = ifp->vrf;
+
+ if (ifp->name[0] != '\0')
+ IFNAME_RB_REMOVE(old_vrf, ifp);
+
+ if (ifp->ifindex != IFINDEX_INTERNAL)
+ IFINDEX_RB_REMOVE(old_vrf, ifp);
+
+ vrf = vrf_get(vrf_id, NULL);
+ ifp->vrf = vrf;
+
+ if (ifp->name[0] != '\0')
+ IFNAME_RB_INSERT(vrf, ifp);
+
+ if (ifp->ifindex != IFINDEX_INTERNAL)
+ IFINDEX_RB_INSERT(vrf, ifp);
+}
+
+
+/* Delete interface structure. */
+void if_delete_retain(struct interface *ifp)
+{
+ hook_call(if_del, ifp);
+ QOBJ_UNREG(ifp);
+
+ /* Free connected address list */
+ list_delete_all_node(ifp->connected);
+
+ /* Free connected nbr address list */
+ list_delete_all_node(ifp->nbr_connected);
+}
+
+/* Delete and free interface structure. */
+void if_delete(struct interface **ifp)
+{
+ struct interface *ptr = *ifp;
+ struct vrf *vrf = ptr->vrf;
+
+ IFNAME_RB_REMOVE(vrf, ptr);
+ if (ptr->ifindex != IFINDEX_INTERNAL)
+ IFINDEX_RB_REMOVE(vrf, ptr);
+
+ if_delete_retain(ptr);
+
+ list_delete(&ptr->connected);
+ list_delete(&ptr->nbr_connected);
+
+ if_link_params_free(ptr);
+
+ XFREE(MTYPE_TMP, ptr->desc);
+
+ XFREE(MTYPE_IF, ptr);
+ *ifp = NULL;
+}
+
+/* Used only internally to check within VRF only */
+static struct interface *if_lookup_by_ifindex(ifindex_t ifindex,
+ vrf_id_t vrf_id)
+{
+ struct vrf *vrf;
+ struct interface if_tmp;
+
+ vrf = vrf_lookup_by_id(vrf_id);
+ if (!vrf)
+ return NULL;
+
+ if_tmp.ifindex = ifindex;
+ return RB_FIND(if_index_head, &vrf->ifaces_by_index, &if_tmp);
+}
+
+/* Interface existence check by index. */
+struct interface *if_lookup_by_index(ifindex_t ifindex, vrf_id_t vrf_id)
+{
+ switch (vrf_get_backend()) {
+ case VRF_BACKEND_UNKNOWN:
+ case VRF_BACKEND_NETNS:
+ return(if_lookup_by_ifindex(ifindex, vrf_id));
+ case VRF_BACKEND_VRF_LITE:
+ return(if_lookup_by_index_all_vrf(ifindex));
+ }
+ return NULL;
+}
+
+/* Interface existence check by index. */
+struct interface *if_vrf_lookup_by_index_next(ifindex_t ifindex,
+ vrf_id_t vrf_id)
+{
+ struct vrf *vrf = vrf_lookup_by_id(vrf_id);
+ struct interface *tmp_ifp;
+ bool found = false;
+
+ if (!vrf)
+ return NULL;
+
+ if (ifindex == 0) {
+ tmp_ifp = RB_MIN(if_index_head, &vrf->ifaces_by_index);
+ /* skip the vrf interface */
+ if (tmp_ifp && if_is_vrf(tmp_ifp))
+ ifindex = tmp_ifp->ifindex;
+ else
+ return tmp_ifp;
+ }
+
+ RB_FOREACH (tmp_ifp, if_index_head, &vrf->ifaces_by_index) {
+ if (found) {
+ /* skip the vrf interface */
+ if (tmp_ifp && if_is_vrf(tmp_ifp))
+ continue;
+ else
+ return tmp_ifp;
+ }
+ if (tmp_ifp->ifindex == ifindex)
+ found = true;
+ }
+ return NULL;
+}
+
+const char *ifindex2ifname(ifindex_t ifindex, vrf_id_t vrf_id)
+{
+ struct interface *ifp;
+
+ return ((ifp = if_lookup_by_index(ifindex, vrf_id)) != NULL)
+ ? ifp->name
+ : "unknown";
+}
+
+ifindex_t ifname2ifindex(const char *name, vrf_id_t vrf_id)
+{
+ struct interface *ifp;
+
+ return ((ifp = if_lookup_by_name(name, vrf_id)) != NULL)
+ ? ifp->ifindex
+ : IFINDEX_INTERNAL;
+}
+
+/* Interface existence check by interface name. */
+struct interface *if_lookup_by_name(const char *name, vrf_id_t vrf_id)
+{
+ struct vrf *vrf = vrf_lookup_by_id(vrf_id);
+ struct interface if_tmp;
+
+ if (!vrf || !name
+ || strnlen(name, INTERFACE_NAMSIZ) == INTERFACE_NAMSIZ)
+ return NULL;
+
+ strlcpy(if_tmp.name, name, sizeof(if_tmp.name));
+ return RB_FIND(if_name_head, &vrf->ifaces_by_name, &if_tmp);
+}
+
+struct interface *if_lookup_by_name_vrf(const char *name, struct vrf *vrf)
+{
+ struct interface if_tmp;
+
+ if (!name || strnlen(name, INTERFACE_NAMSIZ) == INTERFACE_NAMSIZ)
+ return NULL;
+
+ strlcpy(if_tmp.name, name, sizeof(if_tmp.name));
+ return RB_FIND(if_name_head, &vrf->ifaces_by_name, &if_tmp);
+}
+
+static struct interface *if_lookup_by_name_all_vrf(const char *name)
+{
+ struct vrf *vrf;
+ struct interface *ifp;
+
+ if (!name || strnlen(name, INTERFACE_NAMSIZ) == INTERFACE_NAMSIZ)
+ return NULL;
+
+ RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) {
+ ifp = if_lookup_by_name_vrf(name, vrf);
+ if (ifp)
+ return ifp;
+ }
+
+ return NULL;
+}
+
+static struct interface *if_lookup_by_index_all_vrf(ifindex_t ifindex)
+{
+ struct vrf *vrf;
+ struct interface *ifp;
+
+ if (ifindex == IFINDEX_INTERNAL)
+ return NULL;
+
+ RB_FOREACH (vrf, vrf_id_head, &vrfs_by_id) {
+ ifp = if_lookup_by_ifindex(ifindex, vrf->vrf_id);
+ if (ifp)
+ return ifp;
+ }
+
+ return NULL;
+}
+
+/* Lookup interface by IP address.
+ *
+ * supersedes if_lookup_exact_address(), which didn't care about up/down
+ * state. but all users we have either only care if the address is local
+ * (=> use if_address_is_local() please), or care about UP interfaces before
+ * anything else
+ *
+ * to accept only UP interfaces, check if_is_up() on the returned ifp.
+ */
+struct interface *if_lookup_address_local(const void *src, int family,
+ vrf_id_t vrf_id)
+{
+ struct vrf *vrf = vrf_lookup_by_id(vrf_id);
+ struct listnode *cnode;
+ struct interface *ifp, *best_down = NULL;
+ struct prefix *p;
+ struct connected *c;
+
+ if (family != AF_INET && family != AF_INET6)
+ return NULL;
+
+ FOR_ALL_INTERFACES (vrf, ifp) {
+ for (ALL_LIST_ELEMENTS_RO(ifp->connected, cnode, c)) {
+ p = c->address;
+
+ if (!p || p->family != family)
+ continue;
+
+ if (family == AF_INET) {
+ if (!IPV4_ADDR_SAME(&p->u.prefix4,
+ (struct in_addr *)src))
+ continue;
+ } else if (family == AF_INET6) {
+ if (!IPV6_ADDR_SAME(&p->u.prefix6,
+ (struct in6_addr *)src))
+ continue;
+ }
+
+ if (if_is_up(ifp))
+ return ifp;
+ if (!best_down)
+ best_down = ifp;
+ }
+ }
+ return best_down;
+}
+
+/* Lookup interface by IP address. */
+struct connected *if_lookup_address(const void *matchaddr, int family,
+ vrf_id_t vrf_id)
+{
+ struct vrf *vrf = vrf_lookup_by_id(vrf_id);
+ struct prefix addr;
+ int bestlen = 0;
+ struct listnode *cnode;
+ struct interface *ifp;
+ struct connected *c;
+ struct connected *match;
+
+ if (family == AF_INET) {
+ addr.family = AF_INET;
+ addr.u.prefix4 = *((struct in_addr *)matchaddr);
+ addr.prefixlen = IPV4_MAX_BITLEN;
+ } else if (family == AF_INET6) {
+ addr.family = AF_INET6;
+ addr.u.prefix6 = *((struct in6_addr *)matchaddr);
+ addr.prefixlen = IPV6_MAX_BITLEN;
+ } else
+ assert(!"Attempted lookup of family not supported");
+
+ match = NULL;
+
+ FOR_ALL_INTERFACES (vrf, ifp) {
+ for (ALL_LIST_ELEMENTS_RO(ifp->connected, cnode, c)) {
+ if (c->address && (c->address->family == AF_INET)
+ && prefix_match(CONNECTED_PREFIX(c), &addr)
+ && (c->address->prefixlen > bestlen)) {
+ bestlen = c->address->prefixlen;
+ match = c;
+ }
+ }
+ }
+ return match;
+}
+
+/* Lookup interface by prefix */
+struct interface *if_lookup_prefix(const struct prefix *prefix, vrf_id_t vrf_id)
+{
+ struct vrf *vrf = vrf_lookup_by_id(vrf_id);
+ struct listnode *cnode;
+ struct interface *ifp;
+ struct connected *c;
+
+ FOR_ALL_INTERFACES (vrf, ifp) {
+ for (ALL_LIST_ELEMENTS_RO(ifp->connected, cnode, c)) {
+ if (prefix_cmp(c->address, prefix) == 0) {
+ return ifp;
+ }
+ }
+ }
+ return NULL;
+}
+
+size_t if_lookup_by_hwaddr(const uint8_t *hw_addr, size_t addrsz,
+ struct interface ***result, vrf_id_t vrf_id)
+{
+ struct vrf *vrf = vrf_lookup_by_id(vrf_id);
+
+ struct list *rs = list_new();
+ struct interface *ifp;
+
+ FOR_ALL_INTERFACES (vrf, ifp) {
+ if (ifp->hw_addr_len == (int)addrsz
+ && !memcmp(hw_addr, ifp->hw_addr, addrsz))
+ listnode_add(rs, ifp);
+ }
+
+ if (rs->count) {
+ *result = XCALLOC(MTYPE_TMP,
+ sizeof(struct interface *) * rs->count);
+ list_to_array(rs, (void **)*result, rs->count);
+ }
+
+ int count = rs->count;
+
+ list_delete(&rs);
+
+ return count;
+}
+
+
+/* Get interface by name if given name interface doesn't exist create
+ one. */
+struct interface *if_get_by_name(const char *name, vrf_id_t vrf_id,
+ const char *vrf_name)
+{
+ struct interface *ifp = NULL;
+ struct vrf *vrf;
+
+ switch (vrf_get_backend()) {
+ case VRF_BACKEND_UNKNOWN:
+ case VRF_BACKEND_NETNS:
+ vrf = vrf_get(vrf_id, vrf_name);
+ assert(vrf);
+
+ ifp = if_lookup_by_name_vrf(name, vrf);
+ if (ifp) {
+ /* If it came from the kernel or by way of zclient,
+ * believe it and update the ifp accordingly.
+ */
+ if (ifp->vrf->vrf_id != vrf_id && vrf_id != VRF_UNKNOWN)
+ if_update_to_new_vrf(ifp, vrf_id);
+
+ return ifp;
+ }
+
+ break;
+ case VRF_BACKEND_VRF_LITE:
+ ifp = if_lookup_by_name_all_vrf(name);
+ if (ifp) {
+ /* If it came from the kernel or by way of zclient,
+ * believe it and update the ifp accordingly.
+ */
+ if (ifp->vrf->vrf_id != vrf_id && vrf_id != VRF_UNKNOWN)
+ if_update_to_new_vrf(ifp, vrf_id);
+
+ return ifp;
+ }
+
+ vrf = vrf_get(vrf_id, vrf_name);
+ assert(vrf);
+
+ break;
+ default:
+ return NULL;
+ }
+
+ return if_create_name(name, vrf);
+}
+
+int if_set_index(struct interface *ifp, ifindex_t ifindex)
+{
+ if (ifp->ifindex == ifindex)
+ return 0;
+
+ /*
+ * If there is already an interface with this ifindex, we will collide
+ * on insertion, so don't even try.
+ */
+ if (if_lookup_by_ifindex(ifindex, ifp->vrf->vrf_id))
+ return -1;
+
+ if (ifp->ifindex != IFINDEX_INTERNAL)
+ IFINDEX_RB_REMOVE(ifp->vrf, ifp);
+
+ ifp->ifindex = ifindex;
+
+ if (ifp->ifindex != IFINDEX_INTERNAL) {
+ /*
+ * This should never happen, since we checked if there was
+ * already an interface with the desired ifindex at the top of
+ * the function. Nevertheless.
+ */
+ if (IFINDEX_RB_INSERT(ifp->vrf, ifp))
+ return -1;
+ }
+
+ return 0;
+}
+
+static void if_set_name(struct interface *ifp, const char *name)
+{
+ if (if_cmp_name_func(ifp->name, name) == 0)
+ return;
+
+ if (ifp->name[0] != '\0')
+ IFNAME_RB_REMOVE(ifp->vrf, ifp);
+
+ strlcpy(ifp->name, name, sizeof(ifp->name));
+
+ if (ifp->name[0] != '\0')
+ IFNAME_RB_INSERT(ifp->vrf, ifp);
+}
+
+/* Does interface up ? */
+int if_is_up(const struct interface *ifp)
+{
+ return ifp->flags & IFF_UP;
+}
+
+/* Is interface running? */
+int if_is_running(const struct interface *ifp)
+{
+ return ifp->flags & IFF_RUNNING;
+}
+
+/* Is the interface operative, eg. either UP & RUNNING
+ or UP & !ZEBRA_INTERFACE_LINK_DETECTION and
+ if ptm checking is enabled, then ptm check has passed */
+int if_is_operative(const struct interface *ifp)
+{
+ return ((ifp->flags & IFF_UP)
+ && (((ifp->flags & IFF_RUNNING)
+ && (ifp->ptm_status || !ifp->ptm_enable))
+ || !CHECK_FLAG(ifp->status,
+ ZEBRA_INTERFACE_LINKDETECTION)));
+}
+
+/* Is the interface operative, eg. either UP & RUNNING
+ or UP & !ZEBRA_INTERFACE_LINK_DETECTION, without PTM check */
+int if_is_no_ptm_operative(const struct interface *ifp)
+{
+ return ((ifp->flags & IFF_UP)
+ && ((ifp->flags & IFF_RUNNING)
+ || !CHECK_FLAG(ifp->status,
+ ZEBRA_INTERFACE_LINKDETECTION)));
+}
+
+/* Is this loopback interface ? */
+int if_is_loopback_exact(const struct interface *ifp)
+{
+ /* XXX: Do this better, eg what if IFF_WHATEVER means X on platform M
+ * but Y on platform N?
+ */
+ return (ifp->flags & (IFF_LOOPBACK | IFF_NOXMIT | IFF_VIRTUAL));
+}
+
+/* Check interface is VRF */
+int if_is_vrf(const struct interface *ifp)
+{
+ return CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_VRF_LOOPBACK);
+}
+
+/* Should this interface be treated as a loopback? */
+bool if_is_loopback(const struct interface *ifp)
+{
+ if (if_is_loopback_exact(ifp) || if_is_vrf(ifp))
+ return true;
+
+ return false;
+}
+
+/* Does this interface support broadcast ? */
+int if_is_broadcast(const struct interface *ifp)
+{
+ return ifp->flags & IFF_BROADCAST;
+}
+
+/* Does this interface support pointopoint ? */
+int if_is_pointopoint(const struct interface *ifp)
+{
+ return ifp->flags & IFF_POINTOPOINT;
+}
+
+/* Does this interface support multicast ? */
+int if_is_multicast(const struct interface *ifp)
+{
+ return ifp->flags & IFF_MULTICAST;
+}
+
+/* Printout flag information into log */
+const char *if_flag_dump(unsigned long flag)
+{
+ int separator = 0;
+ static char logbuf[BUFSIZ];
+
+#define IFF_OUT_LOG(X, STR) \
+ if (flag & (X)) { \
+ if (separator) \
+ strlcat(logbuf, ",", sizeof(logbuf)); \
+ else \
+ separator = 1; \
+ strlcat(logbuf, STR, sizeof(logbuf)); \
+ }
+
+ strlcpy(logbuf, "<", BUFSIZ);
+ IFF_OUT_LOG(IFF_UP, "UP");
+ IFF_OUT_LOG(IFF_BROADCAST, "BROADCAST");
+ IFF_OUT_LOG(IFF_DEBUG, "DEBUG");
+ IFF_OUT_LOG(IFF_LOOPBACK, "LOOPBACK");
+ IFF_OUT_LOG(IFF_POINTOPOINT, "POINTOPOINT");
+ IFF_OUT_LOG(IFF_NOTRAILERS, "NOTRAILERS");
+ IFF_OUT_LOG(IFF_RUNNING, "RUNNING");
+ IFF_OUT_LOG(IFF_NOARP, "NOARP");
+ IFF_OUT_LOG(IFF_PROMISC, "PROMISC");
+ IFF_OUT_LOG(IFF_ALLMULTI, "ALLMULTI");
+ IFF_OUT_LOG(IFF_OACTIVE, "OACTIVE");
+ IFF_OUT_LOG(IFF_SIMPLEX, "SIMPLEX");
+ IFF_OUT_LOG(IFF_LINK0, "LINK0");
+ IFF_OUT_LOG(IFF_LINK1, "LINK1");
+ IFF_OUT_LOG(IFF_LINK2, "LINK2");
+ IFF_OUT_LOG(IFF_MULTICAST, "MULTICAST");
+ IFF_OUT_LOG(IFF_NOXMIT, "NOXMIT");
+ IFF_OUT_LOG(IFF_NORTEXCH, "NORTEXCH");
+ IFF_OUT_LOG(IFF_VIRTUAL, "VIRTUAL");
+ IFF_OUT_LOG(IFF_IPV4, "IPv4");
+ IFF_OUT_LOG(IFF_IPV6, "IPv6");
+
+ strlcat(logbuf, ">", sizeof(logbuf));
+
+ return logbuf;
+#undef IFF_OUT_LOG
+}
+
+/* For debugging */
+static void if_dump(const struct interface *ifp)
+{
+ struct listnode *node;
+ struct connected *c __attribute__((unused));
+
+ for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, c))
+ zlog_info(
+ "Interface %s vrf %s(%u) index %d metric %d mtu %d mtu6 %d %s",
+ ifp->name, ifp->vrf->name, ifp->vrf->vrf_id,
+ ifp->ifindex, ifp->metric, ifp->mtu, ifp->mtu6,
+ if_flag_dump(ifp->flags));
+}
+
+/* Interface printing for all interface. */
+void if_dump_all(void)
+{
+ struct vrf *vrf;
+ void *ifp;
+
+ RB_FOREACH (vrf, vrf_id_head, &vrfs_by_id)
+ FOR_ALL_INTERFACES (vrf, ifp)
+ if_dump(ifp);
+}
+
+/* Allocate connected structure. */
+struct connected *connected_new(void)
+{
+ return XCALLOC(MTYPE_CONNECTED, sizeof(struct connected));
+}
+
+/* Allocate nbr connected structure. */
+struct nbr_connected *nbr_connected_new(void)
+{
+ return XCALLOC(MTYPE_NBR_CONNECTED, sizeof(struct nbr_connected));
+}
+
+/* Free connected structure. */
+void connected_free(struct connected **connected)
+{
+ struct connected *ptr = *connected;
+
+ prefix_free(&ptr->address);
+ prefix_free(&ptr->destination);
+
+ XFREE(MTYPE_CONNECTED_LABEL, ptr->label);
+
+ XFREE(MTYPE_CONNECTED, ptr);
+ *connected = NULL;
+}
+
+/* Free nbr connected structure. */
+void nbr_connected_free(struct nbr_connected *connected)
+{
+ if (connected->address)
+ prefix_free(&connected->address);
+
+ XFREE(MTYPE_NBR_CONNECTED, connected);
+}
+
+/* If same interface nbr address already exists... */
+struct nbr_connected *nbr_connected_check(struct interface *ifp,
+ struct prefix *p)
+{
+ struct nbr_connected *ifc;
+ struct listnode *node;
+
+ for (ALL_LIST_ELEMENTS_RO(ifp->nbr_connected, node, ifc))
+ if (prefix_same(ifc->address, p))
+ return ifc;
+
+ return NULL;
+}
+
+/* Print if_addr structure. */
+static void __attribute__((unused))
+connected_log(struct connected *connected, char *str)
+{
+ struct prefix *p;
+ struct interface *ifp;
+ char logbuf[BUFSIZ];
+ char buf[BUFSIZ];
+
+ ifp = connected->ifp;
+ p = connected->address;
+
+ snprintf(logbuf, sizeof(logbuf), "%s interface %s vrf %s(%u) %s %pFX ",
+ str, ifp->name, ifp->vrf->name, ifp->vrf->vrf_id,
+ prefix_family_str(p), p);
+
+ p = connected->destination;
+ if (p) {
+ strlcat(logbuf, inet_ntop(p->family, &p->u.prefix, buf, BUFSIZ),
+ BUFSIZ);
+ }
+ zlog_info("%s", logbuf);
+}
+
+/* Print if_addr structure. */
+static void __attribute__((unused))
+nbr_connected_log(struct nbr_connected *connected, char *str)
+{
+ struct prefix *p;
+ struct interface *ifp;
+ char logbuf[BUFSIZ];
+
+ ifp = connected->ifp;
+ p = connected->address;
+
+ snprintf(logbuf, sizeof(logbuf), "%s interface %s %s %pFX ", str,
+ ifp->name, prefix_family_str(p), p);
+
+ zlog_info("%s", logbuf);
+}
+
+/* If two connected address has same prefix return 1. */
+static int connected_same_prefix(const struct prefix *p1,
+ const struct prefix *p2)
+{
+ if (p1->family == p2->family) {
+ if (p1->family == AF_INET
+ && IPV4_ADDR_SAME(&p1->u.prefix4, &p2->u.prefix4))
+ return 1;
+ if (p1->family == AF_INET6
+ && IPV6_ADDR_SAME(&p1->u.prefix6, &p2->u.prefix6))
+ return 1;
+ }
+ return 0;
+}
+
+/* count the number of connected addresses that are in the given family */
+unsigned int connected_count_by_family(struct interface *ifp, int family)
+{
+ struct listnode *cnode;
+ struct connected *connected;
+ unsigned int cnt = 0;
+
+ for (ALL_LIST_ELEMENTS_RO(ifp->connected, cnode, connected))
+ if (connected->address->family == family)
+ cnt++;
+
+ return cnt;
+}
+
+struct connected *connected_lookup_prefix_exact(struct interface *ifp,
+ const struct prefix *p)
+{
+ struct listnode *node;
+ struct listnode *next;
+ struct connected *ifc;
+
+ for (node = listhead(ifp->connected); node; node = next) {
+ ifc = listgetdata(node);
+ next = node->next;
+
+ if (connected_same_prefix(ifc->address, p))
+ return ifc;
+ }
+ return NULL;
+}
+
+struct connected *connected_delete_by_prefix(struct interface *ifp,
+ struct prefix *p)
+{
+ struct listnode *node;
+ struct listnode *next;
+ struct connected *ifc;
+
+ /* In case of same prefix come, replace it with new one. */
+ for (node = listhead(ifp->connected); node; node = next) {
+ ifc = listgetdata(node);
+ next = node->next;
+
+ if (connected_same_prefix(ifc->address, p)) {
+ listnode_delete(ifp->connected, ifc);
+ return ifc;
+ }
+ }
+ return NULL;
+}
+
+/* Find the address on our side that will be used when packets
+ are sent to dst. */
+struct connected *connected_lookup_prefix(struct interface *ifp,
+ const struct prefix *addr)
+{
+ struct listnode *cnode;
+ struct connected *c;
+ struct connected *match;
+
+ match = NULL;
+
+ for (ALL_LIST_ELEMENTS_RO(ifp->connected, cnode, c)) {
+ if (c->address && (c->address->family == addr->family)
+ && prefix_match(CONNECTED_PREFIX(c), addr)
+ && (!match
+ || (c->address->prefixlen > match->address->prefixlen)))
+ match = c;
+ }
+ return match;
+}
+
+struct connected *connected_add_by_prefix(struct interface *ifp,
+ struct prefix *p,
+ struct prefix *destination)
+{
+ struct connected *ifc;
+
+ /* Allocate new connected address. */
+ ifc = connected_new();
+ ifc->ifp = ifp;
+
+ /* Fetch interface address */
+ ifc->address = prefix_new();
+ memcpy(ifc->address, p, sizeof(struct prefix));
+
+ /* Fetch dest address */
+ if (destination) {
+ ifc->destination = prefix_new();
+ memcpy(ifc->destination, destination, sizeof(struct prefix));
+ }
+
+ /* Add connected address to the interface. */
+ listnode_add(ifp->connected, ifc);
+ return ifc;
+}
+
+struct connected *connected_get_linklocal(struct interface *ifp)
+{
+ struct listnode *n;
+ struct connected *c = NULL;
+
+ for (ALL_LIST_ELEMENTS_RO(ifp->connected, n, c)) {
+ if (c->address->family == AF_INET6
+ && IN6_IS_ADDR_LINKLOCAL(&c->address->u.prefix6))
+ break;
+ }
+ return c;
+}
+
+void if_terminate(struct vrf *vrf)
+{
+ struct interface *ifp;
+
+ while (!RB_EMPTY(if_name_head, &vrf->ifaces_by_name)) {
+ ifp = RB_ROOT(if_name_head, &vrf->ifaces_by_name);
+
+ if (ifp->node) {
+ ifp->node->info = NULL;
+ route_unlock_node(ifp->node);
+ }
+ if_delete(&ifp);
+ }
+}
+
+const char *if_link_type_str(enum zebra_link_type llt)
+{
+ switch (llt) {
+#define llts(T,S) case (T): return (S)
+ llts(ZEBRA_LLT_UNKNOWN, "Unknown");
+ llts(ZEBRA_LLT_ETHER, "Ethernet");
+ llts(ZEBRA_LLT_EETHER, "Experimental Ethernet");
+ llts(ZEBRA_LLT_AX25, "AX.25 Level 2");
+ llts(ZEBRA_LLT_PRONET, "PROnet token ring");
+ llts(ZEBRA_LLT_IEEE802, "IEEE 802.2 Ethernet/TR/TB");
+ llts(ZEBRA_LLT_ARCNET, "ARCnet");
+ llts(ZEBRA_LLT_APPLETLK, "AppleTalk");
+ llts(ZEBRA_LLT_DLCI, "Frame Relay DLCI");
+ llts(ZEBRA_LLT_ATM, "ATM");
+ llts(ZEBRA_LLT_METRICOM, "Metricom STRIP");
+ llts(ZEBRA_LLT_IEEE1394, "IEEE 1394 IPv4");
+ llts(ZEBRA_LLT_EUI64, "EUI-64");
+ llts(ZEBRA_LLT_INFINIBAND, "InfiniBand");
+ llts(ZEBRA_LLT_SLIP, "SLIP");
+ llts(ZEBRA_LLT_CSLIP, "Compressed SLIP");
+ llts(ZEBRA_LLT_SLIP6, "SLIPv6");
+ llts(ZEBRA_LLT_CSLIP6, "Compressed SLIPv6");
+ llts(ZEBRA_LLT_RSRVD, "Reserved");
+ llts(ZEBRA_LLT_ADAPT, "Adapt");
+ llts(ZEBRA_LLT_ROSE, "ROSE packet radio");
+ llts(ZEBRA_LLT_X25, "CCITT X.25");
+ llts(ZEBRA_LLT_PPP, "PPP");
+ llts(ZEBRA_LLT_CHDLC, "Cisco HDLC");
+ llts(ZEBRA_LLT_RAWHDLC, "Raw HDLC");
+ llts(ZEBRA_LLT_LAPB, "LAPB");
+ llts(ZEBRA_LLT_IPIP, "IPIP Tunnel");
+ llts(ZEBRA_LLT_IPIP6, "IPIP6 Tunnel");
+ llts(ZEBRA_LLT_FRAD, "FRAD");
+ llts(ZEBRA_LLT_SKIP, "SKIP vif");
+ llts(ZEBRA_LLT_LOOPBACK, "Loopback");
+ llts(ZEBRA_LLT_LOCALTLK, "Localtalk");
+ llts(ZEBRA_LLT_FDDI, "FDDI");
+ llts(ZEBRA_LLT_SIT, "IPv6-in-IPv4 SIT");
+ llts(ZEBRA_LLT_IPDDP, "IP-in-DDP tunnel");
+ llts(ZEBRA_LLT_IPGRE, "GRE over IP");
+ llts(ZEBRA_LLT_IP6GRE, "GRE over IPv6");
+ llts(ZEBRA_LLT_PIMREG, "PIMSM registration");
+ llts(ZEBRA_LLT_HIPPI, "HiPPI");
+ llts(ZEBRA_LLT_ECONET, "Acorn Econet");
+ llts(ZEBRA_LLT_IRDA, "IrDA");
+ llts(ZEBRA_LLT_FCPP, "Fibre-Channel PtP");
+ llts(ZEBRA_LLT_FCAL, "Fibre-Channel Arbitrated Loop");
+ llts(ZEBRA_LLT_FCPL, "Fibre-Channel Public Loop");
+ llts(ZEBRA_LLT_FCFABRIC, "Fibre-Channel Fabric");
+ llts(ZEBRA_LLT_IEEE802_TR, "IEEE 802.2 Token Ring");
+ llts(ZEBRA_LLT_IEEE80211, "IEEE 802.11");
+ llts(ZEBRA_LLT_IEEE80211_RADIOTAP, "IEEE 802.11 Radiotap");
+ llts(ZEBRA_LLT_IEEE802154, "IEEE 802.15.4");
+ llts(ZEBRA_LLT_IEEE802154_PHY, "IEEE 802.15.4 Phy");
+#undef llts
+ }
+ return NULL;
+}
+
+struct if_link_params *if_link_params_get(struct interface *ifp)
+{
+ int i;
+
+ if (ifp->link_params != NULL)
+ return ifp->link_params;
+
+ struct if_link_params *iflp =
+ XCALLOC(MTYPE_IF_LINK_PARAMS, sizeof(struct if_link_params));
+
+ /* Compute default bandwidth based on interface */
+ iflp->default_bw =
+ ((ifp->bandwidth ? ifp->bandwidth : DEFAULT_BANDWIDTH)
+ * TE_MEGA_BIT / TE_BYTE);
+
+ /* Set Max, Reservable and Unreserved Bandwidth */
+ iflp->max_bw = iflp->default_bw;
+ iflp->max_rsv_bw = iflp->default_bw;
+ for (i = 0; i < MAX_CLASS_TYPE; i++)
+ iflp->unrsv_bw[i] = iflp->default_bw;
+
+ /* Update Link parameters status */
+ iflp->lp_status = LP_MAX_BW | LP_MAX_RSV_BW | LP_UNRSV_BW;
+
+ /* Set TE metric equal to standard metric only if it is set */
+ if (ifp->metric != 0) {
+ iflp->te_metric = ifp->metric;
+ iflp->lp_status |= LP_TE_METRIC;
+ }
+
+ /* Finally attach newly created Link Parameters */
+ ifp->link_params = iflp;
+
+ return iflp;
+}
+
+void if_link_params_free(struct interface *ifp)
+{
+ XFREE(MTYPE_IF_LINK_PARAMS, ifp->link_params);
+}
+
+/* ----------- CLI commands ----------- */
+
+/* Guess the VRF of an interface. */
+static int vrfname_by_ifname(const char *ifname, const char **vrfname)
+{
+ struct vrf *vrf;
+ struct interface *ifp;
+ int count = 0;
+
+ RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) {
+ FOR_ALL_INTERFACES (vrf, ifp) {
+ if (strmatch(ifp->name, ifname)) {
+ *vrfname = vrf->name;
+ count++;
+ }
+ }
+ }
+
+ return count;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface
+ */
+DEFPY_YANG_NOSH (interface,
+ interface_cmd,
+ "interface IFNAME [vrf NAME$vrf_name]",
+ "Select an interface to configure\n"
+ "Interface's name\n"
+ VRF_CMD_HELP_STR)
+{
+ char xpath_list[XPATH_MAXLEN];
+ struct interface *ifp;
+ struct vrf *vrf;
+ int ret, count;
+
+ if (vrf_is_backend_netns()) {
+ /*
+ * For backward compatibility, if the VRF name is not specified
+ * and there is exactly one interface with this name in the
+ * system, use its VRF. Otherwise fallback to the default VRF.
+ */
+ if (!vrf_name) {
+ count = vrfname_by_ifname(ifname, &vrf_name);
+ if (count != 1)
+ vrf_name = VRF_DEFAULT_NAME;
+ }
+
+ snprintf(xpath_list, XPATH_MAXLEN,
+ "/frr-interface:lib/interface[name='%s:%s']", vrf_name,
+ ifname);
+ } else {
+ snprintf(xpath_list, XPATH_MAXLEN,
+ "/frr-interface:lib/interface[name='%s']", ifname);
+ }
+
+ nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL);
+ ret = nb_cli_apply_changes_clear_pending(vty, xpath_list);
+ if (ret == CMD_SUCCESS) {
+ VTY_PUSH_XPATH(INTERFACE_NODE, xpath_list);
+
+ /*
+ * For backward compatibility with old commands we still need
+ * to use the qobj infrastructure. This can be removed once
+ * all interface-level commands are converted to the new
+ * northbound model.
+ */
+ if (vrf_is_backend_netns()) {
+ vrf = vrf_lookup_by_name(vrf_name);
+ if (vrf)
+ ifp = if_lookup_by_name_vrf(ifname, vrf);
+ else
+ ifp = NULL;
+ } else {
+ ifp = if_lookup_by_name_all_vrf(ifname);
+ }
+ if (ifp)
+ VTY_PUSH_CONTEXT(INTERFACE_NODE, ifp);
+ }
+
+ return ret;
+}
+
+DEFPY_YANG (no_interface,
+ no_interface_cmd,
+ "no interface IFNAME [vrf NAME$vrf_name]",
+ NO_STR
+ "Delete a pseudo interface's configuration\n"
+ "Interface's name\n"
+ VRF_CMD_HELP_STR)
+{
+ char xpath_list[XPATH_MAXLEN];
+ int count;
+
+ if (vrf_is_backend_netns()) {
+ /*
+ * For backward compatibility, if the VRF name is not specified
+ * and there is exactly one interface with this name in the
+ * system, use its VRF. Otherwise fallback to the default VRF.
+ */
+ if (!vrf_name) {
+ count = vrfname_by_ifname(ifname, &vrf_name);
+ if (count != 1)
+ vrf_name = VRF_DEFAULT_NAME;
+ }
+
+ snprintf(xpath_list, XPATH_MAXLEN,
+ "/frr-interface:lib/interface[name='%s:%s']", vrf_name,
+ ifname);
+ } else {
+ snprintf(xpath_list, XPATH_MAXLEN,
+ "/frr-interface:lib/interface[name='%s']", ifname);
+ }
+
+ nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, xpath_list);
+}
+
+static void netns_ifname_split(const char *xpath, char *ifname, char *vrfname)
+{
+ char *delim;
+ int len;
+
+ assert(vrf_is_backend_netns());
+
+ delim = strchr(xpath, ':');
+ assert(delim);
+
+ len = delim - xpath;
+ memcpy(vrfname, xpath, len);
+ vrfname[len] = 0;
+
+ strlcpy(ifname, delim + 1, XPATH_MAXLEN);
+}
+
+static void cli_show_interface(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, "!\n");
+
+ if (vrf_is_backend_netns()) {
+ char ifname[XPATH_MAXLEN];
+ char vrfname[XPATH_MAXLEN];
+
+ netns_ifname_split(yang_dnode_get_string(dnode, "./name"),
+ ifname, vrfname);
+
+ vty_out(vty, "interface %s", ifname);
+ if (!strmatch(vrfname, VRF_DEFAULT_NAME))
+ vty_out(vty, " vrf %s", vrfname);
+ } else {
+ const char *ifname = yang_dnode_get_string(dnode, "./name");
+
+ vty_out(vty, "interface %s", ifname);
+ }
+
+ vty_out(vty, "\n");
+}
+
+static void cli_show_interface_end(struct vty *vty,
+ const struct lyd_node *dnode)
+{
+ vty_out(vty, "exit\n");
+}
+
+void if_vty_config_start(struct vty *vty, struct interface *ifp)
+{
+ vty_frame(vty, "!\n");
+ vty_frame(vty, "interface %s", ifp->name);
+
+ if (vrf_is_backend_netns() && strcmp(ifp->vrf->name, VRF_DEFAULT_NAME))
+ vty_frame(vty, " vrf %s", ifp->vrf->name);
+
+ vty_frame(vty, "\n");
+}
+
+void if_vty_config_end(struct vty *vty)
+{
+ vty_endframe(vty, "exit\n!\n");
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/description
+ */
+DEFPY_YANG (interface_desc,
+ interface_desc_cmd,
+ "description LINE...",
+ "Interface specific description\n"
+ "Characters describing this interface\n")
+{
+ char *desc;
+ int ret;
+
+ desc = argv_concat(argv, argc, 1);
+ nb_cli_enqueue_change(vty, "./description", NB_OP_MODIFY, desc);
+ ret = nb_cli_apply_changes(vty, NULL);
+ XFREE(MTYPE_TMP, desc);
+
+ return ret;
+}
+
+DEFPY_YANG (no_interface_desc,
+ no_interface_desc_cmd,
+ "no description",
+ NO_STR
+ "Interface specific description\n")
+{
+ nb_cli_enqueue_change(vty, "./description", NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+static void cli_show_interface_desc(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " description %s\n", yang_dnode_get_string(dnode, NULL));
+}
+
+/* Interface autocomplete. */
+static void if_autocomplete(vector comps, struct cmd_token *token)
+{
+ struct interface *ifp;
+ struct vrf *vrf;
+
+ RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) {
+ FOR_ALL_INTERFACES (vrf, ifp) {
+ vector_set(comps, XSTRDUP(MTYPE_COMPLETION, ifp->name));
+ }
+ }
+}
+
+static const struct cmd_variable_handler if_var_handlers[] = {
+ {/* "interface NAME" */
+ .varname = "interface",
+ .completions = if_autocomplete},
+ {.tokenname = "IFNAME", .completions = if_autocomplete},
+ {.tokenname = "INTERFACE", .completions = if_autocomplete},
+ {.completions = NULL}};
+
+static struct cmd_node interface_node = {
+ .name = "interface",
+ .node = INTERFACE_NODE,
+ .parent_node = CONFIG_NODE,
+ .prompt = "%s(config-if)# ",
+};
+
+static int if_config_write_single(const struct lyd_node *dnode, void *arg)
+{
+ nb_cli_show_dnode_cmds(arg, dnode, false);
+
+ return YANG_ITER_CONTINUE;
+}
+
+static int if_nb_config_write(struct vty *vty)
+{
+ yang_dnode_iterate(if_config_write_single, vty, running_config->dnode,
+ "/frr-interface:lib/interface");
+ return 1;
+}
+
+void if_cmd_init(int (*config_write)(struct vty *))
+{
+ cmd_variable_handler_register(if_var_handlers);
+
+ interface_node.config_write = config_write;
+ install_node(&interface_node);
+
+ install_element(CONFIG_NODE, &interface_cmd);
+ install_element(CONFIG_NODE, &no_interface_cmd);
+
+ install_default(INTERFACE_NODE);
+ install_element(INTERFACE_NODE, &interface_desc_cmd);
+ install_element(INTERFACE_NODE, &no_interface_desc_cmd);
+}
+
+void if_cmd_init_default(void)
+{
+ if_cmd_init(if_nb_config_write);
+}
+
+void if_zapi_callbacks(int (*create)(struct interface *ifp),
+ int (*up)(struct interface *ifp),
+ int (*down)(struct interface *ifp),
+ int (*destroy)(struct interface *ifp))
+{
+ ifp_master.create_hook = create;
+ ifp_master.up_hook = up;
+ ifp_master.down_hook = down;
+ ifp_master.destroy_hook = destroy;
+}
+
+/* ------- Northbound callbacks ------- */
+
+/*
+ * XPath: /frr-interface:lib/interface
+ */
+static int lib_interface_create(struct nb_cb_create_args *args)
+{
+ const char *ifname;
+ struct interface *ifp;
+
+ ifname = yang_dnode_get_string(args->dnode, "./name");
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ if (vrf_is_backend_netns()) {
+ char ifname_ns[XPATH_MAXLEN];
+ char vrfname_ns[XPATH_MAXLEN];
+
+ netns_ifname_split(ifname, ifname_ns, vrfname_ns);
+
+ if (strlen(ifname_ns) > 16) {
+ snprintf(
+ args->errmsg, args->errmsg_len,
+ "Maximum interface name length is 16 characters");
+ return NB_ERR_VALIDATION;
+ }
+ if (strlen(vrfname_ns) > 36) {
+ snprintf(
+ args->errmsg, args->errmsg_len,
+ "Maximum VRF name length is 36 characters");
+ return NB_ERR_VALIDATION;
+ }
+ } else {
+ if (strlen(ifname) > 16) {
+ snprintf(
+ args->errmsg, args->errmsg_len,
+ "Maximum interface name length is 16 characters");
+ return NB_ERR_VALIDATION;
+ }
+ }
+ break;
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ break;
+ case NB_EV_APPLY:
+ if (vrf_is_backend_netns()) {
+ char ifname_ns[XPATH_MAXLEN];
+ char vrfname_ns[XPATH_MAXLEN];
+
+ netns_ifname_split(ifname, ifname_ns, vrfname_ns);
+
+ ifp = if_get_by_name(ifname_ns, VRF_UNKNOWN,
+ vrfname_ns);
+ } else {
+ ifp = if_get_by_name(ifname, VRF_UNKNOWN,
+ VRF_DEFAULT_NAME);
+ }
+
+ ifp->configured = true;
+ nb_running_set_entry(args->dnode, ifp);
+ break;
+ }
+
+ return NB_OK;
+}
+
+static int lib_interface_destroy(struct nb_cb_destroy_args *args)
+{
+ struct interface *ifp;
+ struct vrf *vrf;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ ifp = nb_running_get_entry(args->dnode, NULL, true);
+ if (CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_ACTIVE)) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "only inactive interfaces can be deleted");
+ return NB_ERR_VALIDATION;
+ }
+ break;
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ break;
+ case NB_EV_APPLY:
+ ifp = nb_running_unset_entry(args->dnode);
+ vrf = ifp->vrf;
+
+ ifp->configured = false;
+ if_delete(&ifp);
+
+ if (!vrf_is_enabled(vrf))
+ vrf_delete(vrf);
+ break;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface
+ */
+static const void *lib_interface_get_next(struct nb_cb_get_next_args *args)
+{
+ struct vrf *vrf;
+ struct interface *pif = (struct interface *)args->list_entry;
+
+ if (args->list_entry == NULL) {
+ vrf = RB_MIN(vrf_name_head, &vrfs_by_name);
+ assert(vrf);
+ pif = RB_MIN(if_name_head, &vrf->ifaces_by_name);
+ } else {
+ vrf = pif->vrf;
+ pif = RB_NEXT(if_name_head, pif);
+ /* if no more interfaces, switch to next vrf */
+ while (pif == NULL) {
+ vrf = RB_NEXT(vrf_name_head, vrf);
+ if (!vrf)
+ return NULL;
+ pif = RB_MIN(if_name_head, &vrf->ifaces_by_name);
+ }
+ }
+
+ return pif;
+}
+
+static int lib_interface_get_keys(struct nb_cb_get_keys_args *args)
+{
+ const struct interface *ifp = args->list_entry;
+
+ args->keys->num = 1;
+
+ if (vrf_is_backend_netns())
+ snprintf(args->keys->key[0], sizeof(args->keys->key[0]),
+ "%s:%s", ifp->vrf->name, ifp->name);
+ else
+ snprintf(args->keys->key[0], sizeof(args->keys->key[0]), "%s",
+ ifp->name);
+
+ return NB_OK;
+}
+
+static const void *
+lib_interface_lookup_entry(struct nb_cb_lookup_entry_args *args)
+{
+ if (vrf_is_backend_netns()) {
+ char ifname[XPATH_MAXLEN];
+ char vrfname[XPATH_MAXLEN];
+ struct vrf *vrf;
+
+ netns_ifname_split(args->keys->key[0], ifname, vrfname);
+
+ vrf = vrf_lookup_by_name(vrfname);
+
+ return vrf ? if_lookup_by_name(ifname, vrf->vrf_id) : NULL;
+ } else {
+ return if_lookup_by_name_all_vrf(args->keys->key[0]);
+ }
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/description
+ */
+static int lib_interface_description_modify(struct nb_cb_modify_args *args)
+{
+ struct interface *ifp;
+ const char *description;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ ifp = nb_running_get_entry(args->dnode, NULL, true);
+ XFREE(MTYPE_TMP, ifp->desc);
+ description = yang_dnode_get_string(args->dnode, NULL);
+ ifp->desc = XSTRDUP(MTYPE_TMP, description);
+
+ return NB_OK;
+}
+
+static int lib_interface_description_destroy(struct nb_cb_destroy_args *args)
+{
+ struct interface *ifp;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ ifp = nb_running_get_entry(args->dnode, NULL, true);
+ XFREE(MTYPE_TMP, ifp->desc);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/vrf
+ */
+static struct yang_data *
+lib_interface_vrf_get_elem(struct nb_cb_get_elem_args *args)
+{
+ const struct interface *ifp = args->list_entry;
+
+ return yang_data_new_string(args->xpath, ifp->vrf->name);
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/state/if-index
+ */
+static struct yang_data *
+lib_interface_state_if_index_get_elem(struct nb_cb_get_elem_args *args)
+{
+ const struct interface *ifp = args->list_entry;
+
+ return yang_data_new_int32(args->xpath, ifp->ifindex);
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/state/mtu
+ */
+static struct yang_data *
+lib_interface_state_mtu_get_elem(struct nb_cb_get_elem_args *args)
+{
+ const struct interface *ifp = args->list_entry;
+
+ return yang_data_new_uint16(args->xpath, ifp->mtu);
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/state/mtu6
+ */
+static struct yang_data *
+lib_interface_state_mtu6_get_elem(struct nb_cb_get_elem_args *args)
+{
+ const struct interface *ifp = args->list_entry;
+
+ return yang_data_new_uint32(args->xpath, ifp->mtu6);
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/state/speed
+ */
+static struct yang_data *
+lib_interface_state_speed_get_elem(struct nb_cb_get_elem_args *args)
+{
+ const struct interface *ifp = args->list_entry;
+
+ return yang_data_new_uint32(args->xpath, ifp->speed);
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/state/metric
+ */
+static struct yang_data *
+lib_interface_state_metric_get_elem(struct nb_cb_get_elem_args *args)
+{
+ const struct interface *ifp = args->list_entry;
+
+ return yang_data_new_uint32(args->xpath, ifp->metric);
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/state/flags
+ */
+static struct yang_data *
+lib_interface_state_flags_get_elem(struct nb_cb_get_elem_args *args)
+{
+ /* TODO: implement me. */
+ return NULL;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/state/type
+ */
+static struct yang_data *
+lib_interface_state_type_get_elem(struct nb_cb_get_elem_args *args)
+{
+ /* TODO: implement me. */
+ return NULL;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/state/phy-address
+ */
+static struct yang_data *
+lib_interface_state_phy_address_get_elem(struct nb_cb_get_elem_args *args)
+{
+ const struct interface *ifp = args->list_entry;
+ struct ethaddr macaddr;
+
+ memcpy(&macaddr.octet, ifp->hw_addr, ETH_ALEN);
+
+ return yang_data_new_mac(args->xpath, &macaddr);
+}
+
+/* clang-format off */
+const struct frr_yang_module_info frr_interface_info = {
+ .name = "frr-interface",
+ .nodes = {
+ {
+ .xpath = "/frr-interface:lib/interface",
+ .cbs = {
+ .create = lib_interface_create,
+ .destroy = lib_interface_destroy,
+ .cli_show = cli_show_interface,
+ .cli_show_end = cli_show_interface_end,
+ .get_next = lib_interface_get_next,
+ .get_keys = lib_interface_get_keys,
+ .lookup_entry = lib_interface_lookup_entry,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/description",
+ .cbs = {
+ .modify = lib_interface_description_modify,
+ .destroy = lib_interface_description_destroy,
+ .cli_show = cli_show_interface_desc,
+ },
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/vrf",
+ .cbs = {
+ .get_elem = lib_interface_vrf_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/if-index",
+ .cbs = {
+ .get_elem = lib_interface_state_if_index_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/mtu",
+ .cbs = {
+ .get_elem = lib_interface_state_mtu_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/mtu6",
+ .cbs = {
+ .get_elem = lib_interface_state_mtu6_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/speed",
+ .cbs = {
+ .get_elem = lib_interface_state_speed_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/metric",
+ .cbs = {
+ .get_elem = lib_interface_state_metric_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/flags",
+ .cbs = {
+ .get_elem = lib_interface_state_flags_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/type",
+ .cbs = {
+ .get_elem = lib_interface_state_type_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-interface:lib/interface/state/phy-address",
+ .cbs = {
+ .get_elem = lib_interface_state_phy_address_get_elem,
+ }
+ },
+ {
+ .xpath = NULL,
+ },
+ }
+};
diff --git a/lib/if.h b/lib/if.h
new file mode 100644
index 0000000..1c948b8
--- /dev/null
+++ b/lib/if.h
@@ -0,0 +1,615 @@
+/* Interface related header.
+ * Copyright (C) 1997, 98, 99 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra 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 _ZEBRA_IF_H
+#define _ZEBRA_IF_H
+
+#include "zebra.h"
+#include "linklist.h"
+#include "memory.h"
+#include "qobj.h"
+#include "hook.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+DECLARE_MTYPE(CONNECTED_LABEL);
+
+/* Interface link-layer type, if known. Derived from:
+ *
+ * net/if_arp.h on various platforms - Linux especially.
+ * http://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml
+ *
+ * Some of the more obviously defunct technologies left out.
+ */
+enum zebra_link_type {
+ ZEBRA_LLT_UNKNOWN = 0,
+ ZEBRA_LLT_ETHER,
+ ZEBRA_LLT_EETHER,
+ ZEBRA_LLT_AX25,
+ ZEBRA_LLT_PRONET,
+ ZEBRA_LLT_IEEE802,
+ ZEBRA_LLT_ARCNET,
+ ZEBRA_LLT_APPLETLK,
+ ZEBRA_LLT_DLCI,
+ ZEBRA_LLT_ATM,
+ ZEBRA_LLT_METRICOM,
+ ZEBRA_LLT_IEEE1394,
+ ZEBRA_LLT_EUI64,
+ ZEBRA_LLT_INFINIBAND,
+ ZEBRA_LLT_SLIP,
+ ZEBRA_LLT_CSLIP,
+ ZEBRA_LLT_SLIP6,
+ ZEBRA_LLT_CSLIP6,
+ ZEBRA_LLT_RSRVD,
+ ZEBRA_LLT_ADAPT,
+ ZEBRA_LLT_ROSE,
+ ZEBRA_LLT_X25,
+ ZEBRA_LLT_PPP,
+ ZEBRA_LLT_CHDLC,
+ ZEBRA_LLT_LAPB,
+ ZEBRA_LLT_RAWHDLC,
+ ZEBRA_LLT_IPIP,
+ ZEBRA_LLT_IPIP6,
+ ZEBRA_LLT_FRAD,
+ ZEBRA_LLT_SKIP,
+ ZEBRA_LLT_LOOPBACK,
+ ZEBRA_LLT_LOCALTLK,
+ ZEBRA_LLT_FDDI,
+ ZEBRA_LLT_SIT,
+ ZEBRA_LLT_IPDDP,
+ ZEBRA_LLT_IPGRE,
+ ZEBRA_LLT_IP6GRE,
+ ZEBRA_LLT_PIMREG,
+ ZEBRA_LLT_HIPPI,
+ ZEBRA_LLT_ECONET,
+ ZEBRA_LLT_IRDA,
+ ZEBRA_LLT_FCPP,
+ ZEBRA_LLT_FCAL,
+ ZEBRA_LLT_FCPL,
+ ZEBRA_LLT_FCFABRIC,
+ ZEBRA_LLT_IEEE802_TR,
+ ZEBRA_LLT_IEEE80211,
+ ZEBRA_LLT_IEEE80211_RADIOTAP,
+ ZEBRA_LLT_IEEE802154,
+ ZEBRA_LLT_IEEE802154_PHY,
+};
+
+/*
+ Interface name length.
+
+ Linux define value in /usr/include/linux/if.h.
+ #define IFNAMSIZ 16
+
+ FreeBSD define value in /usr/include/net/if.h.
+ #define IFNAMSIZ 16
+*/
+
+#define INTERFACE_NAMSIZ IFNAMSIZ
+#define INTERFACE_HWADDR_MAX 20
+
+typedef signed int ifindex_t;
+
+#ifdef HAVE_PROC_NET_DEV
+struct if_stats {
+ unsigned long rx_packets; /* total packets received */
+ unsigned long tx_packets; /* total packets transmitted */
+ unsigned long rx_bytes; /* total bytes received */
+ unsigned long tx_bytes; /* total bytes transmitted */
+ unsigned long rx_errors; /* bad packets received */
+ unsigned long tx_errors; /* packet transmit problems */
+ unsigned long rx_dropped; /* no space in linux buffers */
+ unsigned long tx_dropped; /* no space available in linux */
+ unsigned long rx_multicast; /* multicast packets received */
+ unsigned long rx_compressed;
+ unsigned long tx_compressed;
+ unsigned long collisions;
+
+ /* detailed rx_errors: */
+ unsigned long rx_length_errors;
+ unsigned long rx_over_errors; /* receiver ring buff overflow */
+ unsigned long rx_crc_errors; /* recved pkt with crc error */
+ unsigned long rx_frame_errors; /* recv'd frame alignment error */
+ unsigned long rx_fifo_errors; /* recv'r fifo overrun */
+ unsigned long rx_missed_errors; /* receiver missed packet */
+ /* detailed tx_errors */
+ unsigned long tx_aborted_errors;
+ unsigned long tx_carrier_errors;
+ unsigned long tx_fifo_errors;
+ unsigned long tx_heartbeat_errors;
+ unsigned long tx_window_errors;
+};
+#endif /* HAVE_PROC_NET_DEV */
+
+/* Here are "non-official" architectural constants. */
+#define TE_EXT_MASK 0x0FFFFFFF
+#define TE_EXT_ANORMAL 0x80000000
+#define LOSS_PRECISION 0.000003
+#define TE_MEGA_BIT 1000000
+#define TE_BYTE 8
+#define DEFAULT_BANDWIDTH 10000
+#define MAX_CLASS_TYPE 8
+#define MAX_PKT_LOSS 50.331642
+
+/*
+ * Link Parameters Status:
+ * equal to 0: unset
+ * different from 0: set
+ */
+#define LP_UNSET 0x0000
+#define LP_TE_METRIC 0x0001
+#define LP_MAX_BW 0x0002
+#define LP_MAX_RSV_BW 0x0004
+#define LP_UNRSV_BW 0x0008
+#define LP_ADM_GRP 0x0010
+#define LP_RMT_AS 0x0020
+#define LP_DELAY 0x0040
+#define LP_MM_DELAY 0x0080
+#define LP_DELAY_VAR 0x0100
+#define LP_PKT_LOSS 0x0200
+#define LP_RES_BW 0x0400
+#define LP_AVA_BW 0x0800
+#define LP_USE_BW 0x1000
+
+#define IS_PARAM_UNSET(lp, st) !(lp->lp_status & st)
+#define IS_PARAM_SET(lp, st) (lp->lp_status & st)
+#define IS_LINK_PARAMS_SET(lp) (lp->lp_status != LP_UNSET)
+
+#define SET_PARAM(lp, st) (lp->lp_status) |= (st)
+#define UNSET_PARAM(lp, st) (lp->lp_status) &= ~(st)
+#define RESET_LINK_PARAM(lp) (lp->lp_status = LP_UNSET)
+
+/* Link Parameters for Traffic Engineering */
+struct if_link_params {
+ uint32_t lp_status; /* Status of Link Parameters: */
+ uint32_t te_metric; /* Traffic Engineering metric */
+ float default_bw;
+ float max_bw; /* Maximum Bandwidth */
+ float max_rsv_bw; /* Maximum Reservable Bandwidth */
+ float unrsv_bw[MAX_CLASS_TYPE]; /* Unreserved Bandwidth per Class Type
+ (8) */
+ uint32_t admin_grp; /* Administrative group */
+ uint32_t rmt_as; /* Remote AS number */
+ struct in_addr rmt_ip; /* Remote IP address */
+ uint32_t av_delay; /* Link Average Delay */
+ uint32_t min_delay; /* Link Min Delay */
+ uint32_t max_delay; /* Link Max Delay */
+ uint32_t delay_var; /* Link Delay Variation */
+ float pkt_loss; /* Link Packet Loss */
+ float res_bw; /* Residual Bandwidth */
+ float ava_bw; /* Available Bandwidth */
+ float use_bw; /* Utilized Bandwidth */
+};
+
+#define INTERFACE_LINK_PARAMS_SIZE sizeof(struct if_link_params)
+#define HAS_LINK_PARAMS(ifp) ((ifp)->link_params != NULL)
+
+/* Interface structure */
+struct interface {
+ RB_ENTRY(interface) name_entry, index_entry;
+
+ /* Interface name. This should probably never be changed after the
+ interface is created, because the configuration info for this
+ interface
+ is associated with this structure. For that reason, the interface
+ should also never be deleted (to avoid losing configuration info).
+ To delete, just set ifindex to IFINDEX_INTERNAL to indicate that the
+ interface does not exist in the kernel.
+ */
+ char name[INTERFACE_NAMSIZ];
+
+ /* Interface index (should be IFINDEX_INTERNAL for non-kernel or
+ deleted interfaces).
+ WARNING: the ifindex needs to be changed using the if_set_index()
+ function. Failure to respect this will cause corruption in the data
+ structure used to store the interfaces and if_lookup_by_index() will
+ not work as expected.
+ */
+ ifindex_t ifindex;
+ ifindex_t oldifindex;
+
+ /*
+ * ifindex of parent interface, if any
+ */
+ ifindex_t link_ifindex;
+#define IFINDEX_INTERNAL 0
+
+ /* Zebra internal interface status */
+ uint8_t status;
+#define ZEBRA_INTERFACE_ACTIVE (1 << 0)
+#define ZEBRA_INTERFACE_SUB (1 << 1)
+#define ZEBRA_INTERFACE_LINKDETECTION (1 << 2)
+#define ZEBRA_INTERFACE_VRF_LOOPBACK (1 << 3)
+
+ /* Interface flags. */
+ uint64_t flags;
+
+ /* Interface metric */
+ uint32_t metric;
+
+ /* Interface Speed in Mb/s */
+ uint32_t speed;
+
+ /* Interface MTU. */
+ unsigned int mtu; /* IPv4 MTU */
+ unsigned int
+ mtu6; /* IPv6 MTU - probably, but not necessarily same as mtu
+ */
+
+ /* Link-layer information and hardware address */
+ enum zebra_link_type ll_type;
+ uint8_t hw_addr[INTERFACE_HWADDR_MAX];
+ int hw_addr_len;
+
+ /* interface bandwidth, kbits */
+ unsigned int bandwidth;
+
+ /* Link parameters for Traffic Engineering */
+ struct if_link_params *link_params;
+
+ /* description of the interface. */
+ char *desc;
+
+ /* Distribute list. */
+ void *distribute_in;
+ void *distribute_out;
+
+ /* Connected address list. */
+ struct list *connected;
+
+ /* Neighbor connected address list. */
+ struct list *nbr_connected;
+
+ /* Daemon specific interface data pointer. */
+ void *info;
+
+ char ptm_enable; /* Should we look at ptm_status ? */
+ char ptm_status;
+
+/* Statistics fileds. */
+#ifdef HAVE_PROC_NET_DEV
+ struct if_stats stats;
+#endif /* HAVE_PROC_NET_DEV */
+#ifdef HAVE_NET_RT_IFLIST
+ struct if_data stats;
+#endif /* HAVE_NET_RT_IFLIST */
+
+ struct route_node *node;
+
+ struct vrf *vrf;
+
+ /*
+ * Has the end users entered `interface XXXX` from the cli in some
+ * fashion?
+ */
+ bool configured;
+
+ QOBJ_FIELDS;
+};
+
+RB_HEAD(if_name_head, interface);
+RB_PROTOTYPE(if_name_head, interface, name_entry, if_cmp_func)
+RB_HEAD(if_index_head, interface);
+RB_PROTOTYPE(if_index_head, interface, index_entry, if_cmp_index_func)
+DECLARE_QOBJ_TYPE(interface);
+
+#define IFNAME_RB_INSERT(v, ifp) \
+ ({ \
+ struct interface *_iz = \
+ RB_INSERT(if_name_head, &v->ifaces_by_name, (ifp)); \
+ if (_iz) \
+ flog_err( \
+ EC_LIB_INTERFACE, \
+ "%s(%s): corruption detected -- interface with this " \
+ "name exists already in VRF %s!", \
+ __func__, (ifp)->name, (ifp)->vrf->name); \
+ _iz; \
+ })
+
+#define IFNAME_RB_REMOVE(v, ifp) \
+ ({ \
+ struct interface *_iz = \
+ RB_REMOVE(if_name_head, &v->ifaces_by_name, (ifp)); \
+ if (_iz == NULL) \
+ flog_err( \
+ EC_LIB_INTERFACE, \
+ "%s(%s): corruption detected -- interface with this " \
+ "name doesn't exist in VRF %s!", \
+ __func__, (ifp)->name, (ifp)->vrf->name); \
+ _iz; \
+ })
+
+
+#define IFINDEX_RB_INSERT(v, ifp) \
+ ({ \
+ struct interface *_iz = \
+ RB_INSERT(if_index_head, &v->ifaces_by_index, (ifp)); \
+ if (_iz) \
+ flog_err( \
+ EC_LIB_INTERFACE, \
+ "%s(%u): corruption detected -- interface with this " \
+ "ifindex exists already in VRF %s!", \
+ __func__, (ifp)->ifindex, (ifp)->vrf->name); \
+ _iz; \
+ })
+
+#define IFINDEX_RB_REMOVE(v, ifp) \
+ ({ \
+ struct interface *_iz = \
+ RB_REMOVE(if_index_head, &v->ifaces_by_index, (ifp)); \
+ if (_iz == NULL) \
+ flog_err( \
+ EC_LIB_INTERFACE, \
+ "%s(%u): corruption detected -- interface with this " \
+ "ifindex doesn't exist in VRF %s!", \
+ __func__, (ifp)->ifindex, (ifp)->vrf->name); \
+ _iz; \
+ })
+
+#define FOR_ALL_INTERFACES(vrf, ifp) \
+ if (vrf) \
+ RB_FOREACH (ifp, if_name_head, &vrf->ifaces_by_name)
+
+#define FOR_ALL_INTERFACES_ADDRESSES(ifp, connected, node) \
+ for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, connected))
+
+/* called from the library code whenever interfaces are created/deleted
+ * note: interfaces may not be fully realized at that point; also they
+ * may not exist in the system (ifindex = IFINDEX_INTERNAL)
+ *
+ * priority values are important here, daemons should be at 0 while modules
+ * can use 1000+ so they run after the daemon has initialised daemon-specific
+ * interface data
+ */
+DECLARE_HOOK(if_add, (struct interface * ifp), (ifp));
+DECLARE_KOOH(if_del, (struct interface * ifp), (ifp));
+
+#define METRIC_MAX (~0)
+
+/* Connected address structure. */
+struct connected {
+ /* Attached interface. */
+ struct interface *ifp;
+
+ /* Flags for configuration. */
+ uint8_t conf;
+#define ZEBRA_IFC_REAL (1 << 0)
+#define ZEBRA_IFC_CONFIGURED (1 << 1)
+#define ZEBRA_IFC_QUEUED (1 << 2)
+#define ZEBRA_IFC_DOWN (1 << 3)
+ /*
+ The ZEBRA_IFC_REAL flag should be set if and only if this address
+ exists in the kernel and is actually usable. (A case where it exists
+ but is not yet usable would be IPv6 with DAD)
+ The ZEBRA_IFC_CONFIGURED flag should be set if and only if this
+ address was configured by the user from inside frr.
+ The ZEBRA_IFC_QUEUED flag should be set if and only if the address
+ exists in the kernel. It may and should be set although the
+ address might not be usable yet. (compare with ZEBRA_IFC_REAL)
+ The ZEBRA_IFC_DOWN flag is used to record that an address is
+ present, but down/unavailable.
+ */
+
+ /* Flags for connected address. */
+ uint8_t flags;
+#define ZEBRA_IFA_SECONDARY (1 << 0)
+#define ZEBRA_IFA_PEER (1 << 1)
+#define ZEBRA_IFA_UNNUMBERED (1 << 2)
+ /* N.B. the ZEBRA_IFA_PEER flag should be set if and only if
+ a peer address has been configured. If this flag is set,
+ the destination field must contain the peer address.
+ */
+
+ /* Address of connected network. */
+ struct prefix *address;
+
+ /* Peer address, if ZEBRA_IFA_PEER is set, otherwise NULL */
+ struct prefix *destination;
+
+ /* Label for Linux 2.2.X and upper. */
+ char *label;
+
+ /*
+ * Used for setting the connected route's cost. If the metric
+ * here is set to METRIC_MAX the connected route falls back to
+ * "struct interface"
+ */
+ uint32_t metric;
+};
+
+/* Nbr Connected address structure. */
+struct nbr_connected {
+ /* Attached interface. */
+ struct interface *ifp;
+
+ /* Address of connected network. */
+ struct prefix *address;
+};
+
+/* Does the destination field contain a peer address? */
+#define CONNECTED_PEER(C) CHECK_FLAG((C)->flags, ZEBRA_IFA_PEER)
+
+/* Prefix to insert into the RIB */
+#define CONNECTED_PREFIX(C) \
+ (CONNECTED_PEER(C) ? (C)->destination : (C)->address)
+
+/* Identifying address. We guess that if there's a peer address, but the
+ local address is in the same prefix, then the local address may be unique. */
+#define CONNECTED_ID(C) \
+ ((CONNECTED_PEER(C) && !prefix_match((C)->destination, (C)->address)) \
+ ? (C)->destination \
+ : (C)->address)
+
+/* There are some interface flags which are only supported by some
+ operating system. */
+
+#ifndef IFF_NOTRAILERS
+#define IFF_NOTRAILERS 0x0
+#endif /* IFF_NOTRAILERS */
+#ifndef IFF_OACTIVE
+#define IFF_OACTIVE 0x0
+#endif /* IFF_OACTIVE */
+#ifndef IFF_SIMPLEX
+#define IFF_SIMPLEX 0x0
+#endif /* IFF_SIMPLEX */
+#ifndef IFF_LINK0
+#define IFF_LINK0 0x0
+#endif /* IFF_LINK0 */
+#ifndef IFF_LINK1
+#define IFF_LINK1 0x0
+#endif /* IFF_LINK1 */
+#ifndef IFF_LINK2
+#define IFF_LINK2 0x0
+#endif /* IFF_LINK2 */
+#ifndef IFF_NOXMIT
+#define IFF_NOXMIT 0x0
+#endif /* IFF_NOXMIT */
+#ifndef IFF_NORTEXCH
+#define IFF_NORTEXCH 0x0
+#endif /* IFF_NORTEXCH */
+#ifndef IFF_IPV4
+#define IFF_IPV4 0x0
+#endif /* IFF_IPV4 */
+#ifndef IFF_IPV6
+#define IFF_IPV6 0x0
+#endif /* IFF_IPV6 */
+#ifndef IFF_VIRTUAL
+#define IFF_VIRTUAL 0x0
+#endif /* IFF_VIRTUAL */
+
+/* Prototypes. */
+extern int if_cmp_name_func(const char *p1, const char *p2);
+
+/*
+ * Passing in VRF_UNKNOWN is a valid thing to do, unless we
+ * are creating a new interface.
+ *
+ * This is useful for vrf route-leaking. So more than anything
+ * else think before you use VRF_UNKNOWN
+ */
+extern void if_update_to_new_vrf(struct interface *, vrf_id_t vrf_id);
+
+extern struct interface *if_lookup_by_index(ifindex_t, vrf_id_t vrf_id);
+extern struct interface *if_vrf_lookup_by_index_next(ifindex_t ifindex,
+ vrf_id_t vrf_id);
+extern struct interface *if_lookup_address_local(const void *matchaddr,
+ int family, vrf_id_t vrf_id);
+extern struct connected *if_lookup_address(const void *matchaddr, int family,
+ vrf_id_t vrf_id);
+extern struct interface *if_lookup_prefix(const struct prefix *prefix,
+ vrf_id_t vrf_id);
+size_t if_lookup_by_hwaddr(const uint8_t *hw_addr, size_t addrsz,
+ struct interface ***result, vrf_id_t vrf_id);
+
+static inline bool if_address_is_local(const void *matchaddr, int family,
+ vrf_id_t vrf_id)
+{
+ return if_lookup_address_local(matchaddr, family, vrf_id) != NULL;
+}
+
+struct vrf;
+extern struct interface *if_lookup_by_name_vrf(const char *name, struct vrf *vrf);
+extern struct interface *if_lookup_by_name(const char *ifname, vrf_id_t vrf_id);
+extern struct interface *if_get_by_name(const char *ifname, vrf_id_t vrf_id,
+ const char *vrf_name);
+
+/* Sets the index and adds to index list */
+extern int if_set_index(struct interface *ifp, ifindex_t ifindex);
+
+/* Delete the interface, but do not free the structure, and leave it in the
+ interface list. It is often advisable to leave the pseudo interface
+ structure because there may be configuration information attached. */
+extern void if_delete_retain(struct interface *);
+
+/* Delete and free the interface structure: calls if_delete_retain and then
+ deletes it from the interface list and frees the structure. */
+extern void if_delete(struct interface **ifp);
+
+extern int if_is_up(const struct interface *ifp);
+extern int if_is_running(const struct interface *ifp);
+extern int if_is_operative(const struct interface *ifp);
+extern int if_is_no_ptm_operative(const struct interface *ifp);
+extern int if_is_loopback_exact(const struct interface *ifp);
+extern int if_is_vrf(const struct interface *ifp);
+extern bool if_is_loopback(const struct interface *ifp);
+extern int if_is_broadcast(const struct interface *ifp);
+extern int if_is_pointopoint(const struct interface *ifp);
+extern int if_is_multicast(const struct interface *ifp);
+extern void if_terminate(struct vrf *vrf);
+extern void if_dump_all(void);
+extern const char *if_flag_dump(unsigned long);
+extern const char *if_link_type_str(enum zebra_link_type);
+
+/* Please use ifindex2ifname instead of if_indextoname where possible;
+ ifindex2ifname uses internal interface info, whereas if_indextoname must
+ make a system call. */
+extern const char *ifindex2ifname(ifindex_t, vrf_id_t vrf_id);
+
+/* Please use ifname2ifindex instead of if_nametoindex where possible;
+ ifname2ifindex uses internal interface info, whereas if_nametoindex must
+ make a system call. */
+extern ifindex_t ifname2ifindex(const char *ifname, vrf_id_t vrf_id);
+
+/* Connected address functions. */
+extern struct connected *connected_new(void);
+extern void connected_free(struct connected **connected);
+extern void connected_add(struct interface *, struct connected *);
+extern struct connected *
+connected_add_by_prefix(struct interface *, struct prefix *, struct prefix *);
+extern struct connected *connected_delete_by_prefix(struct interface *,
+ struct prefix *);
+extern struct connected *connected_lookup_prefix(struct interface *,
+ const struct prefix *);
+extern struct connected *connected_lookup_prefix_exact(struct interface *,
+ const struct prefix *);
+extern unsigned int connected_count_by_family(struct interface *, int family);
+extern struct nbr_connected *nbr_connected_new(void);
+extern void nbr_connected_free(struct nbr_connected *);
+struct nbr_connected *nbr_connected_check(struct interface *, struct prefix *);
+struct connected *connected_get_linklocal(struct interface *ifp);
+
+/* link parameters */
+struct if_link_params *if_link_params_get(struct interface *);
+void if_link_params_free(struct interface *);
+
+/* Northbound. */
+struct vty;
+extern void if_vty_config_start(struct vty *vty, struct interface *ifp);
+extern void if_vty_config_end(struct vty *vty);
+extern void if_cmd_init(int (*config_write)(struct vty *));
+extern void if_cmd_init_default(void);
+extern void if_zapi_callbacks(int (*create)(struct interface *ifp),
+ int (*up)(struct interface *ifp),
+ int (*down)(struct interface *ifp),
+ int (*destroy)(struct interface *ifp));
+
+extern void if_new_via_zapi(struct interface *ifp);
+extern void if_up_via_zapi(struct interface *ifp);
+extern void if_down_via_zapi(struct interface *ifp);
+extern void if_destroy_via_zapi(struct interface *ifp);
+
+extern const struct frr_yang_module_info frr_interface_info;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ZEBRA_IF_H */
diff --git a/lib/if_rmap.c b/lib/if_rmap.c
new file mode 100644
index 0000000..bd66ffa
--- /dev/null
+++ b/lib/if_rmap.c
@@ -0,0 +1,332 @@
+/* route-map for interface.
+ * Copyright (C) 1999 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 "hash.h"
+#include "command.h"
+#include "memory.h"
+#include "if.h"
+#include "if_rmap.h"
+
+DEFINE_MTYPE_STATIC(LIB, IF_RMAP_CTX, "Interface route map container");
+DEFINE_MTYPE_STATIC(LIB, IF_RMAP_CTX_NAME,
+ "Interface route map container name");
+DEFINE_MTYPE_STATIC(LIB, IF_RMAP, "Interface route map");
+DEFINE_MTYPE_STATIC(LIB, IF_RMAP_NAME, "I.f. route map name");
+
+static struct list *if_rmap_ctx_list;
+
+static struct if_rmap *if_rmap_new(void)
+{
+ struct if_rmap *new;
+
+ new = XCALLOC(MTYPE_IF_RMAP, sizeof(struct if_rmap));
+
+ return new;
+}
+
+static void if_rmap_free(struct if_rmap *if_rmap)
+{
+ XFREE(MTYPE_IF_RMAP_NAME, if_rmap->ifname);
+
+ XFREE(MTYPE_IF_RMAP_NAME, if_rmap->routemap[IF_RMAP_IN]);
+ XFREE(MTYPE_IF_RMAP_NAME, if_rmap->routemap[IF_RMAP_OUT]);
+
+ XFREE(MTYPE_IF_RMAP, if_rmap);
+}
+
+struct if_rmap *if_rmap_lookup(struct if_rmap_ctx *ctx, const char *ifname)
+{
+ struct if_rmap key;
+ struct if_rmap *if_rmap;
+
+ /* temporary copy */
+ key.ifname = (ifname) ? XSTRDUP(MTYPE_IF_RMAP_NAME, ifname) : NULL;
+
+ if_rmap = hash_lookup(ctx->ifrmaphash, &key);
+
+ XFREE(MTYPE_IF_RMAP_NAME, key.ifname);
+
+ return if_rmap;
+}
+
+void if_rmap_hook_add(struct if_rmap_ctx *ctx,
+ void (*func)(struct if_rmap_ctx *ctx,
+ struct if_rmap *))
+{
+ ctx->if_rmap_add_hook = func;
+}
+
+void if_rmap_hook_delete(struct if_rmap_ctx *ctx,
+ void (*func)(struct if_rmap_ctx *ctx,
+ struct if_rmap *))
+{
+ ctx->if_rmap_delete_hook = func;
+}
+
+static void *if_rmap_hash_alloc(void *arg)
+{
+ struct if_rmap *ifarg = (struct if_rmap *)arg;
+ struct if_rmap *if_rmap;
+
+ if_rmap = if_rmap_new();
+ if_rmap->ifname = XSTRDUP(MTYPE_IF_RMAP_NAME, ifarg->ifname);
+
+ return if_rmap;
+}
+
+static struct if_rmap *if_rmap_get(struct if_rmap_ctx *ctx, const char *ifname)
+{
+ struct if_rmap key;
+ struct if_rmap *ret;
+
+ /* temporary copy */
+ key.ifname = (ifname) ? XSTRDUP(MTYPE_IF_RMAP_NAME, ifname) : NULL;
+
+ ret = hash_get(ctx->ifrmaphash, &key, if_rmap_hash_alloc);
+
+ XFREE(MTYPE_IF_RMAP_NAME, key.ifname);
+
+ return ret;
+}
+
+static unsigned int if_rmap_hash_make(const void *data)
+{
+ const struct if_rmap *if_rmap = data;
+
+ return string_hash_make(if_rmap->ifname);
+}
+
+static bool if_rmap_hash_cmp(const void *arg1, const void *arg2)
+{
+ const struct if_rmap *if_rmap1 = arg1;
+ const struct if_rmap *if_rmap2 = arg2;
+
+ return strcmp(if_rmap1->ifname, if_rmap2->ifname) == 0;
+}
+
+static struct if_rmap *if_rmap_set(struct if_rmap_ctx *ctx,
+ const char *ifname, enum if_rmap_type type,
+ const char *routemap_name)
+{
+ struct if_rmap *if_rmap;
+
+ if_rmap = if_rmap_get(ctx, ifname);
+
+ if (type == IF_RMAP_IN) {
+ XFREE(MTYPE_IF_RMAP_NAME, if_rmap->routemap[IF_RMAP_IN]);
+ if_rmap->routemap[IF_RMAP_IN] =
+ XSTRDUP(MTYPE_IF_RMAP_NAME, routemap_name);
+ }
+ if (type == IF_RMAP_OUT) {
+ XFREE(MTYPE_IF_RMAP_NAME, if_rmap->routemap[IF_RMAP_OUT]);
+ if_rmap->routemap[IF_RMAP_OUT] =
+ XSTRDUP(MTYPE_IF_RMAP_NAME, routemap_name);
+ }
+
+ if (ctx->if_rmap_add_hook)
+ (ctx->if_rmap_add_hook)(ctx, if_rmap);
+
+ return if_rmap;
+}
+
+static int if_rmap_unset(struct if_rmap_ctx *ctx,
+ const char *ifname, enum if_rmap_type type,
+ const char *routemap_name)
+{
+ struct if_rmap *if_rmap;
+
+ if_rmap = if_rmap_lookup(ctx, ifname);
+ if (!if_rmap)
+ return 0;
+
+ if (type == IF_RMAP_IN) {
+ if (!if_rmap->routemap[IF_RMAP_IN])
+ return 0;
+ if (strcmp(if_rmap->routemap[IF_RMAP_IN], routemap_name) != 0)
+ return 0;
+
+ XFREE(MTYPE_IF_RMAP_NAME, if_rmap->routemap[IF_RMAP_IN]);
+ }
+
+ if (type == IF_RMAP_OUT) {
+ if (!if_rmap->routemap[IF_RMAP_OUT])
+ return 0;
+ if (strcmp(if_rmap->routemap[IF_RMAP_OUT], routemap_name) != 0)
+ return 0;
+
+ XFREE(MTYPE_IF_RMAP_NAME, if_rmap->routemap[IF_RMAP_OUT]);
+ }
+
+ if (ctx->if_rmap_delete_hook)
+ ctx->if_rmap_delete_hook(ctx, if_rmap);
+
+ if (if_rmap->routemap[IF_RMAP_IN] == NULL
+ && if_rmap->routemap[IF_RMAP_OUT] == NULL) {
+ hash_release(ctx->ifrmaphash, if_rmap);
+ if_rmap_free(if_rmap);
+ }
+
+ return 1;
+}
+
+DEFUN (if_rmap,
+ if_rmap_cmd,
+ "route-map RMAP_NAME <in|out> IFNAME",
+ "Route map set\n"
+ "Route map name\n"
+ "Route map set for input filtering\n"
+ "Route map set for output filtering\n"
+ "Route map interface name\n")
+{
+ int idx_rmap_name = 1;
+ int idx_in_out = 2;
+ int idx_ifname = 3;
+ enum if_rmap_type type;
+ struct if_rmap_ctx *ctx =
+ (struct if_rmap_ctx *)listnode_head(if_rmap_ctx_list);
+
+ if (strncmp(argv[idx_in_out]->text, "in", 1) == 0)
+ type = IF_RMAP_IN;
+ else if (strncmp(argv[idx_in_out]->text, "out", 1) == 0)
+ type = IF_RMAP_OUT;
+ else {
+ vty_out(vty, "route-map direction must be [in|out]\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ if_rmap_set(ctx, argv[idx_ifname]->arg,
+ type, argv[idx_rmap_name]->arg);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_if_rmap,
+ no_if_rmap_cmd,
+ "no route-map ROUTEMAP_NAME <in|out> IFNAME",
+ NO_STR
+ "Route map unset\n"
+ "Route map name\n"
+ "Route map for input filtering\n"
+ "Route map for output filtering\n"
+ "Route map interface name\n")
+{
+ int idx_routemap_name = 2;
+ int idx_in_out = 3;
+ int idx_ifname = 4;
+ int ret;
+ enum if_rmap_type type;
+ struct if_rmap_ctx *ctx =
+ (struct if_rmap_ctx *)listnode_head(if_rmap_ctx_list);
+
+ if (strncmp(argv[idx_in_out]->arg, "i", 1) == 0)
+ type = IF_RMAP_IN;
+ else if (strncmp(argv[idx_in_out]->arg, "o", 1) == 0)
+ type = IF_RMAP_OUT;
+ else {
+ vty_out(vty, "route-map direction must be [in|out]\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ ret = if_rmap_unset(ctx, argv[idx_ifname]->arg, type,
+ argv[idx_routemap_name]->arg);
+ if (!ret) {
+ vty_out(vty, "route-map doesn't exist\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+ return CMD_SUCCESS;
+}
+
+
+/* Configuration write function. */
+int config_write_if_rmap(struct vty *vty,
+ struct if_rmap_ctx *ctx)
+{
+ unsigned int i;
+ struct hash_bucket *mp;
+ int write = 0;
+ struct hash *ifrmaphash = ctx->ifrmaphash;
+
+ for (i = 0; i < ifrmaphash->size; i++)
+ for (mp = ifrmaphash->index[i]; mp; mp = mp->next) {
+ struct if_rmap *if_rmap;
+
+ if_rmap = mp->data;
+
+ if (if_rmap->routemap[IF_RMAP_IN]) {
+ vty_out(vty, " route-map %s in %s\n",
+ if_rmap->routemap[IF_RMAP_IN],
+ if_rmap->ifname);
+ write++;
+ }
+
+ if (if_rmap->routemap[IF_RMAP_OUT]) {
+ vty_out(vty, " route-map %s out %s\n",
+ if_rmap->routemap[IF_RMAP_OUT],
+ if_rmap->ifname);
+ write++;
+ }
+ }
+ return write;
+}
+
+void if_rmap_ctx_delete(struct if_rmap_ctx *ctx)
+{
+ listnode_delete(if_rmap_ctx_list, ctx);
+ hash_clean(ctx->ifrmaphash, (void (*)(void *))if_rmap_free);
+ if (ctx->name)
+ XFREE(MTYPE_IF_RMAP_CTX_NAME, ctx->name);
+ XFREE(MTYPE_IF_RMAP_CTX, ctx);
+}
+
+/* name is optional: either vrf name, or other */
+struct if_rmap_ctx *if_rmap_ctx_create(const char *name)
+{
+ struct if_rmap_ctx *ctx;
+
+ ctx = XCALLOC(MTYPE_IF_RMAP_CTX, sizeof(struct if_rmap_ctx));
+
+ if (ctx->name)
+ ctx->name = XSTRDUP(MTYPE_IF_RMAP_CTX_NAME, name);
+ ctx->ifrmaphash = hash_create_size(4, if_rmap_hash_make, if_rmap_hash_cmp,
+ "Interface Route-Map Hash");
+ if (!if_rmap_ctx_list)
+ if_rmap_ctx_list = list_new();
+ listnode_add(if_rmap_ctx_list, ctx);
+ return ctx;
+}
+
+void if_rmap_init(int node)
+{
+ if (node == RIPNG_NODE) {
+ } else if (node == RIP_NODE) {
+ install_element(RIP_NODE, &if_rmap_cmd);
+ install_element(RIP_NODE, &no_if_rmap_cmd);
+ }
+ if_rmap_ctx_list = list_new();
+}
+
+void if_rmap_terminate(void)
+{
+ if (!if_rmap_ctx_list)
+ return;
+ list_delete(&if_rmap_ctx_list);
+}
diff --git a/lib/if_rmap.h b/lib/if_rmap.h
new file mode 100644
index 0000000..dfc7298
--- /dev/null
+++ b/lib/if_rmap.h
@@ -0,0 +1,69 @@
+/* route-map for interface.
+ * Copyright (C) 1999 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 _ZEBRA_IF_RMAP_H
+#define _ZEBRA_IF_RMAP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum if_rmap_type { IF_RMAP_IN, IF_RMAP_OUT, IF_RMAP_MAX };
+
+struct if_rmap {
+ /* Name of the interface. */
+ char *ifname;
+
+ char *routemap[IF_RMAP_MAX];
+};
+
+struct if_rmap_ctx {
+ /* if_rmap */
+ struct hash *ifrmaphash;
+
+ /* Hook functions. */
+ void (*if_rmap_add_hook)(struct if_rmap_ctx *ctx,
+ struct if_rmap *ifrmap);
+ void (*if_rmap_delete_hook)(struct if_rmap_ctx *ctx,
+ struct if_rmap *ifrmap);
+
+ /* naming information */
+ char *name;
+};
+
+extern struct if_rmap_ctx *if_rmap_ctx_create(const char *name);
+extern void if_rmap_ctx_delete(struct if_rmap_ctx *ctx);
+extern void if_rmap_init(int node);
+extern void if_rmap_terminate(void);
+void if_rmap_hook_add(struct if_rmap_ctx *ctx,
+ void (*func)(struct if_rmap_ctx *ctx,
+ struct if_rmap *));
+void if_rmap_hook_delete(struct if_rmap_ctx *ctx,
+ void (*func)(struct if_rmap_ctx *ctx,
+ struct if_rmap *));
+extern struct if_rmap *if_rmap_lookup(struct if_rmap_ctx *ctx,
+ const char *ifname);
+extern int config_write_if_rmap(struct vty *, struct if_rmap_ctx *ctx);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ZEBRA_IF_RMAP_H */
diff --git a/lib/imsg-buffer.c b/lib/imsg-buffer.c
new file mode 100644
index 0000000..4d41702
--- /dev/null
+++ b/lib/imsg-buffer.c
@@ -0,0 +1,285 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and 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 "queue.h"
+#include "imsg.h"
+
+static int ibuf_realloc(struct ibuf *, size_t);
+static void ibuf_enqueue(struct msgbuf *, struct ibuf *);
+static void ibuf_dequeue(struct msgbuf *, struct ibuf *);
+
+struct ibuf *ibuf_open(size_t len)
+{
+ struct ibuf *buf;
+
+ if ((buf = calloc(1, sizeof(struct ibuf))) == NULL)
+ return NULL;
+ if ((buf->buf = malloc(len)) == NULL) {
+ free(buf);
+ return NULL;
+ }
+ buf->size = buf->max = len;
+ buf->fd = -1;
+
+ return (buf);
+}
+
+struct ibuf *ibuf_dynamic(size_t len, size_t max)
+{
+ struct ibuf *buf;
+
+ if (max < len)
+ return NULL;
+
+ if ((buf = ibuf_open(len)) == NULL)
+ return NULL;
+
+ if (max > 0)
+ buf->max = max;
+
+ return (buf);
+}
+
+static int ibuf_realloc(struct ibuf *buf, size_t len)
+{
+ uint8_t *b;
+
+ /* on static buffers max is eq size and so the following fails */
+ if (buf->wpos + len > buf->max) {
+ errno = ERANGE;
+ return -1;
+ }
+
+ b = realloc(buf->buf, buf->wpos + len);
+ if (b == NULL)
+ return -1;
+ buf->buf = b;
+ buf->size = buf->wpos + len;
+
+ return 0;
+}
+
+int ibuf_add(struct ibuf *buf, const void *data, size_t len)
+{
+ if (buf->wpos + len > buf->size)
+ if (ibuf_realloc(buf, len) == -1)
+ return -1;
+
+ memcpy(buf->buf + buf->wpos, data, len);
+ buf->wpos += len;
+ return 0;
+}
+
+void *ibuf_reserve(struct ibuf *buf, size_t len)
+{
+ void *b;
+
+ if (buf->wpos + len > buf->size)
+ if (ibuf_realloc(buf, len) == -1)
+ return NULL;
+
+ b = buf->buf + buf->wpos;
+ buf->wpos += len;
+ return (b);
+}
+
+void *ibuf_seek(struct ibuf *buf, size_t pos, size_t len)
+{
+ /* only allowed to seek in already written parts */
+ if (pos + len > buf->wpos)
+ return NULL;
+
+ return (buf->buf + pos);
+}
+
+size_t ibuf_size(struct ibuf *buf)
+{
+ return (buf->wpos);
+}
+
+size_t ibuf_left(struct ibuf *buf)
+{
+ return (buf->max - buf->wpos);
+}
+
+void ibuf_close(struct msgbuf *msgbuf, struct ibuf *buf)
+{
+ ibuf_enqueue(msgbuf, buf);
+}
+
+int ibuf_write(struct msgbuf *msgbuf)
+{
+ struct iovec iov[IOV_MAX];
+ struct ibuf *buf;
+ unsigned int i = 0;
+ ssize_t n;
+
+ memset(&iov, 0, sizeof(iov));
+ TAILQ_FOREACH (buf, &msgbuf->bufs, entry) {
+ if (i >= IOV_MAX)
+ break;
+ iov[i].iov_base = buf->buf + buf->rpos;
+ iov[i].iov_len = buf->wpos - buf->rpos;
+ i++;
+ }
+
+again:
+ if ((n = writev(msgbuf->fd, iov, i)) == -1) {
+ if (errno == EINTR)
+ goto again;
+ if (errno == ENOBUFS)
+ errno = EAGAIN;
+ return -1;
+ }
+
+ if (n == 0) { /* connection closed */
+ errno = 0;
+ return 0;
+ }
+
+ msgbuf_drain(msgbuf, n);
+
+ return 1;
+}
+
+void ibuf_free(struct ibuf *buf)
+{
+ if (buf == NULL)
+ return;
+ free(buf->buf);
+ free(buf);
+}
+
+void msgbuf_init(struct msgbuf *msgbuf)
+{
+ msgbuf->queued = 0;
+ msgbuf->fd = -1;
+ TAILQ_INIT(&msgbuf->bufs);
+}
+
+void msgbuf_drain(struct msgbuf *msgbuf, size_t n)
+{
+ struct ibuf *buf, *next;
+
+ for (buf = TAILQ_FIRST(&msgbuf->bufs); buf != NULL && n > 0;
+ buf = next) {
+ next = TAILQ_NEXT(buf, entry);
+ if (buf->rpos + n >= buf->wpos) {
+ n -= buf->wpos - buf->rpos;
+
+ TAILQ_REMOVE(&msgbuf->bufs, buf, entry);
+ ibuf_dequeue(msgbuf, buf);
+ } else {
+ buf->rpos += n;
+ n = 0;
+ }
+ }
+}
+
+void msgbuf_clear(struct msgbuf *msgbuf)
+{
+ struct ibuf *buf;
+
+ while ((buf = TAILQ_POP_FIRST(&msgbuf->bufs, entry)) != NULL)
+ ibuf_dequeue(msgbuf, buf);
+}
+
+int msgbuf_write(struct msgbuf *msgbuf)
+{
+ struct iovec iov[IOV_MAX];
+ struct ibuf *buf;
+ unsigned int i = 0;
+ ssize_t n;
+ struct msghdr msg;
+ struct cmsghdr *cmsg;
+ union {
+ struct cmsghdr hdr;
+ char buf[CMSG_SPACE(sizeof(int))];
+ } cmsgbuf;
+
+ memset(&iov, 0, sizeof(iov));
+ memset(&msg, 0, sizeof(msg));
+ memset(&cmsgbuf, 0, sizeof(cmsgbuf));
+ TAILQ_FOREACH (buf, &msgbuf->bufs, entry) {
+ if (i >= IOV_MAX)
+ break;
+ iov[i].iov_base = buf->buf + buf->rpos;
+ iov[i].iov_len = buf->wpos - buf->rpos;
+ i++;
+ if (buf->fd != -1)
+ break;
+ }
+
+ msg.msg_iov = iov;
+ msg.msg_iovlen = i;
+
+ if (buf != NULL && buf->fd != -1) {
+ msg.msg_control = (caddr_t)&cmsgbuf.buf;
+ msg.msg_controllen = sizeof(cmsgbuf.buf);
+ cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ memcpy(CMSG_DATA(cmsg), &buf->fd, sizeof(int));
+ }
+
+again:
+ if ((n = sendmsg(msgbuf->fd, &msg, 0)) == -1) {
+ if (errno == EINTR)
+ goto again;
+ if (errno == ENOBUFS)
+ errno = EAGAIN;
+ return -1;
+ }
+
+ if (n == 0) { /* connection closed */
+ errno = 0;
+ return 0;
+ }
+
+ /*
+ * assumption: fd got sent if sendmsg sent anything
+ * this works because fds are passed one at a time
+ */
+ if (buf != NULL && buf->fd != -1) {
+ close(buf->fd);
+ buf->fd = -1;
+ }
+
+ msgbuf_drain(msgbuf, n);
+
+ return 1;
+}
+
+static void ibuf_enqueue(struct msgbuf *msgbuf, struct ibuf *buf)
+{
+ TAILQ_INSERT_TAIL(&msgbuf->bufs, buf, entry);
+ msgbuf->queued++;
+}
+
+static void ibuf_dequeue(struct msgbuf *msgbuf, struct ibuf *buf)
+{
+ /* TAILQ_REMOVE done by caller */
+ if (buf->fd != -1)
+ close(buf->fd);
+
+ msgbuf->queued--;
+ ibuf_free(buf);
+}
diff --git a/lib/imsg.c b/lib/imsg.c
new file mode 100644
index 0000000..b46d5cb
--- /dev/null
+++ b/lib/imsg.c
@@ -0,0 +1,327 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and 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 "memory.h"
+#include "queue.h"
+#include "imsg.h"
+
+static int imsg_fd_overhead = 0;
+
+static int imsg_get_fd(struct imsgbuf *);
+
+#ifndef __OpenBSD__
+/*
+ * The original code calls getdtablecount() which is OpenBSD specific. Use
+ * available_fds() from OpenSMTPD instead.
+ */
+static int available_fds(unsigned int n)
+{
+ unsigned int i;
+ int ret, fds[256];
+
+ if (n > (unsigned int)array_size(fds))
+ return 1;
+
+ ret = 0;
+ for (i = 0; i < n; i++) {
+ fds[i] = -1;
+ if ((fds[i] = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
+ if (errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT)
+ fds[i] = socket(AF_INET6, SOCK_DGRAM, 0);
+ if (fds[i] < 0) {
+ ret = 1;
+ break;
+ }
+ }
+ }
+
+ for (i = 0; i < n && fds[i] >= 0; i++)
+ close(fds[i]);
+
+ return (ret);
+}
+#endif
+
+void imsg_init(struct imsgbuf *ibuf, int fd)
+{
+ msgbuf_init(&ibuf->w);
+ memset(&ibuf->r, 0, sizeof(ibuf->r));
+ ibuf->fd = fd;
+ ibuf->w.fd = fd;
+ ibuf->pid = getpid();
+ TAILQ_INIT(&ibuf->fds);
+}
+
+ssize_t imsg_read(struct imsgbuf *ibuf)
+{
+ struct msghdr msg;
+ struct cmsghdr *cmsg;
+ union {
+ struct cmsghdr hdr;
+ char buf[CMSG_SPACE(sizeof(int) * 1)];
+ } cmsgbuf;
+ struct iovec iov;
+ ssize_t n;
+ int fd;
+ struct imsg_fd *ifd;
+
+ memset(&msg, 0, sizeof(msg));
+ memset(&cmsgbuf, 0, sizeof(cmsgbuf));
+
+ iov.iov_base = ibuf->r.buf + ibuf->r.wpos;
+ iov.iov_len = sizeof(ibuf->r.buf) - ibuf->r.wpos;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = &cmsgbuf.buf;
+ msg.msg_controllen = sizeof(cmsgbuf.buf);
+
+ if ((ifd = calloc(1, sizeof(struct imsg_fd))) == NULL)
+ return -1;
+
+again:
+#ifdef __OpenBSD__
+ if (getdtablecount() + imsg_fd_overhead
+ + (int)((CMSG_SPACE(sizeof(int)) - CMSG_SPACE(0))
+ / sizeof(int))
+ >= getdtablesize()) {
+#else
+ if (available_fds(imsg_fd_overhead
+ + (CMSG_SPACE(sizeof(int)) - CMSG_SPACE(0))
+ / sizeof(int))) {
+#endif
+ errno = EAGAIN;
+ free(ifd);
+ return -1;
+ }
+
+ n = recvmsg(ibuf->fd, &msg, 0);
+ if (n == -1) {
+ if (errno == EINTR)
+ goto again;
+ goto fail;
+ }
+
+ ibuf->r.wpos += n;
+
+ for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
+ cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+ if (cmsg->cmsg_level == SOL_SOCKET
+ && cmsg->cmsg_type == SCM_RIGHTS) {
+ int i;
+ int j;
+
+ /*
+ * We only accept one file descriptor. Due to C
+ * padding rules, our control buffer might contain
+ * more than one fd, and we must close them.
+ */
+ j = ((char *)cmsg + cmsg->cmsg_len
+ - (char *)CMSG_DATA(cmsg))
+ / sizeof(int);
+ for (i = 0; i < j; i++) {
+ fd = ((int *)CMSG_DATA(cmsg))[i];
+ if (ifd != NULL) {
+ ifd->fd = fd;
+ TAILQ_INSERT_TAIL(&ibuf->fds, ifd,
+ entry);
+ ifd = NULL;
+ } else
+ close(fd);
+ }
+ }
+ /* we do not handle other ctl data level */
+ }
+
+fail:
+ free(ifd);
+ return (n);
+}
+
+ssize_t imsg_get(struct imsgbuf *ibuf, struct imsg *imsg)
+{
+ size_t av, left, datalen;
+
+ av = ibuf->r.wpos;
+
+ if (IMSG_HEADER_SIZE > av)
+ return 0;
+
+ memcpy(&imsg->hdr, ibuf->r.buf, sizeof(imsg->hdr));
+ if (imsg->hdr.len < IMSG_HEADER_SIZE || imsg->hdr.len > MAX_IMSGSIZE) {
+ errno = ERANGE;
+ return -1;
+ }
+ if (imsg->hdr.len > av)
+ return 0;
+ datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+ ibuf->r.rptr = ibuf->r.buf + IMSG_HEADER_SIZE;
+ if (datalen == 0)
+ imsg->data = NULL;
+ else if ((imsg->data = malloc(datalen)) == NULL)
+ return -1;
+
+ if (imsg->hdr.flags & IMSGF_HASFD)
+ imsg->fd = imsg_get_fd(ibuf);
+ else
+ imsg->fd = -1;
+
+ if (imsg->data)
+ memcpy(imsg->data, ibuf->r.rptr, datalen);
+
+ if (imsg->hdr.len < av) {
+ left = av - imsg->hdr.len;
+ memmove(&ibuf->r.buf, ibuf->r.buf + imsg->hdr.len, left);
+ ibuf->r.wpos = left;
+ } else
+ ibuf->r.wpos = 0;
+
+ return (datalen + IMSG_HEADER_SIZE);
+}
+
+int imsg_compose(struct imsgbuf *ibuf, uint32_t type, uint32_t peerid,
+ pid_t pid, int fd, const void *data, uint16_t datalen)
+{
+ struct ibuf *wbuf;
+
+ if ((wbuf = imsg_create(ibuf, type, peerid, pid, datalen)) == NULL)
+ return -1;
+
+ if (imsg_add(wbuf, data, datalen) == -1)
+ return -1;
+
+ wbuf->fd = fd;
+
+ imsg_close(ibuf, wbuf);
+
+ return 1;
+}
+
+int imsg_composev(struct imsgbuf *ibuf, uint32_t type, uint32_t peerid,
+ pid_t pid, int fd, const struct iovec *iov, int iovcnt)
+{
+ struct ibuf *wbuf;
+ int i, datalen = 0;
+
+ for (i = 0; i < iovcnt; i++)
+ datalen += iov[i].iov_len;
+
+ if ((wbuf = imsg_create(ibuf, type, peerid, pid, datalen)) == NULL)
+ return -1;
+
+ for (i = 0; i < iovcnt; i++)
+ if (imsg_add(wbuf, iov[i].iov_base, iov[i].iov_len) == -1)
+ return -1;
+
+ wbuf->fd = fd;
+
+ imsg_close(ibuf, wbuf);
+
+ return 1;
+}
+
+/* ARGSUSED */
+struct ibuf *imsg_create(struct imsgbuf *ibuf, uint32_t type, uint32_t peerid,
+ pid_t pid, uint16_t datalen)
+{
+ struct ibuf *wbuf;
+ struct imsg_hdr hdr;
+
+ memset(&hdr, 0x00, IMSG_HEADER_SIZE);
+
+ datalen += IMSG_HEADER_SIZE;
+ if (datalen > MAX_IMSGSIZE) {
+ errno = ERANGE;
+ return NULL;
+ }
+
+ hdr.type = type;
+ hdr.flags = 0;
+ hdr.peerid = peerid;
+ if ((hdr.pid = pid) == 0)
+ hdr.pid = ibuf->pid;
+ if ((wbuf = ibuf_dynamic(datalen, MAX_IMSGSIZE)) == NULL) {
+ return NULL;
+ }
+ if (imsg_add(wbuf, &hdr, sizeof(hdr)) == -1)
+ return NULL;
+
+ return (wbuf);
+}
+
+int imsg_add(struct ibuf *msg, const void *data, uint16_t datalen)
+{
+ if (datalen)
+ if (ibuf_add(msg, data, datalen) == -1) {
+ ibuf_free(msg);
+ return -1;
+ }
+ return (datalen);
+}
+
+void imsg_close(struct imsgbuf *ibuf, struct ibuf *msg)
+{
+ struct imsg_hdr *hdr;
+
+ hdr = (struct imsg_hdr *)msg->buf;
+
+ hdr->flags &= ~IMSGF_HASFD;
+ if (msg->fd != -1)
+ hdr->flags |= IMSGF_HASFD;
+
+ hdr->len = (uint16_t)msg->wpos;
+
+ ibuf_close(&ibuf->w, msg);
+}
+
+void imsg_free(struct imsg *imsg)
+{
+ free(imsg->data);
+}
+
+int imsg_get_fd(struct imsgbuf *ibuf)
+{
+ int fd;
+ struct imsg_fd *ifd;
+
+ if ((ifd = TAILQ_POP_FIRST(&ibuf->fds, entry)) == NULL)
+ return -1;
+
+ fd = ifd->fd;
+ free(ifd);
+
+ return (fd);
+}
+
+int imsg_flush(struct imsgbuf *ibuf)
+{
+ while (ibuf->w.queued)
+ if (msgbuf_write(&ibuf->w) <= 0)
+ return -1;
+ return 0;
+}
+
+void imsg_clear(struct imsgbuf *ibuf)
+{
+ int fd;
+
+ msgbuf_clear(&ibuf->w);
+ while ((fd = imsg_get_fd(ibuf)) != -1)
+ close(fd);
+}
diff --git a/lib/imsg.h b/lib/imsg.h
new file mode 100644
index 0000000..3f81b76
--- /dev/null
+++ b/lib/imsg.h
@@ -0,0 +1,119 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2006, 2007 Pierre-Yves Ritschard <pyr@openbsd.org>
+ * Copyright (c) 2006, 2007, 2008 Reyk Floeter <reyk@openbsd.org>
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and 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 _IMSG_H_
+#define _IMSG_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define IBUF_READ_SIZE 65535
+#define IMSG_HEADER_SIZE sizeof(struct imsg_hdr)
+#define MAX_IMSGSIZE 16384
+
+struct ibuf {
+ TAILQ_ENTRY(ibuf) entry;
+ uint8_t *buf;
+ size_t size;
+ size_t max;
+ size_t wpos;
+ size_t rpos;
+ int fd;
+};
+
+struct msgbuf {
+ TAILQ_HEAD(, ibuf) bufs;
+ uint32_t queued;
+ int fd;
+};
+
+struct ibuf_read {
+ uint8_t buf[IBUF_READ_SIZE];
+ uint8_t *rptr;
+ size_t wpos;
+};
+
+struct imsg_fd {
+ TAILQ_ENTRY(imsg_fd) entry;
+ int fd;
+};
+
+struct imsgbuf {
+ TAILQ_HEAD(, imsg_fd) fds;
+ struct ibuf_read r;
+ struct msgbuf w;
+ int fd;
+ pid_t pid;
+};
+
+#define IMSGF_HASFD 1
+
+struct imsg_hdr {
+ uint32_t type;
+ uint16_t len;
+ uint16_t flags;
+ uint32_t peerid;
+ uint32_t pid;
+};
+
+struct imsg {
+ struct imsg_hdr hdr;
+ int fd;
+ void *data;
+};
+
+
+/* buffer.c */
+struct ibuf *ibuf_open(size_t);
+struct ibuf *ibuf_dynamic(size_t, size_t);
+int ibuf_add(struct ibuf *, const void *, size_t);
+void *ibuf_reserve(struct ibuf *, size_t);
+void *ibuf_seek(struct ibuf *, size_t, size_t);
+size_t ibuf_size(struct ibuf *);
+size_t ibuf_left(struct ibuf *);
+void ibuf_close(struct msgbuf *, struct ibuf *);
+int ibuf_write(struct msgbuf *);
+void ibuf_free(struct ibuf *);
+void msgbuf_init(struct msgbuf *);
+void msgbuf_clear(struct msgbuf *);
+int msgbuf_write(struct msgbuf *);
+void msgbuf_drain(struct msgbuf *, size_t);
+
+/* imsg.c */
+void imsg_init(struct imsgbuf *, int);
+ssize_t imsg_read(struct imsgbuf *);
+ssize_t imsg_get(struct imsgbuf *, struct imsg *);
+int imsg_compose(struct imsgbuf *, uint32_t, uint32_t, pid_t, int, const void *,
+ uint16_t);
+int imsg_composev(struct imsgbuf *, uint32_t, uint32_t, pid_t, int,
+ const struct iovec *, int);
+struct ibuf *imsg_create(struct imsgbuf *, uint32_t, uint32_t, pid_t, uint16_t);
+int imsg_add(struct ibuf *, const void *, uint16_t);
+void imsg_close(struct imsgbuf *, struct ibuf *);
+void imsg_free(struct imsg *);
+int imsg_flush(struct imsgbuf *);
+void imsg_clear(struct imsgbuf *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/ipaddr.h b/lib/ipaddr.h
new file mode 100644
index 0000000..d7ab358
--- /dev/null
+++ b/lib/ipaddr.h
@@ -0,0 +1,185 @@
+/*
+ * IP address structure (for generic IPv4 or IPv6 address)
+ * Copyright (C) 2016, 2017 Cumulus Networks, Inc.
+ *
+ * This file is part of FRR.
+ *
+ * FRR 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, or (at your option) any
+ * later version.
+ *
+ * FRR 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 FRR; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef __IPADDR_H__
+#define __IPADDR_H__
+
+#include <zebra.h>
+
+#include "lib/log.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Generic IP address - union of IPv4 and IPv6 address.
+ */
+enum ipaddr_type_t {
+ IPADDR_NONE = AF_UNSPEC,
+ IPADDR_V4 = AF_INET,
+ IPADDR_V6 = AF_INET6,
+};
+
+struct ipaddr {
+ enum ipaddr_type_t ipa_type;
+ union {
+ uint8_t addr;
+ struct in_addr _v4_addr;
+ struct in6_addr _v6_addr;
+ } ip;
+#define ipaddr_v4 ip._v4_addr
+#define ipaddr_v6 ip._v6_addr
+};
+
+#define IS_IPADDR_NONE(p) ((p)->ipa_type == IPADDR_NONE)
+#define IS_IPADDR_V4(p) ((p)->ipa_type == IPADDR_V4)
+#define IS_IPADDR_V6(p) ((p)->ipa_type == IPADDR_V6)
+
+#define SET_IPADDR_V4(p) (p)->ipa_type = IPADDR_V4
+#define SET_IPADDR_V6(p) (p)->ipa_type = IPADDR_V6
+
+#define IPADDRSZ(p) \
+ (IS_IPADDR_V4((p)) ? sizeof(struct in_addr) : sizeof(struct in6_addr))
+
+static inline int ipaddr_family(const struct ipaddr *ip)
+{
+ switch (ip->ipa_type) {
+ case IPADDR_V4:
+ return AF_INET;
+ case IPADDR_V6:
+ return AF_INET6;
+ default:
+ return AF_UNSPEC;
+ }
+}
+
+static inline int str2ipaddr(const char *str, struct ipaddr *ip)
+{
+ int ret;
+
+ memset(ip, 0, sizeof(struct ipaddr));
+
+ ret = inet_pton(AF_INET, str, &ip->ipaddr_v4);
+ if (ret > 0) /* Valid IPv4 address. */
+ {
+ ip->ipa_type = IPADDR_V4;
+ return 0;
+ }
+ ret = inet_pton(AF_INET6, str, &ip->ipaddr_v6);
+ if (ret > 0) /* Valid IPv6 address. */
+ {
+ ip->ipa_type = IPADDR_V6;
+ return 0;
+ }
+
+ return -1;
+}
+
+static inline char *ipaddr2str(const struct ipaddr *ip, char *buf, int size)
+{
+ buf[0] = '\0';
+ if (ip)
+ inet_ntop(ip->ipa_type, &ip->ip.addr, buf, size);
+ return buf;
+}
+
+#define IS_MAPPED_IPV6(A) \
+ ((A)->s6_addr32[0] == 0x00000000 \
+ ? ((A)->s6_addr32[1] == 0x00000000 \
+ ? (ntohl((A)->s6_addr32[2]) == 0xFFFF ? 1 : 0) \
+ : 0) \
+ : 0)
+
+/*
+ * Convert IPv4 address to IPv4-mapped IPv6 address which is of the
+ * form ::FFFF:<IPv4 address> (RFC 4291). This IPv6 address can then
+ * be used to represent the IPv4 address, wherever only an IPv6 address
+ * is required.
+ */
+static inline void ipv4_to_ipv4_mapped_ipv6(struct in6_addr *in6,
+ struct in_addr in)
+{
+ uint32_t addr_type = htonl(0xFFFF);
+
+ memset(in6, 0, sizeof(struct in6_addr));
+ memcpy((char *)in6 + 8, &addr_type, sizeof(addr_type));
+ memcpy((char *)in6 + 12, &in, sizeof(struct in_addr));
+}
+
+/*
+ * convert an ipv4 mapped ipv6 address back to ipv4 address
+ */
+static inline void ipv4_mapped_ipv6_to_ipv4(const struct in6_addr *in6,
+ struct in_addr *in)
+{
+ memset(in, 0, sizeof(struct in_addr));
+ memcpy(in, (char *)in6 + 12, sizeof(struct in_addr));
+}
+
+/*
+ * generic ordering comparison between IP addresses
+ */
+static inline int ipaddr_cmp(const struct ipaddr *a, const struct ipaddr *b)
+{
+ uint32_t va, vb;
+ va = a->ipa_type;
+ vb = b->ipa_type;
+ if (va != vb)
+ return (va < vb) ? -1 : 1;
+ switch (a->ipa_type) {
+ case IPADDR_V4:
+ va = ntohl(a->ipaddr_v4.s_addr);
+ vb = ntohl(b->ipaddr_v4.s_addr);
+ if (va != vb)
+ return (va < vb) ? -1 : 1;
+ return 0;
+ case IPADDR_V6:
+ return memcmp((void *)&a->ipaddr_v6, (void *)&b->ipaddr_v6,
+ sizeof(a->ipaddr_v6));
+ default:
+ return 0;
+ }
+}
+
+static inline bool ipaddr_is_zero(const struct ipaddr *ip)
+{
+ switch (ip->ipa_type) {
+ case IPADDR_NONE:
+ return true;
+ case IPADDR_V4:
+ return ip->ipaddr_v4.s_addr == INADDR_ANY;
+ case IPADDR_V6:
+ return IN6_IS_ADDR_UNSPECIFIED(&ip->ipaddr_v6);
+ }
+ return true;
+}
+
+#ifdef _FRR_ATTRIBUTE_PRINTFRR
+#pragma FRR printfrr_ext "%pIA" (struct ipaddr *)
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __IPADDR_H__ */
diff --git a/lib/jhash.c b/lib/jhash.c
new file mode 100644
index 0000000..0d561ef
--- /dev/null
+++ b/lib/jhash.c
@@ -0,0 +1,187 @@
+/* jhash.h: Jenkins hash support.
+ *
+ * Copyright (C) 1996 Bob Jenkins (bob_jenkins@burtleburtle.net)
+ *
+ * http://burtleburtle.net/bob/hash/
+ *
+ * These are the credits from Bob's sources:
+ *
+ * lookup2.c, by Bob Jenkins, December 1996, Public Domain.
+ * hash(), hash2(), hash3, and mix() are externally useful functions.
+ * Routines to test the hash are included if SELF_TEST is defined.
+ * You can use this free for any purpose. It has no warranty.
+ *
+ * Copyright (C) 2003 David S. Miller (davem@redhat.com)
+ *
+ * I've modified Bob's hash to be useful in the Linux kernel, and
+ * any bugs present are surely my fault. -DaveM
+ */
+
+#include "zebra.h"
+#include "jhash.h"
+
+/* The golden ration: an arbitrary value */
+#define JHASH_GOLDEN_RATIO 0x9e3779b9
+
+/* NOTE: Arguments are modified. */
+#define __jhash_mix(a, b, c) \
+ { \
+ a -= b; \
+ a -= c; \
+ a ^= (c >> 13); \
+ b -= c; \
+ b -= a; \
+ b ^= (a << 8); \
+ c -= a; \
+ c -= b; \
+ c ^= (b >> 13); \
+ a -= b; \
+ a -= c; \
+ a ^= (c >> 12); \
+ b -= c; \
+ b -= a; \
+ b ^= (a << 16); \
+ c -= a; \
+ c -= b; \
+ c ^= (b >> 5); \
+ a -= b; \
+ a -= c; \
+ a ^= (c >> 3); \
+ b -= c; \
+ b -= a; \
+ b ^= (a << 10); \
+ c -= a; \
+ c -= b; \
+ c ^= (b >> 15); \
+ }
+
+/* The most generic version, hashes an arbitrary sequence
+ * of bytes. No alignment or length assumptions are made about
+ * the input key.
+ */
+uint32_t jhash(const void *key, uint32_t length, uint32_t initval)
+{
+ uint32_t a, b, c, len;
+ const uint8_t *k = key;
+
+ len = length;
+ a = b = JHASH_GOLDEN_RATIO;
+ c = initval;
+
+ while (len >= 12) {
+ a += (k[0] + ((uint32_t)k[1] << 8) + ((uint32_t)k[2] << 16)
+ + ((uint32_t)k[3] << 24));
+ b += (k[4] + ((uint32_t)k[5] << 8) + ((uint32_t)k[6] << 16)
+ + ((uint32_t)k[7] << 24));
+ c += (k[8] + ((uint32_t)k[9] << 8) + ((uint32_t)k[10] << 16)
+ + ((uint32_t)k[11] << 24));
+
+ __jhash_mix(a, b, c);
+
+ k += 12;
+ len -= 12;
+ }
+
+ c += length;
+ switch (len) {
+ case 11:
+ c += ((uint32_t)k[10] << 24);
+ /* fallthru */
+ case 10:
+ c += ((uint32_t)k[9] << 16);
+ /* fallthru */
+ case 9:
+ c += ((uint32_t)k[8] << 8);
+ /* fallthru */
+ case 8:
+ b += ((uint32_t)k[7] << 24);
+ /* fallthru */
+ case 7:
+ b += ((uint32_t)k[6] << 16);
+ /* fallthru */
+ case 6:
+ b += ((uint32_t)k[5] << 8);
+ /* fallthru */
+ case 5:
+ b += k[4];
+ /* fallthru */
+ case 4:
+ a += ((uint32_t)k[3] << 24);
+ /* fallthru */
+ case 3:
+ a += ((uint32_t)k[2] << 16);
+ /* fallthru */
+ case 2:
+ a += ((uint32_t)k[1] << 8);
+ /* fallthru */
+ case 1:
+ a += k[0];
+ }
+
+ __jhash_mix(a, b, c);
+
+ return c;
+}
+
+/* A special optimized version that handles 1 or more of uint32_ts.
+ * The length parameter here is the number of uint32_ts in the key.
+ */
+uint32_t jhash2(const uint32_t *k, uint32_t length, uint32_t initval)
+{
+ uint32_t a, b, c, len;
+
+ a = b = JHASH_GOLDEN_RATIO;
+ c = initval;
+ len = length;
+
+ while (len >= 3) {
+ a += k[0];
+ b += k[1];
+ c += k[2];
+ __jhash_mix(a, b, c);
+ k += 3;
+ len -= 3;
+ }
+
+ c += length * 4;
+
+ switch (len) {
+ case 2:
+ b += k[1];
+ /* fallthru */
+ case 1:
+ a += k[0];
+ }
+
+ __jhash_mix(a, b, c);
+
+ return c;
+}
+
+
+/* A special ultra-optimized versions that knows they are hashing exactly
+ * 3, 2 or 1 word(s).
+ *
+ * NOTE: In partilar the "c += length; __jhash_mix(a,b,c);" normally
+ * done at the end is not done here.
+ */
+uint32_t jhash_3words(uint32_t a, uint32_t b, uint32_t c, uint32_t initval)
+{
+ a += JHASH_GOLDEN_RATIO;
+ b += JHASH_GOLDEN_RATIO;
+ c += initval;
+
+ __jhash_mix(a, b, c);
+
+ return c;
+}
+
+uint32_t jhash_2words(uint32_t a, uint32_t b, uint32_t initval)
+{
+ return jhash_3words(a, b, 0, initval);
+}
+
+uint32_t jhash_1word(uint32_t a, uint32_t initval)
+{
+ return jhash_3words(a, 0, 0, initval);
+}
diff --git a/lib/jhash.h b/lib/jhash.h
new file mode 100644
index 0000000..9774214
--- /dev/null
+++ b/lib/jhash.h
@@ -0,0 +1,53 @@
+/* jhash.h: Jenkins hash support.
+ *
+ * Copyright (C) 1996 Bob Jenkins (bob_jenkins@burtleburtle.net)
+ *
+ * http://burtleburtle.net/bob/hash/
+ *
+ * These are the credits from Bob's sources:
+ *
+ * lookup2.c, by Bob Jenkins, December 1996, Public Domain.
+ * hash(), hash2(), hash3, and mix() are externally useful functions.
+ * Routines to test the hash are included if SELF_TEST is defined.
+ * You can use this free for any purpose. It has no warranty.
+ *
+ * Copyright (C) 2003 David S. Miller (davem@redhat.com)
+ *
+ * I've modified Bob's hash to be useful in the Linux kernel, and
+ * any bugs present are surely my fault. -DaveM
+ */
+
+#ifndef _QUAGGA_JHASH_H
+#define _QUAGGA_JHASH_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* The most generic version, hashes an arbitrary sequence
+ * of bytes. No alignment or length assumptions are made about
+ * the input key.
+ */
+extern uint32_t jhash(const void *key, uint32_t length, uint32_t initval);
+
+/* A special optimized version that handles 1 or more of uint32_ts.
+ * The length parameter here is the number of uint32_ts in the key.
+ */
+extern uint32_t jhash2(const uint32_t *k, uint32_t length, uint32_t initval);
+
+/* A special ultra-optimized versions that knows they are hashing exactly
+ * 3, 2 or 1 word(s).
+ *
+ * NOTE: In partilar the "c += length; __jhash_mix(a,b,c);" normally
+ * done at the end is not done here.
+ */
+extern uint32_t jhash_3words(uint32_t a, uint32_t b, uint32_t c,
+ uint32_t initval);
+extern uint32_t jhash_2words(uint32_t a, uint32_t b, uint32_t initval);
+extern uint32_t jhash_1word(uint32_t a, uint32_t initval);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QUAGGA_JHASH_H */
diff --git a/lib/json.c b/lib/json.c
new file mode 100644
index 0000000..d85a212
--- /dev/null
+++ b/lib/json.c
@@ -0,0 +1,123 @@
+/* json-c wrapper
+ * Copyright (C) 2015 Cumulus Networks, Inc.
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 "command.h"
+#include "lib/json.h"
+
+/*
+ * This function assumes that the json keyword
+ * is the *last* keyword on the line no matter
+ * what.
+ */
+bool use_json(const int argc, struct cmd_token *argv[])
+{
+ if (argc == 0)
+ return false;
+
+ if (argv[argc - 1]->arg && strmatch(argv[argc - 1]->text, "json"))
+ return true;
+
+ return false;
+}
+
+struct json_object *json_object_new_stringv(const char *fmt, va_list args)
+{
+ struct json_object *ret;
+ char *text, buf[256];
+
+ text = vasnprintfrr(MTYPE_TMP, buf, sizeof(buf), fmt, args);
+ ret = json_object_new_string(text);
+
+ if (text != buf)
+ XFREE(MTYPE_TMP, text);
+ return ret;
+}
+
+void json_array_string_add(json_object *json, const char *str)
+{
+ json_object_array_add(json, json_object_new_string(str));
+}
+
+void json_array_string_addv(json_object *json, const char *fmt, va_list args)
+{
+ json_object_array_add(json, json_object_new_stringv(fmt, args));
+}
+
+void json_object_string_add(struct json_object *obj, const char *key,
+ const char *s)
+{
+ json_object_object_add(obj, key, json_object_new_string(s));
+}
+
+void json_object_string_addv(struct json_object *obj, const char *key,
+ const char *fmt, va_list args)
+{
+ json_object_object_add(obj, key, json_object_new_stringv(fmt, args));
+}
+
+void json_object_object_addv(struct json_object *parent,
+ struct json_object *child, const char *keyfmt,
+ va_list args)
+{
+ char *text, buf[256];
+
+ text = vasnprintfrr(MTYPE_TMP, buf, sizeof(buf), keyfmt, args);
+ json_object_object_add(parent, text, child);
+
+ if (text != buf)
+ XFREE(MTYPE_TMP, text);
+}
+
+void json_object_int_add(struct json_object *obj, const char *key, int64_t i)
+{
+ json_object_object_add(obj, key, json_object_new_int64(i));
+}
+
+void json_object_double_add(struct json_object *obj, const char *key, double i)
+{
+ json_object_object_add(obj, key, json_object_new_double(i));
+}
+
+void json_object_boolean_false_add(struct json_object *obj, const char *key)
+{
+ json_object_object_add(obj, key, json_object_new_boolean(0));
+}
+
+void json_object_boolean_true_add(struct json_object *obj, const char *key)
+{
+ json_object_object_add(obj, key, json_object_new_boolean(1));
+}
+
+void json_object_boolean_add(struct json_object *obj, const char *key, bool val)
+{
+ json_object_object_add(obj, key, json_object_new_boolean(val));
+}
+
+struct json_object *json_object_lock(struct json_object *obj)
+{
+ return json_object_get(obj);
+}
+
+void json_object_free(struct json_object *obj)
+{
+ json_object_put(obj);
+}
diff --git a/lib/json.h b/lib/json.h
new file mode 100644
index 0000000..78c3836
--- /dev/null
+++ b/lib/json.h
@@ -0,0 +1,162 @@
+/* json-c wrapper
+ * Copyright (C) 2015 Cumulus Networks, Inc.
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 _QUAGGA_JSON_H
+#define _QUAGGA_JSON_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "command.h"
+#include "printfrr.h"
+#include <json-c/json.h>
+
+/*
+ * FRR style JSON iteration.
+ * Usage: JSON_FOREACH(...) { ... }
+ */
+#define JSON_FOREACH(jo, joi, join) \
+ /* struct json_object *jo; */ \
+ /* struct json_object_iterator joi; */ \
+ /* struct json_object_iterator join; */ \
+ for ((joi) = json_object_iter_begin((jo)), \
+ (join) = json_object_iter_end((jo)); \
+ json_object_iter_equal(&(joi), &(join)) == 0; \
+ json_object_iter_next(&(joi)))
+
+#define JSON_OBJECT_NEW_ARRAY(json_func, fields, n) \
+ ({ \
+ struct json_object *_json_array = json_object_new_array(); \
+ for (int _i = 0; _i < (n); _i++) \
+ json_object_array_add(_json_array, \
+ (json_func)((fields)[_i])); \
+ (_json_array); \
+ })
+
+extern bool use_json(const int argc, struct cmd_token *argv[]);
+extern void json_object_string_add(struct json_object *obj, const char *key,
+ const char *s);
+extern void json_object_int_add(struct json_object *obj, const char *key,
+ int64_t i);
+void json_object_boolean_add(struct json_object *obj, const char *key,
+ bool val);
+
+extern void json_object_double_add(struct json_object *obj, const char *key,
+ double i);
+extern void json_object_boolean_false_add(struct json_object *obj,
+ const char *key);
+extern void json_object_boolean_true_add(struct json_object *obj,
+ const char *key);
+extern struct json_object *json_object_lock(struct json_object *obj);
+extern void json_object_free(struct json_object *obj);
+extern void json_array_string_add(json_object *json, const char *str);
+
+/* printfrr => json helpers */
+
+PRINTFRR(3, 0)
+extern void json_object_string_addv(struct json_object *obj, const char *key,
+ const char *fmt, va_list args);
+PRINTFRR(3, 4)
+static inline void json_object_string_addf(struct json_object *obj,
+ const char *key, const char *fmt,
+ ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ json_object_string_addv(obj, key, fmt, args);
+ va_end(args);
+}
+
+PRINTFRR(2, 0)
+extern void json_array_string_addv(json_object *json, const char *fmt,
+ va_list args);
+PRINTFRR(2, 3)
+static inline void json_array_string_addf(struct json_object *obj,
+ const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ json_array_string_addv(obj, fmt, args);
+ va_end(args);
+}
+
+PRINTFRR(1, 0)
+extern struct json_object *json_object_new_stringv(const char *fmt,
+ va_list args);
+PRINTFRR(1, 2)
+static inline struct json_object *json_object_new_stringf(const char *fmt, ...)
+{
+ struct json_object *ret;
+ va_list args;
+
+ va_start(args, fmt);
+ ret = json_object_new_stringv(fmt, args);
+ va_end(args);
+
+ return ret;
+}
+
+/* NOTE: argument order differs! (due to varargs)
+ * json_object_object_add(parent, key, child)
+ * json_object_object_addv(parent, child, key, va)
+ * json_object_object_addf(parent, child, key, ...)
+ * (would be weird to have the child inbetween the format string and args)
+ */
+PRINTFRR(3, 0)
+extern void json_object_object_addv(struct json_object *parent,
+ struct json_object *child,
+ const char *keyfmt, va_list args);
+PRINTFRR(3, 4)
+static inline void json_object_object_addf(struct json_object *parent,
+ struct json_object *child,
+ const char *keyfmt, ...)
+{
+ va_list args;
+
+ va_start(args, keyfmt);
+ json_object_object_addv(parent, child, keyfmt, args);
+ va_end(args);
+}
+
+#define JSON_STR "JavaScript Object Notation\n"
+
+/* NOTE: json-c lib has following commit 316da85 which
+ * handles escape of forward slash.
+ * This allows prefix "20.0.14.0\/24":{
+ * to "20.0.14.0/24":{ some platforms do not have
+ * latest copy of json-c where defining below macro.
+ */
+
+#ifndef JSON_C_TO_STRING_NOSLASHESCAPE
+
+/**
+ * Don't escape forward slashes.
+ */
+#define JSON_C_TO_STRING_NOSLASHESCAPE (1<<4)
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QUAGGA_JSON_H */
diff --git a/lib/keychain.c b/lib/keychain.c
new file mode 100644
index 0000000..5dcb572
--- /dev/null
+++ b/lib/keychain.c
@@ -0,0 +1,1271 @@
+/* key-chain for authentication.
+ * Copyright (C) 2000 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra 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 "config.h"
+#include <zebra.h>
+
+#include "command.h"
+#include "memory.h"
+#include "linklist.h"
+#include "keychain.h"
+
+DEFINE_MTYPE_STATIC(LIB, KEY, "Key");
+DEFINE_MTYPE_STATIC(LIB, KEYCHAIN, "Key chain");
+
+DEFINE_QOBJ_TYPE(keychain);
+DEFINE_QOBJ_TYPE(key);
+
+/* Master list of key chain. */
+static struct list *keychain_list;
+
+static struct keychain *keychain_new(void)
+{
+ struct keychain *keychain;
+ keychain = XCALLOC(MTYPE_KEYCHAIN, sizeof(struct keychain));
+ QOBJ_REG(keychain, keychain);
+ return keychain;
+}
+
+static void keychain_free(struct keychain *keychain)
+{
+ QOBJ_UNREG(keychain);
+ XFREE(MTYPE_KEYCHAIN, keychain);
+}
+
+static struct key *key_new(void)
+{
+ struct key *key = XCALLOC(MTYPE_KEY, sizeof(struct key));
+ QOBJ_REG(key, key);
+ return key;
+}
+
+static void key_free(struct key *key)
+{
+ QOBJ_UNREG(key);
+ XFREE(MTYPE_KEY, key);
+}
+
+struct keychain *keychain_lookup(const char *name)
+{
+ struct listnode *node;
+ struct keychain *keychain;
+
+ if (name == NULL)
+ return NULL;
+
+ for (ALL_LIST_ELEMENTS_RO(keychain_list, node, keychain)) {
+ if (strcmp(keychain->name, name) == 0)
+ return keychain;
+ }
+ return NULL;
+}
+
+static int key_cmp_func(void *arg1, void *arg2)
+{
+ const struct key *k1 = arg1;
+ const struct key *k2 = arg2;
+
+ if (k1->index > k2->index)
+ return 1;
+ if (k1->index < k2->index)
+ return -1;
+ return 0;
+}
+
+static void key_delete_func(struct key *key)
+{
+ if (key->string)
+ free(key->string);
+ key_free(key);
+}
+
+static struct keychain *keychain_get(const char *name)
+{
+ struct keychain *keychain;
+
+ keychain = keychain_lookup(name);
+
+ if (keychain)
+ return keychain;
+
+ keychain = keychain_new();
+ keychain->name = XSTRDUP(MTYPE_KEYCHAIN, name);
+ keychain->key = list_new();
+ keychain->key->cmp = (int (*)(void *, void *))key_cmp_func;
+ keychain->key->del = (void (*)(void *))key_delete_func;
+ listnode_add(keychain_list, keychain);
+
+ return keychain;
+}
+
+static void keychain_delete(struct keychain *keychain)
+{
+ XFREE(MTYPE_KEYCHAIN, keychain->name);
+
+ list_delete(&keychain->key);
+ listnode_delete(keychain_list, keychain);
+ keychain_free(keychain);
+}
+
+static struct key *key_lookup(const struct keychain *keychain, uint32_t index)
+{
+ struct listnode *node;
+ struct key *key;
+
+ for (ALL_LIST_ELEMENTS_RO(keychain->key, node, key)) {
+ if (key->index == index)
+ return key;
+ }
+ return NULL;
+}
+
+struct key *key_lookup_for_accept(const struct keychain *keychain,
+ uint32_t index)
+{
+ struct listnode *node;
+ struct key *key;
+ time_t now;
+
+ now = time(NULL);
+
+ for (ALL_LIST_ELEMENTS_RO(keychain->key, node, key)) {
+ if (key->index >= index) {
+ if (key->accept.start == 0)
+ return key;
+
+ if (key->accept.start <= now)
+ if (key->accept.end >= now
+ || key->accept.end == -1)
+ return key;
+ }
+ }
+ return NULL;
+}
+
+struct key *key_match_for_accept(const struct keychain *keychain,
+ const char *auth_str)
+{
+ struct listnode *node;
+ struct key *key;
+ time_t now;
+
+ now = time(NULL);
+
+ for (ALL_LIST_ELEMENTS_RO(keychain->key, node, key)) {
+ if (key->accept.start == 0
+ || (key->accept.start <= now
+ && (key->accept.end >= now || key->accept.end == -1)))
+ if (key->string && (strncmp(key->string, auth_str, 16) == 0))
+ return key;
+ }
+ return NULL;
+}
+
+struct key *key_lookup_for_send(const struct keychain *keychain)
+{
+ struct listnode *node;
+ struct key *key;
+ time_t now;
+
+ now = time(NULL);
+
+ for (ALL_LIST_ELEMENTS_RO(keychain->key, node, key)) {
+ if (key->send.start == 0)
+ return key;
+
+ if (key->send.start <= now)
+ if (key->send.end >= now || key->send.end == -1)
+ return key;
+ }
+ return NULL;
+}
+
+static struct key *key_get(const struct keychain *keychain, uint32_t index)
+{
+ struct key *key;
+
+ key = key_lookup(keychain, index);
+
+ if (key)
+ return key;
+
+ key = key_new();
+ key->index = index;
+ key->hash_algo = KEYCHAIN_ALGO_NULL;
+ listnode_add_sort(keychain->key, key);
+
+ return key;
+}
+
+static void key_delete(struct keychain *keychain, struct key *key)
+{
+ listnode_delete(keychain->key, key);
+
+ XFREE(MTYPE_KEY, key->string);
+ key_free(key);
+}
+
+DEFUN_NOSH (key_chain,
+ key_chain_cmd,
+ "key chain WORD",
+ "Authentication key management\n"
+ "Key-chain management\n"
+ "Key-chain name\n")
+{
+ int idx_word = 2;
+ struct keychain *keychain;
+
+ keychain = keychain_get(argv[idx_word]->arg);
+ VTY_PUSH_CONTEXT(KEYCHAIN_NODE, keychain);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_key_chain,
+ no_key_chain_cmd,
+ "no key chain WORD",
+ NO_STR
+ "Authentication key management\n"
+ "Key-chain management\n"
+ "Key-chain name\n")
+{
+ int idx_word = 3;
+ struct keychain *keychain;
+
+ keychain = keychain_lookup(argv[idx_word]->arg);
+
+ if (!keychain) {
+ vty_out(vty, "Can't find keychain %s\n", argv[idx_word]->arg);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ keychain_delete(keychain);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_NOSH (key,
+ key_cmd,
+ "key (0-2147483647)",
+ "Configure a key\n"
+ "Key identifier number\n")
+{
+ int idx_number = 1;
+ VTY_DECLVAR_CONTEXT(keychain, keychain);
+ struct key *key;
+ uint32_t index;
+
+ index = strtoul(argv[idx_number]->arg, NULL, 10);
+ key = key_get(keychain, index);
+ VTY_PUSH_CONTEXT_SUB(KEYCHAIN_KEY_NODE, key);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_key,
+ no_key_cmd,
+ "no key (0-2147483647)",
+ NO_STR
+ "Delete a key\n"
+ "Key identifier number\n")
+{
+ int idx_number = 2;
+ VTY_DECLVAR_CONTEXT(keychain, keychain);
+ struct key *key;
+ uint32_t index;
+
+ index = strtoul(argv[idx_number]->arg, NULL, 10);
+ key = key_lookup(keychain, index);
+ if (!key) {
+ vty_out(vty, "Can't find key %d\n", index);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ key_delete(keychain, key);
+
+ vty->node = KEYCHAIN_NODE;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (key_string,
+ key_string_cmd,
+ "key-string LINE",
+ "Set key string\n"
+ "The key\n")
+{
+ int idx_line = 1;
+ VTY_DECLVAR_CONTEXT_SUB(key, key);
+
+ if (key->string)
+ XFREE(MTYPE_KEY, key->string);
+ key->string = XSTRDUP(MTYPE_KEY, argv[idx_line]->arg);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_key_string,
+ no_key_string_cmd,
+ "no key-string [LINE]",
+ NO_STR
+ "Unset key string\n"
+ "The key\n")
+{
+ VTY_DECLVAR_CONTEXT_SUB(key, key);
+
+ if (key->string) {
+ XFREE(MTYPE_KEY, key->string);
+ key->string = NULL;
+ }
+
+ return CMD_SUCCESS;
+}
+
+const struct keychain_algo_info algo_info[] = {
+ {KEYCHAIN_ALGO_NULL, "null", 0, 0, "NULL"},
+ {KEYCHAIN_ALGO_MD5, "md5", KEYCHAIN_MD5_HASH_SIZE,
+ KEYCHAIN_ALGO_MD5_INTERNAL_BLK_SIZE, "MD5"},
+ {KEYCHAIN_ALGO_HMAC_SHA1, "hmac-sha-1", KEYCHAIN_HMAC_SHA1_HASH_SIZE,
+ KEYCHAIN_ALGO_SHA1_INTERNAL_BLK_SIZE, "HMAC-SHA-1"},
+ {KEYCHAIN_ALGO_HMAC_SHA256, "hmac-sha-256",
+ KEYCHAIN_HMAC_SHA256_HASH_SIZE, KEYCHAIN_ALGO_SHA256_INTERNAL_BLK_SIZE,
+ "HMAC-SHA-256"},
+ {KEYCHAIN_ALGO_HMAC_SHA384, "hmac-sha-384",
+ KEYCHAIN_HMAC_SHA384_HASH_SIZE, KEYCHAIN_ALGO_SHA384_INTERNAL_BLK_SIZE,
+ "HMAC-SHA-384"},
+ {KEYCHAIN_ALGO_HMAC_SHA512, "hmac-sha-512",
+ KEYCHAIN_HMAC_SHA512_HASH_SIZE, KEYCHAIN_ALGO_SHA512_INTERNAL_BLK_SIZE,
+ "HMAC-SHA-512"},
+ {KEYCHAIN_ALGO_MAX, "max", KEYCHAIN_MAX_HASH_SIZE,
+ KEYCHAIN_ALGO_MAX_INTERNAL_BLK_SIZE, "Not defined"}
+};
+
+uint16_t keychain_get_block_size(enum keychain_hash_algo key)
+{
+ return algo_info[key].block;
+}
+
+uint16_t keychain_get_hash_len(enum keychain_hash_algo key)
+{
+ return algo_info[key].length;
+}
+
+const char *keychain_get_description(enum keychain_hash_algo key)
+{
+ return algo_info[key].desc;
+}
+
+struct keychain_algo_info
+keychain_get_hash_algo_info(enum keychain_hash_algo key)
+{
+ return algo_info[key];
+}
+
+enum keychain_hash_algo keychain_get_algo_id_by_name(const char *name)
+{
+#ifdef CRYPTO_INTERNAL
+ if (!strncmp(name, "hmac-sha-2", 10))
+ return KEYCHAIN_ALGO_HMAC_SHA256;
+ else if (!strncmp(name, "m", 1))
+ return KEYCHAIN_ALGO_MD5;
+ else
+ return KEYCHAIN_ALGO_NULL;
+#else
+ if (!strncmp(name, "m", 1))
+ return KEYCHAIN_ALGO_MD5;
+ else if (!strncmp(name, "hmac-sha-1", 10))
+ return KEYCHAIN_ALGO_HMAC_SHA1;
+ else if (!strncmp(name, "hmac-sha-2", 10))
+ return KEYCHAIN_ALGO_HMAC_SHA256;
+ else if (!strncmp(name, "hmac-sha-3", 10))
+ return KEYCHAIN_ALGO_HMAC_SHA384;
+ else if (!strncmp(name, "hmac-sha-5", 10))
+ return KEYCHAIN_ALGO_HMAC_SHA512;
+ else
+ return KEYCHAIN_ALGO_NULL;
+#endif
+}
+
+const char *keychain_get_algo_name_by_id(enum keychain_hash_algo key)
+{
+ return algo_info[key].name;
+}
+
+DEFUN(cryptographic_algorithm, cryptographic_algorithm_cmd,
+ "cryptographic-algorithm "
+ "<md5|hmac-sha-1|hmac-sha-256|hmac-sha-384|hmac-sha-512>",
+ "Cryptographic-algorithm\n"
+ "Use MD5 algorithm\n"
+ "Use HMAC-SHA-1 algorithm\n"
+ "Use HMAC-SHA-256 algorithm\n"
+ "Use HMAC-SHA-384 algorithm\n"
+ "Use HMAC-SHA-512 algorithm\n")
+{
+ int algo_idx = 1;
+ uint8_t hash_algo = KEYCHAIN_ALGO_NULL;
+
+ VTY_DECLVAR_CONTEXT_SUB(key, key);
+ hash_algo = keychain_get_algo_id_by_name(argv[algo_idx]->arg);
+#ifndef CRYPTO_OPENSSL
+ if (hash_algo == KEYCHAIN_ALGO_NULL) {
+ vty_out(vty,
+ "Hash algorithm not supported, compile with --with-crypto=openssl\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+#endif /* CRYPTO_OPENSSL */
+ key->hash_algo = hash_algo;
+ return CMD_SUCCESS;
+}
+
+DEFUN(no_cryptographic_algorithm, no_cryptographic_algorithm_cmd,
+ "no cryptographic-algorithm "
+ "[<md5|hmac-sha-1|hmac-sha-256|hmac-sha-384|hmac-sha-512>]",
+ NO_STR
+ "Cryptographic-algorithm\n"
+ "Use MD5 algorithm\n"
+ "Use HMAC-SHA-1 algorithm\n"
+ "Use HMAC-SHA-256 algorithm\n"
+ "Use HMAC-SHA-384 algorithm\n"
+ "Use HMAC-SHA-512 algorithm\n")
+{
+ int algo_idx = 2;
+ uint8_t hash_algo = KEYCHAIN_ALGO_NULL;
+
+ VTY_DECLVAR_CONTEXT_SUB(key, key);
+ if (argc > algo_idx) {
+ hash_algo = keychain_get_algo_id_by_name(argv[algo_idx]->arg);
+ if (hash_algo == KEYCHAIN_ALGO_NULL) {
+ vty_out(vty,
+ "Hash algorithm not supported, try compiling with --with-crypto=openssl\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+ }
+
+ if ((hash_algo != KEYCHAIN_ALGO_NULL) && (hash_algo != key->hash_algo))
+ return CMD_SUCCESS;
+
+ key->hash_algo = KEYCHAIN_ALGO_NULL;
+ return CMD_SUCCESS;
+}
+
+/* Convert HH:MM:SS MON DAY YEAR to time_t value. -1 is returned when
+ given string is malformed. */
+static time_t key_str2time(const char *time_str, const char *day_str,
+ const char *month_str, const char *year_str)
+{
+ int i = 0;
+ char *colon;
+ struct tm tm;
+ time_t time;
+ unsigned int sec, min, hour;
+ unsigned int day, month, year;
+
+ const char *month_name[] = {
+ "January", "February", "March", "April", "May",
+ "June", "July", "August", "September", "October",
+ "November", "December", NULL};
+
+#define _GET_LONG_RANGE(V, STR, MMCOND) \
+ { \
+ unsigned long tmpl; \
+ char *endptr = NULL; \
+ tmpl = strtoul((STR), &endptr, 10); \
+ if (*endptr != '\0' || tmpl == ULONG_MAX) \
+ return -1; \
+ if (MMCOND) \
+ return -1; \
+ (V) = tmpl; \
+ }
+#define GET_LONG_RANGE(V, STR, MIN, MAX) \
+ _GET_LONG_RANGE(V, STR, tmpl<(MIN) || tmpl>(MAX))
+#define GET_LONG_RANGE0(V, STR, MAX) _GET_LONG_RANGE(V, STR, tmpl > (MAX))
+
+ /* Check hour field of time_str. */
+ colon = strchr(time_str, ':');
+ if (colon == NULL)
+ return -1;
+ *colon = '\0';
+
+ /* Hour must be between 0 and 23. */
+ GET_LONG_RANGE0(hour, time_str, 23);
+
+ /* Check min field of time_str. */
+ time_str = colon + 1;
+ colon = strchr(time_str, ':');
+ if (*time_str == '\0' || colon == NULL)
+ return -1;
+ *colon = '\0';
+
+ /* Min must be between 0 and 59. */
+ GET_LONG_RANGE0(min, time_str, 59);
+
+ /* Check sec field of time_str. */
+ time_str = colon + 1;
+ if (*time_str == '\0')
+ return -1;
+
+ /* Sec must be between 0 and 59. */
+ GET_LONG_RANGE0(sec, time_str, 59);
+
+ /* Check day_str. Day must be <1-31>. */
+ GET_LONG_RANGE(day, day_str, 1, 31);
+
+ /* Check month_str. Month must match month_name. */
+ month = 0;
+ if (strlen(month_str) >= 3)
+ for (i = 0; month_name[i]; i++)
+ if (strncmp(month_str, month_name[i], strlen(month_str))
+ == 0) {
+ month = i;
+ break;
+ }
+ if (!month_name[i])
+ return -1;
+
+ /* Check year_str. Year must be <1993-2035>. */
+ GET_LONG_RANGE(year, year_str, 1993, 2035);
+
+ memset(&tm, 0, sizeof(tm));
+ tm.tm_sec = sec;
+ tm.tm_min = min;
+ tm.tm_hour = hour;
+ tm.tm_mon = month;
+ tm.tm_mday = day;
+ tm.tm_year = year - 1900;
+
+ time = mktime(&tm);
+
+ return time;
+#undef GET_LONG_RANGE
+}
+
+static int key_lifetime_set(struct vty *vty, struct key_range *krange,
+ const char *stime_str, const char *sday_str,
+ const char *smonth_str, const char *syear_str,
+ const char *etime_str, const char *eday_str,
+ const char *emonth_str, const char *eyear_str)
+{
+ time_t time_start;
+ time_t time_end;
+
+ time_start = key_str2time(stime_str, sday_str, smonth_str, syear_str);
+ if (time_start < 0) {
+ vty_out(vty, "Malformed time value\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+ time_end = key_str2time(etime_str, eday_str, emonth_str, eyear_str);
+
+ if (time_end < 0) {
+ vty_out(vty, "Malformed time value\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ if (time_end <= time_start) {
+ vty_out(vty, "Expire time is not later than start time\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ krange->start = time_start;
+ krange->end = time_end;
+
+ return CMD_SUCCESS;
+}
+
+static int key_lifetime_duration_set(struct vty *vty, struct key_range *krange,
+ const char *stime_str,
+ const char *sday_str,
+ const char *smonth_str,
+ const char *syear_str,
+ const char *duration_str)
+{
+ time_t time_start;
+ uint32_t duration;
+
+ time_start = key_str2time(stime_str, sday_str, smonth_str, syear_str);
+ if (time_start < 0) {
+ vty_out(vty, "Malformed time value\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+ krange->start = time_start;
+
+ duration = strtoul(duration_str, NULL, 10);
+ krange->duration = 1;
+ krange->end = time_start + duration;
+
+ return CMD_SUCCESS;
+}
+
+static int key_lifetime_infinite_set(struct vty *vty, struct key_range *krange,
+ const char *stime_str,
+ const char *sday_str,
+ const char *smonth_str,
+ const char *syear_str)
+{
+ time_t time_start;
+
+ time_start = key_str2time(stime_str, sday_str, smonth_str, syear_str);
+ if (time_start < 0) {
+ vty_out(vty, "Malformed time value\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+ krange->start = time_start;
+
+ krange->end = -1;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (accept_lifetime_day_month_day_month,
+ accept_lifetime_day_month_day_month_cmd,
+ "accept-lifetime HH:MM:SS (1-31) MONTH (1993-2035) HH:MM:SS (1-31) MONTH (1993-2035)",
+ "Set accept lifetime of the key\n"
+ "Time to start\n"
+ "Day of th month to start\n"
+ "Month of the year to start\n"
+ "Year to start\n"
+ "Time to expire\n"
+ "Day of th month to expire\n"
+ "Month of the year to expire\n"
+ "Year to expire\n")
+{
+ int idx_hhmmss = 1;
+ int idx_number = 2;
+ int idx_month = 3;
+ int idx_number_2 = 4;
+ int idx_hhmmss_2 = 5;
+ int idx_number_3 = 6;
+ int idx_month_2 = 7;
+ int idx_number_4 = 8;
+ VTY_DECLVAR_CONTEXT_SUB(key, key);
+
+ return key_lifetime_set(
+ vty, &key->accept, argv[idx_hhmmss]->arg, argv[idx_number]->arg,
+ argv[idx_month]->arg, argv[idx_number_2]->arg,
+ argv[idx_hhmmss_2]->arg, argv[idx_number_3]->arg,
+ argv[idx_month_2]->arg, argv[idx_number_4]->arg);
+}
+
+DEFUN (accept_lifetime_day_month_month_day,
+ accept_lifetime_day_month_month_day_cmd,
+ "accept-lifetime HH:MM:SS (1-31) MONTH (1993-2035) HH:MM:SS MONTH (1-31) (1993-2035)",
+ "Set accept lifetime of the key\n"
+ "Time to start\n"
+ "Day of th month to start\n"
+ "Month of the year to start\n"
+ "Year to start\n"
+ "Time to expire\n"
+ "Month of the year to expire\n"
+ "Day of th month to expire\n"
+ "Year to expire\n")
+{
+ int idx_hhmmss = 1;
+ int idx_number = 2;
+ int idx_month = 3;
+ int idx_number_2 = 4;
+ int idx_hhmmss_2 = 5;
+ int idx_month_2 = 6;
+ int idx_number_3 = 7;
+ int idx_number_4 = 8;
+ VTY_DECLVAR_CONTEXT_SUB(key, key);
+
+ return key_lifetime_set(
+ vty, &key->accept, argv[idx_hhmmss]->arg, argv[idx_number]->arg,
+ argv[idx_month]->arg, argv[idx_number_2]->arg,
+ argv[idx_hhmmss_2]->arg, argv[idx_number_3]->arg,
+ argv[idx_month_2]->arg, argv[idx_number_4]->arg);
+}
+
+DEFUN (accept_lifetime_month_day_day_month,
+ accept_lifetime_month_day_day_month_cmd,
+ "accept-lifetime HH:MM:SS MONTH (1-31) (1993-2035) HH:MM:SS (1-31) MONTH (1993-2035)",
+ "Set accept lifetime of the key\n"
+ "Time to start\n"
+ "Month of the year to start\n"
+ "Day of th month to start\n"
+ "Year to start\n"
+ "Time to expire\n"
+ "Day of th month to expire\n"
+ "Month of the year to expire\n"
+ "Year to expire\n")
+{
+ int idx_hhmmss = 1;
+ int idx_month = 2;
+ int idx_number = 3;
+ int idx_number_2 = 4;
+ int idx_hhmmss_2 = 5;
+ int idx_number_3 = 6;
+ int idx_month_2 = 7;
+ int idx_number_4 = 8;
+ VTY_DECLVAR_CONTEXT_SUB(key, key);
+
+ return key_lifetime_set(
+ vty, &key->accept, argv[idx_hhmmss]->arg, argv[idx_number]->arg,
+ argv[idx_month]->arg, argv[idx_number_2]->arg,
+ argv[idx_hhmmss_2]->arg, argv[idx_number_3]->arg,
+ argv[idx_month_2]->arg, argv[idx_number_4]->arg);
+}
+
+DEFUN (accept_lifetime_month_day_month_day,
+ accept_lifetime_month_day_month_day_cmd,
+ "accept-lifetime HH:MM:SS MONTH (1-31) (1993-2035) HH:MM:SS MONTH (1-31) (1993-2035)",
+ "Set accept lifetime of the key\n"
+ "Time to start\n"
+ "Month of the year to start\n"
+ "Day of th month to start\n"
+ "Year to start\n"
+ "Time to expire\n"
+ "Month of the year to expire\n"
+ "Day of th month to expire\n"
+ "Year to expire\n")
+{
+ int idx_hhmmss = 1;
+ int idx_month = 2;
+ int idx_number = 3;
+ int idx_number_2 = 4;
+ int idx_hhmmss_2 = 5;
+ int idx_month_2 = 6;
+ int idx_number_3 = 7;
+ int idx_number_4 = 8;
+ VTY_DECLVAR_CONTEXT_SUB(key, key);
+
+ return key_lifetime_set(
+ vty, &key->accept, argv[idx_hhmmss]->arg, argv[idx_number]->arg,
+ argv[idx_month]->arg, argv[idx_number_2]->arg,
+ argv[idx_hhmmss_2]->arg, argv[idx_number_3]->arg,
+ argv[idx_month_2]->arg, argv[idx_number_4]->arg);
+}
+
+DEFUN (accept_lifetime_infinite_day_month,
+ accept_lifetime_infinite_day_month_cmd,
+ "accept-lifetime HH:MM:SS (1-31) MONTH (1993-2035) infinite",
+ "Set accept lifetime of the key\n"
+ "Time to start\n"
+ "Day of th month to start\n"
+ "Month of the year to start\n"
+ "Year to start\n"
+ "Never expires\n")
+{
+ int idx_hhmmss = 1;
+ int idx_number = 2;
+ int idx_month = 3;
+ int idx_number_2 = 4;
+ VTY_DECLVAR_CONTEXT_SUB(key, key);
+
+ return key_lifetime_infinite_set(
+ vty, &key->accept, argv[idx_hhmmss]->arg, argv[idx_number]->arg,
+ argv[idx_month]->arg, argv[idx_number_2]->arg);
+}
+
+DEFUN (accept_lifetime_infinite_month_day,
+ accept_lifetime_infinite_month_day_cmd,
+ "accept-lifetime HH:MM:SS MONTH (1-31) (1993-2035) infinite",
+ "Set accept lifetime of the key\n"
+ "Time to start\n"
+ "Month of the year to start\n"
+ "Day of th month to start\n"
+ "Year to start\n"
+ "Never expires\n")
+{
+ int idx_hhmmss = 1;
+ int idx_month = 2;
+ int idx_number = 3;
+ int idx_number_2 = 4;
+ VTY_DECLVAR_CONTEXT_SUB(key, key);
+
+ return key_lifetime_infinite_set(
+ vty, &key->accept, argv[idx_hhmmss]->arg, argv[idx_number]->arg,
+ argv[idx_month]->arg, argv[idx_number_2]->arg);
+}
+
+DEFUN (accept_lifetime_duration_day_month,
+ accept_lifetime_duration_day_month_cmd,
+ "accept-lifetime HH:MM:SS (1-31) MONTH (1993-2035) duration (1-2147483646)",
+ "Set accept lifetime of the key\n"
+ "Time to start\n"
+ "Day of th month to start\n"
+ "Month of the year to start\n"
+ "Year to start\n"
+ "Duration of the key\n"
+ "Duration seconds\n")
+{
+ int idx_hhmmss = 1;
+ int idx_number = 2;
+ int idx_month = 3;
+ int idx_number_2 = 4;
+ int idx_number_3 = 6;
+ VTY_DECLVAR_CONTEXT_SUB(key, key);
+
+ return key_lifetime_duration_set(
+ vty, &key->accept, argv[idx_hhmmss]->arg, argv[idx_number]->arg,
+ argv[idx_month]->arg, argv[idx_number_2]->arg,
+ argv[idx_number_3]->arg);
+}
+
+DEFUN (accept_lifetime_duration_month_day,
+ accept_lifetime_duration_month_day_cmd,
+ "accept-lifetime HH:MM:SS MONTH (1-31) (1993-2035) duration (1-2147483646)",
+ "Set accept lifetime of the key\n"
+ "Time to start\n"
+ "Month of the year to start\n"
+ "Day of th month to start\n"
+ "Year to start\n"
+ "Duration of the key\n"
+ "Duration seconds\n")
+{
+ int idx_hhmmss = 1;
+ int idx_month = 2;
+ int idx_number = 3;
+ int idx_number_2 = 4;
+ int idx_number_3 = 6;
+ VTY_DECLVAR_CONTEXT_SUB(key, key);
+
+ return key_lifetime_duration_set(
+ vty, &key->accept, argv[idx_hhmmss]->arg, argv[idx_number]->arg,
+ argv[idx_month]->arg, argv[idx_number_2]->arg,
+ argv[idx_number_3]->arg);
+}
+
+DEFUN (no_accept_lifetime,
+ no_accept_lifetime_cmd,
+ "no accept-lifetime",
+ NO_STR
+ "Unset accept-lifetime\n")
+{
+ VTY_DECLVAR_CONTEXT_SUB(key, key);
+
+ if (key->accept.start)
+ key->accept.start = 0;
+ if (key->accept.end)
+ key->accept.end = 0;
+ if (key->accept.duration)
+ key->accept.duration = 0;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (send_lifetime_day_month_day_month,
+ send_lifetime_day_month_day_month_cmd,
+ "send-lifetime HH:MM:SS (1-31) MONTH (1993-2035) HH:MM:SS (1-31) MONTH (1993-2035)",
+ "Set send lifetime of the key\n"
+ "Time to start\n"
+ "Day of th month to start\n"
+ "Month of the year to start\n"
+ "Year to start\n"
+ "Time to expire\n"
+ "Day of th month to expire\n"
+ "Month of the year to expire\n"
+ "Year to expire\n")
+{
+ int idx_hhmmss = 1;
+ int idx_number = 2;
+ int idx_month = 3;
+ int idx_number_2 = 4;
+ int idx_hhmmss_2 = 5;
+ int idx_number_3 = 6;
+ int idx_month_2 = 7;
+ int idx_number_4 = 8;
+ VTY_DECLVAR_CONTEXT_SUB(key, key);
+
+ return key_lifetime_set(
+ vty, &key->send, argv[idx_hhmmss]->arg, argv[idx_number]->arg,
+ argv[idx_month]->arg, argv[idx_number_2]->arg,
+ argv[idx_hhmmss_2]->arg, argv[idx_number_3]->arg,
+ argv[idx_month_2]->arg, argv[idx_number_4]->arg);
+}
+
+DEFUN (send_lifetime_day_month_month_day,
+ send_lifetime_day_month_month_day_cmd,
+ "send-lifetime HH:MM:SS (1-31) MONTH (1993-2035) HH:MM:SS MONTH (1-31) (1993-2035)",
+ "Set send lifetime of the key\n"
+ "Time to start\n"
+ "Day of th month to start\n"
+ "Month of the year to start\n"
+ "Year to start\n"
+ "Time to expire\n"
+ "Month of the year to expire\n"
+ "Day of th month to expire\n"
+ "Year to expire\n")
+{
+ int idx_hhmmss = 1;
+ int idx_number = 2;
+ int idx_month = 3;
+ int idx_number_2 = 4;
+ int idx_hhmmss_2 = 5;
+ int idx_month_2 = 6;
+ int idx_number_3 = 7;
+ int idx_number_4 = 8;
+ VTY_DECLVAR_CONTEXT_SUB(key, key);
+
+ return key_lifetime_set(
+ vty, &key->send, argv[idx_hhmmss]->arg, argv[idx_number]->arg,
+ argv[idx_month]->arg, argv[idx_number_2]->arg,
+ argv[idx_hhmmss_2]->arg, argv[idx_number_3]->arg,
+ argv[idx_month_2]->arg, argv[idx_number_4]->arg);
+}
+
+DEFUN (send_lifetime_month_day_day_month,
+ send_lifetime_month_day_day_month_cmd,
+ "send-lifetime HH:MM:SS MONTH (1-31) (1993-2035) HH:MM:SS (1-31) MONTH (1993-2035)",
+ "Set send lifetime of the key\n"
+ "Time to start\n"
+ "Month of the year to start\n"
+ "Day of th month to start\n"
+ "Year to start\n"
+ "Time to expire\n"
+ "Day of th month to expire\n"
+ "Month of the year to expire\n"
+ "Year to expire\n")
+{
+ int idx_hhmmss = 1;
+ int idx_month = 2;
+ int idx_number = 3;
+ int idx_number_2 = 4;
+ int idx_hhmmss_2 = 5;
+ int idx_number_3 = 6;
+ int idx_month_2 = 7;
+ int idx_number_4 = 8;
+ VTY_DECLVAR_CONTEXT_SUB(key, key);
+
+ return key_lifetime_set(
+ vty, &key->send, argv[idx_hhmmss]->arg, argv[idx_number]->arg,
+ argv[idx_month]->arg, argv[idx_number_2]->arg,
+ argv[idx_hhmmss_2]->arg, argv[idx_number_3]->arg,
+ argv[idx_month_2]->arg, argv[idx_number_4]->arg);
+}
+
+DEFUN (send_lifetime_month_day_month_day,
+ send_lifetime_month_day_month_day_cmd,
+ "send-lifetime HH:MM:SS MONTH (1-31) (1993-2035) HH:MM:SS MONTH (1-31) (1993-2035)",
+ "Set send lifetime of the key\n"
+ "Time to start\n"
+ "Month of the year to start\n"
+ "Day of th month to start\n"
+ "Year to start\n"
+ "Time to expire\n"
+ "Month of the year to expire\n"
+ "Day of th month to expire\n"
+ "Year to expire\n")
+{
+ int idx_hhmmss = 1;
+ int idx_month = 2;
+ int idx_number = 3;
+ int idx_number_2 = 4;
+ int idx_hhmmss_2 = 5;
+ int idx_month_2 = 6;
+ int idx_number_3 = 7;
+ int idx_number_4 = 8;
+ VTY_DECLVAR_CONTEXT_SUB(key, key);
+
+ return key_lifetime_set(
+ vty, &key->send, argv[idx_hhmmss]->arg, argv[idx_number]->arg,
+ argv[idx_month]->arg, argv[idx_number_2]->arg,
+ argv[idx_hhmmss_2]->arg, argv[idx_number_3]->arg,
+ argv[idx_month_2]->arg, argv[idx_number_4]->arg);
+}
+
+DEFUN (send_lifetime_infinite_day_month,
+ send_lifetime_infinite_day_month_cmd,
+ "send-lifetime HH:MM:SS (1-31) MONTH (1993-2035) infinite",
+ "Set send lifetime of the key\n"
+ "Time to start\n"
+ "Day of th month to start\n"
+ "Month of the year to start\n"
+ "Year to start\n"
+ "Never expires\n")
+{
+ int idx_hhmmss = 1;
+ int idx_number = 2;
+ int idx_month = 3;
+ int idx_number_2 = 4;
+ VTY_DECLVAR_CONTEXT_SUB(key, key);
+
+ return key_lifetime_infinite_set(
+ vty, &key->send, argv[idx_hhmmss]->arg, argv[idx_number]->arg,
+ argv[idx_month]->arg, argv[idx_number_2]->arg);
+}
+
+DEFUN (send_lifetime_infinite_month_day,
+ send_lifetime_infinite_month_day_cmd,
+ "send-lifetime HH:MM:SS MONTH (1-31) (1993-2035) infinite",
+ "Set send lifetime of the key\n"
+ "Time to start\n"
+ "Month of the year to start\n"
+ "Day of th month to start\n"
+ "Year to start\n"
+ "Never expires\n")
+{
+ int idx_hhmmss = 1;
+ int idx_month = 2;
+ int idx_number = 3;
+ int idx_number_2 = 4;
+ VTY_DECLVAR_CONTEXT_SUB(key, key);
+
+ return key_lifetime_infinite_set(
+ vty, &key->send, argv[idx_hhmmss]->arg, argv[idx_number]->arg,
+ argv[idx_month]->arg, argv[idx_number_2]->arg);
+}
+
+DEFUN (send_lifetime_duration_day_month,
+ send_lifetime_duration_day_month_cmd,
+ "send-lifetime HH:MM:SS (1-31) MONTH (1993-2035) duration (1-2147483646)",
+ "Set send lifetime of the key\n"
+ "Time to start\n"
+ "Day of th month to start\n"
+ "Month of the year to start\n"
+ "Year to start\n"
+ "Duration of the key\n"
+ "Duration seconds\n")
+{
+ int idx_hhmmss = 1;
+ int idx_number = 2;
+ int idx_month = 3;
+ int idx_number_2 = 4;
+ int idx_number_3 = 6;
+ VTY_DECLVAR_CONTEXT_SUB(key, key);
+
+ return key_lifetime_duration_set(
+ vty, &key->send, argv[idx_hhmmss]->arg, argv[idx_number]->arg,
+ argv[idx_month]->arg, argv[idx_number_2]->arg,
+ argv[idx_number_3]->arg);
+}
+
+DEFUN (send_lifetime_duration_month_day,
+ send_lifetime_duration_month_day_cmd,
+ "send-lifetime HH:MM:SS MONTH (1-31) (1993-2035) duration (1-2147483646)",
+ "Set send lifetime of the key\n"
+ "Time to start\n"
+ "Month of the year to start\n"
+ "Day of th month to start\n"
+ "Year to start\n"
+ "Duration of the key\n"
+ "Duration seconds\n")
+{
+ int idx_hhmmss = 1;
+ int idx_month = 2;
+ int idx_number = 3;
+ int idx_number_2 = 4;
+ int idx_number_3 = 6;
+ VTY_DECLVAR_CONTEXT_SUB(key, key);
+
+ return key_lifetime_duration_set(
+ vty, &key->send, argv[idx_hhmmss]->arg, argv[idx_number]->arg,
+ argv[idx_month]->arg, argv[idx_number_2]->arg,
+ argv[idx_number_3]->arg);
+}
+
+DEFUN (no_send_lifetime,
+ no_send_lifetime_cmd,
+ "no send-lifetime",
+ NO_STR
+ "Unset send-lifetime\n")
+{
+ VTY_DECLVAR_CONTEXT_SUB(key, key);
+
+ if (key->send.start)
+ key->send.start = 0;
+ if (key->send.end)
+ key->send.end = 0;
+ if (key->send.duration)
+ key->send.duration = 0;
+
+ return CMD_SUCCESS;
+}
+
+static int keychain_config_write(struct vty *vty);
+static struct cmd_node keychain_node = {
+ .name = "keychain",
+ .node = KEYCHAIN_NODE,
+ .parent_node = CONFIG_NODE,
+ .prompt = "%s(config-keychain)# ",
+ .config_write = keychain_config_write,
+};
+
+static struct cmd_node keychain_key_node = {
+ .name = "keychain key",
+ .node = KEYCHAIN_KEY_NODE,
+ .parent_node = KEYCHAIN_NODE,
+ .prompt = "%s(config-keychain-key)# ",
+};
+
+static int keychain_strftime(char *buf, int bufsiz, time_t *time)
+{
+ struct tm tm;
+ size_t len;
+
+ localtime_r(time, &tm);
+
+ len = strftime(buf, bufsiz, "%T %b %d %Y", &tm);
+
+ return len;
+}
+
+static int keychain_config_write(struct vty *vty)
+{
+ struct keychain *keychain;
+ struct key *key;
+ struct listnode *node;
+ struct listnode *knode;
+ char buf[BUFSIZ];
+
+ for (ALL_LIST_ELEMENTS_RO(keychain_list, node, keychain)) {
+ vty_out(vty, "key chain %s\n", keychain->name);
+
+ for (ALL_LIST_ELEMENTS_RO(keychain->key, knode, key)) {
+ vty_out(vty, " key %d\n", key->index);
+
+ if (key->string)
+ vty_out(vty, " key-string %s\n", key->string);
+
+ if (key->hash_algo != KEYCHAIN_ALGO_NULL)
+ vty_out(vty, " cryptographic-algorithm %s\n",
+ keychain_get_algo_name_by_id(
+ key->hash_algo));
+
+ if (key->accept.start) {
+ keychain_strftime(buf, BUFSIZ,
+ &key->accept.start);
+ vty_out(vty, " accept-lifetime %s", buf);
+
+ if (key->accept.end == -1)
+ vty_out(vty, " infinite");
+ else if (key->accept.duration)
+ vty_out(vty, " duration %ld",
+ (long)(key->accept.end
+ - key->accept.start));
+ else {
+ keychain_strftime(buf, BUFSIZ,
+ &key->accept.end);
+ vty_out(vty, " %s", buf);
+ }
+ vty_out(vty, "\n");
+ }
+
+ if (key->send.start) {
+ keychain_strftime(buf, BUFSIZ,
+ &key->send.start);
+ vty_out(vty, " send-lifetime %s", buf);
+
+ if (key->send.end == -1)
+ vty_out(vty, " infinite");
+ else if (key->send.duration)
+ vty_out(vty, " duration %ld",
+ (long)(key->send.end
+ - key->send.start));
+ else {
+ keychain_strftime(buf, BUFSIZ,
+ &key->send.end);
+ vty_out(vty, " %s", buf);
+ }
+ vty_out(vty, "\n");
+ }
+
+ vty_out(vty, " exit\n");
+ }
+ vty_out(vty, "exit\n");
+ vty_out(vty, "!\n");
+ }
+
+ return 0;
+}
+
+
+static void keychain_active_config(vector comps, struct cmd_token *token)
+{
+ struct keychain *keychain;
+ struct listnode *node;
+
+ for (ALL_LIST_ELEMENTS_RO(keychain_list, node, keychain))
+ vector_set(comps, XSTRDUP(MTYPE_COMPLETION, keychain->name));
+}
+
+static const struct cmd_variable_handler keychain_var_handlers[] = {
+ {.varname = "key_chain", .completions = keychain_active_config},
+ {.tokenname = "KEYCHAIN_NAME", .completions = keychain_active_config},
+ {.tokenname = "KCHAIN_NAME", .completions = keychain_active_config},
+ {.completions = NULL}
+};
+
+void keychain_init(void)
+{
+ keychain_list = list_new();
+
+ /* Register handler for keychain auto config support */
+ cmd_variable_handler_register(keychain_var_handlers);
+ install_node(&keychain_node);
+ install_node(&keychain_key_node);
+
+ install_default(KEYCHAIN_NODE);
+ install_default(KEYCHAIN_KEY_NODE);
+
+ install_element(CONFIG_NODE, &key_chain_cmd);
+ install_element(CONFIG_NODE, &no_key_chain_cmd);
+ install_element(KEYCHAIN_NODE, &key_cmd);
+ install_element(KEYCHAIN_NODE, &no_key_cmd);
+
+ install_element(KEYCHAIN_NODE, &key_chain_cmd);
+ install_element(KEYCHAIN_NODE, &no_key_chain_cmd);
+
+ install_element(KEYCHAIN_KEY_NODE, &key_string_cmd);
+ install_element(KEYCHAIN_KEY_NODE, &no_key_string_cmd);
+
+ install_element(KEYCHAIN_KEY_NODE, &key_chain_cmd);
+ install_element(KEYCHAIN_KEY_NODE, &no_key_chain_cmd);
+
+ install_element(KEYCHAIN_KEY_NODE, &key_cmd);
+ install_element(KEYCHAIN_KEY_NODE, &no_key_cmd);
+
+ install_element(KEYCHAIN_KEY_NODE,
+ &accept_lifetime_day_month_day_month_cmd);
+ install_element(KEYCHAIN_KEY_NODE,
+ &accept_lifetime_day_month_month_day_cmd);
+ install_element(KEYCHAIN_KEY_NODE,
+ &accept_lifetime_month_day_day_month_cmd);
+ install_element(KEYCHAIN_KEY_NODE,
+ &accept_lifetime_month_day_month_day_cmd);
+ install_element(KEYCHAIN_KEY_NODE,
+ &accept_lifetime_infinite_day_month_cmd);
+ install_element(KEYCHAIN_KEY_NODE,
+ &accept_lifetime_infinite_month_day_cmd);
+ install_element(KEYCHAIN_KEY_NODE,
+ &accept_lifetime_duration_day_month_cmd);
+ install_element(KEYCHAIN_KEY_NODE,
+ &accept_lifetime_duration_month_day_cmd);
+ install_element(KEYCHAIN_KEY_NODE, &no_accept_lifetime_cmd);
+
+ install_element(KEYCHAIN_KEY_NODE,
+ &send_lifetime_day_month_day_month_cmd);
+ install_element(KEYCHAIN_KEY_NODE,
+ &send_lifetime_day_month_month_day_cmd);
+ install_element(KEYCHAIN_KEY_NODE,
+ &send_lifetime_month_day_day_month_cmd);
+ install_element(KEYCHAIN_KEY_NODE,
+ &send_lifetime_month_day_month_day_cmd);
+ install_element(KEYCHAIN_KEY_NODE,
+ &send_lifetime_infinite_day_month_cmd);
+ install_element(KEYCHAIN_KEY_NODE,
+ &send_lifetime_infinite_month_day_cmd);
+ install_element(KEYCHAIN_KEY_NODE,
+ &send_lifetime_duration_day_month_cmd);
+ install_element(KEYCHAIN_KEY_NODE,
+ &send_lifetime_duration_month_day_cmd);
+ install_element(KEYCHAIN_KEY_NODE, &no_send_lifetime_cmd);
+ install_element(KEYCHAIN_KEY_NODE, &cryptographic_algorithm_cmd);
+ install_element(KEYCHAIN_KEY_NODE, &no_cryptographic_algorithm_cmd);
+}
diff --git a/lib/keychain.h b/lib/keychain.h
new file mode 100644
index 0000000..71319d9
--- /dev/null
+++ b/lib/keychain.h
@@ -0,0 +1,109 @@
+/* key-chain for authentication.
+ * Copyright (C) 2000 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra 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 _ZEBRA_KEYCHAIN_H
+#define _ZEBRA_KEYCHAIN_H
+
+#include "qobj.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum keychain_hash_algo {
+ KEYCHAIN_ALGO_NULL,
+ KEYCHAIN_ALGO_MD5,
+ KEYCHAIN_ALGO_HMAC_SHA1,
+ KEYCHAIN_ALGO_HMAC_SHA256,
+ KEYCHAIN_ALGO_HMAC_SHA384,
+ KEYCHAIN_ALGO_HMAC_SHA512,
+ KEYCHAIN_ALGO_MAX
+};
+
+#define KEYCHAIN_MD5_HASH_SIZE 16
+#define KEYCHAIN_HMAC_SHA1_HASH_SIZE 20
+#define KEYCHAIN_HMAC_SHA256_HASH_SIZE 32
+#define KEYCHAIN_HMAC_SHA384_HASH_SIZE 48
+#define KEYCHAIN_HMAC_SHA512_HASH_SIZE 64
+#define KEYCHAIN_MAX_HASH_SIZE 64
+
+#define KEYCHAIN_ALGO_MD5_INTERNAL_BLK_SIZE 16
+#define KEYCHAIN_ALGO_SHA1_INTERNAL_BLK_SIZE 64
+#define KEYCHAIN_ALGO_SHA256_INTERNAL_BLK_SIZE 64
+#define KEYCHAIN_ALGO_SHA384_INTERNAL_BLK_SIZE 128
+#define KEYCHAIN_ALGO_SHA512_INTERNAL_BLK_SIZE 128
+#define KEYCHAIN_ALGO_MAX_INTERNAL_BLK_SIZE 128
+
+struct keychain_algo_info {
+ enum keychain_hash_algo key;
+ const char *name;
+ uint16_t length;
+ uint16_t block;
+ const char *desc;
+};
+
+extern const struct keychain_algo_info algo_info[];
+uint16_t keychain_get_block_size(enum keychain_hash_algo key);
+uint16_t keychain_get_hash_len(enum keychain_hash_algo key);
+const char *keychain_get_description(enum keychain_hash_algo key);
+struct keychain_algo_info
+keychain_get_hash_algo_info(enum keychain_hash_algo key);
+enum keychain_hash_algo keychain_get_algo_id_by_name(const char *name);
+const char *keychain_get_algo_name_by_id(enum keychain_hash_algo key);
+
+struct keychain {
+ char *name;
+
+ struct list *key;
+
+ QOBJ_FIELDS;
+};
+DECLARE_QOBJ_TYPE(keychain);
+
+struct key_range {
+ time_t start;
+ time_t end;
+
+ uint8_t duration;
+};
+
+struct key {
+ uint32_t index;
+
+ char *string;
+ enum keychain_hash_algo hash_algo;
+ struct key_range send;
+ struct key_range accept;
+
+ QOBJ_FIELDS;
+};
+DECLARE_QOBJ_TYPE(key);
+
+extern void keychain_init(void);
+extern struct keychain *keychain_lookup(const char *);
+extern struct key *key_lookup_for_accept(const struct keychain *, uint32_t);
+extern struct key *key_match_for_accept(const struct keychain *, const char *);
+extern struct key *key_lookup_for_send(const struct keychain *);
+const char *keychain_algo_str(enum keychain_hash_algo hash_algo);
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ZEBRA_KEYCHAIN_H */
diff --git a/lib/ldp_sync.c b/lib/ldp_sync.c
new file mode 100644
index 0000000..8912d15
--- /dev/null
+++ b/lib/ldp_sync.c
@@ -0,0 +1,91 @@
+/*
+ * ldp_sync.c: LDP-SYNC handling routines
+ * Copyright (C) 2020 Volta Networks, Inc.
+ *
+ * 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 "command.h"
+#include "memory.h"
+#include "prefix.h"
+#include "log.h"
+#include "thread.h"
+#include "stream.h"
+#include "zclient.h"
+#include "table.h"
+#include "vty.h"
+#include "ldp_sync.h"
+
+/* Library code */
+DEFINE_MTYPE_STATIC(LIB, LDP_SYNC_INFO, "LDP SYNC info");
+
+/*
+ * ldp_sync_info_create - Allocate the LDP_SYNC information
+ */
+struct ldp_sync_info *ldp_sync_info_create(void)
+{
+ struct ldp_sync_info *ldp_sync_info;
+
+ ldp_sync_info = XCALLOC(MTYPE_LDP_SYNC_INFO,
+ sizeof(struct ldp_sync_info));
+ assert(ldp_sync_info);
+
+ ldp_sync_info->flags = 0;
+ ldp_sync_info->enabled = LDP_IGP_SYNC_DEFAULT;
+ ldp_sync_info->state = LDP_IGP_SYNC_STATE_NOT_REQUIRED;
+ ldp_sync_info->holddown = LDP_IGP_SYNC_HOLDDOWN_DEFAULT;
+ ldp_sync_info->t_holddown = NULL;
+ return ldp_sync_info;
+}
+
+/*
+ * ldp_sync_info_free - Free the LDP_SYNC information.
+ */
+void ldp_sync_info_free(struct ldp_sync_info **ldp_sync_info)
+{
+ if (*ldp_sync_info)
+ XFREE(MTYPE_LDP_SYNC_INFO, *ldp_sync_info);
+}
+
+bool ldp_sync_if_is_enabled(struct ldp_sync_info *ldp_sync_info)
+{
+ /* return true if LDP-SYNC is configured on this interface */
+ if (ldp_sync_info &&
+ ldp_sync_info->enabled == LDP_IGP_SYNC_ENABLED &&
+ ldp_sync_info->state == LDP_IGP_SYNC_STATE_REQUIRED_NOT_UP)
+ return true;
+
+ return false;
+}
+
+bool ldp_sync_if_down(struct ldp_sync_info *ldp_sync_info)
+{
+ /* Stop LDP-SYNC on this interface:
+ * if holddown timer is running stop it
+ * update state
+ */
+ if (ldp_sync_info && ldp_sync_info->enabled == LDP_IGP_SYNC_ENABLED) {
+ THREAD_OFF(ldp_sync_info->t_holddown);
+
+ if (ldp_sync_info->state == LDP_IGP_SYNC_STATE_REQUIRED_UP)
+ ldp_sync_info->state =
+ LDP_IGP_SYNC_STATE_REQUIRED_NOT_UP;
+ return true;
+ }
+
+ return false;
+}
diff --git a/lib/ldp_sync.h b/lib/ldp_sync.h
new file mode 100644
index 0000000..0429e17
--- /dev/null
+++ b/lib/ldp_sync.h
@@ -0,0 +1,82 @@
+/*
+ * Defines and structures common to LDP-Sync for OSPFv2 and OSPFv3 and ISIS
+ * Copyright (C) 2020 Volta Networks, Inc.
+ *
+ * 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 _LIBLDPSYNC_H
+#define _LIBLDPSYNC_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* LDP-IGP Sync values */
+#define LDP_SYNC_FLAG_ENABLE (1 << 0) /* LDP-SYNC enabled */
+#define LDP_SYNC_FLAG_HOLDDOWN (1 << 1) /* Holddown timer enabled */
+#define LDP_SYNC_FLAG_IF_CONFIG (1 << 2) /* LDP-SYNC enabled on interface */
+#define LDP_SYNC_FLAG_SET_METRIC (1 << 3) /* Metric has been set on ISIS intf */
+
+#define LDP_IGP_SYNC_DEFAULT 0
+#define LDP_IGP_SYNC_ENABLED 1
+
+#define LDP_IGP_SYNC_STATE_NOT_REQUIRED 0
+#define LDP_IGP_SYNC_STATE_REQUIRED_NOT_UP 1
+#define LDP_IGP_SYNC_STATE_REQUIRED_UP 2
+
+#define LDP_IGP_SYNC_HOLDDOWN_DEFAULT 0
+
+/* LDP-IGP Sync structures */
+struct ldp_sync_info_cmd {
+ uint16_t flags;
+ uint16_t holddown; /* timer value */
+};
+
+struct ldp_sync_info {
+ uint16_t flags; /* indicate if set on interface or globally */
+ uint8_t enabled; /* enabled */
+ uint8_t state; /* running state */
+ uint16_t holddown; /* timer value */
+ struct thread *t_holddown; /* holddown timer*/
+ uint32_t metric[2]; /* isis interface metric */
+};
+
+/* Prototypes. */
+extern struct ldp_sync_info *ldp_sync_info_create(void);
+extern bool ldp_sync_if_is_enabled(struct ldp_sync_info *ldp_sync_info);
+extern bool ldp_sync_if_down(struct ldp_sync_info *ldp_sync_info);
+extern void ldp_sync_info_free(struct ldp_sync_info **ldp_sync_info);
+
+struct ldp_igp_sync_announce {
+ int proto;
+};
+
+struct ldp_igp_sync_if_state {
+ ifindex_t ifindex;
+ bool sync_start;
+};
+
+struct ldp_igp_sync_if_state_req {
+ int proto;
+ ifindex_t ifindex;
+ char name[INTERFACE_NAMSIZ];
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LIBLDPSYNC_H */
diff --git a/lib/lib_errors.c b/lib/lib_errors.c
new file mode 100644
index 0000000..a658e4c
--- /dev/null
+++ b/lib/lib_errors.c
@@ -0,0 +1,393 @@
+/*
+ * Library-specific error messages.
+ * Copyright (C) 2018 Cumulus Networks, Inc.
+ * Donald Sharp
+ *
+ * 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
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "lib_errors.h"
+
+/* clang-format off */
+static struct log_ref ferr_lib_warn[] = {
+ {
+ .code = EC_LIB_SNMP,
+ .title = "SNMP has discovered a warning",
+ .description = "The SNMP AgentX library has returned a warning that we should report to the end user",
+ .suggestion = "Gather Log data and open an Issue.",
+ },
+ {
+ .code = EC_LIB_STREAM,
+ .title = "The stream subsystem has encountered an error",
+ .description = "During sanity checking stream.c has detected an error in the data associated with a particular stream",
+ .suggestion = "Gather log data and open an Issue, restart FRR",
+ },
+ {
+ .code = EC_LIB_LINUX_NS,
+ .title = "The Linux namespace subsystem has encountered a parsing error",
+ .description = "During system startup an invalid parameter for the namespace was give to FRR",
+ .suggestion = "Gather log data and open an Issue. restart FRR",
+ },
+ {
+ .code = EC_LIB_SLOW_THREAD_CPU,
+ .title = "The Event subsystem has detected a slow cpu time process",
+ .description = "The Event subsystem has detected a slow process, this typically indicates that FRR is having trouble completing work in a timely manner. This can be either a misconfiguration, bug, or some combination thereof. In this case total CPU time was over 5 seconds. Which indicates that FRR is very busy doing some work and should be addressed",
+ .suggestion = "Gather log data and open an Issue",
+ },
+ {
+ .code = EC_LIB_SLOW_THREAD_WALL,
+ .title = "The Event subsystem has detected a slow wall time process",
+ .description = "The Event subsystem has detected a slow process, this typically indicates that FRR is having trouble completing work in a timely manner. This can be either a misconfiguration, bug or some combination thereof. In this case total WALL time was over 5 seconds. Which indicates that FRR might be having trouble being scheduled or some system call is delaying",
+ .suggestion = "Gather log data and open an Issue",
+ },
+ {
+ .code = EC_LIB_STARVE_THREAD,
+ .title = "The Event subsystem has detected a thread starvation issue",
+ .description = "The event subsystem has detected a thread starvation issue. This typically indicates that the system FRR is running on is heavily loaded and this load might be impacting FRR's ability to handle events in a timely fashion",
+ .suggestion = "Gather log data and open an Issue",
+ },
+ {
+ .code = EC_LIB_NO_THREAD,
+ .title = "The Event subsystem has detected an internal FD problem",
+ .description = "The Event subsystem has detected a file descriptor read/write event without an associated handling function. This is a bug, please collect log data and open an issue.",
+ .suggestion = "Gather log data and open an Issue",
+ },
+ {
+ .code = EC_LIB_TIMER_TOO_LONG,
+ .title = "The Event subsystem has detected an internal timer that is scheduled to pop in greater than one year",
+ .description = "The Event subsystem has detected a timer being started that will pop in a timer that is greater than one year. This is a bug, please collect log data and open an issue.",
+ .suggestion = "Gather log data and open an Issue",
+ },
+ {
+ .code = EC_LIB_RMAP_RECURSION_LIMIT,
+ .title = "Reached the Route-Map Recursion Limit",
+ .description = "The Route-Map subsystem has detected a route-map depth of RMAP_RECURSION_LIMIT and has stopped processing",
+ .suggestion = "Re-work the Route-Map in question to not have so many route-map statements, or recompile FRR with a higher limit",
+ },
+ {
+ .code = EC_LIB_BACKUP_CONFIG,
+ .title = "Unable to open configuration file",
+ .description = "The config subsystem attempted to read in it's configuration file which failed, so we are falling back to the backup config file to see if it is available",
+ .suggestion = "Create configuration file",
+ },
+ {
+ .code = EC_LIB_VRF_LENGTH,
+ .title = "The VRF subsystem has encountered a parsing error",
+ .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 = "This is a bug; please report it"
+ },
+ {
+ .code = EC_LIB_NB_CB_CONFIG_VALIDATE,
+ .title = "A northbound configuration callback has failed in the VALIDATE phase",
+ .description = "A callback used to process a configuration change has returned a validation error",
+ .suggestion = "The provided configuration is invalid. Fix any inconsistency and try again.",
+ },
+ {
+ .code = EC_LIB_NB_CB_CONFIG_PREPARE,
+ .title = "A northbound configuration callback has failed in the PREPARE phase",
+ .description = "A callback used to process a configuration change has returned a resource allocation error",
+ .suggestion = "The system might be running out of resources. Check the log for more details.",
+ },
+ {
+ .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_OPERATIONAL_DATA,
+ .title = "Failure to obtain operational data",
+ .description = "The northbound subsystem failed to obtain YANG-modeled operational data",
+ .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,
+ },
+};
+
+static struct log_ref ferr_lib_err[] = {
+ {
+ .code = EC_LIB_PRIVILEGES,
+ .title = "Failure to raise or lower privileges",
+ .description = "FRR attempted to raise or lower its privileges and was unable to do so",
+ .suggestion = "Ensure that you are running FRR as the frr user and that the user has sufficient privileges to properly access root privileges"
+ },
+ {
+ .code = EC_LIB_VRF_START,
+ .title = "VRF Failure on Start",
+ .description = "Upon startup FRR failed to properly initialize and startup the VRF subsystem",
+ .suggestion = "Ensure that there is sufficient memory to start processes and restart FRR",
+ },
+ {
+ .code = EC_LIB_SOCKET,
+ .title = "Socket Error",
+ .description = "When attempting to access a socket a system error has occurred and we were unable to properly complete the request",
+ .suggestion = "Ensure that there are sufficient system resources available and ensure that the frr user has sufficient permissions to work. If necessary open an Issue",
+ },
+ {
+ .code = EC_LIB_ZAPI_MISSMATCH,
+ .title = "ZAPI Error",
+ .description = "A version miss-match has been detected between zebra and client protocol",
+ .suggestion = "Two different versions of FRR have been installed and the install is not properly setup. Completely stop FRR, remove it from the system and reinstall. Typically only developers should see this issue."
+ },
+ {
+ .code = EC_LIB_ZAPI_ENCODE,
+ .title = "ZAPI Error",
+ .description = "The ZAPI subsystem has detected an encoding issue, between zebra and a client protocol",
+ .suggestion = "Gather data and open an Issue, also Restart FRR"
+ },
+ {
+ .code = EC_LIB_ZAPI_SOCKET,
+ .title = "ZAPI Error",
+ .description = "The ZAPI subsystem has detected a socket error between zebra and a client",
+ .suggestion = "Restart FRR"
+ },
+ {
+ .code = EC_LIB_SYSTEM_CALL,
+ .title = "System Call Error",
+ .description = "FRR has detected a error from using a vital system call and has probably already exited",
+ .suggestion = "Ensure permissions are correct for FRR files, users and groups are correct. Additionally check that sufficient system resources are available."
+ },
+ {
+ .code = EC_LIB_VTY,
+ .title = "VTY Subsystem Error",
+ .description = "FRR has detected a problem with the specified configuration file",
+ .suggestion = "Ensure configuration file exists and has correct permissions for operations Additionally ensure that all config lines are correct as well",
+ },
+ {
+ .code = EC_LIB_INTERFACE,
+ .title = "Interface Subsystem Error",
+ .description = "FRR has detected a problem with interface data from the kernel as it deviates from what we would expect to happen via normal netlink messaging",
+ .suggestion = "Open an Issue with all relevant log files and restart FRR"
+ },
+ {
+ .code = EC_LIB_NS,
+ .title = "NameSpace Subsystem Error",
+ .description = "FRR has detected a problem with NameSpace data from the kernel as it deviates from what we would expect to happen via normal kernel messaging",
+ .suggestion = "Open an Issue with all relevant log files and restart FRR"
+ },
+ {
+ .code = EC_LIB_DEVELOPMENT,
+ .title = "Developmental Escape Error",
+ .description = "FRR has detected an issue where new development has not properly updated all code paths.",
+ .suggestion = "Open an Issue with all relevant log files"
+ },
+ {
+ .code = EC_LIB_ZMQ,
+ .title = "ZMQ Subsystem Error",
+ .description = "FRR has detected an issue with the Zero MQ subsystem and ZeroMQ is not working properly now",
+ .suggestion = "Open an Issue with all relevant log files and restart FRR"
+ },
+ {
+ .code = EC_LIB_UNAVAILABLE,
+ .title = "Feature or system unavailable",
+ .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 = "This is a bug; please report it"
+ },
+ {
+ .code = EC_LIB_NB_CB_INVALID_PRIO,
+ .title = "Northbound callback has an invalid priority",
+ .description = "The northbound subsystem, during initialization, has detected a callback whose priority is invalid",
+ .suggestion = "This is a bug; please report it"
+ },
+ {
+ .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 = "This is a bug; please report it"
+ },
+ {
+ .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 = EC_LIB_GRPC_INIT,
+ .title = "gRPC initialization error",
+ .description = "Upon startup FRR failed to properly initialize and startup the gRPC northbound plugin",
+ .suggestion = "Check if the gRPC libraries are installed correctly in the system.",
+ },
+ {
+ .code = EC_LIB_NB_CB_CONFIG_ABORT,
+ .title = "A northbound configuration callback has failed in the ABORT phase",
+ .description = "A callback used to process a configuration change has returned an error while trying to abort a change",
+ .suggestion = "Gather log data and open an Issue.",
+ },
+ {
+ .code = EC_LIB_NB_CB_CONFIG_APPLY,
+ .title = "A northbound configuration callback has failed in the APPLY phase",
+ .description = "A callback used to process a configuration change has returned an error while applying the changes",
+ .suggestion = "Gather log data and open an Issue.",
+ },
+ {
+ .code = EC_LIB_RESOLVER,
+ .title = "DNS Resolution",
+ .description = "An error was detected while attempting to resolve a hostname",
+ .suggestion = "Ensure that DNS is working properly and the hostname is configured in dns. If you are still seeing this error, open an issue"
+ },
+ {
+ .code = END_FERR,
+ }
+};
+/* clang-format on */
+
+void lib_error_init(void)
+{
+ log_ref_add(ferr_lib_warn);
+ log_ref_add(ferr_lib_err);
+}
diff --git a/lib/lib_errors.h b/lib/lib_errors.h
new file mode 100644
index 0000000..91f206f
--- /dev/null
+++ b/lib/lib_errors.h
@@ -0,0 +1,99 @@
+/*
+ * Library-specific error messages.
+ * Copyright (C) 2018 Cumulus Networks, Inc.
+ * Donald Sharp
+ *
+ * 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 __LIB_ERRORS_H__
+#define __LIB_ERRORS_H__
+
+#include "lib/ferr.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum lib_log_refs {
+ EC_LIB_PRIVILEGES = LIB_FERR_START,
+ EC_LIB_VRF_START,
+ EC_LIB_SOCKET,
+ EC_LIB_ZAPI_MISSMATCH,
+ EC_LIB_ZAPI_ENCODE,
+ EC_LIB_ZAPI_SOCKET,
+ EC_LIB_SYSTEM_CALL,
+ EC_LIB_VTY,
+ EC_LIB_INTERFACE,
+ EC_LIB_NS,
+ EC_LIB_DEVELOPMENT,
+ EC_LIB_ZMQ,
+ EC_LIB_UNAVAILABLE,
+ EC_LIB_SNMP,
+ EC_LIB_STREAM,
+ EC_LIB_LINUX_NS,
+ EC_LIB_SLOW_THREAD_CPU,
+ EC_LIB_SLOW_THREAD_WALL,
+ EC_LIB_STARVE_THREAD,
+ EC_LIB_NO_THREAD,
+ EC_LIB_TIMER_TOO_LONG,
+ 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_VALIDATE,
+ EC_LIB_NB_CB_CONFIG_PREPARE,
+ EC_LIB_NB_CB_CONFIG_ABORT,
+ EC_LIB_NB_CB_CONFIG_APPLY,
+ EC_LIB_NB_CB_STATE,
+ EC_LIB_NB_CB_RPC,
+ EC_LIB_NB_CANDIDATE_INVALID,
+ EC_LIB_NB_CANDIDATE_EDIT_ERROR,
+ EC_LIB_NB_OPERATIONAL_DATA,
+ 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,
+ EC_LIB_GRPC_INIT,
+ EC_LIB_ID_CONSISTENCY,
+ EC_LIB_ID_EXHAUST,
+ EC_LIB_RESOLVER,
+};
+
+extern void lib_error_init(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/lib_vty.c b/lib/lib_vty.c
new file mode 100644
index 0000000..85816c5
--- /dev/null
+++ b/lib/lib_vty.c
@@ -0,0 +1,347 @@
+/*
+ * Assorted library VTY commands
+ *
+ * Copyright (C) 1998 Kunihiro Ishiguro
+ * Copyright (C) 2016-2017 David Lamparter for NetDEF, Inc.
+ *
+ * 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>
+/* malloc.h is generally obsolete, however GNU Libc mallinfo wants it. */
+#ifdef HAVE_MALLOC_H
+#include <malloc.h>
+#endif
+#ifdef HAVE_MALLOC_MALLOC_H
+#include <malloc/malloc.h>
+#endif
+#include <dlfcn.h>
+#ifdef HAVE_LINK_H
+#include <link.h>
+#endif
+
+#include "log.h"
+#include "memory.h"
+#include "module.h"
+#include "defaults.h"
+#include "lib_vty.h"
+#include "northbound_cli.h"
+
+/* Looking up memory status from vty interface. */
+#include "vector.h"
+#include "vty.h"
+#include "command.h"
+
+#if defined(HAVE_MALLINFO2) || defined(HAVE_MALLINFO)
+static int show_memory_mallinfo(struct vty *vty)
+{
+#if defined(HAVE_MALLINFO2)
+ struct mallinfo2 minfo = mallinfo2();
+#elif defined(HAVE_MALLINFO)
+ struct mallinfo minfo = mallinfo();
+#endif
+ char buf[MTYPE_MEMSTR_LEN];
+
+ vty_out(vty, "System allocator statistics:\n");
+ vty_out(vty, " Total heap allocated: %s\n",
+ mtype_memstr(buf, MTYPE_MEMSTR_LEN, minfo.arena));
+ vty_out(vty, " Holding block headers: %s\n",
+ mtype_memstr(buf, MTYPE_MEMSTR_LEN, minfo.hblkhd));
+ vty_out(vty, " Used small blocks: %s\n",
+ mtype_memstr(buf, MTYPE_MEMSTR_LEN, minfo.usmblks));
+ vty_out(vty, " Used ordinary blocks: %s\n",
+ mtype_memstr(buf, MTYPE_MEMSTR_LEN, minfo.uordblks));
+ vty_out(vty, " Free small blocks: %s\n",
+ mtype_memstr(buf, MTYPE_MEMSTR_LEN, minfo.fsmblks));
+ vty_out(vty, " Free ordinary blocks: %s\n",
+ mtype_memstr(buf, MTYPE_MEMSTR_LEN, minfo.fordblks));
+ vty_out(vty, " Ordinary blocks: %ld\n",
+ (unsigned long)minfo.ordblks);
+ vty_out(vty, " Small blocks: %ld\n",
+ (unsigned long)minfo.smblks);
+ vty_out(vty, " Holding blocks: %ld\n",
+ (unsigned long)minfo.hblks);
+ vty_out(vty, "(see system documentation for 'mallinfo' for meaning)\n");
+ return 1;
+}
+#endif /* HAVE_MALLINFO */
+
+static int qmem_walker(void *arg, struct memgroup *mg, struct memtype *mt)
+{
+ struct vty *vty = arg;
+ if (!mt) {
+ vty_out(vty, "--- qmem %s ---\n", mg->name);
+ vty_out(vty, "%-30s: %8s %-8s%s %8s %9s\n",
+ "Type", "Current#", " Size",
+#ifdef HAVE_MALLOC_USABLE_SIZE
+ " Total",
+#else
+ "",
+#endif
+ "Max#",
+#ifdef HAVE_MALLOC_USABLE_SIZE
+ "MaxBytes"
+#else
+ ""
+#endif
+ );
+ } else {
+ if (mt->n_max != 0) {
+ char size[32];
+ snprintf(size, sizeof(size), "%6zu", mt->size);
+#ifdef HAVE_MALLOC_USABLE_SIZE
+#define TSTR " %9zu"
+#define TARG , mt->total
+#define TARG2 , mt->max_size
+#else
+#define TSTR ""
+#define TARG
+#define TARG2
+#endif
+ vty_out(vty, "%-30s: %8zu %-8s"TSTR" %8zu"TSTR"\n",
+ mt->name,
+ mt->n_alloc,
+ mt->size == 0 ? ""
+ : mt->size == SIZE_VAR
+ ? "variable"
+ : size
+ TARG,
+ mt->n_max
+ TARG2);
+ }
+ }
+ return 0;
+}
+
+
+DEFUN_NOSH (show_memory,
+ show_memory_cmd,
+ "show memory",
+ "Show running system information\n"
+ "Memory statistics\n")
+{
+#ifdef HAVE_MALLINFO
+ show_memory_mallinfo(vty);
+#endif /* HAVE_MALLINFO */
+
+ qmem_walk(qmem_walker, vty);
+ return CMD_SUCCESS;
+}
+
+DEFUN_NOSH (show_modules,
+ show_modules_cmd,
+ "show modules",
+ "Show running system information\n"
+ "Loaded modules\n")
+{
+ struct frrmod_runtime *plug = frrmod_list;
+
+ vty_out(vty, "%-12s %-25s %s\n\n", "Module Name", "Version",
+ "Description");
+ while (plug) {
+ const struct frrmod_info *i = plug->info;
+
+ vty_out(vty, "%-12s %-25s %s\n", i->name, i->version,
+ i->description);
+ if (plug->dl_handle) {
+#ifdef HAVE_DLINFO_ORIGIN
+ char origin[MAXPATHLEN] = "";
+ dlinfo(plug->dl_handle, RTLD_DI_ORIGIN, &origin);
+#ifdef HAVE_DLINFO_LINKMAP
+ const char *name;
+ struct link_map *lm = NULL;
+ dlinfo(plug->dl_handle, RTLD_DI_LINKMAP, &lm);
+ if (lm) {
+ name = strrchr(lm->l_name, '/');
+ name = name ? name + 1 : lm->l_name;
+ vty_out(vty, "\tfrom: %s/%s\n", origin, name);
+ }
+#else
+ vty_out(vty, "\tfrom: %s \n", origin, plug->load_name);
+#endif
+#else
+ vty_out(vty, "\tfrom: %s\n", plug->load_name);
+#endif
+ }
+ plug = plug->next;
+ }
+
+ vty_out(vty, "pid: %u\n", (uint32_t)(getpid()));
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (frr_defaults,
+ frr_defaults_cmd,
+ "frr defaults PROFILE...",
+ "FRRouting global parameters\n"
+ "set of configuration defaults used\n"
+ "profile string\n")
+{
+ char *profile = argv_concat(argv, argc, 2);
+ int rv = CMD_SUCCESS;
+
+ if (!frr_defaults_profile_valid(profile)) {
+ vty_out(vty, "%% WARNING: profile %s is not known in this version\n",
+ profile);
+ rv = CMD_WARNING;
+ }
+ frr_defaults_profile_set(profile);
+ XFREE(MTYPE_TMP, profile);
+ return rv;
+}
+
+DEFUN (frr_version,
+ frr_version_cmd,
+ "frr version VERSION...",
+ "FRRouting global parameters\n"
+ "version configuration was written by\n"
+ "version string\n")
+{
+ char *version = argv_concat(argv, argc, 2);
+
+ frr_defaults_version_set(version);
+ XFREE(MTYPE_TMP, version);
+ return CMD_SUCCESS;
+}
+
+static struct call_back {
+ time_t readin_time;
+
+ void (*start_config)(void);
+ void (*end_config)(void);
+} callback;
+
+
+DEFUN_HIDDEN (start_config,
+ start_config_cmd,
+ "XFRR_start_configuration",
+ "The Beginning of Configuration\n")
+{
+ callback.readin_time = monotime(NULL);
+
+ vty->pending_allowed = 1;
+
+ if (callback.start_config)
+ (*callback.start_config)();
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_HIDDEN (end_config,
+ end_config_cmd,
+ "XFRR_end_configuration",
+ "The End of Configuration\n")
+{
+ time_t readin_time;
+ char readin_time_str[MONOTIME_STRLEN];
+ int ret;
+
+ readin_time = monotime(NULL);
+ readin_time -= callback.readin_time;
+
+ frrtime_to_interval(readin_time, readin_time_str,
+ sizeof(readin_time_str));
+
+ vty->pending_allowed = 0;
+ ret = nb_cli_pending_commit_check(vty);
+
+ zlog_info("Configuration Read in Took: %s", readin_time_str);
+
+ if (callback.end_config)
+ (*callback.end_config)();
+
+ return ret;
+}
+
+void cmd_init_config_callbacks(void (*start_config_cb)(void),
+ void (*end_config_cb)(void))
+{
+ callback.start_config = start_config_cb;
+ callback.end_config = end_config_cb;
+}
+
+
+static void defaults_autocomplete(vector comps, struct cmd_token *token)
+{
+ const char **p;
+
+ for (p = frr_defaults_profiles; *p; p++)
+ vector_set(comps, XSTRDUP(MTYPE_COMPLETION, *p));
+}
+
+static const struct cmd_variable_handler default_var_handlers[] = {
+ {.tokenname = "PROFILE", .completions = defaults_autocomplete},
+ {.completions = NULL},
+};
+
+void lib_cmd_init(void)
+{
+ cmd_variable_handler_register(default_var_handlers);
+
+ install_element(CONFIG_NODE, &frr_defaults_cmd);
+ install_element(CONFIG_NODE, &frr_version_cmd);
+
+ install_element(VIEW_NODE, &show_memory_cmd);
+ install_element(VIEW_NODE, &show_modules_cmd);
+
+ install_element(CONFIG_NODE, &start_config_cmd);
+ install_element(CONFIG_NODE, &end_config_cmd);
+}
+
+/* Stats querying from users */
+/* Return a pointer to a human friendly string describing
+ * the byte count passed in. E.g:
+ * "0 bytes", "2048 bytes", "110kB", "500MiB", "11GiB", etc.
+ * Up to 4 significant figures will be given.
+ * The pointer returned may be NULL (indicating an error)
+ * or point to the given buffer, or point to static storage.
+ */
+const char *mtype_memstr(char *buf, size_t len, unsigned long bytes)
+{
+ unsigned int m, k;
+
+ /* easy cases */
+ if (!bytes)
+ return "0 bytes";
+ if (bytes == 1)
+ return "1 byte";
+
+ /*
+ * When we pass the 2gb barrier mallinfo() can no longer report
+ * correct data so it just does something odd...
+ * Reporting like Terrabytes of data. Which makes users...
+ * edgy.. yes edgy that's the term for it.
+ * So let's just give up gracefully
+ */
+ if (bytes > 0x7fffffff)
+ return "> 2GB";
+
+ m = bytes >> 20;
+ k = bytes >> 10;
+
+ if (m > 10) {
+ if (bytes & (1 << 19))
+ m++;
+ snprintf(buf, len, "%d MiB", m);
+ } else if (k > 10) {
+ if (bytes & (1 << 9))
+ k++;
+ snprintf(buf, len, "%d KiB", k);
+ } else
+ snprintf(buf, len, "%ld bytes", bytes);
+
+ return buf;
+}
diff --git a/lib/lib_vty.h b/lib/lib_vty.h
new file mode 100644
index 0000000..48e409e
--- /dev/null
+++ b/lib/lib_vty.h
@@ -0,0 +1,40 @@
+/* Memory management routine
+ * Copyright (C) 1998 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 _ZEBRA_LIB_VTY_H
+#define _ZEBRA_LIB_VTY_H
+
+#include "memory.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern void lib_cmd_init(void);
+
+/* Human friendly string for given byte count */
+#define MTYPE_MEMSTR_LEN 20
+extern const char *mtype_memstr(char *, size_t, unsigned long);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ZEBRA_LIB_VTY_H */
diff --git a/lib/libfrr.c b/lib/libfrr.c
new file mode 100644
index 0000000..aee6981
--- /dev/null
+++ b/lib/libfrr.c
@@ -0,0 +1,1279 @@
+/*
+ * libfrr overall management functions
+ *
+ * Copyright (C) 2016 David Lamparter for NetDEF, Inc.
+ *
+ * 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 <sys/un.h>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include "libfrr.h"
+#include "getopt.h"
+#include "privs.h"
+#include "vty.h"
+#include "command.h"
+#include "lib/version.h"
+#include "lib_vty.h"
+#include "log_vty.h"
+#include "zclient.h"
+#include "module.h"
+#include "network.h"
+#include "lib_errors.h"
+#include "db.h"
+#include "northbound_cli.h"
+#include "northbound_db.h"
+#include "debug.h"
+#include "frrcu.h"
+#include "frr_pthread.h"
+#include "defaults.h"
+#include "frrscript.h"
+#include "systemd.h"
+
+DEFINE_HOOK(frr_early_init, (struct thread_master * tm), (tm));
+DEFINE_HOOK(frr_late_init, (struct thread_master * tm), (tm));
+DEFINE_HOOK(frr_config_pre, (struct thread_master * tm), (tm));
+DEFINE_HOOK(frr_config_post, (struct thread_master * tm), (tm));
+DEFINE_KOOH(frr_early_fini, (), ());
+DEFINE_KOOH(frr_fini, (), ());
+
+const char frr_sysconfdir[] = SYSCONFDIR;
+char frr_vtydir[256];
+#ifdef HAVE_SQLITE3
+const char frr_dbdir[] = DAEMON_DB_DIR;
+#endif
+const char frr_moduledir[] = MODULE_PATH;
+const char frr_scriptdir[] = SCRIPT_PATH;
+
+char frr_protoname[256] = "NONE";
+char frr_protonameinst[256] = "NONE";
+
+char config_default[512];
+char frr_zclientpath[256];
+static char pidfile_default[1024];
+#ifdef HAVE_SQLITE3
+static char dbfile_default[512];
+#endif
+static char vtypath_default[512];
+
+/* cleared in frr_preinit(), then re-set after daemonizing */
+bool frr_is_after_fork = true;
+bool debug_memstats_at_exit = false;
+static bool nodetach_term, nodetach_daemon;
+static uint64_t startup_fds;
+
+static char comb_optstr[256];
+static struct option comb_lo[64];
+static struct option *comb_next_lo = &comb_lo[0];
+static char comb_helpstr[4096];
+
+struct optspec {
+ const char *optstr;
+ const char *helpstr;
+ const struct option *longopts;
+};
+
+static void opt_extend(const struct optspec *os)
+{
+ const struct option *lo;
+
+ strlcat(comb_optstr, os->optstr, sizeof(comb_optstr));
+ strlcat(comb_helpstr, os->helpstr, sizeof(comb_helpstr));
+ for (lo = os->longopts; lo->name; lo++)
+ memcpy(comb_next_lo++, lo, sizeof(*lo));
+}
+
+
+#define OPTION_VTYSOCK 1000
+#define OPTION_MODULEDIR 1002
+#define OPTION_LOG 1003
+#define OPTION_LOGLEVEL 1004
+#define OPTION_TCLI 1005
+#define OPTION_DB_FILE 1006
+#define OPTION_LOGGING 1007
+#define OPTION_LIMIT_FDS 1008
+#define OPTION_SCRIPTDIR 1009
+
+static const struct option lo_always[] = {
+ {"help", no_argument, NULL, 'h'},
+ {"version", no_argument, NULL, 'v'},
+ {"daemon", no_argument, NULL, 'd'},
+ {"module", no_argument, NULL, 'M'},
+ {"profile", required_argument, NULL, 'F'},
+ {"pathspace", required_argument, NULL, 'N'},
+ {"vrfdefaultname", required_argument, NULL, 'o'},
+ {"vty_socket", required_argument, NULL, OPTION_VTYSOCK},
+ {"moduledir", required_argument, NULL, OPTION_MODULEDIR},
+ {"scriptdir", required_argument, NULL, OPTION_SCRIPTDIR},
+ {"log", required_argument, NULL, OPTION_LOG},
+ {"log-level", required_argument, NULL, OPTION_LOGLEVEL},
+ {"command-log-always", no_argument, NULL, OPTION_LOGGING},
+ {"limit-fds", required_argument, NULL, OPTION_LIMIT_FDS},
+ {NULL}};
+static const struct optspec os_always = {
+ "hvdM:F:N:o:",
+ " -h, --help Display this help and exit\n"
+ " -v, --version Print program version\n"
+ " -d, --daemon Runs in daemon mode\n"
+ " -M, --module Load specified module\n"
+ " -F, --profile Use specified configuration profile\n"
+ " -N, --pathspace Insert prefix into config & socket paths\n"
+ " -o, --vrfdefaultname Set default VRF name.\n"
+ " --vty_socket Override vty socket path\n"
+ " --moduledir Override modules directory\n"
+ " --scriptdir Override scripts directory\n"
+ " --log Set Logging to stdout, syslog, or file:<name>\n"
+ " --log-level Set Logging Level to use, debug, info, warn, etc\n"
+ " --limit-fds Limit number of fds supported\n",
+ lo_always};
+
+
+static const struct option lo_cfg[] = {
+ {"config_file", required_argument, NULL, 'f'},
+ {"dryrun", no_argument, NULL, 'C'},
+ {NULL}};
+static const struct optspec os_cfg = {
+ "f:C",
+ " -f, --config_file Set configuration file name\n"
+ " -C, --dryrun Check configuration for validity and exit\n",
+ lo_cfg};
+
+
+static const struct option lo_fullcli[] = {
+ {"terminal", no_argument, NULL, 't'},
+ {"tcli", no_argument, NULL, OPTION_TCLI},
+#ifdef HAVE_SQLITE3
+ {"db_file", required_argument, NULL, OPTION_DB_FILE},
+#endif
+ {NULL}};
+static const struct optspec os_fullcli = {
+ "t",
+ " --tcli Use transaction-based CLI\n"
+ " -t, --terminal Open terminal session on stdio\n"
+ " -d -t Daemonize after terminal session ends\n",
+ lo_fullcli};
+
+
+static const struct option lo_pid[] = {
+ {"pid_file", required_argument, NULL, 'i'},
+ {NULL}};
+static const struct optspec os_pid = {
+ "i:",
+ " -i, --pid_file Set process identifier file name\n",
+ lo_pid};
+
+
+static const struct option lo_zclient[] = {
+ {"socket", required_argument, NULL, 'z'},
+ {NULL}};
+static const struct optspec os_zclient = {
+ "z:", " -z, --socket Set path of zebra socket\n", lo_zclient};
+
+
+static const struct option lo_vty[] = {
+ {"vty_addr", required_argument, NULL, 'A'},
+ {"vty_port", required_argument, NULL, 'P'},
+ {NULL}};
+static const struct optspec os_vty = {
+ "A:P:",
+ " -A, --vty_addr Set vty's bind address\n"
+ " -P, --vty_port Set vty's port number\n",
+ lo_vty};
+
+
+static const struct option lo_user[] = {{"user", required_argument, NULL, 'u'},
+ {"group", required_argument, NULL, 'g'},
+ {NULL}};
+static const struct optspec os_user = {"u:g:",
+ " -u, --user User to run as\n"
+ " -g, --group Group to run as\n",
+ lo_user};
+
+bool frr_zclient_addr(struct sockaddr_storage *sa, socklen_t *sa_len,
+ const char *path)
+{
+ memset(sa, 0, sizeof(*sa));
+
+ if (!path)
+ path = frr_zclientpath;
+
+ if (!strncmp(path, ZAPI_TCP_PATHNAME, strlen(ZAPI_TCP_PATHNAME))) {
+ /* note: this functionality is disabled at bottom */
+ int af;
+ int port = ZEBRA_PORT;
+ char *err = NULL;
+ struct sockaddr_in *sin = NULL;
+ struct sockaddr_in6 *sin6 = NULL;
+
+ path += strlen(ZAPI_TCP_PATHNAME);
+
+ switch (path[0]) {
+ case '4':
+ path++;
+ af = AF_INET;
+ break;
+ case '6':
+ path++;
+ /* fallthrough */
+ default:
+ af = AF_INET6;
+ break;
+ }
+
+ switch (path[0]) {
+ case '\0':
+ break;
+ case ':':
+ path++;
+ port = strtoul(path, &err, 10);
+ if (*err || !*path)
+ return false;
+ break;
+ default:
+ return false;
+ }
+
+ sa->ss_family = af;
+ switch (af) {
+ case AF_INET:
+ sin = (struct sockaddr_in *)sa;
+ sin->sin_port = htons(port);
+ sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ *sa_len = sizeof(struct sockaddr_in);
+#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN
+ sin->sin_len = *sa_len;
+#endif
+ break;
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *)sa;
+ sin6->sin6_port = htons(port);
+ inet_pton(AF_INET6, "::1", &sin6->sin6_addr);
+ *sa_len = sizeof(struct sockaddr_in6);
+#ifdef SIN6_LEN
+ sin6->sin6_len = *sa_len;
+#endif
+ break;
+ }
+
+#if 1
+ /* force-disable this path, because tcp-zebra is a
+ * SECURITY ISSUE. there are no checks at all against
+ * untrusted users on the local system connecting on TCP
+ * and injecting bogus routing data into the entire routing
+ * domain.
+ *
+ * The functionality is only left here because it may be
+ * useful during development, in order to be able to get
+ * tcpdump or wireshark watching ZAPI as TCP. If you want
+ * to do that, flip the #if 1 above to #if 0. */
+ memset(sa, 0, sizeof(*sa));
+ return false;
+#endif
+ } else {
+ /* "sun" is a #define on solaris */
+ struct sockaddr_un *suna = (struct sockaddr_un *)sa;
+
+ suna->sun_family = AF_UNIX;
+ strlcpy(suna->sun_path, path, sizeof(suna->sun_path));
+#ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN
+ *sa_len = suna->sun_len = SUN_LEN(suna);
+#else
+ *sa_len = sizeof(suna->sun_family) + strlen(suna->sun_path);
+#endif /* HAVE_STRUCT_SOCKADDR_UN_SUN_LEN */
+#if 0
+ /* this is left here for future reference; Linux abstract
+ * socket namespace support can be enabled by replacing
+ * above #if 0 with #ifdef GNU_LINUX.
+ *
+ * THIS IS A SECURITY ISSUE, the abstract socket namespace
+ * does not have user/group permission control on sockets.
+ * we'd need to implement SCM_CREDENTIALS support first to
+ * check that only proper users can connect to abstract
+ * sockets. (same problem as tcp-zebra, except there is a
+ * fix with SCM_CREDENTIALS. tcp-zebra has no such fix.)
+ */
+ if (suna->sun_path[0] == '@')
+ suna->sun_path[0] = '\0';
+#endif
+ }
+ return true;
+}
+
+static struct frr_daemon_info *di = NULL;
+
+void frr_init_vtydir(void)
+{
+ snprintf(frr_vtydir, sizeof(frr_vtydir), DAEMON_VTY_DIR, "", "");
+}
+
+void frr_preinit(struct frr_daemon_info *daemon, int argc, char **argv)
+{
+ di = daemon;
+ frr_is_after_fork = false;
+
+ /* basename(), opencoded. */
+ char *p = strrchr(argv[0], '/');
+ di->progname = p ? p + 1 : argv[0];
+
+ umask(0027);
+
+ log_args_init(daemon->early_logging);
+
+ opt_extend(&os_always);
+ if (!(di->flags & FRR_NO_SPLIT_CONFIG))
+ opt_extend(&os_cfg);
+ if (!(di->flags & FRR_LIMITED_CLI))
+ opt_extend(&os_fullcli);
+ if (!(di->flags & FRR_NO_PID))
+ opt_extend(&os_pid);
+ if (!(di->flags & FRR_NO_PRIVSEP))
+ opt_extend(&os_user);
+ if (!(di->flags & FRR_NO_ZCLIENT))
+ opt_extend(&os_zclient);
+ if (!(di->flags & FRR_NO_TCPVTY))
+ opt_extend(&os_vty);
+ if (di->flags & FRR_DETACH_LATER)
+ nodetach_daemon = true;
+
+ frr_init_vtydir();
+ snprintf(config_default, sizeof(config_default), "%s/%s.conf",
+ frr_sysconfdir, di->name);
+ snprintf(pidfile_default, sizeof(pidfile_default), "%s/%s.pid",
+ frr_vtydir, di->name);
+ snprintf(frr_zclientpath, sizeof(frr_zclientpath),
+ ZEBRA_SERV_PATH, "", "");
+#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));
+
+ di->cli_mode = FRR_CLI_CLASSIC;
+
+ /* we may be starting with extra FDs open for whatever purpose,
+ * e.g. logging, some module, etc. Recording them here allows later
+ * checking whether an fd is valid for such extension purposes,
+ * without this we could end up e.g. logging to a BGP session fd.
+ */
+ startup_fds = 0;
+ for (int i = 0; i < 64; i++) {
+ struct stat st;
+
+ if (fstat(i, &st))
+ continue;
+ if (S_ISDIR(st.st_mode) || S_ISBLK(st.st_mode))
+ continue;
+
+ startup_fds |= UINT64_C(0x1) << (uint64_t)i;
+ }
+
+ /* note this doesn't do anything, it just grabs state, so doing it
+ * early in _preinit is perfect.
+ */
+ systemd_init_env();
+}
+
+bool frr_is_startup_fd(int fd)
+{
+ return !!(startup_fds & (UINT64_C(0x1) << (uint64_t)fd));
+}
+
+void frr_opt_add(const char *optstr, const struct option *longopts,
+ const char *helpstr)
+{
+ const struct optspec main_opts = {optstr, helpstr, longopts};
+ opt_extend(&main_opts);
+}
+
+void frr_help_exit(int status)
+{
+ FILE *target = status ? stderr : stdout;
+
+ if (status != 0)
+ fprintf(stderr, "Invalid options.\n\n");
+
+ if (di->printhelp)
+ di->printhelp(target);
+ else
+ fprintf(target, "Usage: %s [OPTION...]\n\n%s%s%s\n\n%s",
+ di->progname, di->proghelp, di->copyright ? "\n\n" : "",
+ di->copyright ? di->copyright : "", comb_helpstr);
+ fprintf(target, "\nReport bugs to %s\n", FRR_BUG_ADDRESS);
+ exit(status);
+}
+
+struct option_chain {
+ struct option_chain *next;
+ const char *arg;
+};
+
+static struct option_chain *modules = NULL, **modnext = &modules;
+static int errors = 0;
+
+static int frr_opt(int opt)
+{
+ static int vty_port_set = 0;
+ static int vty_addr_set = 0;
+ struct option_chain *oc;
+ struct log_arg *log_arg;
+ size_t arg_len;
+ char *err;
+
+ switch (opt) {
+ case 'h':
+ frr_help_exit(0);
+ case 'v':
+ print_version(di->progname);
+ exit(0);
+ break;
+ case 'd':
+ di->daemon_mode = true;
+ break;
+ case 'M':
+ oc = XMALLOC(MTYPE_TMP, sizeof(*oc));
+ oc->arg = optarg;
+ oc->next = NULL;
+ *modnext = oc;
+ modnext = &oc->next;
+ break;
+ case 'F':
+ if (!frr_defaults_profile_valid(optarg)) {
+ const char **p;
+ FILE *ofd = stderr;
+
+ if (!strcmp(optarg, "help"))
+ ofd = stdout;
+ else
+ fprintf(stderr,
+ "The \"%s\" configuration profile is not valid for this FRR version.\n",
+ optarg);
+
+ fprintf(ofd, "Available profiles are:\n");
+ for (p = frr_defaults_profiles; *p; p++)
+ fprintf(ofd, "%s%s\n",
+ strcmp(*p, DFLT_NAME) ? " " : " * ",
+ *p);
+
+ if (ofd == stdout)
+ exit(0);
+ fprintf(ofd, "\n");
+ errors++;
+ break;
+ }
+ frr_defaults_profile_set(optarg);
+ break;
+ case 'i':
+ if (di->flags & FRR_NO_PID)
+ return 1;
+ di->pid_file = optarg;
+ break;
+ case 'f':
+ if (di->flags & FRR_NO_SPLIT_CONFIG)
+ return 1;
+ di->config_file = optarg;
+ break;
+ case 'N':
+ if (di->pathspace) {
+ fprintf(stderr,
+ "-N/--pathspace option specified more than once!\n");
+ errors++;
+ break;
+ }
+ if (di->zpathspace)
+ fprintf(stderr,
+ "-N option overridden by -z for zebra named socket path\n");
+
+ if (strchr(optarg, '/') || strchr(optarg, '.')) {
+ fprintf(stderr,
+ "slashes or dots are not permitted in the --pathspace option.\n");
+ errors++;
+ break;
+ }
+ di->pathspace = optarg;
+
+ if (!di->zpathspace)
+ snprintf(frr_zclientpath, sizeof(frr_zclientpath),
+ ZEBRA_SERV_PATH, "/", di->pathspace);
+ snprintf(frr_vtydir, sizeof(frr_vtydir), DAEMON_VTY_DIR, "/",
+ di->pathspace);
+ snprintf(pidfile_default, sizeof(pidfile_default), "%s/%s.pid",
+ frr_vtydir, di->name);
+ break;
+ case 'o':
+ vrf_set_default_name(optarg);
+ break;
+#ifdef HAVE_SQLITE3
+ case OPTION_DB_FILE:
+ if (di->flags & FRR_NO_PID)
+ return 1;
+ di->db_file = optarg;
+ break;
+#endif
+ case 'C':
+ if (di->flags & FRR_NO_SPLIT_CONFIG)
+ return 1;
+ di->dryrun = true;
+ break;
+ case 't':
+ if (di->flags & FRR_LIMITED_CLI)
+ return 1;
+ di->terminal = true;
+ break;
+ case 'z':
+ di->zpathspace = true;
+ if (di->pathspace)
+ fprintf(stderr,
+ "-z option overrides -N option for zebra named socket path\n");
+ if (di->flags & FRR_NO_ZCLIENT)
+ return 1;
+ strlcpy(frr_zclientpath, optarg, sizeof(frr_zclientpath));
+ break;
+ case 'A':
+ if (di->flags & FRR_NO_TCPVTY)
+ return 1;
+ if (vty_addr_set) {
+ fprintf(stderr,
+ "-A option specified more than once!\n");
+ errors++;
+ break;
+ }
+ vty_addr_set = 1;
+ di->vty_addr = optarg;
+ break;
+ case 'P':
+ if (di->flags & FRR_NO_TCPVTY)
+ return 1;
+ if (vty_port_set) {
+ fprintf(stderr,
+ "-P option specified more than once!\n");
+ errors++;
+ break;
+ }
+ vty_port_set = 1;
+ di->vty_port = strtoul(optarg, &err, 0);
+ if (*err || !*optarg) {
+ fprintf(stderr,
+ "invalid port number \"%s\" for -P option\n",
+ optarg);
+ errors++;
+ break;
+ }
+ break;
+ case OPTION_VTYSOCK:
+ if (di->vty_sock_path) {
+ fprintf(stderr,
+ "--vty_socket option specified more than once!\n");
+ errors++;
+ break;
+ }
+ di->vty_sock_path = optarg;
+ break;
+ case OPTION_MODULEDIR:
+ if (di->module_path) {
+ fprintf(stderr,
+ "----moduledir option specified more than once!\n");
+ errors++;
+ break;
+ }
+ di->module_path = optarg;
+ break;
+ case OPTION_SCRIPTDIR:
+ if (di->script_path) {
+ fprintf(stderr, "--scriptdir option specified more than once!\n");
+ errors++;
+ break;
+ }
+ di->script_path = optarg;
+ break;
+ case OPTION_TCLI:
+ di->cli_mode = FRR_CLI_TRANSACTIONAL;
+ break;
+ case 'u':
+ if (di->flags & FRR_NO_PRIVSEP)
+ return 1;
+ di->privs->user = optarg;
+ break;
+ case 'g':
+ if (di->flags & FRR_NO_PRIVSEP)
+ return 1;
+ di->privs->group = optarg;
+ break;
+ case OPTION_LOG:
+ arg_len = strlen(optarg) + 1;
+ log_arg = XCALLOC(MTYPE_TMP, sizeof(*log_arg) + arg_len);
+ memcpy(log_arg->target, optarg, arg_len);
+ log_args_add_tail(di->early_logging, log_arg);
+ break;
+ case OPTION_LOGLEVEL:
+ di->early_loglevel = optarg;
+ break;
+ case OPTION_LOGGING:
+ di->log_always = true;
+ break;
+ case OPTION_LIMIT_FDS:
+ di->limit_fds = strtoul(optarg, &err, 0);
+ break;
+ default:
+ return 1;
+ }
+ return 0;
+}
+
+int frr_getopt(int argc, char *const argv[], int *longindex)
+{
+ int opt;
+ int lidx;
+
+ comb_next_lo->name = NULL;
+
+ do {
+ opt = getopt_long(argc, argv, comb_optstr, comb_lo, &lidx);
+ if (frr_opt(opt))
+ break;
+ } while (opt != -1);
+
+ if (opt == -1 && errors)
+ frr_help_exit(1);
+ if (longindex)
+ *longindex = lidx;
+ return opt;
+}
+
+static void frr_mkdir(const char *path, bool strip)
+{
+ char buf[256];
+ mode_t prev;
+ int ret;
+ struct zprivs_ids_t ids;
+
+ if (strip) {
+ char *slash = strrchr(path, '/');
+ size_t plen;
+ if (!slash)
+ return;
+ plen = slash - path;
+ if (plen > sizeof(buf) - 1)
+ return;
+ memcpy(buf, path, plen);
+ buf[plen] = '\0';
+ path = buf;
+ }
+
+ /* o+rx (..5) is needed for the frrvty group to work properly;
+ * without it, users in the frrvty group can't access the vty sockets.
+ */
+ prev = umask(0022);
+ ret = mkdir(path, 0755);
+ umask(prev);
+
+ if (ret != 0) {
+ /* if EEXIST, return without touching the permissions,
+ * so user-set custom permissions are left in place
+ */
+ if (errno == EEXIST)
+ return;
+
+ flog_err(EC_LIB_SYSTEM_CALL, "failed to mkdir \"%s\": %s", path,
+ strerror(errno));
+ return;
+ }
+
+ zprivs_get_ids(&ids);
+ if (chown(path, ids.uid_normal, ids.gid_normal))
+ flog_err(EC_LIB_SYSTEM_CALL, "failed to chown \"%s\": %s", path,
+ strerror(errno));
+}
+
+static void _err_print(const void *cookie, const char *errstr)
+{
+ const char *prefix = (const char *)cookie;
+
+ fprintf(stderr, "%s: %s\n", prefix, errstr);
+}
+
+static struct thread_master *master;
+struct thread_master *frr_init(void)
+{
+ struct option_chain *oc;
+ struct log_arg *log_arg;
+ struct frrmod_runtime *module;
+ struct zprivs_ids_t ids;
+ char p_instance[16] = "", p_pathspace[256] = "";
+ const char *dir;
+
+ dir = di->module_path ? di->module_path : frr_moduledir;
+
+ srandom(time(NULL));
+ frr_defaults_apply();
+
+ if (di->instance) {
+ snprintf(frr_protonameinst, sizeof(frr_protonameinst), "%s[%u]",
+ di->logname, di->instance);
+ snprintf(p_instance, sizeof(p_instance), "-%d", di->instance);
+ }
+ if (di->pathspace)
+ snprintf(p_pathspace, sizeof(p_pathspace), "%s/",
+ di->pathspace);
+
+ snprintf(config_default, sizeof(config_default), "%s%s%s%s.conf",
+ frr_sysconfdir, p_pathspace, di->name, p_instance);
+ snprintf(pidfile_default, sizeof(pidfile_default), "%s/%s%s.pid",
+ frr_vtydir, 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);
+ zprivs_get_ids(&ids);
+
+ zlog_init(di->progname, di->logname, di->instance,
+ ids.uid_normal, ids.gid_normal);
+
+ while ((log_arg = log_args_pop(di->early_logging))) {
+ command_setup_early_logging(log_arg->target,
+ di->early_loglevel);
+ XFREE(MTYPE_TMP, log_arg);
+ }
+
+ if (!frr_zclient_addr(&zclient_addr, &zclient_addr_len,
+ frr_zclientpath)) {
+ fprintf(stderr, "Invalid zserv socket path: %s\n",
+ frr_zclientpath);
+ exit(1);
+ }
+
+ /* don't mkdir these as root... */
+ if (!(di->flags & FRR_NO_PRIVSEP)) {
+ if (!di->pid_file || !di->vty_path)
+ frr_mkdir(frr_vtydir, false);
+ if (di->pid_file)
+ frr_mkdir(di->pid_file, true);
+ if (di->vty_path)
+ frr_mkdir(di->vty_path, true);
+ }
+
+ frrmod_init(di->module);
+ while (modules) {
+ modules = (oc = modules)->next;
+ module = frrmod_load(oc->arg, dir, _err_print, __func__);
+ if (!module)
+ exit(1);
+ XFREE(MTYPE_TMP, oc);
+ }
+
+ zprivs_init(di->privs);
+
+ master = thread_master_create(NULL);
+ signal_init(master, di->n_signals, di->signals);
+ hook_call(frr_early_init, master);
+
+#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, di->log_always);
+ lib_cmd_init();
+
+ frr_pthread_init();
+#ifdef HAVE_SCRIPTING
+ frrscript_init(di->script_path ? di->script_path : frr_scriptdir);
+#endif
+
+ log_ref_init();
+ log_ref_vty_init();
+ lib_error_init();
+
+ nb_init(master, di->yang_modules, di->n_yang_modules, true);
+ if (nb_db_init() != NB_OK)
+ flog_warn(EC_LIB_NB_DATABASE,
+ "%s: failed to initialize northbound database",
+ __func__);
+
+ debug_init_cli();
+
+ 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;
+}
+
+uint32_t frr_get_fd_limit(void)
+{
+ return di ? di->limit_fds : 0;
+}
+
+static int rcvd_signal = 0;
+
+static void rcv_signal(int signum)
+{
+ rcvd_signal = signum;
+ /* poll() is interrupted by the signal; handled below */
+}
+
+static void frr_daemon_wait(int fd)
+{
+ struct pollfd pfd[1];
+ int ret;
+ pid_t exitpid;
+ int exitstat;
+ sigset_t sigs, prevsigs;
+
+ sigemptyset(&sigs);
+ sigaddset(&sigs, SIGTSTP);
+ sigaddset(&sigs, SIGQUIT);
+ sigaddset(&sigs, SIGINT);
+ sigprocmask(SIG_BLOCK, &sigs, &prevsigs);
+
+ struct sigaction sa = {
+ .sa_handler = rcv_signal, .sa_flags = SA_RESETHAND,
+ };
+ sigemptyset(&sa.sa_mask);
+ sigaction(SIGTSTP, &sa, NULL);
+ sigaction(SIGQUIT, &sa, NULL);
+ sigaction(SIGINT, &sa, NULL);
+
+ do {
+ char buf[1];
+ ssize_t nrecv;
+
+ pfd[0].fd = fd;
+ pfd[0].events = POLLIN;
+
+ rcvd_signal = 0;
+
+#if defined(HAVE_PPOLL)
+ ret = ppoll(pfd, 1, NULL, &prevsigs);
+#elif defined(HAVE_POLLTS)
+ ret = pollts(pfd, 1, NULL, &prevsigs);
+#else
+ /* racy -- only used on FreeBSD 9 */
+ sigset_t tmpsigs;
+ sigprocmask(SIG_SETMASK, &prevsigs, &tmpsigs);
+ ret = poll(pfd, 1, -1);
+ sigprocmask(SIG_SETMASK, &tmpsigs, NULL);
+#endif
+ if (ret < 0 && errno != EINTR && errno != EAGAIN) {
+ perror("poll()");
+ exit(1);
+ }
+ switch (rcvd_signal) {
+ case SIGTSTP:
+ send(fd, "S", 1, 0);
+ do {
+ nrecv = recv(fd, buf, sizeof(buf), 0);
+ } while (nrecv == -1
+ && (errno == EINTR || errno == EAGAIN));
+
+ raise(SIGTSTP);
+ sigaction(SIGTSTP, &sa, NULL);
+ send(fd, "R", 1, 0);
+ break;
+ case SIGINT:
+ send(fd, "I", 1, 0);
+ break;
+ case SIGQUIT:
+ send(fd, "Q", 1, 0);
+ break;
+ }
+ } while (ret <= 0);
+
+ exitpid = waitpid(-1, &exitstat, WNOHANG);
+ if (exitpid == 0)
+ /* child successfully went to main loop & closed socket */
+ exit(0);
+
+ /* child failed one way or another ... */
+ if (WIFEXITED(exitstat) && WEXITSTATUS(exitstat) == 0)
+ /* can happen in --terminal case if exit is fast enough */
+ (void)0;
+ else if (WIFEXITED(exitstat))
+ fprintf(stderr, "%s failed to start, exited %d\n", di->name,
+ WEXITSTATUS(exitstat));
+ else if (WIFSIGNALED(exitstat))
+ fprintf(stderr, "%s crashed in startup, signal %d\n", di->name,
+ WTERMSIG(exitstat));
+ else
+ fprintf(stderr, "%s failed to start, unknown problem\n",
+ di->name);
+ exit(1);
+}
+
+static int daemon_ctl_sock = -1;
+
+static void frr_daemonize(void)
+{
+ int fds[2];
+ pid_t pid;
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds)) {
+ perror("socketpair() for daemon control");
+ exit(1);
+ }
+ set_cloexec(fds[0]);
+ set_cloexec(fds[1]);
+
+ pid = fork();
+ if (pid < 0) {
+ perror("fork()");
+ exit(1);
+ }
+ if (pid == 0) {
+ /* child */
+ close(fds[0]);
+ if (setsid() < 0) {
+ perror("setsid()");
+ exit(1);
+ }
+
+ daemon_ctl_sock = fds[1];
+ return;
+ }
+
+ close(fds[1]);
+ nb_terminate();
+ yang_terminate();
+ frr_daemon_wait(fds[0]);
+}
+
+/*
+ * Why is this a thread?
+ *
+ * The read in of config for integrated config happens *after*
+ * thread execution starts( because it is passed in via a vtysh -b -n )
+ * While if you are not using integrated config we want the ability
+ * to read the config in after thread execution starts, so that
+ * we can match this behavior.
+ */
+static void frr_config_read_in(struct thread *t)
+{
+ hook_call(frr_config_pre, master);
+
+ if (!vty_read_config(vty_shared_candidate_config, 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(vty_shared_candidate_config,
+ di->backup_config_file, config_default);
+
+ host_config_set(orig);
+ XFREE(MTYPE_TMP, orig);
+ }
+
+ /*
+ * Automatically commit the candidate configuration after
+ * reading the configuration file.
+ */
+ if (frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL) {
+ struct nb_context context = {};
+ char errmsg[BUFSIZ] = {0};
+ int ret;
+
+ context.client = NB_CLIENT_CLI;
+ ret = nb_candidate_commit(&context, vty_shared_candidate_config,
+ true, "Read configuration file", NULL,
+ errmsg, sizeof(errmsg));
+ if (ret != NB_OK && ret != NB_ERR_NO_CHANGES)
+ zlog_err(
+ "%s: failed to read configuration file: %s (%s)",
+ __func__, nb_err_name(ret), errmsg);
+ }
+
+ hook_call(frr_config_post, master);
+}
+
+void frr_config_fork(void)
+{
+ hook_call(frr_late_init, master);
+
+ if (!(di->flags & FRR_NO_SPLIT_CONFIG)) {
+ /* Don't start execution if we are in dry-run mode */
+ if (di->dryrun) {
+ frr_config_read_in(NULL);
+ exit(0);
+ }
+
+ thread_add_event(master, frr_config_read_in, NULL, 0,
+ &di->read_in);
+ }
+
+ if (di->daemon_mode || di->terminal)
+ frr_daemonize();
+
+ frr_is_after_fork = true;
+
+ if (!di->pid_file)
+ di->pid_file = pidfile_default;
+ pid_output(di->pid_file);
+ zlog_tls_buffer_init();
+}
+
+static void frr_vty_serv(void)
+{
+ /* allow explicit override of vty_path in the future
+ * (not currently set anywhere) */
+ if (!di->vty_path) {
+ const char *dir;
+ char defvtydir[256];
+
+ snprintf(defvtydir, sizeof(defvtydir), "%s", frr_vtydir);
+
+ dir = di->vty_sock_path ? di->vty_sock_path : defvtydir;
+
+ if (di->instance)
+ snprintf(vtypath_default, sizeof(vtypath_default),
+ "%s/%s-%d.vty", dir, di->name, di->instance);
+ else
+ snprintf(vtypath_default, sizeof(vtypath_default),
+ "%s/%s.vty", dir, di->name);
+
+ di->vty_path = vtypath_default;
+ }
+
+ vty_serv_sock(di->vty_addr, di->vty_port, di->vty_path);
+}
+
+static void frr_check_detach(void)
+{
+ if (nodetach_term || nodetach_daemon)
+ return;
+
+ if (daemon_ctl_sock != -1)
+ close(daemon_ctl_sock);
+ daemon_ctl_sock = -1;
+}
+
+static void frr_terminal_close(int isexit)
+{
+ int nullfd;
+
+ nodetach_term = false;
+ frr_check_detach();
+
+ if (!di->daemon_mode || isexit) {
+ printf("\n%s exiting\n", di->name);
+ if (!isexit)
+ raise(SIGINT);
+ return;
+ } else {
+ printf("\n%s daemonizing\n", di->name);
+ fflush(stdout);
+ }
+
+ nullfd = open("/dev/null", O_RDONLY | O_NOCTTY);
+ if (nullfd == -1) {
+ flog_err_sys(EC_LIB_SYSTEM_CALL,
+ "%s: failed to open /dev/null: %s", __func__,
+ safe_strerror(errno));
+ } else {
+ dup2(nullfd, 0);
+ dup2(nullfd, 1);
+ dup2(nullfd, 2);
+ close(nullfd);
+ }
+}
+
+static struct thread *daemon_ctl_thread = NULL;
+
+static void frr_daemon_ctl(struct thread *t)
+{
+ char buf[1];
+ ssize_t nr;
+
+ nr = recv(daemon_ctl_sock, buf, sizeof(buf), 0);
+ if (nr < 0 && (errno == EINTR || errno == EAGAIN))
+ goto out;
+ if (nr <= 0)
+ return;
+
+ switch (buf[0]) {
+ case 'S': /* SIGTSTP */
+ vty_stdio_suspend();
+ if (send(daemon_ctl_sock, "s", 1, 0) < 0)
+ zlog_err("%s send(\"s\") error (SIGTSTP propagation)",
+ (di && di->name ? di->name : ""));
+ break;
+ case 'R': /* SIGTCNT [implicit] */
+ vty_stdio_resume();
+ break;
+ case 'I': /* SIGINT */
+ di->daemon_mode = false;
+ raise(SIGINT);
+ break;
+ case 'Q': /* SIGQUIT */
+ di->daemon_mode = true;
+ vty_stdio_close();
+ break;
+ }
+
+out:
+ thread_add_read(master, frr_daemon_ctl, NULL, daemon_ctl_sock,
+ &daemon_ctl_thread);
+}
+
+void frr_detach(void)
+{
+ nodetach_daemon = false;
+ frr_check_detach();
+}
+
+void frr_run(struct thread_master *master)
+{
+ char instanceinfo[64] = "";
+
+ frr_vty_serv();
+
+ if (di->instance)
+ snprintf(instanceinfo, sizeof(instanceinfo), "instance %u ",
+ di->instance);
+
+ zlog_notice("%s %s starting: %svty@%d%s", di->name, FRR_VERSION,
+ instanceinfo, di->vty_port, di->startinfo);
+
+ if (di->terminal) {
+ nodetach_term = true;
+
+ vty_stdio(frr_terminal_close);
+ if (daemon_ctl_sock != -1) {
+ set_nonblocking(daemon_ctl_sock);
+ thread_add_read(master, frr_daemon_ctl, NULL,
+ daemon_ctl_sock, &daemon_ctl_thread);
+ }
+ } else if (di->daemon_mode) {
+ int nullfd = open("/dev/null", O_RDONLY | O_NOCTTY);
+ if (nullfd == -1) {
+ flog_err_sys(EC_LIB_SYSTEM_CALL,
+ "%s: failed to open /dev/null: %s",
+ __func__, safe_strerror(errno));
+ } else {
+ dup2(nullfd, 0);
+ dup2(nullfd, 1);
+ dup2(nullfd, 2);
+ close(nullfd);
+ }
+
+ frr_check_detach();
+ }
+
+ /* end fixed stderr startup logging */
+ zlog_startup_end();
+
+ struct thread thread;
+ while (thread_fetch(master, &thread))
+ thread_call(&thread);
+}
+
+void frr_early_fini(void)
+{
+ hook_call(frr_early_fini);
+}
+
+void frr_fini(void)
+{
+ FILE *fp;
+ char filename[128];
+ int have_leftovers;
+
+ hook_call(frr_fini);
+
+ vty_terminate();
+ cmd_terminate();
+ nb_terminate();
+ yang_terminate();
+#ifdef HAVE_SQLITE3
+ db_close();
+#endif
+ log_ref_fini();
+
+#ifdef HAVE_SCRIPTING
+ frrscript_fini();
+#endif
+ frr_pthread_finish();
+ zprivs_terminate(di->privs);
+ /* signal_init -> nothing needed */
+ thread_master_free(master);
+ master = NULL;
+ zlog_tls_buffer_fini();
+ zlog_fini();
+ /* frrmod_init -> nothing needed / hooks */
+ rcu_shutdown();
+
+ if (!debug_memstats_at_exit)
+ return;
+
+ have_leftovers = log_memstats(stderr, di->name);
+
+ /* in case we decide at runtime that we want exit-memstats for
+ * a daemon, but it has no stderr because it's daemonized
+ * (only do this if we actually have something to print though)
+ */
+ if (!have_leftovers)
+ return;
+
+ snprintf(filename, sizeof(filename), "/tmp/frr-memstats-%s-%llu-%llu",
+ di->name, (unsigned long long)getpid(),
+ (unsigned long long)time(NULL));
+
+ fp = fopen(filename, "w");
+ if (fp) {
+ log_memstats(fp, di->name);
+ fclose(fp);
+ }
+}
+
+#ifdef INTERP
+static const char interp[]
+ __attribute__((section(".interp"), used)) = INTERP;
+#endif
+/*
+ * executable entry point for libfrr.so
+ *
+ * note that libc initialization is skipped for this so the set of functions
+ * that can be called is rather limited
+ */
+extern void _libfrr_version(void)
+ __attribute__((visibility("hidden"), noreturn));
+void _libfrr_version(void)
+{
+ const char banner[] =
+ FRR_FULL_NAME " " FRR_VERSION ".\n"
+ FRR_COPYRIGHT GIT_INFO "\n"
+ "configured with:\n " FRR_CONFIG_ARGS "\n";
+ write(1, banner, sizeof(banner) - 1);
+ _exit(0);
+}
diff --git a/lib/libfrr.h b/lib/libfrr.h
new file mode 100644
index 0000000..69054e4
--- /dev/null
+++ b/lib/libfrr.h
@@ -0,0 +1,196 @@
+/*
+ * libfrr overall management functions
+ *
+ * Copyright (C) 2016 David Lamparter for NetDEF, Inc.
+ *
+ * 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 _ZEBRA_FRR_H
+#define _ZEBRA_FRR_H
+
+#include "typesafe.h"
+#include "sigevent.h"
+#include "privs.h"
+#include "thread.h"
+#include "log.h"
+#include "getopt.h"
+#include "module.h"
+#include "hook.h"
+#include "northbound.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* The following options disable specific command line options that
+ * are not applicable for a particular daemon.
+ */
+#define FRR_NO_PRIVSEP (1 << 0)
+#define FRR_NO_TCPVTY (1 << 1)
+#define FRR_LIMITED_CLI (1 << 2)
+#define FRR_NO_SPLIT_CONFIG (1 << 3)
+#define FRR_NO_PID (1 << 4)
+#define FRR_NO_CFG_PID_DRY (FRR_NO_PID | FRR_NO_SPLIT_CONFIG)
+#define FRR_NO_ZCLIENT (1 << 5)
+/* If FRR_DETACH_LATER is used, the daemon will keep its parent running
+ * until frr_detach() is called. Normally "somedaemon -d" returns once the
+ * main event loop is reached in the daemon; use this for extra startup bits.
+ *
+ * Does nothing if -d isn't used.
+ */
+#define FRR_DETACH_LATER (1 << 6)
+
+PREDECL_DLIST(log_args);
+struct log_arg {
+ struct log_args_item itm;
+
+ char target[0];
+};
+DECLARE_DLIST(log_args, struct log_arg, itm);
+
+enum frr_cli_mode {
+ FRR_CLI_CLASSIC = 0,
+ FRR_CLI_TRANSACTIONAL,
+};
+
+struct frr_daemon_info {
+ unsigned flags;
+
+ const char *progname;
+ const char *name;
+ const char *logname;
+ unsigned short instance;
+ struct frrmod_runtime *module;
+
+ char *vty_addr;
+ int vty_port;
+ char *vty_sock_path;
+ 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 *script_path;
+
+ const char *pathspace;
+ bool zpathspace;
+
+ struct log_args_head early_logging[1];
+ const char *early_loglevel;
+
+ const char *proghelp;
+ void (*printhelp)(FILE *target);
+ const char *copyright;
+ char startinfo[128];
+
+ struct frr_signal_t *signals;
+ size_t n_signals;
+
+ struct zebra_privs_t *privs;
+
+ const struct frr_yang_module_info *const *yang_modules;
+ size_t n_yang_modules;
+
+ bool log_always;
+
+ /* Optional upper limit on the number of fds used in select/poll */
+ uint32_t limit_fds;
+};
+
+/* execname is the daemon's executable (and pidfile and configfile) name,
+ * i.e. "zebra" or "bgpd"
+ * constname is the daemons source-level name, primarily for the logging ID,
+ * i.e. "ZEBRA" or "BGP"
+ *
+ * note that this macro is also a latch-on point for other changes (e.g.
+ * upcoming module support) that need to place some per-daemon things. Each
+ * daemon should have one of these.
+ */
+#define FRR_DAEMON_INFO(execname, constname, ...) \
+ static struct frr_daemon_info execname##_di = {.name = #execname, \
+ .logname = #constname, \
+ .module = THIS_MODULE, \
+ __VA_ARGS__}; \
+ FRR_COREMOD_SETUP(.name = #execname, \
+ .description = #execname " daemon", \
+ .version = FRR_VERSION, ); \
+ MACRO_REQUIRE_SEMICOLON() /* end */
+
+extern void frr_init_vtydir(void);
+extern void frr_preinit(struct frr_daemon_info *daemon, int argc, char **argv);
+extern void frr_opt_add(const char *optstr, const struct option *longopts,
+ const char *helpstr);
+extern int frr_getopt(int argc, char *const argv[], int *longindex);
+
+extern __attribute__((__noreturn__)) 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);
+extern uint32_t frr_get_fd_limit(void);
+extern bool frr_is_startup_fd(int fd);
+
+/* call order of these hooks is as ordered here */
+DECLARE_HOOK(frr_early_init, (struct thread_master * tm), (tm));
+DECLARE_HOOK(frr_late_init, (struct thread_master * tm), (tm));
+/* fork() happens between late_init and config_pre */
+DECLARE_HOOK(frr_config_pre, (struct thread_master * tm), (tm));
+DECLARE_HOOK(frr_config_post, (struct thread_master * tm), (tm));
+
+extern void frr_config_fork(void);
+
+extern void frr_run(struct thread_master *master);
+extern void frr_detach(void);
+
+extern bool frr_zclient_addr(struct sockaddr_storage *sa, socklen_t *sa_len,
+ const char *path);
+
+/* these two are before the protocol daemon does its own shutdown
+ * it's named this way being the counterpart to frr_late_init */
+DECLARE_KOOH(frr_early_fini, (), ());
+extern void frr_early_fini(void);
+/* and these two are after the daemon did its own cleanup */
+DECLARE_KOOH(frr_fini, (), ());
+extern void frr_fini(void);
+
+extern char config_default[512];
+extern char frr_zclientpath[256];
+extern const char frr_sysconfdir[];
+extern char frr_vtydir[256];
+extern const char frr_moduledir[];
+extern const char frr_scriptdir[];
+
+extern char frr_protoname[];
+extern char frr_protonameinst[];
+/* always set in the spot where we *would* fork even if we don't do so */
+extern bool frr_is_after_fork;
+
+extern bool debug_memstats_at_exit;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ZEBRA_FRR_H */
diff --git a/lib/libfrr_trace.c b/lib/libfrr_trace.c
new file mode 100644
index 0000000..5932032
--- /dev/null
+++ b/lib/libfrr_trace.c
@@ -0,0 +1,6 @@
+#define TRACEPOINT_CREATE_PROBES
+#define TRACEPOINT_DEFINE
+
+#include <zebra.h>
+
+#include "libfrr_trace.h"
diff --git a/lib/libfrr_trace.h b/lib/libfrr_trace.h
new file mode 100644
index 0000000..7215007
--- /dev/null
+++ b/lib/libfrr_trace.h
@@ -0,0 +1,240 @@
+/* Tracing
+ *
+ * Copyright (C) 2020 NVIDIA Corporation
+ * Quentin Young
+ *
+ * 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
+ */
+
+#if !defined(_LIBFRR_TRACE_H_) || defined(TRACEPOINT_HEADER_MULTI_READ)
+#define _LIBFRR_TRACE_H_
+
+#include "trace.h"
+
+#ifdef HAVE_LTTNG
+
+#undef TRACEPOINT_PROVIDER
+#define TRACEPOINT_PROVIDER frr_libfrr
+
+#undef TRACEPOINT_INCLUDE
+#define TRACEPOINT_INCLUDE "./libfrr_trace.h"
+
+#include <lttng/tracepoint.h>
+
+#include "hash.h"
+#include "thread.h"
+#include "memory.h"
+#include "linklist.h"
+#include "table.h"
+
+/* clang-format off */
+
+TRACEPOINT_EVENT(
+ frr_libfrr,
+ hash_get,
+ TP_ARGS(struct hash *, hash, void *, data),
+ TP_FIELDS(
+ ctf_string(name, hash->name ? hash->name : "(unnamed)")
+ ctf_integer(unsigned int, index_size, hash->size)
+ ctf_integer(unsigned long, item_count, hash->count)
+ ctf_integer_hex(intptr_t, data_ptr, data)
+ )
+)
+
+TRACEPOINT_LOGLEVEL(frr_libfrr, hash_get, TRACE_INFO)
+
+TRACEPOINT_EVENT(
+ frr_libfrr,
+ hash_insert,
+ TP_ARGS(struct hash *, hash, void *, data, unsigned int, key),
+ TP_FIELDS(
+ ctf_string(name, hash->name ? hash->name : "(unnamed)")
+ ctf_integer(unsigned int, key, hash->size)
+ ctf_integer(unsigned int, index_size, hash->size)
+ ctf_integer(unsigned long, item_count, hash->count)
+ ctf_integer_hex(intptr_t, data_ptr, data)
+ )
+)
+
+TRACEPOINT_LOGLEVEL(frr_libfrr, hash_insert, TRACE_INFO)
+
+TRACEPOINT_EVENT(
+ frr_libfrr,
+ hash_release,
+ TP_ARGS(struct hash *, hash, void *, data, void *, released_item),
+ TP_FIELDS(
+ ctf_string(name, hash->name ? hash->name : "(unnamed)")
+ ctf_integer(unsigned int, index_size, hash->size)
+ ctf_integer(unsigned long, item_count, hash->count)
+ ctf_integer_hex(intptr_t, data_ptr, data)
+ ctf_integer_hex(intptr_t, released_item, data)
+ )
+)
+
+TRACEPOINT_LOGLEVEL(frr_libfrr, hash_release, TRACE_INFO)
+
+#define THREAD_SCHEDULE_ARGS \
+ TP_ARGS(struct thread_master *, master, const char *, funcname, \
+ const char *, schedfrom, int, fromln, struct thread **, \
+ thread_ptr, int, fd, int, val, void *, arg, long, time)
+
+TRACEPOINT_EVENT_CLASS(
+ frr_libfrr,
+ thread_operation,
+ THREAD_SCHEDULE_ARGS,
+ TP_FIELDS(
+ ctf_string(threadmaster_name, master->name)
+ ctf_string(function_name, funcname ? funcname : "(unknown function)")
+ ctf_string(scheduled_from, schedfrom ? schedfrom : "(unknown file)")
+ ctf_integer(int, scheduled_on_line, fromln)
+ ctf_integer_hex(intptr_t, thread_addr, thread_ptr ? *thread_ptr : NULL)
+ ctf_integer(int, file_descriptor, fd)
+ ctf_integer(int, event_value, val)
+ ctf_integer_hex(intptr_t, argument_ptr, arg)
+ ctf_integer(long, timer, time)
+ )
+)
+
+#define THREAD_OPERATION_TRACEPOINT_INSTANCE(name) \
+ TRACEPOINT_EVENT_INSTANCE(frr_libfrr, thread_operation, name, \
+ THREAD_SCHEDULE_ARGS) \
+ TRACEPOINT_LOGLEVEL(frr_libfrr, name, TRACE_INFO)
+
+THREAD_OPERATION_TRACEPOINT_INSTANCE(schedule_timer)
+THREAD_OPERATION_TRACEPOINT_INSTANCE(schedule_event)
+THREAD_OPERATION_TRACEPOINT_INSTANCE(schedule_read)
+THREAD_OPERATION_TRACEPOINT_INSTANCE(schedule_write)
+THREAD_OPERATION_TRACEPOINT_INSTANCE(thread_cancel)
+THREAD_OPERATION_TRACEPOINT_INSTANCE(thread_cancel_async)
+THREAD_OPERATION_TRACEPOINT_INSTANCE(thread_call)
+
+TRACEPOINT_EVENT(
+ frr_libfrr,
+ frr_pthread_run,
+ TP_ARGS(
+ char *, name
+ ),
+ TP_FIELDS(
+ ctf_string(frr_pthread_name, name)
+ )
+)
+
+TRACEPOINT_EVENT(
+ frr_libfrr,
+ frr_pthread_stop,
+ TP_ARGS(
+ char *, name
+ ),
+ TP_FIELDS(
+ ctf_string(frr_pthread_name, name)
+ )
+)
+
+TRACEPOINT_EVENT(
+ frr_libfrr,
+ memalloc,
+ TP_ARGS(
+ struct memtype *, mt, void *, ptr, size_t, size
+ ),
+ TP_FIELDS(
+ ctf_string(memtype, mt->name)
+ ctf_integer(size_t, size, size)
+ ctf_integer_hex(intptr_t, ptr, ptr)
+ )
+)
+
+TRACEPOINT_EVENT(
+ frr_libfrr,
+ memfree,
+ TP_ARGS(
+ struct memtype *, mt, void *, ptr
+ ),
+ TP_FIELDS(
+ ctf_string(memtype, mt->name)
+ ctf_integer_hex(intptr_t, ptr, ptr)
+ )
+)
+
+TRACEPOINT_EVENT(
+ frr_libfrr,
+ list_add,
+ TP_ARGS(
+ struct list *, list, const void *, ptr
+ ),
+ TP_FIELDS(
+ ctf_integer_hex(intptr_t, list, list)
+ ctf_integer(unsigned int, count, list->count)
+ ctf_integer_hex(intptr_t, ptr, ptr)
+ )
+)
+
+TRACEPOINT_EVENT(
+ frr_libfrr,
+ list_remove,
+ TP_ARGS(
+ struct list *, list, const void *, ptr
+ ),
+ TP_FIELDS(
+ ctf_integer_hex(intptr_t, list, list)
+ ctf_integer(unsigned int, count, list->count)
+ ctf_integer_hex(intptr_t, ptr, ptr)
+ )
+)
+
+TRACEPOINT_EVENT(
+ frr_libfrr,
+ list_delete_node,
+ TP_ARGS(
+ struct list *, list, const void *, node
+ ),
+ TP_FIELDS(
+ ctf_integer_hex(intptr_t, list, list)
+ ctf_integer(unsigned int, count, list->count)
+ ctf_integer_hex(intptr_t, node, node)
+ )
+)
+
+TRACEPOINT_EVENT(
+ frr_libfrr,
+ list_sort,
+ TP_ARGS(
+ struct list *, list
+ ),
+ TP_FIELDS(
+ ctf_integer_hex(intptr_t, list, list)
+ ctf_integer(unsigned int, count, list->count)
+ )
+)
+
+TRACEPOINT_EVENT(
+ frr_libfrr,
+ route_node_get,
+ TP_ARGS(
+ struct route_table *, table, char *, prefix
+ ),
+ TP_FIELDS(
+ ctf_integer_hex(intptr_t, table, table)
+ ctf_string(prefix, prefix)
+ )
+)
+
+/* clang-format on */
+
+#include <lttng/tracepoint-event.h>
+#include <lttng/tracelog.h>
+
+#endif /* HAVE_LTTNG */
+
+#endif /* _LIBFRR_TRACE_H_ */
diff --git a/lib/libospf.h b/lib/libospf.h
new file mode 100644
index 0000000..161c763
--- /dev/null
+++ b/lib/libospf.h
@@ -0,0 +1,106 @@
+/*
+ * Defines and structures common to OSPFv2 and OSPFv3
+ * Copyright (C) 1998, 99, 2000 Kunihiro Ishiguro, Toshiaki Takada
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 _LIBOSPFD_H
+#define _LIBOSPFD_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* IP precedence. */
+#ifndef IPTOS_PREC_INTERNETCONTROL
+#define IPTOS_PREC_INTERNETCONTROL 0xC0
+#endif /* IPTOS_PREC_INTERNETCONTROL */
+
+/* Default protocol, port number. */
+#ifndef IPPROTO_OSPFIGP
+#define IPPROTO_OSPFIGP 89
+#endif /* IPPROTO_OSPFIGP */
+
+/* Architectural Constants */
+#ifdef DEBUG
+#define OSPF_LS_REFRESH_TIME 120
+#else
+#define OSPF_LS_REFRESH_TIME 1800
+#endif
+#define OSPF_MIN_LS_INTERVAL 5000 /* msec */
+#define OSPF_MIN_LS_ARRIVAL 1000 /* in milliseconds */
+#define OSPF_LSA_INITIAL_AGE 0 /* useful for debug */
+#define OSPF_LSA_MAXAGE 3600
+#define OSPF_CHECK_AGE 300
+#define OSPF_LSA_MAXAGE_DIFF 900
+#define OSPF_LS_INFINITY 0xffffff
+#define OSPF_DEFAULT_DESTINATION 0x00000000 /* 0.0.0.0 */
+#define OSPF_INITIAL_SEQUENCE_NUMBER 0x80000001U
+#define OSPF_MAX_SEQUENCE_NUMBER 0x7fffffffU
+#define OSPF_INVALID_SEQUENCE_NUMBER 0x80000000U
+
+/* OSPF Interface Types */
+#define OSPF_IFTYPE_NONE 0
+#define OSPF_IFTYPE_POINTOPOINT 1
+#define OSPF_IFTYPE_BROADCAST 2
+#define OSPF_IFTYPE_NBMA 3
+#define OSPF_IFTYPE_POINTOMULTIPOINT 4
+#define OSPF_IFTYPE_VIRTUALLINK 5
+#define OSPF_IFTYPE_LOOPBACK 6
+#define OSPF_IFTYPE_MAX 7
+
+/* OSPF interface default values. */
+#define OSPF_OUTPUT_COST_DEFAULT 10
+#define OSPF_OUTPUT_COST_INFINITE UINT16_MAX
+#define OSPF_ROUTER_DEAD_INTERVAL_DEFAULT 40
+#define OSPF_ROUTER_DEAD_INTERVAL_MINIMAL 1
+#define OSPF_HELLO_INTERVAL_DEFAULT 10
+#define OSPF_ROUTER_PRIORITY_DEFAULT 1
+#define OSPF_RETRANSMIT_INTERVAL_DEFAULT 5
+#define OSPF_TRANSMIT_DELAY_DEFAULT 1
+#define OSPF_DEFAULT_BANDWIDTH 10000 /* Mbps */
+
+#define OSPF_DEFAULT_REF_BANDWIDTH 100000 /* Mbps */
+
+#define OSPF_POLL_INTERVAL_DEFAULT 60
+#define OSPF_NEIGHBOR_PRIORITY_DEFAULT 0
+
+#define OSPF_MTU_IGNORE_DEFAULT 0
+#define OSPF_FAST_HELLO_DEFAULT 0
+
+#define OSPF_AREA_BACKBONE 0x00000000 /* 0.0.0.0 */
+#define OSPF_AREA_RANGE_COST_UNSPEC -1U
+
+#define OSPF_AREA_DEFAULT 0
+#define OSPF_AREA_STUB 1
+#define OSPF_AREA_NSSA 2
+#define OSPF_AREA_TYPE_MAX 3
+
+/* SPF Throttling timer values. */
+#define OSPF_SPF_DELAY_DEFAULT 0
+#define OSPF_SPF_HOLDTIME_DEFAULT 50
+#define OSPF_SPF_MAX_HOLDTIME_DEFAULT 5000
+
+#define OSPF_LSA_MAXAGE_CHECK_INTERVAL 30
+#define OSPF_LSA_MAXAGE_REMOVE_DELAY_DEFAULT 60
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LIBOSPFD_H */
diff --git a/lib/link_state.c b/lib/link_state.c
new file mode 100644
index 0000000..7c770e2
--- /dev/null
+++ b/lib/link_state.c
@@ -0,0 +1,2711 @@
+/*
+ * Link State Database - link_state.c
+ *
+ * Author: Olivier Dugeon <olivier.dugeon@orange.com>
+ *
+ * Copyright (C) 2020 Orange http://www.orange.com
+ *
+ * This file is part of Free Range Routing (FRR).
+ *
+ * FRR 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, or (at your option) any
+ * later version.
+ *
+ * FRR 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 "if.h"
+#include "linklist.h"
+#include "log.h"
+#include "command.h"
+#include "termtable.h"
+#include "memory.h"
+#include "prefix.h"
+#include "table.h"
+#include "vty.h"
+#include "zclient.h"
+#include "stream.h"
+#include "sbuf.h"
+#include "printfrr.h"
+#include <lib/json.h>
+#include "link_state.h"
+
+/* Link State Memory allocation */
+DEFINE_MTYPE_STATIC(LIB, LS_DB, "Link State Database");
+
+/**
+ * Link State Node management functions
+ */
+int ls_node_id_same(struct ls_node_id i1, struct ls_node_id i2)
+{
+ if (i1.origin != i2.origin)
+ return 0;
+
+ if (i1.origin == UNKNOWN)
+ return 1;
+
+ if (i1.origin == ISIS_L1 || i1.origin == ISIS_L2) {
+ if (memcmp(i1.id.iso.sys_id, i2.id.iso.sys_id, ISO_SYS_ID_LEN)
+ != 0
+ || (i1.id.iso.level != i2.id.iso.level))
+ return 0;
+ } else {
+ if (!IPV4_ADDR_SAME(&i1.id.ip.addr, &i2.id.ip.addr)
+ || !IPV4_ADDR_SAME(&i1.id.ip.area_id, &i2.id.ip.area_id))
+ return 1;
+ }
+
+ return 1;
+}
+
+struct ls_node *ls_node_new(struct ls_node_id adv, struct in_addr rid,
+ struct in6_addr rid6)
+{
+ struct ls_node *new;
+
+ if (adv.origin == UNKNOWN)
+ return NULL;
+
+ new = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_node));
+ new->adv = adv;
+ if (!IPV4_NET0(rid.s_addr)) {
+ new->router_id = rid;
+ SET_FLAG(new->flags, LS_NODE_ROUTER_ID);
+ } else {
+ if (adv.origin == OSPFv2 || adv.origin == STATIC
+ || adv.origin == DIRECT) {
+ new->router_id = adv.id.ip.addr;
+ SET_FLAG(new->flags, LS_NODE_ROUTER_ID);
+ }
+ }
+ if (!IN6_IS_ADDR_UNSPECIFIED(&rid6)) {
+ new->router_id6 = rid6;
+ SET_FLAG(new->flags, LS_NODE_ROUTER_ID6);
+ }
+ return new;
+}
+
+void ls_node_del(struct ls_node *node)
+{
+ if (!node)
+ return;
+
+ XFREE(MTYPE_LS_DB, node);
+}
+
+int ls_node_same(struct ls_node *n1, struct ls_node *n2)
+{
+ /* First, check pointer */
+ if ((n1 && !n2) || (!n1 && n2))
+ return 0;
+
+ if (n1 == n2)
+ return 1;
+
+ /* Then, verify Flags and Origin */
+ if (n1->flags != n2->flags)
+ return 0;
+
+ if (!ls_node_id_same(n1->adv, n2->adv))
+ return 0;
+
+ /* Finally, check each individual parameters that are valid */
+ if (CHECK_FLAG(n1->flags, LS_NODE_NAME)
+ && (strncmp(n1->name, n2->name, MAX_NAME_LENGTH) != 0))
+ return 0;
+ if (CHECK_FLAG(n1->flags, LS_NODE_ROUTER_ID)
+ && !IPV4_ADDR_SAME(&n1->router_id, &n2->router_id))
+ return 0;
+ if (CHECK_FLAG(n1->flags, LS_NODE_ROUTER_ID6)
+ && !IPV6_ADDR_SAME(&n1->router_id6, &n2->router_id6))
+ return 0;
+ if (CHECK_FLAG(n1->flags, LS_NODE_FLAG)
+ && (n1->node_flag != n2->node_flag))
+ return 0;
+ if (CHECK_FLAG(n1->flags, LS_NODE_TYPE) && (n1->type != n2->type))
+ return 0;
+ if (CHECK_FLAG(n1->flags, LS_NODE_AS_NUMBER)
+ && (n1->as_number != n2->as_number))
+ return 0;
+ if (CHECK_FLAG(n1->flags, LS_NODE_SR)) {
+ if (n1->srgb.flag != n2->srgb.flag
+ || n1->srgb.lower_bound != n2->srgb.lower_bound
+ || n1->srgb.range_size != n2->srgb.range_size)
+ return 0;
+ if ((n1->algo[0] != n2->algo[0])
+ || (n1->algo[1] != n2->algo[1]))
+ return 0;
+ if (CHECK_FLAG(n1->flags, LS_NODE_SRLB)
+ && ((n1->srlb.lower_bound != n2->srlb.lower_bound
+ || n1->srlb.range_size != n2->srlb.range_size)))
+ return 0;
+ if (CHECK_FLAG(n1->flags, LS_NODE_MSD) && (n1->msd != n2->msd))
+ return 0;
+ }
+
+ /* OK, n1 & n2 are equal */
+ return 1;
+}
+
+/**
+ * Link State Attributes management functions
+ */
+struct ls_attributes *ls_attributes_new(struct ls_node_id adv,
+ struct in_addr local,
+ struct in6_addr local6,
+ uint32_t local_id)
+{
+ struct ls_attributes *new;
+
+ if (adv.origin == UNKNOWN)
+ return NULL;
+
+ new = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_attributes));
+ new->adv = adv;
+ if (!IPV4_NET0(local.s_addr)) {
+ new->standard.local = local;
+ SET_FLAG(new->flags, LS_ATTR_LOCAL_ADDR);
+ }
+ if (!IN6_IS_ADDR_UNSPECIFIED(&local6)) {
+ new->standard.local6 = local6;
+ SET_FLAG(new->flags, LS_ATTR_LOCAL_ADDR6);
+ }
+ if (local_id != 0) {
+ new->standard.local_id = local_id;
+ SET_FLAG(new->flags, LS_ATTR_LOCAL_ID);
+ }
+
+ /* Check that almost one identifier is set */
+ if (!CHECK_FLAG(new->flags, LS_ATTR_LOCAL_ADDR | LS_ATTR_LOCAL_ADDR6
+ | LS_ATTR_LOCAL_ID)) {
+ XFREE(MTYPE_LS_DB, new);
+ return NULL;
+ }
+
+ return new;
+}
+
+void ls_attributes_srlg_del(struct ls_attributes *attr)
+{
+ if (!attr)
+ return;
+
+ if (attr->srlgs)
+ XFREE(MTYPE_LS_DB, attr->srlgs);
+
+ attr->srlgs = NULL;
+ attr->srlg_len = 0;
+ UNSET_FLAG(attr->flags, LS_ATTR_SRLG);
+}
+
+void ls_attributes_del(struct ls_attributes *attr)
+{
+ if (!attr)
+ return;
+
+ ls_attributes_srlg_del(attr);
+
+ XFREE(MTYPE_LS_DB, attr);
+}
+
+int ls_attributes_same(struct ls_attributes *l1, struct ls_attributes *l2)
+{
+ /* First, check pointer */
+ if ((l1 && !l2) || (!l1 && l2))
+ return 0;
+
+ if (l1 == l2)
+ return 1;
+
+ /* Then, verify Flags and Origin */
+ if (l1->flags != l2->flags)
+ return 0;
+
+ if (!ls_node_id_same(l1->adv, l2->adv))
+ return 0;
+
+ /* Finally, check each individual parameters that are valid */
+ if (CHECK_FLAG(l1->flags, LS_ATTR_NAME)
+ && strncmp(l1->name, l2->name, MAX_NAME_LENGTH) != 0)
+ return 0;
+ if (CHECK_FLAG(l1->flags, LS_ATTR_METRIC) && (l1->metric != l2->metric))
+ return 0;
+ if (CHECK_FLAG(l1->flags, LS_ATTR_TE_METRIC)
+ && (l1->standard.te_metric != l2->standard.te_metric))
+ return 0;
+ if (CHECK_FLAG(l1->flags, LS_ATTR_ADM_GRP)
+ && (l1->standard.admin_group != l2->standard.admin_group))
+ return 0;
+ if (CHECK_FLAG(l1->flags, LS_ATTR_LOCAL_ADDR)
+ && !IPV4_ADDR_SAME(&l1->standard.local, &l2->standard.local))
+ return 0;
+ if (CHECK_FLAG(l1->flags, LS_ATTR_NEIGH_ADDR)
+ && !IPV4_ADDR_SAME(&l1->standard.remote, &l2->standard.remote))
+ return 0;
+ if (CHECK_FLAG(l1->flags, LS_ATTR_LOCAL_ADDR6)
+ && !IPV6_ADDR_SAME(&l1->standard.local6, &l2->standard.local6))
+ return 0;
+ if (CHECK_FLAG(l1->flags, LS_ATTR_NEIGH_ADDR6)
+ && !IPV6_ADDR_SAME(&l1->standard.remote6, &l2->standard.remote6))
+ return 0;
+ if (CHECK_FLAG(l1->flags, LS_ATTR_LOCAL_ID)
+ && (l1->standard.local_id != l2->standard.local_id))
+ return 0;
+ if (CHECK_FLAG(l1->flags, LS_ATTR_NEIGH_ID)
+ && (l1->standard.remote_id != l2->standard.remote_id))
+ return 0;
+ if (CHECK_FLAG(l1->flags, LS_ATTR_MAX_BW)
+ && (l1->standard.max_bw != l2->standard.max_bw))
+ return 0;
+ if (CHECK_FLAG(l1->flags, LS_ATTR_MAX_RSV_BW)
+ && (l1->standard.max_rsv_bw != l2->standard.max_rsv_bw))
+ return 0;
+ if (CHECK_FLAG(l1->flags, LS_ATTR_UNRSV_BW)
+ && memcmp(&l1->standard.unrsv_bw, &l2->standard.unrsv_bw, 32) != 0)
+ return 0;
+ if (CHECK_FLAG(l1->flags, LS_ATTR_REMOTE_AS)
+ && (l1->standard.remote_as != l2->standard.remote_as))
+ return 0;
+ if (CHECK_FLAG(l1->flags, LS_ATTR_REMOTE_ADDR)
+ && !IPV4_ADDR_SAME(&l1->standard.remote_addr,
+ &l2->standard.remote_addr))
+ return 0;
+ if (CHECK_FLAG(l1->flags, LS_ATTR_REMOTE_ADDR6)
+ && !IPV6_ADDR_SAME(&l1->standard.remote_addr6,
+ &l2->standard.remote_addr6))
+ return 0;
+ if (CHECK_FLAG(l1->flags, LS_ATTR_DELAY)
+ && (l1->extended.delay != l2->extended.delay))
+ return 0;
+ if (CHECK_FLAG(l1->flags, LS_ATTR_MIN_MAX_DELAY)
+ && ((l1->extended.min_delay != l2->extended.min_delay)
+ || (l1->extended.max_delay != l2->extended.max_delay)))
+ return 0;
+ if (CHECK_FLAG(l1->flags, LS_ATTR_JITTER)
+ && (l1->extended.jitter != l2->extended.jitter))
+ return 0;
+ if (CHECK_FLAG(l1->flags, LS_ATTR_PACKET_LOSS)
+ && (l1->extended.pkt_loss != l2->extended.pkt_loss))
+ return 0;
+ if (CHECK_FLAG(l1->flags, LS_ATTR_AVA_BW)
+ && (l1->extended.ava_bw != l2->extended.ava_bw))
+ return 0;
+ if (CHECK_FLAG(l1->flags, LS_ATTR_RSV_BW)
+ && (l1->extended.rsv_bw != l2->extended.rsv_bw))
+ return 0;
+ if (CHECK_FLAG(l1->flags, LS_ATTR_USE_BW)
+ && (l1->extended.used_bw != l2->extended.used_bw))
+ return 0;
+ for (int i = 0; i < LS_ADJ_MAX; i++) {
+ if (!CHECK_FLAG(l1->flags, (LS_ATTR_ADJ_SID << i)))
+ continue;
+ if ((l1->adj_sid[i].sid != l2->adj_sid[i].sid)
+ || (l1->adj_sid[i].flags != l2->adj_sid[i].flags)
+ || (l1->adj_sid[i].weight != l2->adj_sid[i].weight))
+ return 0;
+ if (((l1->adv.origin == ISIS_L1) || (l1->adv.origin == ISIS_L2))
+ && (memcmp(&l1->adj_sid[i].neighbor.sysid,
+ &l2->adj_sid[i].neighbor.sysid, ISO_SYS_ID_LEN)
+ != 0))
+ return 0;
+ if (((l1->adv.origin == OSPFv2) || (l1->adv.origin == STATIC)
+ || (l1->adv.origin == DIRECT))
+ && (i < ADJ_PRI_IPV6)
+ && (!IPV4_ADDR_SAME(&l1->adj_sid[i].neighbor.addr,
+ &l2->adj_sid[i].neighbor.addr)))
+ return 0;
+ }
+ if (CHECK_FLAG(l1->flags, LS_ATTR_SRLG)
+ && ((l1->srlg_len != l2->srlg_len)
+ || memcmp(l1->srlgs, l2->srlgs,
+ l1->srlg_len * sizeof(uint32_t))
+ != 0))
+ return 0;
+
+ /* OK, l1 & l2 are equal */
+ return 1;
+}
+
+/**
+ * Link State prefix management functions
+ */
+struct ls_prefix *ls_prefix_new(struct ls_node_id adv, struct prefix p)
+{
+ struct ls_prefix *new;
+
+ if (adv.origin == UNKNOWN)
+ return NULL;
+
+ new = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_attributes));
+ new->adv = adv;
+ new->pref = p;
+
+ return new;
+}
+
+void ls_prefix_del(struct ls_prefix *pref)
+{
+ if (!pref)
+ return;
+
+ XFREE(MTYPE_LS_DB, pref);
+}
+
+int ls_prefix_same(struct ls_prefix *p1, struct ls_prefix *p2)
+{
+ /* First, check pointer */
+ if ((p1 && !p2) || (!p1 && p2))
+ return 0;
+
+ if (p1 == p2)
+ return 1;
+
+ /* Then, verify Flags and Origin */
+ if (p1->flags != p2->flags)
+ return 0;
+
+ if (!ls_node_id_same(p1->adv, p2->adv))
+ return 0;
+
+ /* Finally, check each individual parameters that are valid */
+ if (prefix_same(&p1->pref, &p2->pref) == 0)
+ return 0;
+ if (CHECK_FLAG(p1->flags, LS_PREF_IGP_FLAG)
+ && (p1->igp_flag != p2->igp_flag))
+ return 0;
+ if (CHECK_FLAG(p1->flags, LS_PREF_ROUTE_TAG)
+ && (p1->route_tag != p2->route_tag))
+ return 0;
+ if (CHECK_FLAG(p1->flags, LS_PREF_EXTENDED_TAG)
+ && (p1->extended_tag != p2->extended_tag))
+ return 0;
+ if (CHECK_FLAG(p1->flags, LS_PREF_METRIC) && (p1->metric != p2->metric))
+ return 0;
+ if (CHECK_FLAG(p1->flags, LS_PREF_SR)) {
+ if ((p1->sr.algo != p2->sr.algo) || (p1->sr.sid != p2->sr.sid)
+ || (p1->sr.sid_flag != p2->sr.sid_flag))
+ return 0;
+ }
+
+ /* OK, p1 & p2 are equal */
+ return 1;
+}
+
+/**
+ * Link State Vertices management functions
+ */
+uint64_t sysid_to_key(const uint8_t sysid[ISO_SYS_ID_LEN])
+{
+ uint64_t key = 0;
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+ uint8_t *byte = (uint8_t *)&key;
+
+ for (int i = 0; i < ISO_SYS_ID_LEN; i++)
+ byte[i] = sysid[ISO_SYS_ID_LEN - i - 1];
+
+ byte[6] = 0;
+ byte[7] = 0;
+#else
+ memcpy(&key, sysid, ISO_SYS_ID_LEN);
+#endif
+
+ return key;
+}
+
+struct ls_vertex *ls_vertex_add(struct ls_ted *ted, struct ls_node *node)
+{
+ struct ls_vertex *new;
+ uint64_t key = 0;
+
+ if ((ted == NULL) || (node == NULL))
+ return NULL;
+
+ /* set Key as the IPv4/Ipv6 Router ID or ISO System ID */
+ switch (node->adv.origin) {
+ case OSPFv2:
+ case STATIC:
+ case DIRECT:
+ key = ((uint64_t)ntohl(node->adv.id.ip.addr.s_addr))
+ & 0xffffffff;
+ break;
+ case ISIS_L1:
+ case ISIS_L2:
+ key = sysid_to_key(node->adv.id.iso.sys_id);
+ break;
+ default:
+ key = 0;
+ break;
+ }
+
+ /* Check that key is valid */
+ if (key == 0)
+ return NULL;
+
+ /* Create Vertex and add it to the TED */
+ new = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_vertex));
+ if (!new)
+ return NULL;
+
+ new->key = key;
+ new->node = node;
+ new->status = NEW;
+ new->type = VERTEX;
+ new->incoming_edges = list_new();
+ new->incoming_edges->cmp = (int (*)(void *, void *))edge_cmp;
+ new->outgoing_edges = list_new();
+ new->outgoing_edges->cmp = (int (*)(void *, void *))edge_cmp;
+ new->prefixes = list_new();
+ new->prefixes->cmp = (int (*)(void *, void *))subnet_cmp;
+ vertices_add(&ted->vertices, new);
+
+ return new;
+}
+
+void ls_vertex_del(struct ls_ted *ted, struct ls_vertex *vertex)
+{
+ struct listnode *node, *nnode;
+ struct ls_edge *edge;
+ struct ls_subnet *subnet;
+
+ if (!ted || !vertex)
+ return;
+
+ /* Remove outgoing Edges and list */
+ for (ALL_LIST_ELEMENTS(vertex->outgoing_edges, node, nnode, edge))
+ ls_edge_del_all(ted, edge);
+ list_delete(&vertex->outgoing_edges);
+
+ /* Disconnect incoming Edges and remove list */
+ for (ALL_LIST_ELEMENTS(vertex->incoming_edges, node, nnode, edge)) {
+ ls_disconnect(vertex, edge, false);
+ if (edge->source == NULL)
+ ls_edge_del_all(ted, edge);
+ }
+ list_delete(&vertex->incoming_edges);
+
+ /* Remove subnet and list */
+ for (ALL_LIST_ELEMENTS(vertex->prefixes, node, nnode, subnet))
+ ls_subnet_del_all(ted, subnet);
+ list_delete(&vertex->prefixes);
+
+ /* Then remove Vertex from Link State Data Base and free memory */
+ vertices_del(&ted->vertices, vertex);
+ XFREE(MTYPE_LS_DB, vertex);
+ vertex = NULL;
+}
+
+void ls_vertex_del_all(struct ls_ted *ted, struct ls_vertex *vertex)
+{
+ if (!ted || !vertex)
+ return;
+
+ /* First remove associated Link State Node */
+ ls_node_del(vertex->node);
+
+ /* Then, Vertex itself */
+ ls_vertex_del(ted, vertex);
+}
+
+struct ls_vertex *ls_vertex_update(struct ls_ted *ted, struct ls_node *node)
+{
+ struct ls_vertex *old;
+
+ if (node == NULL)
+ return NULL;
+
+ old = ls_find_vertex_by_id(ted, node->adv);
+ if (old) {
+ if (!ls_node_same(old->node, node)) {
+ ls_node_del(old->node);
+ old->node = node;
+ }
+ old->status = UPDATE;
+ return old;
+ }
+
+ return ls_vertex_add(ted, node);
+}
+
+struct ls_vertex *ls_find_vertex_by_key(struct ls_ted *ted, const uint64_t key)
+{
+ struct ls_vertex vertex = {};
+
+ if (key == 0)
+ return NULL;
+
+ vertex.key = key;
+ return vertices_find(&ted->vertices, &vertex);
+}
+
+struct ls_vertex *ls_find_vertex_by_id(struct ls_ted *ted,
+ struct ls_node_id nid)
+{
+ struct ls_vertex vertex = {};
+
+ vertex.key = 0;
+ switch (nid.origin) {
+ case OSPFv2:
+ case STATIC:
+ case DIRECT:
+ vertex.key =
+ ((uint64_t)ntohl(nid.id.ip.addr.s_addr)) & 0xffffffff;
+ break;
+ case ISIS_L1:
+ case ISIS_L2:
+ vertex.key = sysid_to_key(nid.id.iso.sys_id);
+ break;
+ default:
+ return NULL;
+ }
+
+ return vertices_find(&ted->vertices, &vertex);
+}
+
+int ls_vertex_same(struct ls_vertex *v1, struct ls_vertex *v2)
+{
+ if ((v1 && !v2) || (!v1 && v2))
+ return 0;
+
+ if (!v1 && !v2)
+ return 1;
+
+ if (v1->key != v2->key)
+ return 0;
+
+ if (v1->node == v2->node)
+ return 1;
+
+ return ls_node_same(v1->node, v2->node);
+}
+
+void ls_vertex_clean(struct ls_ted *ted, struct ls_vertex *vertex,
+ struct zclient *zclient)
+{
+ struct listnode *node, *nnode;
+ struct ls_edge *edge;
+ struct ls_subnet *subnet;
+ struct ls_message msg;
+
+ /* Remove Orphan Edge ... */
+ for (ALL_LIST_ELEMENTS(vertex->outgoing_edges, node, nnode, edge)) {
+ if (edge->status == ORPHAN) {
+ if (zclient) {
+ edge->status = DELETE;
+ ls_edge2msg(&msg, edge);
+ ls_send_msg(zclient, &msg, NULL);
+ }
+ ls_edge_del_all(ted, edge);
+ }
+ }
+ for (ALL_LIST_ELEMENTS(vertex->incoming_edges, node, nnode, edge)) {
+ if (edge->status == ORPHAN) {
+ if (zclient) {
+ edge->status = DELETE;
+ ls_edge2msg(&msg, edge);
+ ls_send_msg(zclient, &msg, NULL);
+ }
+ ls_edge_del_all(ted, edge);
+ }
+ }
+
+ /* ... and Subnet from the Vertex */
+ for (ALL_LIST_ELEMENTS(vertex->prefixes, node, nnode, subnet)) {
+ if (subnet->status == ORPHAN) {
+ if (zclient) {
+ subnet->status = DELETE;
+ ls_subnet2msg(&msg, subnet);
+ ls_send_msg(zclient, &msg, NULL);
+ }
+ ls_subnet_del_all(ted, subnet);
+ }
+ }
+}
+
+/**
+ * Link State Edges management functions
+ */
+
+/**
+ * This function allows to connect the Edge to the vertices present in the TED.
+ * A temporary vertex that corresponds to the source of this Edge i.e. the
+ * advertised router, is created if not found in the Data Base. If a Edge that
+ * corresponds to the reverse path is found, the Edge is attached to the
+ * destination vertex as destination and reverse Edge is attached to the source
+ * vertex as source.
+ *
+ * @param ted Link State Data Base
+ * @param edge Link State Edge to be attached
+ */
+static void ls_edge_connect_to(struct ls_ted *ted, struct ls_edge *edge)
+{
+ struct ls_vertex *vertex = NULL;
+ struct ls_node *node;
+ struct ls_edge *dst;
+ const struct in_addr inaddr_any = {.s_addr = INADDR_ANY};
+
+ /* First, search if there is a Vertex that correspond to the Node ID */
+ vertex = ls_find_vertex_by_id(ted, edge->attributes->adv);
+ if (vertex == NULL) {
+ /* Create a new temporary Node & Vertex if not found */
+ node = ls_node_new(edge->attributes->adv, inaddr_any,
+ in6addr_any);
+ vertex = ls_vertex_add(ted, node);
+ }
+ /* and attach the edge as source to the vertex */
+ listnode_add_sort_nodup(vertex->outgoing_edges, edge);
+ edge->source = vertex;
+
+ /* Then search if there is a reverse Edge */
+ dst = ls_find_edge_by_destination(ted, edge->attributes);
+ /* attach the destination edge to the vertex */
+ if (dst) {
+ listnode_add_sort_nodup(vertex->incoming_edges, dst);
+ dst->destination = vertex;
+ /* and destination vertex to this edge */
+ vertex = dst->source;
+ listnode_add_sort_nodup(vertex->incoming_edges, edge);
+ edge->destination = vertex;
+ }
+}
+
+static uint64_t get_edge_key(struct ls_attributes *attr, bool dst)
+{
+ uint64_t key = 0;
+ struct ls_standard *std;
+
+ if (!attr)
+ return key;
+
+ std = &attr->standard;
+
+ if (dst) {
+ /* Key is the IPv4 remote address */
+ if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ADDR))
+ key = ((uint64_t)ntohl(std->remote.s_addr))
+ & 0xffffffff;
+ /* or the 64 bits LSB of IPv6 remote address */
+ else if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ADDR6))
+ key = ((uint64_t)ntohl(std->remote6.s6_addr32[2]) << 32
+ | (uint64_t)ntohl(std->remote6.s6_addr32[3]));
+ /* of remote identifier if no IP addresses are defined */
+ else if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ID))
+ key = (((uint64_t)std->remote_id) & 0xffffffff)
+ | ((uint64_t)std->local_id << 32);
+ } else {
+ /* Key is the IPv4 local address */
+ if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR))
+ key = ((uint64_t)ntohl(std->local.s_addr)) & 0xffffffff;
+ /* or the 64 bits LSB of IPv6 local address */
+ else if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR6))
+ key = ((uint64_t)ntohl(std->local6.s6_addr32[2]) << 32
+ | (uint64_t)ntohl(std->local6.s6_addr32[3]));
+ /* of local identifier if no IP addresses are defined */
+ else if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ID))
+ key = (((uint64_t)std->local_id) & 0xffffffff)
+ | ((uint64_t)std->remote_id << 32);
+ }
+
+ return key;
+}
+
+struct ls_edge *ls_edge_add(struct ls_ted *ted,
+ struct ls_attributes *attributes)
+{
+ struct ls_edge *new;
+ uint64_t key = 0;
+
+ if (attributes == NULL)
+ return NULL;
+
+ key = get_edge_key(attributes, false);
+ if (key == 0)
+ return NULL;
+
+ /* Create Edge and add it to the TED */
+ new = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_edge));
+
+ new->attributes = attributes;
+ new->key = key;
+ new->status = NEW;
+ new->type = EDGE;
+ edges_add(&ted->edges, new);
+
+ /* Finally, connect Edge to Vertices */
+ ls_edge_connect_to(ted, new);
+
+ return new;
+}
+
+struct ls_edge *ls_find_edge_by_key(struct ls_ted *ted, const uint64_t key)
+{
+ struct ls_edge edge = {};
+
+ if (key == 0)
+ return NULL;
+
+ edge.key = key;
+ return edges_find(&ted->edges, &edge);
+}
+
+struct ls_edge *ls_find_edge_by_source(struct ls_ted *ted,
+ struct ls_attributes *attributes)
+{
+ struct ls_edge edge = {};
+
+ if (attributes == NULL)
+ return NULL;
+
+ edge.key = get_edge_key(attributes, false);
+ if (edge.key == 0)
+ return NULL;
+
+ return edges_find(&ted->edges, &edge);
+}
+
+struct ls_edge *ls_find_edge_by_destination(struct ls_ted *ted,
+ struct ls_attributes *attributes)
+{
+ struct ls_edge edge = {};
+
+ if (attributes == NULL)
+ return NULL;
+
+ edge.key = get_edge_key(attributes, true);
+ if (edge.key == 0)
+ return NULL;
+
+ return edges_find(&ted->edges, &edge);
+}
+
+struct ls_edge *ls_edge_update(struct ls_ted *ted,
+ struct ls_attributes *attributes)
+{
+ struct ls_edge *old;
+
+ if (attributes == NULL)
+ return NULL;
+
+ /* First, search for an existing Edge */
+ old = ls_find_edge_by_source(ted, attributes);
+ if (old) {
+ /* Check if attributes are similar */
+ if (!ls_attributes_same(old->attributes, attributes)) {
+ ls_attributes_del(old->attributes);
+ old->attributes = attributes;
+ }
+ old->status = UPDATE;
+ return old;
+ }
+
+ /* If not found, add new Edge from the attributes */
+ return ls_edge_add(ted, attributes);
+}
+
+int ls_edge_same(struct ls_edge *e1, struct ls_edge *e2)
+{
+ if ((e1 && !e2) || (!e1 && e2))
+ return 0;
+
+ if (!e1 && !e2)
+ return 1;
+
+ if (e1->key != e2->key)
+ return 0;
+
+ if (e1->attributes == e2->attributes)
+ return 1;
+
+ return ls_attributes_same(e1->attributes, e2->attributes);
+}
+
+void ls_edge_del(struct ls_ted *ted, struct ls_edge *edge)
+{
+ if (!ted || !edge)
+ return;
+
+ /* Fist disconnect Edge from Vertices */
+ ls_disconnect_edge(edge);
+ /* Then remove it from the Data Base */
+ edges_del(&ted->edges, edge);
+ XFREE(MTYPE_LS_DB, edge);
+}
+
+void ls_edge_del_all(struct ls_ted *ted, struct ls_edge *edge)
+{
+ if (!ted || !edge)
+ return;
+
+ /* Remove associated Link State Attributes */
+ ls_attributes_del(edge->attributes);
+ /* Then Edge itself */
+ ls_edge_del(ted, edge);
+}
+
+/**
+ * Link State Subnet Management functions.
+ */
+struct ls_subnet *ls_subnet_add(struct ls_ted *ted,
+ struct ls_prefix *ls_pref)
+{
+ struct ls_subnet *new;
+ struct ls_vertex *vertex;
+ struct ls_node *node;
+ const struct in_addr inaddr_any = {.s_addr = INADDR_ANY};
+
+ if (ls_pref == NULL)
+ return NULL;
+
+ new = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_subnet));
+ new->ls_pref = ls_pref;
+ new->key = ls_pref->pref;
+ new->status = NEW;
+ new->type = SUBNET;
+
+ /* Find Vertex */
+ vertex = ls_find_vertex_by_id(ted, ls_pref->adv);
+ if (vertex == NULL) {
+ /* Create a new temporary Node & Vertex if not found */
+ node = ls_node_new(ls_pref->adv, inaddr_any, in6addr_any);
+ vertex = ls_vertex_add(ted, node);
+ }
+ /* And attach the subnet to the corresponding Vertex */
+ new->vertex = vertex;
+ listnode_add_sort_nodup(vertex->prefixes, new);
+
+ subnets_add(&ted->subnets, new);
+
+ return new;
+}
+
+struct ls_subnet *ls_subnet_update(struct ls_ted *ted, struct ls_prefix *pref)
+{
+ struct ls_subnet *old;
+
+ if (pref == NULL)
+ return NULL;
+
+ old = ls_find_subnet(ted, pref->pref);
+ if (old) {
+ if (!ls_prefix_same(old->ls_pref, pref)) {
+ ls_prefix_del(old->ls_pref);
+ old->ls_pref = pref;
+ }
+ old->status = UPDATE;
+ return old;
+ }
+
+ return ls_subnet_add(ted, pref);
+}
+
+int ls_subnet_same(struct ls_subnet *s1, struct ls_subnet *s2)
+{
+ if ((s1 && !s2) || (!s1 && s2))
+ return 0;
+
+ if (!s1 && !s2)
+ return 1;
+
+ if (!prefix_same(&s1->key, &s2->key))
+ return 0;
+
+ if (s1->ls_pref == s2->ls_pref)
+ return 1;
+
+ return ls_prefix_same(s1->ls_pref, s2->ls_pref);
+}
+
+void ls_subnet_del(struct ls_ted *ted, struct ls_subnet *subnet)
+{
+ if (!ted || !subnet)
+ return;
+
+ /* First, disconnect Subnet from associated Vertex */
+ listnode_delete(subnet->vertex->prefixes, subnet);
+ /* Then delete Subnet */
+ subnets_del(&ted->subnets, subnet);
+ XFREE(MTYPE_LS_DB, subnet);
+}
+
+void ls_subnet_del_all(struct ls_ted *ted, struct ls_subnet *subnet)
+{
+ if (!ted || !subnet)
+ return;
+
+ /* First, remove associated Link State Subnet */
+ ls_prefix_del(subnet->ls_pref);
+ /* Then, delete Subnet itself */
+ ls_subnet_del(ted, subnet);
+}
+
+struct ls_subnet *ls_find_subnet(struct ls_ted *ted, const struct prefix prefix)
+{
+ struct ls_subnet subnet = {};
+
+ subnet.key = prefix;
+ return subnets_find(&ted->subnets, &subnet);
+}
+
+/**
+ * Link State TED management functions
+ */
+struct ls_ted *ls_ted_new(const uint32_t key, const char *name,
+ uint32_t as_number)
+{
+ struct ls_ted *new;
+
+ new = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_ted));
+
+ /* Set basic information for this ted */
+ new->key = key;
+ new->as_number = as_number;
+ strlcpy(new->name, name, MAX_NAME_LENGTH);
+
+ /* Initialize the various RB tree */
+ vertices_init(&new->vertices);
+ edges_init(&new->edges);
+ subnets_init(&new->subnets);
+
+ return new;
+}
+
+void ls_ted_del(struct ls_ted *ted)
+{
+ if (ted == NULL)
+ return;
+
+ /* Check that TED is empty */
+ if (vertices_count(&ted->vertices) || edges_count(&ted->edges)
+ || subnets_count(&ted->subnets))
+ return;
+
+ /* Release RB Tree */
+ vertices_fini(&ted->vertices);
+ edges_fini(&ted->edges);
+ subnets_fini(&ted->subnets);
+
+ XFREE(MTYPE_LS_DB, ted);
+}
+
+void ls_ted_del_all(struct ls_ted **ted)
+{
+ struct ls_vertex *vertex;
+ struct ls_edge *edge;
+ struct ls_subnet *subnet;
+
+ if (*ted == NULL)
+ return;
+
+ /* First remove Vertices, Edges and Subnets and associated Link State */
+ frr_each_safe (vertices, &(*ted)->vertices, vertex)
+ ls_vertex_del_all(*ted, vertex);
+ frr_each_safe (edges, &(*ted)->edges, edge)
+ ls_edge_del_all(*ted, edge);
+ frr_each_safe (subnets, &(*ted)->subnets, subnet)
+ ls_subnet_del_all(*ted, subnet);
+
+ /* then remove TED itself */
+ ls_ted_del(*ted);
+ *ted = NULL;
+}
+
+void ls_ted_clean(struct ls_ted *ted)
+{
+ struct ls_vertex *vertex;
+ struct ls_edge *edge;
+ struct ls_subnet *subnet;
+
+ if (ted == NULL)
+ return;
+
+ /* First, start with Vertices */
+ frr_each_safe (vertices, &ted->vertices, vertex)
+ if (vertex->status == ORPHAN)
+ ls_vertex_del_all(ted, vertex);
+
+ /* Then Edges */
+ frr_each_safe (edges, &ted->edges, edge)
+ if (edge->status == ORPHAN)
+ ls_edge_del_all(ted, edge);
+
+ /* and Subnets */
+ frr_each_safe (subnets, &ted->subnets, subnet)
+ if (subnet->status == ORPHAN)
+ ls_subnet_del_all(ted, subnet);
+
+}
+
+void ls_connect(struct ls_vertex *vertex, struct ls_edge *edge, bool source)
+{
+ if (vertex == NULL || edge == NULL)
+ return;
+
+ if (source) {
+ listnode_add_sort_nodup(vertex->outgoing_edges, edge);
+ edge->source = vertex;
+ } else {
+ listnode_add_sort_nodup(vertex->incoming_edges, edge);
+ edge->destination = vertex;
+ }
+}
+
+void ls_disconnect(struct ls_vertex *vertex, struct ls_edge *edge, bool source)
+{
+
+ if (vertex == NULL || edge == NULL)
+ return;
+
+ if (source) {
+ listnode_delete(vertex->outgoing_edges, edge);
+ edge->source = NULL;
+ } else {
+ listnode_delete(vertex->incoming_edges, edge);
+ edge->destination = NULL;
+ }
+}
+
+void ls_connect_vertices(struct ls_vertex *src, struct ls_vertex *dst,
+ struct ls_edge *edge)
+{
+ if (edge == NULL)
+ return;
+
+ edge->source = src;
+ edge->destination = dst;
+
+ if (src != NULL)
+ listnode_add_sort_nodup(src->outgoing_edges, edge);
+
+ if (dst != NULL)
+ listnode_add_sort_nodup(dst->incoming_edges, edge);
+}
+
+void ls_disconnect_edge(struct ls_edge *edge)
+{
+ if (edge == NULL)
+ return;
+
+ ls_disconnect(edge->source, edge, true);
+ ls_disconnect(edge->destination, edge, false);
+
+ /* Mark this Edge as ORPHAN for future cleanup */
+ edge->status = ORPHAN;
+}
+
+/**
+ * Link State Message management functions
+ */
+
+int ls_register(struct zclient *zclient, bool server)
+{
+ int rc;
+
+ if (server)
+ rc = zclient_register_opaque(zclient, LINK_STATE_SYNC);
+ else
+ rc = zclient_register_opaque(zclient, LINK_STATE_UPDATE);
+
+ return rc;
+}
+
+int ls_unregister(struct zclient *zclient, bool server)
+{
+ int rc;
+
+ if (server)
+ rc = zclient_unregister_opaque(zclient, LINK_STATE_SYNC);
+ else
+ rc = zclient_unregister_opaque(zclient, LINK_STATE_UPDATE);
+
+ return rc;
+}
+
+int ls_request_sync(struct zclient *zclient)
+{
+ struct stream *s;
+ uint16_t flags = 0;
+
+ /* Check buffer size */
+ if (STREAM_SIZE(zclient->obuf)
+ < (ZEBRA_HEADER_SIZE + 3 * sizeof(uint32_t)))
+ return -1;
+
+ s = zclient->obuf;
+ stream_reset(s);
+
+ zclient_create_header(s, ZEBRA_OPAQUE_MESSAGE, VRF_DEFAULT);
+
+ /* Set type and flags */
+ stream_putl(s, LINK_STATE_SYNC);
+ stream_putw(s, flags);
+ /* Send destination client info */
+ stream_putc(s, zclient->redist_default);
+ stream_putw(s, zclient->instance);
+ stream_putl(s, zclient->session_id);
+
+ /* Put length into the header at the start of the stream. */
+ stream_putw_at(s, 0, stream_get_endp(s));
+
+ return zclient_send_message(zclient);
+}
+
+static struct ls_node *ls_parse_node(struct stream *s)
+{
+ struct ls_node *node;
+ size_t len;
+
+ node = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_node));
+
+ STREAM_GET(&node->adv, s, sizeof(struct ls_node_id));
+ STREAM_GETW(s, node->flags);
+ if (CHECK_FLAG(node->flags, LS_NODE_NAME)) {
+ STREAM_GETC(s, len);
+ STREAM_GET(node->name, s, len);
+ }
+ if (CHECK_FLAG(node->flags, LS_NODE_ROUTER_ID))
+ node->router_id.s_addr = stream_get_ipv4(s);
+ if (CHECK_FLAG(node->flags, LS_NODE_ROUTER_ID6))
+ STREAM_GET(&node->router_id6, s, IPV6_MAX_BYTELEN);
+ if (CHECK_FLAG(node->flags, LS_NODE_FLAG))
+ STREAM_GETC(s, node->node_flag);
+ if (CHECK_FLAG(node->flags, LS_NODE_TYPE))
+ STREAM_GETC(s, node->type);
+ if (CHECK_FLAG(node->flags, LS_NODE_AS_NUMBER))
+ STREAM_GETL(s, node->as_number);
+ if (CHECK_FLAG(node->flags, LS_NODE_SR)) {
+ STREAM_GETL(s, node->srgb.lower_bound);
+ STREAM_GETL(s, node->srgb.range_size);
+ STREAM_GETC(s, node->srgb.flag);
+ STREAM_GET(node->algo, s, 2);
+ }
+ if (CHECK_FLAG(node->flags, LS_NODE_SRLB)) {
+ STREAM_GETL(s, node->srlb.lower_bound);
+ STREAM_GETL(s, node->srlb.range_size);
+ }
+ if (CHECK_FLAG(node->flags, LS_NODE_MSD))
+ STREAM_GETC(s, node->msd);
+
+ return node;
+
+stream_failure:
+ zlog_err("LS(%s): Could not parse Link State Node. Abort!", __func__);
+ XFREE(MTYPE_LS_DB, node);
+ return NULL;
+}
+
+static struct ls_attributes *ls_parse_attributes(struct stream *s)
+{
+ struct ls_attributes *attr;
+ size_t len;
+
+ attr = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_attributes));
+ attr->srlgs = NULL;
+
+ STREAM_GET(&attr->adv, s, sizeof(struct ls_node_id));
+ STREAM_GETL(s, attr->flags);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_NAME)) {
+ STREAM_GETC(s, len);
+ STREAM_GET(attr->name, s, len);
+ }
+ if (CHECK_FLAG(attr->flags, LS_ATTR_METRIC))
+ STREAM_GETL(s, attr->metric);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_TE_METRIC))
+ STREAM_GETL(s, attr->standard.te_metric);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_ADM_GRP))
+ STREAM_GETL(s, attr->standard.admin_group);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR))
+ attr->standard.local.s_addr = stream_get_ipv4(s);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ADDR))
+ attr->standard.remote.s_addr = stream_get_ipv4(s);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR6))
+ STREAM_GET(&attr->standard.local6, s, IPV6_MAX_BYTELEN);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ADDR6))
+ STREAM_GET(&attr->standard.remote6, s, IPV6_MAX_BYTELEN);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ID))
+ STREAM_GETL(s, attr->standard.local_id);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ID))
+ STREAM_GETL(s, attr->standard.remote_id);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_MAX_BW))
+ STREAM_GETF(s, attr->standard.max_bw);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_MAX_RSV_BW))
+ STREAM_GETF(s, attr->standard.max_rsv_bw);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_UNRSV_BW))
+ for (len = 0; len < MAX_CLASS_TYPE; len++)
+ STREAM_GETF(s, attr->standard.unrsv_bw[len]);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_REMOTE_AS))
+ STREAM_GETL(s, attr->standard.remote_as);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_REMOTE_ADDR))
+ attr->standard.remote_addr.s_addr = stream_get_ipv4(s);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_REMOTE_ADDR6))
+ STREAM_GET(&attr->standard.remote_addr6, s, IPV6_MAX_BYTELEN);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_DELAY))
+ STREAM_GETL(s, attr->extended.delay);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_MIN_MAX_DELAY)) {
+ STREAM_GETL(s, attr->extended.min_delay);
+ STREAM_GETL(s, attr->extended.max_delay);
+ }
+ if (CHECK_FLAG(attr->flags, LS_ATTR_JITTER))
+ STREAM_GETL(s, attr->extended.jitter);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_PACKET_LOSS))
+ STREAM_GETL(s, attr->extended.pkt_loss);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_AVA_BW))
+ STREAM_GETF(s, attr->extended.ava_bw);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_RSV_BW))
+ STREAM_GETF(s, attr->extended.rsv_bw);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_USE_BW))
+ STREAM_GETF(s, attr->extended.used_bw);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_ADJ_SID)) {
+ STREAM_GETL(s, attr->adj_sid[ADJ_PRI_IPV4].sid);
+ STREAM_GETC(s, attr->adj_sid[ADJ_PRI_IPV4].flags);
+ STREAM_GETC(s, attr->adj_sid[ADJ_PRI_IPV4].weight);
+ attr->adj_sid[ADJ_PRI_IPV4].neighbor.addr.s_addr =
+ stream_get_ipv4(s);
+ }
+ if (CHECK_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SID)) {
+ STREAM_GETL(s, attr->adj_sid[ADJ_BCK_IPV4].sid);
+ STREAM_GETC(s, attr->adj_sid[ADJ_BCK_IPV4].flags);
+ STREAM_GETC(s, attr->adj_sid[ADJ_BCK_IPV4].weight);
+ attr->adj_sid[ADJ_BCK_IPV4].neighbor.addr.s_addr =
+ stream_get_ipv4(s);
+ }
+ if (CHECK_FLAG(attr->flags, LS_ATTR_ADJ_SID6)) {
+ STREAM_GETL(s, attr->adj_sid[ADJ_PRI_IPV6].sid);
+ STREAM_GETC(s, attr->adj_sid[ADJ_PRI_IPV6].flags);
+ STREAM_GETC(s, attr->adj_sid[ADJ_PRI_IPV6].weight);
+ STREAM_GET(attr->adj_sid[ADJ_PRI_IPV6].neighbor.sysid, s,
+ ISO_SYS_ID_LEN);
+ }
+ if (CHECK_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SID6)) {
+ STREAM_GETL(s, attr->adj_sid[ADJ_BCK_IPV6].sid);
+ STREAM_GETC(s, attr->adj_sid[ADJ_BCK_IPV6].flags);
+ STREAM_GETC(s, attr->adj_sid[ADJ_BCK_IPV6].weight);
+ STREAM_GET(attr->adj_sid[ADJ_BCK_IPV6].neighbor.sysid, s,
+ ISO_SYS_ID_LEN);
+ }
+ if (CHECK_FLAG(attr->flags, LS_ATTR_SRLG)) {
+ STREAM_GETC(s, len);
+ attr->srlgs = XCALLOC(MTYPE_LS_DB, len*sizeof(uint32_t));
+ attr->srlg_len = len;
+ for (len = 0; len < attr->srlg_len; len++)
+ STREAM_GETL(s, attr->srlgs[len]);
+ }
+
+ return attr;
+
+stream_failure:
+ zlog_err("LS(%s): Could not parse Link State Attributes. Abort!",
+ __func__);
+ /* Clean memory allocation */
+ if (attr->srlgs != NULL)
+ XFREE(MTYPE_LS_DB, attr->srlgs);
+ XFREE(MTYPE_LS_DB, attr);
+ return NULL;
+
+}
+
+static struct ls_prefix *ls_parse_prefix(struct stream *s)
+{
+ struct ls_prefix *ls_pref;
+ size_t len;
+
+ ls_pref = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_prefix));
+
+ STREAM_GET(&ls_pref->adv, s, sizeof(struct ls_node_id));
+ STREAM_GETW(s, ls_pref->flags);
+ STREAM_GETC(s, ls_pref->pref.family);
+ STREAM_GETW(s, ls_pref->pref.prefixlen);
+ len = prefix_blen(&ls_pref->pref);
+ STREAM_GET(&ls_pref->pref.u.prefix, s, len);
+ if (CHECK_FLAG(ls_pref->flags, LS_PREF_IGP_FLAG))
+ STREAM_GETC(s, ls_pref->igp_flag);
+ if (CHECK_FLAG(ls_pref->flags, LS_PREF_ROUTE_TAG))
+ STREAM_GETL(s, ls_pref->route_tag);
+ if (CHECK_FLAG(ls_pref->flags, LS_PREF_EXTENDED_TAG))
+ STREAM_GETQ(s, ls_pref->extended_tag);
+ if (CHECK_FLAG(ls_pref->flags, LS_PREF_METRIC))
+ STREAM_GETL(s, ls_pref->metric);
+ if (CHECK_FLAG(ls_pref->flags, LS_PREF_SR)) {
+ STREAM_GETL(s, ls_pref->sr.sid);
+ STREAM_GETC(s, ls_pref->sr.sid_flag);
+ STREAM_GETC(s, ls_pref->sr.algo);
+ }
+
+ return ls_pref;
+
+stream_failure:
+ zlog_err("LS(%s): Could not parse Link State Prefix. Abort!", __func__);
+ XFREE(MTYPE_LS_DB, ls_pref);
+ return NULL;
+}
+
+struct ls_message *ls_parse_msg(struct stream *s)
+{
+ struct ls_message *msg;
+
+ msg = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_message));
+
+ /* Read LS Message header */
+ STREAM_GETC(s, msg->event);
+ STREAM_GETC(s, msg->type);
+
+ /* Read Message Payload */
+ switch (msg->type) {
+ case LS_MSG_TYPE_NODE:
+ msg->data.node = ls_parse_node(s);
+ break;
+ case LS_MSG_TYPE_ATTRIBUTES:
+ STREAM_GET(&msg->remote_id, s, sizeof(struct ls_node_id));
+ msg->data.attr = ls_parse_attributes(s);
+ break;
+ case LS_MSG_TYPE_PREFIX:
+ msg->data.prefix = ls_parse_prefix(s);
+ break;
+ default:
+ zlog_err("Unsupported Payload");
+ goto stream_failure;
+ }
+
+ if (msg->data.node == NULL || msg->data.attr == NULL
+ || msg->data.prefix == NULL)
+ goto stream_failure;
+
+ return msg;
+
+stream_failure:
+ zlog_err("LS(%s): Could not parse LS message. Abort!", __func__);
+ XFREE(MTYPE_LS_DB, msg);
+ return NULL;
+}
+
+static int ls_format_node(struct stream *s, struct ls_node *node)
+{
+ size_t len;
+
+ /* Push Advertise node information first */
+ stream_put(s, &node->adv, sizeof(struct ls_node_id));
+
+ /* Push Flags & Origin then Node information if there are present */
+ stream_putw(s, node->flags);
+ if (CHECK_FLAG(node->flags, LS_NODE_NAME)) {
+ len = strlen(node->name);
+ stream_putc(s, len + 1);
+ stream_put(s, node->name, len);
+ stream_putc(s, '\0');
+ }
+ if (CHECK_FLAG(node->flags, LS_NODE_ROUTER_ID))
+ stream_put_ipv4(s, node->router_id.s_addr);
+ if (CHECK_FLAG(node->flags, LS_NODE_ROUTER_ID6))
+ stream_put(s, &node->router_id6, IPV6_MAX_BYTELEN);
+ if (CHECK_FLAG(node->flags, LS_NODE_FLAG))
+ stream_putc(s, node->node_flag);
+ if (CHECK_FLAG(node->flags, LS_NODE_TYPE))
+ stream_putc(s, node->type);
+ if (CHECK_FLAG(node->flags, LS_NODE_AS_NUMBER))
+ stream_putl(s, node->as_number);
+ if (CHECK_FLAG(node->flags, LS_NODE_SR)) {
+ stream_putl(s, node->srgb.lower_bound);
+ stream_putl(s, node->srgb.range_size);
+ stream_putc(s, node->srgb.flag);
+ stream_put(s, node->algo, 2);
+ }
+ if (CHECK_FLAG(node->flags, LS_NODE_SRLB)) {
+ stream_putl(s, node->srlb.lower_bound);
+ stream_putl(s, node->srlb.range_size);
+ }
+ if (CHECK_FLAG(node->flags, LS_NODE_MSD))
+ stream_putc(s, node->msd);
+
+ return 0;
+}
+
+static int ls_format_attributes(struct stream *s, struct ls_attributes *attr)
+{
+ size_t len;
+
+ /* Push Advertise node information first */
+ stream_put(s, &attr->adv, sizeof(struct ls_node_id));
+
+ /* Push Flags & Origin then LS attributes if there are present */
+ stream_putl(s, attr->flags);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_NAME)) {
+ len = strlen(attr->name);
+ stream_putc(s, len + 1);
+ stream_put(s, attr->name, len);
+ stream_putc(s, '\0');
+ }
+ if (CHECK_FLAG(attr->flags, LS_ATTR_METRIC))
+ stream_putl(s, attr->metric);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_TE_METRIC))
+ stream_putl(s, attr->standard.te_metric);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_ADM_GRP))
+ stream_putl(s, attr->standard.admin_group);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR))
+ stream_put_ipv4(s, attr->standard.local.s_addr);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ADDR))
+ stream_put_ipv4(s, attr->standard.remote.s_addr);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR6))
+ stream_put(s, &attr->standard.local6, IPV6_MAX_BYTELEN);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ADDR6))
+ stream_put(s, &attr->standard.remote6, IPV6_MAX_BYTELEN);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ID))
+ stream_putl(s, attr->standard.local_id);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ID))
+ stream_putl(s, attr->standard.remote_id);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_MAX_BW))
+ stream_putf(s, attr->standard.max_bw);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_MAX_RSV_BW))
+ stream_putf(s, attr->standard.max_rsv_bw);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_UNRSV_BW))
+ for (len = 0; len < MAX_CLASS_TYPE; len++)
+ stream_putf(s, attr->standard.unrsv_bw[len]);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_REMOTE_AS))
+ stream_putl(s, attr->standard.remote_as);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_REMOTE_ADDR))
+ stream_put_ipv4(s, attr->standard.remote_addr.s_addr);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_REMOTE_ADDR6))
+ stream_put(s, &attr->standard.remote_addr6, IPV6_MAX_BYTELEN);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_DELAY))
+ stream_putl(s, attr->extended.delay);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_MIN_MAX_DELAY)) {
+ stream_putl(s, attr->extended.min_delay);
+ stream_putl(s, attr->extended.max_delay);
+ }
+ if (CHECK_FLAG(attr->flags, LS_ATTR_JITTER))
+ stream_putl(s, attr->extended.jitter);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_PACKET_LOSS))
+ stream_putl(s, attr->extended.pkt_loss);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_AVA_BW))
+ stream_putf(s, attr->extended.ava_bw);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_RSV_BW))
+ stream_putf(s, attr->extended.rsv_bw);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_USE_BW))
+ stream_putf(s, attr->extended.used_bw);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_ADJ_SID)) {
+ stream_putl(s, attr->adj_sid[ADJ_PRI_IPV4].sid);
+ stream_putc(s, attr->adj_sid[ADJ_PRI_IPV4].flags);
+ stream_putc(s, attr->adj_sid[ADJ_PRI_IPV4].weight);
+ stream_put_ipv4(
+ s, attr->adj_sid[ADJ_PRI_IPV4].neighbor.addr.s_addr);
+ }
+ if (CHECK_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SID)) {
+ stream_putl(s, attr->adj_sid[ADJ_BCK_IPV4].sid);
+ stream_putc(s, attr->adj_sid[ADJ_BCK_IPV4].flags);
+ stream_putc(s, attr->adj_sid[ADJ_BCK_IPV4].weight);
+ stream_put_ipv4(
+ s, attr->adj_sid[ADJ_BCK_IPV4].neighbor.addr.s_addr);
+ }
+ if (CHECK_FLAG(attr->flags, LS_ATTR_ADJ_SID6)) {
+ stream_putl(s, attr->adj_sid[ADJ_PRI_IPV6].sid);
+ stream_putc(s, attr->adj_sid[ADJ_PRI_IPV6].flags);
+ stream_putc(s, attr->adj_sid[ADJ_PRI_IPV6].weight);
+ stream_put(s, attr->adj_sid[ADJ_PRI_IPV6].neighbor.sysid,
+ ISO_SYS_ID_LEN);
+ }
+ if (CHECK_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SID6)) {
+ stream_putl(s, attr->adj_sid[ADJ_BCK_IPV6].sid);
+ stream_putc(s, attr->adj_sid[ADJ_BCK_IPV6].flags);
+ stream_putc(s, attr->adj_sid[ADJ_BCK_IPV6].weight);
+ stream_put(s, attr->adj_sid[ADJ_BCK_IPV6].neighbor.sysid,
+ ISO_SYS_ID_LEN);
+ }
+ if (CHECK_FLAG(attr->flags, LS_ATTR_SRLG)) {
+ stream_putc(s, attr->srlg_len);
+ for (len = 0; len < attr->srlg_len; len++)
+ stream_putl(s, attr->srlgs[len]);
+ }
+
+ return 0;
+}
+
+static int ls_format_prefix(struct stream *s, struct ls_prefix *ls_pref)
+{
+ size_t len;
+
+ /* Push Advertise node information first */
+ stream_put(s, &ls_pref->adv, sizeof(struct ls_node_id));
+
+ /* Push Flags, Origin & Prefix then information if there are present */
+ stream_putw(s, ls_pref->flags);
+ stream_putc(s, ls_pref->pref.family);
+ stream_putw(s, ls_pref->pref.prefixlen);
+ len = prefix_blen(&ls_pref->pref);
+ stream_put(s, &ls_pref->pref.u.prefix, len);
+ if (CHECK_FLAG(ls_pref->flags, LS_PREF_IGP_FLAG))
+ stream_putc(s, ls_pref->igp_flag);
+ if (CHECK_FLAG(ls_pref->flags, LS_PREF_ROUTE_TAG))
+ stream_putl(s, ls_pref->route_tag);
+ if (CHECK_FLAG(ls_pref->flags, LS_PREF_EXTENDED_TAG))
+ stream_putq(s, ls_pref->extended_tag);
+ if (CHECK_FLAG(ls_pref->flags, LS_PREF_METRIC))
+ stream_putl(s, ls_pref->metric);
+ if (CHECK_FLAG(ls_pref->flags, LS_PREF_SR)) {
+ stream_putl(s, ls_pref->sr.sid);
+ stream_putc(s, ls_pref->sr.sid_flag);
+ stream_putc(s, ls_pref->sr.algo);
+ }
+
+ return 0;
+}
+
+static int ls_format_msg(struct stream *s, struct ls_message *msg)
+{
+
+ /* Prepare Link State header */
+ stream_putc(s, msg->event);
+ stream_putc(s, msg->type);
+
+ /* Add Message Payload */
+ switch (msg->type) {
+ case LS_MSG_TYPE_NODE:
+ return ls_format_node(s, msg->data.node);
+ case LS_MSG_TYPE_ATTRIBUTES:
+ /* Add remote node first */
+ stream_put(s, &msg->remote_id, sizeof(struct ls_node_id));
+ return ls_format_attributes(s, msg->data.attr);
+ case LS_MSG_TYPE_PREFIX:
+ return ls_format_prefix(s, msg->data.prefix);
+ default:
+ zlog_warn("Unsupported Payload");
+ break;
+ }
+
+ return -1;
+}
+
+int ls_send_msg(struct zclient *zclient, struct ls_message *msg,
+ struct zapi_opaque_reg_info *dst)
+{
+ struct stream *s;
+ uint16_t flags = 0;
+
+ /* Check if we have a valid message */
+ if (msg->event == LS_MSG_EVENT_UNDEF)
+ return -1;
+
+ /* Check buffer size */
+ if (STREAM_SIZE(zclient->obuf) <
+ (ZEBRA_HEADER_SIZE + sizeof(uint32_t) + sizeof(msg)))
+ return -1;
+
+ s = zclient->obuf;
+ stream_reset(s);
+
+ zclient_create_header(s, ZEBRA_OPAQUE_MESSAGE, VRF_DEFAULT);
+
+ /* Set sub-type, flags and destination for unicast message */
+ stream_putl(s, LINK_STATE_UPDATE);
+ if (dst != NULL) {
+ SET_FLAG(flags, ZAPI_OPAQUE_FLAG_UNICAST);
+ stream_putw(s, flags);
+ /* Send destination client info */
+ stream_putc(s, dst->proto);
+ stream_putw(s, dst->instance);
+ stream_putl(s, dst->session_id);
+ } else {
+ stream_putw(s, flags);
+ }
+
+ /* Format Link State message */
+ if (ls_format_msg(s, msg) < 0) {
+ stream_reset(s);
+ return -1;
+ }
+
+ /* Put length into the header at the start of the stream. */
+ stream_putw_at(s, 0, stream_get_endp(s));
+
+ return zclient_send_message(zclient);
+}
+struct ls_message *ls_vertex2msg(struct ls_message *msg,
+ struct ls_vertex *vertex)
+{
+ /* Allocate space if needed */
+ if (msg == NULL)
+ msg = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_message));
+ else
+ memset(msg, 0, sizeof(*msg));
+
+ msg->type = LS_MSG_TYPE_NODE;
+ switch (vertex->status) {
+ case NEW:
+ msg->event = LS_MSG_EVENT_ADD;
+ break;
+ case UPDATE:
+ msg->event = LS_MSG_EVENT_UPDATE;
+ break;
+ case DELETE:
+ msg->event = LS_MSG_EVENT_DELETE;
+ break;
+ case SYNC:
+ msg->event = LS_MSG_EVENT_SYNC;
+ break;
+ default:
+ msg->event = LS_MSG_EVENT_UNDEF;
+ break;
+ }
+ msg->data.node = vertex->node;
+ msg->remote_id.origin = UNKNOWN;
+
+ return msg;
+}
+
+struct ls_message *ls_edge2msg(struct ls_message *msg, struct ls_edge *edge)
+{
+ /* Allocate space if needed */
+ if (msg == NULL)
+ msg = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_message));
+ else
+ memset(msg, 0, sizeof(*msg));
+
+ msg->type = LS_MSG_TYPE_ATTRIBUTES;
+ switch (edge->status) {
+ case NEW:
+ msg->event = LS_MSG_EVENT_ADD;
+ break;
+ case UPDATE:
+ msg->event = LS_MSG_EVENT_UPDATE;
+ break;
+ case DELETE:
+ msg->event = LS_MSG_EVENT_DELETE;
+ break;
+ case SYNC:
+ msg->event = LS_MSG_EVENT_SYNC;
+ break;
+ default:
+ msg->event = LS_MSG_EVENT_UNDEF;
+ break;
+ }
+ msg->data.attr = edge->attributes;
+ if (edge->destination != NULL)
+ msg->remote_id = edge->destination->node->adv;
+ else
+ msg->remote_id.origin = UNKNOWN;
+
+ return msg;
+}
+
+struct ls_message *ls_subnet2msg(struct ls_message *msg,
+ struct ls_subnet *subnet)
+{
+ /* Allocate space if needed */
+ if (msg == NULL)
+ msg = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_message));
+ else
+ memset(msg, 0, sizeof(*msg));
+
+ msg->type = LS_MSG_TYPE_PREFIX;
+ switch (subnet->status) {
+ case NEW:
+ msg->event = LS_MSG_EVENT_ADD;
+ break;
+ case UPDATE:
+ msg->event = LS_MSG_EVENT_UPDATE;
+ break;
+ case DELETE:
+ msg->event = LS_MSG_EVENT_DELETE;
+ break;
+ case SYNC:
+ msg->event = LS_MSG_EVENT_SYNC;
+ break;
+ default:
+ msg->event = LS_MSG_EVENT_UNDEF;
+ break;
+ }
+ msg->data.prefix = subnet->ls_pref;
+ msg->remote_id.origin = UNKNOWN;
+
+ return msg;
+}
+
+struct ls_vertex *ls_msg2vertex(struct ls_ted *ted, struct ls_message *msg,
+ bool delete)
+{
+ struct ls_node *node = (struct ls_node *)msg->data.node;
+ struct ls_vertex *vertex = NULL;
+
+ switch (msg->event) {
+ case LS_MSG_EVENT_SYNC:
+ vertex = ls_vertex_add(ted, node);
+ if (vertex)
+ vertex->status = SYNC;
+ break;
+ case LS_MSG_EVENT_ADD:
+ vertex = ls_vertex_add(ted, node);
+ if (vertex)
+ vertex->status = NEW;
+ break;
+ case LS_MSG_EVENT_UPDATE:
+ vertex = ls_vertex_update(ted, node);
+ if (vertex)
+ vertex->status = UPDATE;
+ break;
+ case LS_MSG_EVENT_DELETE:
+ vertex = ls_find_vertex_by_id(ted, node->adv);
+ if (vertex) {
+ if (delete)
+ ls_vertex_del_all(ted, vertex);
+ else
+ vertex->status = DELETE;
+ }
+ break;
+ default:
+ vertex = NULL;
+ break;
+ }
+
+ return vertex;
+}
+
+struct ls_edge *ls_msg2edge(struct ls_ted *ted, struct ls_message *msg,
+ bool delete)
+{
+ struct ls_attributes *attr = (struct ls_attributes *)msg->data.attr;
+ struct ls_edge *edge = NULL;
+
+ switch (msg->event) {
+ case LS_MSG_EVENT_SYNC:
+ edge = ls_edge_add(ted, attr);
+ if (edge)
+ edge->status = SYNC;
+ break;
+ case LS_MSG_EVENT_ADD:
+ edge = ls_edge_add(ted, attr);
+ if (edge)
+ edge->status = NEW;
+ break;
+ case LS_MSG_EVENT_UPDATE:
+ edge = ls_edge_update(ted, attr);
+ if (edge)
+ edge->status = UPDATE;
+ break;
+ case LS_MSG_EVENT_DELETE:
+ edge = ls_find_edge_by_source(ted, attr);
+ if (edge) {
+ if (delete) {
+ ls_edge_del_all(ted, edge);
+ edge = NULL;
+ } else
+ edge->status = DELETE;
+ }
+ break;
+ default:
+ edge = NULL;
+ break;
+ }
+
+ return edge;
+}
+
+struct ls_subnet *ls_msg2subnet(struct ls_ted *ted, struct ls_message *msg,
+ bool delete)
+{
+ struct ls_prefix *pref = (struct ls_prefix *)msg->data.prefix;
+ struct ls_subnet *subnet = NULL;
+
+ switch (msg->event) {
+ case LS_MSG_EVENT_SYNC:
+ subnet = ls_subnet_add(ted, pref);
+ if (subnet)
+ subnet->status = SYNC;
+ break;
+ case LS_MSG_EVENT_ADD:
+ subnet = ls_subnet_add(ted, pref);
+ if (subnet)
+ subnet->status = NEW;
+ break;
+ case LS_MSG_EVENT_UPDATE:
+ subnet = ls_subnet_update(ted, pref);
+ if (subnet)
+ subnet->status = UPDATE;
+ break;
+ case LS_MSG_EVENT_DELETE:
+ subnet = ls_find_subnet(ted, pref->pref);
+ if (subnet) {
+ if (delete)
+ ls_subnet_del_all(ted, subnet);
+ else
+ subnet->status = DELETE;
+ }
+ break;
+ default:
+ subnet = NULL;
+ break;
+ }
+
+ return subnet;
+}
+
+struct ls_element *ls_msg2ted(struct ls_ted *ted, struct ls_message *msg,
+ bool delete)
+{
+ struct ls_element *lse = NULL;
+
+ switch (msg->type) {
+ case LS_MSG_TYPE_NODE:
+ lse = (struct ls_element *)ls_msg2vertex(ted, msg, delete);
+ break;
+ case LS_MSG_TYPE_ATTRIBUTES:
+ lse = (struct ls_element *)ls_msg2edge(ted, msg, delete);
+ break;
+ case LS_MSG_TYPE_PREFIX:
+ lse = (struct ls_element *)ls_msg2subnet(ted, msg, delete);
+ break;
+ default:
+ lse = NULL;
+ break;
+ }
+
+ return lse;
+}
+
+struct ls_element *ls_stream2ted(struct ls_ted *ted, struct stream *s,
+ bool delete)
+{
+ struct ls_message *msg;
+ struct ls_element *lse = NULL;
+
+ msg = ls_parse_msg(s);
+ if (msg) {
+ lse = ls_msg2ted(ted, msg, delete);
+ ls_delete_msg(msg);
+ }
+
+ return lse;
+}
+
+void ls_delete_msg(struct ls_message *msg)
+{
+ if (msg == NULL)
+ return;
+
+ if (msg->event == LS_MSG_EVENT_DELETE) {
+ switch (msg->type) {
+ case LS_MSG_TYPE_NODE:
+ ls_node_del(msg->data.node);
+ break;
+ case LS_MSG_TYPE_ATTRIBUTES:
+ ls_attributes_del(msg->data.attr);
+ break;
+ case LS_MSG_TYPE_PREFIX:
+ ls_prefix_del(msg->data.prefix);
+ break;
+ }
+ }
+
+ XFREE(MTYPE_LS_DB, msg);
+}
+
+int ls_sync_ted(struct ls_ted *ted, struct zclient *zclient,
+ struct zapi_opaque_reg_info *dst)
+{
+ struct ls_vertex *vertex;
+ struct ls_edge *edge;
+ struct ls_subnet *subnet;
+ struct ls_message msg;
+
+ /* Loop TED, start sending Node, then Attributes and finally Prefix */
+ frr_each(vertices, &ted->vertices, vertex) {
+ ls_vertex2msg(&msg, vertex);
+ ls_send_msg(zclient, &msg, dst);
+ }
+ frr_each(edges, &ted->edges, edge) {
+ ls_edge2msg(&msg, edge);
+ ls_send_msg(zclient, &msg, dst);
+ }
+ frr_each(subnets, &ted->subnets, subnet) {
+ ls_subnet2msg(&msg, subnet);
+ ls_send_msg(zclient, &msg, dst);
+ }
+ return 0;
+}
+
+/**
+ * Link State Show functions
+ */
+static const char *const origin2txt[] = {
+ "Unknown",
+ "ISIS_L1",
+ "ISIS_L2",
+ "OSPFv2",
+ "Direct",
+ "Static"
+};
+
+static const char *const type2txt[] = {
+ "Unknown",
+ "Standard",
+ "ABR",
+ "ASBR",
+ "Remote ASBR",
+ "Pseudo"
+};
+
+static const char *const status2txt[] = {
+ "Unknown",
+ "New",
+ "Update",
+ "Delete",
+ "Sync",
+ "Orphan"
+};
+
+static const char *ls_node_id_to_text(struct ls_node_id lnid, char *str,
+ size_t size)
+{
+ if (lnid.origin == ISIS_L1 || lnid.origin == ISIS_L2) {
+ uint8_t *id;
+
+ id = lnid.id.iso.sys_id;
+ snprintfrr(str, size, "%02x%02x.%02x%02x.%02x%02x", id[0],
+ id[1], id[2], id[3], id[4], id[5]);
+ } else
+ snprintfrr(str, size, "%pI4", &lnid.id.ip.addr);
+
+ return str;
+}
+
+static void ls_show_vertex_vty(struct ls_vertex *vertex, struct vty *vty,
+ bool verbose)
+{
+ struct listnode *node;
+ struct ls_node *lsn;
+ struct ls_edge *edge;
+ struct ls_attributes *attr;
+ struct ls_subnet *subnet;
+ struct sbuf sbuf;
+ uint32_t upper;
+
+ /* Sanity Check */
+ if (!vertex)
+ return;
+
+ lsn = vertex->node;
+
+ sbuf_init(&sbuf, NULL, 0);
+
+ sbuf_push(&sbuf, 2, "Vertex (%" PRIu64 "): %s", vertex->key, lsn->name);
+ sbuf_push(&sbuf, 0, "\tRouter Id: %pI4", &lsn->router_id);
+ sbuf_push(&sbuf, 0, "\tOrigin: %s", origin2txt[lsn->adv.origin]);
+ sbuf_push(&sbuf, 0, "\tStatus: %s\n", status2txt[vertex->status]);
+ if (!verbose) {
+ sbuf_push(
+ &sbuf, 0,
+ "\t%d Outgoing Edges, %d Incoming Edges, %d Subnets\n",
+ listcount(vertex->outgoing_edges),
+ listcount(vertex->incoming_edges),
+ listcount(vertex->prefixes));
+ goto end;
+ }
+
+ if (CHECK_FLAG(lsn->flags, LS_NODE_TYPE))
+ sbuf_push(&sbuf, 4, "Type: %s\n", type2txt[lsn->type]);
+ if (CHECK_FLAG(lsn->flags, LS_NODE_AS_NUMBER))
+ sbuf_push(&sbuf, 4, "AS number: %u\n", lsn->as_number);
+ if (CHECK_FLAG(lsn->flags, LS_NODE_SR)) {
+ sbuf_push(&sbuf, 4, "Segment Routing Capabilities:\n");
+ upper = lsn->srgb.lower_bound + lsn->srgb.range_size - 1;
+ sbuf_push(&sbuf, 8, "SRGB: [%d/%d]", lsn->srgb.lower_bound,
+ upper);
+ if (CHECK_FLAG(lsn->flags, LS_NODE_SRLB)) {
+ upper = lsn->srlb.lower_bound + lsn->srlb.range_size
+ - 1;
+ sbuf_push(&sbuf, 0, "\tSRLB: [%d/%d]",
+ lsn->srlb.lower_bound, upper);
+ }
+ sbuf_push(&sbuf, 0, "\tAlgo: ");
+ for (int i = 0; i < 2; i++) {
+ if (lsn->algo[i] == 255)
+ continue;
+
+ sbuf_push(&sbuf, 0,
+ lsn->algo[i] == 0 ? "SPF " : "S-SPF ");
+ }
+ if (CHECK_FLAG(lsn->flags, LS_NODE_MSD))
+ sbuf_push(&sbuf, 0, "\tMSD: %d", lsn->msd);
+ sbuf_push(&sbuf, 0, "\n");
+ }
+
+ sbuf_push(&sbuf, 4, "Outgoing Edges: %d\n",
+ listcount(vertex->outgoing_edges));
+ for (ALL_LIST_ELEMENTS_RO(vertex->outgoing_edges, node, edge)) {
+ if (edge->destination) {
+ lsn = edge->destination->node;
+ sbuf_push(&sbuf, 6, "To:\t%s(%pI4)", lsn->name,
+ &lsn->router_id);
+ } else {
+ sbuf_push(&sbuf, 6, "To:\t- (0.0.0.0)");
+ }
+ attr = edge->attributes;
+ if ((CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR)))
+ sbuf_push(&sbuf, 0, "\tLocal: %pI4\tRemote: %pI4\n",
+ &attr->standard.local,
+ &attr->standard.remote);
+ else if ((CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR6)))
+ sbuf_push(&sbuf, 0, "\tLocal: %pI6\tRemote: %pI6\n",
+ &attr->standard.local6,
+ &attr->standard.remote6);
+ }
+
+ sbuf_push(&sbuf, 4, "Incoming Edges: %d\n",
+ listcount(vertex->incoming_edges));
+ for (ALL_LIST_ELEMENTS_RO(vertex->incoming_edges, node, edge)) {
+ if (edge->source) {
+ lsn = edge->source->node;
+ sbuf_push(&sbuf, 6, "From:\t%s(%pI4)", lsn->name,
+ &lsn->router_id);
+ } else {
+ sbuf_push(&sbuf, 6, "From:\t- (0.0.0.0)");
+ }
+ attr = edge->attributes;
+ if ((CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR)))
+ sbuf_push(&sbuf, 0, "\tLocal: %pI4\tRemote: %pI4\n",
+ &attr->standard.local,
+ &attr->standard.remote);
+ else if ((CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR6)))
+ sbuf_push(&sbuf, 0, "\tLocal: %pI6\tRemote: %pI6\n",
+ &attr->standard.local6,
+ &attr->standard.remote6);
+ }
+
+ sbuf_push(&sbuf, 4, "Subnets: %d\n", listcount(vertex->prefixes));
+ for (ALL_LIST_ELEMENTS_RO(vertex->prefixes, node, subnet))
+ sbuf_push(&sbuf, 6, "Prefix:\t%pFX\n", &subnet->key);
+
+end:
+ vty_out(vty, "%s\n", sbuf_buf(&sbuf));
+ sbuf_free(&sbuf);
+}
+
+static void ls_show_vertex_json(struct ls_vertex *vertex,
+ struct json_object *json)
+{
+ struct ls_node *lsn;
+ json_object *jsr, *jalgo, *jobj;
+ char buf[INET6_BUFSIZ];
+
+ /* Sanity Check */
+ if (!vertex)
+ return;
+
+ lsn = vertex->node;
+
+ json_object_int_add(json, "vertex-id", vertex->key);
+ json_object_string_add(json, "status", status2txt[vertex->status]);
+ json_object_string_add(json, "origin", origin2txt[lsn->adv.origin]);
+ if (CHECK_FLAG(lsn->flags, LS_NODE_NAME))
+ json_object_string_add(json, "name", lsn->name);
+ if (CHECK_FLAG(lsn->flags, LS_NODE_ROUTER_ID)) {
+ snprintfrr(buf, INET6_BUFSIZ, "%pI4", &lsn->router_id);
+ json_object_string_add(json, "router-id", buf);
+ }
+ if (CHECK_FLAG(lsn->flags, LS_NODE_ROUTER_ID6)) {
+ snprintfrr(buf, INET6_BUFSIZ, "%pI6", &lsn->router_id6);
+ json_object_string_add(json, "router-id-v6", buf);
+ }
+ if (CHECK_FLAG(lsn->flags, LS_NODE_TYPE))
+ json_object_string_add(json, "vertex-type",
+ type2txt[lsn->type]);
+ if (CHECK_FLAG(lsn->flags, LS_NODE_AS_NUMBER))
+ json_object_int_add(json, "asn", lsn->as_number);
+ if (CHECK_FLAG(lsn->flags, LS_NODE_SR)) {
+ jsr = json_object_new_object();
+ json_object_object_add(json, "segment-routing", jsr);
+ json_object_int_add(jsr, "srgb-size", lsn->srgb.range_size);
+ json_object_int_add(jsr, "srgb-lower", lsn->srgb.lower_bound);
+ jalgo = json_object_new_array();
+ json_object_object_add(jsr, "algorithms", jalgo);
+ for (int i = 0; i < 2; i++) {
+ if (lsn->algo[i] == 255)
+ continue;
+ jobj = json_object_new_object();
+
+ snprintfrr(buf, 2, "%u", i);
+ json_object_string_add(
+ jobj, buf, lsn->algo[i] == 0 ? "SPF" : "S-SPF");
+ json_object_array_add(jalgo, jobj);
+ }
+ if (CHECK_FLAG(lsn->flags, LS_NODE_SRLB)) {
+ json_object_int_add(jsr, "srlb-size",
+ lsn->srlb.range_size);
+ json_object_int_add(jsr, "srlb-lower",
+ lsn->srlb.lower_bound);
+ }
+ if (CHECK_FLAG(lsn->flags, LS_NODE_MSD))
+ json_object_int_add(jsr, "msd", lsn->msd);
+ }
+}
+
+void ls_show_vertex(struct ls_vertex *vertex, struct vty *vty,
+ struct json_object *json, bool verbose)
+{
+ if (json)
+ ls_show_vertex_json(vertex, json);
+ else if (vty)
+ ls_show_vertex_vty(vertex, vty, verbose);
+}
+
+void ls_show_vertices(struct ls_ted *ted, struct vty *vty,
+ struct json_object *json, bool verbose)
+{
+ struct ls_vertex *vertex;
+ json_object *jnodes, *jnode;
+
+ if (json) {
+ jnodes = json_object_new_array();
+ json_object_object_add(json, "vertices", jnodes);
+ frr_each (vertices, &ted->vertices, vertex) {
+ jnode = json_object_new_object();
+ ls_show_vertex(vertex, NULL, jnode, verbose);
+ json_object_array_add(jnodes, jnode);
+ }
+ } else if (vty) {
+ frr_each (vertices, &ted->vertices, vertex)
+ ls_show_vertex(vertex, vty, NULL, verbose);
+ }
+}
+
+static void ls_show_edge_vty(struct ls_edge *edge, struct vty *vty,
+ bool verbose)
+{
+ struct ls_attributes *attr;
+ struct sbuf sbuf;
+ char buf[INET6_BUFSIZ];
+
+ attr = edge->attributes;
+ sbuf_init(&sbuf, NULL, 0);
+
+ sbuf_push(&sbuf, 2, "Edge (%" PRIu64 "): ", edge->key);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR))
+ sbuf_push(&sbuf, 0, "%pI4", &attr->standard.local);
+ else if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR6))
+ sbuf_push(&sbuf, 0, "%pI6", &attr->standard.local6);
+ else
+ sbuf_push(&sbuf, 0, "%u/%u", attr->standard.local_id,
+ attr->standard.remote_id);
+ ls_node_id_to_text(attr->adv, buf, INET6_BUFSIZ);
+ sbuf_push(&sbuf, 0, "\tAdv. Vertex: %s", buf);
+ sbuf_push(&sbuf, 0, "\tMetric: %u", attr->metric);
+ sbuf_push(&sbuf, 0, "\tStatus: %s\n", status2txt[edge->status]);
+
+ if (!verbose)
+ goto end;
+
+ sbuf_push(&sbuf, 4, "Origin: %s\n", origin2txt[attr->adv.origin]);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_NAME))
+ sbuf_push(&sbuf, 4, "Name: %s\n", attr->name);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_TE_METRIC))
+ sbuf_push(&sbuf, 4, "TE Metric: %u\n",
+ attr->standard.te_metric);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_ADM_GRP))
+ sbuf_push(&sbuf, 4, "Admin Group: 0x%x\n",
+ attr->standard.admin_group);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR))
+ sbuf_push(&sbuf, 4, "Local IPv4 address: %pI4\n",
+ &attr->standard.local);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ADDR))
+ sbuf_push(&sbuf, 4, "Remote IPv4 address: %pI4\n",
+ &attr->standard.remote);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR6))
+ sbuf_push(&sbuf, 4, "Local IPv6 address: %pI6\n",
+ &attr->standard.local6);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ADDR6))
+ sbuf_push(&sbuf, 4, "Remote IPv6 address: %pI6\n",
+ &attr->standard.remote6);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ID))
+ sbuf_push(&sbuf, 4, "Local Identifier: %u\n",
+ attr->standard.local_id);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ID))
+ sbuf_push(&sbuf, 4, "Remote Identifier: %u\n",
+ attr->standard.remote_id);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_MAX_BW))
+ sbuf_push(&sbuf, 4, "Maximum Bandwidth: %g (Bytes/s)\n",
+ attr->standard.max_bw);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_MAX_RSV_BW))
+ sbuf_push(&sbuf, 4,
+ "Maximum Reservable Bandwidth: %g (Bytes/s)\n",
+ attr->standard.max_rsv_bw);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_UNRSV_BW)) {
+ sbuf_push(&sbuf, 4, "Unreserved Bandwidth per Class Type\n");
+ for (int i = 0; i < MAX_CLASS_TYPE; i += 2)
+ sbuf_push(&sbuf, 8,
+ "[%d]: %g (Bytes/sec)\t[%d]: %g (Bytes/s)\n",
+ i, attr->standard.unrsv_bw[i], i + 1,
+ attr->standard.unrsv_bw[i + 1]);
+ }
+ if (CHECK_FLAG(attr->flags, LS_ATTR_REMOTE_AS))
+ sbuf_push(&sbuf, 4, "Remote AS: %u\n",
+ attr->standard.remote_as);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_REMOTE_ADDR))
+ sbuf_push(&sbuf, 4, "Remote ASBR IPv4 address: %pI4\n",
+ &attr->standard.remote_addr);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_REMOTE_ADDR6))
+ sbuf_push(&sbuf, 4, "Remote ASBR IPv6 address: %pI6\n",
+ &attr->standard.remote_addr6);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_DELAY))
+ sbuf_push(&sbuf, 4, "Average Link Delay: %d (micro-sec)\n",
+ attr->extended.delay);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_MIN_MAX_DELAY))
+ sbuf_push(&sbuf, 4, "Min/Max Link Delay: %d/%d (micro-sec)\n",
+ attr->extended.min_delay, attr->extended.max_delay);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_JITTER))
+ sbuf_push(&sbuf, 4, "Delay Variation: %d (micro-sec)\n",
+ attr->extended.jitter);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_PACKET_LOSS))
+ sbuf_push(&sbuf, 4, "Link Loss: %g (%%)\n",
+ (float)(attr->extended.pkt_loss * LOSS_PRECISION));
+ if (CHECK_FLAG(attr->flags, LS_ATTR_AVA_BW))
+ sbuf_push(&sbuf, 4, "Available Bandwidth: %g (Bytes/s)\n",
+ attr->extended.ava_bw);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_RSV_BW))
+ sbuf_push(&sbuf, 4, "Residual Bandwidth: %g (Bytes/s)\n",
+ attr->extended.rsv_bw);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_USE_BW))
+ sbuf_push(&sbuf, 4, "Utilized Bandwidth: %g (Bytes/s)\n",
+ attr->extended.used_bw);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_ADJ_SID)) {
+ sbuf_push(&sbuf, 4, "IPv4 Adjacency-SID: %u",
+ attr->adj_sid[ADJ_PRI_IPV4].sid);
+ sbuf_push(&sbuf, 0, "\tFlags: 0x%x\tWeight: 0x%x\n",
+ attr->adj_sid[ADJ_PRI_IPV4].flags,
+ attr->adj_sid[ADJ_PRI_IPV4].weight);
+ }
+ if (CHECK_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SID)) {
+ sbuf_push(&sbuf, 4, "IPv4 Bck. Adjacency-SID: %u",
+ attr->adj_sid[ADJ_BCK_IPV4].sid);
+ sbuf_push(&sbuf, 0, "\tFlags: 0x%x\tWeight: 0x%x\n",
+ attr->adj_sid[ADJ_BCK_IPV4].flags,
+ attr->adj_sid[ADJ_BCK_IPV4].weight);
+ }
+ if (CHECK_FLAG(attr->flags, LS_ATTR_ADJ_SID6)) {
+ sbuf_push(&sbuf, 4, "IPv6 Adjacency-SID: %u",
+ attr->adj_sid[ADJ_PRI_IPV6].sid);
+ sbuf_push(&sbuf, 0, "\tFlags: 0x%x\tWeight: 0x%x\n",
+ attr->adj_sid[ADJ_PRI_IPV6].flags,
+ attr->adj_sid[ADJ_PRI_IPV6].weight);
+ }
+ if (CHECK_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SID6)) {
+ sbuf_push(&sbuf, 4, "IPv6 Bck. Adjacency-SID: %u",
+ attr->adj_sid[ADJ_BCK_IPV6].sid);
+ sbuf_push(&sbuf, 0, "\tFlags: 0x%x\tWeight: 0x%x\n",
+ attr->adj_sid[ADJ_BCK_IPV6].flags,
+ attr->adj_sid[ADJ_BCK_IPV6].weight);
+ }
+ if (CHECK_FLAG(attr->flags, LS_ATTR_SRLG)) {
+ sbuf_push(&sbuf, 4, "SRLGs: %d", attr->srlg_len);
+ for (int i = 1; i < attr->srlg_len; i++) {
+ if (i % 8)
+ sbuf_push(&sbuf, 8, "\n%u", attr->srlgs[i]);
+ else
+ sbuf_push(&sbuf, 8, ", %u", attr->srlgs[i]);
+ }
+ sbuf_push(&sbuf, 0, "\n");
+ }
+
+end:
+ vty_out(vty, "%s\n", sbuf_buf(&sbuf));
+ sbuf_free(&sbuf);
+}
+
+static void ls_show_edge_json(struct ls_edge *edge, struct json_object *json)
+{
+ struct ls_attributes *attr;
+ struct json_object *jte, *jbw, *jobj, *jsr = NULL, *jsrlg;
+ char buf[INET6_BUFSIZ];
+
+ attr = edge->attributes;
+
+ json_object_int_add(json, "edge-id", edge->key);
+ json_object_string_add(json, "status", status2txt[edge->status]);
+ json_object_string_add(json, "origin", origin2txt[attr->adv.origin]);
+ ls_node_id_to_text(attr->adv, buf, INET6_BUFSIZ);
+ json_object_string_add(json, "advertised-router", buf);
+ if (edge->source)
+ json_object_int_add(json, "local-vertex-id", edge->source->key);
+ if (edge->destination)
+ json_object_int_add(json, "remote-vertex-id",
+ edge->destination->key);
+ json_object_int_add(json, "metric", attr->metric);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_NAME))
+ json_object_string_add(json, "name", attr->name);
+ jte = json_object_new_object();
+ json_object_object_add(json, "edge-attributes", jte);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_TE_METRIC))
+ json_object_int_add(jte, "te-metric", attr->standard.te_metric);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_ADM_GRP))
+ json_object_int_add(jte, "admin-group",
+ attr->standard.admin_group);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR)) {
+ snprintfrr(buf, INET6_BUFSIZ, "%pI4", &attr->standard.local);
+ json_object_string_add(jte, "local-address", buf);
+ }
+ if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ADDR)) {
+ snprintfrr(buf, INET6_BUFSIZ, "%pI4", &attr->standard.remote);
+ json_object_string_add(jte, "remote-address", buf);
+ }
+ if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR6)) {
+ snprintfrr(buf, INET6_BUFSIZ, "%pI6", &attr->standard.local6);
+ json_object_string_add(jte, "local-address-v6", buf);
+ }
+ if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ADDR6)) {
+ snprintfrr(buf, INET6_BUFSIZ, "%pI6", &attr->standard.remote6);
+ json_object_string_add(jte, "remote-address-v6", buf);
+ }
+ if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ID))
+ json_object_int_add(jte, "local-identifier",
+ attr->standard.local_id);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ID))
+ json_object_int_add(jte, "remote-identifier",
+ attr->standard.remote_id);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_MAX_BW))
+ json_object_double_add(jte, "max-link-bandwidth",
+ attr->standard.max_bw);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_MAX_RSV_BW))
+ json_object_double_add(jte, "max-resv-link-bandwidth",
+ attr->standard.max_rsv_bw);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_UNRSV_BW)) {
+ jbw = json_object_new_array();
+ json_object_object_add(jte, "unreserved-bandwidth", jbw);
+ for (int i = 0; i < MAX_CLASS_TYPE; i++) {
+ jobj = json_object_new_object();
+ snprintfrr(buf, 13, "class-type-%u", i);
+ json_object_double_add(jobj, buf,
+ attr->standard.unrsv_bw[i]);
+ json_object_array_add(jbw, jobj);
+ }
+ }
+ if (CHECK_FLAG(attr->flags, LS_ATTR_REMOTE_AS))
+ json_object_int_add(jte, "remote-asn",
+ attr->standard.remote_as);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_REMOTE_ADDR)) {
+ snprintfrr(buf, INET6_BUFSIZ, "%pI4",
+ &attr->standard.remote_addr);
+ json_object_string_add(jte, "remote-as-address", buf);
+ }
+ if (CHECK_FLAG(attr->flags, LS_ATTR_REMOTE_ADDR6)) {
+ snprintfrr(buf, INET6_BUFSIZ, "%pI6",
+ &attr->standard.remote_addr6);
+ json_object_string_add(jte, "remote-as-address-v6", buf);
+ }
+ if (CHECK_FLAG(attr->flags, LS_ATTR_DELAY))
+ json_object_int_add(jte, "delay", attr->extended.delay);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_MIN_MAX_DELAY)) {
+ json_object_int_add(jte, "min-delay", attr->extended.min_delay);
+ json_object_int_add(jte, "max-delay", attr->extended.max_delay);
+ }
+ if (CHECK_FLAG(attr->flags, LS_ATTR_JITTER))
+ json_object_int_add(jte, "jitter", attr->extended.jitter);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_PACKET_LOSS))
+ json_object_double_add(
+ jte, "loss", attr->extended.pkt_loss * LOSS_PRECISION);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_AVA_BW))
+ json_object_double_add(jte, "available-bandwidth",
+ attr->extended.ava_bw);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_RSV_BW))
+ json_object_double_add(jte, "residual-bandwidth",
+ attr->extended.rsv_bw);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_USE_BW))
+ json_object_double_add(jte, "utilized-bandwidth",
+ attr->extended.used_bw);
+ if (CHECK_FLAG(attr->flags, LS_ATTR_SRLG)) {
+ jsrlg = json_object_new_array();
+ json_object_object_add(jte, "srlgs", jsrlg);
+ for (int i = 1; i < attr->srlg_len; i++) {
+ jobj = json_object_new_object();
+ json_object_int_add(jobj, "srlg", attr->srlgs[i]);
+ json_object_array_add(jsrlg, jobj);
+ }
+ }
+ if (CHECK_FLAG(attr->flags, LS_ATTR_ADJ_SID)) {
+ jsr = json_object_new_array();
+ json_object_object_add(json, "segment-routing", jsr);
+ jobj = json_object_new_object();
+ json_object_int_add(jobj, "adj-sid",
+ attr->adj_sid[ADJ_PRI_IPV4].sid);
+ snprintfrr(buf, 6, "0x%x", attr->adj_sid[ADJ_PRI_IPV4].flags);
+ json_object_string_add(jobj, "flags", buf);
+ json_object_int_add(jobj, "weight",
+ attr->adj_sid[ADJ_PRI_IPV4].weight);
+ json_object_array_add(jsr, jobj);
+ }
+ if (CHECK_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SID)) {
+ if (!jsr) {
+ jsr = json_object_new_array();
+ json_object_object_add(json, "segment-routing", jsr);
+ }
+ jobj = json_object_new_object();
+ json_object_int_add(jobj, "adj-sid",
+ attr->adj_sid[ADJ_BCK_IPV4].sid);
+ snprintfrr(buf, 6, "0x%x", attr->adj_sid[ADJ_BCK_IPV4].flags);
+ json_object_string_add(jobj, "flags", buf);
+ json_object_int_add(jobj, "weight",
+ attr->adj_sid[ADJ_BCK_IPV4].weight);
+ json_object_array_add(jsr, jobj);
+ }
+ if (CHECK_FLAG(attr->flags, LS_ATTR_ADJ_SID6)) {
+ jsr = json_object_new_array();
+ json_object_object_add(json, "segment-routing", jsr);
+ jobj = json_object_new_object();
+ json_object_int_add(jobj, "adj-sid",
+ attr->adj_sid[ADJ_PRI_IPV6].sid);
+ snprintfrr(buf, 6, "0x%x", attr->adj_sid[ADJ_PRI_IPV6].flags);
+ json_object_string_add(jobj, "flags", buf);
+ json_object_int_add(jobj, "weight",
+ attr->adj_sid[ADJ_PRI_IPV6].weight);
+ json_object_array_add(jsr, jobj);
+ }
+ if (CHECK_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SID6)) {
+ if (!jsr) {
+ jsr = json_object_new_array();
+ json_object_object_add(json, "segment-routing", jsr);
+ }
+ jobj = json_object_new_object();
+ json_object_int_add(jobj, "adj-sid",
+ attr->adj_sid[ADJ_BCK_IPV6].sid);
+ snprintfrr(buf, 6, "0x%x", attr->adj_sid[ADJ_BCK_IPV6].flags);
+ json_object_string_add(jobj, "flags", buf);
+ json_object_int_add(jobj, "weight",
+ attr->adj_sid[ADJ_BCK_IPV6].weight);
+ json_object_array_add(jsr, jobj);
+ }
+}
+
+void ls_show_edge(struct ls_edge *edge, struct vty *vty,
+ struct json_object *json, bool verbose)
+{
+ /* Sanity Check */
+ if (!edge)
+ return;
+
+ if (json)
+ ls_show_edge_json(edge, json);
+ else if (vty)
+ ls_show_edge_vty(edge, vty, verbose);
+}
+
+void ls_show_edges(struct ls_ted *ted, struct vty *vty,
+ struct json_object *json, bool verbose)
+{
+ struct ls_edge *edge;
+ json_object *jedges, *jedge;
+
+ if (json) {
+ jedges = json_object_new_array();
+ json_object_object_add(json, "edges", jedges);
+ frr_each (edges, &ted->edges, edge) {
+ jedge = json_object_new_object();
+ ls_show_edge(edge, NULL, jedge, verbose);
+ json_object_array_add(jedges, jedge);
+ }
+ } else if (vty) {
+ frr_each (edges, &ted->edges, edge)
+ ls_show_edge(edge, vty, NULL, verbose);
+ }
+}
+
+static void ls_show_subnet_vty(struct ls_subnet *subnet, struct vty *vty,
+ bool verbose)
+{
+ struct ls_prefix *pref;
+ struct sbuf sbuf;
+ char buf[INET6_BUFSIZ];
+
+ pref = subnet->ls_pref;
+ sbuf_init(&sbuf, NULL, 0);
+
+ sbuf_push(&sbuf, 2, "Subnet: %pFX", &subnet->key);
+ ls_node_id_to_text(pref->adv, buf, INET6_BUFSIZ);
+ sbuf_push(&sbuf, 0, "\tAdv. Vertex: %s", buf);
+ sbuf_push(&sbuf, 0, "\tMetric: %d", pref->metric);
+ sbuf_push(&sbuf, 0, "\tStatus: %s\n", status2txt[subnet->status]);
+
+ if (!verbose)
+ goto end;
+
+ sbuf_push(&sbuf, 4, "Origin: %s\n", origin2txt[pref->adv.origin]);
+ if (CHECK_FLAG(pref->flags, LS_PREF_IGP_FLAG))
+ sbuf_push(&sbuf, 4, "Flags: %d\n", pref->igp_flag);
+
+ if (CHECK_FLAG(pref->flags, LS_PREF_ROUTE_TAG))
+ sbuf_push(&sbuf, 4, "Tag: %d\n", pref->route_tag);
+
+ if (CHECK_FLAG(pref->flags, LS_PREF_EXTENDED_TAG))
+ sbuf_push(&sbuf, 4, "Extended Tag: %" PRIu64 "\n",
+ pref->extended_tag);
+
+ if (CHECK_FLAG(pref->flags, LS_PREF_SR))
+ sbuf_push(&sbuf, 4, "SID: %d\tAlgorithm: %d\tFlags: 0x%x\n",
+ pref->sr.sid, pref->sr.algo, pref->sr.sid_flag);
+
+end:
+ vty_out(vty, "%s\n", sbuf_buf(&sbuf));
+ sbuf_free(&sbuf);
+}
+
+static void ls_show_subnet_json(struct ls_subnet *subnet,
+ struct json_object *json)
+{
+ struct ls_prefix *pref;
+ json_object *jsr;
+ char buf[INET6_BUFSIZ];
+
+ pref = subnet->ls_pref;
+
+ snprintfrr(buf, INET6_BUFSIZ, "%pFX", &subnet->key);
+ json_object_string_add(json, "subnet-id", buf);
+ json_object_string_add(json, "status", status2txt[subnet->status]);
+ json_object_string_add(json, "origin", origin2txt[pref->adv.origin]);
+ ls_node_id_to_text(pref->adv, buf, INET6_BUFSIZ);
+ json_object_string_add(json, "advertised-router", buf);
+ if (subnet->vertex)
+ json_object_int_add(json, "vertex-id", subnet->vertex->key);
+ json_object_int_add(json, "metric", pref->metric);
+ if (CHECK_FLAG(pref->flags, LS_PREF_IGP_FLAG)) {
+ snprintfrr(buf, INET6_BUFSIZ, "0x%x", pref->igp_flag);
+ json_object_string_add(json, "flags", buf);
+ }
+ if (CHECK_FLAG(pref->flags, LS_PREF_ROUTE_TAG))
+ json_object_int_add(json, "tag", pref->route_tag);
+ if (CHECK_FLAG(pref->flags, LS_PREF_EXTENDED_TAG))
+ json_object_int_add(json, "extended-tag", pref->extended_tag);
+ if (CHECK_FLAG(pref->flags, LS_PREF_SR)) {
+ jsr = json_object_new_object();
+ json_object_object_add(json, "segment-routing", jsr);
+ json_object_int_add(jsr, "pref-sid", pref->sr.sid);
+ json_object_int_add(jsr, "algo", pref->sr.algo);
+ snprintfrr(buf, INET6_BUFSIZ, "0x%x", pref->sr.sid_flag);
+ json_object_string_add(jsr, "flags", buf);
+ }
+}
+
+void ls_show_subnet(struct ls_subnet *subnet, struct vty *vty,
+ struct json_object *json, bool verbose)
+{
+ /* Sanity Check */
+ if (!subnet)
+ return;
+
+ if (json)
+ ls_show_subnet_json(subnet, json);
+ else if (vty)
+ ls_show_subnet_vty(subnet, vty, verbose);
+}
+
+void ls_show_subnets(struct ls_ted *ted, struct vty *vty,
+ struct json_object *json, bool verbose)
+{
+ struct ls_subnet *subnet;
+ json_object *jsubs, *jsub;
+
+ if (json) {
+ jsubs = json_object_new_array();
+ json_object_object_add(json, "subnets", jsubs);
+ frr_each (subnets, &ted->subnets, subnet) {
+ jsub = json_object_new_object();
+ ls_show_subnet(subnet, NULL, jsub, verbose);
+ json_object_array_add(jsubs, jsub);
+ }
+ } else if (vty) {
+ frr_each (subnets, &ted->subnets, subnet)
+ ls_show_subnet(subnet, vty, NULL, verbose);
+ }
+}
+
+void ls_show_ted(struct ls_ted *ted, struct vty *vty, struct json_object *json,
+ bool verbose)
+{
+ json_object *jted;
+
+ if (json) {
+ jted = json_object_new_object();
+ json_object_object_add(json, "ted", jted);
+ json_object_string_add(jted, "name", ted->name);
+ json_object_int_add(jted, "key", ted->key);
+ json_object_int_add(jted, "verticesCount",
+ vertices_count(&ted->vertices));
+ json_object_int_add(jted, "edgesCount",
+ edges_count(&ted->edges));
+ json_object_int_add(jted, "subnetsCount",
+ subnets_count(&ted->subnets));
+ ls_show_vertices(ted, NULL, jted, verbose);
+ ls_show_edges(ted, NULL, jted, verbose);
+ ls_show_subnets(ted, NULL, jted, verbose);
+ return;
+ }
+
+ if (vty) {
+ vty_out(vty,
+ "\n\tTraffic Engineering Database: %s (key: %d)\n\n",
+ ted->name, ted->key);
+ ls_show_vertices(ted, vty, NULL, verbose);
+ ls_show_edges(ted, vty, NULL, verbose);
+ ls_show_subnets(ted, vty, NULL, verbose);
+ vty_out(vty,
+ "\n\tTotal: %zu Vertices, %zu Edges, %zu Subnets\n\n",
+ vertices_count(&ted->vertices),
+ edges_count(&ted->edges), subnets_count(&ted->subnets));
+ }
+}
+
+void ls_dump_ted(struct ls_ted *ted)
+{
+ struct ls_vertex *vertex;
+ struct ls_edge *edge;
+ struct ls_subnet *subnet;
+ const struct in_addr inaddr_any = {.s_addr = INADDR_ANY};
+
+ zlog_debug("(%s) Ted init", __func__);
+
+ /* Loop TED, start printing Node, then Attributes and finally Prefix */
+ frr_each (vertices, &ted->vertices, vertex) {
+ zlog_debug(" Ted node (%s %pI4 %s)",
+ vertex->node->name[0] ? vertex->node->name
+ : "no name node",
+ &vertex->node->router_id,
+ origin2txt[vertex->node->adv.origin]);
+ struct listnode *lst_node;
+ struct ls_edge *vertex_edge;
+
+ for (ALL_LIST_ELEMENTS_RO(vertex->incoming_edges, lst_node,
+ vertex_edge)) {
+ zlog_debug(
+ " inc edge key:%" PRIu64 " attr key:%pI4 loc:(%pI4) rmt:(%pI4)",
+ vertex_edge->key,
+ &vertex_edge->attributes->adv.id.ip.addr,
+ &vertex_edge->attributes->standard.local,
+ &vertex_edge->attributes->standard.remote);
+ }
+ for (ALL_LIST_ELEMENTS_RO(vertex->outgoing_edges, lst_node,
+ vertex_edge)) {
+ zlog_debug(
+ " out edge key:%" PRIu64 " attr key:%pI4 loc:(%pI4) rmt:(%pI4)",
+ vertex_edge->key,
+ &vertex_edge->attributes->adv.id.ip.addr,
+ &vertex_edge->attributes->standard.local,
+ &vertex_edge->attributes->standard.remote);
+ }
+ }
+ frr_each (edges, &ted->edges, edge) {
+ zlog_debug(" Ted edge key:%" PRIu64 "src:%pI4 dst:%pI4", edge->key,
+ edge->source ? &edge->source->node->router_id
+ : &inaddr_any,
+ edge->destination
+ ? &edge->destination->node->router_id
+ : &inaddr_any);
+ }
+ frr_each (subnets, &ted->subnets, subnet) {
+ zlog_debug(" Ted subnet key:%pFX vertex:%pI4",
+ &subnet->ls_pref->pref,
+ &subnet->vertex->node->adv.id.ip.addr);
+ }
+ zlog_debug("(%s) Ted end", __func__);
+}
diff --git a/lib/link_state.h b/lib/link_state.h
new file mode 100644
index 0000000..ed31545
--- /dev/null
+++ b/lib/link_state.h
@@ -0,0 +1,1131 @@
+/*
+ * Link State Database definition - ted.h
+ *
+ * Author: Olivier Dugeon <olivier.dugeon@orange.com>
+ *
+ * Copyright (C) 2020 Orange http://www.orange.com
+ *
+ * This file is part of Free Range Routing (FRR).
+ *
+ * FRR 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, or (at your option) any
+ * later version.
+ *
+ * FRR 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_LINK_STATE_H_
+#define _FRR_LINK_STATE_H_
+
+#include "typesafe.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * This file defines the model used to implement a Link State Database
+ * suitable to be used by various protocol like RSVP-TE, BGP-LS, PCEP ...
+ * This database is normally fulfill by the link state routing protocol,
+ * commonly OSPF or ISIS, carrying Traffic Engineering information within
+ * Link State Attributes. See, RFC3630.(OSPF-TE) and RFC5305 (ISIS-TE).
+ *
+ * At least, 3 types of Link State structure are defined:
+ * - Link State Node that groups all information related to a node
+ * - Link State Attributes that groups all information related to a link
+ * - Link State Prefix that groups all information related to a prefix
+ *
+ * These 3 types of structures are those handled by BGP-LS (see RFC7752).
+ *
+ * Each structure, in addition to the specific parameters, embed the node
+ * identifier which advertises the Link State and a bit mask as flags to
+ * indicates which parameters are valid i.e. for which the value corresponds
+ * to a Link State information convey by the routing protocol.
+ * Node identifier is composed of the route id as IPv4 address plus the area
+ * id for OSPF and the ISO System id plus the IS-IS level for IS-IS.
+ */
+
+/* external reference */
+struct zapi_opaque_reg_info;
+struct zclient;
+
+/* Link State Common definitions */
+#define MAX_NAME_LENGTH 256
+#define ISO_SYS_ID_LEN 6
+
+/* Type of Node */
+enum ls_node_type {
+ NONE = 0, /* Unknown */
+ STANDARD, /* a P or PE node */
+ ABR, /* an Array Border Node */
+ ASBR, /* an Autonomous System Border Node */
+ RMT_ASBR, /* Remote ASBR */
+ PSEUDO /* a Pseudo Node */
+};
+
+/* Origin of the Link State information */
+enum ls_origin { UNKNOWN = 0, ISIS_L1, ISIS_L2, OSPFv2, DIRECT, STATIC };
+
+/**
+ * Link State Node Identifier as:
+ * - IPv4 address + Area ID for OSPF
+ * - ISO System ID + ISIS Level for ISIS
+ */
+struct ls_node_id {
+ enum ls_origin origin; /* Origin of the LS information */
+ union {
+ struct {
+ struct in_addr addr; /* OSPF Router IS */
+ struct in_addr area_id; /* OSPF Area ID */
+ } ip;
+ struct {
+ uint8_t sys_id[ISO_SYS_ID_LEN]; /* ISIS System ID */
+ uint8_t level; /* ISIS Level */
+ uint8_t padding;
+ } iso;
+ } id;
+};
+
+/**
+ * Check if two Link State Node IDs are equal. Note that this routine has the
+ * same return value sense as '==' (which is different from a comparison).
+ *
+ * @param i1 First Link State Node Identifier
+ * @param i2 Second Link State Node Identifier
+ * @return 1 if equal, 0 otherwise
+ */
+extern int ls_node_id_same(struct ls_node_id i1, struct ls_node_id i2);
+
+/* Link State flags to indicate which Node parameters are valid */
+#define LS_NODE_UNSET 0x0000
+#define LS_NODE_NAME 0x0001
+#define LS_NODE_ROUTER_ID 0x0002
+#define LS_NODE_ROUTER_ID6 0x0004
+#define LS_NODE_FLAG 0x0008
+#define LS_NODE_TYPE 0x0010
+#define LS_NODE_AS_NUMBER 0x0020
+#define LS_NODE_SR 0x0040
+#define LS_NODE_SRLB 0x0080
+#define LS_NODE_MSD 0x0100
+
+/* Link State Node structure */
+struct ls_node {
+ uint16_t flags; /* Flag for parameters validity */
+ struct ls_node_id adv; /* Adv. Router of this Link State */
+ char name[MAX_NAME_LENGTH]; /* Name of the Node (IS-IS only) */
+ struct in_addr router_id; /* IPv4 Router ID */
+ struct in6_addr router_id6; /* IPv6 Router ID */
+ uint8_t node_flag; /* IS-IS or OSPF Node flag */
+ enum ls_node_type type; /* Type of Node */
+ uint32_t as_number; /* Local or neighbor AS number */
+ struct ls_srgb { /* Segment Routing Global Block */
+ uint32_t lower_bound; /* MPLS label lower bound */
+ uint32_t range_size; /* MPLS label range size */
+ uint8_t flag; /* IS-IS SRGB flags */
+ } srgb;
+ struct ls_srlb { /* Segment Routing Local Block */
+ uint32_t lower_bound; /* MPLS label lower bound */
+ uint32_t range_size; /* MPLS label range size */
+ } srlb;
+ uint8_t algo[2]; /* Segment Routing Algorithms */
+ uint8_t msd; /* Maximum Stack Depth */
+};
+
+/* Link State flags to indicate which Attribute parameters are valid */
+#define LS_ATTR_UNSET 0x00000000
+#define LS_ATTR_NAME 0x00000001
+#define LS_ATTR_METRIC 0x00000002
+#define LS_ATTR_TE_METRIC 0x00000004
+#define LS_ATTR_ADM_GRP 0x00000008
+#define LS_ATTR_LOCAL_ADDR 0x00000010
+#define LS_ATTR_NEIGH_ADDR 0x00000020
+#define LS_ATTR_LOCAL_ADDR6 0x00000040
+#define LS_ATTR_NEIGH_ADDR6 0x00000080
+#define LS_ATTR_LOCAL_ID 0x00000100
+#define LS_ATTR_NEIGH_ID 0x00000200
+#define LS_ATTR_MAX_BW 0x00000400
+#define LS_ATTR_MAX_RSV_BW 0x00000800
+#define LS_ATTR_UNRSV_BW 0x00001000
+#define LS_ATTR_REMOTE_AS 0x00002000
+#define LS_ATTR_REMOTE_ADDR 0x00004000
+#define LS_ATTR_REMOTE_ADDR6 0x00008000
+#define LS_ATTR_DELAY 0x00010000
+#define LS_ATTR_MIN_MAX_DELAY 0x00020000
+#define LS_ATTR_JITTER 0x00040000
+#define LS_ATTR_PACKET_LOSS 0x00080000
+#define LS_ATTR_AVA_BW 0x00100000
+#define LS_ATTR_RSV_BW 0x00200000
+#define LS_ATTR_USE_BW 0x00400000
+#define LS_ATTR_ADJ_SID 0x01000000
+#define LS_ATTR_BCK_ADJ_SID 0x02000000
+#define LS_ATTR_ADJ_SID6 0x04000000
+#define LS_ATTR_BCK_ADJ_SID6 0x08000000
+#define LS_ATTR_SRLG 0x10000000
+
+/* Link State Attributes */
+struct ls_attributes {
+ uint32_t flags; /* Flag for parameters validity */
+ struct ls_node_id adv; /* Adv. Router of this Link State */
+ char name[MAX_NAME_LENGTH]; /* Name of the Edge. Could be null */
+ uint32_t metric; /* IGP standard metric */
+ struct ls_standard { /* Standard TE metrics */
+ uint32_t te_metric; /* Traffic Engineering metric */
+ uint32_t admin_group; /* Administrative Group */
+ struct in_addr local; /* Local IPv4 address */
+ struct in_addr remote; /* Remote IPv4 address */
+ struct in6_addr local6; /* Local IPv6 address */
+ struct in6_addr remote6; /* Remote IPv6 address */
+ uint32_t local_id; /* Local Identifier */
+ uint32_t remote_id; /* Remote Identifier */
+ float max_bw; /* Maximum Link Bandwidth */
+ float max_rsv_bw; /* Maximum Reservable BW */
+ float unrsv_bw[8]; /* Unreserved BW per CT (8) */
+ uint32_t remote_as; /* Remote AS number */
+ struct in_addr remote_addr; /* Remote IPv4 address */
+ struct in6_addr remote_addr6; /* Remote IPv6 address */
+ } standard;
+ struct ls_extended { /* Extended TE Metrics */
+ uint32_t delay; /* Unidirectional average delay */
+ uint32_t min_delay; /* Unidirectional minimum delay */
+ uint32_t max_delay; /* Unidirectional maximum delay */
+ uint32_t jitter; /* Unidirectional delay variation */
+ uint32_t pkt_loss; /* Unidirectional packet loss */
+ float ava_bw; /* Available Bandwidth */
+ float rsv_bw; /* Reserved Bandwidth */
+ float used_bw; /* Utilized Bandwidth */
+ } extended;
+#define ADJ_PRI_IPV4 0
+#define ADJ_BCK_IPV4 1
+#define ADJ_PRI_IPV6 2
+#define ADJ_BCK_IPV6 3
+#define LS_ADJ_MAX 4
+ struct ls_adjacency { /* (LAN)-Adjacency SID for OSPF */
+ uint32_t sid; /* SID as MPLS label or index */
+ uint8_t flags; /* Flags */
+ uint8_t weight; /* Administrative weight */
+ union {
+ struct in_addr addr; /* Neighbor @IP for OSPF */
+ uint8_t sysid[ISO_SYS_ID_LEN]; /* or Sys-ID for ISIS */
+ } neighbor;
+ } adj_sid[4]; /* IPv4/IPv6 & Primary/Backup (LAN)-Adj. SID */
+ uint32_t *srlgs; /* List of Shared Risk Link Group */
+ uint8_t srlg_len; /* number of SRLG in the list */
+};
+
+/* Link State flags to indicate which Prefix parameters are valid */
+#define LS_PREF_UNSET 0x00
+#define LS_PREF_IGP_FLAG 0x01
+#define LS_PREF_ROUTE_TAG 0x02
+#define LS_PREF_EXTENDED_TAG 0x04
+#define LS_PREF_METRIC 0x08
+#define LS_PREF_SR 0x10
+
+/* Link State Prefix */
+struct ls_prefix {
+ uint8_t flags; /* Flag for parameters validity */
+ struct ls_node_id adv; /* Adv. Router of this Link State */
+ struct prefix pref; /* IPv4 or IPv6 prefix */
+ uint8_t igp_flag; /* IGP Flags associated to the prefix */
+ uint32_t route_tag; /* IGP Route Tag */
+ uint64_t extended_tag; /* IGP Extended Route Tag */
+ uint32_t metric; /* Route metric for this prefix */
+ struct ls_sid {
+ uint32_t sid; /* Segment Routing ID */
+ uint8_t sid_flag; /* Segment Routing Flags */
+ uint8_t algo; /* Algorithm for Segment Routing */
+ } sr;
+};
+
+/**
+ * Create a new Link State Node. Structure is dynamically allocated.
+ *
+ * @param adv Mandatory Link State Node ID i.e. advertise router information
+ * @param rid Router ID as IPv4 address
+ * @param rid6 Router ID as IPv6 address
+ *
+ * @return New Link State Node
+ */
+extern struct ls_node *ls_node_new(struct ls_node_id adv, struct in_addr rid,
+ struct in6_addr rid6);
+
+/**
+ * Remove Link State Node. Data structure is freed.
+ *
+ * @param node Pointer to a valid Link State Node structure
+ */
+extern void ls_node_del(struct ls_node *node);
+
+/**
+ * Check if two Link State Nodes are equal. Note that this routine has the same
+ * return value sense as '==' (which is different from a comparison).
+ *
+ * @param n1 First Link State Node to be compare
+ * @param n2 Second Link State Node to be compare
+ *
+ * @return 1 if equal, 0 otherwise
+ */
+extern int ls_node_same(struct ls_node *n1, struct ls_node *n2);
+
+/**
+ * Create a new Link State Attributes. Structure is dynamically allocated.
+ * At least one of parameters MUST be valid and not equal to 0.
+ *
+ * @param adv Mandatory Link State Node ID i.e. advertise router ID
+ * @param local Local IPv4 address
+ * @param local6 Local Ipv6 address
+ * @param local_id Local Identifier
+ *
+ * @return New Link State Attributes
+ */
+extern struct ls_attributes *ls_attributes_new(struct ls_node_id adv,
+ struct in_addr local,
+ struct in6_addr local6,
+ uint32_t local_id);
+
+/**
+ * Remove SRLGs from Link State Attributes if defined.
+ *
+ * @param attr Pointer to a valid Link State Attribute structure
+ */
+extern void ls_attributes_srlg_del(struct ls_attributes *attr);
+
+/**
+ * Remove Link State Attributes. Data structure is freed.
+ *
+ * @param attr Pointer to a valid Link State Attribute structure
+ */
+extern void ls_attributes_del(struct ls_attributes *attr);
+
+/**
+ * Check if two Link State Attributes are equal. Note that this routine has the
+ * same return value sense as '==' (which is different from a comparison).
+ *
+ * @param a1 First Link State Attributes to be compare
+ * @param a2 Second Link State Attributes to be compare
+ *
+ * @return 1 if equal, 0 otherwise
+ */
+extern int ls_attributes_same(struct ls_attributes *a1,
+ struct ls_attributes *a2);
+
+/**
+ * Create a new Link State Prefix. Structure is dynamically allocated.
+ *
+ * @param adv Mandatory Link State Node ID i.e. advertise router ID
+ * @param p Mandatory Prefix
+ *
+ * @return New Link State Prefix
+ */
+extern struct ls_prefix *ls_prefix_new(struct ls_node_id adv, struct prefix p);
+
+/**
+ * Remove Link State Prefix. Data Structure is freed.
+ *
+ * @param pref Pointer to a valid Link State Attribute Prefix.
+ */
+extern void ls_prefix_del(struct ls_prefix *pref);
+
+/**
+ * Check if two Link State Prefix are equal. Note that this routine has the
+ * same return value sense as '==' (which is different from a comparison).
+ *
+ * @param p1 First Link State Prefix to be compare
+ * @param p2 Second Link State Prefix to be compare
+ *
+ * @return 1 if equal, 0 otherwise
+ */
+extern int ls_prefix_same(struct ls_prefix *p1, struct ls_prefix *p2);
+
+/**
+ * In addition a Graph model is defined as an overlay on top of link state
+ * database in order to ease Path Computation algorithm implementation.
+ * Denoted G(V, E), a graph is composed by a list of Vertices (V) which
+ * represents the network Node and a list of Edges (E) which represents node
+ * Link. An additional list of prefixes (P) is also added.
+ * A prefix (P) is also attached to the Vertex (V) which advertise it.
+ *
+ * Vertex (V) contains the list of outgoing Edges (E) that connect this Vertex
+ * with its direct neighbors and the list of incoming Edges (E) that connect
+ * the direct neighbors to this Vertex. Indeed, the Edge (E) is unidirectional,
+ * thus, it is necessary to add 2 Edges to model a bidirectional relation
+ * between 2 Vertices.
+ *
+ * Edge (E) contains the source and destination Vertex that this Edge
+ * is connecting.
+ *
+ * A unique Key is used to identify both Vertices and Edges within the Graph.
+ * An easy way to build this key is to used the IP address: i.e. loopback
+ * address for Vertices and link IP address for Edges.
+ *
+ * -------------- --------------------------- --------------
+ * | Connected |---->| Connected Edge Va to Vb |--->| Connected |
+ * --->| Vertex | --------------------------- | Vertex |---->
+ * | | | |
+ * | - Key (Va) | | - Key (Vb) |
+ * <---| - Vertex | --------------------------- | - Vertex |<----
+ * | |<----| Connected Edge Vb to Va |<---| |
+ * -------------- --------------------------- --------------
+ *
+ */
+
+enum ls_status { UNSET = 0, NEW, UPDATE, DELETE, SYNC, ORPHAN };
+enum ls_type { GENERIC = 0, VERTEX, EDGE, SUBNET };
+
+/* Link State Vertex structure */
+PREDECL_RBTREE_UNIQ(vertices);
+struct ls_vertex {
+ enum ls_type type; /* Link State Type */
+ enum ls_status status; /* Status of the Vertex in the TED */
+ struct vertices_item entry; /* Entry in RB Tree */
+ uint64_t key; /* Unique Key identifier */
+ struct ls_node *node; /* Link State Node */
+ struct list *incoming_edges; /* List of incoming Link State links */
+ struct list *outgoing_edges; /* List of outgoing Link State links */
+ struct list *prefixes; /* List of advertised prefix */
+};
+
+/* Link State Edge structure */
+PREDECL_RBTREE_UNIQ(edges);
+struct ls_edge {
+ enum ls_type type; /* Link State Type */
+ enum ls_status status; /* Status of the Edge in the TED */
+ struct edges_item entry; /* Entry in RB tree */
+ uint64_t key; /* Unique Key identifier */
+ struct ls_attributes *attributes; /* Link State attributes */
+ struct ls_vertex *source; /* Pointer to the source Vertex */
+ struct ls_vertex *destination; /* Pointer to the destination Vertex */
+};
+
+/* Link State Subnet structure */
+PREDECL_RBTREE_UNIQ(subnets);
+struct ls_subnet {
+ enum ls_type type; /* Link State Type */
+ enum ls_status status; /* Status of the Subnet in the TED */
+ struct subnets_item entry; /* Entry in RB tree */
+ struct prefix key; /* Unique Key identifier */
+ struct ls_prefix *ls_pref; /* Link State Prefix */
+ struct ls_vertex *vertex; /* Back pointer to the Vertex owner */
+};
+
+/* Declaration of Vertices, Edges and Prefixes RB Trees */
+macro_inline int vertex_cmp(const struct ls_vertex *node1,
+ const struct ls_vertex *node2)
+{
+ return numcmp(node1->key, node2->key);
+}
+DECLARE_RBTREE_UNIQ(vertices, struct ls_vertex, entry, vertex_cmp);
+
+macro_inline int edge_cmp(const struct ls_edge *edge1,
+ const struct ls_edge *edge2)
+{
+ return numcmp(edge1->key, edge2->key);
+}
+DECLARE_RBTREE_UNIQ(edges, struct ls_edge, entry, edge_cmp);
+
+/*
+ * Prefix comparison are done to the host part so, 10.0.0.1/24
+ * and 10.0.0.2/24 are considered come different
+ */
+macro_inline int subnet_cmp(const struct ls_subnet *a,
+ const struct ls_subnet *b)
+{
+ if (a->key.family != b->key.family)
+ return numcmp(a->key.family, b->key.family);
+
+ if (a->key.prefixlen != b->key.prefixlen)
+ return numcmp(a->key.prefixlen, b->key.prefixlen);
+
+ if (a->key.family == AF_INET)
+ return memcmp(&a->key.u.val, &b->key.u.val, 4);
+
+ return memcmp(&a->key.u.val, &b->key.u.val, 16);
+}
+DECLARE_RBTREE_UNIQ(subnets, struct ls_subnet, entry, subnet_cmp);
+
+/* Link State TED Structure */
+struct ls_ted {
+ uint32_t key; /* Unique identifier */
+ char name[MAX_NAME_LENGTH]; /* Name of this graph. Could be null */
+ uint32_t as_number; /* AS number of the modeled network */
+ struct ls_vertex *self; /* Vertex of the FRR instance */
+ struct vertices_head vertices; /* List of Vertices */
+ struct edges_head edges; /* List of Edges */
+ struct subnets_head subnets; /* List of Subnets */
+};
+
+/* Generic Link State Element */
+struct ls_element {
+ enum ls_type type; /* Link State Element Type */
+ enum ls_status status; /* Link State Status in the TED */
+ void *data; /* Link State payload */
+};
+
+/**
+ * Add new vertex to the Link State DB. Vertex is created from the Link State
+ * Node. Vertex data structure is dynamically allocated.
+ *
+ * @param ted Traffic Engineering Database structure
+ * @param node Link State Node
+ *
+ * @return New Vertex or NULL in case of error
+ */
+extern struct ls_vertex *ls_vertex_add(struct ls_ted *ted,
+ struct ls_node *node);
+
+/**
+ * Delete Link State Vertex. This function clean internal Vertex lists (incoming
+ * and outgoing Link State Edge and Link State Subnet). Vertex Data structure
+ * is freed but not the Link State Node. Link State DB is not modified if Vertex
+ * is NULL or not found in the Data Base. Note that referenced to Link State
+ * Edges & SubNets are not removed as they could be connected to other Vertices.
+ *
+ * @param ted Traffic Engineering Database structure
+ * @param vertex Link State Vertex to be removed
+ */
+extern void ls_vertex_del(struct ls_ted *ted, struct ls_vertex *vertex);
+
+/**
+ * Delete Link State Vertex as ls_vertex_del() but also removed associated
+ * Link State Node.
+ *
+ * @param ted Traffic Engineering Database structure
+ * @param vertex Link State Vertex to be removed
+ */
+extern void ls_vertex_del_all(struct ls_ted *ted, struct ls_vertex *vertex);
+
+/**
+ * Update Vertex with the Link State Node. A new vertex is created if no one
+ * corresponds to the Link State Node.
+ *
+ * @param ted Link State Data Base
+ * @param node Link State Node to be updated
+ *
+ * @return Updated Link State Vertex or Null in case of error
+ */
+extern struct ls_vertex *ls_vertex_update(struct ls_ted *ted,
+ struct ls_node *node);
+
+/**
+ * Clean Vertex structure by removing all Edges and Subnets marked as ORPHAN
+ * from this vertex. Link State Update message is sent if zclient is not NULL.
+ *
+ * @param ted Link State Data Base
+ * @param vertex Link State Vertex to be cleaned
+ * @param zclient Reference to Zebra Client
+ */
+extern void ls_vertex_clean(struct ls_ted *ted, struct ls_vertex *vertex,
+ struct zclient *zclient);
+
+/**
+ * This function convert the ISIS ISO system ID into a 64 bits unsigned integer
+ * following the architecture dependent byte order.
+ *
+ * @param sysid The ISO system ID
+ * @return Key as 64 bits unsigned integer
+ */
+extern uint64_t sysid_to_key(const uint8_t sysid[ISO_SYS_ID_LEN]);
+
+/**
+ * Find Vertex in the Link State DB by its unique key.
+ *
+ * @param ted Link State Data Base
+ * @param key Vertex Key different from 0
+ *
+ * @return Vertex if found, NULL otherwise
+ */
+extern struct ls_vertex *ls_find_vertex_by_key(struct ls_ted *ted,
+ const uint64_t key);
+
+/**
+ * Find Vertex in the Link State DB by its Link State Node.
+ *
+ * @param ted Link State Data Base
+ * @param nid Link State Node ID
+ *
+ * @return Vertex if found, NULL otherwise
+ */
+extern struct ls_vertex *ls_find_vertex_by_id(struct ls_ted *ted,
+ struct ls_node_id nid);
+
+/**
+ * Check if two Vertices are equal. Note that this routine has the same return
+ * value sense as '==' (which is different from a comparison).
+ *
+ * @param v1 First vertex to compare
+ * @param v2 Second vertex to compare
+ *
+ * @return 1 if equal, 0 otherwise
+ */
+extern int ls_vertex_same(struct ls_vertex *v1, struct ls_vertex *v2);
+
+/**
+ * Add new Edge to the Link State DB. Edge is created from the Link State
+ * Attributes. Edge data structure is dynamically allocated.
+ *
+ * @param ted Link State Data Base
+ * @param attributes Link State attributes
+ *
+ * @return New Edge or NULL in case of error
+ */
+extern struct ls_edge *ls_edge_add(struct ls_ted *ted,
+ struct ls_attributes *attributes);
+
+/**
+ * Update the Link State Attributes information of an existing Edge. If there is
+ * no corresponding Edge in the Link State Data Base, a new Edge is created.
+ *
+ * @param ted Link State Data Base
+ * @param attributes Link State Attributes
+ *
+ * @return Updated Link State Edge, or NULL in case of error
+ */
+extern struct ls_edge *ls_edge_update(struct ls_ted *ted,
+ struct ls_attributes *attributes);
+
+/**
+ * Check if two Edges are equal. Note that this routine has the same return
+ * value sense as '==' (which is different from a comparison).
+ *
+ * @param e1 First edge to compare
+ * @param e2 Second edge to compare
+ *
+ * @return 1 if equal, 0 otherwise
+ */
+extern int ls_edge_same(struct ls_edge *e1, struct ls_edge *e2);
+
+/**
+ * Remove Edge from the Link State DB. Edge data structure is freed but not the
+ * Link State Attributes data structure. Link State DB is not modified if Edge
+ * is NULL or not found in the Data Base.
+ *
+ * @param ted Link State Data Base
+ * @param edge Edge to be removed
+ */
+extern void ls_edge_del(struct ls_ted *ted, struct ls_edge *edge);
+
+/**
+ * Remove Edge and associated Link State Attributes from the Link State DB.
+ * Link State DB is not modified if Edge is NULL or not found.
+ *
+ * @param ted Link State Data Base
+ * @param edge Edge to be removed
+ */
+extern void ls_edge_del_all(struct ls_ted *ted, struct ls_edge *edge);
+
+/**
+ * Find Edge in the Link State Data Base by Edge key.
+ *
+ * @param ted Link State Data Base
+ * @param key Edge key
+ *
+ * @return Edge if found, NULL otherwise
+ */
+extern struct ls_edge *ls_find_edge_by_key(struct ls_ted *ted,
+ const uint64_t key);
+
+/**
+ * Find Edge in the Link State Data Base by the source (local IPv4 or IPv6
+ * address or local ID) informations of the Link State Attributes
+ *
+ * @param ted Link State Data Base
+ * @param attributes Link State Attributes
+ *
+ * @return Edge if found, NULL otherwise
+ */
+extern struct ls_edge *
+ls_find_edge_by_source(struct ls_ted *ted, struct ls_attributes *attributes);
+
+/**
+ * Find Edge in the Link State Data Base by the destination (remote IPv4 or IPv6
+ * address of remote ID) information of the Link State Attributes
+ *
+ * @param ted Link State Data Base
+ * @param attributes Link State Attributes
+ *
+ * @return Edge if found, NULL otherwise
+ */
+extern struct ls_edge *
+ls_find_edge_by_destination(struct ls_ted *ted,
+ struct ls_attributes *attributes);
+
+/**
+ * Add new Subnet to the Link State DB. Subnet is created from the Link State
+ * prefix. Subnet data structure is dynamically allocated.
+ *
+ * @param ted Link State Data Base
+ * @param pref Link State Prefix
+ *
+ * @return New Subnet
+ */
+extern struct ls_subnet *ls_subnet_add(struct ls_ted *ted,
+ struct ls_prefix *pref);
+
+/**
+ * Update the Link State Prefix information of an existing Subnet. If there is
+ * no corresponding Subnet in the Link State Data Base, a new Subnet is created.
+ *
+ * @param ted Link State Data Base
+ * @param pref Link State Prefix
+ *
+ * @return Updated Link State Subnet, or NULL in case of error
+ */
+extern struct ls_subnet *ls_subnet_update(struct ls_ted *ted,
+ struct ls_prefix *pref);
+
+/**
+ * Check if two Subnets are equal. Note that this routine has the same return
+ * value sense as '==' (which is different from a comparison).
+ *
+ * @param s1 First subnet to compare
+ * @param s2 Second subnet to compare
+ *
+ * @return 1 if equal, 0 otherwise
+ */
+extern int ls_subnet_same(struct ls_subnet *s1, struct ls_subnet *s2);
+
+/**
+ * Remove Subnet from the Link State DB. Subnet data structure is freed but
+ * not the Link State prefix data structure. Link State DB is not modified
+ * if Subnet is NULL or not found in the Data Base.
+ *
+ * @param ted Link State Data Base
+ * @param subnet Subnet to be removed
+ */
+extern void ls_subnet_del(struct ls_ted *ted, struct ls_subnet *subnet);
+
+/**
+ * Remove Subnet and the associated Link State Prefix from the Link State DB.
+ * Link State DB is not modified if Subnet is NULL or not found.
+ *
+ * @param ted Link State Data Base
+ * @param subnet Subnet to be removed
+ */
+extern void ls_subnet_del_all(struct ls_ted *ted, struct ls_subnet *subnet);
+
+/**
+ * Find Subnet in the Link State Data Base by prefix.
+ *
+ * @param ted Link State Data Base
+ * @param prefix Link State Prefix
+ *
+ * @return Subnet if found, NULL otherwise
+ */
+extern struct ls_subnet *ls_find_subnet(struct ls_ted *ted,
+ const struct prefix prefix);
+
+/**
+ * Create a new Link State Data Base.
+ *
+ * @param key Unique key of the data base. Must be different from 0
+ * @param name Name of the data base (may be NULL)
+ * @param asn AS Number for this data base. 0 if unknown
+ *
+ * @return New Link State Database or NULL in case of error
+ */
+extern struct ls_ted *ls_ted_new(const uint32_t key, const char *name,
+ uint32_t asn);
+
+/**
+ * Delete existing Link State Data Base. Vertices, Edges, and Subnets are not
+ * removed.
+ *
+ * @param ted Link State Data Base
+ */
+extern void ls_ted_del(struct ls_ted *ted);
+
+/**
+ * Delete all Link State Vertices, Edges and SubNets and the Link State DB.
+ *
+ * @param ted Link State Data Base
+ */
+extern void ls_ted_del_all(struct ls_ted **ted);
+
+/**
+ * Clean Link State Data Base by removing all Vertices, Edges and SubNets marked
+ * as ORPHAN.
+ *
+ * @param ted Link State Data Base
+ */
+extern void ls_ted_clean(struct ls_ted *ted);
+
+/**
+ * Connect Source and Destination Vertices by given Edge. Only non NULL source
+ * and destination vertices are connected.
+ *
+ * @param src Link State Source Vertex
+ * @param dst Link State Destination Vertex
+ * @param edge Link State Edge. Must not be NULL
+ */
+extern void ls_connect_vertices(struct ls_vertex *src, struct ls_vertex *dst,
+ struct ls_edge *edge);
+
+/**
+ * Connect Link State Edge to the Link State Vertex which could be a Source or
+ * a Destination Vertex.
+ *
+ * @param vertex Link State Vertex to be connected. Must not be NULL
+ * @param edge Link State Edge connection. Must not be NULL
+ * @param source True for a Source, false for a Destination Vertex
+ */
+extern void ls_connect(struct ls_vertex *vertex, struct ls_edge *edge,
+ bool source);
+
+/**
+ * Disconnect Link State Edge from the Link State Vertex which could be a
+ * Source or a Destination Vertex.
+ *
+ * @param vertex Link State Vertex to be connected. Must not be NULL
+ * @param edge Link State Edge connection. Must not be NULL
+ * @param source True for a Source, false for a Destination Vertex
+ */
+extern void ls_disconnect(struct ls_vertex *vertex, struct ls_edge *edge,
+ bool source);
+
+/**
+ * Disconnect Link State Edge from both Source and Destination Vertex.
+ *
+ * @param edge Link State Edge to be disconnected
+ */
+extern void ls_disconnect_edge(struct ls_edge *edge);
+
+
+/**
+ * The Link State Message is defined to convey Link State parameters from
+ * the routing protocol (OSPF or IS-IS) to other daemons e.g. BGP.
+ *
+ * The structure is composed of:
+ * - Event of the message:
+ * - Sync: Send the whole LS DB following a request
+ * - Add: Send the a new Link State element
+ * - Update: Send an update of an existing Link State element
+ * - Delete: Indicate that the given Link State element is removed
+ * - Type of Link State element: Node, Attribute or Prefix
+ * - Remote node id when known
+ * - Data: Node, Attributes or Prefix
+ *
+ * A Link State Message can carry only one Link State Element (Node, Attributes
+ * of Prefix) at once, and only one Link State Message is sent through ZAPI
+ * Opaque Link State type at once.
+ */
+
+/* ZAPI Opaque Link State Message Event */
+#define LS_MSG_EVENT_UNDEF 0
+#define LS_MSG_EVENT_SYNC 1
+#define LS_MSG_EVENT_ADD 2
+#define LS_MSG_EVENT_UPDATE 3
+#define LS_MSG_EVENT_DELETE 4
+
+/* ZAPI Opaque Link State Message sub-Type */
+#define LS_MSG_TYPE_NODE 1
+#define LS_MSG_TYPE_ATTRIBUTES 2
+#define LS_MSG_TYPE_PREFIX 3
+
+/* Link State Message */
+struct ls_message {
+ uint8_t event; /* Message Event: Sync, Add, Update, Delete */
+ uint8_t type; /* Message Data Type: Node, Attribute, Prefix */
+ struct ls_node_id remote_id; /* Remote Link State Node ID */
+ union {
+ struct ls_node *node; /* Link State Node */
+ struct ls_attributes *attr; /* Link State Attributes */
+ struct ls_prefix *prefix; /* Link State Prefix */
+ } data;
+};
+
+/**
+ * Register Link State daemon as a server or client for Zebra OPAQUE API.
+ *
+ * @param zclient Zebra client structure
+ * @param server Register daemon as a server (true) or as a client (false)
+ *
+ * @return 0 if success, -1 otherwise
+ */
+extern int ls_register(struct zclient *zclient, bool server);
+
+/**
+ * Unregister Link State daemon as a server or client for Zebra OPAQUE API.
+ *
+ * @param zclient Zebra client structure
+ * @param server Unregister daemon as a server (true) or as a client (false)
+ *
+ * @return 0 if success, -1 otherwise
+ */
+extern int ls_unregister(struct zclient *zclient, bool server);
+
+/**
+ * Send Link State SYNC message to request the complete Link State Database.
+ *
+ * @param zclient Zebra client
+ *
+ * @return 0 if success, -1 otherwise
+ */
+extern int ls_request_sync(struct zclient *zclient);
+
+/**
+ * Parse Link State Message from stream. Used this function once receiving a
+ * new ZAPI Opaque message of type Link State.
+ *
+ * @param s Stream buffer. Must not be NULL.
+ *
+ * @return New Link State Message or NULL in case of error
+ */
+extern struct ls_message *ls_parse_msg(struct stream *s);
+
+/**
+ * Delete existing message. Data structure is freed.
+ *
+ * @param msg Link state message to be deleted
+ */
+extern void ls_delete_msg(struct ls_message *msg);
+
+/**
+ * Send Link State Message as new ZAPI Opaque message of type Link State.
+ * If destination is not NULL, message is sent as Unicast otherwise it is
+ * broadcast to all registered daemon.
+ *
+ * @param zclient Zebra Client
+ * @param msg Link State Message to be sent
+ * @param dst Destination daemon for unicast message,
+ * NULL for broadcast message
+ *
+ * @return 0 on success, -1 otherwise
+ */
+extern int ls_send_msg(struct zclient *zclient, struct ls_message *msg,
+ struct zapi_opaque_reg_info *dst);
+
+/**
+ * Create a new Link State Message from a Link State Vertex. If Link State
+ * Message is NULL, a new data structure is dynamically allocated.
+ *
+ * @param msg Link State Message to be filled or NULL
+ * @param vertex Link State Vertex. Must not be NULL
+ *
+ * @return New Link State Message msg parameter is NULL or pointer
+ * to the provided Link State Message
+ */
+extern struct ls_message *ls_vertex2msg(struct ls_message *msg,
+ struct ls_vertex *vertex);
+
+/**
+ * Create a new Link State Message from a Link State Edge. If Link State
+ * Message is NULL, a new data structure is dynamically allocated.
+ *
+ * @param msg Link State Message to be filled or NULL
+ * @param edge Link State Edge. Must not be NULL
+ *
+ * @return New Link State Message msg parameter is NULL or pointer
+ * to the provided Link State Message
+ */
+extern struct ls_message *ls_edge2msg(struct ls_message *msg,
+ struct ls_edge *edge);
+
+/**
+ * Create a new Link State Message from a Link State Subnet. If Link State
+ * Message is NULL, a new data structure is dynamically allocated.
+ *
+ * @param msg Link State Message to be filled or NULL
+ * @param subnet Link State Subnet. Must not be NULL
+ *
+ * @return New Link State Message msg parameter is NULL or pointer
+ * to the provided Link State Message
+ */
+extern struct ls_message *ls_subnet2msg(struct ls_message *msg,
+ struct ls_subnet *subnet);
+
+/**
+ * Convert Link State Message into Vertex and update TED accordingly to
+ * the message event: SYNC, ADD, UPDATE or DELETE.
+ *
+ * @param ted Link State Database
+ * @param msg Link State Message
+ * @param delete True to delete the Link State Vertex from the Database,
+ * False otherwise. If true, return value is NULL in case
+ * of deletion.
+ *
+ * @return Vertex if success, NULL otherwise or if Vertex is removed
+ */
+extern struct ls_vertex *ls_msg2vertex(struct ls_ted *ted,
+ struct ls_message *msg, bool delete);
+
+/**
+ * Convert Link State Message into Edge and update TED accordingly to
+ * the message event: SYNC, ADD, UPDATE or DELETE.
+ *
+ * @param ted Link State Database
+ * @param msg Link State Message
+ * @param delete True to delete the Link State Edge from the Database,
+ * False otherwise. If true, return value is NULL in case
+ * of deletion.
+ *
+ * @return Edge if success, NULL otherwise or if Edge is removed
+ */
+extern struct ls_edge *ls_msg2edge(struct ls_ted *ted, struct ls_message *msg,
+ bool delete);
+
+/**
+ * Convert Link State Message into Subnet and update TED accordingly to
+ * the message event: SYNC, ADD, UPDATE or DELETE.
+ *
+ * @param ted Link State Database
+ * @param msg Link State Message
+ * @param delete True to delete the Link State Subnet from the Database,
+ * False otherwise. If true, return value is NULL in case
+ * of deletion.
+ *
+ * @return Subnet if success, NULL otherwise or if Subnet is removed
+ */
+extern struct ls_subnet *ls_msg2subnet(struct ls_ted *ted,
+ struct ls_message *msg, bool delete);
+
+/**
+ * Convert Link State Message into Link State element (Vertex, Edge or Subnet)
+ * and update TED accordingly to the message event: SYNC, ADD, UPDATE or DELETE.
+ *
+ * @param ted Link State Database
+ * @param msg Link State Message
+ * @param delete True to delete the Link State Element from the Database,
+ * False otherwise. If true, return value is NULL in case
+ * of deletion.
+ *
+ * @return Element if success, NULL otherwise or if Element is removed
+ */
+extern struct ls_element *ls_msg2ted(struct ls_ted *ted, struct ls_message *msg,
+ bool delete);
+
+/**
+ * Convert stream buffer into Link State element (Vertex, Edge or Subnet) and
+ * update TED accordingly to the message event: SYNC, ADD, UPDATE or DELETE.
+ *
+ * @param ted Link State Database
+ * @param s Stream buffer
+ * @param delete True to delete the Link State Element from the Database,
+ * False otherwise. If true, return value is NULL in case
+ * of deletion.
+ *
+ * @return Element if success, NULL otherwise or if Element is removed
+ */
+extern struct ls_element *ls_stream2ted(struct ls_ted *ted, struct stream *s,
+ bool delete);
+
+/**
+ * Send all the content of the Link State Data Base to the given destination.
+ * Link State content is sent is this order: Vertices, Edges, Subnet.
+ * This function must be used when a daemon request a Link State Data Base
+ * Synchronization.
+ *
+ * @param ted Link State Data Base. Must not be NULL
+ * @param zclient Zebra Client. Must not be NULL
+ * @param dst Destination FRR daemon. Must not be NULL
+ *
+ * @return 0 on success, -1 otherwise
+ */
+extern int ls_sync_ted(struct ls_ted *ted, struct zclient *zclient,
+ struct zapi_opaque_reg_info *dst);
+
+struct json_object;
+struct vty;
+/**
+ * Show Link State Vertex information. If both vty and json are specified,
+ * Json format output supersedes standard vty output.
+ *
+ * @param vertex Link State Vertex to show. Must not be NULL
+ * @param vty Pointer to vty output, could be NULL
+ * @param json Pointer to json output, could be NULL
+ * @param verbose Set to true for more detail
+ */
+extern void ls_show_vertex(struct ls_vertex *vertex, struct vty *vty,
+ struct json_object *json, bool verbose);
+
+/**
+ * Show all Link State Vertices information. If both vty and json are specified,
+ * Json format output supersedes standard vty output.
+ *
+ * @param ted Link State Data Base. Must not be NULL
+ * @param vty Pointer to vty output, could be NULL
+ * @param json Pointer to json output, could be NULL
+ * @param verbose Set to true for more detail
+ */
+extern void ls_show_vertices(struct ls_ted *ted, struct vty *vty,
+ struct json_object *json, bool verbose);
+
+/**
+ * Show Link State Edge information. If both vty and json are specified,
+ * Json format output supersedes standard vty output.
+ *
+ * @param edge Link State Edge to show. Must not be NULL
+ * @param vty Pointer to vty output, could be NULL
+ * @param json Pointer to json output, could be NULL
+ * @param verbose Set to true for more detail
+ */
+extern void ls_show_edge(struct ls_edge *edge, struct vty *vty,
+ struct json_object *json, bool verbose);
+
+/**
+ * Show all Link State Edges information. If both vty and json are specified,
+ * Json format output supersedes standard vty output.
+ *
+ * @param ted Link State Data Base. Must not be NULL
+ * @param vty Pointer to vty output, could be NULL
+ * @param json Pointer to json output, could be NULL
+ * @param verbose Set to true for more detail
+ */
+extern void ls_show_edges(struct ls_ted *ted, struct vty *vty,
+ struct json_object *json, bool verbose);
+
+/**
+ * Show Link State Subnets information. If both vty and json are specified,
+ * Json format output supersedes standard vty output.
+ *
+ * @param subnet Link State Subnet to show. Must not be NULL
+ * @param vty Pointer to vty output, could be NULL
+ * @param json Pointer to json output, could be NULL
+ * @param verbose Set to true for more detail
+ */
+extern void ls_show_subnet(struct ls_subnet *subnet, struct vty *vty,
+ struct json_object *json, bool verbose);
+
+/**
+ * Show all Link State Subnet information. If both vty and json are specified,
+ * Json format output supersedes standard vty output.
+ *
+ * @param ted Link State Data Base. Must not be NULL
+ * @param vty Pointer to vty output, could be NULL
+ * @param json Pointer to json output, could be NULL
+ * @param verbose Set to true for more detail
+ */
+extern void ls_show_subnets(struct ls_ted *ted, struct vty *vty,
+ struct json_object *json, bool verbose);
+
+/**
+ * Show Link State Data Base information. If both vty and json are specified,
+ * Json format output supersedes standard vty output.
+ *
+ * @param ted Link State Data Base to show. Must not be NULL
+ * @param vty Pointer to vty output, could be NULL
+ * @param json Pointer to json output, could be NULL
+ * @param verbose Set to true for more detail
+ */
+extern void ls_show_ted(struct ls_ted *ted, struct vty *vty,
+ struct json_object *json, bool verbose);
+
+/**
+ * Dump all Link State Data Base elements for debugging purposes
+ *
+ * @param ted Link State Data Base. Must not be NULL
+ *
+ */
+extern void ls_dump_ted(struct ls_ted *ted);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRR_LINK_STATE_H_ */
diff --git a/lib/linklist.c b/lib/linklist.c
new file mode 100644
index 0000000..ece24db
--- /dev/null
+++ b/lib/linklist.c
@@ -0,0 +1,446 @@
+/* Generic linked list routine.
+ * Copyright (C) 1997, 2000 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 <stdlib.h>
+
+#include "linklist.h"
+#include "memory.h"
+#include "libfrr_trace.h"
+
+DEFINE_MTYPE_STATIC(LIB, LINK_LIST, "Link List");
+DEFINE_MTYPE_STATIC(LIB, LINK_NODE, "Link Node");
+
+/* these *do not* cleanup list nodes and referenced data, as the functions
+ * do - these macros simply {de,at}tach a listnode from/to a list.
+ */
+
+/* List node attach macro. */
+#define LISTNODE_ATTACH(L, N) \
+ do { \
+ (N)->prev = (L)->tail; \
+ (N)->next = NULL; \
+ if ((L)->head == NULL) \
+ (L)->head = (N); \
+ else \
+ (L)->tail->next = (N); \
+ (L)->tail = (N); \
+ (L)->count++; \
+ } while (0)
+
+/* List node detach macro. */
+#define LISTNODE_DETACH(L, N) \
+ do { \
+ if ((N)->prev) \
+ (N)->prev->next = (N)->next; \
+ else \
+ (L)->head = (N)->next; \
+ if ((N)->next) \
+ (N)->next->prev = (N)->prev; \
+ else \
+ (L)->tail = (N)->prev; \
+ (L)->count--; \
+ } while (0)
+
+struct list *list_new(void)
+{
+ return XCALLOC(MTYPE_LINK_LIST, sizeof(struct list));
+}
+
+/* Free list. */
+static void list_free_internal(struct list *l)
+{
+ XFREE(MTYPE_LINK_LIST, l);
+}
+
+
+/* Allocate new listnode. Internal use only. */
+static struct listnode *listnode_new(struct list *list, void *val)
+{
+ struct listnode *node;
+
+ /* if listnode memory is managed by the app then the val
+ * passed in is the listnode
+ */
+ if (list->flags & LINKLIST_FLAG_NODE_MEM_BY_APP) {
+ node = val;
+ node->prev = node->next = NULL;
+ } else {
+ node = XCALLOC(MTYPE_LINK_NODE, sizeof(struct listnode));
+ node->data = val;
+ }
+ return node;
+}
+
+/* Free listnode. */
+static void listnode_free(struct list *list, struct listnode *node)
+{
+ if (!(list->flags & LINKLIST_FLAG_NODE_MEM_BY_APP))
+ XFREE(MTYPE_LINK_NODE, node);
+}
+
+struct listnode *listnode_add(struct list *list, void *val)
+{
+ frrtrace(2, frr_libfrr, list_add, list, val);
+
+ struct listnode *node;
+
+ assert(val != NULL);
+
+ node = listnode_new(list, val);
+
+ node->prev = list->tail;
+
+ if (list->head == NULL)
+ list->head = node;
+ else
+ list->tail->next = node;
+ list->tail = node;
+
+ list->count++;
+
+ return node;
+}
+
+void listnode_add_head(struct list *list, void *val)
+{
+ struct listnode *node;
+
+ assert(val != NULL);
+
+ node = listnode_new(list, val);
+
+ node->next = list->head;
+
+ if (list->head == NULL)
+ list->head = node;
+ else
+ list->head->prev = node;
+ list->head = node;
+
+ list->count++;
+}
+
+bool listnode_add_sort_nodup(struct list *list, void *val)
+{
+ struct listnode *n;
+ struct listnode *new;
+ int ret;
+ void *data;
+
+ assert(val != NULL);
+
+ if (list->flags & LINKLIST_FLAG_NODE_MEM_BY_APP) {
+ n = val;
+ data = n->data;
+ } else {
+ data = val;
+ }
+
+ if (list->cmp) {
+ for (n = list->head; n; n = n->next) {
+ ret = (*list->cmp)(data, n->data);
+ if (ret < 0) {
+ new = listnode_new(list, val);
+
+ new->next = n;
+ new->prev = n->prev;
+
+ if (n->prev)
+ n->prev->next = new;
+ else
+ list->head = new;
+ n->prev = new;
+ list->count++;
+ return true;
+ }
+ /* found duplicate return false */
+ if (ret == 0)
+ return false;
+ }
+ }
+
+ new = listnode_new(list, val);
+
+ LISTNODE_ATTACH(list, new);
+
+ return true;
+}
+
+struct list *list_dup(struct list *list)
+{
+ struct list *dup;
+ struct listnode *node;
+ void *data;
+
+ assert(list);
+
+ dup = list_new();
+ dup->cmp = list->cmp;
+ dup->del = list->del;
+ for (ALL_LIST_ELEMENTS_RO(list, node, data))
+ listnode_add(dup, data);
+
+ return dup;
+}
+
+void listnode_add_sort(struct list *list, void *val)
+{
+ struct listnode *n;
+ struct listnode *new;
+
+ assert(val != NULL);
+
+ new = listnode_new(list, val);
+ val = new->data;
+
+ if (list->cmp) {
+ for (n = list->head; n; n = n->next) {
+ if ((*list->cmp)(val, n->data) < 0) {
+ new->next = n;
+ new->prev = n->prev;
+
+ if (n->prev)
+ n->prev->next = new;
+ else
+ list->head = new;
+ n->prev = new;
+ list->count++;
+ return;
+ }
+ }
+ }
+
+ new->prev = list->tail;
+
+ if (list->tail)
+ list->tail->next = new;
+ else
+ list->head = new;
+
+ list->tail = new;
+ list->count++;
+}
+
+struct listnode *listnode_add_after(struct list *list, struct listnode *pp,
+ void *val)
+{
+ struct listnode *nn;
+
+ assert(val != NULL);
+
+ nn = listnode_new(list, val);
+
+ if (pp == NULL) {
+ if (list->head)
+ list->head->prev = nn;
+ else
+ list->tail = nn;
+
+ nn->next = list->head;
+ nn->prev = pp;
+
+ list->head = nn;
+ } else {
+ if (pp->next)
+ pp->next->prev = nn;
+ else
+ list->tail = nn;
+
+ nn->next = pp->next;
+ nn->prev = pp;
+
+ pp->next = nn;
+ }
+ list->count++;
+ return nn;
+}
+
+struct listnode *listnode_add_before(struct list *list, struct listnode *pp,
+ void *val)
+{
+ struct listnode *nn;
+
+ assert(val != NULL);
+
+ nn = listnode_new(list, val);
+
+ if (pp == NULL) {
+ if (list->tail)
+ list->tail->next = nn;
+ else
+ list->head = nn;
+
+ nn->prev = list->tail;
+ nn->next = pp;
+
+ list->tail = nn;
+ } else {
+ if (pp->prev)
+ pp->prev->next = nn;
+ else
+ list->head = nn;
+
+ nn->prev = pp->prev;
+ nn->next = pp;
+
+ pp->prev = nn;
+ }
+ list->count++;
+ return nn;
+}
+
+void listnode_move_to_tail(struct list *l, struct listnode *n)
+{
+ LISTNODE_DETACH(l, n);
+ LISTNODE_ATTACH(l, n);
+}
+
+void listnode_delete(struct list *list, const void *val)
+{
+ frrtrace(2, frr_libfrr, list_remove, list, val);
+
+ struct listnode *node = listnode_lookup(list, val);
+
+ if (node)
+ list_delete_node(list, node);
+}
+
+void *listnode_head(struct list *list)
+{
+ struct listnode *node;
+
+ assert(list);
+ node = list->head;
+
+ if (node)
+ return node->data;
+ return NULL;
+}
+
+void list_delete_all_node(struct list *list)
+{
+ struct listnode *node;
+ struct listnode *next;
+
+ assert(list);
+ for (node = list->head; node; node = next) {
+ next = node->next;
+ if (*list->del)
+ (*list->del)(node->data);
+ listnode_free(list, node);
+ }
+ list->head = list->tail = NULL;
+ list->count = 0;
+}
+
+void list_delete(struct list **list)
+{
+ assert(*list);
+ list_delete_all_node(*list);
+ list_free_internal(*list);
+ *list = NULL;
+}
+
+struct listnode *listnode_lookup(struct list *list, const void *data)
+{
+ struct listnode *node;
+
+ assert(list);
+ for (node = listhead(list); node; node = listnextnode(node))
+ if (data == listgetdata(node))
+ return node;
+ return NULL;
+}
+
+struct listnode *listnode_lookup_nocheck(struct list *list, void *data)
+{
+ if (!list)
+ return NULL;
+ return listnode_lookup(list, data);
+}
+
+void list_delete_node(struct list *list, struct listnode *node)
+{
+ frrtrace(2, frr_libfrr, list_delete_node, list, node);
+
+ if (node->prev)
+ node->prev->next = node->next;
+ else
+ list->head = node->next;
+ if (node->next)
+ node->next->prev = node->prev;
+ else
+ list->tail = node->prev;
+ list->count--;
+ listnode_free(list, node);
+}
+
+void list_sort(struct list *list, int (*cmp)(const void **, const void **))
+{
+ frrtrace(1, frr_libfrr, list_sort, list);
+
+ struct listnode *ln, *nn;
+ int i = -1;
+ void *data;
+ size_t n = list->count;
+ void **items;
+ int (*realcmp)(const void *, const void *) =
+ (int (*)(const void *, const void *))cmp;
+
+ if (!n)
+ return;
+
+ items = XCALLOC(MTYPE_TMP, (sizeof(void *)) * n);
+
+ for (ALL_LIST_ELEMENTS(list, ln, nn, data)) {
+ items[++i] = data;
+ list_delete_node(list, ln);
+ }
+
+ qsort(items, n, sizeof(void *), realcmp);
+
+ for (unsigned int j = 0; j < n; ++j)
+ listnode_add(list, items[j]);
+
+ XFREE(MTYPE_TMP, items);
+}
+
+struct listnode *listnode_add_force(struct list **list, void *val)
+{
+ if (*list == NULL)
+ *list = list_new();
+ return listnode_add(*list, val);
+}
+
+void **list_to_array(struct list *list, void **arr, size_t arrlen)
+{
+ struct listnode *ln;
+ void *vp;
+ size_t idx = 0;
+
+ for (ALL_LIST_ELEMENTS_RO(list, ln, vp)) {
+ arr[idx++] = vp;
+ if (idx == arrlen)
+ break;
+ }
+
+ return arr;
+}
diff --git a/lib/linklist.h b/lib/linklist.h
new file mode 100644
index 0000000..e759472
--- /dev/null
+++ b/lib/linklist.h
@@ -0,0 +1,366 @@
+/* Generic linked list
+ * Copyright (C) 1997, 2000 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 _ZEBRA_LINKLIST_H
+#define _ZEBRA_LINKLIST_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* listnodes must always contain data to be valid. Adding an empty node
+ * to a list is invalid
+ */
+struct listnode {
+ struct listnode *next;
+ struct listnode *prev;
+
+ /* private member, use getdata() to retrieve, do not access directly */
+ void *data;
+};
+
+struct list {
+ struct listnode *head;
+ struct listnode *tail;
+
+ /* invariant: count is the number of listnodes in the list */
+ unsigned int count;
+
+ uint8_t flags;
+/* Indicates that listnode memory is managed by the application and
+ * doesn't need to be freed by this library via listnode_delete etc.
+ */
+#define LINKLIST_FLAG_NODE_MEM_BY_APP (1 << 0)
+
+ /*
+ * Returns -1 if val1 < val2, 0 if equal?, 1 if val1 > val2.
+ * Used as definition of sorted for listnode_add_sort
+ */
+ int (*cmp)(void *val1, void *val2);
+
+ /* callback to free user-owned data when listnode is deleted. supplying
+ * this callback is very much encouraged!
+ */
+ void (*del)(void *val);
+};
+
+#define listnextnode(X) ((X) ? ((X)->next) : NULL)
+#define listnextnode_unchecked(X) ((X)->next)
+#define listhead(X) ((X) ? ((X)->head) : NULL)
+#define listhead_unchecked(X) ((X)->head)
+#define listtail(X) ((X) ? ((X)->tail) : NULL)
+#define listtail_unchecked(X) ((X)->tail)
+#define listcount(X) ((X)->count)
+#define list_isempty(X) ((X)->head == NULL && (X)->tail == NULL)
+/* return X->data only if X and X->data are not NULL */
+#define listgetdata(X) (assert(X), assert((X)->data != NULL), (X)->data)
+/* App is going to manage listnode memory */
+#define listset_app_node_mem(X) ((X)->flags |= LINKLIST_FLAG_NODE_MEM_BY_APP)
+#define listnode_init(X, val) ((X)->data = (val))
+
+/*
+ * Create a new linked list.
+ *
+ * Returns:
+ * the created linked list
+ */
+extern struct list *list_new(void);
+
+/*
+ * Add a new element to the tail of a list.
+ *
+ * Runtime is O(1).
+ *
+ * list
+ * list to operate on
+ *
+ * data
+ * element to add
+ */
+extern struct listnode *listnode_add(struct list *list, void *data);
+
+/*
+ * Add a new element to the beginning of a list.
+ *
+ * Runtime is O(1).
+ *
+ * list
+ * list to operate on
+ *
+ * data
+ * If MEM_BY_APP is set this is listnode. Otherwise it is element to add.
+ */
+extern void listnode_add_head(struct list *list, void *data);
+
+/*
+ * Insert a new element into a list with insertion sort.
+ *
+ * If list->cmp is set, this function is used to determine the position to
+ * insert the new element. If it is not set, this function is equivalent to
+ * listnode_add.
+ *
+ * Runtime is O(N).
+ *
+ * list
+ * list to operate on
+ *
+ * val
+ * If MEM_BY_APP is set this is listnode. Otherwise it is element to add.
+ */
+extern void listnode_add_sort(struct list *list, void *val);
+
+/*
+ * Insert a new element into a list after another element.
+ *
+ * Runtime is O(1).
+ *
+ * list
+ * list to operate on
+ *
+ * pp
+ * listnode to insert after
+ *
+ * data
+ * If MEM_BY_APP is set this is listnode. Otherwise it is element to add.
+ *
+ * Returns:
+ * pointer to newly created listnode that contains the inserted data
+ */
+extern struct listnode *listnode_add_after(struct list *list,
+ struct listnode *pp, void *data);
+
+/*
+ * Insert a new element into a list before another element.
+ *
+ * Runtime is O(1).
+ *
+ * list
+ * list to operate on
+ *
+ * pp
+ * listnode to insert before
+ *
+ * data
+ * If MEM_BY_APP is set this is listnode. Otherwise it is element to add.
+ *
+ * Returns:
+ * pointer to newly created listnode that contains the inserted data
+ */
+extern struct listnode *listnode_add_before(struct list *list,
+ struct listnode *pp, void *data);
+
+/*
+ * Move a node to the tail of a list.
+ *
+ * Runtime is O(1).
+ *
+ * list
+ * list to operate on
+ *
+ * node
+ * node to move to tail
+ */
+extern void listnode_move_to_tail(struct list *list, struct listnode *node);
+
+/*
+ * Delete an element from a list.
+ *
+ * Runtime is O(N).
+ *
+ * list
+ * list to operate on
+ *
+ * data
+ * data to insert into list
+ */
+extern void listnode_delete(struct list *list, const void *data);
+
+/*
+ * Find the listnode corresponding to an element in a list.
+ *
+ * list
+ * list to operate on
+ *
+ * data
+ * data to search for
+ *
+ * Returns:
+ * pointer to listnode storing the given data if found, NULL otherwise
+ */
+extern struct listnode *listnode_lookup(struct list *list, const void *data);
+
+/*
+ * Retrieve the element at the head of a list.
+ *
+ * list
+ * list to operate on
+ *
+ * Returns:
+ * data at head of list, or NULL if list is empty
+ */
+extern void *listnode_head(struct list *list);
+
+/*
+ * Sort a list in place.
+ *
+ * The sorting algorithm used is quicksort. Runtimes are equivalent to those of
+ * quicksort plus N. The sort is not stable.
+ *
+ * For portability reasons, the comparison function takes a pointer to pointer
+ * to void. This pointer should be dereferenced to get the actual data pointer.
+ * It is always safe to do this.
+ *
+ * list
+ * list to sort
+ *
+ * cmp
+ * comparison function for quicksort. Should return less than, equal to or
+ * greater than zero if the first argument is less than, equal to or greater
+ * than the second argument.
+ */
+extern void list_sort(struct list *list,
+ int (*cmp)(const void **, const void **));
+
+/*
+ * Convert a list to an array of void pointers.
+ *
+ * Starts from the list head and ends either on the last node of the list or
+ * when the provided array cannot store any more elements.
+ *
+ * list
+ * list to convert
+ *
+ * arr
+ * Pre-allocated array of void *
+ *
+ * arrlen
+ * Number of elements in arr
+ *
+ * Returns:
+ * arr
+ */
+void **list_to_array(struct list *list, void **arr, size_t arrlen);
+
+/*
+ * Delete a list and NULL its pointer.
+ *
+ * If non-null, list->del is called with each data element.
+ *
+ * plist
+ * pointer to list pointer; this will be set to NULL after the list has been
+ * deleted
+ */
+extern void list_delete(struct list **plist);
+
+/*
+ * Delete all nodes from a list without deleting the list itself.
+ *
+ * If non-null, list->del is called with each data element.
+ *
+ * list
+ * list to operate on
+ */
+extern void list_delete_all_node(struct list *list);
+
+/*
+ * Delete a node from a list.
+ *
+ * list->del is not called with the data associated with the node.
+ *
+ * Runtime is O(1).
+ *
+ * list
+ * list to operate on
+ *
+ * node
+ * the node to delete
+ */
+extern void list_delete_node(struct list *list, struct listnode *node);
+
+/*
+ * Insert a new element into a list with insertion sort if there is no
+ * duplicate element present in the list. This assumes the input list is
+ * sorted. If unsorted, it will check for duplicate until it finds out
+ * the position to do insertion sort with the unsorted list.
+ *
+ * If list->cmp is set, this function is used to determine the position to
+ * insert the new element. If it is not set, this function is equivalent to
+ * listnode_add. duplicate element is determined by cmp function returning 0.
+ *
+ * Runtime is O(N).
+ *
+ * list
+ * list to operate on
+ *
+ * val
+ * If MEM_BY_APP is set this is listnode. Otherwise it is element to add.
+ */
+
+extern bool listnode_add_sort_nodup(struct list *list, void *val);
+
+/*
+ * Duplicate the specified list, creating a shallow copy of each of its
+ * elements.
+ *
+ * list
+ * list to duplicate
+ *
+ * Returns:
+ * the duplicated list
+ */
+extern struct list *list_dup(struct list *list);
+
+/* List iteration macro.
+ * Usage: for (ALL_LIST_ELEMENTS (...) { ... }
+ * It is safe to delete the listnode using this macro.
+ */
+#define ALL_LIST_ELEMENTS(list, node, nextnode, data) \
+ (node) = listhead(list), ((data) = NULL); \
+ (node) != NULL \
+ && ((data) = static_cast(data, listgetdata(node)), \
+ (nextnode) = node->next, 1); \
+ (node) = (nextnode), ((data) = NULL)
+
+/* read-only list iteration macro.
+ * Usage: as per ALL_LIST_ELEMENTS, but not safe to delete the listnode Only
+ * use this macro when it is *immediately obvious* the listnode is not
+ * deleted in the body of the loop. Does not have forward-reference overhead
+ * of previous macro.
+ */
+#define ALL_LIST_ELEMENTS_RO(list, node, data) \
+ (node) = listhead(list), ((data) = NULL); \
+ (node) != NULL && ((data) = static_cast(data, listgetdata(node)), 1); \
+ (node) = listnextnode(node), ((data) = NULL)
+
+extern struct listnode *listnode_lookup_nocheck(struct list *list, void *data);
+
+/*
+ * Add a node to *list, if non-NULL. Otherwise, allocate a new list, mail
+ * it back in *list, and add a new node.
+ *
+ * Return: the new node.
+ */
+extern struct listnode *listnode_add_force(struct list **list, void *val);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ZEBRA_LINKLIST_H */
diff --git a/lib/log.c b/lib/log.c
new file mode 100644
index 0000000..9548f26
--- /dev/null
+++ b/lib/log.c
@@ -0,0 +1,662 @@
+/*
+ * Logging of zebra
+ * Copyright (C) 1997, 1998, 1999 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 FRR_DEFINE_DESC_TABLE
+
+#include <zebra.h>
+
+#include "zclient.h"
+#include "log.h"
+#include "memory.h"
+#include "command.h"
+#include "lib_errors.h"
+#include "lib/hook.h"
+#include "printfrr.h"
+#include "frr_pthread.h"
+
+#ifdef HAVE_LIBUNWIND
+#define UNW_LOCAL_ONLY
+#include <libunwind.h>
+#include <dlfcn.h>
+#endif
+
+/**
+ * Looks up a message in a message list by key.
+ *
+ * If the message is not found, returns the provided error message.
+ *
+ * Terminates when it hits a struct message that's all zeros.
+ *
+ * @param mz the message list
+ * @param kz the message key
+ * @param nf the message to return if not found
+ * @return the message
+ */
+const char *lookup_msg(const struct message *mz, int kz, const char *nf)
+{
+ static struct message nt = {0};
+ const char *rz = nf ? nf : "(no message found)";
+ const struct message *pnt;
+ for (pnt = mz; memcmp(pnt, &nt, sizeof(struct message)); pnt++)
+ if (pnt->key == kz) {
+ rz = pnt->str ? pnt->str : rz;
+ break;
+ }
+ return rz;
+}
+
+/* For time string format. */
+size_t frr_timestamp(int timestamp_precision, char *buf, size_t buflen)
+{
+ static struct {
+ time_t last;
+ size_t len;
+ char buf[28];
+ } cache;
+ struct timeval clock;
+
+ gettimeofday(&clock, NULL);
+
+ /* first, we update the cache if the time has changed */
+ if (cache.last != clock.tv_sec) {
+ struct tm tm;
+ cache.last = clock.tv_sec;
+ localtime_r(&cache.last, &tm);
+ cache.len = strftime(cache.buf, sizeof(cache.buf),
+ "%Y/%m/%d %H:%M:%S", &tm);
+ }
+ /* note: it's not worth caching the subsecond part, because
+ chances are that back-to-back calls are not sufficiently close
+ together
+ for the clock not to have ticked forward */
+
+ if (buflen > cache.len) {
+ memcpy(buf, cache.buf, cache.len);
+ if ((timestamp_precision > 0)
+ && (buflen > cache.len + 1 + timestamp_precision)) {
+ /* should we worry about locale issues? */
+ static const int divisor[] = {0, 100000, 10000, 1000,
+ 100, 10, 1};
+ int prec;
+ char *p = buf + cache.len + 1
+ + (prec = timestamp_precision);
+ *p-- = '\0';
+ while (prec > 6)
+ /* this is unlikely to happen, but protect anyway */
+ {
+ *p-- = '0';
+ prec--;
+ }
+ clock.tv_usec /= divisor[prec];
+ do {
+ *p-- = '0' + (clock.tv_usec % 10);
+ clock.tv_usec /= 10;
+ } while (--prec > 0);
+ *p = '.';
+ return cache.len + 1 + timestamp_precision;
+ }
+ buf[cache.len] = '\0';
+ return cache.len;
+ }
+ if (buflen > 0)
+ buf[0] = '\0';
+ return 0;
+}
+
+/*
+ * crash handling
+ *
+ * NB: only AS-Safe (async-signal) functions can be used here!
+ */
+
+/* Note: the goal here is to use only async-signal-safe functions. */
+void zlog_signal(int signo, const char *action, void *siginfo_v,
+ void *program_counter)
+{
+ siginfo_t *siginfo = siginfo_v;
+ time_t now;
+ char buf[sizeof("DEFAULT: Received signal S at T (si_addr 0xP, PC 0xP); aborting...")
+ + 100];
+ struct fbuf fb = { .buf = buf, .pos = buf, .len = sizeof(buf) };
+
+ time(&now);
+
+ bprintfrr(&fb, "Received signal %d at %lld", signo, (long long)now);
+ if (program_counter)
+ bprintfrr(&fb, " (si_addr 0x%tx, PC 0x%tx)",
+ (ptrdiff_t)siginfo->si_addr,
+ (ptrdiff_t)program_counter);
+ else
+ bprintfrr(&fb, " (si_addr 0x%tx)",
+ (ptrdiff_t)siginfo->si_addr);
+ bprintfrr(&fb, "; %s\n", action);
+
+ zlog_sigsafe(fb.buf, fb.pos - fb.buf);
+
+ zlog_backtrace_sigsafe(LOG_CRIT, program_counter);
+
+ fb.pos = buf;
+
+ struct thread *tc;
+ tc = pthread_getspecific(thread_current);
+
+ if (!tc)
+ bprintfrr(&fb, "no thread information available\n");
+ else
+ bprintfrr(&fb, "in thread %s scheduled from %s:%d %s()\n",
+ tc->xref->funcname, tc->xref->xref.file,
+ tc->xref->xref.line, tc->xref->xref.func);
+
+ zlog_sigsafe(fb.buf, fb.pos - fb.buf);
+}
+
+/* Log a backtrace using only async-signal-safe functions.
+ Needs to be enhanced to support syslog logging. */
+void zlog_backtrace_sigsafe(int priority, void *program_counter)
+{
+#ifdef HAVE_LIBUNWIND
+ char buf[256];
+ struct fbuf fb = { .buf = buf, .len = sizeof(buf) };
+ unw_cursor_t cursor;
+ unw_context_t uc;
+ unw_word_t ip, off, sp;
+ Dl_info dlinfo;
+
+ memset(&uc, 0, sizeof(uc));
+ memset(&cursor, 0, sizeof(cursor));
+
+ unw_getcontext(&uc);
+ unw_init_local(&cursor, &uc);
+ while (unw_step(&cursor) > 0) {
+ char name[128] = "?";
+
+ unw_get_reg(&cursor, UNW_REG_IP, &ip);
+ unw_get_reg(&cursor, UNW_REG_SP, &sp);
+
+ if (!unw_get_proc_name(&cursor, buf, sizeof(buf), &off))
+ snprintfrr(name, sizeof(name), "%s+%#lx",
+ buf, (long)off);
+
+ fb.pos = buf;
+ if (unw_is_signal_frame(&cursor))
+ bprintfrr(&fb, " ---- signal ----\n");
+ bprintfrr(&fb, "%-30s %16lx %16lx", name, (long)ip, (long)sp);
+ if (dladdr((void *)ip, &dlinfo))
+ bprintfrr(&fb, " %s (mapped at %p)",
+ dlinfo.dli_fname, dlinfo.dli_fbase);
+ bprintfrr(&fb, "\n");
+ zlog_sigsafe(fb.buf, fb.pos - fb.buf);
+ }
+#elif defined(HAVE_GLIBC_BACKTRACE)
+ void *array[64];
+ int size, i;
+ char buf[128];
+ struct fbuf fb = { .buf = buf, .pos = buf, .len = sizeof(buf) };
+ char **bt = NULL;
+
+ size = backtrace(array, array_size(array));
+ if (size <= 0 || (size_t)size > array_size(array))
+ return;
+
+ bprintfrr(&fb, "Backtrace for %d stack frames:", size);
+ zlog_sigsafe(fb.pos, fb.buf - fb.pos);
+
+ bt = backtrace_symbols(array, size);
+
+ for (i = 0; i < size; i++) {
+ fb.pos = buf;
+ if (bt)
+ bprintfrr(&fb, "%s", bt[i]);
+ else
+ bprintfrr(&fb, "[bt %d] 0x%tx", i,
+ (ptrdiff_t)(array[i]));
+ zlog_sigsafe(fb.buf, fb.pos - fb.buf);
+ }
+ if (bt)
+ free(bt);
+#endif /* HAVE_STRACK_TRACE */
+}
+
+void zlog_backtrace(int priority)
+{
+#ifdef HAVE_LIBUNWIND
+ char buf[100];
+ unw_cursor_t cursor = {};
+ unw_context_t uc;
+ unw_word_t ip, off, sp;
+ Dl_info dlinfo;
+
+ unw_getcontext(&uc);
+ unw_init_local(&cursor, &uc);
+ zlog(priority, "Backtrace:");
+ while (unw_step(&cursor) > 0) {
+ char name[128] = "?";
+
+ unw_get_reg(&cursor, UNW_REG_IP, &ip);
+ unw_get_reg(&cursor, UNW_REG_SP, &sp);
+
+ if (unw_is_signal_frame(&cursor))
+ zlog(priority, " ---- signal ----");
+
+ if (!unw_get_proc_name(&cursor, buf, sizeof(buf), &off))
+ snprintf(name, sizeof(name), "%s+%#lx",
+ buf, (long)off);
+
+ if (dladdr((void *)ip, &dlinfo))
+ zlog(priority, "%-30s %16lx %16lx %s (mapped at %p)",
+ name, (long)ip, (long)sp,
+ dlinfo.dli_fname, dlinfo.dli_fbase);
+ else
+ zlog(priority, "%-30s %16lx %16lx",
+ name, (long)ip, (long)sp);
+ }
+#elif defined(HAVE_GLIBC_BACKTRACE)
+ void *array[20];
+ int size, i;
+ char **strings;
+
+ size = backtrace(array, array_size(array));
+ if (size <= 0 || (size_t)size > array_size(array)) {
+ flog_err_sys(
+ EC_LIB_SYSTEM_CALL,
+ "Cannot get backtrace, returned invalid # of frames %d (valid range is between 1 and %lu)",
+ size, (unsigned long)(array_size(array)));
+ return;
+ }
+ zlog(priority, "Backtrace for %d stack frames:", size);
+ if (!(strings = backtrace_symbols(array, size))) {
+ flog_err_sys(EC_LIB_SYSTEM_CALL,
+ "Cannot get backtrace symbols (out of memory?)");
+ for (i = 0; i < size; i++)
+ zlog(priority, "[bt %d] %p", i, array[i]);
+ } else {
+ for (i = 0; i < size; i++)
+ zlog(priority, "[bt %d] %s", i, strings[i]);
+ free(strings);
+ }
+#else /* !HAVE_GLIBC_BACKTRACE && !HAVE_LIBUNWIND */
+ zlog(priority, "No backtrace available on this platform.");
+#endif
+}
+
+void zlog_thread_info(int log_level)
+{
+ struct thread *tc;
+ tc = pthread_getspecific(thread_current);
+
+ if (tc)
+ zlog(log_level,
+ "Current thread function %s, scheduled from file %s, line %u in %s()",
+ tc->xref->funcname, tc->xref->xref.file,
+ tc->xref->xref.line, tc->xref->xref.func);
+ else
+ zlog(log_level, "Current thread not known/applicable");
+}
+
+void memory_oom(size_t size, const char *name)
+{
+ zlog(LOG_CRIT,
+ "out of memory: failed to allocate %zu bytes for %s object",
+ size, name);
+ zlog_backtrace(LOG_CRIT);
+ log_memstats(stderr, "log");
+ abort();
+}
+
+/* Wrapper around strerror to handle case where it returns NULL. */
+const char *safe_strerror(int errnum)
+{
+ const char *s = strerror(errnum);
+ return (s != NULL) ? s : "Unknown error";
+}
+
+#define DESC_ENTRY(T) [(T)] = { (T), (#T), '\0' }
+static const struct zebra_desc_table command_types[] = {
+ DESC_ENTRY(ZEBRA_INTERFACE_ADD),
+ DESC_ENTRY(ZEBRA_INTERFACE_DELETE),
+ DESC_ENTRY(ZEBRA_INTERFACE_ADDRESS_ADD),
+ DESC_ENTRY(ZEBRA_INTERFACE_ADDRESS_DELETE),
+ DESC_ENTRY(ZEBRA_INTERFACE_UP),
+ DESC_ENTRY(ZEBRA_INTERFACE_DOWN),
+ DESC_ENTRY(ZEBRA_INTERFACE_SET_MASTER),
+ DESC_ENTRY(ZEBRA_ROUTE_ADD),
+ DESC_ENTRY(ZEBRA_ROUTE_DELETE),
+ DESC_ENTRY(ZEBRA_ROUTE_NOTIFY_OWNER),
+ DESC_ENTRY(ZEBRA_REDISTRIBUTE_ADD),
+ DESC_ENTRY(ZEBRA_REDISTRIBUTE_DELETE),
+ DESC_ENTRY(ZEBRA_REDISTRIBUTE_DEFAULT_ADD),
+ DESC_ENTRY(ZEBRA_REDISTRIBUTE_DEFAULT_DELETE),
+ DESC_ENTRY(ZEBRA_ROUTER_ID_ADD),
+ DESC_ENTRY(ZEBRA_ROUTER_ID_DELETE),
+ DESC_ENTRY(ZEBRA_ROUTER_ID_UPDATE),
+ DESC_ENTRY(ZEBRA_HELLO),
+ DESC_ENTRY(ZEBRA_CAPABILITIES),
+ DESC_ENTRY(ZEBRA_NEXTHOP_REGISTER),
+ DESC_ENTRY(ZEBRA_NEXTHOP_UNREGISTER),
+ DESC_ENTRY(ZEBRA_NEXTHOP_UPDATE),
+ DESC_ENTRY(ZEBRA_INTERFACE_NBR_ADDRESS_ADD),
+ DESC_ENTRY(ZEBRA_INTERFACE_NBR_ADDRESS_DELETE),
+ DESC_ENTRY(ZEBRA_INTERFACE_BFD_DEST_UPDATE),
+ DESC_ENTRY(ZEBRA_BFD_DEST_REGISTER),
+ DESC_ENTRY(ZEBRA_BFD_DEST_DEREGISTER),
+ DESC_ENTRY(ZEBRA_BFD_DEST_UPDATE),
+ DESC_ENTRY(ZEBRA_BFD_DEST_REPLAY),
+ DESC_ENTRY(ZEBRA_REDISTRIBUTE_ROUTE_ADD),
+ DESC_ENTRY(ZEBRA_REDISTRIBUTE_ROUTE_DEL),
+ DESC_ENTRY(ZEBRA_VRF_UNREGISTER),
+ DESC_ENTRY(ZEBRA_VRF_ADD),
+ DESC_ENTRY(ZEBRA_VRF_DELETE),
+ DESC_ENTRY(ZEBRA_VRF_LABEL),
+ DESC_ENTRY(ZEBRA_INTERFACE_VRF_UPDATE),
+ DESC_ENTRY(ZEBRA_BFD_CLIENT_REGISTER),
+ DESC_ENTRY(ZEBRA_BFD_CLIENT_DEREGISTER),
+ DESC_ENTRY(ZEBRA_INTERFACE_ENABLE_RADV),
+ DESC_ENTRY(ZEBRA_INTERFACE_DISABLE_RADV),
+ DESC_ENTRY(ZEBRA_NEXTHOP_LOOKUP_MRIB),
+ DESC_ENTRY(ZEBRA_INTERFACE_LINK_PARAMS),
+ DESC_ENTRY(ZEBRA_MPLS_LABELS_ADD),
+ DESC_ENTRY(ZEBRA_MPLS_LABELS_DELETE),
+ DESC_ENTRY(ZEBRA_MPLS_LABELS_REPLACE),
+ DESC_ENTRY(ZEBRA_SR_POLICY_SET),
+ DESC_ENTRY(ZEBRA_SR_POLICY_DELETE),
+ DESC_ENTRY(ZEBRA_SR_POLICY_NOTIFY_STATUS),
+ DESC_ENTRY(ZEBRA_IPMR_ROUTE_STATS),
+ DESC_ENTRY(ZEBRA_LABEL_MANAGER_CONNECT),
+ DESC_ENTRY(ZEBRA_LABEL_MANAGER_CONNECT_ASYNC),
+ DESC_ENTRY(ZEBRA_GET_LABEL_CHUNK),
+ DESC_ENTRY(ZEBRA_RELEASE_LABEL_CHUNK),
+ DESC_ENTRY(ZEBRA_FEC_REGISTER),
+ DESC_ENTRY(ZEBRA_FEC_UNREGISTER),
+ DESC_ENTRY(ZEBRA_FEC_UPDATE),
+ DESC_ENTRY(ZEBRA_ADVERTISE_ALL_VNI),
+ DESC_ENTRY(ZEBRA_ADVERTISE_DEFAULT_GW),
+ DESC_ENTRY(ZEBRA_ADVERTISE_SVI_MACIP),
+ DESC_ENTRY(ZEBRA_ADVERTISE_SUBNET),
+ DESC_ENTRY(ZEBRA_LOCAL_ES_ADD),
+ DESC_ENTRY(ZEBRA_LOCAL_ES_DEL),
+ DESC_ENTRY(ZEBRA_REMOTE_ES_VTEP_ADD),
+ DESC_ENTRY(ZEBRA_REMOTE_ES_VTEP_DEL),
+ DESC_ENTRY(ZEBRA_LOCAL_ES_EVI_ADD),
+ DESC_ENTRY(ZEBRA_LOCAL_ES_EVI_DEL),
+ DESC_ENTRY(ZEBRA_VNI_ADD),
+ DESC_ENTRY(ZEBRA_VNI_DEL),
+ DESC_ENTRY(ZEBRA_L3VNI_ADD),
+ DESC_ENTRY(ZEBRA_L3VNI_DEL),
+ DESC_ENTRY(ZEBRA_REMOTE_VTEP_ADD),
+ DESC_ENTRY(ZEBRA_REMOTE_VTEP_DEL),
+ DESC_ENTRY(ZEBRA_MACIP_ADD),
+ DESC_ENTRY(ZEBRA_MACIP_DEL),
+ DESC_ENTRY(ZEBRA_IP_PREFIX_ROUTE_ADD),
+ DESC_ENTRY(ZEBRA_IP_PREFIX_ROUTE_DEL),
+ DESC_ENTRY(ZEBRA_REMOTE_MACIP_ADD),
+ DESC_ENTRY(ZEBRA_REMOTE_MACIP_DEL),
+ DESC_ENTRY(ZEBRA_DUPLICATE_ADDR_DETECTION),
+ DESC_ENTRY(ZEBRA_PW_ADD),
+ DESC_ENTRY(ZEBRA_PW_DELETE),
+ DESC_ENTRY(ZEBRA_PW_SET),
+ DESC_ENTRY(ZEBRA_PW_UNSET),
+ DESC_ENTRY(ZEBRA_PW_STATUS_UPDATE),
+ DESC_ENTRY(ZEBRA_RULE_ADD),
+ DESC_ENTRY(ZEBRA_RULE_DELETE),
+ DESC_ENTRY(ZEBRA_RULE_NOTIFY_OWNER),
+ DESC_ENTRY(ZEBRA_TABLE_MANAGER_CONNECT),
+ DESC_ENTRY(ZEBRA_GET_TABLE_CHUNK),
+ DESC_ENTRY(ZEBRA_RELEASE_TABLE_CHUNK),
+ DESC_ENTRY(ZEBRA_IPSET_CREATE),
+ DESC_ENTRY(ZEBRA_IPSET_DESTROY),
+ DESC_ENTRY(ZEBRA_IPSET_ENTRY_ADD),
+ DESC_ENTRY(ZEBRA_IPSET_ENTRY_DELETE),
+ DESC_ENTRY(ZEBRA_IPSET_NOTIFY_OWNER),
+ DESC_ENTRY(ZEBRA_IPSET_ENTRY_NOTIFY_OWNER),
+ DESC_ENTRY(ZEBRA_IPTABLE_ADD),
+ DESC_ENTRY(ZEBRA_IPTABLE_DELETE),
+ DESC_ENTRY(ZEBRA_IPTABLE_NOTIFY_OWNER),
+ DESC_ENTRY(ZEBRA_VXLAN_FLOOD_CONTROL),
+ DESC_ENTRY(ZEBRA_VXLAN_SG_ADD),
+ DESC_ENTRY(ZEBRA_VXLAN_SG_DEL),
+ DESC_ENTRY(ZEBRA_VXLAN_SG_REPLAY),
+ DESC_ENTRY(ZEBRA_MLAG_PROCESS_UP),
+ DESC_ENTRY(ZEBRA_MLAG_PROCESS_DOWN),
+ DESC_ENTRY(ZEBRA_MLAG_CLIENT_REGISTER),
+ DESC_ENTRY(ZEBRA_MLAG_CLIENT_UNREGISTER),
+ DESC_ENTRY(ZEBRA_MLAG_FORWARD_MSG),
+ DESC_ENTRY(ZEBRA_ERROR),
+ DESC_ENTRY(ZEBRA_CLIENT_CAPABILITIES),
+ DESC_ENTRY(ZEBRA_OPAQUE_MESSAGE),
+ DESC_ENTRY(ZEBRA_OPAQUE_REGISTER),
+ DESC_ENTRY(ZEBRA_OPAQUE_UNREGISTER),
+ DESC_ENTRY(ZEBRA_NEIGH_DISCOVER),
+ DESC_ENTRY(ZEBRA_NHG_ADD),
+ DESC_ENTRY(ZEBRA_NHG_DEL),
+ DESC_ENTRY(ZEBRA_NHG_NOTIFY_OWNER),
+ DESC_ENTRY(ZEBRA_ROUTE_NOTIFY_REQUEST),
+ DESC_ENTRY(ZEBRA_CLIENT_CLOSE_NOTIFY),
+ DESC_ENTRY(ZEBRA_EVPN_REMOTE_NH_ADD),
+ DESC_ENTRY(ZEBRA_EVPN_REMOTE_NH_DEL),
+ DESC_ENTRY(ZEBRA_NHRP_NEIGH_ADDED),
+ DESC_ENTRY(ZEBRA_NHRP_NEIGH_REMOVED),
+ DESC_ENTRY(ZEBRA_NHRP_NEIGH_GET),
+ DESC_ENTRY(ZEBRA_NHRP_NEIGH_REGISTER),
+ DESC_ENTRY(ZEBRA_NHRP_NEIGH_UNREGISTER),
+ DESC_ENTRY(ZEBRA_NEIGH_IP_ADD),
+ DESC_ENTRY(ZEBRA_NEIGH_IP_DEL),
+ DESC_ENTRY(ZEBRA_CONFIGURE_ARP),
+ DESC_ENTRY(ZEBRA_GRE_GET),
+ DESC_ENTRY(ZEBRA_GRE_UPDATE),
+ DESC_ENTRY(ZEBRA_GRE_SOURCE_SET)};
+#undef DESC_ENTRY
+
+static const struct zebra_desc_table unknown = {0, "unknown", '?'};
+
+static const struct zebra_desc_table *zroute_lookup(unsigned int zroute)
+{
+ unsigned int i;
+
+ if (zroute >= array_size(route_types)) {
+ flog_err(EC_LIB_DEVELOPMENT, "unknown zebra route type: %u",
+ zroute);
+ return &unknown;
+ }
+ if (zroute == route_types[zroute].type)
+ return &route_types[zroute];
+ for (i = 0; i < array_size(route_types); i++) {
+ if (zroute == route_types[i].type) {
+ zlog_warn(
+ "internal error: route type table out of order while searching for %u, please notify developers",
+ zroute);
+ return &route_types[i];
+ }
+ }
+ flog_err(EC_LIB_DEVELOPMENT,
+ "internal error: cannot find route type %u in table!", zroute);
+ return &unknown;
+}
+
+const char *zebra_route_string(unsigned int zroute)
+{
+ return zroute_lookup(zroute)->string;
+}
+
+char zebra_route_char(unsigned int zroute)
+{
+ return zroute_lookup(zroute)->chr;
+}
+
+const char *zserv_command_string(unsigned int command)
+{
+ if (command >= array_size(command_types)) {
+ flog_err(EC_LIB_DEVELOPMENT, "unknown zserv command type: %u",
+ command);
+ return unknown.string;
+ }
+ return command_types[command].string;
+}
+
+int proto_name2num(const char *s)
+{
+ unsigned i;
+
+ for (i = 0; i < array_size(route_types); ++i)
+ if (strcasecmp(s, route_types[i].string) == 0)
+ return route_types[i].type;
+ return -1;
+}
+
+int proto_redistnum(int afi, const char *s)
+{
+ if (!s)
+ return -1;
+
+ if (afi == AFI_IP) {
+ if (strmatch(s, "kernel"))
+ return ZEBRA_ROUTE_KERNEL;
+ else if (strmatch(s, "connected"))
+ return ZEBRA_ROUTE_CONNECT;
+ else if (strmatch(s, "static"))
+ return ZEBRA_ROUTE_STATIC;
+ else if (strmatch(s, "rip"))
+ return ZEBRA_ROUTE_RIP;
+ else if (strmatch(s, "eigrp"))
+ return ZEBRA_ROUTE_EIGRP;
+ else if (strmatch(s, "ospf"))
+ return ZEBRA_ROUTE_OSPF;
+ else if (strmatch(s, "isis"))
+ return ZEBRA_ROUTE_ISIS;
+ else if (strmatch(s, "bgp"))
+ return ZEBRA_ROUTE_BGP;
+ else if (strmatch(s, "table"))
+ return ZEBRA_ROUTE_TABLE;
+ else if (strmatch(s, "vnc"))
+ return ZEBRA_ROUTE_VNC;
+ else if (strmatch(s, "vnc-direct"))
+ return ZEBRA_ROUTE_VNC_DIRECT;
+ else if (strmatch(s, "nhrp"))
+ return ZEBRA_ROUTE_NHRP;
+ else if (strmatch(s, "babel"))
+ return ZEBRA_ROUTE_BABEL;
+ else if (strmatch(s, "sharp"))
+ return ZEBRA_ROUTE_SHARP;
+ else if (strmatch(s, "openfabric"))
+ return ZEBRA_ROUTE_OPENFABRIC;
+ }
+ if (afi == AFI_IP6) {
+ if (strmatch(s, "kernel"))
+ return ZEBRA_ROUTE_KERNEL;
+ else if (strmatch(s, "connected"))
+ return ZEBRA_ROUTE_CONNECT;
+ else if (strmatch(s, "static"))
+ return ZEBRA_ROUTE_STATIC;
+ else if (strmatch(s, "ripng"))
+ return ZEBRA_ROUTE_RIPNG;
+ else if (strmatch(s, "ospf6"))
+ return ZEBRA_ROUTE_OSPF6;
+ else if (strmatch(s, "isis"))
+ return ZEBRA_ROUTE_ISIS;
+ else if (strmatch(s, "bgp"))
+ return ZEBRA_ROUTE_BGP;
+ else if (strmatch(s, "table"))
+ return ZEBRA_ROUTE_TABLE;
+ else if (strmatch(s, "vnc"))
+ return ZEBRA_ROUTE_VNC;
+ else if (strmatch(s, "vnc-direct"))
+ return ZEBRA_ROUTE_VNC_DIRECT;
+ else if (strmatch(s, "nhrp"))
+ return ZEBRA_ROUTE_NHRP;
+ else if (strmatch(s, "babel"))
+ return ZEBRA_ROUTE_BABEL;
+ else if (strmatch(s, "sharp"))
+ return ZEBRA_ROUTE_SHARP;
+ else if (strmatch(s, "openfabric"))
+ return ZEBRA_ROUTE_OPENFABRIC;
+ }
+ return -1;
+}
+
+void zlog_hexdump(const void *mem, size_t len)
+{
+ char line[64];
+ const uint8_t *src = mem;
+ const uint8_t *end = src + len;
+
+ if (len == 0) {
+ zlog_debug("%016lx: (zero length / no data)", (long)src);
+ return;
+ }
+
+ while (src < end) {
+ struct fbuf fb = {
+ .buf = line,
+ .pos = line,
+ .len = sizeof(line),
+ };
+ const uint8_t *lineend = src + 8;
+ unsigned line_bytes = 0;
+
+ bprintfrr(&fb, "%016lx: ", (long)src);
+
+ while (src < lineend && src < end) {
+ bprintfrr(&fb, "%02x ", *src++);
+ line_bytes++;
+ }
+ if (line_bytes < 8)
+ bprintfrr(&fb, "%*s", (8 - line_bytes) * 3, "");
+
+ src -= line_bytes;
+ while (src < lineend && src < end && fb.pos < fb.buf + fb.len) {
+ uint8_t byte = *src++;
+
+ if (isprint(byte))
+ *fb.pos++ = byte;
+ else
+ *fb.pos++ = '.';
+ }
+
+ zlog_debug("%.*s", (int)(fb.pos - fb.buf), fb.buf);
+ }
+}
+
+const char *zlog_sanitize(char *buf, size_t bufsz, const void *in, size_t inlen)
+{
+ const char *inbuf = in;
+ char *pos = buf, *end = buf + bufsz;
+ const char *iend = inbuf + inlen;
+
+ memset(buf, 0, bufsz);
+ for (; inbuf < iend; inbuf++) {
+ /* don't write partial escape sequence */
+ if (end - pos < 5)
+ break;
+
+ if (*inbuf == '\n')
+ snprintf(pos, end - pos, "\\n");
+ else if (*inbuf == '\r')
+ snprintf(pos, end - pos, "\\r");
+ else if (*inbuf == '\t')
+ snprintf(pos, end - pos, "\\t");
+ else if (*inbuf < ' ' || *inbuf == '"' || *inbuf >= 127)
+ snprintf(pos, end - pos, "\\x%02hhx", *inbuf);
+ else
+ *pos = *inbuf;
+
+ pos += strlen(pos);
+ }
+ return buf;
+}
diff --git a/lib/log.h b/lib/log.h
new file mode 100644
index 0000000..3011fa9
--- /dev/null
+++ b/lib/log.h
@@ -0,0 +1,178 @@
+/*
+ * Zebra logging funcions.
+ * Copyright (C) 1997, 1998, 1999 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 _ZEBRA_LOG_H
+#define _ZEBRA_LOG_H
+
+#include <syslog.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+#include "lib/hook.h"
+#include "lib/zlog.h"
+#include "lib/zlog_targets.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Here is some guidance on logging levels to use:
+ *
+ * LOG_DEBUG - For all messages that are enabled by optional debugging
+ * features, typically preceded by "if (IS...DEBUG...)"
+ * LOG_INFO - Information that may be of interest, but everything seems
+ * to be working properly.
+ * LOG_NOTICE - Only for message pertaining to daemon startup or shutdown.
+ * LOG_WARNING - Warning conditions: unexpected events, but the daemon believes
+ * it can continue to operate correctly.
+ * LOG_ERR - Error situations indicating malfunctions. Probably require
+ * attention.
+ *
+ * Note: LOG_CRIT, LOG_ALERT, and LOG_EMERG are currently not used anywhere,
+ * please use LOG_ERR instead.
+ */
+
+extern void zlog_rotate(void);
+
+/* Message structure. */
+struct message {
+ int key;
+ const char *str;
+};
+
+extern void zlog_thread_info(int log_level);
+
+#define ZLOG_FILTERS_MAX 100 /* Max # of filters at once */
+#define ZLOG_FILTER_LENGTH_MAX 80 /* 80 character filter limit */
+
+struct zlog_cfg_filterfile {
+ struct zlog_cfg_file parent;
+};
+
+extern void zlog_filterfile_init(struct zlog_cfg_filterfile *zcf);
+extern void zlog_filterfile_fini(struct zlog_cfg_filterfile *zcf);
+
+/* Add/Del/Dump log filters */
+extern void zlog_filter_clear(void);
+extern int zlog_filter_add(const char *filter);
+extern int zlog_filter_del(const char *filter);
+extern int zlog_filter_dump(char *buf, size_t max_size);
+
+const char *lookup_msg(const struct message *mz, int kz, const char *nf);
+
+/* Safe version of strerror -- never returns NULL. */
+extern const char *safe_strerror(int errnum);
+
+/* To be called when a fatal signal is caught. */
+extern void zlog_signal(int signo, const char *action, void *siginfo,
+ void *program_counter);
+
+/* Log a backtrace. */
+extern void zlog_backtrace(int priority);
+
+/* Log a backtrace, but in an async-signal-safe way. Should not be
+ called unless the program is about to exit or abort, since it messes
+ up the state of zlog file pointers. If program_counter is non-NULL,
+ that is logged in addition to the current backtrace. */
+extern void zlog_backtrace_sigsafe(int priority, void *program_counter);
+
+/* Puts a current timestamp in buf and returns the number of characters
+ written (not including the terminating NUL). The purpose of
+ this function is to avoid calls to localtime appearing all over the code.
+ It caches the most recent localtime result and can therefore
+ avoid multiple calls within the same second. If buflen is too small,
+ *buf will be set to '\0', and 0 will be returned. */
+#define FRR_TIMESTAMP_LEN 40
+extern size_t frr_timestamp(int timestamp_precision /* # subsecond digits */,
+ char *buf, size_t buflen);
+
+extern void zlog_hexdump(const void *mem, size_t len);
+extern const char *zlog_sanitize(char *buf, size_t bufsz, const void *in,
+ size_t inlen);
+
+/* Note: whenever a new route-type or zserv-command is added the
+ * corresponding {command,route}_types[] table in lib/log.c MUST be
+ * updated! */
+
+/* Map a route type to a string. For example, ZEBRA_ROUTE_RIPNG -> "ripng". */
+extern const char *zebra_route_string(unsigned int route_type);
+/* Map a route type to a char. For example, ZEBRA_ROUTE_RIPNG -> 'R'. */
+extern char zebra_route_char(unsigned int route_type);
+/* Map a zserv command type to the same string,
+ * e.g. ZEBRA_INTERFACE_ADD -> "ZEBRA_INTERFACE_ADD" */
+/* Map a protocol name to its number. e.g. ZEBRA_ROUTE_BGP->9*/
+extern int proto_name2num(const char *s);
+/* Map redistribute X argument to protocol number.
+ * unlike proto_name2num, this accepts shorthands and takes
+ * an AFI value to restrict input */
+extern int proto_redistnum(int afi, const char *s);
+
+extern const char *zserv_command_string(unsigned int command);
+
+
+/* structure useful for avoiding repeated rendering of the same timestamp */
+struct timestamp_control {
+ size_t len; /* length of rendered timestamp */
+ int precision; /* configuration parameter */
+ int already_rendered; /* should be initialized to 0 */
+ char buf[FRR_TIMESTAMP_LEN]; /* will contain the rendered timestamp
+ */
+};
+
+/* Defines for use in command construction: */
+
+#define LOG_LEVEL_DESC \
+ "System is unusable\n" \
+ "Immediate action needed\n" \
+ "Critical conditions\n" \
+ "Error conditions\n" \
+ "Warning conditions\n" \
+ "Normal but significant conditions\n" \
+ "Informational messages\n" \
+ "Debugging messages\n"
+
+#define LOG_FACILITY_DESC \
+ "Kernel\n" \
+ "User process\n" \
+ "Mail system\n" \
+ "System daemons\n" \
+ "Authorization system\n" \
+ "Syslog itself\n" \
+ "Line printer system\n" \
+ "USENET news\n" \
+ "Unix-to-Unix copy system\n" \
+ "Cron/at facility\n" \
+ "Local use\n" \
+ "Local use\n" \
+ "Local use\n" \
+ "Local use\n" \
+ "Local use\n" \
+ "Local use\n" \
+ "Local use\n" \
+ "Local use\n"
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ZEBRA_LOG_H */
diff --git a/lib/log_filter.c b/lib/log_filter.c
new file mode 100644
index 0000000..df74a8c
--- /dev/null
+++ b/lib/log_filter.c
@@ -0,0 +1,160 @@
+/*
+ * Logging - Filtered file log target
+ * Copyright (C) 2019 Cumulus Networks, Inc.
+ * Stephen Worley
+ *
+ * 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 "frr_pthread.h"
+#include "log.h"
+
+static pthread_mutex_t logfilterlock = PTHREAD_MUTEX_INITIALIZER;
+static char zlog_filters[ZLOG_FILTERS_MAX][ZLOG_FILTER_LENGTH_MAX + 1];
+static uint8_t zlog_filter_count;
+
+/*
+ * look for a match on the filter in the current filters,
+ * logfilterlock must be held
+ */
+static int zlog_filter_lookup(const char *lookup)
+{
+ for (int i = 0; i < zlog_filter_count; i++) {
+ if (strncmp(lookup, zlog_filters[i], sizeof(zlog_filters[0]))
+ == 0)
+ return i;
+ }
+ return -1;
+}
+
+void zlog_filter_clear(void)
+{
+ frr_with_mutex (&logfilterlock) {
+ zlog_filter_count = 0;
+ }
+}
+
+int zlog_filter_add(const char *filter)
+{
+ frr_with_mutex (&logfilterlock) {
+ if (zlog_filter_count >= ZLOG_FILTERS_MAX)
+ return 1;
+
+ if (zlog_filter_lookup(filter) != -1)
+ /* Filter already present */
+ return -1;
+
+ strlcpy(zlog_filters[zlog_filter_count], filter,
+ sizeof(zlog_filters[0]));
+
+ if (zlog_filters[zlog_filter_count][0] == '\0')
+ /* Filter was either empty or didn't get copied
+ * correctly
+ */
+ return -1;
+
+ zlog_filter_count++;
+ }
+ return 0;
+}
+
+int zlog_filter_del(const char *filter)
+{
+ frr_with_mutex (&logfilterlock) {
+ int found_idx = zlog_filter_lookup(filter);
+ int last_idx = zlog_filter_count - 1;
+
+ if (found_idx == -1)
+ /* Didn't find the filter to delete */
+ return -1;
+
+ /* Adjust the filter array */
+ memmove(zlog_filters[found_idx], zlog_filters[found_idx + 1],
+ (last_idx - found_idx) * sizeof(zlog_filters[0]));
+
+ zlog_filter_count--;
+ }
+ return 0;
+}
+
+/* Dump all filters to buffer, delimited by new line */
+int zlog_filter_dump(char *buf, size_t max_size)
+{
+ int len = 0;
+
+ frr_with_mutex (&logfilterlock) {
+ for (int i = 0; i < zlog_filter_count; i++) {
+ int ret;
+
+ ret = snprintf(buf + len, max_size - len, " %s\n",
+ zlog_filters[i]);
+ len += ret;
+ if ((ret < 0) || ((size_t)len >= max_size))
+ return -1;
+ }
+ }
+
+ return len;
+}
+
+static int search_buf(const char *buf, size_t len)
+{
+ char *found = NULL;
+
+ frr_with_mutex (&logfilterlock) {
+ for (int i = 0; i < zlog_filter_count; i++) {
+ found = memmem(buf, len, zlog_filters[i],
+ strlen(zlog_filters[i]));
+ if (found != NULL)
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+static void zlog_filterfile_fd(struct zlog_target *zt, struct zlog_msg *msgs[],
+ size_t nmsgs)
+{
+ struct zlog_msg *msgfilt[nmsgs];
+ size_t i, o = 0;
+ const char *text;
+ size_t text_len;
+
+ for (i = 0; i < nmsgs; i++) {
+ if (zlog_msg_prio(msgs[i]) >= LOG_DEBUG) {
+ text = zlog_msg_text(msgs[i], &text_len);
+ if (search_buf(text, text_len) < 0)
+ continue;
+ }
+ msgfilt[o++] = msgs[i];
+ }
+
+ if (o)
+ zlog_fd(zt, msgfilt, o);
+}
+
+void zlog_filterfile_init(struct zlog_cfg_filterfile *zcf)
+{
+ zlog_file_init(&zcf->parent);
+ zcf->parent.zlog_wrap = zlog_filterfile_fd;
+}
+
+void zlog_filterfile_fini(struct zlog_cfg_filterfile *zcf)
+{
+ zlog_file_fini(&zcf->parent);
+}
diff --git a/lib/log_vty.c b/lib/log_vty.c
new file mode 100644
index 0000000..81280f3
--- /dev/null
+++ b/lib/log_vty.c
@@ -0,0 +1,928 @@
+/*
+ * Logging - VTY code
+ * Copyright (C) 2019 Cumulus Networks, Inc.
+ * Stephen Worley
+ *
+ * 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 "lib/log_vty.h"
+#include "command.h"
+#include "lib/log.h"
+#include "lib/zlog_targets.h"
+#include "lib/zlog_5424.h"
+#include "lib/lib_errors.h"
+#include "lib/printfrr.h"
+#include "lib/systemd.h"
+
+#ifndef VTYSH_EXTRACT_PL
+#include "lib/log_vty_clippy.c"
+#endif
+
+#define ZLOG_MAXLVL(a, b) MAX(a, b)
+
+DEFINE_HOOK(zlog_rotate, (), ());
+DEFINE_HOOK(zlog_cli_show, (struct vty * vty), (vty));
+
+static unsigned logmsgs_with_persist_bt;
+
+static const int log_default_lvl = LOG_DEBUG;
+
+static int log_config_stdout_lvl = ZLOG_DISABLED;
+static int log_config_syslog_lvl = ZLOG_DISABLED;
+static int log_cmdline_stdout_lvl = ZLOG_DISABLED;
+static int log_cmdline_syslog_lvl = ZLOG_DISABLED;
+
+static struct zlog_cfg_file zt_file_cmdline = {
+ .prio_min = ZLOG_DISABLED,
+};
+static struct zlog_cfg_file zt_file = {
+ .prio_min = ZLOG_DISABLED,
+};
+static struct zlog_cfg_filterfile zt_filterfile = {
+ .parent = {
+ .prio_min = ZLOG_DISABLED,
+ },
+};
+
+static struct zlog_cfg_file zt_stdout_file = {
+ .prio_min = ZLOG_DISABLED,
+};
+static struct zlog_cfg_5424 zt_stdout_journald = {
+ .prio_min = ZLOG_DISABLED,
+
+ .fmt = ZLOG_FMT_JOURNALD,
+ .dst = ZLOG_5424_DST_UNIX,
+ .filename = "/run/systemd/journal/socket",
+
+ /* this can't be changed through config since this target substitutes
+ * in for the "plain" stdout target
+ */
+ .facility = LOG_DAEMON,
+ .kw_version = false,
+ .kw_location = true,
+ .kw_uid = true,
+ .kw_ec = true,
+ .kw_args = true,
+};
+static bool stdout_journald_in_use;
+
+const char *zlog_progname;
+static const char *zlog_protoname;
+
+static const struct facility_map {
+ int facility;
+ const char *name;
+ size_t match;
+} syslog_facilities[] = {
+ {LOG_KERN, "kern", 1},
+ {LOG_USER, "user", 2},
+ {LOG_MAIL, "mail", 1},
+ {LOG_DAEMON, "daemon", 1},
+ {LOG_AUTH, "auth", 1},
+ {LOG_SYSLOG, "syslog", 1},
+ {LOG_LPR, "lpr", 2},
+ {LOG_NEWS, "news", 1},
+ {LOG_UUCP, "uucp", 2},
+ {LOG_CRON, "cron", 1},
+#ifdef LOG_FTP
+ {LOG_FTP, "ftp", 1},
+#endif
+ {LOG_LOCAL0, "local0", 6},
+ {LOG_LOCAL1, "local1", 6},
+ {LOG_LOCAL2, "local2", 6},
+ {LOG_LOCAL3, "local3", 6},
+ {LOG_LOCAL4, "local4", 6},
+ {LOG_LOCAL5, "local5", 6},
+ {LOG_LOCAL6, "local6", 6},
+ {LOG_LOCAL7, "local7", 6},
+ {0, NULL, 0},
+};
+
+static const char * const zlog_priority[] = {
+ "emergencies", "alerts", "critical", "errors", "warnings",
+ "notifications", "informational", "debugging", NULL,
+};
+
+const char *zlog_priority_str(int priority)
+{
+ if (priority > LOG_DEBUG)
+ return "???";
+ return zlog_priority[priority];
+}
+
+const char *facility_name(int facility)
+{
+ const struct facility_map *fm;
+
+ for (fm = syslog_facilities; fm->name; fm++)
+ if (fm->facility == facility)
+ return fm->name;
+ return "";
+}
+
+int facility_match(const char *str)
+{
+ const struct facility_map *fm;
+
+ for (fm = syslog_facilities; fm->name; fm++)
+ if (!strncmp(str, fm->name, fm->match))
+ return fm->facility;
+ return -1;
+}
+
+int log_level_match(const char *s)
+{
+ int level;
+
+ for (level = 0; zlog_priority[level] != NULL; level++)
+ if (!strncmp(s, zlog_priority[level], 2))
+ return level;
+ return ZLOG_DISABLED;
+}
+
+void zlog_rotate(void)
+{
+ zlog_file_rotate(&zt_file);
+ zlog_file_rotate(&zt_filterfile.parent);
+ zlog_file_rotate(&zt_file_cmdline);
+ hook_call(zlog_rotate);
+}
+
+
+void log_show_syslog(struct vty *vty)
+{
+ int level = zlog_syslog_get_prio_min();
+
+ vty_out(vty, "Syslog logging: ");
+ if (level == ZLOG_DISABLED)
+ vty_out(vty, "disabled\n");
+ else
+ vty_out(vty, "level %s, facility %s, ident %s\n",
+ zlog_priority[level],
+ facility_name(zlog_syslog_get_facility()),
+ zlog_progname);
+}
+
+DEFUN_NOSH (show_logging,
+ show_logging_cmd,
+ "show logging",
+ SHOW_STR
+ "Show current logging configuration\n")
+{
+ int stdout_prio;
+
+ log_show_syslog(vty);
+
+ stdout_prio = stdout_journald_in_use ? zt_stdout_journald.prio_min
+ : zt_stdout_file.prio_min;
+
+ vty_out(vty, "Stdout logging: ");
+ if (stdout_prio == ZLOG_DISABLED)
+ vty_out(vty, "disabled");
+ else
+ vty_out(vty, "level %s", zlog_priority[stdout_prio]);
+ vty_out(vty, "\n");
+
+ vty_out(vty, "File logging: ");
+ if (zt_file.prio_min == ZLOG_DISABLED || !zt_file.filename)
+ vty_out(vty, "disabled");
+ else
+ vty_out(vty, "level %s, filename %s",
+ zlog_priority[zt_file.prio_min], zt_file.filename);
+ vty_out(vty, "\n");
+
+ if (zt_filterfile.parent.prio_min != ZLOG_DISABLED
+ && zt_filterfile.parent.filename)
+ vty_out(vty, "Filtered-file logging: level %s, filename %s\n",
+ zlog_priority[zt_filterfile.parent.prio_min],
+ zt_filterfile.parent.filename);
+
+ if (log_cmdline_syslog_lvl != ZLOG_DISABLED)
+ vty_out(vty,
+ "From command line: \"--log syslog --log-level %s\"\n",
+ zlog_priority[log_cmdline_syslog_lvl]);
+ if (log_cmdline_stdout_lvl != ZLOG_DISABLED)
+ vty_out(vty,
+ "From command line: \"--log stdout --log-level %s\"\n",
+ zlog_priority[log_cmdline_stdout_lvl]);
+ if (zt_file_cmdline.prio_min != ZLOG_DISABLED)
+ vty_out(vty,
+ "From command line: \"--log file:%s --log-level %s\"\n",
+ zt_file_cmdline.filename,
+ zlog_priority[zt_file_cmdline.prio_min]);
+
+ vty_out(vty, "Protocol name: %s\n", zlog_protoname);
+ vty_out(vty, "Record priority: %s\n",
+ (zt_file.record_priority ? "enabled" : "disabled"));
+ vty_out(vty, "Timestamp precision: %d\n", zt_file.ts_subsec);
+
+ hook_call(zlog_cli_show, vty);
+ return CMD_SUCCESS;
+}
+
+static void log_stdout_apply_level(void)
+{
+ int maxlvl;
+
+ maxlvl = ZLOG_MAXLVL(log_config_stdout_lvl, log_cmdline_stdout_lvl);
+
+ if (stdout_journald_in_use) {
+ zt_stdout_journald.prio_min = maxlvl;
+ zlog_5424_apply_meta(&zt_stdout_journald);
+ } else {
+ zt_stdout_file.prio_min = maxlvl;
+ zlog_file_set_other(&zt_stdout_file);
+ }
+}
+
+DEFPY (config_log_stdout,
+ config_log_stdout_cmd,
+ "log stdout [<emergencies|alerts|critical|errors|warnings|notifications|informational|debugging>$levelarg]",
+ "Logging control\n"
+ "Set stdout logging level\n"
+ LOG_LEVEL_DESC)
+{
+ int level;
+
+ if (levelarg) {
+ level = log_level_match(levelarg);
+ if (level == ZLOG_DISABLED)
+ return CMD_ERR_NO_MATCH;
+ } else
+ level = log_default_lvl;
+
+ log_config_stdout_lvl = level;
+ log_stdout_apply_level();
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_config_log_stdout,
+ no_config_log_stdout_cmd,
+ "no log stdout [<emergencies|alerts|critical|errors|warnings|notifications|informational|debugging>]",
+ NO_STR
+ "Logging control\n"
+ "Cancel logging to stdout\n"
+ LOG_LEVEL_DESC)
+{
+ log_config_stdout_lvl = ZLOG_DISABLED;
+ log_stdout_apply_level();
+ return CMD_SUCCESS;
+}
+
+DEFUN_HIDDEN (config_log_monitor,
+ config_log_monitor_cmd,
+ "log monitor [<emergencies|alerts|critical|errors|warnings|notifications|informational|debugging>]",
+ "Logging control\n"
+ "Set terminal line (monitor) logging level\n"
+ LOG_LEVEL_DESC)
+{
+ vty_out(vty, "%% \"log monitor\" is deprecated and does nothing.\n");
+ return CMD_SUCCESS;
+}
+
+DEFUN_HIDDEN (no_config_log_monitor,
+ no_config_log_monitor_cmd,
+ "no log monitor [<emergencies|alerts|critical|errors|warnings|notifications|informational|debugging>]",
+ NO_STR
+ "Logging control\n"
+ "Disable terminal line (monitor) logging\n"
+ LOG_LEVEL_DESC)
+{
+ return CMD_SUCCESS;
+}
+
+DEFPY_NOSH (debug_uid_backtrace,
+ debug_uid_backtrace_cmd,
+ "[no] debug unique-id UID backtrace",
+ NO_STR
+ DEBUG_STR
+ "Options per individual log message, by unique ID\n"
+ "Log message unique ID (XXXXX-XXXXX)\n"
+ "Add backtrace to log when message is printed\n")
+{
+ struct xrefdata search, *xrd;
+ struct xrefdata_logmsg *xrdl;
+ uint8_t flag;
+
+ strlcpy(search.uid, uid, sizeof(search.uid));
+ xrd = xrefdata_uid_find(&xrefdata_uid, &search);
+
+ if (!xrd)
+ return CMD_ERR_NOTHING_TODO;
+
+ if (xrd->xref->type != XREFT_LOGMSG) {
+ vty_out(vty, "%% ID \"%s\" is not a log message\n", uid);
+ return CMD_WARNING;
+ }
+ xrdl = container_of(xrd, struct xrefdata_logmsg, xrefdata);
+
+ flag = (vty->node == CONFIG_NODE) ? LOGMSG_FLAG_PERSISTENT
+ : LOGMSG_FLAG_EPHEMERAL;
+
+ if ((xrdl->fl_print_bt & flag) == (no ? 0 : flag))
+ return CMD_SUCCESS;
+ if (flag == LOGMSG_FLAG_PERSISTENT)
+ logmsgs_with_persist_bt += no ? -1 : 1;
+
+ xrdl->fl_print_bt ^= flag;
+ return CMD_SUCCESS;
+}
+
+static int set_log_file(struct zlog_cfg_file *target, struct vty *vty,
+ const char *fname, int loglevel)
+{
+ char path[MAXPATHLEN + 1];
+ const char *fullpath;
+ bool ok;
+
+
+ /* Path detection. */
+ if (!IS_DIRECTORY_SEP(*fname)) {
+ char cwd[MAXPATHLEN + 1];
+
+ cwd[MAXPATHLEN] = '\0';
+
+ if (getcwd(cwd, MAXPATHLEN) == NULL) {
+ flog_err_sys(EC_LIB_SYSTEM_CALL,
+ "config_log_file: Unable to alloc mem!");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ int pr = snprintf(path, sizeof(path), "%s/%s", cwd, fname);
+ if (pr < 0 || (unsigned int)pr >= sizeof(path)) {
+ flog_err_sys(
+ EC_LIB_SYSTEM_CALL,
+ "%s: Path too long ('%s/%s'); system maximum is %u",
+ __func__, cwd, fname, MAXPATHLEN);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ fullpath = path;
+ } else
+ fullpath = fname;
+
+ target->prio_min = loglevel;
+ ok = zlog_file_set_filename(target, fullpath);
+
+ if (!ok) {
+ if (vty)
+ vty_out(vty, "can't open logfile %s\n", fname);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+ return CMD_SUCCESS;
+}
+
+void command_setup_early_logging(const char *dest, const char *level)
+{
+ int nlevel;
+ char *sep;
+ int len;
+ char type[8];
+
+ if (level) {
+ nlevel = log_level_match(level);
+
+ if (nlevel == ZLOG_DISABLED) {
+ fprintf(stderr, "invalid log level \"%s\"\n", level);
+ exit(1);
+ }
+ } else
+ nlevel = log_default_lvl;
+
+ if (!dest)
+ return;
+
+ sep = strchr(dest, ':');
+ len = sep ? (int)(sep - dest) : (int)strlen(dest);
+
+ snprintfrr(type, sizeof(type), "%.*s", len, dest);
+
+ if (strcmp(type, "stdout") == 0) {
+ log_cmdline_stdout_lvl = nlevel;
+ log_stdout_apply_level();
+ return;
+ }
+ if (strcmp(type, "syslog") == 0) {
+ log_cmdline_syslog_lvl = nlevel;
+ zlog_syslog_set_prio_min(ZLOG_MAXLVL(log_config_syslog_lvl,
+ log_cmdline_syslog_lvl));
+ return;
+ }
+ if (strcmp(type, "file") == 0 && sep) {
+ sep++;
+ set_log_file(&zt_file_cmdline, NULL, sep, nlevel);
+ return;
+ }
+ if (strcmp(type, "monitor") == 0 && sep) {
+ struct zlog_live_cfg cfg = {};
+ unsigned long fd;
+ char *endp;
+
+ sep++;
+ fd = strtoul(sep, &endp, 10);
+ if (!*sep || *endp) {
+ fprintf(stderr, "invalid monitor fd \"%s\"\n", sep);
+ exit(1);
+ }
+
+ zlog_live_open_fd(&cfg, nlevel, fd);
+ zlog_live_disown(&cfg);
+ return;
+ }
+
+ fprintf(stderr, "invalid log target \"%s\" (\"%s\")\n", type, dest);
+ exit(1);
+}
+
+DEFUN (clear_log_cmdline,
+ clear_log_cmdline_cmd,
+ "clear log cmdline-targets",
+ CLEAR_STR
+ "Logging control\n"
+ "Disable log targets specified at startup by --log option\n")
+{
+ zt_file_cmdline.prio_min = ZLOG_DISABLED;
+ zlog_file_set_other(&zt_file_cmdline);
+
+ log_cmdline_syslog_lvl = ZLOG_DISABLED;
+ zlog_syslog_set_prio_min(ZLOG_MAXLVL(log_config_syslog_lvl,
+ log_cmdline_syslog_lvl));
+
+ log_cmdline_stdout_lvl = ZLOG_DISABLED;
+ log_stdout_apply_level();
+
+ return CMD_SUCCESS;
+}
+
+DEFPY (config_log_file,
+ config_log_file_cmd,
+ "log file FILENAME [<emergencies|alerts|critical|errors|warnings|notifications|informational|debugging>$levelarg]",
+ "Logging control\n"
+ "Logging to file\n"
+ "Logging filename\n"
+ LOG_LEVEL_DESC)
+{
+ int level = log_default_lvl;
+
+ if (levelarg) {
+ level = log_level_match(levelarg);
+ if (level == ZLOG_DISABLED)
+ return CMD_ERR_NO_MATCH;
+ }
+ return set_log_file(&zt_file, vty, filename, level);
+}
+
+DEFUN (no_config_log_file,
+ no_config_log_file_cmd,
+ "no log file [FILENAME [LEVEL]]",
+ NO_STR
+ "Logging control\n"
+ "Cancel logging to file\n"
+ "Logging file name\n"
+ "Logging level\n")
+{
+ zt_file.prio_min = ZLOG_DISABLED;
+ zlog_file_set_other(&zt_file);
+ return CMD_SUCCESS;
+}
+
+DEFPY (config_log_syslog,
+ config_log_syslog_cmd,
+ "log syslog [<emergencies|alerts|critical|errors|warnings|notifications|informational|debugging>$levelarg]",
+ "Logging control\n"
+ "Set syslog logging level\n"
+ LOG_LEVEL_DESC)
+{
+ int level;
+
+ if (levelarg) {
+ level = log_level_match(levelarg);
+
+ if (level == ZLOG_DISABLED)
+ return CMD_ERR_NO_MATCH;
+ } else
+ level = log_default_lvl;
+
+ log_config_syslog_lvl = level;
+ zlog_syslog_set_prio_min(ZLOG_MAXLVL(log_config_syslog_lvl,
+ log_cmdline_syslog_lvl));
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_config_log_syslog,
+ no_config_log_syslog_cmd,
+ "no log syslog [<kern|user|mail|daemon|auth|syslog|lpr|news|uucp|cron|local0|local1|local2|local3|local4|local5|local6|local7>] [<emergencies|alerts|critical|errors|warnings|notifications|informational|debugging>]",
+ NO_STR
+ "Logging control\n"
+ "Cancel logging to syslog\n"
+ LOG_FACILITY_DESC
+ LOG_LEVEL_DESC)
+{
+ log_config_syslog_lvl = ZLOG_DISABLED;
+ zlog_syslog_set_prio_min(ZLOG_MAXLVL(log_config_syslog_lvl,
+ log_cmdline_syslog_lvl));
+ return CMD_SUCCESS;
+}
+
+DEFPY (config_log_facility,
+ config_log_facility_cmd,
+ "log facility <kern|user|mail|daemon|auth|syslog|lpr|news|uucp|cron|local0|local1|local2|local3|local4|local5|local6|local7>$facilityarg",
+ "Logging control\n"
+ "Facility parameter for syslog messages\n"
+ LOG_FACILITY_DESC)
+{
+ int facility = facility_match(facilityarg);
+
+ zlog_syslog_set_facility(facility);
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_config_log_facility,
+ no_config_log_facility_cmd,
+ "no log facility [<kern|user|mail|daemon|auth|syslog|lpr|news|uucp|cron|local0|local1|local2|local3|local4|local5|local6|local7>]",
+ NO_STR
+ "Logging control\n"
+ "Reset syslog facility to default (daemon)\n"
+ LOG_FACILITY_DESC)
+{
+ zlog_syslog_set_facility(LOG_DAEMON);
+ return CMD_SUCCESS;
+}
+
+DEFUN (config_log_record_priority,
+ config_log_record_priority_cmd,
+ "log record-priority",
+ "Logging control\n"
+ "Log the priority of the message within the message\n")
+{
+ zt_file.record_priority = true;
+ zlog_file_set_other(&zt_file);
+ if (!stdout_journald_in_use) {
+ zt_stdout_file.record_priority = true;
+ zlog_file_set_other(&zt_stdout_file);
+ }
+ zt_filterfile.parent.record_priority = true;
+ zlog_file_set_other(&zt_filterfile.parent);
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_config_log_record_priority,
+ no_config_log_record_priority_cmd,
+ "no log record-priority",
+ NO_STR
+ "Logging control\n"
+ "Do not log the priority of the message within the message\n")
+{
+ zt_file.record_priority = false;
+ zlog_file_set_other(&zt_file);
+ if (!stdout_journald_in_use) {
+ zt_stdout_file.record_priority = false;
+ zlog_file_set_other(&zt_stdout_file);
+ }
+ zt_filterfile.parent.record_priority = false;
+ zlog_file_set_other(&zt_filterfile.parent);
+ return CMD_SUCCESS;
+}
+
+DEFPY (config_log_timestamp_precision,
+ config_log_timestamp_precision_cmd,
+ "log timestamp precision (0-6)",
+ "Logging control\n"
+ "Timestamp configuration\n"
+ "Set the timestamp precision\n"
+ "Number of subsecond digits\n")
+{
+ zt_file.ts_subsec = precision;
+ zlog_file_set_other(&zt_file);
+ if (!stdout_journald_in_use) {
+ zt_stdout_file.ts_subsec = precision;
+ zlog_file_set_other(&zt_stdout_file);
+ }
+ zt_filterfile.parent.ts_subsec = precision;
+ zlog_file_set_other(&zt_filterfile.parent);
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_config_log_timestamp_precision,
+ no_config_log_timestamp_precision_cmd,
+ "no log timestamp precision [(0-6)]",
+ NO_STR
+ "Logging control\n"
+ "Timestamp configuration\n"
+ "Reset the timestamp precision to the default value of 0\n"
+ "Number of subsecond digits\n")
+{
+ zt_file.ts_subsec = 0;
+ zlog_file_set_other(&zt_file);
+ if (!stdout_journald_in_use) {
+ zt_stdout_file.ts_subsec = 0;
+ zlog_file_set_other(&zt_stdout_file);
+ }
+ zt_filterfile.parent.ts_subsec = 0;
+ zlog_file_set_other(&zt_filterfile.parent);
+ return CMD_SUCCESS;
+}
+
+DEFPY (config_log_ec,
+ config_log_ec_cmd,
+ "[no] log error-category",
+ NO_STR
+ "Logging control\n"
+ "Prefix log message text with [EC 9999] code\n")
+{
+ zlog_set_prefix_ec(!no);
+ return CMD_SUCCESS;
+}
+
+DEFPY (config_log_xid,
+ config_log_xid_cmd,
+ "[no] log unique-id",
+ NO_STR
+ "Logging control\n"
+ "Prefix log message text with [XXXXX-XXXXX] identifier\n")
+{
+ zlog_set_prefix_xid(!no);
+ return CMD_SUCCESS;
+}
+
+DEFPY (config_log_filterfile,
+ config_log_filterfile_cmd,
+ "log filtered-file FILENAME [<emergencies|alerts|critical|errors|warnings|notifications|informational|debugging>$levelarg]",
+ "Logging control\n"
+ "Logging to file with string filter\n"
+ "Logging filename\n"
+ LOG_LEVEL_DESC)
+{
+ int level = log_default_lvl;
+
+ if (levelarg) {
+ level = log_level_match(levelarg);
+ if (level == ZLOG_DISABLED)
+ return CMD_ERR_NO_MATCH;
+ }
+ return set_log_file(&zt_filterfile.parent, vty, filename, level);
+}
+
+DEFUN (no_config_log_filterfile,
+ no_config_log_filterfile_cmd,
+ "no log filtered-file [FILENAME [LEVEL]]",
+ NO_STR
+ "Logging control\n"
+ "Cancel logging to file with string filter\n"
+ "Logging file name\n"
+ "Logging level\n")
+{
+ zt_filterfile.parent.prio_min = ZLOG_DISABLED;
+ zlog_file_set_other(&zt_filterfile.parent);
+ return CMD_SUCCESS;
+}
+
+DEFPY (log_filter,
+ log_filter_cmd,
+ "[no] log filter-text WORD$filter",
+ NO_STR
+ "Logging control\n"
+ FILTER_LOG_STR
+ "String to filter by\n")
+{
+ int ret = 0;
+
+ if (no)
+ ret = zlog_filter_del(filter);
+ else
+ ret = zlog_filter_add(filter);
+
+ if (ret == 1) {
+ vty_out(vty, "%% filter table full\n");
+ return CMD_WARNING;
+ } else if (ret != 0) {
+ vty_out(vty, "%% failed to %s log filter\n",
+ (no ? "remove" : "apply"));
+ return CMD_WARNING;
+ }
+
+ vty_out(vty, " %s\n", filter);
+ return CMD_SUCCESS;
+}
+
+/* Clear all log filters */
+DEFPY (log_filter_clear,
+ log_filter_clear_cmd,
+ "clear log filter-text",
+ CLEAR_STR
+ "Logging control\n"
+ FILTER_LOG_STR)
+{
+ zlog_filter_clear();
+ return CMD_SUCCESS;
+}
+
+/* Show log filter */
+DEFPY (show_log_filter,
+ show_log_filter_cmd,
+ "show logging filter-text",
+ SHOW_STR
+ "Show current logging configuration\n"
+ FILTER_LOG_STR)
+{
+ char log_filters[ZLOG_FILTERS_MAX * (ZLOG_FILTER_LENGTH_MAX + 3)] = "";
+ int len = 0;
+
+ len = zlog_filter_dump(log_filters, sizeof(log_filters));
+
+ if (len == -1) {
+ vty_out(vty, "%% failed to get filters\n");
+ return CMD_WARNING;
+ }
+
+ if (len != 0)
+ vty_out(vty, "%s", log_filters);
+
+ return CMD_SUCCESS;
+}
+
+/* Enable/disable 'immediate' mode, with no output buffering */
+DEFPY (log_immediate_mode,
+ log_immediate_mode_cmd,
+ "[no] log immediate-mode",
+ NO_STR
+ "Logging control"
+ "Output immediately, without buffering")
+{
+ zlog_set_immediate(!no);
+ return CMD_SUCCESS;
+}
+
+void log_config_write(struct vty *vty)
+{
+ bool show_cmdline_hint = false;
+
+ if (zt_file.prio_min != ZLOG_DISABLED && zt_file.filename) {
+ vty_out(vty, "log file %s", zt_file.filename);
+
+ if (zt_file.prio_min != log_default_lvl)
+ vty_out(vty, " %s", zlog_priority[zt_file.prio_min]);
+ vty_out(vty, "\n");
+ }
+
+ if (zt_filterfile.parent.prio_min != ZLOG_DISABLED
+ && zt_filterfile.parent.filename) {
+ vty_out(vty, "log filtered-file %s",
+ zt_filterfile.parent.filename);
+
+ if (zt_filterfile.parent.prio_min != log_default_lvl)
+ vty_out(vty, " %s",
+ zlog_priority[zt_filterfile.parent.prio_min]);
+ vty_out(vty, "\n");
+ }
+
+ if (log_config_stdout_lvl != ZLOG_DISABLED) {
+ vty_out(vty, "log stdout");
+
+ if (log_config_stdout_lvl != log_default_lvl)
+ vty_out(vty, " %s",
+ zlog_priority[log_config_stdout_lvl]);
+ vty_out(vty, "\n");
+ }
+
+ if (log_config_syslog_lvl != ZLOG_DISABLED) {
+ vty_out(vty, "log syslog");
+
+ if (log_config_syslog_lvl != log_default_lvl)
+ vty_out(vty, " %s",
+ zlog_priority[log_config_syslog_lvl]);
+ vty_out(vty, "\n");
+ }
+
+ if (log_cmdline_syslog_lvl != ZLOG_DISABLED) {
+ vty_out(vty,
+ "! \"log syslog %s\" enabled by \"--log\" startup option\n",
+ zlog_priority[log_cmdline_syslog_lvl]);
+ show_cmdline_hint = true;
+ }
+ if (log_cmdline_stdout_lvl != ZLOG_DISABLED) {
+ vty_out(vty,
+ "! \"log stdout %s\" enabled by \"--log\" startup option\n",
+ zlog_priority[log_cmdline_stdout_lvl]);
+ show_cmdline_hint = true;
+ }
+ if (zt_file_cmdline.prio_min != ZLOG_DISABLED) {
+ vty_out(vty,
+ "! \"log file %s %s\" enabled by \"--log\" startup option\n",
+ zt_file_cmdline.filename,
+ zlog_priority[zt_file_cmdline.prio_min]);
+ show_cmdline_hint = true;
+ }
+ if (show_cmdline_hint)
+ vty_out(vty,
+ "! use \"clear log cmdline-targets\" to remove this target\n");
+
+ if (zlog_syslog_get_facility() != LOG_DAEMON)
+ vty_out(vty, "log facility %s\n",
+ facility_name(zlog_syslog_get_facility()));
+
+ if (zt_file.record_priority == 1)
+ vty_out(vty, "log record-priority\n");
+
+ if (zt_file.ts_subsec > 0)
+ vty_out(vty, "log timestamp precision %d\n",
+ zt_file.ts_subsec);
+
+ if (!zlog_get_prefix_ec())
+ vty_out(vty, "no log error-category\n");
+ if (!zlog_get_prefix_xid())
+ vty_out(vty, "no log unique-id\n");
+
+ if (logmsgs_with_persist_bt) {
+ struct xrefdata *xrd;
+ struct xrefdata_logmsg *xrdl;
+
+ vty_out(vty, "!\n");
+
+ frr_each (xrefdata_uid, &xrefdata_uid, xrd) {
+ if (xrd->xref->type != XREFT_LOGMSG)
+ continue;
+
+ xrdl = container_of(xrd, struct xrefdata_logmsg,
+ xrefdata);
+ if (xrdl->fl_print_bt & LOGMSG_FLAG_PERSISTENT)
+ vty_out(vty, "debug unique-id %s backtrace\n",
+ xrd->uid);
+ }
+ }
+}
+
+static int log_vty_init(const char *progname, const char *protoname,
+ unsigned short instance, uid_t uid, gid_t gid)
+{
+ zlog_progname = progname;
+ zlog_protoname = protoname;
+
+ zlog_set_prefix_ec(true);
+ zlog_set_prefix_xid(true);
+
+ zlog_filterfile_init(&zt_filterfile);
+
+ if (sd_stdout_is_journal) {
+ stdout_journald_in_use = true;
+ zlog_5424_init(&zt_stdout_journald);
+ zlog_5424_apply_dst(&zt_stdout_journald);
+ } else
+ zlog_file_set_fd(&zt_stdout_file, STDOUT_FILENO);
+ return 0;
+}
+
+__attribute__((_CONSTRUCTOR(475))) static void log_vty_preinit(void)
+{
+ hook_register(zlog_init, log_vty_init);
+}
+
+void log_cmd_init(void)
+{
+ install_element(VIEW_NODE, &show_logging_cmd);
+ install_element(ENABLE_NODE, &clear_log_cmdline_cmd);
+
+ install_element(CONFIG_NODE, &config_log_stdout_cmd);
+ install_element(CONFIG_NODE, &no_config_log_stdout_cmd);
+ install_element(CONFIG_NODE, &config_log_monitor_cmd);
+ install_element(CONFIG_NODE, &no_config_log_monitor_cmd);
+ install_element(CONFIG_NODE, &config_log_file_cmd);
+ install_element(CONFIG_NODE, &no_config_log_file_cmd);
+ install_element(CONFIG_NODE, &config_log_syslog_cmd);
+ install_element(CONFIG_NODE, &no_config_log_syslog_cmd);
+ install_element(CONFIG_NODE, &config_log_facility_cmd);
+ install_element(CONFIG_NODE, &no_config_log_facility_cmd);
+ install_element(CONFIG_NODE, &config_log_record_priority_cmd);
+ install_element(CONFIG_NODE, &no_config_log_record_priority_cmd);
+ install_element(CONFIG_NODE, &config_log_timestamp_precision_cmd);
+ install_element(CONFIG_NODE, &no_config_log_timestamp_precision_cmd);
+ install_element(CONFIG_NODE, &config_log_ec_cmd);
+ install_element(CONFIG_NODE, &config_log_xid_cmd);
+
+ install_element(VIEW_NODE, &show_log_filter_cmd);
+ install_element(CONFIG_NODE, &log_filter_cmd);
+ install_element(CONFIG_NODE, &log_filter_clear_cmd);
+ install_element(CONFIG_NODE, &config_log_filterfile_cmd);
+ install_element(CONFIG_NODE, &no_config_log_filterfile_cmd);
+ install_element(CONFIG_NODE, &log_immediate_mode_cmd);
+
+ install_element(ENABLE_NODE, &debug_uid_backtrace_cmd);
+ install_element(CONFIG_NODE, &debug_uid_backtrace_cmd);
+
+ log_5424_cmd_init();
+}
diff --git a/lib/log_vty.h b/lib/log_vty.h
new file mode 100644
index 0000000..db46b3c
--- /dev/null
+++ b/lib/log_vty.h
@@ -0,0 +1,49 @@
+/*
+ * Logging - VTY library
+ * Copyright (C) 2019 Cumulus Networks, Inc.
+ * Stephen Worley
+ *
+ * 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 __LOG_VTY_H__
+#define __LOG_VTY_H__
+
+#include "lib/hook.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct vty;
+
+extern void log_cmd_init(void);
+extern void log_config_write(struct vty *vty);
+extern int log_level_match(const char *s);
+extern void log_show_syslog(struct vty *vty);
+
+extern int facility_match(const char *str);
+extern const char *facility_name(int facility);
+
+DECLARE_HOOK(zlog_rotate, (), ());
+extern void zlog_rotate(void);
+
+DECLARE_HOOK(zlog_cli_show, (struct vty * vty), (vty));
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __LOG_VTY_H__ */
diff --git a/lib/md5.c b/lib/md5.c
new file mode 100644
index 0000000..20da648
--- /dev/null
+++ b/lib/md5.c
@@ -0,0 +1,443 @@
+/*
+ * Copyright (C) 2004 6WIND
+ * <Vincent.Jardin@6WIND.com>
+ * All rights reserved.
+ *
+ * This MD5 code is Big endian and Little Endian compatible.
+ */
+
+/*
+ * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the project nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <zebra.h>
+#include "md5.h"
+
+#define SHIFT(X, s) (((X) << (s)) | ((X) >> (32 - (s))))
+
+#define F(X, Y, Z) (((X) & (Y)) | ((~X) & (Z)))
+#define G(X, Y, Z) (((X) & (Z)) | ((Y) & (~Z)))
+#define H(X, Y, Z) ((X) ^ (Y) ^ (Z))
+#define I(X, Y, Z) ((Y) ^ ((X) | (~Z)))
+
+#define ROUND1(a, b, c, d, k, s, i) \
+ { \
+ (a) = (a) + F((b), (c), (d)) + X[(k)] + T[(i)]; \
+ (a) = SHIFT((a), (s)); \
+ (a) = (b) + (a); \
+ }
+
+#define ROUND2(a, b, c, d, k, s, i) \
+ { \
+ (a) = (a) + G((b), (c), (d)) + X[(k)] + T[(i)]; \
+ (a) = SHIFT((a), (s)); \
+ (a) = (b) + (a); \
+ }
+
+#define ROUND3(a, b, c, d, k, s, i) \
+ { \
+ (a) = (a) + H((b), (c), (d)) + X[(k)] + T[(i)]; \
+ (a) = SHIFT((a), (s)); \
+ (a) = (b) + (a); \
+ }
+
+#define ROUND4(a, b, c, d, k, s, i) \
+ { \
+ (a) = (a) + I((b), (c), (d)) + X[(k)] + T[(i)]; \
+ (a) = SHIFT((a), (s)); \
+ (a) = (b) + (a); \
+ }
+
+#define Sa 7
+#define Sb 12
+#define Sc 17
+#define Sd 22
+
+#define Se 5
+#define Sf 9
+#define Sg 14
+#define Sh 20
+
+#define Si 4
+#define Sj 11
+#define Sk 16
+#define Sl 23
+
+#define Sm 6
+#define Sn 10
+#define So 15
+#define Sp 21
+
+#define MD5_A0 0x67452301
+#define MD5_B0 0xefcdab89
+#define MD5_C0 0x98badcfe
+#define MD5_D0 0x10325476
+
+/* Integer part of 4294967296 times abs(sin(i)), where i is in radians. */
+static const uint32_t T[65] = {
+ 0, 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf,
+ 0x4787c62a, 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1,
+ 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
+
+ 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x2441453,
+ 0xd8a1e681, 0xe7d3fbc8, 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
+ 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
+
+ 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9,
+ 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05,
+ 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
+
+ 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92,
+ 0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
+ 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391,
+};
+
+static const uint8_t md5_paddat[MD5_BUFLEN] = {
+ 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static void md5_calc(const uint8_t *, md5_ctxt *);
+
+void md5_init(md5_ctxt *ctxt)
+{
+ ctxt->md5_n = 0;
+ ctxt->md5_i = 0;
+ ctxt->md5_sta = MD5_A0;
+ ctxt->md5_stb = MD5_B0;
+ ctxt->md5_stc = MD5_C0;
+ ctxt->md5_std = MD5_D0;
+ memset(ctxt->md5_buf, 0, sizeof(ctxt->md5_buf));
+}
+
+void md5_loop(md5_ctxt *ctxt, const void *vinput, uint len)
+{
+ uint gap, i;
+ const uint8_t *input = vinput;
+
+ ctxt->md5_n += len * 8; /* byte to bit */
+ gap = MD5_BUFLEN - ctxt->md5_i;
+
+ if (len >= gap) {
+ memcpy(ctxt->md5_buf + ctxt->md5_i, input, gap);
+ md5_calc(ctxt->md5_buf, ctxt);
+
+ for (i = gap; i + MD5_BUFLEN <= len; i += MD5_BUFLEN) {
+ md5_calc((input + i), ctxt);
+ }
+
+ ctxt->md5_i = len - i;
+ memcpy(ctxt->md5_buf, (input + i), ctxt->md5_i);
+ } else {
+ memcpy(ctxt->md5_buf + ctxt->md5_i, input, len);
+ ctxt->md5_i += len;
+ }
+}
+
+void md5_pad(md5_ctxt *ctxt)
+{
+ uint gap;
+
+ /* Don't count up padding. Keep md5_n. */
+ gap = MD5_BUFLEN - ctxt->md5_i;
+ if (gap > 8) {
+ memcpy(ctxt->md5_buf + ctxt->md5_i, md5_paddat,
+ gap - sizeof(ctxt->md5_n));
+ } else {
+ /* including gap == 8 */
+ memcpy(ctxt->md5_buf + ctxt->md5_i, md5_paddat, gap);
+ md5_calc(ctxt->md5_buf, ctxt);
+ memcpy(ctxt->md5_buf, md5_paddat + gap,
+ MD5_BUFLEN - sizeof(ctxt->md5_n));
+ }
+
+ /* 8 byte word */
+ if (BYTE_ORDER == LITTLE_ENDIAN)
+ memcpy(&ctxt->md5_buf[56], &ctxt->md5_n8[0], 8);
+ else {
+ ctxt->md5_buf[56] = ctxt->md5_n8[7];
+ ctxt->md5_buf[57] = ctxt->md5_n8[6];
+ ctxt->md5_buf[58] = ctxt->md5_n8[5];
+ ctxt->md5_buf[59] = ctxt->md5_n8[4];
+ ctxt->md5_buf[60] = ctxt->md5_n8[3];
+ ctxt->md5_buf[61] = ctxt->md5_n8[2];
+ ctxt->md5_buf[62] = ctxt->md5_n8[1];
+ ctxt->md5_buf[63] = ctxt->md5_n8[0];
+ }
+ md5_calc(ctxt->md5_buf, ctxt);
+}
+
+void md5_result(uint8_t *digest, md5_ctxt *ctxt)
+{
+ /* 4 byte words */
+ if (BYTE_ORDER == LITTLE_ENDIAN)
+ memcpy(digest, &ctxt->md5_st8[0], 16);
+ else if (BYTE_ORDER == BIG_ENDIAN) {
+ digest[0] = ctxt->md5_st8[3];
+ digest[1] = ctxt->md5_st8[2];
+ digest[2] = ctxt->md5_st8[1];
+ digest[3] = ctxt->md5_st8[0];
+ digest[4] = ctxt->md5_st8[7];
+ digest[5] = ctxt->md5_st8[6];
+ digest[6] = ctxt->md5_st8[5];
+ digest[7] = ctxt->md5_st8[4];
+ digest[8] = ctxt->md5_st8[11];
+ digest[9] = ctxt->md5_st8[10];
+ digest[10] = ctxt->md5_st8[9];
+ digest[11] = ctxt->md5_st8[8];
+ digest[12] = ctxt->md5_st8[15];
+ digest[13] = ctxt->md5_st8[14];
+ digest[14] = ctxt->md5_st8[13];
+ digest[15] = ctxt->md5_st8[12];
+ }
+}
+
+static void md5_calc(const uint8_t *b64, md5_ctxt *ctxt)
+{
+ uint32_t A = ctxt->md5_sta;
+ uint32_t B = ctxt->md5_stb;
+ uint32_t C = ctxt->md5_stc;
+ uint32_t D = ctxt->md5_std;
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+ const uint32_t *X = (const uint32_t *)b64;
+#elif (BYTE_ORDER == BIG_ENDIAN)
+ uint32_t X[16];
+
+ if (BYTE_ORDER == BIG_ENDIAN) {
+ /* 4 byte words */
+ /* what a brute force but fast! */
+ uint8_t *y = (uint8_t *)X;
+ y[0] = b64[3];
+ y[1] = b64[2];
+ y[2] = b64[1];
+ y[3] = b64[0];
+ y[4] = b64[7];
+ y[5] = b64[6];
+ y[6] = b64[5];
+ y[7] = b64[4];
+ y[8] = b64[11];
+ y[9] = b64[10];
+ y[10] = b64[9];
+ y[11] = b64[8];
+ y[12] = b64[15];
+ y[13] = b64[14];
+ y[14] = b64[13];
+ y[15] = b64[12];
+ y[16] = b64[19];
+ y[17] = b64[18];
+ y[18] = b64[17];
+ y[19] = b64[16];
+ y[20] = b64[23];
+ y[21] = b64[22];
+ y[22] = b64[21];
+ y[23] = b64[20];
+ y[24] = b64[27];
+ y[25] = b64[26];
+ y[26] = b64[25];
+ y[27] = b64[24];
+ y[28] = b64[31];
+ y[29] = b64[30];
+ y[30] = b64[29];
+ y[31] = b64[28];
+ y[32] = b64[35];
+ y[33] = b64[34];
+ y[34] = b64[33];
+ y[35] = b64[32];
+ y[36] = b64[39];
+ y[37] = b64[38];
+ y[38] = b64[37];
+ y[39] = b64[36];
+ y[40] = b64[43];
+ y[41] = b64[42];
+ y[42] = b64[41];
+ y[43] = b64[40];
+ y[44] = b64[47];
+ y[45] = b64[46];
+ y[46] = b64[45];
+ y[47] = b64[44];
+ y[48] = b64[51];
+ y[49] = b64[50];
+ y[50] = b64[49];
+ y[51] = b64[48];
+ y[52] = b64[55];
+ y[53] = b64[54];
+ y[54] = b64[53];
+ y[55] = b64[52];
+ y[56] = b64[59];
+ y[57] = b64[58];
+ y[58] = b64[57];
+ y[59] = b64[56];
+ y[60] = b64[63];
+ y[61] = b64[62];
+ y[62] = b64[61];
+ y[63] = b64[60];
+ }
+#endif
+
+ ROUND1(A, B, C, D, 0, Sa, 1);
+ ROUND1(D, A, B, C, 1, Sb, 2);
+ ROUND1(C, D, A, B, 2, Sc, 3);
+ ROUND1(B, C, D, A, 3, Sd, 4);
+ ROUND1(A, B, C, D, 4, Sa, 5);
+ ROUND1(D, A, B, C, 5, Sb, 6);
+ ROUND1(C, D, A, B, 6, Sc, 7);
+ ROUND1(B, C, D, A, 7, Sd, 8);
+ ROUND1(A, B, C, D, 8, Sa, 9);
+ ROUND1(D, A, B, C, 9, Sb, 10);
+ ROUND1(C, D, A, B, 10, Sc, 11);
+ ROUND1(B, C, D, A, 11, Sd, 12);
+ ROUND1(A, B, C, D, 12, Sa, 13);
+ ROUND1(D, A, B, C, 13, Sb, 14);
+ ROUND1(C, D, A, B, 14, Sc, 15);
+ ROUND1(B, C, D, A, 15, Sd, 16);
+
+ ROUND2(A, B, C, D, 1, Se, 17);
+ ROUND2(D, A, B, C, 6, Sf, 18);
+ ROUND2(C, D, A, B, 11, Sg, 19);
+ ROUND2(B, C, D, A, 0, Sh, 20);
+ ROUND2(A, B, C, D, 5, Se, 21);
+ ROUND2(D, A, B, C, 10, Sf, 22);
+ ROUND2(C, D, A, B, 15, Sg, 23);
+ ROUND2(B, C, D, A, 4, Sh, 24);
+ ROUND2(A, B, C, D, 9, Se, 25);
+ ROUND2(D, A, B, C, 14, Sf, 26);
+ ROUND2(C, D, A, B, 3, Sg, 27);
+ ROUND2(B, C, D, A, 8, Sh, 28);
+ ROUND2(A, B, C, D, 13, Se, 29);
+ ROUND2(D, A, B, C, 2, Sf, 30);
+ ROUND2(C, D, A, B, 7, Sg, 31);
+ ROUND2(B, C, D, A, 12, Sh, 32);
+
+ ROUND3(A, B, C, D, 5, Si, 33);
+ ROUND3(D, A, B, C, 8, Sj, 34);
+ ROUND3(C, D, A, B, 11, Sk, 35);
+ ROUND3(B, C, D, A, 14, Sl, 36);
+ ROUND3(A, B, C, D, 1, Si, 37);
+ ROUND3(D, A, B, C, 4, Sj, 38);
+ ROUND3(C, D, A, B, 7, Sk, 39);
+ ROUND3(B, C, D, A, 10, Sl, 40);
+ ROUND3(A, B, C, D, 13, Si, 41);
+ ROUND3(D, A, B, C, 0, Sj, 42);
+ ROUND3(C, D, A, B, 3, Sk, 43);
+ ROUND3(B, C, D, A, 6, Sl, 44);
+ ROUND3(A, B, C, D, 9, Si, 45);
+ ROUND3(D, A, B, C, 12, Sj, 46);
+ ROUND3(C, D, A, B, 15, Sk, 47);
+ ROUND3(B, C, D, A, 2, Sl, 48);
+
+ ROUND4(A, B, C, D, 0, Sm, 49);
+ ROUND4(D, A, B, C, 7, Sn, 50);
+ ROUND4(C, D, A, B, 14, So, 51);
+ ROUND4(B, C, D, A, 5, Sp, 52);
+ ROUND4(A, B, C, D, 12, Sm, 53);
+ ROUND4(D, A, B, C, 3, Sn, 54);
+ ROUND4(C, D, A, B, 10, So, 55);
+ ROUND4(B, C, D, A, 1, Sp, 56);
+ ROUND4(A, B, C, D, 8, Sm, 57);
+ ROUND4(D, A, B, C, 15, Sn, 58);
+ ROUND4(C, D, A, B, 6, So, 59);
+ ROUND4(B, C, D, A, 13, Sp, 60);
+ ROUND4(A, B, C, D, 4, Sm, 61);
+ ROUND4(D, A, B, C, 11, Sn, 62);
+ ROUND4(C, D, A, B, 2, So, 63);
+ ROUND4(B, C, D, A, 9, Sp, 64);
+
+ ctxt->md5_sta += A;
+ ctxt->md5_stb += B;
+ ctxt->md5_stc += C;
+ ctxt->md5_std += D;
+}
+
+/* From RFC 2104 */
+void hmac_md5(unsigned char *text, int text_len, unsigned char *key,
+ int key_len, uint8_t *digest)
+{
+ MD5_CTX context;
+ unsigned char k_ipad[65]; /* inner padding -
+ * key XORd with ipad
+ */
+ unsigned char k_opad[65]; /* outer padding -
+ * key XORd with opad
+ */
+ unsigned char tk[16];
+ int i;
+ /* if key is longer than 64 bytes reset it to key=MD5(key) */
+ if (key_len > 64) {
+
+ MD5_CTX tctx;
+
+ MD5Init(&tctx);
+ MD5Update(&tctx, key, key_len);
+ MD5Final(tk, &tctx);
+
+ key = tk;
+ key_len = 16;
+ }
+
+ /*
+ * the HMAC_MD5 transform looks like:
+ *
+ * MD5(K XOR opad, MD5(K XOR ipad, text))
+ *
+ * where K is an n byte key
+ * ipad is the byte 0x36 repeated 64 times
+ * opad is the byte 0x5c repeated 64 times
+ * and text is the data being protected
+ */
+
+ /* start out by storing key in pads */
+ bzero(k_ipad, sizeof(k_ipad));
+ bzero(k_opad, sizeof(k_opad));
+ bcopy(key, k_ipad, key_len);
+ bcopy(key, k_opad, key_len);
+
+ /* XOR key with ipad and opad values */
+ for (i = 0; i < 64; i++) {
+ k_ipad[i] ^= 0x36;
+ k_opad[i] ^= 0x5c;
+ }
+ /*
+ * perform inner MD5
+ */
+ MD5Init(&context); /* init context for 1st
+ * pass */
+ MD5Update(&context, k_ipad, 64); /* start with inner pad */
+ MD5Update(&context, text, text_len); /* then text of datagram */
+ MD5Final(digest, &context); /* finish up 1st pass */
+ /*
+ * perform outer MD5
+ */
+ MD5Init(&context); /* init context for 2nd
+ * pass */
+ MD5Update(&context, k_opad, 64); /* start with outer pad */
+ MD5Update(&context, digest, 16); /* then results of 1st
+ * hash */
+ MD5Final(digest, &context); /* finish up 2nd pass */
+ explicit_bzero(&context, sizeof(context));
+}
diff --git a/lib/md5.h b/lib/md5.h
new file mode 100644
index 0000000..691a294
--- /dev/null
+++ b/lib/md5.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2004 6WIND
+ * <Vincent.Jardin@6WIND.com>
+ * All rights reserved.
+ *
+ * This MD5 code is Big endian and Little Endian compatible.
+ */
+
+/*
+ * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the project nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _LIBZEBRA_MD5_H_
+#define _LIBZEBRA_MD5_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define MD5_BUFLEN 64
+
+typedef struct {
+ union {
+ uint32_t md5_state32[4];
+ uint8_t md5_state8[16];
+ } md5_st;
+
+#define md5_sta md5_st.md5_state32[0]
+#define md5_stb md5_st.md5_state32[1]
+#define md5_stc md5_st.md5_state32[2]
+#define md5_std md5_st.md5_state32[3]
+#define md5_st8 md5_st.md5_state8
+
+ union {
+ uint64_t md5_count64;
+ uint8_t md5_count8[8];
+ } md5_count;
+#define md5_n md5_count.md5_count64
+#define md5_n8 md5_count.md5_count8
+
+ uint md5_i;
+ uint8_t md5_buf[MD5_BUFLEN];
+} md5_ctxt;
+
+extern void md5_init(md5_ctxt *);
+extern void md5_loop(md5_ctxt *, const void *, unsigned int);
+extern void md5_pad(md5_ctxt *);
+extern void md5_result(uint8_t *, md5_ctxt *);
+
+/* compatibility */
+#define MD5_CTX md5_ctxt
+#define MD5Init(x) md5_init((x))
+#define MD5Update(x, y, z) md5_loop((x), (y), (z))
+#define MD5Final(x, y) \
+ do { \
+ md5_pad((y)); \
+ md5_result((x), (y)); \
+ } while (0)
+
+/* From RFC 2104 */
+void hmac_md5(unsigned char *text, int text_len, unsigned char *key,
+ int key_len, uint8_t *digest);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ! _LIBZEBRA_MD5_H_*/
diff --git a/lib/memory.c b/lib/memory.c
new file mode 100644
index 0000000..1881177
--- /dev/null
+++ b/lib/memory.c
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2015-16 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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 <stdlib.h>
+#ifdef HAVE_MALLOC_H
+#include <malloc.h>
+#endif
+#ifdef HAVE_MALLOC_NP_H
+#include <malloc_np.h>
+#endif
+#ifdef HAVE_MALLOC_MALLOC_H
+#include <malloc/malloc.h>
+#endif
+
+#include "memory.h"
+#include "log.h"
+#include "libfrr_trace.h"
+
+static struct memgroup *mg_first = NULL;
+struct memgroup **mg_insert = &mg_first;
+
+DEFINE_MGROUP(LIB, "libfrr");
+DEFINE_MTYPE(LIB, TMP, "Temporary memory");
+DEFINE_MTYPE(LIB, BITFIELD, "Bitfield memory");
+
+static inline void mt_count_alloc(struct memtype *mt, size_t size, void *ptr)
+{
+ size_t current;
+ size_t oldsize;
+
+ current = 1 + atomic_fetch_add_explicit(&mt->n_alloc, 1,
+ memory_order_relaxed);
+
+ oldsize = atomic_load_explicit(&mt->n_max, memory_order_relaxed);
+ if (current > oldsize)
+ /* note that this may fail, but approximation is sufficient */
+ atomic_compare_exchange_weak_explicit(&mt->n_max, &oldsize,
+ current,
+ memory_order_relaxed,
+ memory_order_relaxed);
+
+ oldsize = atomic_load_explicit(&mt->size, memory_order_relaxed);
+ if (oldsize == 0)
+ oldsize = atomic_exchange_explicit(&mt->size, size,
+ memory_order_relaxed);
+ if (oldsize != 0 && oldsize != size && oldsize != SIZE_VAR)
+ atomic_store_explicit(&mt->size, SIZE_VAR,
+ memory_order_relaxed);
+
+#ifdef HAVE_MALLOC_USABLE_SIZE
+ size_t mallocsz = malloc_usable_size(ptr);
+
+ current = mallocsz + atomic_fetch_add_explicit(&mt->total, mallocsz,
+ memory_order_relaxed);
+ oldsize = atomic_load_explicit(&mt->max_size, memory_order_relaxed);
+ if (current > oldsize)
+ /* note that this may fail, but approximation is sufficient */
+ atomic_compare_exchange_weak_explicit(&mt->max_size, &oldsize,
+ current,
+ memory_order_relaxed,
+ memory_order_relaxed);
+#endif
+}
+
+static inline void mt_count_free(struct memtype *mt, void *ptr)
+{
+ frrtrace(2, frr_libfrr, memfree, mt, ptr);
+
+ assert(mt->n_alloc);
+ atomic_fetch_sub_explicit(&mt->n_alloc, 1, memory_order_relaxed);
+
+#ifdef HAVE_MALLOC_USABLE_SIZE
+ size_t mallocsz = malloc_usable_size(ptr);
+
+ atomic_fetch_sub_explicit(&mt->total, mallocsz, memory_order_relaxed);
+#endif
+}
+
+static inline void *mt_checkalloc(struct memtype *mt, void *ptr, size_t size)
+{
+ frrtrace(3, frr_libfrr, memalloc, mt, ptr, size);
+
+ if (__builtin_expect(ptr == NULL, 0)) {
+ if (size) {
+ /* malloc(0) is allowed to return NULL */
+ memory_oom(size, mt->name);
+ }
+ return NULL;
+ }
+ mt_count_alloc(mt, size, ptr);
+ return ptr;
+}
+
+void *qmalloc(struct memtype *mt, size_t size)
+{
+ return mt_checkalloc(mt, malloc(size), size);
+}
+
+void *qcalloc(struct memtype *mt, size_t size)
+{
+ return mt_checkalloc(mt, calloc(size, 1), size);
+}
+
+void *qrealloc(struct memtype *mt, void *ptr, size_t size)
+{
+ if (ptr)
+ mt_count_free(mt, ptr);
+ return mt_checkalloc(mt, ptr ? realloc(ptr, size) : malloc(size), size);
+}
+
+void *qstrdup(struct memtype *mt, const char *str)
+{
+ return str ? mt_checkalloc(mt, strdup(str), strlen(str) + 1) : NULL;
+}
+
+void qcountfree(struct memtype *mt, void *ptr)
+{
+ if (ptr)
+ mt_count_free(mt, ptr);
+}
+
+void qfree(struct memtype *mt, void *ptr)
+{
+ if (ptr)
+ mt_count_free(mt, ptr);
+ free(ptr);
+}
+
+int qmem_walk(qmem_walk_fn *func, void *arg)
+{
+ struct memgroup *mg;
+ struct memtype *mt;
+ int rv;
+
+ for (mg = mg_first; mg; mg = mg->next) {
+ if ((rv = func(arg, mg, NULL)))
+ return rv;
+ for (mt = mg->types; mt; mt = mt->next)
+ if ((rv = func(arg, mg, mt)))
+ return rv;
+ }
+ return 0;
+}
+
+struct exit_dump_args {
+ FILE *fp;
+ const char *prefix;
+ int error;
+};
+
+static int qmem_exit_walker(void *arg, struct memgroup *mg, struct memtype *mt)
+{
+ struct exit_dump_args *eda = arg;
+
+ if (!mt) {
+ fprintf(eda->fp,
+ "%s: showing active allocations in memory group %s\n",
+ eda->prefix, mg->name);
+
+ } else if (mt->n_alloc) {
+ char size[32];
+ if (!mg->active_at_exit)
+ eda->error++;
+ snprintf(size, sizeof(size), "%10zu", mt->size);
+ fprintf(eda->fp, "%s: memstats: %-30s: %6zu * %s\n",
+ eda->prefix, mt->name, mt->n_alloc,
+ mt->size == SIZE_VAR ? "(variably sized)" : size);
+ }
+ return 0;
+}
+
+int log_memstats(FILE *fp, const char *prefix)
+{
+ struct exit_dump_args eda = {.fp = fp, .prefix = prefix, .error = 0};
+ qmem_walk(qmem_exit_walker, &eda);
+ return eda.error;
+}
diff --git a/lib/memory.h b/lib/memory.h
new file mode 100644
index 0000000..c95602f
--- /dev/null
+++ b/lib/memory.h
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 2015-16 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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 _QUAGGA_MEMORY_H
+#define _QUAGGA_MEMORY_H
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <frratomic.h>
+#include "compiler.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if defined(HAVE_MALLOC_SIZE) && !defined(HAVE_MALLOC_USABLE_SIZE)
+#define malloc_usable_size(x) malloc_size(x)
+#define HAVE_MALLOC_USABLE_SIZE
+#endif
+
+#define SIZE_VAR ~0UL
+struct memtype {
+ struct memtype *next, **ref;
+ const char *name;
+ atomic_size_t n_alloc;
+ atomic_size_t n_max;
+ atomic_size_t size;
+#ifdef HAVE_MALLOC_USABLE_SIZE
+ atomic_size_t total;
+ atomic_size_t max_size;
+#endif
+};
+
+struct memgroup {
+ struct memgroup *next, **ref;
+ struct memtype *types, **insert;
+ const char *name;
+ /* ignore group on dumping memleaks at exit */
+ bool active_at_exit;
+};
+
+/* macro usage:
+ *
+ * mydaemon.h
+ * DECLARE_MGROUP(MYDAEMON);
+ * DECLARE_MTYPE(MYDAEMON_COMMON);
+ *
+ * mydaemon.c
+ * DEFINE_MGROUP(MYDAEMON, "my daemon memory");
+ * DEFINE_MTYPE(MYDAEMON, MYDAEMON_COMMON,
+ * "this mtype is used in multiple files in mydaemon");
+ * foo = qmalloc(MTYPE_MYDAEMON_COMMON, sizeof(*foo))
+ *
+ * mydaemon_io.c
+ * bar = qmalloc(MTYPE_MYDAEMON_COMMON, sizeof(*bar))
+ *
+ * DEFINE_MTYPE_STATIC(MYDAEMON, MYDAEMON_IO,
+ * "this mtype is used only in this file");
+ * baz = qmalloc(MTYPE_MYDAEMON_IO, sizeof(*baz))
+ *
+ * Note: Naming conventions (MGROUP_ and MTYPE_ prefixes are enforced
+ * by not having these as part of the macro arguments)
+ * Note: MTYPE_* are symbols to the compiler (of type struct memtype *),
+ * but MGROUP_* aren't.
+ */
+
+#define DECLARE_MGROUP(name) extern struct memgroup _mg_##name
+#define _DEFINE_MGROUP(mname, desc, ...) \
+ struct memgroup _mg_##mname \
+ __attribute__((section(".data.mgroups"))) = { \
+ .name = desc, \
+ .types = NULL, \
+ .next = NULL, \
+ .insert = NULL, \
+ .ref = NULL, \
+ __VA_ARGS__ \
+ }; \
+ static void _mginit_##mname(void) __attribute__((_CONSTRUCTOR(1000))); \
+ static void _mginit_##mname(void) \
+ { \
+ extern struct memgroup **mg_insert; \
+ _mg_##mname.ref = mg_insert; \
+ *mg_insert = &_mg_##mname; \
+ mg_insert = &_mg_##mname.next; \
+ } \
+ static void _mgfini_##mname(void) __attribute__((_DESTRUCTOR(1000))); \
+ static void _mgfini_##mname(void) \
+ { \
+ if (_mg_##mname.next) \
+ _mg_##mname.next->ref = _mg_##mname.ref; \
+ *_mg_##mname.ref = _mg_##mname.next; \
+ } \
+ MACRO_REQUIRE_SEMICOLON() /* end */
+
+#define DEFINE_MGROUP(mname, desc) \
+ _DEFINE_MGROUP(mname, desc, )
+#define DEFINE_MGROUP_ACTIVEATEXIT(mname, desc) \
+ _DEFINE_MGROUP(mname, desc, .active_at_exit = true)
+
+#define DECLARE_MTYPE(name) \
+ extern struct memtype MTYPE_##name[1] \
+ /* end */
+
+#define DEFINE_MTYPE_ATTR(group, mname, attr, desc) \
+ attr struct memtype MTYPE_##mname[1] \
+ __attribute__((section(".data.mtypes"))) = { { \
+ .name = desc, \
+ .next = NULL, \
+ .n_alloc = 0, \
+ .size = 0, \
+ .ref = NULL, \
+ } }; \
+ static void _mtinit_##mname(void) __attribute__((_CONSTRUCTOR(1001))); \
+ static void _mtinit_##mname(void) \
+ { \
+ if (_mg_##group.insert == NULL) \
+ _mg_##group.insert = &_mg_##group.types; \
+ MTYPE_##mname->ref = _mg_##group.insert; \
+ *_mg_##group.insert = MTYPE_##mname; \
+ _mg_##group.insert = &MTYPE_##mname->next; \
+ } \
+ static void _mtfini_##mname(void) __attribute__((_DESTRUCTOR(1001))); \
+ static void _mtfini_##mname(void) \
+ { \
+ if (MTYPE_##mname->next) \
+ MTYPE_##mname->next->ref = MTYPE_##mname->ref; \
+ *MTYPE_##mname->ref = MTYPE_##mname->next; \
+ } \
+ MACRO_REQUIRE_SEMICOLON() /* end */
+
+#define DEFINE_MTYPE(group, name, desc) \
+ DEFINE_MTYPE_ATTR(group, name, , desc) \
+ /* end */
+
+#define DEFINE_MTYPE_STATIC(group, name, desc) \
+ DEFINE_MTYPE_ATTR(group, name, static, desc) \
+ /* end */
+
+DECLARE_MGROUP(LIB);
+DECLARE_MTYPE(TMP);
+
+
+extern void *qmalloc(struct memtype *mt, size_t size)
+ __attribute__((malloc, _ALLOC_SIZE(2), nonnull(1) _RET_NONNULL));
+extern void *qcalloc(struct memtype *mt, size_t size)
+ __attribute__((malloc, _ALLOC_SIZE(2), nonnull(1) _RET_NONNULL));
+extern void *qrealloc(struct memtype *mt, void *ptr, size_t size)
+ __attribute__((_ALLOC_SIZE(3), nonnull(1) _RET_NONNULL));
+extern void *qstrdup(struct memtype *mt, const char *str)
+ __attribute__((malloc, nonnull(1) _RET_NONNULL));
+extern void qcountfree(struct memtype *mt, void *ptr)
+ __attribute__((nonnull(1)));
+extern void qfree(struct memtype *mt, void *ptr) __attribute__((nonnull(1)));
+
+#define XMALLOC(mtype, size) qmalloc(mtype, size)
+#define XCALLOC(mtype, size) qcalloc(mtype, size)
+#define XREALLOC(mtype, ptr, size) qrealloc(mtype, ptr, size)
+#define XSTRDUP(mtype, str) qstrdup(mtype, str)
+#define XCOUNTFREE(mtype, ptr) qcountfree(mtype, ptr)
+#define XFREE(mtype, ptr) \
+ do { \
+ qfree(mtype, ptr); \
+ ptr = NULL; \
+ } while (0)
+
+static inline size_t mtype_stats_alloc(struct memtype *mt)
+{
+ return mt->n_alloc;
+}
+
+/* NB: calls are ordered by memgroup; and there is a call with mt == NULL for
+ * each memgroup (so that a header can be printed, and empty memgroups show)
+ *
+ * return value: 0: continue, !0: abort walk. qmem_walk will return the
+ * last value from qmem_walk_fn. */
+typedef int qmem_walk_fn(void *arg, struct memgroup *mg, struct memtype *mt);
+extern int qmem_walk(qmem_walk_fn *func, void *arg);
+extern int log_memstats(FILE *fp, const char *);
+#define log_memstats_stderr(prefix) log_memstats(stderr, prefix)
+
+extern __attribute__((__noreturn__)) void memory_oom(size_t size,
+ const char *name);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QUAGGA_MEMORY_H */
diff --git a/lib/mlag.c b/lib/mlag.c
new file mode 100644
index 0000000..653fbe8
--- /dev/null
+++ b/lib/mlag.c
@@ -0,0 +1,186 @@
+/* mlag generic code.
+ * Copyright (C) 2018 Cumulus Networks, Inc.
+ * Donald Sharp
+ *
+ * This file is part of FRR.
+ *
+ * FRR 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, or (at your option) any
+ * later version.
+ *
+ * FRR 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 FRR; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+#include <zebra.h>
+
+#include <mlag.h>
+
+char *mlag_role2str(enum mlag_role role, char *buf, size_t size)
+{
+ switch (role) {
+ case MLAG_ROLE_NONE:
+ snprintf(buf, size, "NONE");
+ break;
+ case MLAG_ROLE_PRIMARY:
+ snprintf(buf, size, "PRIMARY");
+ break;
+ case MLAG_ROLE_SECONDARY:
+ snprintf(buf, size, "SECONDARY");
+ break;
+ }
+
+ return buf;
+}
+
+char *mlag_lib_msgid_to_str(enum mlag_msg_type msg_type, char *buf, size_t size)
+{
+ switch (msg_type) {
+ case MLAG_REGISTER:
+ snprintf(buf, size, "Register");
+ break;
+ case MLAG_DEREGISTER:
+ snprintf(buf, size, "De-Register");
+ break;
+ case MLAG_MROUTE_ADD:
+ snprintf(buf, size, "Mroute add");
+ break;
+ case MLAG_MROUTE_DEL:
+ snprintf(buf, size, "Mroute del");
+ break;
+ case MLAG_DUMP:
+ snprintf(buf, size, "Mlag Replay");
+ break;
+ case MLAG_MROUTE_ADD_BULK:
+ snprintf(buf, size, "Mroute Add Batch");
+ break;
+ case MLAG_MROUTE_DEL_BULK:
+ snprintf(buf, size, "Mroute Del Batch");
+ break;
+ case MLAG_STATUS_UPDATE:
+ snprintf(buf, size, "Mlag Status");
+ break;
+ case MLAG_VXLAN_UPDATE:
+ snprintf(buf, size, "Mlag vxlan update");
+ break;
+ case MLAG_PEER_FRR_STATUS:
+ snprintf(buf, size, "Mlag Peer FRR Status");
+ break;
+ default:
+ snprintf(buf, size, "Unknown %d", msg_type);
+ break;
+ }
+ return buf;
+}
+
+
+int mlag_lib_decode_mlag_hdr(struct stream *s, struct mlag_msg *msg,
+ size_t *length)
+{
+#define LIB_MLAG_HDR_LENGTH 8
+ if (s == NULL || msg == NULL)
+ return -1;
+
+ *length = stream_get_endp(s);
+
+ if (*length < LIB_MLAG_HDR_LENGTH)
+ return -1;
+
+ *length -= LIB_MLAG_HDR_LENGTH;
+
+ STREAM_GETL(s, msg->msg_type);
+ STREAM_GETW(s, msg->data_len);
+ STREAM_GETW(s, msg->msg_cnt);
+
+ return 0;
+stream_failure:
+ return -1;
+}
+
+#define MLAG_MROUTE_ADD_LENGTH \
+ (VRF_NAMSIZ + INTERFACE_NAMSIZ + 4 + 4 + 4 + 4 + 1 + 1 + 4)
+
+int mlag_lib_decode_mroute_add(struct stream *s, struct mlag_mroute_add *msg,
+ size_t *length)
+{
+ if (s == NULL || msg == NULL || *length < MLAG_MROUTE_ADD_LENGTH)
+ return -1;
+
+ STREAM_GET(msg->vrf_name, s, VRF_NAMSIZ);
+ STREAM_GETL(s, msg->source_ip);
+ STREAM_GETL(s, msg->group_ip);
+ STREAM_GETL(s, msg->cost_to_rp);
+ STREAM_GETL(s, msg->owner_id);
+ STREAM_GETC(s, msg->am_i_dr);
+ STREAM_GETC(s, msg->am_i_dual_active);
+ STREAM_GETL(s, msg->vrf_id);
+ STREAM_GET(msg->intf_name, s, INTERFACE_NAMSIZ);
+
+ return 0;
+stream_failure:
+ return -1;
+}
+
+#define MLAG_MROUTE_DEL_LENGTH (VRF_NAMSIZ + INTERFACE_NAMSIZ + 4 + 4 + 4 + 4)
+
+int mlag_lib_decode_mroute_del(struct stream *s, struct mlag_mroute_del *msg,
+ size_t *length)
+{
+ if (s == NULL || msg == NULL || *length < MLAG_MROUTE_DEL_LENGTH)
+ return -1;
+
+ STREAM_GET(msg->vrf_name, s, VRF_NAMSIZ);
+ STREAM_GETL(s, msg->source_ip);
+ STREAM_GETL(s, msg->group_ip);
+ STREAM_GETL(s, msg->owner_id);
+ STREAM_GETL(s, msg->vrf_id);
+ STREAM_GET(msg->intf_name, s, INTERFACE_NAMSIZ);
+
+ return 0;
+stream_failure:
+ return -1;
+}
+
+int mlag_lib_decode_mlag_status(struct stream *s, struct mlag_status *msg)
+{
+ if (s == NULL || msg == NULL)
+ return -1;
+
+ STREAM_GET(msg->peerlink_rif, s, INTERFACE_NAMSIZ);
+ STREAM_GETL(s, msg->my_role);
+ STREAM_GETL(s, msg->peer_state);
+ return 0;
+stream_failure:
+ return -1;
+}
+
+int mlag_lib_decode_vxlan_update(struct stream *s, struct mlag_vxlan *msg)
+{
+ if (s == NULL || msg == NULL)
+ return -1;
+
+ STREAM_GETL(s, msg->anycast_ip);
+ STREAM_GETL(s, msg->local_ip);
+ return 0;
+
+stream_failure:
+ return -1;
+}
+
+int mlag_lib_decode_frr_status(struct stream *s, struct mlag_frr_status *msg)
+{
+ if (s == NULL || msg == NULL)
+ return -1;
+
+ STREAM_GETL(s, msg->frr_state);
+ return 0;
+stream_failure:
+ return -1;
+}
diff --git a/lib/mlag.h b/lib/mlag.h
new file mode 100644
index 0000000..37bb3aa
--- /dev/null
+++ b/lib/mlag.h
@@ -0,0 +1,146 @@
+/* mlag header.
+ * Copyright (C) 2018 Cumulus Networks, Inc.
+ * Donald Sharp
+ *
+ * This file is part of FRR.
+ *
+ * FRR 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, or (at your option) any
+ * later version.
+ *
+ * FRR 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 FRR; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+#ifndef __MLAG_H__
+#define __MLAG_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "lib/if.h"
+#include "lib/vrf.h"
+#include "lib/stream.h"
+
+#define MLAG_MSG_NULL_PAYLOAD 0
+#define MLAG_MSG_NO_BATCH 1
+#define MLAG_BUF_LIMIT 2048
+
+enum mlag_role {
+ MLAG_ROLE_NONE,
+ MLAG_ROLE_PRIMARY,
+ MLAG_ROLE_SECONDARY
+};
+
+enum mlag_state {
+ MLAG_STATE_DOWN,
+ MLAG_STATE_RUNNING,
+};
+
+enum mlag_frr_state {
+ MLAG_FRR_STATE_NONE,
+ MLAG_FRR_STATE_DOWN,
+ MLAG_FRR_STATE_UP,
+};
+
+enum mlag_owner {
+ MLAG_OWNER_NONE,
+ MLAG_OWNER_INTERFACE,
+ MLAG_OWNER_VXLAN,
+};
+
+/*
+ * This message definition should match mlag.proto
+ * Because message registration is based on this
+ */
+enum mlag_msg_type {
+ MLAG_MSG_NONE = 0,
+ MLAG_REGISTER = 1,
+ MLAG_DEREGISTER = 2,
+ MLAG_STATUS_UPDATE = 3,
+ MLAG_MROUTE_ADD = 4,
+ MLAG_MROUTE_DEL = 5,
+ MLAG_DUMP = 6,
+ MLAG_MROUTE_ADD_BULK = 7,
+ MLAG_MROUTE_DEL_BULK = 8,
+ MLAG_PIM_CFG_DUMP = 10,
+ MLAG_VXLAN_UPDATE = 11,
+ MLAG_PEER_FRR_STATUS = 12,
+};
+
+struct mlag_frr_status {
+ enum mlag_frr_state frr_state;
+};
+
+struct mlag_status {
+ char peerlink_rif[INTERFACE_NAMSIZ];
+ enum mlag_role my_role;
+ enum mlag_state peer_state;
+};
+
+#define MLAG_ROLE_STRSIZE 16
+
+struct mlag_vxlan {
+ uint32_t anycast_ip;
+ uint32_t local_ip;
+};
+
+struct mlag_mroute_add {
+ char vrf_name[VRF_NAMSIZ];
+ uint32_t source_ip;
+ uint32_t group_ip;
+ uint32_t cost_to_rp;
+ enum mlag_owner owner_id;
+ bool am_i_dr;
+ bool am_i_dual_active;
+ vrf_id_t vrf_id;
+ char intf_name[INTERFACE_NAMSIZ];
+};
+
+struct mlag_mroute_del {
+ char vrf_name[VRF_NAMSIZ];
+ uint32_t source_ip;
+ uint32_t group_ip;
+ enum mlag_owner owner_id;
+ vrf_id_t vrf_id;
+ char intf_name[INTERFACE_NAMSIZ];
+};
+
+struct mlag_msg {
+ enum mlag_msg_type msg_type;
+ uint16_t data_len;
+ uint16_t msg_cnt;
+ uint8_t data[0];
+} __attribute__((packed));
+
+
+extern char *mlag_role2str(enum mlag_role role, char *buf, size_t size);
+extern char *mlag_lib_msgid_to_str(enum mlag_msg_type msg_type, char *buf,
+ size_t size);
+extern int mlag_lib_decode_mlag_hdr(struct stream *s, struct mlag_msg *msg,
+ size_t *length);
+extern int mlag_lib_decode_mroute_add(struct stream *s,
+ struct mlag_mroute_add *msg,
+ size_t *length);
+extern int mlag_lib_decode_mroute_del(struct stream *s,
+ struct mlag_mroute_del *msg,
+ size_t *length);
+extern int mlag_lib_decode_mlag_status(struct stream *s,
+ struct mlag_status *msg);
+extern int mlag_lib_decode_vxlan_update(struct stream *s,
+ struct mlag_vxlan *msg);
+extern int mlag_lib_decode_frr_status(struct stream *s,
+ struct mlag_frr_status *msg);
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/module.c b/lib/module.c
new file mode 100644
index 0000000..4037bfb
--- /dev/null
+++ b/lib/module.c
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) 2015-16 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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 "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+#include <dlfcn.h>
+
+#include "module.h"
+#include "memory.h"
+#include "lib/version.h"
+#include "printfrr.h"
+
+DEFINE_MTYPE_STATIC(LIB, MODULE_LOADNAME, "Module loading name");
+DEFINE_MTYPE_STATIC(LIB, MODULE_LOADARGS, "Module loading arguments");
+DEFINE_MTYPE_STATIC(LIB, MODULE_LOAD_ERR, "Module loading error");
+
+static struct frrmod_info frrmod_default_info = {
+ .name = "libfrr",
+ .version = FRR_VERSION,
+ .description = "libfrr core module",
+};
+union _frrmod_runtime_u frrmod_default = {
+ .r =
+ {
+ .info = &frrmod_default_info,
+ .finished_loading = 1,
+ },
+};
+
+XREF_SETUP();
+
+// if defined(HAVE_SYS_WEAK_ALIAS_ATTRIBUTE)
+// union _frrmod_runtime_u _frrmod_this_module
+// __attribute__((weak, alias("frrmod_default")));
+// elif defined(HAVE_SYS_WEAK_ALIAS_PRAGMA)
+#pragma weak _frrmod_this_module = frrmod_default
+// else
+// error need weak symbol support
+// endif
+
+struct frrmod_runtime *frrmod_list = &frrmod_default.r;
+static struct frrmod_runtime **frrmod_last = &frrmod_default.r.next;
+static const char *execname = NULL;
+
+void frrmod_init(struct frrmod_runtime *modinfo)
+{
+ modinfo->finished_loading = true;
+ *frrmod_last = modinfo;
+ frrmod_last = &modinfo->next;
+
+ execname = modinfo->info->name;
+}
+
+/*
+ * If caller wants error strings, it should define non-NULL pFerrlog
+ * which will be called with 0-terminated error messages. These
+ * messages will NOT contain newlines, and the (*pFerrlog)() function
+ * could be called multiple times for a single call to frrmod_load().
+ *
+ * The (*pFerrlog)() function may copy these strings if needed, but
+ * should expect them to be freed by frrmod_load() before frrmod_load()
+ * returns.
+ *
+ * frrmod_load() is coded such that (*pFerrlog)() will be called only
+ * in the case where frrmod_load() returns an error.
+ */
+struct frrmod_runtime *frrmod_load(const char *spec, const char *dir,
+ void (*pFerrlog)(const void *, const char *),
+ const void *pErrlogCookie)
+{
+ void *handle = NULL;
+ char name[PATH_MAX], fullpath[PATH_MAX * 2], *args;
+ struct frrmod_runtime *rtinfo, **rtinfop;
+ const struct frrmod_info *info;
+
+#define FRRMOD_LOAD_N_ERRSTR 10
+ char *aErr[FRRMOD_LOAD_N_ERRSTR];
+ unsigned int iErr = 0;
+
+ memset(aErr, 0, sizeof(aErr));
+
+#define ERR_RECORD(...) \
+ do { \
+ if (pFerrlog && (iErr < FRRMOD_LOAD_N_ERRSTR)) { \
+ aErr[iErr++] = asprintfrr(MTYPE_MODULE_LOAD_ERR, \
+ __VA_ARGS__); \
+ } \
+ } while (0)
+
+#define ERR_REPORT \
+ do { \
+ if (pFerrlog) { \
+ unsigned int i; \
+ \
+ for (i = 0; i < iErr; ++i) { \
+ (*pFerrlog)(pErrlogCookie, aErr[i]); \
+ } \
+ } \
+ } while (0)
+
+#define ERR_FREE \
+ do { \
+ unsigned int i; \
+ \
+ for (i = 0; i < iErr; ++i) { \
+ XFREE(MTYPE_MODULE_LOAD_ERR, aErr[i]); \
+ aErr[i] = 0; \
+ } \
+ iErr = 0; \
+ } while (0)
+
+ snprintf(name, sizeof(name), "%s", spec);
+ args = strchr(name, ':');
+ if (args)
+ *args++ = '\0';
+
+ if (!strchr(name, '/')) {
+ if (execname) {
+ snprintf(fullpath, sizeof(fullpath), "%s/%s_%s.so", dir,
+ execname, name);
+ handle = dlopen(fullpath, RTLD_NOW | RTLD_GLOBAL);
+ if (!handle)
+ ERR_RECORD("loader error: dlopen(%s): %s",
+ fullpath, dlerror());
+ }
+ if (!handle) {
+ snprintf(fullpath, sizeof(fullpath), "%s/%s.so", dir,
+ name);
+ handle = dlopen(fullpath, RTLD_NOW | RTLD_GLOBAL);
+ if (!handle)
+ ERR_RECORD("loader error: dlopen(%s): %s",
+ fullpath, dlerror());
+ }
+ }
+ if (!handle) {
+ snprintf(fullpath, sizeof(fullpath), "%s", name);
+ handle = dlopen(fullpath, RTLD_NOW | RTLD_GLOBAL);
+ if (!handle)
+ ERR_RECORD("loader error: dlopen(%s): %s", fullpath,
+ dlerror());
+ }
+ if (!handle) {
+ ERR_REPORT;
+ ERR_FREE;
+ return NULL;
+ }
+
+ /* previous dlopen() errors are no longer relevant */
+ ERR_FREE;
+
+ rtinfop = dlsym(handle, "frr_module");
+ if (!rtinfop) {
+ dlclose(handle);
+ ERR_RECORD("\"%s\" is not an FRR module: %s", name, dlerror());
+ ERR_REPORT;
+ ERR_FREE;
+ return NULL;
+ }
+ rtinfo = *rtinfop;
+ rtinfo->load_name = XSTRDUP(MTYPE_MODULE_LOADNAME, name);
+ rtinfo->dl_handle = handle;
+ if (args)
+ rtinfo->load_args = XSTRDUP(MTYPE_MODULE_LOADARGS, args);
+ info = rtinfo->info;
+
+ if (rtinfo->finished_loading) {
+ dlclose(handle);
+ ERR_RECORD("module \"%s\" already loaded", name);
+ goto out_fail;
+ }
+
+ if (info->init && info->init()) {
+ dlclose(handle);
+ ERR_RECORD("module \"%s\" initialisation failed", name);
+ goto out_fail;
+ }
+
+ rtinfo->finished_loading = true;
+
+ *frrmod_last = rtinfo;
+ frrmod_last = &rtinfo->next;
+ ERR_FREE;
+ return rtinfo;
+
+out_fail:
+ XFREE(MTYPE_MODULE_LOADARGS, rtinfo->load_args);
+ XFREE(MTYPE_MODULE_LOADNAME, rtinfo->load_name);
+ ERR_REPORT;
+ ERR_FREE;
+ return NULL;
+}
+
+#if 0
+void frrmod_unload(struct frrmod_runtime *module)
+{
+}
+#endif
diff --git a/lib/module.h b/lib/module.h
new file mode 100644
index 0000000..ae1ca2f
--- /dev/null
+++ b/lib/module.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2015-16 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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_MODULE_H
+#define _FRR_MODULE_H
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "compiler.h"
+#include "xref.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct frrmod_runtime;
+
+struct frrmod_info {
+ /* single-line few-word title */
+ const char *name;
+ /* human-readable version number, should not contain spaces */
+ const char *version;
+ /* one-paragraph description */
+ const char *description;
+
+ int (*init)(void);
+};
+
+/* primary entry point structure to be present in loadable module under
+ * "_frrmod_this_module" dlsym() name
+ *
+ * note space for future extensions is reserved below, so other modules
+ * (e.g. memory management, hooks) can add fields
+ *
+ * const members/info are in frrmod_info.
+ */
+struct frrmod_runtime {
+ struct frrmod_runtime *next;
+
+ const struct frrmod_info *info;
+ void *dl_handle;
+ bool finished_loading;
+
+ char *load_name;
+ char *load_args;
+};
+
+/* space-reserving foo */
+struct _frrmod_runtime_size {
+ struct frrmod_runtime r;
+ /* this will barf if frrmod_runtime exceeds 1024 bytes ... */
+ uint8_t space[1024 - sizeof(struct frrmod_runtime)];
+};
+union _frrmod_runtime_u {
+ struct frrmod_runtime r;
+ struct _frrmod_runtime_size s;
+};
+
+extern union _frrmod_runtime_u _frrmod_this_module;
+#define THIS_MODULE (&_frrmod_this_module.r)
+
+#define FRR_COREMOD_SETUP(...) \
+ static const struct frrmod_info _frrmod_info = {__VA_ARGS__}; \
+ DSO_LOCAL union _frrmod_runtime_u _frrmod_this_module = {{ \
+ NULL, \
+ &_frrmod_info, \
+ }}; \
+ XREF_SETUP(); \
+ MACRO_REQUIRE_SEMICOLON() /* end */
+
+#define FRR_MODULE_SETUP(...) \
+ FRR_COREMOD_SETUP(__VA_ARGS__); \
+ DSO_SELF struct frrmod_runtime *frr_module = &_frrmod_this_module.r; \
+ MACRO_REQUIRE_SEMICOLON() /* end */
+
+extern struct frrmod_runtime *frrmod_list;
+
+extern void frrmod_init(struct frrmod_runtime *modinfo);
+extern struct frrmod_runtime *frrmod_load(const char *spec, const char *dir,
+ void (*pFerrlog)(const void *,
+ const char *),
+ const void *pErrlogCookie);
+#if 0
+/* not implemented yet */
+extern void frrmod_unload(struct frrmod_runtime *module);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRR_MODULE_H */
diff --git a/lib/monotime.h b/lib/monotime.h
new file mode 100644
index 0000000..89616c5
--- /dev/null
+++ b/lib/monotime.h
@@ -0,0 +1,214 @@
+/*
+ * Copyright (c) 2017 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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_MONOTIME_H
+#define _FRR_MONOTIME_H
+
+#include <stdint.h>
+#include <time.h>
+#include <sys/time.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct fbuf;
+struct printfrr_eargs;
+
+#ifndef TIMESPEC_TO_TIMEVAL
+/* should be in sys/time.h on BSD & Linux libcs */
+#define TIMESPEC_TO_TIMEVAL(tv, ts) \
+ do { \
+ (tv)->tv_sec = (ts)->tv_sec; \
+ (tv)->tv_usec = (ts)->tv_nsec / 1000; \
+ } while (0)
+#endif
+#ifndef TIMEVAL_TO_TIMESPEC
+/* should be in sys/time.h on BSD & Linux libcs */
+#define TIMEVAL_TO_TIMESPEC(tv, ts) \
+ do { \
+ (ts)->tv_sec = (tv)->tv_sec; \
+ (ts)->tv_nsec = (tv)->tv_usec * 1000; \
+ } while (0)
+#endif
+
+/* Linux/glibc is sadly missing these timespec helpers */
+#ifndef timespecadd
+#define timespecadd(tsp, usp, vsp) \
+ do { \
+ (vsp)->tv_sec = (tsp)->tv_sec + (usp)->tv_sec; \
+ (vsp)->tv_nsec = (tsp)->tv_nsec + (usp)->tv_nsec; \
+ if ((vsp)->tv_nsec >= 1000000000L) { \
+ (vsp)->tv_sec++; \
+ (vsp)->tv_nsec -= 1000000000L; \
+ } \
+ } while (0)
+#endif
+
+#ifndef timespecsub
+#define timespecsub(tsp, usp, vsp) \
+ do { \
+ (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \
+ (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \
+ if ((vsp)->tv_nsec < 0) { \
+ (vsp)->tv_sec--; \
+ (vsp)->tv_nsec += 1000000000L; \
+ } \
+ } while (0)
+#endif
+
+static inline time_t monotime(struct timeval *tvo)
+{
+ struct timespec ts;
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ if (tvo) {
+ TIMESPEC_TO_TIMEVAL(tvo, &ts);
+ }
+ return ts.tv_sec;
+}
+
+#define ONE_DAY_SECOND (60 * 60 * 24)
+#define ONE_WEEK_SECOND (ONE_DAY_SECOND * 7)
+#define ONE_YEAR_SECOND (ONE_DAY_SECOND * 365)
+
+/* the following two return microseconds, not time_t!
+ *
+ * also, they're negative forms of each other, but having both makes the
+ * code more readable
+ */
+static inline int64_t monotime_since(const struct timeval *ref,
+ struct timeval *out)
+{
+ struct timeval tv;
+ monotime(&tv);
+ timersub(&tv, ref, &tv);
+ if (out)
+ *out = tv;
+ return (int64_t)tv.tv_sec * 1000000LL + tv.tv_usec;
+}
+
+static inline int64_t monotime_until(const struct timeval *ref,
+ struct timeval *out)
+{
+ struct timeval tv;
+ monotime(&tv);
+ timersub(ref, &tv, &tv);
+ if (out)
+ *out = tv;
+ return (int64_t)tv.tv_sec * 1000000LL + tv.tv_usec;
+}
+
+static inline time_t monotime_to_realtime(const struct timeval *mono,
+ struct timeval *realout)
+{
+ struct timeval delta, real;
+
+ monotime_since(mono, &delta);
+ gettimeofday(&real, NULL);
+
+ timersub(&real, &delta, &real);
+ if (realout)
+ *realout = real;
+ return real.tv_sec;
+}
+
+/* Char buffer size for time-to-string api */
+#define MONOTIME_STRLEN 32
+
+static inline char *time_to_string(time_t ts, char *buf)
+{
+ struct timeval tv;
+ time_t tbuf;
+
+ monotime(&tv);
+ tbuf = time(NULL) - (tv.tv_sec - ts);
+
+ return ctime_r(&tbuf, buf);
+}
+
+/* Convert interval to human-friendly string, used in cli output e.g. */
+static inline const char *frrtime_to_interval(time_t t, char *buf,
+ size_t buflen)
+{
+ struct tm tm;
+
+ gmtime_r(&t, &tm);
+
+ if (t < ONE_DAY_SECOND)
+ snprintf(buf, buflen, "%02d:%02d:%02d", tm.tm_hour, tm.tm_min,
+ tm.tm_sec);
+ else if (t < ONE_WEEK_SECOND)
+ snprintf(buf, buflen, "%dd%02dh%02dm", tm.tm_yday, tm.tm_hour,
+ tm.tm_min);
+ else
+ snprintf(buf, buflen, "%02dw%dd%02dh", tm.tm_yday / 7,
+ tm.tm_yday - ((tm.tm_yday / 7) * 7), tm.tm_hour);
+ return buf;
+}
+
+enum {
+ /* n/a - input was seconds precision, don't print any fractional */
+ TIMEFMT_SECONDS = (1 << 0),
+ /* caller is directly invoking printfrr_time and has pre-specified
+ * I/Iu/Is/M/Mu/Ms/R/Ru/Rs (for printing timers)
+ */
+ TIMEFMT_PRESELECT = (1 << 1),
+ /* don't print any output - this is needed for invoking printfrr_time
+ * from another printfrr extensions to skip over flag characters
+ */
+ TIMEFMT_SKIP = (1 << 2),
+ /* use spaces in appropriate places */
+ TIMEFMT_SPACE = (1 << 3),
+
+ /* input interpretations: */
+ TIMEFMT_REALTIME = (1 << 8),
+ TIMEFMT_MONOTONIC = (1 << 9),
+ TIMEFMT_SINCE = (1 << 10),
+ TIMEFMT_UNTIL = (1 << 11),
+
+ TIMEFMT_ABSOLUTE = TIMEFMT_REALTIME | TIMEFMT_MONOTONIC,
+ TIMEFMT_ANCHORS = TIMEFMT_SINCE | TIMEFMT_UNTIL,
+
+ /* calendaric formats: */
+ TIMEFMT_ISO8601 = (1 << 16),
+
+ /* interval formats: */
+ /* 't' - use [t]raditional 3-block format */
+ TIMEFMT_BASIC = (1 << 24),
+ /* 'm' - select mm:ss */
+ TIMEFMT_MMSS = (1 << 25),
+ /* 'h' - select hh:mm:ss */
+ TIMEFMT_HHMMSS = (1 << 26),
+ /* 'd' - print as decimal number of seconds */
+ TIMEFMT_DECIMAL = (1 << 27),
+ /* 'mx'/'hx' - replace zero value with "--:--" or "--:--:--" */
+ TIMEFMT_DASHES = (1 << 31),
+
+ /* helpers for reference */
+ TIMEFMT_TIMER_DEADLINE =
+ TIMEFMT_PRESELECT | TIMEFMT_MONOTONIC | TIMEFMT_UNTIL,
+ TIMEFMT_TIMER_INTERVAL = TIMEFMT_PRESELECT,
+};
+
+extern ssize_t printfrr_time(struct fbuf *buf, struct printfrr_eargs *ea,
+ const struct timespec *ts, unsigned int flags);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRR_MONOTIME_H */
diff --git a/lib/mpls.c b/lib/mpls.c
new file mode 100644
index 0000000..ac5792a
--- /dev/null
+++ b/lib/mpls.c
@@ -0,0 +1,100 @@
+/*
+ * mpls functions
+ *
+ * Copyright (C) 2018 Cumulus Networks, Inc.
+ * Donald Sharp
+ *
+ * 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 <mpls.h>
+#include <memory.h>
+
+/*
+ * String to label conversion, labels separated by '/'.
+ *
+ * @param label_str labels separated by /
+ * @param num_labels number of labels; zero if conversion was unsuccessful
+ * @param labels preallocated mpls_label_t array of size MPLS_MAX_LABELS; only
+ * modified if the conversion succeeded
+ * @return 0 on success
+ * -1 if the string could not be parsed as integers
+ * -2 if a label was inside the reserved range (0-15)
+ * -3 if the number of labels given exceeds MPLS_MAX_LABELS
+ */
+int mpls_str2label(const char *label_str, uint8_t *num_labels,
+ mpls_label_t *labels)
+{
+ char *ostr; // copy of label string (start)
+ char *lstr; // copy of label string
+ char *nump; // pointer to next segment
+ char *endp; // end pointer
+ int i; // for iterating label_str
+ int rc; // return code
+ mpls_label_t pl[MPLS_MAX_LABELS]; // parsed labels
+
+ /* labels to zero until we have a successful parse */
+ ostr = lstr = XSTRDUP(MTYPE_TMP, label_str);
+ *num_labels = 0;
+ rc = 0;
+
+ for (i = 0; i < MPLS_MAX_LABELS && lstr && !rc; i++) {
+ nump = strsep(&lstr, "/");
+ pl[i] = strtoul(nump, &endp, 10);
+
+ /* format check */
+ if (*endp != '\0')
+ rc = -1;
+ /* validity check */
+ else if (!IS_MPLS_UNRESERVED_LABEL(pl[i]))
+ rc = -2;
+ }
+
+ /* excess labels */
+ if (!rc && i == MPLS_MAX_LABELS && lstr)
+ rc = -3;
+
+ if (!rc) {
+ *num_labels = i;
+ memcpy(labels, pl, *num_labels * sizeof(mpls_label_t));
+ }
+
+ XFREE(MTYPE_TMP, ostr);
+
+ return rc;
+}
+
+/*
+ * Label to string conversion, labels in string separated by '/'.
+ */
+char *mpls_label2str(uint8_t num_labels, const mpls_label_t *labels, char *buf,
+ int len, int pretty)
+{
+ char label_buf[BUFSIZ];
+ int i;
+
+ buf[0] = '\0';
+ for (i = 0; i < num_labels; i++) {
+ if (i != 0)
+ strlcat(buf, "/", len);
+ if (pretty)
+ label2str(labels[i], label_buf, sizeof(label_buf));
+ else
+ snprintf(label_buf, sizeof(label_buf), "%u", labels[i]);
+ strlcat(buf, label_buf, len);
+ }
+
+ return buf;
+}
diff --git a/lib/mpls.h b/lib/mpls.h
new file mode 100644
index 0000000..74bd7aa
--- /dev/null
+++ b/lib/mpls.h
@@ -0,0 +1,226 @@
+/*
+ * MPLS definitions
+ * Copyright 2015 Cumulus Networks, Inc.
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra 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 _QUAGGA_MPLS_H
+#define _QUAGGA_MPLS_H
+
+#include <zebra.h>
+#include <arpa/inet.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef MPLS_LABEL_MAX
+#undef MPLS_LABEL_MAX
+#endif
+
+#define MPLS_LABEL_HELPSTR \
+ "Specify label(s) for this route\nOne or more " \
+ "labels in the range (16-1048575) separated by '/'\n"
+
+/* Well-known MPLS label values (RFC 3032 etc). */
+#define MPLS_LABEL_IPV4_EXPLICIT_NULL 0 /* [RFC3032] */
+#define MPLS_LABEL_ROUTER_ALERT 1 /* [RFC3032] */
+#define MPLS_LABEL_IPV6_EXPLICIT_NULL 2 /* [RFC3032] */
+#define MPLS_LABEL_IMPLICIT_NULL 3 /* [RFC3032] */
+#define MPLS_LABEL_ELI 7 /* [RFC6790] */
+#define MPLS_LABEL_GAL 13 /* [RFC5586] */
+#define MPLS_LABEL_OAM_ALERT 14 /* [RFC3429] */
+#define MPLS_LABEL_EXTENSION 15 /* [RFC7274] */
+#define MPLS_LABEL_MAX 1048575
+#define MPLS_LABEL_VALUE_MASK 0x000FFFFF
+#define MPLS_LABEL_NONE 0xFFFFFFFF /* for internal use only */
+
+/* Minimum and maximum label values */
+#define MPLS_LABEL_RESERVED_MIN 0
+#define MPLS_LABEL_RESERVED_MAX 15
+#define MPLS_LABEL_UNRESERVED_MIN 16
+#define MPLS_LABEL_UNRESERVED_MAX 1048575
+#define MPLS_LABEL_BASE_ANY 0
+
+/* Default min and max SRGB label range */
+/* Even if the SRGB allows to manage different Label space between routers,
+ * if an operator want to use the same SRGB for all its router, we must fix
+ * a common range. However, Cisco start its SRGB at 16000 and Juniper ends
+ * its SRGB at 16384 for OSPF. Thus, by fixing the minimum SRGB label to
+ * 8000 we could deal with both Cisco and Juniper.
+ */
+#define MPLS_DEFAULT_MIN_SRGB_LABEL 8000
+#define MPLS_DEFAULT_MAX_SRGB_LABEL 50000
+#define MPLS_DEFAULT_MIN_SRGB_SIZE 5000
+#define MPLS_DEFAULT_MAX_SRGB_SIZE 20000
+
+/* Maximum # labels that can be pushed. */
+#define MPLS_MAX_LABELS 16
+
+#define IS_MPLS_RESERVED_LABEL(label) (label <= MPLS_LABEL_RESERVED_MAX)
+
+#define IS_MPLS_UNRESERVED_LABEL(label) \
+ (label >= MPLS_LABEL_UNRESERVED_MIN \
+ && label <= MPLS_LABEL_UNRESERVED_MAX)
+
+/* Definitions for a MPLS label stack entry (RFC 3032). This encodes the
+ * label, EXP, BOS and TTL fields.
+ */
+typedef unsigned int mpls_lse_t;
+
+#define MPLS_LS_LABEL_MASK 0xFFFFF000
+#define MPLS_LS_LABEL_SHIFT 12
+#define MPLS_LS_EXP_MASK 0x00000E00
+#define MPLS_LS_EXP_SHIFT 9
+#define MPLS_LS_S_MASK 0x00000100
+#define MPLS_LS_S_SHIFT 8
+#define MPLS_LS_TTL_MASK 0x000000FF
+#define MPLS_LS_TTL_SHIFT 0
+
+#define MPLS_LABEL_VALUE(lse) \
+ ((lse & MPLS_LS_LABEL_MASK) >> MPLS_LS_LABEL_SHIFT)
+#define MPLS_LABEL_EXP(lse) ((lse & MPLS_LS_EXP_MASK) >> MPLS_LS_EXP_SHIFT)
+#define MPLS_LABEL_BOS(lse) ((lse & MPLS_LS_S_MASK) >> MPLS_LS_S_SHIFT)
+#define MPLS_LABEL_TTL(lse) ((lse & MPLS_LS_TTL_MASK) >> MPLS_LS_TTL_SHIFT)
+
+#define IS_MPLS_LABEL_BOS(ls) (MPLS_LABEL_BOS(ls) == 1)
+
+#define MPLS_LABEL_LEN_BITS 20
+
+/* MPLS label value as a 32-bit (mostly we only care about the label value). */
+typedef unsigned int mpls_label_t;
+
+struct mpls_label_stack {
+ uint8_t num_labels;
+ uint8_t reserved[3];
+ mpls_label_t label[0]; /* 1 or more labels */
+};
+
+/* The MPLS explicit-null label is 0 which means when you memset a mpls_label_t
+ * to zero you have set that variable to explicit-null which was probably not
+ * your intent. The work-around is to use one bit to indicate if the
+ * mpls_label_t has been set by the user. MPLS_INVALID_LABEL has this bit clear
+ * so that we can use MPLS_INVALID_LABEL to initialize mpls_label_t variables.
+ */
+#define MPLS_INVALID_LABEL 0xFFFDFFFF
+
+/* LSP types. */
+enum lsp_types_t {
+ ZEBRA_LSP_NONE = 0, /* No LSP. */
+ ZEBRA_LSP_STATIC = 1, /* Static LSP. */
+ ZEBRA_LSP_LDP = 2, /* LDP LSP. */
+ ZEBRA_LSP_BGP = 3, /* BGP LSP. */
+ ZEBRA_LSP_OSPF_SR = 4,/* OSPF Segment Routing LSP. */
+ ZEBRA_LSP_ISIS_SR = 5,/* IS-IS Segment Routing LSP. */
+ ZEBRA_LSP_SHARP = 6, /* Identifier for test protocol */
+ ZEBRA_LSP_SRTE = 7, /* SR-TE LSP */
+};
+
+/* Functions for basic label operations. */
+
+/* Encode a label stack entry from fields; convert to network byte-order as
+ * the Netlink interface expects MPLS labels to be in this format.
+ */
+static inline mpls_lse_t mpls_lse_encode(mpls_label_t label, uint32_t ttl,
+ uint32_t exp, uint32_t bos)
+{
+ mpls_lse_t lse;
+ lse = htonl((label << MPLS_LS_LABEL_SHIFT) | (exp << MPLS_LS_EXP_SHIFT)
+ | (bos ? (1 << MPLS_LS_S_SHIFT) : 0)
+ | (ttl << MPLS_LS_TTL_SHIFT));
+ return lse;
+}
+
+/* Extract the fields from a label stack entry after converting to host-byte
+ * order. This is expected to be called only for messages received over the
+ * Netlink interface.
+ */
+static inline void mpls_lse_decode(mpls_lse_t lse, mpls_label_t *label,
+ uint32_t *ttl, uint32_t *exp, uint32_t *bos)
+{
+ mpls_lse_t local_lse;
+
+ local_lse = ntohl(lse);
+ *label = MPLS_LABEL_VALUE(local_lse);
+ *exp = MPLS_LABEL_EXP(local_lse);
+ *bos = MPLS_LABEL_BOS(local_lse);
+ *ttl = MPLS_LABEL_TTL(local_lse);
+}
+
+/* Invalid label index value (when used with BGP Prefix-SID). Should
+ * match the BGP definition.
+ */
+#define MPLS_INVALID_LABEL_INDEX 0xFFFFFFFF
+
+/* Printable string for labels (with consideration for reserved values). */
+static inline char *label2str(mpls_label_t label, char *buf, size_t len)
+{
+ switch (label) {
+ case MPLS_LABEL_IPV4_EXPLICIT_NULL:
+ strlcpy(buf, "IPv4 Explicit Null", len);
+ return (buf);
+ case MPLS_LABEL_ROUTER_ALERT:
+ strlcpy(buf, "Router Alert", len);
+ return (buf);
+ case MPLS_LABEL_IPV6_EXPLICIT_NULL:
+ strlcpy(buf, "IPv6 Explicit Null", len);
+ return (buf);
+ case MPLS_LABEL_IMPLICIT_NULL:
+ strlcpy(buf, "implicit-null", len);
+ return (buf);
+ case MPLS_LABEL_ELI:
+ strlcpy(buf, "Entropy Label Indicator", len);
+ return (buf);
+ case MPLS_LABEL_GAL:
+ strlcpy(buf, "Generic Associated Channel", len);
+ return (buf);
+ case MPLS_LABEL_OAM_ALERT:
+ strlcpy(buf, "OAM Alert", len);
+ return (buf);
+ case MPLS_LABEL_EXTENSION:
+ strlcpy(buf, "Extension", len);
+ return (buf);
+ default:
+ if (label < 16)
+ snprintf(buf, len, "Reserved (%u)", label);
+ else
+ snprintf(buf, len, "%u", label);
+ return (buf);
+ }
+}
+
+/*
+ * String to label conversion, labels separated by '/'.
+ */
+int mpls_str2label(const char *label_str, uint8_t *num_labels,
+ mpls_label_t *labels);
+
+/* Generic string buffer for label-stack-to-str */
+#define MPLS_LABEL_STRLEN 1024
+
+/*
+ * Label to string conversion, labels in string separated by '/'.
+ */
+char *mpls_label2str(uint8_t num_labels, const mpls_label_t *labels, char *buf,
+ int len, int pretty);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/netns_linux.c b/lib/netns_linux.c
new file mode 100644
index 0000000..43c0d8c
--- /dev/null
+++ b/lib/netns_linux.c
@@ -0,0 +1,612 @@
+/*
+ * NS functions.
+ * Copyright (C) 2014 6WIND S.A.
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra 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>
+
+#ifdef HAVE_NETNS
+#undef _GNU_SOURCE
+#define _GNU_SOURCE
+
+#include <sched.h>
+#endif
+
+/* for basename */
+#include <libgen.h>
+
+#include "if.h"
+#include "ns.h"
+#include "log.h"
+#include "memory.h"
+#include "command.h"
+#include "vty.h"
+#include "vrf.h"
+#include "lib_errors.h"
+
+DEFINE_MTYPE_STATIC(LIB, NS, "NetNS Context");
+DEFINE_MTYPE_STATIC(LIB, NS_NAME, "NetNS Name");
+
+static inline int ns_compare(const struct ns *ns, const struct ns *ns2);
+static struct ns *ns_lookup_name_internal(const char *name);
+
+RB_GENERATE(ns_head, ns, entry, ns_compare)
+
+static struct ns_head ns_tree = RB_INITIALIZER(&ns_tree);
+
+static struct ns *default_ns;
+static int ns_current_ns_fd;
+static int ns_default_ns_fd;
+
+static int ns_debug;
+
+struct ns_map_nsid {
+ RB_ENTRY(ns_map_nsid) id_entry;
+ ns_id_t ns_id_external;
+ ns_id_t ns_id;
+};
+
+static inline int ns_map_compare(const struct ns_map_nsid *a,
+ const struct ns_map_nsid *b)
+{
+ return (a->ns_id - b->ns_id);
+}
+
+RB_HEAD(ns_map_nsid_head, ns_map_nsid);
+RB_PROTOTYPE(ns_map_nsid_head, ns_map_nsid, id_entry, ns_map_compare);
+RB_GENERATE(ns_map_nsid_head, ns_map_nsid, id_entry, ns_map_compare);
+static struct ns_map_nsid_head ns_map_nsid_list =
+ RB_INITIALIZER(&ns_map_nsid_list);
+
+static ns_id_t ns_id_external_numbering;
+
+
+#ifndef CLONE_NEWNET
+#define CLONE_NEWNET 0x40000000
+/* New network namespace (lo, device, names sockets, etc) */
+#endif
+
+#ifndef HAVE_SETNS
+static inline int setns(int fd, int nstype)
+{
+#ifdef __NR_setns
+ return syscall(__NR_setns, fd, nstype);
+#else
+ errno = EINVAL;
+ return -1;
+#endif
+}
+#endif /* !HAVE_SETNS */
+
+#ifdef HAVE_NETNS
+static int have_netns_enabled = -1;
+#endif /* HAVE_NETNS */
+
+static int have_netns(void)
+{
+#ifdef HAVE_NETNS
+ if (have_netns_enabled < 0) {
+ int fd = open(NS_DEFAULT_NAME, O_RDONLY);
+
+ if (fd < 0)
+ have_netns_enabled = 0;
+ else {
+ have_netns_enabled = 1;
+ close(fd);
+ }
+ }
+ return have_netns_enabled;
+#else
+ return 0;
+#endif
+}
+
+/* Holding NS hooks */
+static struct ns_master {
+ int (*ns_new_hook)(struct ns *ns);
+ int (*ns_delete_hook)(struct ns *ns);
+ int (*ns_enable_hook)(struct ns *ns);
+ int (*ns_disable_hook)(struct ns *ns);
+} ns_master = {
+ 0,
+};
+
+static int ns_is_enabled(struct ns *ns);
+
+static inline int ns_compare(const struct ns *a, const struct ns *b)
+{
+ return (a->ns_id - b->ns_id);
+}
+
+/* Look up a NS by identifier. */
+static struct ns *ns_lookup_internal(ns_id_t ns_id)
+{
+ struct ns ns;
+
+ ns.ns_id = ns_id;
+ return RB_FIND(ns_head, &ns_tree, &ns);
+}
+
+/* Look up a NS by name */
+static struct ns *ns_lookup_name_internal(const char *name)
+{
+ struct ns *ns = NULL;
+
+ RB_FOREACH (ns, ns_head, &ns_tree) {
+ if (ns->name != NULL) {
+ if (strcmp(name, ns->name) == 0)
+ return ns;
+ }
+ }
+ return NULL;
+}
+
+static struct ns *ns_get_created_internal(struct ns *ns, char *name,
+ ns_id_t ns_id)
+{
+ int created = 0;
+ /*
+ * Initialize interfaces.
+ */
+ if (!ns && !name && ns_id != NS_UNKNOWN)
+ ns = ns_lookup_internal(ns_id);
+ if (!ns && name)
+ ns = ns_lookup_name_internal(name);
+ if (!ns) {
+ ns = XCALLOC(MTYPE_NS, sizeof(struct ns));
+ ns->ns_id = ns_id;
+ if (name)
+ ns->name = XSTRDUP(MTYPE_NS_NAME, name);
+ ns->fd = -1;
+ RB_INSERT(ns_head, &ns_tree, ns);
+ created = 1;
+ }
+ if (ns_id != ns->ns_id) {
+ RB_REMOVE(ns_head, &ns_tree, ns);
+ ns->ns_id = ns_id;
+ RB_INSERT(ns_head, &ns_tree, ns);
+ }
+ if (!created)
+ return ns;
+ if (ns_debug) {
+ if (ns->ns_id != NS_UNKNOWN)
+ zlog_info("NS %u is created.", ns->ns_id);
+ else
+ zlog_info("NS %s is created.", ns->name);
+ }
+ if (ns_master.ns_new_hook)
+ (*ns_master.ns_new_hook)(ns);
+ return ns;
+}
+
+/*
+ * Enable a NS - that is, let the NS be ready to use.
+ * The NS_ENABLE_HOOK callback will be called to inform
+ * that they can allocate resources in this NS.
+ *
+ * RETURN: 1 - enabled successfully; otherwise, 0.
+ */
+static int ns_enable_internal(struct ns *ns, void (*func)(ns_id_t, void *))
+{
+ if (!ns_is_enabled(ns)) {
+ if (have_netns()) {
+ ns->fd = open(ns->name, O_RDONLY);
+ } else {
+ ns->fd = -2;
+ /* Remember ns_enable_hook has been called */
+ errno = -ENOTSUP;
+ }
+
+ if (!ns_is_enabled(ns)) {
+ flog_err_sys(EC_LIB_SYSTEM_CALL,
+ "Can not enable NS %u: %s!", ns->ns_id,
+ safe_strerror(errno));
+ return 0;
+ }
+
+ /* Non default NS. leave */
+ if (ns->ns_id == NS_UNKNOWN) {
+ flog_err(EC_LIB_NS,
+ "Can not enable NS %s %u: Invalid NSID",
+ ns->name, ns->ns_id);
+ return 0;
+ }
+ if (func)
+ func(ns->ns_id, (void *)ns->vrf_ctxt);
+ if (ns_debug) {
+ if (have_netns())
+ zlog_info("NS %u is associated with NETNS %s.",
+ ns->ns_id, ns->name);
+ zlog_info("NS %u is enabled.", ns->ns_id);
+ }
+ /* zebra first receives NS enable event,
+ * then VRF enable event
+ */
+ if (ns_master.ns_enable_hook)
+ (*ns_master.ns_enable_hook)(ns);
+ }
+
+ return 1;
+}
+
+/*
+ * Check whether the NS is enabled - that is, whether the NS
+ * is ready to allocate resources. Currently there's only one
+ * type of resource: socket.
+ */
+static int ns_is_enabled(struct ns *ns)
+{
+ if (have_netns())
+ return ns && ns->fd >= 0;
+ else
+ return ns && ns->fd == -2 && ns->ns_id == NS_DEFAULT;
+}
+
+/*
+ * Disable a NS - that is, let the NS be unusable.
+ * The NS_DELETE_HOOK callback will be called to inform
+ * that they must release the resources in the NS.
+ */
+static void ns_disable_internal(struct ns *ns)
+{
+ if (ns_is_enabled(ns)) {
+ if (ns_debug)
+ zlog_info("NS %u is to be disabled.", ns->ns_id);
+
+ if (ns_master.ns_disable_hook)
+ (*ns_master.ns_disable_hook)(ns);
+
+ if (have_netns())
+ close(ns->fd);
+
+ ns->fd = -1;
+ }
+}
+
+/* VRF list existence check by name. */
+static struct ns_map_nsid *ns_map_nsid_lookup_by_nsid(ns_id_t ns_id)
+{
+ struct ns_map_nsid ns_map;
+
+ ns_map.ns_id = ns_id;
+ return RB_FIND(ns_map_nsid_head, &ns_map_nsid_list, &ns_map);
+}
+
+ns_id_t ns_map_nsid_with_external(ns_id_t ns_id, bool map)
+{
+ struct ns_map_nsid *ns_map;
+ vrf_id_t ns_id_external;
+
+ ns_map = ns_map_nsid_lookup_by_nsid(ns_id);
+ if (ns_map && !map) {
+ ns_id_external = ns_map->ns_id_external;
+ RB_REMOVE(ns_map_nsid_head, &ns_map_nsid_list, ns_map);
+ return ns_id_external;
+ }
+ if (ns_map)
+ return ns_map->ns_id_external;
+ ns_map = XCALLOC(MTYPE_NS, sizeof(struct ns_map_nsid));
+ /* increase vrf_id
+ * default vrf is the first one : 0
+ */
+ ns_map->ns_id_external = ns_id_external_numbering++;
+ ns_map->ns_id = ns_id;
+ RB_INSERT(ns_map_nsid_head, &ns_map_nsid_list, ns_map);
+ return ns_map->ns_id_external;
+}
+
+struct ns *ns_get_created(struct ns *ns, char *name, ns_id_t ns_id)
+{
+ return ns_get_created_internal(ns, name, ns_id);
+}
+
+int ns_have_netns(void)
+{
+ return have_netns();
+}
+
+/* Delete a NS. This is called in ns_terminate(). */
+void ns_delete(struct ns *ns)
+{
+ if (ns_debug)
+ zlog_info("NS %u is to be deleted.", ns->ns_id);
+
+ ns_disable(ns);
+
+ if (ns_master.ns_delete_hook)
+ (*ns_master.ns_delete_hook)(ns);
+
+ /*
+ * I'm not entirely sure if the vrf->iflist
+ * needs to be moved into here or not.
+ */
+ // if_terminate (&ns->iflist);
+
+ RB_REMOVE(ns_head, &ns_tree, ns);
+ XFREE(MTYPE_NS_NAME, ns->name);
+
+ XFREE(MTYPE_NS, ns);
+}
+
+/* Look up the data pointer of the specified VRF. */
+void *ns_info_lookup(ns_id_t ns_id)
+{
+ struct ns *ns = ns_lookup_internal(ns_id);
+
+ return ns ? ns->info : NULL;
+}
+
+/* Look up a NS by name */
+struct ns *ns_lookup_name(const char *name)
+{
+ return ns_lookup_name_internal(name);
+}
+
+int ns_enable(struct ns *ns, void (*func)(ns_id_t, void *))
+{
+ return ns_enable_internal(ns, func);
+}
+
+void ns_disable(struct ns *ns)
+{
+ ns_disable_internal(ns);
+}
+
+struct ns *ns_lookup(ns_id_t ns_id)
+{
+ return ns_lookup_internal(ns_id);
+}
+
+void ns_walk_func(int (*func)(struct ns *,
+ void *param_in,
+ void **param_out),
+ void *param_in,
+ void **param_out)
+{
+ struct ns *ns = NULL;
+ int ret;
+
+ RB_FOREACH (ns, ns_head, &ns_tree) {
+ ret = func(ns, param_in, param_out);
+ if (ret == NS_WALK_STOP)
+ return;
+ }
+}
+
+const char *ns_get_name(struct ns *ns)
+{
+ if (!ns)
+ return NULL;
+ return ns->name;
+}
+
+/* Add a NS hook. Please add hooks before calling ns_init(). */
+void ns_add_hook(int type, int (*func)(struct ns *))
+{
+ switch (type) {
+ case NS_NEW_HOOK:
+ ns_master.ns_new_hook = func;
+ break;
+ case NS_DELETE_HOOK:
+ ns_master.ns_delete_hook = func;
+ break;
+ case NS_ENABLE_HOOK:
+ ns_master.ns_enable_hook = func;
+ break;
+ case NS_DISABLE_HOOK:
+ ns_master.ns_disable_hook = func;
+ break;
+ default:
+ break;
+ }
+}
+
+/*
+ * NS realization with NETNS
+ */
+
+char *ns_netns_pathname(struct vty *vty, const char *name)
+{
+ static char pathname[PATH_MAX];
+ char *result;
+ char *check_base;
+
+ if (name[0] == '/') /* absolute pathname */
+ result = realpath(name, pathname);
+ else {
+ /* relevant pathname */
+ char tmp_name[PATH_MAX];
+
+ snprintf(tmp_name, sizeof(tmp_name), "%s/%s", NS_RUN_DIR, name);
+ result = realpath(tmp_name, pathname);
+ }
+
+ if (!result) {
+ if (vty)
+ vty_out(vty, "Invalid pathname for %s: %s\n",
+ pathname,
+ safe_strerror(errno));
+ else
+ flog_warn(EC_LIB_LINUX_NS,
+ "Invalid pathname for %s: %s", pathname,
+ safe_strerror(errno));
+ return NULL;
+ }
+ check_base = basename(pathname);
+ if (check_base != NULL && strlen(check_base) + 1 > NS_NAMSIZ) {
+ if (vty)
+ vty_out(vty, "NS name (%s) invalid: too long (>%d)\n",
+ check_base, NS_NAMSIZ - 1);
+ else
+ flog_warn(EC_LIB_LINUX_NS,
+ "NS name (%s) invalid: too long (>%d)",
+ check_base, NS_NAMSIZ - 1);
+ return NULL;
+ }
+ return pathname;
+}
+
+void ns_init(void)
+{
+ static int ns_initialised;
+
+ ns_debug = 0;
+ /* silently return as initialisation done */
+ if (ns_initialised == 1)
+ return;
+ errno = 0;
+ if (have_netns())
+ ns_default_ns_fd = open(NS_DEFAULT_NAME, O_RDONLY);
+ else {
+ ns_default_ns_fd = -1;
+ default_ns = NULL;
+ }
+ ns_current_ns_fd = -1;
+ ns_initialised = 1;
+}
+
+/* Initialize NS module. */
+void ns_init_management(ns_id_t default_ns_id, ns_id_t internal_ns)
+{
+ int fd;
+
+ ns_init();
+ default_ns = ns_get_created_internal(NULL, NULL, default_ns_id);
+ if (!default_ns) {
+ flog_err(EC_LIB_NS, "%s: failed to create the default NS!",
+ __func__);
+ exit(1);
+ }
+ if (have_netns()) {
+ fd = open(NS_DEFAULT_NAME, O_RDONLY);
+ default_ns->fd = fd;
+ }
+ default_ns->internal_ns_id = internal_ns;
+
+ /* Set the default NS name. */
+ default_ns->name = XSTRDUP(MTYPE_NS_NAME, NS_DEFAULT_NAME);
+ if (ns_debug)
+ zlog_info("%s: default NSID is %u", __func__,
+ default_ns->ns_id);
+
+ /* Enable the default NS. */
+ if (!ns_enable(default_ns, NULL)) {
+ flog_err(EC_LIB_NS, "%s: failed to enable the default NS!",
+ __func__);
+ exit(1);
+ }
+}
+
+/* Terminate NS module. */
+void ns_terminate(void)
+{
+ struct ns *ns;
+
+ while (!RB_EMPTY(ns_head, &ns_tree)) {
+ ns = RB_ROOT(ns_head, &ns_tree);
+
+ ns_delete(ns);
+ }
+}
+
+int ns_switch_to_netns(const char *name)
+{
+ int ret;
+ int fd;
+
+ if (name == NULL)
+ return -1;
+ if (ns_default_ns_fd == -1)
+ return -1;
+ fd = open(name, O_RDONLY);
+ if (fd == -1) {
+ errno = EINVAL;
+ return -1;
+ }
+ ret = setns(fd, CLONE_NEWNET);
+ ns_current_ns_fd = fd;
+ close(fd);
+ return ret;
+}
+
+/* returns 1 if switch() was not called before
+ * return status of setns() otherwise
+ */
+int ns_switchback_to_initial(void)
+{
+ if (ns_current_ns_fd != -1 && ns_default_ns_fd != -1) {
+ int ret;
+
+ ret = setns(ns_default_ns_fd, CLONE_NEWNET);
+ ns_current_ns_fd = -1;
+ return ret;
+ }
+ /* silently ignore if setns() is not called */
+ return 1;
+}
+
+/* Create a socket for the NS. */
+int ns_socket(int domain, int type, int protocol, ns_id_t ns_id)
+{
+ struct ns *ns = ns_lookup(ns_id);
+ int ret;
+
+ if (!ns || !ns_is_enabled(ns)) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (have_netns()) {
+ ret = (ns_id != NS_DEFAULT) ? setns(ns->fd, CLONE_NEWNET) : 0;
+ if (ret >= 0) {
+ ret = socket(domain, type, protocol);
+ if (ns_id != NS_DEFAULT) {
+ setns(ns_lookup(NS_DEFAULT)->fd, CLONE_NEWNET);
+ ns_current_ns_fd = ns_id;
+ }
+ }
+ } else
+ ret = socket(domain, type, protocol);
+
+ return ret;
+}
+
+/* if relative link_nsid matches default netns,
+ * then return default absolute netns value
+ * otherwise, return NS_UNKNOWN
+ */
+ns_id_t ns_id_get_absolute(ns_id_t ns_id_reference, ns_id_t link_nsid)
+{
+ struct ns *ns;
+
+ ns = ns_lookup(ns_id_reference);
+ if (!ns)
+ return NS_UNKNOWN;
+
+ if (ns->relative_default_ns != link_nsid)
+ return NS_UNKNOWN;
+
+ ns = ns_get_default();
+ assert(ns);
+ return ns->ns_id;
+}
+
+struct ns *ns_get_default(void)
+{
+ return default_ns;
+}
diff --git a/lib/netns_other.c b/lib/netns_other.c
new file mode 100644
index 0000000..b6570d3
--- /dev/null
+++ b/lib/netns_other.c
@@ -0,0 +1,163 @@
+/*
+ * NetNS backend for non Linux systems
+ * Copyright (C) 2018 6WIND S.A.
+ *
+ * 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
+ */
+
+
+#if !defined(GNU_LINUX) && defined(OPEN_BSD)
+/* OPEN_BSD */
+
+#include <zebra.h>
+#include "ns.h"
+#include "log.h"
+#include "memory.h"
+
+DEFINE_MTYPE_STATIC(LIB, NS, "NetNS Context");
+DEFINE_MTYPE_STATIC(LIB, NS_NAME, "NetNS Name");
+
+
+static inline int ns_compare(const struct ns *ns, const struct ns *ns2);
+
+RB_GENERATE(ns_head, ns, entry, ns_compare)
+
+static struct ns_head ns_tree = RB_INITIALIZER(&ns_tree);
+
+static inline int ns_compare(const struct ns *a, const struct ns *b)
+{
+ return (a->ns_id - b->ns_id);
+}
+
+void ns_terminate(void)
+{
+}
+
+/* API to initialize NETNS managerment
+ * parameter is the default ns_id
+ */
+void ns_init_management(ns_id_t ns_id)
+{
+}
+
+
+/*
+ * NS utilities
+ */
+
+/* Create a socket serving for the given NS
+ */
+int ns_socket(int domain, int type, int protocol, ns_id_t ns_id)
+{
+ return -1;
+}
+
+/* return the path of the NETNS */
+char *ns_netns_pathname(struct vty *vty, const char *name)
+{
+ return NULL;
+}
+
+/* Parse and execute a function on all the NETNS */
+void ns_walk_func(int (*func)(struct ns *))
+{
+}
+
+/* API to get the NETNS name, from the ns pointer */
+const char *ns_get_name(struct ns *ns)
+{
+ return NULL;
+}
+
+/* only called from vrf ( when removing netns from vrf)
+ * or at VRF termination
+ */
+void ns_delete(struct ns *ns)
+{
+}
+
+/* return > 0 if netns is available
+ * called by VRF to check netns backend is available for VRF
+ */
+int ns_have_netns(void)
+{
+ return 0;
+}
+
+/* API to get context information of a NS */
+void *ns_info_lookup(ns_id_t ns_id)
+{
+ return NULL;
+}
+
+/*
+ * NS init routine
+ * should be called from backendx
+ */
+void ns_init(void)
+{
+}
+
+/* API that can be used to change from NS */
+int ns_switchback_to_initial(void)
+{
+ return 0;
+}
+int ns_switch_to_netns(const char *netns_name)
+{
+ return 0;
+}
+
+/*
+ * NS handling routines.
+ * called by modules that use NS backend
+ */
+
+/* API to search for already present NETNS */
+struct ns *ns_lookup(ns_id_t ns_id)
+{
+ return NULL;
+}
+
+struct ns *ns_lookup_name(const char *name)
+{
+ return NULL;
+}
+
+/* API to handle NS : creation, enable, disable
+ * for enable, a callback function is passed as parameter
+ * the callback belongs to the module that uses NS as backend
+ * upon enabling the NETNS, the upper layer is informed
+ */
+int ns_enable(struct ns *ns, int (*func)(ns_id_t, void *))
+{
+ return 0;
+}
+
+ns_id_t ns_map_nsid_with_external(ns_id_t ns_id, bool maporunmap)
+{
+ return NS_UNKNOWN;
+}
+
+struct ns *ns_get_created(struct ns *ns, char *name, ns_id_t ns_id)
+{
+ return NULL;
+}
+
+void ns_disable(struct ns *ns)
+{
+}
+
+#endif /* !GNU_LINUX */
diff --git a/lib/network.c b/lib/network.c
new file mode 100644
index 0000000..b60ad9a
--- /dev/null
+++ b/lib/network.c
@@ -0,0 +1,124 @@
+/*
+ * Network library.
+ * Copyright (C) 1997 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 "network.h"
+#include "lib_errors.h"
+
+/* Read nbytes from fd and store into ptr. */
+int readn(int fd, uint8_t *ptr, int nbytes)
+{
+ int nleft;
+ int nread;
+
+ nleft = nbytes;
+
+ while (nleft > 0) {
+ nread = read(fd, ptr, nleft);
+
+ if (nread < 0)
+ return (nread);
+ else if (nread == 0)
+ break;
+
+ nleft -= nread;
+ ptr += nread;
+ }
+
+ return nbytes - nleft;
+}
+
+/* Write nbytes from ptr to fd. */
+int writen(int fd, const uint8_t *ptr, int nbytes)
+{
+ int nleft;
+ int nwritten;
+
+ nleft = nbytes;
+
+ while (nleft > 0) {
+ nwritten = write(fd, ptr, nleft);
+
+ if (nwritten < 0) {
+ if (!ERRNO_IO_RETRY(errno))
+ return nwritten;
+ }
+ if (nwritten == 0)
+ return (nwritten);
+
+ nleft -= nwritten;
+ ptr += nwritten;
+ }
+ return nbytes - nleft;
+}
+
+int set_nonblocking(int fd)
+{
+ int flags;
+
+ /* According to the Single UNIX Spec, the return value for F_GETFL
+ should
+ never be negative. */
+ flags = fcntl(fd, F_GETFL);
+ if (flags < 0) {
+ flog_err(EC_LIB_SYSTEM_CALL,
+ "fcntl(F_GETFL) failed for fd %d: %s", fd,
+ safe_strerror(errno));
+ return -1;
+ }
+ if (fcntl(fd, F_SETFL, (flags | O_NONBLOCK)) < 0) {
+ flog_err(EC_LIB_SYSTEM_CALL,
+ "fcntl failed setting fd %d non-blocking: %s", fd,
+ safe_strerror(errno));
+ return -1;
+ }
+ return 0;
+}
+
+int set_cloexec(int fd)
+{
+ int flags;
+ flags = fcntl(fd, F_GETFD, 0);
+ if (flags == -1)
+ return -1;
+
+ flags |= FD_CLOEXEC;
+ if (fcntl(fd, F_SETFD, flags) == -1)
+ return -1;
+ return 0;
+}
+
+float htonf(float host)
+{
+ uint32_t lu1, lu2;
+ float convert;
+
+ memcpy(&lu1, &host, sizeof(uint32_t));
+ lu2 = htonl(lu1);
+ memcpy(&convert, &lu2, sizeof(uint32_t));
+ return convert;
+}
+
+float ntohf(float net)
+{
+ return htonf(net);
+}
diff --git a/lib/network.h b/lib/network.h
new file mode 100644
index 0000000..10ed917
--- /dev/null
+++ b/lib/network.h
@@ -0,0 +1,106 @@
+/*
+ * Network library header.
+ * Copyright (C) 1998 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 _ZEBRA_NETWORK_H
+#define _ZEBRA_NETWORK_H
+
+#ifdef HAVE_SYS_ENDIAN_H
+#include <sys/endian.h>
+#endif
+#ifdef HAVE_ENDIAN_H
+#include <endian.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Both readn and writen are deprecated and will be removed. They are not
+ suitable for use with non-blocking file descriptors.
+ */
+extern int readn(int, uint8_t *, int);
+extern int writen(int, const uint8_t *, int);
+
+/* Set the file descriptor to use non-blocking I/O. Returns 0 for success,
+ -1 on error. */
+extern int set_nonblocking(int fd);
+
+extern int set_cloexec(int fd);
+
+/* Does the I/O error indicate that the operation should be retried later? */
+#define ERRNO_IO_RETRY(EN) \
+ (((EN) == EAGAIN) || ((EN) == EWOULDBLOCK) || ((EN) == EINTR))
+
+extern float htonf(float);
+extern float ntohf(float);
+
+/* force type for be64toh/htobe64 to be uint64_t, *without* a direct cast
+ *
+ * this is a workaround for false-positive printfrr warnings from FRR's
+ * frr-format GCC plugin that would be triggered from
+ * { printfrr("%"PRIu64, (uint64_t)be64toh(...)); }
+ *
+ * the key element here is that "(uint64_t)expr" causes the warning, while
+ * "({ uint64_t x = expr; x; })" does not. (The cast is the trigger, a
+ * variable of the same type works correctly.)
+ */
+
+/* zap system definitions... */
+#ifdef be64toh
+#undef be64toh
+#endif
+#ifdef htobe64
+#undef htobe64
+#endif
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+#define be64toh(x) ({ uint64_t r = __builtin_bswap64(x); r; })
+#define htobe64(x) ({ uint64_t r = __builtin_bswap64(x); r; })
+#elif BYTE_ORDER == BIG_ENDIAN
+#define be64toh(x) ({ uint64_t r = (x); r; })
+#define htobe64(x) ({ uint64_t r = (x); r; })
+#else
+#error nobody expects the endianish inquisition. check OS endian.h headers.
+#endif
+
+/**
+ * Helper function that returns a random long value. The main purpose of
+ * this function is to hide a `random()` call that gets flagged by coverity
+ * scan and put it into one place.
+ *
+ * The main usage of this function should be for generating jitter or weak
+ * random values for simple purposes.
+ *
+ * See 'man 3 random' for more information.
+ *
+ * \returns random long integer.
+ */
+static inline long frr_weak_random(void)
+{
+ /* coverity[dont_call] */
+ return random();
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ZEBRA_NETWORK_H */
diff --git a/lib/nexthop.c b/lib/nexthop.c
new file mode 100644
index 0000000..248acd2
--- /dev/null
+++ b/lib/nexthop.c
@@ -0,0 +1,1093 @@
+/* A generic nexthop structure
+ * Copyright (C) 2013 Cumulus Networks, Inc.
+ *
+ * This file is part of Quagga.
+ *
+ * Quagga 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, or (at your option) any
+ * later version.
+ *
+ * Quagga 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 "table.h"
+#include "memory.h"
+#include "command.h"
+#include "log.h"
+#include "sockunion.h"
+#include "linklist.h"
+#include "prefix.h"
+#include "nexthop.h"
+#include "mpls.h"
+#include "jhash.h"
+#include "printfrr.h"
+#include "vrf.h"
+#include "nexthop_group.h"
+
+DEFINE_MTYPE_STATIC(LIB, NEXTHOP, "Nexthop");
+DEFINE_MTYPE_STATIC(LIB, NH_LABEL, "Nexthop label");
+DEFINE_MTYPE_STATIC(LIB, NH_SRV6, "Nexthop srv6");
+
+static int _nexthop_labels_cmp(const struct nexthop *nh1,
+ const struct nexthop *nh2)
+{
+ const struct mpls_label_stack *nhl1 = NULL;
+ const struct mpls_label_stack *nhl2 = NULL;
+
+ nhl1 = nh1->nh_label;
+ nhl2 = nh2->nh_label;
+
+ /* No labels is a match */
+ if (!nhl1 && !nhl2)
+ return 0;
+
+ if (nhl1 && !nhl2)
+ return 1;
+
+ if (nhl2 && !nhl1)
+ return -1;
+
+ if (nhl1->num_labels > nhl2->num_labels)
+ return 1;
+
+ if (nhl1->num_labels < nhl2->num_labels)
+ return -1;
+
+ return memcmp(nhl1->label, nhl2->label,
+ (nhl1->num_labels * sizeof(mpls_label_t)));
+}
+
+static int _nexthop_srv6_cmp(const struct nexthop *nh1,
+ const struct nexthop *nh2)
+{
+ int ret = 0;
+
+ if (!nh1->nh_srv6 && !nh2->nh_srv6)
+ return 0;
+
+ if (nh1->nh_srv6 && !nh2->nh_srv6)
+ return 1;
+
+ if (!nh1->nh_srv6 && nh2->nh_srv6)
+ return -1;
+
+ if (nh1->nh_srv6->seg6local_action > nh2->nh_srv6->seg6local_action)
+ return 1;
+
+ if (nh2->nh_srv6->seg6local_action < nh1->nh_srv6->seg6local_action)
+ return -1;
+
+ ret = memcmp(&nh1->nh_srv6->seg6local_ctx,
+ &nh2->nh_srv6->seg6local_ctx,
+ sizeof(struct seg6local_context));
+ if (ret != 0)
+ return ret;
+
+ ret = memcmp(&nh1->nh_srv6->seg6_segs,
+ &nh2->nh_srv6->seg6_segs,
+ sizeof(struct in6_addr));
+
+ return ret;
+}
+
+int nexthop_g_addr_cmp(enum nexthop_types_t type, const union g_addr *addr1,
+ const union g_addr *addr2)
+{
+ int ret = 0;
+
+ switch (type) {
+ case NEXTHOP_TYPE_IPV4:
+ case NEXTHOP_TYPE_IPV4_IFINDEX:
+ ret = IPV4_ADDR_CMP(&addr1->ipv4, &addr2->ipv4);
+ break;
+ case NEXTHOP_TYPE_IPV6:
+ case NEXTHOP_TYPE_IPV6_IFINDEX:
+ ret = IPV6_ADDR_CMP(&addr1->ipv6, &addr2->ipv6);
+ break;
+ case NEXTHOP_TYPE_IFINDEX:
+ case NEXTHOP_TYPE_BLACKHOLE:
+ /* No addr here */
+ break;
+ }
+
+ return ret;
+}
+
+static int _nexthop_gateway_cmp(const struct nexthop *nh1,
+ const struct nexthop *nh2)
+{
+ return nexthop_g_addr_cmp(nh1->type, &nh1->gate, &nh2->gate);
+}
+
+static int _nexthop_source_cmp(const struct nexthop *nh1,
+ const struct nexthop *nh2)
+{
+ return nexthop_g_addr_cmp(nh1->type, &nh1->src, &nh2->src);
+}
+
+static int _nexthop_cmp_no_labels(const struct nexthop *next1,
+ const struct nexthop *next2)
+{
+ int ret = 0;
+
+ if (next1->vrf_id < next2->vrf_id)
+ return -1;
+
+ if (next1->vrf_id > next2->vrf_id)
+ return 1;
+
+ if (next1->type < next2->type)
+ return -1;
+
+ if (next1->type > next2->type)
+ return 1;
+
+ if (next1->weight < next2->weight)
+ return -1;
+
+ if (next1->weight > next2->weight)
+ return 1;
+
+ switch (next1->type) {
+ case NEXTHOP_TYPE_IPV4:
+ case NEXTHOP_TYPE_IPV6:
+ ret = _nexthop_gateway_cmp(next1, next2);
+ if (ret != 0)
+ return ret;
+ break;
+ case NEXTHOP_TYPE_IPV4_IFINDEX:
+ case NEXTHOP_TYPE_IPV6_IFINDEX:
+ ret = _nexthop_gateway_cmp(next1, next2);
+ if (ret != 0)
+ return ret;
+ /* Intentional Fall-Through */
+ case NEXTHOP_TYPE_IFINDEX:
+ if (next1->ifindex < next2->ifindex)
+ return -1;
+
+ if (next1->ifindex > next2->ifindex)
+ return 1;
+ break;
+ case NEXTHOP_TYPE_BLACKHOLE:
+ if (next1->bh_type < next2->bh_type)
+ return -1;
+
+ if (next1->bh_type > next2->bh_type)
+ return 1;
+ break;
+ }
+
+ if (next1->srte_color < next2->srte_color)
+ return -1;
+ if (next1->srte_color > next2->srte_color)
+ return 1;
+
+ ret = _nexthop_source_cmp(next1, next2);
+ if (ret != 0)
+ goto done;
+
+ if (!CHECK_FLAG(next1->flags, NEXTHOP_FLAG_HAS_BACKUP) &&
+ !CHECK_FLAG(next2->flags, NEXTHOP_FLAG_HAS_BACKUP))
+ return 0;
+
+ if (!CHECK_FLAG(next1->flags, NEXTHOP_FLAG_HAS_BACKUP) &&
+ CHECK_FLAG(next2->flags, NEXTHOP_FLAG_HAS_BACKUP))
+ return -1;
+
+ if (CHECK_FLAG(next1->flags, NEXTHOP_FLAG_HAS_BACKUP) &&
+ !CHECK_FLAG(next2->flags, NEXTHOP_FLAG_HAS_BACKUP))
+ return 1;
+
+ if (next1->backup_num == 0 && next2->backup_num == 0)
+ goto done;
+
+ if (next1->backup_num < next2->backup_num)
+ return -1;
+
+ if (next1->backup_num > next2->backup_num)
+ return 1;
+
+ ret = memcmp(next1->backup_idx,
+ next2->backup_idx, next1->backup_num);
+
+done:
+ return ret;
+}
+
+int nexthop_cmp(const struct nexthop *next1, const struct nexthop *next2)
+{
+ int ret = 0;
+
+ ret = _nexthop_cmp_no_labels(next1, next2);
+ if (ret != 0)
+ return ret;
+
+ ret = _nexthop_labels_cmp(next1, next2);
+ if (ret != 0)
+ return ret;
+
+ ret = _nexthop_srv6_cmp(next1, next2);
+
+ return ret;
+}
+
+/*
+ * More-limited comparison function used to detect duplicate
+ * nexthops. This is used in places where we don't need the full
+ * comparison of 'nexthop_cmp()'.
+ */
+int nexthop_cmp_basic(const struct nexthop *nh1,
+ const struct nexthop *nh2)
+{
+ int ret = 0;
+ const struct mpls_label_stack *nhl1 = NULL;
+ const struct mpls_label_stack *nhl2 = NULL;
+
+ if (nh1 == NULL && nh2 == NULL)
+ return 0;
+
+ if (nh1 && !nh2)
+ return 1;
+
+ if (!nh1 && nh2)
+ return -1;
+
+ if (nh1->vrf_id < nh2->vrf_id)
+ return -1;
+
+ if (nh1->vrf_id > nh2->vrf_id)
+ return 1;
+
+ if (nh1->type < nh2->type)
+ return -1;
+
+ if (nh1->type > nh2->type)
+ return 1;
+
+ if (nh1->weight < nh2->weight)
+ return -1;
+
+ if (nh1->weight > nh2->weight)
+ return 1;
+
+ switch (nh1->type) {
+ case NEXTHOP_TYPE_IPV4:
+ case NEXTHOP_TYPE_IPV6:
+ ret = nexthop_g_addr_cmp(nh1->type, &nh1->gate, &nh2->gate);
+ if (ret != 0)
+ return ret;
+ break;
+ case NEXTHOP_TYPE_IPV4_IFINDEX:
+ case NEXTHOP_TYPE_IPV6_IFINDEX:
+ ret = nexthop_g_addr_cmp(nh1->type, &nh1->gate, &nh2->gate);
+ if (ret != 0)
+ return ret;
+ /* Intentional Fall-Through */
+ case NEXTHOP_TYPE_IFINDEX:
+ if (nh1->ifindex < nh2->ifindex)
+ return -1;
+
+ if (nh1->ifindex > nh2->ifindex)
+ return 1;
+ break;
+ case NEXTHOP_TYPE_BLACKHOLE:
+ if (nh1->bh_type < nh2->bh_type)
+ return -1;
+
+ if (nh1->bh_type > nh2->bh_type)
+ return 1;
+ break;
+ }
+
+ /* Compare source addr */
+ ret = nexthop_g_addr_cmp(nh1->type, &nh1->src, &nh2->src);
+ if (ret != 0)
+ goto done;
+
+ nhl1 = nh1->nh_label;
+ nhl2 = nh2->nh_label;
+
+ /* No labels is a match */
+ if (!nhl1 && !nhl2)
+ return 0;
+
+ if (nhl1 && !nhl2)
+ return 1;
+
+ if (nhl2 && !nhl1)
+ return -1;
+
+ if (nhl1->num_labels > nhl2->num_labels)
+ return 1;
+
+ if (nhl1->num_labels < nhl2->num_labels)
+ return -1;
+
+ ret = memcmp(nhl1->label, nhl2->label,
+ (nhl1->num_labels * sizeof(mpls_label_t)));
+
+done:
+ return ret;
+}
+
+/*
+ * nexthop_type_to_str
+ */
+const char *nexthop_type_to_str(enum nexthop_types_t nh_type)
+{
+ static const char *const desc[] = {
+ "none", "Directly connected",
+ "IPv4 nexthop", "IPv4 nexthop with ifindex",
+ "IPv6 nexthop", "IPv6 nexthop with ifindex",
+ "Null0 nexthop",
+ };
+
+ return desc[nh_type];
+}
+
+/*
+ * Check if the labels match for the 2 nexthops specified.
+ */
+bool nexthop_labels_match(const struct nexthop *nh1, const struct nexthop *nh2)
+{
+ if (_nexthop_labels_cmp(nh1, nh2) != 0)
+ return false;
+
+ return true;
+}
+
+struct nexthop *nexthop_new(void)
+{
+ struct nexthop *nh;
+
+ nh = XCALLOC(MTYPE_NEXTHOP, sizeof(struct nexthop));
+
+ /*
+ * Default the weight to 1 here for all nexthops.
+ * The linux kernel does some weird stuff with adding +1 to
+ * all nexthop weights it gets over netlink.
+ * To handle this, just default everything to 1 right from
+ * from the beginning so we don't have to special case
+ * default weights in the linux netlink code.
+ *
+ * 1 should be a valid on all platforms anyway.
+ */
+ nh->weight = 1;
+
+ return nh;
+}
+
+/* Free nexthop. */
+void nexthop_free(struct nexthop *nexthop)
+{
+ nexthop_del_labels(nexthop);
+ nexthop_del_srv6_seg6local(nexthop);
+ nexthop_del_srv6_seg6(nexthop);
+ if (nexthop->resolved)
+ nexthops_free(nexthop->resolved);
+ XFREE(MTYPE_NEXTHOP, nexthop);
+}
+
+/* Frees a list of nexthops */
+void nexthops_free(struct nexthop *nexthop)
+{
+ struct nexthop *nh, *next;
+
+ for (nh = nexthop; nh; nh = next) {
+ next = nh->next;
+ nexthop_free(nh);
+ }
+}
+
+bool nexthop_same(const struct nexthop *nh1, const struct nexthop *nh2)
+{
+ if (nh1 && !nh2)
+ return false;
+
+ if (!nh1 && nh2)
+ return false;
+
+ if (nh1 == nh2)
+ return true;
+
+ if (nexthop_cmp(nh1, nh2) != 0)
+ return false;
+
+ return true;
+}
+
+bool nexthop_same_no_labels(const struct nexthop *nh1,
+ const struct nexthop *nh2)
+{
+ if (nh1 && !nh2)
+ return false;
+
+ if (!nh1 && nh2)
+ return false;
+
+ if (nh1 == nh2)
+ return true;
+
+ if (_nexthop_cmp_no_labels(nh1, nh2) != 0)
+ return false;
+
+ return true;
+}
+
+/*
+ * Allocate a new nexthop object and initialize it from various args.
+ */
+struct nexthop *nexthop_from_ifindex(ifindex_t ifindex, vrf_id_t vrf_id)
+{
+ struct nexthop *nexthop;
+
+ nexthop = nexthop_new();
+ nexthop->type = NEXTHOP_TYPE_IFINDEX;
+ nexthop->ifindex = ifindex;
+ nexthop->vrf_id = vrf_id;
+
+ return nexthop;
+}
+
+struct nexthop *nexthop_from_ipv4(const struct in_addr *ipv4,
+ const struct in_addr *src,
+ vrf_id_t vrf_id)
+{
+ struct nexthop *nexthop;
+
+ nexthop = nexthop_new();
+ nexthop->type = NEXTHOP_TYPE_IPV4;
+ nexthop->vrf_id = vrf_id;
+ nexthop->gate.ipv4 = *ipv4;
+ if (src)
+ nexthop->src.ipv4 = *src;
+
+ return nexthop;
+}
+
+struct nexthop *nexthop_from_ipv4_ifindex(const struct in_addr *ipv4,
+ const struct in_addr *src,
+ ifindex_t ifindex, vrf_id_t vrf_id)
+{
+ struct nexthop *nexthop;
+
+ nexthop = nexthop_new();
+ nexthop->type = NEXTHOP_TYPE_IPV4_IFINDEX;
+ nexthop->vrf_id = vrf_id;
+ nexthop->gate.ipv4 = *ipv4;
+ if (src)
+ nexthop->src.ipv4 = *src;
+ nexthop->ifindex = ifindex;
+
+ return nexthop;
+}
+
+struct nexthop *nexthop_from_ipv6(const struct in6_addr *ipv6,
+ vrf_id_t vrf_id)
+{
+ struct nexthop *nexthop;
+
+ nexthop = nexthop_new();
+ nexthop->vrf_id = vrf_id;
+ nexthop->type = NEXTHOP_TYPE_IPV6;
+ nexthop->gate.ipv6 = *ipv6;
+
+ return nexthop;
+}
+
+struct nexthop *nexthop_from_ipv6_ifindex(const struct in6_addr *ipv6,
+ ifindex_t ifindex, vrf_id_t vrf_id)
+{
+ struct nexthop *nexthop;
+
+ nexthop = nexthop_new();
+ nexthop->vrf_id = vrf_id;
+ nexthop->type = NEXTHOP_TYPE_IPV6_IFINDEX;
+ nexthop->gate.ipv6 = *ipv6;
+ nexthop->ifindex = ifindex;
+
+ return nexthop;
+}
+
+struct nexthop *nexthop_from_blackhole(enum blackhole_type bh_type,
+ vrf_id_t nh_vrf_id)
+{
+ struct nexthop *nexthop;
+
+ nexthop = nexthop_new();
+ nexthop->vrf_id = nh_vrf_id;
+ nexthop->type = NEXTHOP_TYPE_BLACKHOLE;
+ nexthop->bh_type = bh_type;
+
+ return nexthop;
+}
+
+/* Update nexthop with label information. */
+void nexthop_add_labels(struct nexthop *nexthop, enum lsp_types_t ltype,
+ uint8_t num_labels, const mpls_label_t *labels)
+{
+ struct mpls_label_stack *nh_label;
+ int i;
+
+ if (num_labels == 0)
+ return;
+
+ /* Enforce limit on label stack size */
+ if (num_labels > MPLS_MAX_LABELS)
+ num_labels = MPLS_MAX_LABELS;
+
+ nexthop->nh_label_type = ltype;
+
+ nh_label = XCALLOC(MTYPE_NH_LABEL,
+ sizeof(struct mpls_label_stack)
+ + num_labels * sizeof(mpls_label_t));
+ nh_label->num_labels = num_labels;
+ for (i = 0; i < num_labels; i++)
+ nh_label->label[i] = *(labels + i);
+ nexthop->nh_label = nh_label;
+}
+
+/* Free label information of nexthop, if present. */
+void nexthop_del_labels(struct nexthop *nexthop)
+{
+ XFREE(MTYPE_NH_LABEL, nexthop->nh_label);
+ nexthop->nh_label_type = ZEBRA_LSP_NONE;
+}
+
+void nexthop_add_srv6_seg6local(struct nexthop *nexthop, uint32_t action,
+ const struct seg6local_context *ctx)
+{
+ if (action == ZEBRA_SEG6_LOCAL_ACTION_UNSPEC)
+ return;
+
+ if (!nexthop->nh_srv6)
+ nexthop->nh_srv6 = XCALLOC(MTYPE_NH_SRV6,
+ sizeof(struct nexthop_srv6));
+
+ nexthop->nh_srv6->seg6local_action = action;
+ nexthop->nh_srv6->seg6local_ctx = *ctx;
+}
+
+void nexthop_del_srv6_seg6local(struct nexthop *nexthop)
+{
+ if (!nexthop->nh_srv6)
+ return;
+
+ nexthop->nh_srv6->seg6local_action = ZEBRA_SEG6_LOCAL_ACTION_UNSPEC;
+
+ if (sid_zero(&nexthop->nh_srv6->seg6_segs))
+ XFREE(MTYPE_NH_SRV6, nexthop->nh_srv6);
+}
+
+void nexthop_add_srv6_seg6(struct nexthop *nexthop,
+ const struct in6_addr *segs)
+{
+ if (!segs)
+ return;
+
+ if (!nexthop->nh_srv6)
+ nexthop->nh_srv6 = XCALLOC(MTYPE_NH_SRV6,
+ sizeof(struct nexthop_srv6));
+
+ nexthop->nh_srv6->seg6_segs = *segs;
+}
+
+void nexthop_del_srv6_seg6(struct nexthop *nexthop)
+{
+ if (!nexthop->nh_srv6)
+ return;
+
+ memset(&nexthop->nh_srv6->seg6_segs, 0,
+ sizeof(nexthop->nh_srv6->seg6_segs));
+
+ if (nexthop->nh_srv6->seg6local_action ==
+ ZEBRA_SEG6_LOCAL_ACTION_UNSPEC)
+ XFREE(MTYPE_NH_SRV6, nexthop->nh_srv6);
+}
+
+const char *nexthop2str(const struct nexthop *nexthop, char *str, int size)
+{
+ switch (nexthop->type) {
+ case NEXTHOP_TYPE_IFINDEX:
+ snprintf(str, size, "if %u", nexthop->ifindex);
+ break;
+ case NEXTHOP_TYPE_IPV4:
+ case NEXTHOP_TYPE_IPV4_IFINDEX:
+ snprintfrr(str, size, "%pI4 if %u", &nexthop->gate.ipv4,
+ nexthop->ifindex);
+ break;
+ case NEXTHOP_TYPE_IPV6:
+ case NEXTHOP_TYPE_IPV6_IFINDEX:
+ snprintfrr(str, size, "%pI6 if %u", &nexthop->gate.ipv6,
+ nexthop->ifindex);
+ break;
+ case NEXTHOP_TYPE_BLACKHOLE:
+ snprintf(str, size, "blackhole");
+ break;
+ }
+
+ return str;
+}
+
+/*
+ * Iteration step for ALL_NEXTHOPS macro:
+ * This is the tricky part. Check if `nexthop' has
+ * NEXTHOP_FLAG_RECURSIVE set. If yes, this implies that `nexthop' has
+ * at least one nexthop attached to `nexthop->resolved', which will be
+ * the next one.
+ *
+ * If NEXTHOP_FLAG_RECURSIVE is not set, `nexthop' will progress in its
+ * current chain. In case its current chain end is reached, it will move
+ * upwards in the recursion levels and progress there. Whenever a step
+ * forward in a chain is done, recursion will be checked again.
+ * In a nustshell, it's equivalent to a pre-traversal order assuming that
+ * left branch is 'resolved' and right branch is 'next':
+ * https://en.wikipedia.org/wiki/Tree_traversal#/media/File:Sorted_binary_tree_preorder.svg
+ */
+struct nexthop *nexthop_next(const struct nexthop *nexthop)
+{
+ if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_RECURSIVE))
+ return nexthop->resolved;
+
+ if (nexthop->next)
+ return nexthop->next;
+
+ for (struct nexthop *par = nexthop->rparent; par; par = par->rparent)
+ if (par->next)
+ return par->next;
+
+ return NULL;
+}
+
+/* Return the next nexthop in the tree that is resolved and active */
+struct nexthop *nexthop_next_active_resolved(const struct nexthop *nexthop)
+{
+ struct nexthop *next = nexthop_next(nexthop);
+
+ while (next
+ && (CHECK_FLAG(next->flags, NEXTHOP_FLAG_RECURSIVE)
+ || !CHECK_FLAG(next->flags, NEXTHOP_FLAG_ACTIVE)))
+ next = nexthop_next(next);
+
+ return next;
+}
+
+unsigned int nexthop_level(const struct nexthop *nexthop)
+{
+ unsigned int rv = 0;
+
+ for (const struct nexthop *par = nexthop->rparent;
+ par; par = par->rparent)
+ rv++;
+
+ return rv;
+}
+
+/* Only hash word-sized things, let cmp do the rest. */
+uint32_t nexthop_hash_quick(const struct nexthop *nexthop)
+{
+ uint32_t key = 0x45afe398;
+ int i;
+
+ key = jhash_3words(nexthop->type, nexthop->vrf_id,
+ nexthop->nh_label_type, key);
+
+ if (nexthop->nh_label) {
+ int labels = nexthop->nh_label->num_labels;
+
+ i = 0;
+
+ while (labels >= 3) {
+ key = jhash_3words(nexthop->nh_label->label[i],
+ nexthop->nh_label->label[i + 1],
+ nexthop->nh_label->label[i + 2],
+ key);
+ labels -= 3;
+ i += 3;
+ }
+
+ if (labels >= 2) {
+ key = jhash_2words(nexthop->nh_label->label[i],
+ nexthop->nh_label->label[i + 1],
+ key);
+ labels -= 2;
+ i += 2;
+ }
+
+ if (labels >= 1)
+ key = jhash_1word(nexthop->nh_label->label[i], key);
+ }
+
+ key = jhash_2words(nexthop->ifindex,
+ CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ONLINK),
+ key);
+
+ /* Include backup nexthops, if present */
+ if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_HAS_BACKUP)) {
+ int backups = nexthop->backup_num;
+
+ i = 0;
+
+ while (backups >= 3) {
+ key = jhash_3words(nexthop->backup_idx[i],
+ nexthop->backup_idx[i + 1],
+ nexthop->backup_idx[i + 2], key);
+ backups -= 3;
+ i += 3;
+ }
+
+ while (backups >= 2) {
+ key = jhash_2words(nexthop->backup_idx[i],
+ nexthop->backup_idx[i + 1], key);
+ backups -= 2;
+ i += 2;
+ }
+
+ if (backups >= 1)
+ key = jhash_1word(nexthop->backup_idx[i], key);
+ }
+
+ if (nexthop->nh_srv6) {
+ key = jhash_1word(nexthop->nh_srv6->seg6local_action, key);
+ key = jhash(&nexthop->nh_srv6->seg6local_ctx,
+ sizeof(nexthop->nh_srv6->seg6local_ctx), key);
+ key = jhash(&nexthop->nh_srv6->seg6_segs,
+ sizeof(nexthop->nh_srv6->seg6_segs), key);
+ }
+
+ return key;
+}
+
+
+#define GATE_SIZE 4 /* Number of uint32_t words in struct g_addr */
+
+/* For a more granular hash */
+uint32_t nexthop_hash(const struct nexthop *nexthop)
+{
+ uint32_t gate_src_rmap_raw[GATE_SIZE * 3] = {};
+ /* Get all the quick stuff */
+ uint32_t key = nexthop_hash_quick(nexthop);
+
+ assert(((sizeof(nexthop->gate) + sizeof(nexthop->src)
+ + sizeof(nexthop->rmap_src))
+ / 3)
+ == (GATE_SIZE * sizeof(uint32_t)));
+
+ memcpy(gate_src_rmap_raw, &nexthop->gate, GATE_SIZE);
+ memcpy(gate_src_rmap_raw + GATE_SIZE, &nexthop->src, GATE_SIZE);
+ memcpy(gate_src_rmap_raw + (2 * GATE_SIZE), &nexthop->rmap_src,
+ GATE_SIZE);
+
+ key = jhash2(gate_src_rmap_raw, (GATE_SIZE * 3), key);
+
+ return key;
+}
+
+void nexthop_copy_no_recurse(struct nexthop *copy,
+ const struct nexthop *nexthop,
+ struct nexthop *rparent)
+{
+ copy->vrf_id = nexthop->vrf_id;
+ copy->ifindex = nexthop->ifindex;
+ copy->type = nexthop->type;
+ copy->flags = nexthop->flags;
+ copy->weight = nexthop->weight;
+
+ assert(nexthop->backup_num < NEXTHOP_MAX_BACKUPS);
+ copy->backup_num = nexthop->backup_num;
+ if (copy->backup_num > 0)
+ memcpy(copy->backup_idx, nexthop->backup_idx, copy->backup_num);
+
+ copy->srte_color = nexthop->srte_color;
+ memcpy(&copy->gate, &nexthop->gate, sizeof(nexthop->gate));
+ memcpy(&copy->src, &nexthop->src, sizeof(nexthop->src));
+ memcpy(&copy->rmap_src, &nexthop->rmap_src, sizeof(nexthop->rmap_src));
+ copy->rparent = rparent;
+ if (nexthop->nh_label)
+ nexthop_add_labels(copy, nexthop->nh_label_type,
+ nexthop->nh_label->num_labels,
+ &nexthop->nh_label->label[0]);
+
+ if (nexthop->nh_srv6) {
+ if (nexthop->nh_srv6->seg6local_action !=
+ ZEBRA_SEG6_LOCAL_ACTION_UNSPEC)
+ nexthop_add_srv6_seg6local(copy,
+ nexthop->nh_srv6->seg6local_action,
+ &nexthop->nh_srv6->seg6local_ctx);
+ if (!sid_zero(&nexthop->nh_srv6->seg6_segs))
+ nexthop_add_srv6_seg6(copy,
+ &nexthop->nh_srv6->seg6_segs);
+ }
+}
+
+void nexthop_copy(struct nexthop *copy, const struct nexthop *nexthop,
+ struct nexthop *rparent)
+{
+ nexthop_copy_no_recurse(copy, nexthop, rparent);
+
+ /* Bit of a special case here, we need to handle the case
+ * of a nexthop resolving to a group. Hence, we need to
+ * use a nexthop_group API.
+ */
+ if (CHECK_FLAG(copy->flags, NEXTHOP_FLAG_RECURSIVE))
+ copy_nexthops(&copy->resolved, nexthop->resolved, copy);
+}
+
+struct nexthop *nexthop_dup_no_recurse(const struct nexthop *nexthop,
+ struct nexthop *rparent)
+{
+ struct nexthop *new = nexthop_new();
+
+ nexthop_copy_no_recurse(new, nexthop, rparent);
+ return new;
+}
+
+struct nexthop *nexthop_dup(const struct nexthop *nexthop,
+ struct nexthop *rparent)
+{
+ struct nexthop *new = nexthop_new();
+
+ nexthop_copy(new, nexthop, rparent);
+ return new;
+}
+
+/*
+ * Parse one or more backup index values, as comma-separated numbers,
+ * into caller's array of uint8_ts. The array must be NEXTHOP_MAX_BACKUPS
+ * in size. Mails back the number of values converted, and returns 0 on
+ * success, <0 if an error in parsing.
+ */
+int nexthop_str2backups(const char *str, int *num_backups,
+ uint8_t *backups)
+{
+ char *ostr; /* copy of string (start) */
+ char *lstr; /* working copy of string */
+ char *nump; /* pointer to next segment */
+ char *endp; /* end pointer */
+ int i, ret;
+ uint8_t tmp[NEXTHOP_MAX_BACKUPS];
+ uint32_t lval;
+
+ /* Copy incoming string; the parse is destructive */
+ lstr = ostr = XSTRDUP(MTYPE_TMP, str);
+ *num_backups = 0;
+ ret = 0;
+
+ for (i = 0; i < NEXTHOP_MAX_BACKUPS && lstr; i++) {
+ nump = strsep(&lstr, ",");
+ lval = strtoul(nump, &endp, 10);
+
+ /* Format check */
+ if (*endp != '\0') {
+ ret = -1;
+ break;
+ }
+
+ /* Empty value */
+ if (endp == nump) {
+ ret = -1;
+ break;
+ }
+
+ /* Limit to one octet */
+ if (lval > 255) {
+ ret = -1;
+ break;
+ }
+
+ tmp[i] = lval;
+ }
+
+ /* Excess values */
+ if (ret == 0 && i == NEXTHOP_MAX_BACKUPS && lstr)
+ ret = -1;
+
+ if (ret == 0) {
+ *num_backups = i;
+ memcpy(backups, tmp, i);
+ }
+
+ XFREE(MTYPE_TMP, ostr);
+
+ return ret;
+}
+
+ssize_t printfrr_nhs(struct fbuf *buf, const struct nexthop *nexthop)
+{
+ ssize_t ret = 0;
+
+ if (!nexthop)
+ return bputs(buf, "(null)");
+
+ switch (nexthop->type) {
+ case NEXTHOP_TYPE_IFINDEX:
+ ret += bprintfrr(buf, "if %u", nexthop->ifindex);
+ break;
+ case NEXTHOP_TYPE_IPV4:
+ case NEXTHOP_TYPE_IPV4_IFINDEX:
+ ret += bprintfrr(buf, "%pI4 if %u", &nexthop->gate.ipv4,
+ nexthop->ifindex);
+ break;
+ case NEXTHOP_TYPE_IPV6:
+ case NEXTHOP_TYPE_IPV6_IFINDEX:
+ ret += bprintfrr(buf, "%pI6 if %u", &nexthop->gate.ipv6,
+ nexthop->ifindex);
+ break;
+ case NEXTHOP_TYPE_BLACKHOLE:
+ ret += bputs(buf, "blackhole");
+ break;
+ }
+ return ret;
+}
+
+/*
+ * nexthop printing variants:
+ * %pNHvv
+ * via 1.2.3.4
+ * via 1.2.3.4, eth0
+ * is directly connected, eth0
+ * unreachable (blackhole)
+ * %pNHv
+ * 1.2.3.4
+ * 1.2.3.4, via eth0
+ * directly connected, eth0
+ * unreachable (blackhole)
+ * %pNHs
+ * nexthop2str()
+ * %pNHcg
+ * 1.2.3.4
+ * (0-length if no IP address present)
+ * %pNHci
+ * eth0
+ * (0-length if no interface present)
+ */
+printfrr_ext_autoreg_p("NH", printfrr_nh);
+static ssize_t printfrr_nh(struct fbuf *buf, struct printfrr_eargs *ea,
+ const void *ptr)
+{
+ const struct nexthop *nexthop = ptr;
+ bool do_ifi = false;
+ const char *v_is = "", *v_via = "", *v_viaif = "via ";
+ ssize_t ret = 0;
+
+ switch (*ea->fmt) {
+ case 'v':
+ ea->fmt++;
+ if (*ea->fmt == 'v') {
+ v_is = "is ";
+ v_via = "via ";
+ v_viaif = "";
+ ea->fmt++;
+ }
+
+ if (!nexthop)
+ return bputs(buf, "(null)");
+
+ switch (nexthop->type) {
+ case NEXTHOP_TYPE_IPV4:
+ case NEXTHOP_TYPE_IPV4_IFINDEX:
+ ret += bprintfrr(buf, "%s%pI4", v_via,
+ &nexthop->gate.ipv4);
+ do_ifi = true;
+ break;
+ case NEXTHOP_TYPE_IPV6:
+ case NEXTHOP_TYPE_IPV6_IFINDEX:
+ ret += bprintfrr(buf, "%s%pI6", v_via,
+ &nexthop->gate.ipv6);
+ do_ifi = true;
+ break;
+ case NEXTHOP_TYPE_IFINDEX:
+ ret += bprintfrr(buf, "%sdirectly connected, %s", v_is,
+ ifindex2ifname(nexthop->ifindex,
+ nexthop->vrf_id));
+ break;
+ case NEXTHOP_TYPE_BLACKHOLE:
+ ret += bputs(buf, "unreachable");
+
+ switch (nexthop->bh_type) {
+ case BLACKHOLE_REJECT:
+ ret += bputs(buf, " (ICMP unreachable)");
+ break;
+ case BLACKHOLE_ADMINPROHIB:
+ ret += bputs(buf, " (ICMP admin-prohibited)");
+ break;
+ case BLACKHOLE_NULL:
+ ret += bputs(buf, " (blackhole)");
+ break;
+ case BLACKHOLE_UNSPEC:
+ break;
+ }
+ break;
+ }
+ if (do_ifi && nexthop->ifindex)
+ ret += bprintfrr(buf, ", %s%s", v_viaif,
+ ifindex2ifname(nexthop->ifindex,
+ nexthop->vrf_id));
+
+ return ret;
+ case 's':
+ ea->fmt++;
+
+ ret += printfrr_nhs(buf, nexthop);
+ return ret;
+ case 'c':
+ ea->fmt++;
+ if (*ea->fmt == 'g') {
+ ea->fmt++;
+ if (!nexthop)
+ return bputs(buf, "(null)");
+ switch (nexthop->type) {
+ case NEXTHOP_TYPE_IPV4:
+ case NEXTHOP_TYPE_IPV4_IFINDEX:
+ ret += bprintfrr(buf, "%pI4",
+ &nexthop->gate.ipv4);
+ break;
+ case NEXTHOP_TYPE_IPV6:
+ case NEXTHOP_TYPE_IPV6_IFINDEX:
+ ret += bprintfrr(buf, "%pI6",
+ &nexthop->gate.ipv6);
+ break;
+ case NEXTHOP_TYPE_IFINDEX:
+ case NEXTHOP_TYPE_BLACKHOLE:
+ break;
+ }
+ } else if (*ea->fmt == 'i') {
+ ea->fmt++;
+ if (!nexthop)
+ return bputs(buf, "(null)");
+ switch (nexthop->type) {
+ case NEXTHOP_TYPE_IFINDEX:
+ ret += bprintfrr(
+ buf, "%s",
+ ifindex2ifname(nexthop->ifindex,
+ nexthop->vrf_id));
+ break;
+ case NEXTHOP_TYPE_IPV4:
+ case NEXTHOP_TYPE_IPV4_IFINDEX:
+ case NEXTHOP_TYPE_IPV6:
+ case NEXTHOP_TYPE_IPV6_IFINDEX:
+ if (nexthop->ifindex)
+ ret += bprintfrr(
+ buf, "%s",
+ ifindex2ifname(
+ nexthop->ifindex,
+ nexthop->vrf_id));
+ break;
+ case NEXTHOP_TYPE_BLACKHOLE:
+ break;
+ }
+ }
+ return ret;
+ }
+ return -1;
+}
diff --git a/lib/nexthop.h b/lib/nexthop.h
new file mode 100644
index 0000000..f1309aa
--- /dev/null
+++ b/lib/nexthop.h
@@ -0,0 +1,270 @@
+/*
+ * Nexthop structure definition.
+ * Copyright (C) 1997, 98, 99, 2001 Kunihiro Ishiguro
+ * Copyright (C) 2013 Cumulus Networks, Inc.
+ *
+ * This file is part of Quagga.
+ *
+ * Quagga 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, or (at your option) any
+ * later version.
+ *
+ * Quagga 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 _LIB_NEXTHOP_H
+#define _LIB_NEXTHOP_H
+
+#include "prefix.h"
+#include "mpls.h"
+#include "vxlan.h"
+#include "srv6.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Maximum next hop string length - gateway + ifindex */
+#define NEXTHOP_STRLEN (INET6_ADDRSTRLEN + 30)
+
+union g_addr {
+ struct in_addr ipv4;
+ struct in6_addr ipv6;
+};
+
+enum nexthop_types_t {
+ NEXTHOP_TYPE_IFINDEX = 1, /* Directly connected. */
+ NEXTHOP_TYPE_IPV4, /* IPv4 nexthop. */
+ NEXTHOP_TYPE_IPV4_IFINDEX, /* IPv4 nexthop with ifindex. */
+ NEXTHOP_TYPE_IPV6, /* IPv6 nexthop. */
+ NEXTHOP_TYPE_IPV6_IFINDEX, /* IPv6 nexthop with ifindex. */
+ NEXTHOP_TYPE_BLACKHOLE, /* Null0 nexthop. */
+};
+
+enum blackhole_type {
+ BLACKHOLE_UNSPEC = 0,
+ BLACKHOLE_NULL,
+ BLACKHOLE_REJECT,
+ BLACKHOLE_ADMINPROHIB,
+};
+
+enum nh_encap_type {
+ NET_VXLAN = 100, /* value copied from FPM_NH_ENCAP_VXLAN. */
+};
+
+/* Fixed limit on the number of backup nexthops per primary nexthop */
+#define NEXTHOP_MAX_BACKUPS 8
+
+/* Backup index value is limited */
+#define NEXTHOP_BACKUP_IDX_MAX 255
+
+/* Nexthop structure. */
+struct nexthop {
+ struct nexthop *next;
+ struct nexthop *prev;
+
+ /*
+ * What vrf is this nexthop associated with?
+ */
+ vrf_id_t vrf_id;
+
+ /* Interface index. */
+ ifindex_t ifindex;
+
+ enum nexthop_types_t type;
+
+ uint16_t flags;
+#define NEXTHOP_FLAG_ACTIVE (1 << 0) /* This nexthop is alive. */
+#define NEXTHOP_FLAG_FIB (1 << 1) /* FIB nexthop. */
+#define NEXTHOP_FLAG_RECURSIVE (1 << 2) /* Recursive nexthop. */
+#define NEXTHOP_FLAG_ONLINK (1 << 3) /* Nexthop should be installed
+ * onlink.
+ */
+#define NEXTHOP_FLAG_DUPLICATE (1 << 4) /* nexthop duplicates another
+ * active one
+ */
+#define NEXTHOP_FLAG_RNH_FILTERED (1 << 5) /* rmap filtered, used by rnh */
+#define NEXTHOP_FLAG_HAS_BACKUP (1 << 6) /* Backup nexthop index is set */
+#define NEXTHOP_FLAG_SRTE (1 << 7) /* SR-TE color used for BGP traffic */
+#define NEXTHOP_FLAG_EVPN (1 << 8) /* nexthop is EVPN */
+#define NEXTHOP_FLAG_LINKDOWN (1 << 9) /* is not removed on link down */
+
+#define NEXTHOP_IS_ACTIVE(flags) \
+ (CHECK_FLAG(flags, NEXTHOP_FLAG_ACTIVE) \
+ && !CHECK_FLAG(flags, NEXTHOP_FLAG_DUPLICATE))
+
+ /* Nexthop address */
+ union {
+ union g_addr gate;
+ enum blackhole_type bh_type;
+ };
+ union g_addr src;
+ union g_addr rmap_src; /* Src is set via routemap */
+
+ /* Nexthops obtained by recursive resolution.
+ *
+ * If the nexthop struct needs to be resolved recursively,
+ * NEXTHOP_FLAG_RECURSIVE will be set in flags and the nexthops
+ * obtained by recursive resolution will be added to `resolved'.
+ */
+ struct nexthop *resolved;
+ /* Recursive parent */
+ struct nexthop *rparent;
+
+ /* Type of label(s), if any */
+ enum lsp_types_t nh_label_type;
+
+ /* Label(s) associated with this nexthop. */
+ struct mpls_label_stack *nh_label;
+
+ /* Weight of the nexthop ( for unequal cost ECMP ) */
+ uint8_t weight;
+
+ /* Count and index of corresponding backup nexthop(s) in a backup list;
+ * only meaningful if the HAS_BACKUP flag is set.
+ */
+ uint8_t backup_num;
+ uint8_t backup_idx[NEXTHOP_MAX_BACKUPS];
+
+ /* Encapsulation information. */
+ enum nh_encap_type nh_encap_type;
+ union {
+ vni_t vni;
+ } nh_encap;
+
+ /* SR-TE color used for matching SR-TE policies */
+ uint32_t srte_color;
+
+ /* SRv6 information */
+ struct nexthop_srv6 *nh_srv6;
+};
+
+/* Utility to append one nexthop to another. */
+#define NEXTHOP_APPEND(to, new) \
+ do { \
+ (to)->next = (new); \
+ (new)->prev = (to); \
+ (new)->next = NULL; \
+ } while (0)
+
+struct nexthop *nexthop_new(void);
+
+void nexthop_free(struct nexthop *nexthop);
+void nexthops_free(struct nexthop *nexthop);
+
+void nexthop_add_labels(struct nexthop *nexthop, enum lsp_types_t ltype,
+ uint8_t num_labels, const mpls_label_t *labels);
+void nexthop_del_labels(struct nexthop *);
+void nexthop_add_srv6_seg6local(struct nexthop *nexthop, uint32_t action,
+ const struct seg6local_context *ctx);
+void nexthop_del_srv6_seg6local(struct nexthop *nexthop);
+void nexthop_add_srv6_seg6(struct nexthop *nexthop,
+ const struct in6_addr *segs);
+void nexthop_del_srv6_seg6(struct nexthop *nexthop);
+
+/*
+ * Allocate a new nexthop object and initialize it from various args.
+ */
+struct nexthop *nexthop_from_ifindex(ifindex_t ifindex, vrf_id_t vrf_id);
+struct nexthop *nexthop_from_ipv4(const struct in_addr *ipv4,
+ const struct in_addr *src,
+ vrf_id_t vrf_id);
+struct nexthop *nexthop_from_ipv4_ifindex(const struct in_addr *ipv4,
+ const struct in_addr *src,
+ ifindex_t ifindex, vrf_id_t vrf_id);
+struct nexthop *nexthop_from_ipv6(const struct in6_addr *ipv6,
+ vrf_id_t vrf_id);
+struct nexthop *nexthop_from_ipv6_ifindex(const struct in6_addr *ipv6,
+ ifindex_t ifindex, vrf_id_t vrf_id);
+struct nexthop *nexthop_from_blackhole(enum blackhole_type bh_type,
+ vrf_id_t nh_vrf_id);
+
+/*
+ * Hash a nexthop. Suitable for use with hash tables.
+ *
+ * This function uses the following values when computing the hash:
+ * - vrf_id
+ * - ifindex
+ * - type
+ * - gate
+ *
+ * nexthop
+ * The nexthop to hash
+ *
+ * Returns:
+ * 32-bit hash of nexthop
+ */
+uint32_t nexthop_hash(const struct nexthop *nexthop);
+/*
+ * Hash a nexthop only on word-sized attributes:
+ * - vrf_id
+ * - ifindex
+ * - type
+ * - (some) flags
+ */
+uint32_t nexthop_hash_quick(const struct nexthop *nexthop);
+
+extern bool nexthop_same(const struct nexthop *nh1, const struct nexthop *nh2);
+extern bool nexthop_same_no_labels(const struct nexthop *nh1,
+ const struct nexthop *nh2);
+extern int nexthop_cmp(const struct nexthop *nh1, const struct nexthop *nh2);
+extern int nexthop_g_addr_cmp(enum nexthop_types_t type,
+ const union g_addr *addr1,
+ const union g_addr *addr2);
+
+/* More-limited comparison function used to detect duplicate nexthops.
+ * Returns -1, 0, 1
+ */
+int nexthop_cmp_basic(const struct nexthop *nh1, const struct nexthop *nh2);
+
+extern const char *nexthop_type_to_str(enum nexthop_types_t nh_type);
+extern bool nexthop_labels_match(const struct nexthop *nh1,
+ const struct nexthop *nh2);
+
+extern const char *nexthop2str(const struct nexthop *nexthop,
+ char *str, int size);
+extern struct nexthop *nexthop_next(const struct nexthop *nexthop);
+extern struct nexthop *
+nexthop_next_active_resolved(const struct nexthop *nexthop);
+extern unsigned int nexthop_level(const struct nexthop *nexthop);
+/* Copies to an already allocated nexthop struct */
+extern void nexthop_copy(struct nexthop *copy, const struct nexthop *nexthop,
+ struct nexthop *rparent);
+/* Copies to an already allocated nexthop struct, not including recurse info */
+extern void nexthop_copy_no_recurse(struct nexthop *copy,
+ const struct nexthop *nexthop,
+ struct nexthop *rparent);
+/* Duplicates a nexthop and returns the newly allocated nexthop */
+extern struct nexthop *nexthop_dup(const struct nexthop *nexthop,
+ struct nexthop *rparent);
+/* Duplicates a nexthop and returns the newly allocated nexthop */
+extern struct nexthop *nexthop_dup_no_recurse(const struct nexthop *nexthop,
+ struct nexthop *rparent);
+
+/*
+ * Parse one or more backup index values, as comma-separated numbers,
+ * into caller's array of uint8_ts. The array must be NEXTHOP_MAX_BACKUPS
+ * in size. Mails back the number of values converted, and returns 0 on
+ * success, <0 if an error in parsing.
+ */
+int nexthop_str2backups(const char *str, int *num_backups,
+ uint8_t *backups);
+
+#ifdef _FRR_ATTRIBUTE_PRINTFRR
+#pragma FRR printfrr_ext "%pNH" (struct nexthop *)
+#endif
+
+ssize_t printfrr_nhs(struct fbuf *buf, const struct nexthop *nh);
+#ifdef __cplusplus
+}
+#endif
+
+#endif /*_LIB_NEXTHOP_H */
diff --git a/lib/nexthop_group.c b/lib/nexthop_group.c
new file mode 100644
index 0000000..7284d6c
--- /dev/null
+++ b/lib/nexthop_group.c
@@ -0,0 +1,1332 @@
+/*
+ * Nexthop Group structure definition.
+ * Copyright (C) 2018 Cumulus Networks, Inc.
+ * Donald Sharp
+ *
+ * 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 <vrf.h>
+#include <sockunion.h>
+#include <nexthop.h>
+#include <nexthop_group.h>
+#include <nexthop_group_private.h>
+#include <vty.h>
+#include <command.h>
+#include <jhash.h>
+
+#ifndef VTYSH_EXTRACT_PL
+#include "lib/nexthop_group_clippy.c"
+#endif
+
+DEFINE_MTYPE_STATIC(LIB, NEXTHOP_GROUP, "Nexthop Group");
+
+/*
+ * Internal struct used to hold nhg config strings
+ */
+struct nexthop_hold {
+ char *nhvrf_name;
+ union sockunion *addr;
+ char *intf;
+ bool onlink;
+ char *labels;
+ uint32_t weight;
+ char *backup_str;
+};
+
+struct nexthop_group_hooks {
+ void (*new)(const char *name);
+ void (*add_nexthop)(const struct nexthop_group_cmd *nhg,
+ const struct nexthop *nhop);
+ void (*del_nexthop)(const struct nexthop_group_cmd *nhg,
+ const struct nexthop *nhop);
+ void (*delete)(const char *name);
+};
+
+static struct nexthop_group_hooks nhg_hooks;
+
+static inline int
+nexthop_group_cmd_compare(const struct nexthop_group_cmd *nhgc1,
+ const struct nexthop_group_cmd *nhgc2);
+RB_GENERATE(nhgc_entry_head, nexthop_group_cmd, nhgc_entry,
+ nexthop_group_cmd_compare)
+
+static struct nhgc_entry_head nhgc_entries;
+
+static inline int
+nexthop_group_cmd_compare(const struct nexthop_group_cmd *nhgc1,
+ const struct nexthop_group_cmd *nhgc2)
+{
+ return strcmp(nhgc1->name, nhgc2->name);
+}
+
+static struct nexthop *nexthop_group_tail(const struct nexthop_group *nhg)
+{
+ struct nexthop *nexthop = nhg->nexthop;
+
+ while (nexthop && nexthop->next)
+ nexthop = nexthop->next;
+
+ return nexthop;
+}
+
+uint8_t nexthop_group_nexthop_num(const struct nexthop_group *nhg)
+{
+ struct nexthop *nhop;
+ uint8_t num = 0;
+
+ for (ALL_NEXTHOPS_PTR(nhg, nhop))
+ num++;
+
+ return num;
+}
+
+uint8_t nexthop_group_nexthop_num_no_recurse(const struct nexthop_group *nhg)
+{
+ struct nexthop *nhop;
+ uint8_t num = 0;
+
+ for (nhop = nhg->nexthop; nhop; nhop = nhop->next)
+ num++;
+
+ return num;
+}
+
+uint8_t nexthop_group_active_nexthop_num(const struct nexthop_group *nhg)
+{
+ struct nexthop *nhop;
+ uint8_t num = 0;
+
+ for (ALL_NEXTHOPS_PTR(nhg, nhop)) {
+ if (CHECK_FLAG(nhop->flags, NEXTHOP_FLAG_ACTIVE))
+ num++;
+ }
+
+ return num;
+}
+
+uint8_t
+nexthop_group_active_nexthop_num_no_recurse(const struct nexthop_group *nhg)
+{
+ struct nexthop *nhop;
+ uint8_t num = 0;
+
+ for (nhop = nhg->nexthop; nhop; nhop = nhop->next) {
+ if (CHECK_FLAG(nhop->flags, NEXTHOP_FLAG_ACTIVE))
+ num++;
+ }
+
+ return num;
+}
+
+struct nexthop *nexthop_exists(const struct nexthop_group *nhg,
+ const struct nexthop *nh)
+{
+ struct nexthop *nexthop;
+
+ for (nexthop = nhg->nexthop; nexthop; nexthop = nexthop->next) {
+ if (nexthop_same(nh, nexthop))
+ return nexthop;
+ }
+
+ return NULL;
+}
+
+/*
+ * Helper that locates a nexthop in an nhg config list. Note that
+ * this uses a specific matching / equality rule that's different from
+ * the complete match performed by 'nexthop_same()'.
+ */
+static struct nexthop *nhg_nh_find(const struct nexthop_group *nhg,
+ const struct nexthop *nh)
+{
+ struct nexthop *nexthop;
+ int ret;
+
+ /* We compare: vrf, gateway, and interface */
+
+ for (nexthop = nhg->nexthop; nexthop; nexthop = nexthop->next) {
+
+ /* Compare vrf and type */
+ if (nexthop->vrf_id != nh->vrf_id)
+ continue;
+ if (nexthop->type != nh->type)
+ continue;
+
+ /* Compare gateway */
+ switch (nexthop->type) {
+ case NEXTHOP_TYPE_IPV4:
+ case NEXTHOP_TYPE_IPV6:
+ ret = nexthop_g_addr_cmp(nexthop->type,
+ &nexthop->gate, &nh->gate);
+ if (ret != 0)
+ continue;
+ break;
+ case NEXTHOP_TYPE_IPV4_IFINDEX:
+ case NEXTHOP_TYPE_IPV6_IFINDEX:
+ ret = nexthop_g_addr_cmp(nexthop->type,
+ &nexthop->gate, &nh->gate);
+ if (ret != 0)
+ continue;
+ /* Intentional Fall-Through */
+ case NEXTHOP_TYPE_IFINDEX:
+ if (nexthop->ifindex != nh->ifindex)
+ continue;
+ break;
+ case NEXTHOP_TYPE_BLACKHOLE:
+ if (nexthop->bh_type != nh->bh_type)
+ continue;
+ break;
+ }
+
+ return nexthop;
+ }
+
+ return NULL;
+}
+
+static bool
+nexthop_group_equal_common(const struct nexthop_group *nhg1,
+ const struct nexthop_group *nhg2,
+ uint8_t (*nexthop_group_nexthop_num_func)(
+ const struct nexthop_group *nhg))
+{
+ if (nhg1 && !nhg2)
+ return false;
+
+ if (!nhg1 && nhg2)
+ return false;
+
+ if (nhg1 == nhg2)
+ return true;
+
+ if (nexthop_group_nexthop_num_func(nhg1)
+ != nexthop_group_nexthop_num_func(nhg2))
+ return false;
+
+ return true;
+}
+
+/* This assumes ordered */
+bool nexthop_group_equal_no_recurse(const struct nexthop_group *nhg1,
+ const struct nexthop_group *nhg2)
+{
+ struct nexthop *nh1 = NULL;
+ struct nexthop *nh2 = NULL;
+
+ if (!nexthop_group_equal_common(nhg1, nhg2,
+ &nexthop_group_nexthop_num_no_recurse))
+ return false;
+
+ for (nh1 = nhg1->nexthop, nh2 = nhg2->nexthop; nh1 || nh2;
+ nh1 = nh1->next, nh2 = nh2->next) {
+ if (nh1 && !nh2)
+ return false;
+ if (!nh1 && nh2)
+ return false;
+ if (!nexthop_same(nh1, nh2))
+ return false;
+ }
+
+ return true;
+}
+
+/* This assumes ordered */
+bool nexthop_group_equal(const struct nexthop_group *nhg1,
+ const struct nexthop_group *nhg2)
+{
+ struct nexthop *nh1 = NULL;
+ struct nexthop *nh2 = NULL;
+
+ if (!nexthop_group_equal_common(nhg1, nhg2, &nexthop_group_nexthop_num))
+ return false;
+
+ for (nh1 = nhg1->nexthop, nh2 = nhg2->nexthop; nh1 || nh2;
+ nh1 = nexthop_next(nh1), nh2 = nexthop_next(nh2)) {
+ if (nh1 && !nh2)
+ return false;
+ if (!nh1 && nh2)
+ return false;
+ if (!nexthop_same(nh1, nh2))
+ return false;
+ }
+
+ return true;
+}
+struct nexthop_group *nexthop_group_new(void)
+{
+ return XCALLOC(MTYPE_NEXTHOP_GROUP, sizeof(struct nexthop_group));
+}
+
+void nexthop_group_copy(struct nexthop_group *to,
+ const struct nexthop_group *from)
+{
+ /* Copy everything, including recursive info */
+ copy_nexthops(&to->nexthop, from->nexthop, NULL);
+}
+
+void nexthop_group_delete(struct nexthop_group **nhg)
+{
+ /* OK to call with NULL group */
+ if ((*nhg) == NULL)
+ return;
+
+ if ((*nhg)->nexthop)
+ nexthops_free((*nhg)->nexthop);
+
+ XFREE(MTYPE_NEXTHOP_GROUP, *nhg);
+}
+
+/* Add nexthop to the end of a nexthop list. */
+void _nexthop_add(struct nexthop **target, struct nexthop *nexthop)
+{
+ struct nexthop *last;
+
+ for (last = *target; last && last->next; last = last->next)
+ ;
+ if (last)
+ last->next = nexthop;
+ else
+ *target = nexthop;
+ nexthop->prev = last;
+}
+
+/* Add nexthop to sorted list of nexthops */
+static void _nexthop_add_sorted(struct nexthop **head,
+ struct nexthop *nexthop)
+{
+ struct nexthop *position, *prev;
+
+ assert(!nexthop->next);
+
+ for (position = *head, prev = NULL; position;
+ prev = position, position = position->next) {
+ if (nexthop_cmp(position, nexthop) > 0) {
+ nexthop->next = position;
+ nexthop->prev = prev;
+
+ if (nexthop->prev)
+ nexthop->prev->next = nexthop;
+ else
+ *head = nexthop;
+
+ position->prev = nexthop;
+ return;
+ }
+ }
+
+ nexthop->prev = prev;
+ if (prev)
+ prev->next = nexthop;
+ else
+ *head = nexthop;
+}
+
+void nexthop_group_add_sorted(struct nexthop_group *nhg,
+ struct nexthop *nexthop)
+{
+ struct nexthop *tail;
+
+ assert(!nexthop->next);
+
+ /* Try to just append to the end first;
+ * trust the list is already sorted
+ */
+ tail = nexthop_group_tail(nhg);
+
+ if (tail && (nexthop_cmp(tail, nexthop) < 0)) {
+ tail->next = nexthop;
+ nexthop->prev = tail;
+
+ return;
+ }
+
+ _nexthop_add_sorted(&nhg->nexthop, nexthop);
+}
+
+/* Delete nexthop from a nexthop list. */
+void _nexthop_del(struct nexthop_group *nhg, struct nexthop *nh)
+{
+ struct nexthop *nexthop;
+
+ for (nexthop = nhg->nexthop; nexthop; nexthop = nexthop->next) {
+ if (nexthop_same(nh, nexthop))
+ break;
+ }
+
+ assert(nexthop);
+
+ if (nexthop->prev)
+ nexthop->prev->next = nexthop->next;
+ else
+ nhg->nexthop = nexthop->next;
+
+ if (nexthop->next)
+ nexthop->next->prev = nexthop->prev;
+
+ nh->prev = NULL;
+ nh->next = NULL;
+}
+
+/* Unlink a nexthop from the list it's on, unconditionally */
+static void nexthop_unlink(struct nexthop_group *nhg, struct nexthop *nexthop)
+{
+
+ if (nexthop->prev)
+ nexthop->prev->next = nexthop->next;
+ else {
+ assert(nhg->nexthop == nexthop);
+ assert(nexthop->prev == NULL);
+ nhg->nexthop = nexthop->next;
+ }
+
+ if (nexthop->next)
+ nexthop->next->prev = nexthop->prev;
+
+ nexthop->prev = NULL;
+ nexthop->next = NULL;
+}
+
+/*
+ * Copy a list of nexthops in 'nh' to an nhg, enforcing canonical sort order
+ */
+void nexthop_group_copy_nh_sorted(struct nexthop_group *nhg,
+ const struct nexthop *nh)
+{
+ struct nexthop *nexthop, *tail;
+ const struct nexthop *nh1;
+
+ /* We'll try to append to the end of the new list;
+ * if the original list in nh is already sorted, this eliminates
+ * lots of comparison operations.
+ */
+ tail = nexthop_group_tail(nhg);
+
+ for (nh1 = nh; nh1; nh1 = nh1->next) {
+ nexthop = nexthop_dup(nh1, NULL);
+
+ if (tail && (nexthop_cmp(tail, nexthop) < 0)) {
+ tail->next = nexthop;
+ nexthop->prev = tail;
+
+ tail = nexthop;
+ continue;
+ }
+
+ _nexthop_add_sorted(&nhg->nexthop, nexthop);
+
+ if (tail == NULL)
+ tail = nexthop;
+ }
+}
+
+/* Copy a list of nexthops, no effort made to sort or order them. */
+void copy_nexthops(struct nexthop **tnh, const struct nexthop *nh,
+ struct nexthop *rparent)
+{
+ struct nexthop *nexthop;
+ const struct nexthop *nh1;
+
+ for (nh1 = nh; nh1; nh1 = nh1->next) {
+ nexthop = nexthop_dup(nh1, rparent);
+ _nexthop_add(tnh, nexthop);
+ }
+}
+
+uint32_t nexthop_group_hash_no_recurse(const struct nexthop_group *nhg)
+{
+ struct nexthop *nh;
+ uint32_t key = 0;
+
+ /*
+ * We are not interested in hashing over any recursively
+ * resolved nexthops
+ */
+ for (nh = nhg->nexthop; nh; nh = nh->next)
+ key = jhash_1word(nexthop_hash(nh), key);
+
+ return key;
+}
+
+uint32_t nexthop_group_hash(const struct nexthop_group *nhg)
+{
+ struct nexthop *nh;
+ uint32_t key = 0;
+
+ for (ALL_NEXTHOPS_PTR(nhg, nh))
+ key = jhash_1word(nexthop_hash(nh), key);
+
+ return key;
+}
+
+void nexthop_group_mark_duplicates(struct nexthop_group *nhg)
+{
+ struct nexthop *nexthop, *prev;
+
+ for (ALL_NEXTHOPS_PTR(nhg, nexthop)) {
+ UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_DUPLICATE);
+ for (ALL_NEXTHOPS_PTR(nhg, prev)) {
+ if (prev == nexthop)
+ break;
+ if (nexthop_same(nexthop, prev)) {
+ SET_FLAG(nexthop->flags,
+ NEXTHOP_FLAG_DUPLICATE);
+ break;
+ }
+ }
+ }
+}
+
+static void nhgc_delete_nexthops(struct nexthop_group_cmd *nhgc)
+{
+ struct nexthop *nexthop;
+
+ nexthop = nhgc->nhg.nexthop;
+ while (nexthop) {
+ struct nexthop *next = nexthop_next(nexthop);
+
+ _nexthop_del(&nhgc->nhg, nexthop);
+ if (nhg_hooks.del_nexthop)
+ nhg_hooks.del_nexthop(nhgc, nexthop);
+
+ nexthop_free(nexthop);
+
+ nexthop = next;
+ }
+}
+
+struct nexthop_group_cmd *nhgc_find(const char *name)
+{
+ struct nexthop_group_cmd find;
+
+ strlcpy(find.name, name, sizeof(find.name));
+
+ return RB_FIND(nhgc_entry_head, &nhgc_entries, &find);
+}
+
+static int nhgc_cmp_helper(const char *a, const char *b)
+{
+ if (!a && !b)
+ return 0;
+
+ if (a && !b)
+ return -1;
+
+ if (!a && b)
+ return 1;
+
+ return strcmp(a, b);
+}
+
+static int nhgc_addr_cmp_helper(const union sockunion *a, const union sockunion *b)
+{
+ if (!a && !b)
+ return 0;
+
+ if (a && !b)
+ return -1;
+
+ if (!a && b)
+ return 1;
+
+ return sockunion_cmp(a, b);
+}
+
+static int nhgl_cmp(struct nexthop_hold *nh1, struct nexthop_hold *nh2)
+{
+ int ret;
+
+ ret = nhgc_addr_cmp_helper(nh1->addr, nh2->addr);
+ if (ret)
+ return ret;
+
+ ret = nhgc_cmp_helper(nh1->intf, nh2->intf);
+ if (ret)
+ return ret;
+
+ ret = nhgc_cmp_helper(nh1->nhvrf_name, nh2->nhvrf_name);
+ if (ret)
+ return ret;
+
+ ret = ((int)nh2->onlink) - ((int)nh1->onlink);
+ if (ret)
+ return ret;
+
+ return nhgc_cmp_helper(nh1->labels, nh2->labels);
+}
+
+static void nhgl_delete(struct nexthop_hold *nh)
+{
+ XFREE(MTYPE_TMP, nh->intf);
+
+ XFREE(MTYPE_TMP, nh->nhvrf_name);
+
+ if (nh->addr)
+ sockunion_free(nh->addr);
+
+ XFREE(MTYPE_TMP, nh->labels);
+
+ XFREE(MTYPE_TMP, nh);
+}
+
+static struct nexthop_group_cmd *nhgc_get(const char *name)
+{
+ struct nexthop_group_cmd *nhgc;
+
+ nhgc = nhgc_find(name);
+ if (!nhgc) {
+ nhgc = XCALLOC(MTYPE_TMP, sizeof(*nhgc));
+ strlcpy(nhgc->name, name, sizeof(nhgc->name));
+
+ QOBJ_REG(nhgc, nexthop_group_cmd);
+ RB_INSERT(nhgc_entry_head, &nhgc_entries, nhgc);
+
+ nhgc->nhg_list = list_new();
+ nhgc->nhg_list->cmp = (int (*)(void *, void *))nhgl_cmp;
+ nhgc->nhg_list->del = (void (*)(void *))nhgl_delete;
+
+ if (nhg_hooks.new)
+ nhg_hooks.new(name);
+ }
+
+ return nhgc;
+}
+
+static void nhgc_delete(struct nexthop_group_cmd *nhgc)
+{
+ nhgc_delete_nexthops(nhgc);
+
+ if (nhg_hooks.delete)
+ nhg_hooks.delete(nhgc->name);
+
+ RB_REMOVE(nhgc_entry_head, &nhgc_entries, nhgc);
+
+ list_delete(&nhgc->nhg_list);
+
+ QOBJ_UNREG(nhgc);
+ XFREE(MTYPE_TMP, nhgc);
+}
+
+DEFINE_QOBJ_TYPE(nexthop_group_cmd);
+
+DEFUN_NOSH(nexthop_group, nexthop_group_cmd, "nexthop-group NHGNAME",
+ "Enter into the nexthop-group submode\n"
+ "Specify the NAME of the nexthop-group\n")
+{
+ const char *nhg_name = argv[1]->arg;
+ struct nexthop_group_cmd *nhgc = NULL;
+
+ nhgc = nhgc_get(nhg_name);
+ VTY_PUSH_CONTEXT(NH_GROUP_NODE, nhgc);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_NOSH(no_nexthop_group, no_nexthop_group_cmd, "no nexthop-group NHGNAME",
+ NO_STR
+ "Delete the nexthop-group\n"
+ "Specify the NAME of the nexthop-group\n")
+{
+ const char *nhg_name = argv[2]->arg;
+ struct nexthop_group_cmd *nhgc = NULL;
+
+ nhgc = nhgc_find(nhg_name);
+ if (nhgc)
+ nhgc_delete(nhgc);
+
+ return CMD_SUCCESS;
+}
+
+DEFPY(nexthop_group_backup, nexthop_group_backup_cmd,
+ "backup-group WORD$name",
+ "Specify a group name containing backup nexthops\n"
+ "The name of the backup group\n")
+{
+ VTY_DECLVAR_CONTEXT(nexthop_group_cmd, nhgc);
+
+ strlcpy(nhgc->backup_list_name, name, sizeof(nhgc->backup_list_name));
+
+ return CMD_SUCCESS;
+}
+
+DEFPY(no_nexthop_group_backup, no_nexthop_group_backup_cmd,
+ "no backup-group [WORD$name]",
+ NO_STR
+ "Clear group name containing backup nexthops\n"
+ "The name of the backup group\n")
+{
+ VTY_DECLVAR_CONTEXT(nexthop_group_cmd, nhgc);
+
+ nhgc->backup_list_name[0] = 0;
+
+ return CMD_SUCCESS;
+}
+
+static void nexthop_group_save_nhop(struct nexthop_group_cmd *nhgc,
+ const char *nhvrf_name,
+ const union sockunion *addr,
+ const char *intf, bool onlink,
+ const char *labels, const uint32_t weight,
+ const char *backup_str)
+{
+ struct nexthop_hold *nh;
+
+ nh = XCALLOC(MTYPE_TMP, sizeof(*nh));
+
+ if (nhvrf_name)
+ nh->nhvrf_name = XSTRDUP(MTYPE_TMP, nhvrf_name);
+ if (intf)
+ nh->intf = XSTRDUP(MTYPE_TMP, intf);
+ if (addr)
+ nh->addr = sockunion_dup(addr);
+ if (labels)
+ nh->labels = XSTRDUP(MTYPE_TMP, labels);
+
+ nh->onlink = onlink;
+
+ nh->weight = weight;
+
+ if (backup_str)
+ nh->backup_str = XSTRDUP(MTYPE_TMP, backup_str);
+
+ listnode_add_sort(nhgc->nhg_list, nh);
+}
+
+/*
+ * Remove config info about a nexthop from group 'nhgc'. Note that we
+ * use only a subset of the available attributes here to determine
+ * a 'match'.
+ * Note that this doesn't change the list of nexthops, only the config
+ * information.
+ */
+static void nexthop_group_unsave_nhop(struct nexthop_group_cmd *nhgc,
+ const char *nhvrf_name,
+ const union sockunion *addr,
+ const char *intf)
+{
+ struct nexthop_hold *nh;
+ struct listnode *node;
+
+ for (ALL_LIST_ELEMENTS_RO(nhgc->nhg_list, node, nh)) {
+ if (nhgc_cmp_helper(nhvrf_name, nh->nhvrf_name) == 0
+ && nhgc_addr_cmp_helper(addr, nh->addr) == 0
+ && nhgc_cmp_helper(intf, nh->intf) == 0)
+ break;
+ }
+
+ /*
+ * Something has gone seriously wrong, fail gracefully
+ */
+ if (!nh)
+ return;
+
+ list_delete_node(nhgc->nhg_list, node);
+ nhgl_delete(nh);
+}
+
+/*
+ * Parse the config strings we support for a single nexthop. This gets used
+ * in a couple of different ways, and we distinguish between transient
+ * failures - such as a still-unprocessed interface - and fatal errors
+ * from label-string parsing.
+ */
+static bool nexthop_group_parse_nexthop(struct nexthop *nhop,
+ const union sockunion *addr,
+ const char *intf, bool onlink,
+ const char *name, const char *labels,
+ int *lbl_ret, uint32_t weight,
+ const char *backup_str)
+{
+ int ret = 0;
+ struct vrf *vrf;
+ int num;
+
+ memset(nhop, 0, sizeof(*nhop));
+
+ if (name)
+ vrf = vrf_lookup_by_name(name);
+ else
+ vrf = vrf_lookup_by_id(VRF_DEFAULT);
+
+ if (!vrf)
+ return false;
+
+ nhop->vrf_id = vrf->vrf_id;
+
+ if (intf) {
+ nhop->ifindex = ifname2ifindex(intf, vrf->vrf_id);
+ if (nhop->ifindex == IFINDEX_INTERNAL)
+ return false;
+ }
+
+ if (onlink)
+ SET_FLAG(nhop->flags, NEXTHOP_FLAG_ONLINK);
+
+ if (addr) {
+ if (addr->sa.sa_family == AF_INET) {
+ nhop->gate.ipv4.s_addr = addr->sin.sin_addr.s_addr;
+ if (intf)
+ nhop->type = NEXTHOP_TYPE_IPV4_IFINDEX;
+ else
+ nhop->type = NEXTHOP_TYPE_IPV4;
+ } else {
+ nhop->gate.ipv6 = addr->sin6.sin6_addr;
+ if (intf)
+ nhop->type = NEXTHOP_TYPE_IPV6_IFINDEX;
+ else
+ nhop->type = NEXTHOP_TYPE_IPV6;
+ }
+ } else
+ nhop->type = NEXTHOP_TYPE_IFINDEX;
+
+ if (labels) {
+ uint8_t num = 0;
+ mpls_label_t larray[MPLS_MAX_LABELS];
+
+ ret = mpls_str2label(labels, &num, larray);
+
+ /* Return label parse result */
+ if (lbl_ret)
+ *lbl_ret = ret;
+
+ if (ret < 0)
+ return false;
+ else if (num > 0)
+ nexthop_add_labels(nhop, ZEBRA_LSP_NONE,
+ num, larray);
+ }
+
+ nhop->weight = weight;
+
+ if (backup_str) {
+ /* Parse backup indexes */
+ ret = nexthop_str2backups(backup_str,
+ &num, nhop->backup_idx);
+ if (ret == 0) {
+ SET_FLAG(nhop->flags, NEXTHOP_FLAG_HAS_BACKUP);
+ nhop->backup_num = num;
+ } else
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Wrapper to parse the strings in a 'nexthop_hold'
+ */
+static bool nexthop_group_parse_nhh(struct nexthop *nhop,
+ const struct nexthop_hold *nhh)
+{
+ return (nexthop_group_parse_nexthop(
+ nhop, nhh->addr, nhh->intf, nhh->onlink, nhh->nhvrf_name,
+ nhh->labels, NULL, nhh->weight, nhh->backup_str));
+}
+
+DEFPY(ecmp_nexthops, ecmp_nexthops_cmd,
+ "[no] nexthop\
+ <\
+ <A.B.C.D|X:X::X:X>$addr [INTERFACE$intf [onlink$onlink]]\
+ |INTERFACE$intf\
+ >\
+ [{ \
+ nexthop-vrf NAME$vrf_name \
+ |label WORD \
+ |weight (1-255) \
+ |backup-idx WORD \
+ }]",
+ NO_STR
+ "Specify one of the nexthops in this ECMP group\n"
+ "v4 Address\n"
+ "v6 Address\n"
+ "Interface to use\n"
+ "Treat nexthop as directly attached to the interface\n"
+ "Interface to use\n"
+ "If the nexthop is in a different vrf tell us\n"
+ "The nexthop-vrf Name\n"
+ "Specify label(s) for this nexthop\n"
+ "One or more labels in the range (16-1048575) separated by '/'\n"
+ "Weight to be used by the nexthop for purposes of ECMP\n"
+ "Weight value to be used\n"
+ "Specify backup nexthop indexes in another group\n"
+ "One or more indexes in the range (0-254) separated by ','\n")
+{
+ VTY_DECLVAR_CONTEXT(nexthop_group_cmd, nhgc);
+ struct nexthop nhop;
+ struct nexthop *nh;
+ int lbl_ret = 0;
+ bool legal;
+ int num;
+ uint8_t backups[NEXTHOP_MAX_BACKUPS];
+ bool yes = !no;
+
+ /* Pre-parse backup string to validate */
+ if (backup_idx) {
+ lbl_ret = nexthop_str2backups(backup_idx, &num, backups);
+ if (lbl_ret < 0) {
+ vty_out(vty, "%% Invalid backups\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+ }
+
+ legal = nexthop_group_parse_nexthop(&nhop, addr, intf, !!onlink,
+ vrf_name, label, &lbl_ret, weight,
+ backup_idx);
+
+ if (nhop.type == NEXTHOP_TYPE_IPV6
+ && IN6_IS_ADDR_LINKLOCAL(&nhop.gate.ipv6)) {
+ vty_out(vty,
+ "Specified a v6 LL with no interface, rejecting\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ /* Handle label-string errors */
+ if (!legal && lbl_ret < 0) {
+ switch (lbl_ret) {
+ case -1:
+ vty_out(vty, "%% Malformed label(s)\n");
+ break;
+ case -2:
+ vty_out(vty,
+ "%% Cannot use reserved label(s) (%d-%d)\n",
+ MPLS_LABEL_RESERVED_MIN,
+ MPLS_LABEL_RESERVED_MAX);
+ break;
+ case -3:
+ vty_out(vty,
+ "%% Too many labels. Enter %d or fewer\n",
+ MPLS_MAX_LABELS);
+ break;
+ }
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ /* Look for an existing nexthop in the config. Note that the test
+ * here tests only some attributes - it's not a complete comparison.
+ * Note that we've got two kinds of objects to manage: 'nexthop_hold'
+ * that represent config that may or may not be valid (yet), and
+ * actual nexthops that have been validated and parsed.
+ */
+ nh = nhg_nh_find(&nhgc->nhg, &nhop);
+
+ /* Always attempt to remove old config info. */
+ nexthop_group_unsave_nhop(nhgc, vrf_name, addr, intf);
+
+ /* Remove any existing nexthop, for delete and replace cases. */
+ if (nh) {
+ nexthop_unlink(&nhgc->nhg, nh);
+
+ if (nhg_hooks.del_nexthop)
+ nhg_hooks.del_nexthop(nhgc, nh);
+
+ nexthop_free(nh);
+ }
+ if (yes) {
+ /* Add/replace case: capture nexthop if valid, and capture
+ * config info always.
+ */
+ if (legal) {
+ nh = nexthop_new();
+
+ memcpy(nh, &nhop, sizeof(nhop));
+ _nexthop_add(&nhgc->nhg.nexthop, nh);
+ }
+
+ /* Save config always */
+ nexthop_group_save_nhop(nhgc, vrf_name, addr, intf, !!onlink,
+ label, weight, backup_idx);
+
+ if (legal && nhg_hooks.add_nexthop)
+ nhg_hooks.add_nexthop(nhgc, nh);
+ }
+
+ return CMD_SUCCESS;
+}
+
+static int nexthop_group_write(struct vty *vty);
+static struct cmd_node nexthop_group_node = {
+ .name = "nexthop-group",
+ .node = NH_GROUP_NODE,
+ .parent_node = CONFIG_NODE,
+ .prompt = "%s(config-nh-group)# ",
+ .config_write = nexthop_group_write,
+};
+
+void nexthop_group_write_nexthop_simple(struct vty *vty,
+ const struct nexthop *nh,
+ char *altifname)
+{
+ char *ifname;
+
+ vty_out(vty, "nexthop ");
+
+ if (altifname)
+ ifname = altifname;
+ else
+ ifname = (char *)ifindex2ifname(nh->ifindex, nh->vrf_id);
+
+ switch (nh->type) {
+ case NEXTHOP_TYPE_IFINDEX:
+ vty_out(vty, "%s", ifname);
+ break;
+ case NEXTHOP_TYPE_IPV4:
+ vty_out(vty, "%pI4", &nh->gate.ipv4);
+ break;
+ case NEXTHOP_TYPE_IPV4_IFINDEX:
+ vty_out(vty, "%pI4 %s", &nh->gate.ipv4, ifname);
+ break;
+ case NEXTHOP_TYPE_IPV6:
+ vty_out(vty, "%pI6", &nh->gate.ipv6);
+ break;
+ case NEXTHOP_TYPE_IPV6_IFINDEX:
+ vty_out(vty, "%pI6 %s", &nh->gate.ipv6, ifname);
+ break;
+ case NEXTHOP_TYPE_BLACKHOLE:
+ break;
+ }
+}
+
+void nexthop_group_write_nexthop(struct vty *vty, const struct nexthop *nh)
+{
+ struct vrf *vrf;
+ int i;
+
+ nexthop_group_write_nexthop_simple(vty, nh, NULL);
+
+ if (nh->vrf_id != VRF_DEFAULT) {
+ vrf = vrf_lookup_by_id(nh->vrf_id);
+ vty_out(vty, " nexthop-vrf %s", VRF_LOGNAME(vrf));
+ }
+
+ if (nh->nh_label && nh->nh_label->num_labels > 0) {
+ char buf[200];
+
+ mpls_label2str(nh->nh_label->num_labels,
+ nh->nh_label->label,
+ buf, sizeof(buf), 0);
+ vty_out(vty, " label %s", buf);
+ }
+
+ if (nh->weight)
+ vty_out(vty, " weight %u", nh->weight);
+
+ if (CHECK_FLAG(nh->flags, NEXTHOP_FLAG_HAS_BACKUP)) {
+ vty_out(vty, " backup-idx %d", nh->backup_idx[0]);
+
+ for (i = 1; i < nh->backup_num; i++)
+ vty_out(vty, ",%d", nh->backup_idx[i]);
+ }
+
+ vty_out(vty, "\n");
+}
+
+void nexthop_group_json_nexthop(json_object *j, const struct nexthop *nh)
+{
+ struct vrf *vrf;
+ json_object *json_backups = NULL;
+ int i;
+
+ switch (nh->type) {
+ case NEXTHOP_TYPE_IFINDEX:
+ json_object_string_add(j, "nexthop",
+ ifindex2ifname(nh->ifindex, nh->vrf_id));
+ break;
+ case NEXTHOP_TYPE_IPV4:
+ json_object_string_addf(j, "nexthop", "%pI4", &nh->gate.ipv4);
+ break;
+ case NEXTHOP_TYPE_IPV4_IFINDEX:
+ json_object_string_addf(j, "nexthop", "%pI4", &nh->gate.ipv4);
+ json_object_string_add(j, "vrfId",
+ ifindex2ifname(nh->ifindex, nh->vrf_id));
+ break;
+ case NEXTHOP_TYPE_IPV6:
+ json_object_string_addf(j, "nexthop", "%pI6", &nh->gate.ipv6);
+ break;
+ case NEXTHOP_TYPE_IPV6_IFINDEX:
+ json_object_string_addf(j, "nexthop", "%pI6", &nh->gate.ipv6);
+ json_object_string_add(j, "vrfId",
+ ifindex2ifname(nh->ifindex, nh->vrf_id));
+ break;
+ case NEXTHOP_TYPE_BLACKHOLE:
+ break;
+ }
+
+ if (nh->vrf_id != VRF_DEFAULT) {
+ vrf = vrf_lookup_by_id(nh->vrf_id);
+ json_object_string_add(j, "targetVrf", vrf->name);
+ }
+
+ if (nh->nh_label && nh->nh_label->num_labels > 0) {
+ char buf[200];
+
+ mpls_label2str(nh->nh_label->num_labels, nh->nh_label->label,
+ buf, sizeof(buf), 0);
+ json_object_string_add(j, "label", buf);
+ }
+
+ if (nh->weight)
+ json_object_int_add(j, "weight", nh->weight);
+
+ if (CHECK_FLAG(nh->flags, NEXTHOP_FLAG_HAS_BACKUP)) {
+ json_backups = json_object_new_array();
+ for (i = 0; i < nh->backup_num; i++)
+ json_object_array_add(
+ json_backups,
+ json_object_new_int(nh->backup_idx[i]));
+
+ json_object_object_add(j, "backupIdx", json_backups);
+ }
+}
+
+static void nexthop_group_write_nexthop_internal(struct vty *vty,
+ const struct nexthop_hold *nh)
+{
+ vty_out(vty, "nexthop");
+
+ if (nh->addr)
+ vty_out(vty, " %pSU", nh->addr);
+
+ if (nh->intf)
+ vty_out(vty, " %s", nh->intf);
+
+ if (nh->onlink)
+ vty_out(vty, " onlink");
+
+ if (nh->nhvrf_name)
+ vty_out(vty, " nexthop-vrf %s", nh->nhvrf_name);
+
+ if (nh->labels)
+ vty_out(vty, " label %s", nh->labels);
+
+ if (nh->weight)
+ vty_out(vty, " weight %u", nh->weight);
+
+ if (nh->backup_str)
+ vty_out(vty, " backup-idx %s", nh->backup_str);
+
+ vty_out(vty, "\n");
+}
+
+static int nexthop_group_write(struct vty *vty)
+{
+ struct nexthop_group_cmd *nhgc;
+ struct nexthop_hold *nh;
+
+ RB_FOREACH (nhgc, nhgc_entry_head, &nhgc_entries) {
+ struct listnode *node;
+
+ vty_out(vty, "nexthop-group %s\n", nhgc->name);
+
+ if (nhgc->backup_list_name[0])
+ vty_out(vty, " backup-group %s\n",
+ nhgc->backup_list_name);
+
+ for (ALL_LIST_ELEMENTS_RO(nhgc->nhg_list, node, nh)) {
+ vty_out(vty, " ");
+ nexthop_group_write_nexthop_internal(vty, nh);
+ }
+
+ vty_out(vty, "exit\n");
+ vty_out(vty, "!\n");
+ }
+
+ return 1;
+}
+
+void nexthop_group_enable_vrf(struct vrf *vrf)
+{
+ struct nexthop_group_cmd *nhgc;
+ struct nexthop_hold *nhh;
+
+ RB_FOREACH (nhgc, nhgc_entry_head, &nhgc_entries) {
+ struct listnode *node;
+
+ for (ALL_LIST_ELEMENTS_RO(nhgc->nhg_list, node, nhh)) {
+ struct nexthop nhop;
+ struct nexthop *nh;
+
+ if (!nexthop_group_parse_nhh(&nhop, nhh))
+ continue;
+
+ nh = nexthop_exists(&nhgc->nhg, &nhop);
+
+ if (nh)
+ continue;
+
+ if (nhop.vrf_id != vrf->vrf_id)
+ continue;
+
+ nh = nexthop_new();
+
+ memcpy(nh, &nhop, sizeof(nhop));
+ _nexthop_add(&nhgc->nhg.nexthop, nh);
+
+ if (nhg_hooks.add_nexthop)
+ nhg_hooks.add_nexthop(nhgc, nh);
+ }
+ }
+}
+
+void nexthop_group_disable_vrf(struct vrf *vrf)
+{
+ struct nexthop_group_cmd *nhgc;
+ struct nexthop_hold *nhh;
+
+ RB_FOREACH (nhgc, nhgc_entry_head, &nhgc_entries) {
+ struct listnode *node;
+
+ for (ALL_LIST_ELEMENTS_RO(nhgc->nhg_list, node, nhh)) {
+ struct nexthop nhop;
+ struct nexthop *nh;
+
+ if (!nexthop_group_parse_nhh(&nhop, nhh))
+ continue;
+
+ nh = nexthop_exists(&nhgc->nhg, &nhop);
+
+ if (!nh)
+ continue;
+
+ if (nh->vrf_id != vrf->vrf_id)
+ continue;
+
+ _nexthop_del(&nhgc->nhg, nh);
+
+ if (nhg_hooks.del_nexthop)
+ nhg_hooks.del_nexthop(nhgc, nh);
+
+ nexthop_free(nh);
+ }
+ }
+}
+
+void nexthop_group_interface_state_change(struct interface *ifp,
+ ifindex_t oldifindex)
+{
+ struct nexthop_group_cmd *nhgc;
+ struct nexthop_hold *nhh;
+
+ RB_FOREACH (nhgc, nhgc_entry_head, &nhgc_entries) {
+ struct listnode *node;
+ struct nexthop *nh;
+
+ if (if_is_up(ifp)) {
+ for (ALL_LIST_ELEMENTS_RO(nhgc->nhg_list, node, nhh)) {
+ struct nexthop nhop;
+
+ if (!nexthop_group_parse_nhh(&nhop, nhh))
+ continue;
+
+ switch (nhop.type) {
+ case NEXTHOP_TYPE_IPV4:
+ case NEXTHOP_TYPE_IPV6:
+ case NEXTHOP_TYPE_BLACKHOLE:
+ continue;
+ case NEXTHOP_TYPE_IFINDEX:
+ case NEXTHOP_TYPE_IPV4_IFINDEX:
+ case NEXTHOP_TYPE_IPV6_IFINDEX:
+ break;
+ }
+ nh = nexthop_exists(&nhgc->nhg, &nhop);
+
+ if (nh)
+ continue;
+
+ if (ifp->ifindex != nhop.ifindex)
+ continue;
+
+ nh = nexthop_new();
+
+ memcpy(nh, &nhop, sizeof(nhop));
+ _nexthop_add(&nhgc->nhg.nexthop, nh);
+
+ if (nhg_hooks.add_nexthop)
+ nhg_hooks.add_nexthop(nhgc, nh);
+ }
+ } else {
+ struct nexthop *next_nh;
+
+ for (nh = nhgc->nhg.nexthop; nh; nh = next_nh) {
+ next_nh = nh->next;
+ switch (nh->type) {
+ case NEXTHOP_TYPE_IPV4:
+ case NEXTHOP_TYPE_IPV6:
+ case NEXTHOP_TYPE_BLACKHOLE:
+ continue;
+ case NEXTHOP_TYPE_IFINDEX:
+ case NEXTHOP_TYPE_IPV4_IFINDEX:
+ case NEXTHOP_TYPE_IPV6_IFINDEX:
+ break;
+ }
+
+ if (oldifindex != nh->ifindex)
+ continue;
+
+ _nexthop_del(&nhgc->nhg, nh);
+
+ if (nhg_hooks.del_nexthop)
+ nhg_hooks.del_nexthop(nhgc, nh);
+
+ nexthop_free(nh);
+ }
+ }
+ }
+}
+
+static void nhg_name_autocomplete(vector comps, struct cmd_token *token)
+{
+ struct nexthop_group_cmd *nhgc;
+
+ RB_FOREACH (nhgc, nhgc_entry_head, &nhgc_entries) {
+ vector_set(comps, XSTRDUP(MTYPE_COMPLETION, nhgc->name));
+ }
+}
+
+static const struct cmd_variable_handler nhg_name_handlers[] = {
+ {.tokenname = "NHGNAME", .completions = nhg_name_autocomplete},
+ {.completions = NULL}};
+
+void nexthop_group_init(void (*new)(const char *name),
+ void (*add_nexthop)(const struct nexthop_group_cmd *nhg,
+ const struct nexthop *nhop),
+ void (*del_nexthop)(const struct nexthop_group_cmd *nhg,
+ const struct nexthop *nhop),
+ void (*delete)(const char *name))
+{
+ RB_INIT(nhgc_entry_head, &nhgc_entries);
+
+ cmd_variable_handler_register(nhg_name_handlers);
+
+ install_node(&nexthop_group_node);
+ install_element(CONFIG_NODE, &nexthop_group_cmd);
+ install_element(CONFIG_NODE, &no_nexthop_group_cmd);
+
+ install_default(NH_GROUP_NODE);
+ install_element(NH_GROUP_NODE, &nexthop_group_backup_cmd);
+ install_element(NH_GROUP_NODE, &no_nexthop_group_backup_cmd);
+ install_element(NH_GROUP_NODE, &ecmp_nexthops_cmd);
+
+ memset(&nhg_hooks, 0, sizeof(nhg_hooks));
+
+ if (new)
+ nhg_hooks.new = new;
+ if (add_nexthop)
+ nhg_hooks.add_nexthop = add_nexthop;
+ if (del_nexthop)
+ nhg_hooks.del_nexthop = del_nexthop;
+ if (delete)
+ nhg_hooks.delete = delete;
+}
diff --git a/lib/nexthop_group.h b/lib/nexthop_group.h
new file mode 100644
index 0000000..8e75e5c
--- /dev/null
+++ b/lib/nexthop_group.h
@@ -0,0 +1,160 @@
+/*
+ * Nexthop Group structure definition.
+ * Copyright (C) 2018 Cumulus Networks, Inc.
+ * Donald Sharp
+ *
+ * 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 __NEXTHOP_GROUP__
+#define __NEXTHOP_GROUP__
+
+#include <vty.h>
+#include "json.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * What is a nexthop group?
+ *
+ * A nexthop group is a collection of nexthops that make up
+ * the ECMP path for the route.
+ *
+ * This module provides a proper abstraction to this idea.
+ */
+struct nexthop_group {
+ struct nexthop *nexthop;
+};
+
+struct nexthop_group *nexthop_group_new(void);
+void nexthop_group_delete(struct nexthop_group **nhg);
+
+void nexthop_group_copy(struct nexthop_group *to,
+ const struct nexthop_group *from);
+
+/*
+ * Copy a list of nexthops in 'nh' to an nhg, enforcing canonical sort order
+ */
+void nexthop_group_copy_nh_sorted(struct nexthop_group *nhg,
+ const struct nexthop *nh);
+
+void copy_nexthops(struct nexthop **tnh, const struct nexthop *nh,
+ struct nexthop *rparent);
+
+uint32_t nexthop_group_hash_no_recurse(const struct nexthop_group *nhg);
+uint32_t nexthop_group_hash(const struct nexthop_group *nhg);
+void nexthop_group_mark_duplicates(struct nexthop_group *nhg);
+
+/* Add a nexthop to a list, enforcing the canonical sort order. */
+void nexthop_group_add_sorted(struct nexthop_group *nhg,
+ struct nexthop *nexthop);
+
+/* The following for loop allows to iterate over the nexthop
+ * structure of routes.
+ *
+ * head: The pointer to the first nexthop in the chain.
+ *
+ * nexthop: The pointer to the current nexthop, either in the
+ * top-level chain or in a resolved chain.
+ */
+#define ALL_NEXTHOPS(head, nhop) \
+ (nhop) = (head.nexthop); \
+ (nhop); \
+ (nhop) = nexthop_next(nhop)
+
+#define ALL_NEXTHOPS_PTR(head, nhop) \
+ (nhop) = ((head)->nexthop); \
+ (nhop); \
+ (nhop) = nexthop_next(nhop)
+
+
+#define NHGC_NAME_SIZE 80
+
+struct nexthop_group_cmd {
+
+ RB_ENTRY(nexthop_group_cmd) nhgc_entry;
+
+ char name[NHGC_NAME_SIZE];
+
+ /* Name of group containing backup nexthops (if set) */
+ char backup_list_name[NHGC_NAME_SIZE];
+
+ struct nexthop_group nhg;
+
+ struct list *nhg_list;
+
+ QOBJ_FIELDS;
+};
+RB_HEAD(nhgc_entry_head, nexthp_group_cmd);
+RB_PROTOTYPE(nhgc_entry_head, nexthop_group_cmd, nhgc_entry,
+ nexthop_group_cmd_compare)
+DECLARE_QOBJ_TYPE(nexthop_group_cmd);
+
+/*
+ * Initialize nexthop_groups. If you are interested in when
+ * a nexthop_group is added/deleted/modified, then set the
+ * appropriate callback functions to handle it in your
+ * code
+ */
+void nexthop_group_init(
+ void (*create)(const char *name),
+ void (*add_nexthop)(const struct nexthop_group_cmd *nhgc,
+ const struct nexthop *nhop),
+ void (*del_nexthop)(const struct nexthop_group_cmd *nhgc,
+ const struct nexthop *nhop),
+ void (*destroy)(const char *name));
+
+void nexthop_group_enable_vrf(struct vrf *vrf);
+void nexthop_group_disable_vrf(struct vrf *vrf);
+void nexthop_group_interface_state_change(struct interface *ifp,
+ ifindex_t oldifindex);
+
+extern struct nexthop *nexthop_exists(const struct nexthop_group *nhg,
+ const struct nexthop *nh);
+/* This assumes ordered */
+extern bool nexthop_group_equal_no_recurse(const struct nexthop_group *nhg1,
+ const struct nexthop_group *nhg2);
+
+/* This assumes ordered */
+extern bool nexthop_group_equal(const struct nexthop_group *nhg1,
+ const struct nexthop_group *nhg2);
+
+extern struct nexthop_group_cmd *nhgc_find(const char *name);
+
+extern void nexthop_group_write_nexthop_simple(struct vty *vty,
+ const struct nexthop *nh,
+ char *altifname);
+extern void nexthop_group_write_nexthop(struct vty *vty,
+ const struct nexthop *nh);
+
+extern void nexthop_group_json_nexthop(json_object *j,
+ const struct nexthop *nh);
+
+/* Return the number of nexthops in this nhg */
+extern uint8_t nexthop_group_nexthop_num(const struct nexthop_group *nhg);
+extern uint8_t
+nexthop_group_nexthop_num_no_recurse(const struct nexthop_group *nhg);
+extern uint8_t
+nexthop_group_active_nexthop_num(const struct nexthop_group *nhg);
+extern uint8_t
+nexthop_group_active_nexthop_num_no_recurse(const struct nexthop_group *nhg);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/nexthop_group_private.h b/lib/nexthop_group_private.h
new file mode 100644
index 0000000..4abda62
--- /dev/null
+++ b/lib/nexthop_group_private.h
@@ -0,0 +1,43 @@
+/*
+ * Nexthop Group Private Functions.
+ * Copyright (C) 2019 Cumulus Networks, Inc.
+ * Stephen Worley
+ *
+ * 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
+ */
+
+/**
+ * These functions should only be used internally for nexthop groups
+ * and in certain special cases. Please use `lib/nexthop_group.h` for
+ * any general nexthop_group api needs.
+ */
+
+#ifndef __NEXTHOP_GROUP_PRIVATE__
+#define __NEXTHOP_GROUP_PRIVATE__
+
+#include <nexthop_group.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void _nexthop_add(struct nexthop **target, struct nexthop *nexthop);
+void _nexthop_del(struct nexthop_group *nhg, struct nexthop *nexthop);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __NEXTHOP_GROUP_PRIVATE__ */
diff --git a/lib/northbound.c b/lib/northbound.c
new file mode 100644
index 0000000..ccfc384
--- /dev/null
+++ b/lib/northbound.c
@@ -0,0 +1,2503 @@
+/*
+ * 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 "hash.h"
+#include "command.h"
+#include "debug.h"
+#include "db.h"
+#include "frr_pthread.h"
+#include "northbound.h"
+#include "northbound_cli.h"
+#include "northbound_db.h"
+#include "frrstr.h"
+
+DEFINE_MTYPE_STATIC(LIB, NB_NODE, "Northbound Node");
+DEFINE_MTYPE_STATIC(LIB, NB_CONFIG, "Northbound Configuration");
+DEFINE_MTYPE_STATIC(LIB, NB_CONFIG_ENTRY, "Northbound Configuration Entry");
+
+/* Running configuration - shouldn't be modified directly. */
+struct nb_config *running_config;
+
+/* Hash table of user pointers associated with configuration entries. */
+static struct hash *running_config_entries;
+
+/* Management lock for the running configuration. */
+static struct {
+ /* Mutex protecting this structure. */
+ pthread_mutex_t mtx;
+
+ /* Actual lock. */
+ bool locked;
+
+ /* Northbound client who owns this lock. */
+ enum nb_client owner_client;
+
+ /* Northbound user who owns this lock. */
+ const void *owner_user;
+} running_config_mgmt_lock;
+
+/* Knob to record config transaction */
+static bool nb_db_enabled;
+/*
+ * Global lock used to prevent multiple configuration transactions from
+ * happening concurrently.
+ */
+static bool transaction_in_progress;
+
+static int nb_callback_pre_validate(struct nb_context *context,
+ const struct nb_node *nb_node,
+ const struct lyd_node *dnode, char *errmsg,
+ size_t errmsg_len);
+static int nb_callback_configuration(struct nb_context *context,
+ const enum nb_event event,
+ struct nb_config_change *change,
+ char *errmsg, size_t errmsg_len);
+static struct nb_transaction *
+nb_transaction_new(struct nb_context *context, struct nb_config *config,
+ struct nb_config_cbs *changes, const char *comment,
+ char *errmsg, size_t errmsg_len);
+static void nb_transaction_free(struct nb_transaction *transaction);
+static int nb_transaction_process(enum nb_event event,
+ struct nb_transaction *transaction,
+ char *errmsg, size_t errmsg_len);
+static void nb_transaction_apply_finish(struct nb_transaction *transaction,
+ char *errmsg, size_t errmsg_len);
+static int nb_oper_data_iter_node(const struct lysc_node *snode,
+ const char *xpath, const void *list_entry,
+ const struct yang_list_keys *list_keys,
+ struct yang_translator *translator,
+ bool first, uint32_t flags,
+ nb_oper_data_cb cb, void *arg);
+
+static int nb_node_check_config_only(const struct lysc_node *snode, void *arg)
+{
+ bool *config_only = arg;
+
+ if (CHECK_FLAG(snode->flags, LYS_CONFIG_R)) {
+ *config_only = false;
+ return YANG_ITER_STOP;
+ }
+
+ return YANG_ITER_CONTINUE;
+}
+
+static int nb_node_new_cb(const struct lysc_node *snode, void *arg)
+{
+ struct nb_node *nb_node;
+ struct lysc_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;
+
+ /* Set flags. */
+ if (CHECK_FLAG(snode->nodetype, LYS_CONTAINER | LYS_LIST)) {
+ bool config_only = true;
+
+ (void)yang_snodes_iterate_subtree(snode, NULL,
+ nb_node_check_config_only, 0,
+ &config_only);
+ if (config_only)
+ SET_FLAG(nb_node->flags, F_NB_NODE_CONFIG_ONLY);
+ }
+ if (CHECK_FLAG(snode->nodetype, LYS_LIST)) {
+ if (yang_snode_num_keys(snode) == 0)
+ SET_FLAG(nb_node->flags, F_NB_NODE_KEYLESS_LIST);
+ }
+
+ /*
+ * Link the northbound node and the libyang schema node with one
+ * another.
+ */
+ nb_node->snode = snode;
+ assert(snode->priv == NULL);
+ ((struct lysc_node *)snode)->priv = nb_node;
+
+ return YANG_ITER_CONTINUE;
+}
+
+static int nb_node_del_cb(const struct lysc_node *snode, void *arg)
+{
+ struct nb_node *nb_node;
+
+ nb_node = snode->priv;
+ if (nb_node) {
+ ((struct lysc_node *)snode)->priv = NULL;
+ XFREE(MTYPE_NB_NODE, nb_node);
+ }
+
+ return YANG_ITER_CONTINUE;
+}
+
+void nb_nodes_create(void)
+{
+ yang_snodes_iterate(NULL, nb_node_new_cb, 0, NULL);
+}
+
+void nb_nodes_delete(void)
+{
+ yang_snodes_iterate(NULL, nb_node_del_cb, 0, NULL);
+}
+
+struct nb_node *nb_node_find(const char *path)
+{
+ const struct lysc_node *snode;
+
+ /*
+ * Use libyang to find the schema node associated to the path and get
+ * the northbound node from there (snode private pointer).
+ */
+ snode = lys_find_path(ly_native_ctx, NULL, path, 0);
+ if (!snode)
+ return NULL;
+
+ return snode->priv;
+}
+
+void nb_node_set_dependency_cbs(const char *dependency_xpath,
+ const char *dependant_xpath,
+ struct nb_dependency_callbacks *cbs)
+{
+ struct nb_node *dependency = nb_node_find(dependency_xpath);
+ struct nb_node *dependant = nb_node_find(dependant_xpath);
+
+ if (!dependency || !dependant)
+ return;
+
+ dependency->dep_cbs.get_dependant_xpath = cbs->get_dependant_xpath;
+ dependant->dep_cbs.get_dependency_xpath = cbs->get_dependency_xpath;
+}
+
+bool nb_node_has_dependency(struct nb_node *node)
+{
+ return node->dep_cbs.get_dependency_xpath != NULL;
+}
+
+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);
+
+ /*
+ * Add an exception for operational data callbacks. A rw list usually
+ * doesn't need any associated operational data callbacks. But if this
+ * rw list is augmented by another module which adds state nodes under
+ * it, then this list will need to have the 'get_next()', 'get_keys()'
+ * and 'lookup_entry()' callbacks. As such, never log a warning when
+ * these callbacks are implemented when they are not needed, since this
+ * depends on context (e.g. some daemons might augment "frr-interface"
+ * while others don't).
+ */
+ if (!valid && callback_implemented && operation != NB_OP_GET_NEXT
+ && operation != NB_OP_GET_KEYS && operation != NB_OP_LOOKUP_ENTRY)
+ 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_DESTROY,
+ !!nb_node->cbs.destroy, 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_PRE_VALIDATE,
+ !!nb_node->cbs.pre_validate, true);
+ 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 int nb_node_validate(const struct lysc_node *snode, void *arg)
+{
+ struct nb_node *nb_node = snode->priv;
+ unsigned int *errors = arg;
+
+ /* Validate callbacks and priority. */
+ if (nb_node) {
+ *errors += nb_node_validate_cbs(nb_node);
+ *errors += nb_node_validate_priority(nb_node);
+ }
+
+ return YANG_ITER_CONTINUE;
+}
+
+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, true);
+ 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_siblings(&config_dst->dnode, config_src->dnode, 0);
+ 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. */
+ if (config_dst->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;
+
+ /*
+ * Preserve the order of the configuration changes as told by libyang.
+ */
+ if (a->seq < b->seq)
+ return -1;
+ if (a->seq > b->seq)
+ return 1;
+
+ /*
+ * All 'apply_finish' callbacks have their sequence number set to zero.
+ * In this case, compare them using their dnode pointers (the order
+ * doesn't matter for callbacks that have the same priority).
+ */
+ if (a->dnode < b->dnode)
+ return -1;
+ if (a->dnode > b->dnode)
+ return 1;
+
+ return 0;
+}
+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,
+ uint32_t *seq,
+ const struct lyd_node *dnode)
+{
+ struct nb_config_change *change;
+
+ /* Ignore unimplemented nodes. */
+ if (!dnode->schema->priv)
+ return;
+
+ change = XCALLOC(MTYPE_TMP, sizeof(*change));
+ change->cb.operation = operation;
+ change->cb.seq = *seq;
+ *seq = *seq + 1;
+ change->cb.nb_node = dnode->schema->priv;
+ 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_created(const struct lyd_node *dnode, uint32_t *seq,
+ struct nb_config_cbs *changes)
+{
+ enum nb_operation operation;
+ struct lyd_node *child;
+
+ /* Ignore unimplemented nodes. */
+ if (!dnode->schema->priv)
+ return;
+
+ switch (dnode->schema->nodetype) {
+ case LYS_LEAF:
+ case LYS_LEAFLIST:
+ if (lyd_is_default(dnode))
+ break;
+
+ 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
+ return;
+
+ nb_config_diff_add_change(changes, operation, seq, dnode);
+ break;
+ case LYS_CONTAINER:
+ case LYS_LIST:
+ if (nb_operation_is_valid(NB_OP_CREATE, dnode->schema))
+ nb_config_diff_add_change(changes, NB_OP_CREATE, seq,
+ dnode);
+
+ /* Process child nodes recursively. */
+ LY_LIST_FOR (lyd_child(dnode), child) {
+ nb_config_diff_created(child, seq, changes);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void nb_config_diff_deleted(const struct lyd_node *dnode, uint32_t *seq,
+ struct nb_config_cbs *changes)
+{
+ /* Ignore unimplemented nodes. */
+ if (!dnode->schema->priv)
+ return;
+
+ if (nb_operation_is_valid(NB_OP_DESTROY, dnode->schema))
+ nb_config_diff_add_change(changes, NB_OP_DESTROY, seq, dnode);
+ else if (CHECK_FLAG(dnode->schema->nodetype, LYS_CONTAINER)) {
+ struct lyd_node *child;
+
+ /*
+ * Non-presence containers need special handling since they
+ * don't have "destroy" callbacks. In this case, what we need to
+ * do is to call the "destroy" callbacks of their child nodes
+ * when applicable (i.e. optional nodes).
+ */
+ LY_LIST_FOR (lyd_child(dnode), child) {
+ nb_config_diff_deleted(child, seq, changes);
+ }
+ }
+}
+
+static int nb_lyd_diff_get_op(const struct lyd_node *dnode)
+{
+ const struct lyd_meta *meta;
+ LY_LIST_FOR (dnode->meta, meta) {
+ if (strcmp(meta->name, "operation")
+ || strcmp(meta->annotation->module->name, "yang"))
+ continue;
+ return lyd_get_meta_value(meta)[0];
+ }
+ return 'n';
+}
+
+#if 0 /* Used below in nb_config_diff inside normally disabled code */
+static inline void nb_config_diff_dnode_log_path(const char *context,
+ const char *path,
+ const struct lyd_node *dnode)
+{
+ if (dnode->schema->nodetype & LYD_NODE_TERM)
+ zlog_debug("nb_config_diff: %s: %s: %s", context, path,
+ lyd_get_value(dnode));
+ else
+ zlog_debug("nb_config_diff: %s: %s", context, path);
+}
+
+static inline void nb_config_diff_dnode_log(const char *context,
+ const struct lyd_node *dnode)
+{
+ if (!dnode) {
+ zlog_debug("nb_config_diff: %s: NULL", context);
+ return;
+ }
+
+ char *path = lyd_path(dnode, LYD_PATH_STD, NULL, 0);
+ nb_config_diff_dnode_log_path(context, path, dnode);
+ free(path);
+}
+#endif
+
+/* 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_node *diff = NULL;
+ const struct lyd_node *root, *dnode;
+ struct lyd_node *target;
+ int op;
+ LY_ERR err;
+ char *path;
+
+#if 0 /* Useful (noisy) when debugging diff code, and for improving later */
+ if (DEBUG_MODE_CHECK(&nb_dbg_cbs_config, DEBUG_MODE_ALL)) {
+ LY_LIST_FOR(config1->dnode, root) {
+ LYD_TREE_DFS_BEGIN(root, dnode) {
+ nb_config_diff_dnode_log("from", dnode);
+ LYD_TREE_DFS_END(root, dnode);
+ }
+ }
+ LY_LIST_FOR(config2->dnode, root) {
+ LYD_TREE_DFS_BEGIN(root, dnode) {
+ nb_config_diff_dnode_log("to", dnode);
+ LYD_TREE_DFS_END(root, dnode);
+ }
+ }
+ }
+#endif
+
+ err = lyd_diff_siblings(config1->dnode, config2->dnode,
+ LYD_DIFF_DEFAULTS, &diff);
+ assert(!err);
+
+ if (diff && DEBUG_MODE_CHECK(&nb_dbg_cbs_config, DEBUG_MODE_ALL)) {
+ char *s;
+
+ if (!lyd_print_mem(&s, diff, LYD_JSON,
+ LYD_PRINT_WITHSIBLINGS | LYD_PRINT_WD_ALL)) {
+ zlog_debug("%s: %s", __func__, s);
+ free(s);
+ }
+ }
+
+ uint32_t seq = 0;
+
+ LY_LIST_FOR (diff, root) {
+ LYD_TREE_DFS_BEGIN (root, dnode) {
+ op = nb_lyd_diff_get_op(dnode);
+
+ path = lyd_path(dnode, LYD_PATH_STD, NULL, 0);
+
+#if 0 /* Useful (noisy) when debugging diff code, and for improving later */
+ if (DEBUG_MODE_CHECK(&nb_dbg_cbs_config, DEBUG_MODE_ALL)) {
+ char context[80];
+ snprintf(context, sizeof(context),
+ "iterating diff: oper: %c seq: %u", op, seq);
+ nb_config_diff_dnode_log_path(context, path, dnode);
+ }
+#endif
+ switch (op) {
+ case 'c': /* create */
+ /*
+ * This is rather inefficient, but when we use
+ * dnode from the diff instead of the
+ * candidate config node we get failures when
+ * looking up default values, etc, based on
+ * the diff tree.
+ */
+ target = yang_dnode_get(config2->dnode, path);
+ assert(target);
+ nb_config_diff_created(target, &seq, changes);
+
+ /* Skip rest of sub-tree, move to next sibling
+ */
+ LYD_TREE_DFS_continue = 1;
+ break;
+ case 'd': /* delete */
+ target = yang_dnode_get(config1->dnode, path);
+ assert(target);
+ nb_config_diff_deleted(target, &seq, changes);
+
+ /* Skip rest of sub-tree, move to next sibling
+ */
+ LYD_TREE_DFS_continue = 1;
+ break;
+ case 'r': /* replace */
+ /* either moving an entry or changing a value */
+ target = yang_dnode_get(config2->dnode, path);
+ assert(target);
+ nb_config_diff_add_change(changes, NB_OP_MODIFY,
+ &seq, target);
+ break;
+ case 'n': /* none */
+ default:
+ break;
+ }
+ free(path);
+ LYD_TREE_DFS_END(root, dnode);
+ }
+ }
+
+ lyd_free_all(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, *dep_dnode;
+ char xpath_edit[XPATH_MAXLEN];
+ char dep_xpath[XPATH_MAXLEN];
+ LY_ERR 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:
+ err = lyd_new_path(candidate->dnode, ly_native_ctx, xpath_edit,
+ (void *)data->value, LYD_NEW_PATH_UPDATE,
+ &dnode);
+ if (err) {
+ flog_warn(EC_LIB_LIBYANG,
+ "%s: lyd_new_path(%s) failed: %d", __func__,
+ xpath_edit, err);
+ return NB_ERR;
+ } else if (dnode) {
+ /* Create default nodes */
+ LY_ERR err = lyd_new_implicit_tree(
+ dnode, LYD_IMPLICIT_NO_STATE, NULL);
+ if (err) {
+ flog_warn(EC_LIB_LIBYANG,
+ "%s: lyd_new_implicit_all failed: %d",
+ __func__, err);
+ }
+ /*
+ * create dependency
+ *
+ * dnode returned by the lyd_new_path may be from a
+ * different schema, so we need to update the nb_node
+ */
+ nb_node = dnode->schema->priv;
+ if (nb_node->dep_cbs.get_dependency_xpath) {
+ nb_node->dep_cbs.get_dependency_xpath(
+ dnode, dep_xpath);
+
+ err = lyd_new_path(candidate->dnode,
+ ly_native_ctx, dep_xpath,
+ NULL, LYD_NEW_PATH_UPDATE,
+ &dep_dnode);
+ /* Create default nodes */
+ if (!err && dep_dnode)
+ err = lyd_new_implicit_tree(
+ dep_dnode,
+ LYD_IMPLICIT_NO_STATE, NULL);
+ if (err) {
+ flog_warn(
+ EC_LIB_LIBYANG,
+ "%s: dependency: lyd_new_path(%s) failed: %d",
+ __func__, dep_xpath, err);
+ return NB_ERR;
+ }
+ }
+ }
+ break;
+ case NB_OP_DESTROY:
+ 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;
+ /* destroy dependant */
+ if (nb_node->dep_cbs.get_dependant_xpath) {
+ nb_node->dep_cbs.get_dependant_xpath(dnode, dep_xpath);
+
+ dep_dnode = yang_dnode_get(candidate->dnode, dep_xpath);
+ if (dep_dnode)
+ lyd_free_tree(dep_dnode);
+ }
+ lyd_free_tree(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;
+}
+
+/*
+ * 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, char *errmsg,
+ size_t errmsg_len)
+{
+ if (lyd_validate_all(&candidate->dnode, ly_native_ctx,
+ LYD_VALIDATE_NO_STATE, NULL)
+ != 0) {
+ yang_print_errors(ly_native_ctx, errmsg, errmsg_len);
+ return NB_ERR_VALIDATION;
+ }
+
+ return NB_OK;
+}
+
+/* Perform code-level validation using the northbound callbacks. */
+static int nb_candidate_validate_code(struct nb_context *context,
+ struct nb_config *candidate,
+ struct nb_config_cbs *changes,
+ char *errmsg, size_t errmsg_len)
+{
+ struct nb_config_cb *cb;
+ struct lyd_node *root, *child;
+ int ret;
+
+ /* First validate the candidate as a whole. */
+ LY_LIST_FOR (candidate->dnode, root) {
+ LYD_TREE_DFS_BEGIN (root, child) {
+ struct nb_node *nb_node;
+
+ nb_node = child->schema->priv;
+ if (!nb_node || !nb_node->cbs.pre_validate)
+ goto next;
+
+ ret = nb_callback_pre_validate(context, nb_node, child,
+ errmsg, errmsg_len);
+ if (ret != NB_OK)
+ return NB_ERR_VALIDATION;
+
+ next:
+ LYD_TREE_DFS_END(root, child);
+ }
+ }
+
+ /* Now validate the configuration changes. */
+ RB_FOREACH (cb, nb_config_cbs, changes) {
+ struct nb_config_change *change = (struct nb_config_change *)cb;
+
+ ret = nb_callback_configuration(context, NB_EV_VALIDATE, change,
+ errmsg, errmsg_len);
+ if (ret != NB_OK)
+ return NB_ERR_VALIDATION;
+ }
+
+ return NB_OK;
+}
+
+int nb_candidate_validate(struct nb_context *context,
+ struct nb_config *candidate, char *errmsg,
+ size_t errmsg_len)
+{
+ struct nb_config_cbs changes;
+ int ret;
+
+ if (nb_candidate_validate_yang(candidate, errmsg, errmsg_len) != NB_OK)
+ return NB_ERR_VALIDATION;
+
+ RB_INIT(nb_config_cbs, &changes);
+ nb_config_diff(running_config, candidate, &changes);
+ ret = nb_candidate_validate_code(context, candidate, &changes, errmsg,
+ errmsg_len);
+ nb_config_diff_del_changes(&changes);
+
+ return ret;
+}
+
+int nb_candidate_commit_prepare(struct nb_context *context,
+ struct nb_config *candidate,
+ const char *comment,
+ struct nb_transaction **transaction,
+ char *errmsg, size_t errmsg_len)
+{
+ struct nb_config_cbs changes;
+
+ if (nb_candidate_validate_yang(candidate, errmsg, errmsg_len)
+ != 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)) {
+ snprintf(
+ errmsg, errmsg_len,
+ "No changes to apply were found during preparation phase");
+ return NB_ERR_NO_CHANGES;
+ }
+
+ if (nb_candidate_validate_code(context, candidate, &changes, errmsg,
+ errmsg_len)
+ != 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(context, candidate, &changes, comment,
+ errmsg, errmsg_len);
+ if (*transaction == NULL) {
+ flog_warn(EC_LIB_NB_TRANSACTION_CREATION_FAILED,
+ "%s: failed to create transaction: %s", __func__,
+ errmsg);
+ nb_config_diff_del_changes(&changes);
+ return NB_ERR_LOCKED;
+ }
+
+ return nb_transaction_process(NB_EV_PREPARE, *transaction, errmsg,
+ errmsg_len);
+}
+
+void nb_candidate_commit_abort(struct nb_transaction *transaction, char *errmsg,
+ size_t errmsg_len)
+{
+ (void)nb_transaction_process(NB_EV_ABORT, transaction, errmsg,
+ errmsg_len);
+ nb_transaction_free(transaction);
+}
+
+void nb_candidate_commit_apply(struct nb_transaction *transaction,
+ bool save_transaction, uint32_t *transaction_id,
+ char *errmsg, size_t errmsg_len)
+{
+ (void)nb_transaction_process(NB_EV_APPLY, transaction, errmsg,
+ errmsg_len);
+ nb_transaction_apply_finish(transaction, errmsg, errmsg_len);
+
+ /* Replace running by candidate. */
+ transaction->config->version++;
+ nb_config_replace(running_config, transaction->config, true);
+
+ /* Record transaction. */
+ if (save_transaction && nb_db_enabled
+ && 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_context *context, struct nb_config *candidate,
+ bool save_transaction, const char *comment,
+ uint32_t *transaction_id, char *errmsg,
+ size_t errmsg_len)
+{
+ struct nb_transaction *transaction = NULL;
+ int ret;
+
+ ret = nb_candidate_commit_prepare(context, candidate, comment,
+ &transaction, errmsg, errmsg_len);
+ /*
+ * 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, errmsg, errmsg_len);
+ else if (transaction != NULL)
+ nb_candidate_commit_abort(transaction, errmsg, errmsg_len);
+
+ return ret;
+}
+
+int nb_running_lock(enum nb_client client, const void *user)
+{
+ int ret = -1;
+
+ frr_with_mutex (&running_config_mgmt_lock.mtx) {
+ if (!running_config_mgmt_lock.locked) {
+ running_config_mgmt_lock.locked = true;
+ running_config_mgmt_lock.owner_client = client;
+ running_config_mgmt_lock.owner_user = user;
+ ret = 0;
+ }
+ }
+
+ return ret;
+}
+
+int nb_running_unlock(enum nb_client client, const void *user)
+{
+ int ret = -1;
+
+ frr_with_mutex (&running_config_mgmt_lock.mtx) {
+ if (running_config_mgmt_lock.locked
+ && running_config_mgmt_lock.owner_client == client
+ && running_config_mgmt_lock.owner_user == user) {
+ running_config_mgmt_lock.locked = false;
+ running_config_mgmt_lock.owner_client = NB_CLIENT_NONE;
+ running_config_mgmt_lock.owner_user = NULL;
+ ret = 0;
+ }
+ }
+
+ return ret;
+}
+
+int nb_running_lock_check(enum nb_client client, const void *user)
+{
+ int ret = -1;
+
+ frr_with_mutex (&running_config_mgmt_lock.mtx) {
+ if (!running_config_mgmt_lock.locked
+ || (running_config_mgmt_lock.owner_client == client
+ && running_config_mgmt_lock.owner_user == user))
+ ret = 0;
+ }
+
+ return ret;
+}
+
+static void nb_log_config_callback(const enum nb_event event,
+ enum nb_operation operation,
+ const struct lyd_node *dnode)
+{
+ const char *value;
+ char xpath[XPATH_MAXLEN];
+
+ if (!DEBUG_MODE_CHECK(&nb_dbg_cbs_config, DEBUG_MODE_ALL))
+ return;
+
+ yang_dnode_get_path(dnode, xpath, sizeof(xpath));
+ if (yang_snode_is_typeless_data(dnode->schema))
+ value = "(none)";
+ else
+ value = yang_dnode_get_string(dnode, NULL);
+
+ zlog_debug(
+ "northbound callback: event [%s] op [%s] xpath [%s] value [%s]",
+ nb_event_name(event), nb_operation_name(operation), xpath,
+ value);
+}
+
+static int nb_callback_create(struct nb_context *context,
+ const struct nb_node *nb_node,
+ enum nb_event event, const struct lyd_node *dnode,
+ union nb_resource *resource, char *errmsg,
+ size_t errmsg_len)
+{
+ struct nb_cb_create_args args = {};
+ bool unexpected_error = false;
+ int ret;
+
+ nb_log_config_callback(event, NB_OP_CREATE, dnode);
+
+ args.context = context;
+ args.event = event;
+ args.dnode = dnode;
+ args.resource = resource;
+ args.errmsg = errmsg;
+ args.errmsg_len = errmsg_len;
+ ret = nb_node->cbs.create(&args);
+
+ /* Detect and log unexpected errors. */
+ switch (ret) {
+ case NB_OK:
+ case NB_ERR:
+ break;
+ case NB_ERR_VALIDATION:
+ if (event != NB_EV_VALIDATE)
+ unexpected_error = true;
+ break;
+ case NB_ERR_RESOURCE:
+ if (event != NB_EV_PREPARE)
+ unexpected_error = true;
+ break;
+ case NB_ERR_INCONSISTENCY:
+ if (event == NB_EV_VALIDATE)
+ unexpected_error = true;
+ break;
+ default:
+ unexpected_error = true;
+ break;
+ }
+ if (unexpected_error)
+ DEBUGD(&nb_dbg_cbs_config,
+ "northbound callback: unexpected return value: %s",
+ nb_err_name(ret));
+
+ return ret;
+}
+
+static int nb_callback_modify(struct nb_context *context,
+ const struct nb_node *nb_node,
+ enum nb_event event, const struct lyd_node *dnode,
+ union nb_resource *resource, char *errmsg,
+ size_t errmsg_len)
+{
+ struct nb_cb_modify_args args = {};
+ bool unexpected_error = false;
+ int ret;
+
+ nb_log_config_callback(event, NB_OP_MODIFY, dnode);
+
+ args.context = context;
+ args.event = event;
+ args.dnode = dnode;
+ args.resource = resource;
+ args.errmsg = errmsg;
+ args.errmsg_len = errmsg_len;
+ ret = nb_node->cbs.modify(&args);
+
+ /* Detect and log unexpected errors. */
+ switch (ret) {
+ case NB_OK:
+ case NB_ERR:
+ break;
+ case NB_ERR_VALIDATION:
+ if (event != NB_EV_VALIDATE)
+ unexpected_error = true;
+ break;
+ case NB_ERR_RESOURCE:
+ if (event != NB_EV_PREPARE)
+ unexpected_error = true;
+ break;
+ case NB_ERR_INCONSISTENCY:
+ if (event == NB_EV_VALIDATE)
+ unexpected_error = true;
+ break;
+ default:
+ unexpected_error = true;
+ break;
+ }
+ if (unexpected_error)
+ DEBUGD(&nb_dbg_cbs_config,
+ "northbound callback: unexpected return value: %s",
+ nb_err_name(ret));
+
+ return ret;
+}
+
+static int nb_callback_destroy(struct nb_context *context,
+ const struct nb_node *nb_node,
+ enum nb_event event,
+ const struct lyd_node *dnode, char *errmsg,
+ size_t errmsg_len)
+{
+ struct nb_cb_destroy_args args = {};
+ bool unexpected_error = false;
+ int ret;
+
+ nb_log_config_callback(event, NB_OP_DESTROY, dnode);
+
+ args.context = context;
+ args.event = event;
+ args.dnode = dnode;
+ args.errmsg = errmsg;
+ args.errmsg_len = errmsg_len;
+ ret = nb_node->cbs.destroy(&args);
+
+ /* Detect and log unexpected errors. */
+ switch (ret) {
+ case NB_OK:
+ case NB_ERR:
+ break;
+ case NB_ERR_VALIDATION:
+ if (event != NB_EV_VALIDATE)
+ unexpected_error = true;
+ break;
+ case NB_ERR_INCONSISTENCY:
+ if (event == NB_EV_VALIDATE)
+ unexpected_error = true;
+ break;
+ default:
+ unexpected_error = true;
+ break;
+ }
+ if (unexpected_error)
+ DEBUGD(&nb_dbg_cbs_config,
+ "northbound callback: unexpected return value: %s",
+ nb_err_name(ret));
+
+ return ret;
+}
+
+static int nb_callback_move(struct nb_context *context,
+ const struct nb_node *nb_node, enum nb_event event,
+ const struct lyd_node *dnode, char *errmsg,
+ size_t errmsg_len)
+{
+ struct nb_cb_move_args args = {};
+ bool unexpected_error = false;
+ int ret;
+
+ nb_log_config_callback(event, NB_OP_MOVE, dnode);
+
+ args.context = context;
+ args.event = event;
+ args.dnode = dnode;
+ args.errmsg = errmsg;
+ args.errmsg_len = errmsg_len;
+ ret = nb_node->cbs.move(&args);
+
+ /* Detect and log unexpected errors. */
+ switch (ret) {
+ case NB_OK:
+ case NB_ERR:
+ break;
+ case NB_ERR_VALIDATION:
+ if (event != NB_EV_VALIDATE)
+ unexpected_error = true;
+ break;
+ case NB_ERR_INCONSISTENCY:
+ if (event == NB_EV_VALIDATE)
+ unexpected_error = true;
+ break;
+ default:
+ unexpected_error = true;
+ break;
+ }
+ if (unexpected_error)
+ DEBUGD(&nb_dbg_cbs_config,
+ "northbound callback: unexpected return value: %s",
+ nb_err_name(ret));
+
+ return ret;
+}
+
+static int nb_callback_pre_validate(struct nb_context *context,
+ const struct nb_node *nb_node,
+ const struct lyd_node *dnode, char *errmsg,
+ size_t errmsg_len)
+{
+ struct nb_cb_pre_validate_args args = {};
+ bool unexpected_error = false;
+ int ret;
+
+ nb_log_config_callback(NB_EV_VALIDATE, NB_OP_PRE_VALIDATE, dnode);
+
+ args.dnode = dnode;
+ args.errmsg = errmsg;
+ args.errmsg_len = errmsg_len;
+ ret = nb_node->cbs.pre_validate(&args);
+
+ /* Detect and log unexpected errors. */
+ switch (ret) {
+ case NB_OK:
+ case NB_ERR_VALIDATION:
+ break;
+ default:
+ unexpected_error = true;
+ break;
+ }
+ if (unexpected_error)
+ DEBUGD(&nb_dbg_cbs_config,
+ "northbound callback: unexpected return value: %s",
+ nb_err_name(ret));
+
+ return ret;
+}
+
+static void nb_callback_apply_finish(struct nb_context *context,
+ const struct nb_node *nb_node,
+ const struct lyd_node *dnode, char *errmsg,
+ size_t errmsg_len)
+{
+ struct nb_cb_apply_finish_args args = {};
+
+ nb_log_config_callback(NB_EV_APPLY, NB_OP_APPLY_FINISH, dnode);
+
+ args.context = context;
+ args.dnode = dnode;
+ args.errmsg = errmsg;
+ args.errmsg_len = errmsg_len;
+ nb_node->cbs.apply_finish(&args);
+}
+
+struct yang_data *nb_callback_get_elem(const struct nb_node *nb_node,
+ const char *xpath,
+ const void *list_entry)
+{
+ struct nb_cb_get_elem_args args = {};
+
+ DEBUGD(&nb_dbg_cbs_state,
+ "northbound callback (get_elem): xpath [%s] list_entry [%p]",
+ xpath, list_entry);
+
+ args.xpath = xpath;
+ args.list_entry = list_entry;
+ return nb_node->cbs.get_elem(&args);
+}
+
+const void *nb_callback_get_next(const struct nb_node *nb_node,
+ const void *parent_list_entry,
+ const void *list_entry)
+{
+ struct nb_cb_get_next_args args = {};
+
+ DEBUGD(&nb_dbg_cbs_state,
+ "northbound callback (get_next): node [%s] parent_list_entry [%p] list_entry [%p]",
+ nb_node->xpath, parent_list_entry, list_entry);
+
+ args.parent_list_entry = parent_list_entry;
+ args.list_entry = list_entry;
+ return nb_node->cbs.get_next(&args);
+}
+
+int nb_callback_get_keys(const struct nb_node *nb_node, const void *list_entry,
+ struct yang_list_keys *keys)
+{
+ struct nb_cb_get_keys_args args = {};
+
+ DEBUGD(&nb_dbg_cbs_state,
+ "northbound callback (get_keys): node [%s] list_entry [%p]",
+ nb_node->xpath, list_entry);
+
+ args.list_entry = list_entry;
+ args.keys = keys;
+ return nb_node->cbs.get_keys(&args);
+}
+
+const void *nb_callback_lookup_entry(const struct nb_node *nb_node,
+ const void *parent_list_entry,
+ const struct yang_list_keys *keys)
+{
+ struct nb_cb_lookup_entry_args args = {};
+
+ DEBUGD(&nb_dbg_cbs_state,
+ "northbound callback (lookup_entry): node [%s] parent_list_entry [%p]",
+ nb_node->xpath, parent_list_entry);
+
+ args.parent_list_entry = parent_list_entry;
+ args.keys = keys;
+ return nb_node->cbs.lookup_entry(&args);
+}
+
+int nb_callback_rpc(const struct nb_node *nb_node, const char *xpath,
+ const struct list *input, struct list *output, char *errmsg,
+ size_t errmsg_len)
+{
+ struct nb_cb_rpc_args args = {};
+
+ DEBUGD(&nb_dbg_cbs_rpc, "northbound RPC: %s", xpath);
+
+ args.xpath = xpath;
+ args.input = input;
+ args.output = output;
+ args.errmsg = errmsg;
+ args.errmsg_len = errmsg_len;
+ return nb_node->cbs.rpc(&args);
+}
+
+/*
+ * Call the northbound configuration callback associated to a given
+ * configuration change.
+ */
+static int nb_callback_configuration(struct nb_context *context,
+ const enum nb_event event,
+ struct nb_config_change *change,
+ char *errmsg, size_t errmsg_len)
+{
+ enum nb_operation operation = change->cb.operation;
+ char xpath[XPATH_MAXLEN];
+ 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 (event == NB_EV_VALIDATE)
+ resource = NULL;
+ else
+ resource = &change->resource;
+
+ switch (operation) {
+ case NB_OP_CREATE:
+ ret = nb_callback_create(context, nb_node, event, dnode,
+ resource, errmsg, errmsg_len);
+ break;
+ case NB_OP_MODIFY:
+ ret = nb_callback_modify(context, nb_node, event, dnode,
+ resource, errmsg, errmsg_len);
+ break;
+ case NB_OP_DESTROY:
+ ret = nb_callback_destroy(context, nb_node, event, dnode,
+ errmsg, errmsg_len);
+ break;
+ case NB_OP_MOVE:
+ ret = nb_callback_move(context, nb_node, event, dnode, errmsg,
+ errmsg_len);
+ break;
+ default:
+ yang_dnode_get_path(dnode, xpath, sizeof(xpath));
+ flog_err(EC_LIB_DEVELOPMENT,
+ "%s: unknown operation (%u) [xpath %s]", __func__,
+ operation, xpath);
+ exit(1);
+ }
+
+ if (ret != NB_OK) {
+ yang_dnode_get_path(dnode, xpath, sizeof(xpath));
+
+ switch (event) {
+ case NB_EV_VALIDATE:
+ flog_warn(EC_LIB_NB_CB_CONFIG_VALIDATE,
+ "error processing configuration change: error [%s] event [%s] operation [%s] xpath [%s]%s%s",
+ nb_err_name(ret), nb_event_name(event),
+ nb_operation_name(operation), xpath,
+ errmsg[0] ? " message: " : "", errmsg);
+ break;
+ case NB_EV_PREPARE:
+ flog_warn(EC_LIB_NB_CB_CONFIG_PREPARE,
+ "error processing configuration change: error [%s] event [%s] operation [%s] xpath [%s]%s%s",
+ nb_err_name(ret), nb_event_name(event),
+ nb_operation_name(operation), xpath,
+ errmsg[0] ? " message: " : "", errmsg);
+ break;
+ case NB_EV_ABORT:
+ flog_warn(EC_LIB_NB_CB_CONFIG_ABORT,
+ "error processing configuration change: error [%s] event [%s] operation [%s] xpath [%s]%s%s",
+ nb_err_name(ret), nb_event_name(event),
+ nb_operation_name(operation), xpath,
+ errmsg[0] ? " message: " : "", errmsg);
+ break;
+ case NB_EV_APPLY:
+ flog_err(EC_LIB_NB_CB_CONFIG_APPLY,
+ "error processing configuration change: error [%s] event [%s] operation [%s] xpath [%s]%s%s",
+ nb_err_name(ret), nb_event_name(event),
+ nb_operation_name(operation), xpath,
+ errmsg[0] ? " message: " : "", errmsg);
+ break;
+ default:
+ flog_err(EC_LIB_DEVELOPMENT,
+ "%s: unknown event (%u) [xpath %s]", __func__,
+ event, xpath);
+ exit(1);
+ }
+ }
+
+ return ret;
+}
+
+static struct nb_transaction *
+nb_transaction_new(struct nb_context *context, struct nb_config *config,
+ struct nb_config_cbs *changes, const char *comment,
+ char *errmsg, size_t errmsg_len)
+{
+ struct nb_transaction *transaction;
+
+ if (nb_running_lock_check(context->client, context->user)) {
+ strlcpy(errmsg,
+ "running configuration is locked by another client",
+ errmsg_len);
+ return NULL;
+ }
+
+ if (transaction_in_progress) {
+ strlcpy(errmsg,
+ "there's already another transaction in progress",
+ errmsg_len);
+ return NULL;
+ }
+ transaction_in_progress = true;
+
+ transaction = XCALLOC(MTYPE_TMP, sizeof(*transaction));
+ transaction->context = context;
+ 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,
+ char *errmsg, size_t errmsg_len)
+{
+ 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)
+ break;
+
+ /* Call the appropriate callback. */
+ ret = nb_callback_configuration(transaction->context, event,
+ change, errmsg, errmsg_len);
+ 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 struct nb_node *nb_node,
+ const struct lyd_node *dnode)
+{
+ struct nb_config_cb *cb;
+
+ cb = XCALLOC(MTYPE_TMP, sizeof(*cb));
+ 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 struct nb_node *nb_node,
+ const struct lyd_node *dnode)
+{
+ struct nb_config_cb s;
+
+ s.seq = 0;
+ s.nb_node = nb_node;
+ s.dnode = dnode;
+ return RB_FIND(nb_config_cbs, cbs, &s);
+}
+
+/* Call the 'apply_finish' callbacks. */
+static void nb_transaction_apply_finish(struct nb_transaction *transaction,
+ char *errmsg, size_t errmsg_len)
+{
+ 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_DESTROY) {
+ char xpath[XPATH_MAXLEN];
+
+ dnode = lyd_parent(dnode);
+ 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) {
+ struct nb_node *nb_node;
+
+ nb_node = dnode->schema->priv;
+ if (!nb_node || !nb_node->cbs.apply_finish)
+ goto next;
+
+ /*
+ * Don't call the callback more than once for the same
+ * data node.
+ */
+ if (nb_apply_finish_cb_find(&cbs, nb_node, dnode))
+ goto next;
+
+ nb_apply_finish_cb_new(&cbs, nb_node, dnode);
+
+ next:
+ dnode = lyd_parent(dnode);
+ }
+ }
+
+ /* Call the 'apply_finish' callbacks, sorted by their priorities. */
+ RB_FOREACH (cb, nb_config_cbs, &cbs)
+ nb_callback_apply_finish(transaction->context, cb->nb_node,
+ cb->dnode, errmsg, errmsg_len);
+
+ /* 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);
+ }
+}
+
+static int nb_oper_data_iter_children(const struct lysc_node *snode,
+ const char *xpath, const void *list_entry,
+ const struct yang_list_keys *list_keys,
+ struct yang_translator *translator,
+ bool first, uint32_t flags,
+ nb_oper_data_cb cb, void *arg)
+{
+ const struct lysc_node *child;
+
+ LY_LIST_FOR (lysc_node_child(snode), child) {
+ int ret;
+
+ ret = nb_oper_data_iter_node(child, xpath, list_entry,
+ list_keys, translator, false,
+ flags, cb, arg);
+ if (ret != NB_OK)
+ return ret;
+ }
+
+ return NB_OK;
+}
+
+static int nb_oper_data_iter_leaf(const struct nb_node *nb_node,
+ const char *xpath, const void *list_entry,
+ const struct yang_list_keys *list_keys,
+ struct yang_translator *translator,
+ uint32_t flags, nb_oper_data_cb cb, void *arg)
+{
+ struct yang_data *data;
+
+ if (CHECK_FLAG(nb_node->snode->flags, LYS_CONFIG_W))
+ return NB_OK;
+
+ /* Ignore list keys. */
+ if (lysc_is_key(nb_node->snode))
+ return NB_OK;
+
+ data = nb_callback_get_elem(nb_node, xpath, list_entry);
+ if (data == NULL)
+ /* Leaf of type "empty" is not present. */
+ return NB_OK;
+
+ return (*cb)(nb_node->snode, translator, data, arg);
+}
+
+static int nb_oper_data_iter_container(const struct nb_node *nb_node,
+ const char *xpath,
+ const void *list_entry,
+ const struct yang_list_keys *list_keys,
+ struct yang_translator *translator,
+ uint32_t flags, nb_oper_data_cb cb,
+ void *arg)
+{
+ const struct lysc_node *snode = nb_node->snode;
+
+ if (CHECK_FLAG(nb_node->flags, F_NB_NODE_CONFIG_ONLY))
+ return NB_OK;
+
+ /* Read-only presence containers. */
+ if (nb_node->cbs.get_elem) {
+ struct yang_data *data;
+ int ret;
+
+ data = nb_callback_get_elem(nb_node, xpath, list_entry);
+ if (data == NULL)
+ /* Presence container is not present. */
+ return NB_OK;
+
+ ret = (*cb)(snode, translator, data, arg);
+ if (ret != NB_OK)
+ return ret;
+ }
+
+ /* Read-write presence containers. */
+ if (CHECK_FLAG(snode->flags, LYS_CONFIG_W)) {
+ struct lysc_node_container *scontainer;
+
+ scontainer = (struct lysc_node_container *)snode;
+ if (CHECK_FLAG(scontainer->flags, LYS_PRESENCE)
+ && !yang_dnode_get(running_config->dnode, xpath))
+ return NB_OK;
+ }
+
+ /* Iterate over the child nodes. */
+ return nb_oper_data_iter_children(snode, xpath, list_entry, list_keys,
+ translator, false, flags, cb, arg);
+}
+
+static int
+nb_oper_data_iter_leaflist(const struct nb_node *nb_node, const char *xpath,
+ const void *parent_list_entry,
+ const struct yang_list_keys *parent_list_keys,
+ struct yang_translator *translator, uint32_t flags,
+ nb_oper_data_cb cb, void *arg)
+{
+ const void *list_entry = NULL;
+
+ if (CHECK_FLAG(nb_node->snode->flags, LYS_CONFIG_W))
+ return NB_OK;
+
+ do {
+ struct yang_data *data;
+ int ret;
+
+ list_entry = nb_callback_get_next(nb_node, parent_list_entry,
+ list_entry);
+ if (!list_entry)
+ /* End of the list. */
+ break;
+
+ data = nb_callback_get_elem(nb_node, xpath, list_entry);
+ if (data == NULL)
+ continue;
+
+ ret = (*cb)(nb_node->snode, translator, data, arg);
+ if (ret != NB_OK)
+ return ret;
+ } while (list_entry);
+
+ return NB_OK;
+}
+
+static int nb_oper_data_iter_list(const struct nb_node *nb_node,
+ const char *xpath_list,
+ const void *parent_list_entry,
+ const struct yang_list_keys *parent_list_keys,
+ struct yang_translator *translator,
+ uint32_t flags, nb_oper_data_cb cb, void *arg)
+{
+ const struct lysc_node *snode = nb_node->snode;
+ const void *list_entry = NULL;
+ uint32_t position = 1;
+
+ if (CHECK_FLAG(nb_node->flags, F_NB_NODE_CONFIG_ONLY))
+ return NB_OK;
+
+ /* Iterate over all list entries. */
+ do {
+ const struct lysc_node_leaf *skey;
+ struct yang_list_keys list_keys;
+ char xpath[XPATH_MAXLEN * 2];
+ int ret;
+
+ /* Obtain list entry. */
+ list_entry = nb_callback_get_next(nb_node, parent_list_entry,
+ list_entry);
+ if (!list_entry)
+ /* End of the list. */
+ break;
+
+ if (!CHECK_FLAG(nb_node->flags, F_NB_NODE_KEYLESS_LIST)) {
+ /* Obtain the list entry keys. */
+ if (nb_callback_get_keys(nb_node, list_entry,
+ &list_keys)
+ != NB_OK) {
+ flog_warn(EC_LIB_NB_CB_STATE,
+ "%s: failed to get list keys",
+ __func__);
+ return NB_ERR;
+ }
+
+ /* Build XPath of the list entry. */
+ strlcpy(xpath, xpath_list, sizeof(xpath));
+ unsigned int i = 0;
+ LY_FOR_KEYS (snode, skey) {
+ assert(i < list_keys.num);
+ snprintf(xpath + strlen(xpath),
+ sizeof(xpath) - strlen(xpath),
+ "[%s='%s']", skey->name,
+ list_keys.key[i]);
+ i++;
+ }
+ assert(i == list_keys.num);
+ } else {
+ /*
+ * Keyless list - build XPath using a positional index.
+ */
+ snprintf(xpath, sizeof(xpath), "%s[%u]", xpath_list,
+ position);
+ position++;
+ }
+
+ /* Iterate over the child nodes. */
+ ret = nb_oper_data_iter_children(
+ nb_node->snode, xpath, list_entry, &list_keys,
+ translator, false, flags, cb, arg);
+ if (ret != NB_OK)
+ return ret;
+ } while (list_entry);
+
+ return NB_OK;
+}
+
+static int nb_oper_data_iter_node(const struct lysc_node *snode,
+ const char *xpath_parent,
+ const void *list_entry,
+ const struct yang_list_keys *list_keys,
+ struct yang_translator *translator,
+ bool first, uint32_t flags,
+ nb_oper_data_cb cb, void *arg)
+{
+ struct nb_node *nb_node;
+ char xpath[XPATH_MAXLEN];
+ int ret = NB_OK;
+
+ if (!first && CHECK_FLAG(flags, NB_OPER_DATA_ITER_NORECURSE)
+ && CHECK_FLAG(snode->nodetype, LYS_CONTAINER | LYS_LIST))
+ return NB_OK;
+
+ /* Update XPath. */
+ strlcpy(xpath, xpath_parent, sizeof(xpath));
+ if (!first && snode->nodetype != LYS_USES) {
+ struct lysc_node *parent;
+
+ /* Get the real parent. */
+ parent = snode->parent;
+
+ /*
+ * When necessary, include the namespace of the augmenting
+ * module.
+ */
+ if (parent && parent->module != snode->module)
+ snprintf(xpath + strlen(xpath),
+ sizeof(xpath) - strlen(xpath), "/%s:%s",
+ snode->module->name, snode->name);
+ else
+ snprintf(xpath + strlen(xpath),
+ sizeof(xpath) - strlen(xpath), "/%s",
+ snode->name);
+ }
+
+ nb_node = snode->priv;
+ switch (snode->nodetype) {
+ case LYS_CONTAINER:
+ ret = nb_oper_data_iter_container(nb_node, xpath, list_entry,
+ list_keys, translator, flags,
+ cb, arg);
+ break;
+ case LYS_LEAF:
+ ret = nb_oper_data_iter_leaf(nb_node, xpath, list_entry,
+ list_keys, translator, flags, cb,
+ arg);
+ break;
+ case LYS_LEAFLIST:
+ ret = nb_oper_data_iter_leaflist(nb_node, xpath, list_entry,
+ list_keys, translator, flags,
+ cb, arg);
+ break;
+ case LYS_LIST:
+ ret = nb_oper_data_iter_list(nb_node, xpath, list_entry,
+ list_keys, translator, flags, cb,
+ arg);
+ break;
+ case LYS_USES:
+ ret = nb_oper_data_iter_children(snode, xpath, list_entry,
+ list_keys, translator, false,
+ flags, cb, arg);
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+int nb_oper_data_iterate(const char *xpath, struct yang_translator *translator,
+ uint32_t flags, nb_oper_data_cb cb, void *arg)
+{
+ struct nb_node *nb_node;
+ const void *list_entry = NULL;
+ struct yang_list_keys list_keys;
+ struct list *list_dnodes;
+ struct lyd_node *dnode, *dn;
+ struct listnode *ln;
+ 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 NB_ERR;
+ }
+
+ /* For now this function works only with containers and lists. */
+ if (!CHECK_FLAG(nb_node->snode->nodetype, LYS_CONTAINER | LYS_LIST)) {
+ flog_warn(
+ EC_LIB_NB_OPERATIONAL_DATA,
+ "%s: can't iterate over YANG leaf or leaf-list [xpath %s]",
+ __func__, xpath);
+ return NB_ERR;
+ }
+
+ /*
+ * Create a data tree from the XPath so that we can parse the keys of
+ * all YANG lists (if any).
+ */
+
+ LY_ERR err = lyd_new_path(NULL, ly_native_ctx, xpath, NULL,
+ LYD_NEW_PATH_UPDATE, &dnode);
+ if (err || !dnode) {
+ const char *errmsg =
+ err ? ly_errmsg(ly_native_ctx) : "node not found";
+ flog_warn(EC_LIB_LIBYANG, "%s: lyd_new_path() failed %s",
+ __func__, errmsg);
+ return NB_ERR;
+ }
+
+ /*
+ * Create a linked list to sort the data nodes starting from the root.
+ */
+ list_dnodes = list_new();
+ for (dn = dnode; dn; dn = lyd_parent(dn)) {
+ if (dn->schema->nodetype != LYS_LIST || !lyd_child(dn))
+ continue;
+ listnode_add_head(list_dnodes, dn);
+ }
+ /*
+ * Use the northbound callbacks to find list entry pointer corresponding
+ * to the given XPath.
+ */
+ for (ALL_LIST_ELEMENTS_RO(list_dnodes, ln, dn)) {
+ struct lyd_node *child;
+ struct nb_node *nn;
+ unsigned int n = 0;
+
+ /* Obtain the list entry keys. */
+ memset(&list_keys, 0, sizeof(list_keys));
+ LY_LIST_FOR (lyd_child(dn), child) {
+ if (!lysc_is_key(child->schema))
+ break;
+ strlcpy(list_keys.key[n],
+ yang_dnode_get_string(child, NULL),
+ sizeof(list_keys.key[n]));
+ n++;
+ }
+ list_keys.num = n;
+ if (list_keys.num != yang_snode_num_keys(dn->schema)) {
+ list_delete(&list_dnodes);
+ yang_dnode_free(dnode);
+ return NB_ERR_NOT_FOUND;
+ }
+
+ /* Find the list entry pointer. */
+ nn = dn->schema->priv;
+ if (!nn->cbs.lookup_entry) {
+ flog_warn(
+ EC_LIB_NB_OPERATIONAL_DATA,
+ "%s: data path doesn't support iteration over operational data: %s",
+ __func__, xpath);
+ list_delete(&list_dnodes);
+ yang_dnode_free(dnode);
+ return NB_ERR;
+ }
+
+ list_entry =
+ nb_callback_lookup_entry(nn, list_entry, &list_keys);
+ if (list_entry == NULL) {
+ list_delete(&list_dnodes);
+ yang_dnode_free(dnode);
+ return NB_ERR_NOT_FOUND;
+ }
+ }
+
+ /* If a list entry was given, iterate over that list entry only. */
+ if (dnode->schema->nodetype == LYS_LIST && lyd_child(dnode))
+ ret = nb_oper_data_iter_children(
+ nb_node->snode, xpath, list_entry, &list_keys,
+ translator, true, flags, cb, arg);
+ else
+ ret = nb_oper_data_iter_node(nb_node->snode, xpath, list_entry,
+ &list_keys, translator, true,
+ flags, cb, arg);
+
+ list_delete(&list_dnodes);
+ yang_dnode_free(dnode);
+
+ return ret;
+}
+
+bool nb_operation_is_valid(enum nb_operation operation,
+ const struct lysc_node *snode)
+{
+ struct nb_node *nb_node = snode->priv;
+ struct lysc_node_container *scontainer;
+ struct lysc_node_leaf *sleaf;
+
+ switch (operation) {
+ case NB_OP_CREATE:
+ if (!CHECK_FLAG(snode->flags, LYS_CONFIG_W))
+ return false;
+
+ switch (snode->nodetype) {
+ case LYS_LEAF:
+ sleaf = (struct lysc_node_leaf *)snode;
+ if (sleaf->type->basetype != LY_TYPE_EMPTY)
+ return false;
+ break;
+ case LYS_CONTAINER:
+ scontainer = (struct lysc_node_container *)snode;
+ if (!CHECK_FLAG(scontainer->flags, LYS_PRESENCE))
+ return false;
+ break;
+ case LYS_LIST:
+ case LYS_LEAFLIST:
+ break;
+ default:
+ return false;
+ }
+ return true;
+ case NB_OP_MODIFY:
+ if (!CHECK_FLAG(snode->flags, LYS_CONFIG_W))
+ return false;
+
+ switch (snode->nodetype) {
+ case LYS_LEAF:
+ sleaf = (struct lysc_node_leaf *)snode;
+ if (sleaf->type->basetype == LY_TYPE_EMPTY)
+ return false;
+
+ /* List keys can't be modified. */
+ if (lysc_is_key(sleaf))
+ return false;
+ break;
+ default:
+ return false;
+ }
+ return true;
+ case NB_OP_DESTROY:
+ if (!CHECK_FLAG(snode->flags, LYS_CONFIG_W))
+ return false;
+
+ switch (snode->nodetype) {
+ case LYS_LEAF:
+ sleaf = (struct lysc_node_leaf *)snode;
+
+ /* List keys can't be deleted. */
+ if (lysc_is_key(sleaf))
+ 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 (CHECK_FLAG(sleaf->flags, LYS_MAND_TRUE)
+ || sleaf->dflt)
+ return false;
+ break;
+ case LYS_CONTAINER:
+ scontainer = (struct lysc_node_container *)snode;
+ if (!CHECK_FLAG(scontainer->flags, LYS_PRESENCE))
+ return false;
+ break;
+ case LYS_LIST:
+ case LYS_LEAFLIST:
+ break;
+ default:
+ return false;
+ }
+ return true;
+ case NB_OP_MOVE:
+ if (!CHECK_FLAG(snode->flags, LYS_CONFIG_W))
+ return false;
+
+ switch (snode->nodetype) {
+ case LYS_LIST:
+ case LYS_LEAFLIST:
+ if (!CHECK_FLAG(snode->flags, LYS_ORDBY_USER))
+ return false;
+ break;
+ default:
+ return false;
+ }
+ return true;
+ case NB_OP_PRE_VALIDATE:
+ case NB_OP_APPLY_FINISH:
+ if (!CHECK_FLAG(snode->flags, LYS_CONFIG_W))
+ return false;
+ return true;
+ case NB_OP_GET_ELEM:
+ if (!CHECK_FLAG(snode->flags, LYS_CONFIG_R))
+ return false;
+
+ switch (snode->nodetype) {
+ case LYS_LEAF:
+ case LYS_LEAFLIST:
+ break;
+ case LYS_CONTAINER:
+ scontainer = (struct lysc_node_container *)snode;
+ if (!CHECK_FLAG(scontainer->flags, LYS_PRESENCE))
+ return false;
+ break;
+ default:
+ return false;
+ }
+ return true;
+ case NB_OP_GET_NEXT:
+ switch (snode->nodetype) {
+ case LYS_LIST:
+ if (CHECK_FLAG(nb_node->flags, F_NB_NODE_CONFIG_ONLY))
+ return false;
+ break;
+ case LYS_LEAFLIST:
+ if (CHECK_FLAG(snode->flags, LYS_CONFIG_W))
+ return false;
+ break;
+ default:
+ return false;
+ }
+ return true;
+ case NB_OP_GET_KEYS:
+ case NB_OP_LOOKUP_ENTRY:
+ switch (snode->nodetype) {
+ case LYS_LIST:
+ if (CHECK_FLAG(nb_node->flags, F_NB_NODE_CONFIG_ONLY))
+ return false;
+ if (CHECK_FLAG(nb_node->flags, F_NB_NODE_KEYLESS_LIST))
+ return false;
+ break;
+ default:
+ return false;
+ }
+ return true;
+ case NB_OP_RPC:
+ if (CHECK_FLAG(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;
+
+ DEBUGD(&nb_dbg_notif, "northbound notification: %s", xpath);
+
+ ret = hook_call(nb_notification_send, xpath, arguments);
+ if (arguments)
+ list_delete(&arguments);
+
+ return ret;
+}
+
+/* Running configuration user pointers management. */
+struct nb_config_entry {
+ char xpath[XPATH_MAXLEN];
+ void *entry;
+};
+
+static bool running_config_entry_cmp(const void *value1, const void *value2)
+{
+ const struct nb_config_entry *c1 = value1;
+ const struct nb_config_entry *c2 = value2;
+
+ return strmatch(c1->xpath, c2->xpath);
+}
+
+static unsigned int running_config_entry_key_make(const void *value)
+{
+ return string_hash_make(value);
+}
+
+static void *running_config_entry_alloc(void *p)
+{
+ struct nb_config_entry *new, *key = p;
+
+ new = XCALLOC(MTYPE_NB_CONFIG_ENTRY, sizeof(*new));
+ strlcpy(new->xpath, key->xpath, sizeof(new->xpath));
+
+ return new;
+}
+
+static void running_config_entry_free(void *arg)
+{
+ XFREE(MTYPE_NB_CONFIG_ENTRY, arg);
+}
+
+void nb_running_set_entry(const struct lyd_node *dnode, void *entry)
+{
+ struct nb_config_entry *config, s;
+
+ yang_dnode_get_path(dnode, s.xpath, sizeof(s.xpath));
+ config = hash_get(running_config_entries, &s,
+ running_config_entry_alloc);
+ config->entry = entry;
+}
+
+void nb_running_move_tree(const char *xpath_from, const char *xpath_to)
+{
+ struct nb_config_entry *entry;
+ struct list *entries = hash_to_list(running_config_entries);
+ struct listnode *ln;
+
+ for (ALL_LIST_ELEMENTS_RO(entries, ln, entry)) {
+ if (!frrstr_startswith(entry->xpath, xpath_from))
+ continue;
+
+ hash_release(running_config_entries, entry);
+
+ char *newpath =
+ frrstr_replace(entry->xpath, xpath_from, xpath_to);
+ strlcpy(entry->xpath, newpath, sizeof(entry->xpath));
+ XFREE(MTYPE_TMP, newpath);
+
+ (void)hash_get(running_config_entries, entry,
+ hash_alloc_intern);
+ }
+
+ list_delete(&entries);
+}
+
+static void *nb_running_unset_entry_helper(const struct lyd_node *dnode)
+{
+ struct nb_config_entry *config, s;
+ struct lyd_node *child;
+ void *entry = NULL;
+
+ yang_dnode_get_path(dnode, s.xpath, sizeof(s.xpath));
+ config = hash_release(running_config_entries, &s);
+ if (config) {
+ entry = config->entry;
+ running_config_entry_free(config);
+ }
+
+ /* Unset user pointers from the child nodes. */
+ if (CHECK_FLAG(dnode->schema->nodetype, LYS_LIST | LYS_CONTAINER)) {
+ LY_LIST_FOR (lyd_child(dnode), child) {
+ (void)nb_running_unset_entry_helper(child);
+ }
+ }
+
+ return entry;
+}
+
+void *nb_running_unset_entry(const struct lyd_node *dnode)
+{
+ void *entry;
+
+ entry = nb_running_unset_entry_helper(dnode);
+ assert(entry);
+
+ return entry;
+}
+
+static void *nb_running_get_entry_worker(const struct lyd_node *dnode,
+ const char *xpath,
+ bool abort_if_not_found,
+ bool rec_search)
+{
+ const struct lyd_node *orig_dnode = dnode;
+ char xpath_buf[XPATH_MAXLEN];
+ bool rec_flag = true;
+
+ assert(dnode || xpath);
+
+ if (!dnode)
+ dnode = yang_dnode_get(running_config->dnode, xpath);
+
+ while (rec_flag && dnode) {
+ struct nb_config_entry *config, s;
+
+ yang_dnode_get_path(dnode, s.xpath, sizeof(s.xpath));
+ config = hash_lookup(running_config_entries, &s);
+ if (config)
+ return config->entry;
+
+ rec_flag = rec_search;
+
+ dnode = lyd_parent(dnode);
+ }
+
+ if (!abort_if_not_found)
+ return NULL;
+
+ yang_dnode_get_path(orig_dnode, xpath_buf, sizeof(xpath_buf));
+ flog_err(EC_LIB_YANG_DNODE_NOT_FOUND,
+ "%s: failed to find entry [xpath %s]", __func__, xpath_buf);
+ zlog_backtrace(LOG_ERR);
+ abort();
+}
+
+void *nb_running_get_entry(const struct lyd_node *dnode, const char *xpath,
+ bool abort_if_not_found)
+{
+ return nb_running_get_entry_worker(dnode, xpath, abort_if_not_found,
+ true);
+}
+
+void *nb_running_get_entry_non_rec(const struct lyd_node *dnode,
+ const char *xpath, bool abort_if_not_found)
+{
+ return nb_running_get_entry_worker(dnode, xpath, abort_if_not_found,
+ false);
+}
+
+/* Logging functions. */
+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_DESTROY:
+ return "destroy";
+ case NB_OP_MOVE:
+ return "move";
+ case NB_OP_PRE_VALIDATE:
+ return "pre_validate";
+ 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";
+ 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";
+ case NB_CLIENT_CONFD:
+ return "ConfD";
+ case NB_CLIENT_SYSREPO:
+ return "Sysrepo";
+ case NB_CLIENT_GRPC:
+ return "gRPC";
+ 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;
+
+ if (i > YANG_MODULE_MAX_NODES) {
+ zlog_err(
+ "%s: %s.yang has more than %u nodes. Please increase YANG_MODULE_MAX_NODES to fix this problem.",
+ __func__, module->name, YANG_MODULE_MAX_NODES);
+ exit(1);
+ }
+
+ 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_validate_callbacks(void)
+{
+ unsigned int errors = 0;
+
+ yang_snodes_iterate(NULL, nb_node_validate, 0, &errors);
+ if (errors > 0) {
+ flog_err(
+ EC_LIB_NB_CBS_VALIDATION,
+ "%s: failed to validate northbound callbacks: %u error(s)",
+ __func__, errors);
+ exit(1);
+ }
+}
+
+
+void nb_init(struct thread_master *tm,
+ const struct frr_yang_module_info *const modules[],
+ size_t nmodules, bool db_enabled)
+{
+ struct yang_module *loaded[nmodules], **loadedp = loaded;
+ bool explicit_compile;
+
+ /*
+ * Currently using this explicit compile feature in libyang2 leads to
+ * incorrect behavior in FRR. The functionality suppresses the compiling
+ * of modules until they have all been loaded into the context. This
+ * avoids multiple recompiles of the same modules as they are
+ * imported/augmented etc.
+ */
+ explicit_compile = false;
+
+ nb_db_enabled = db_enabled;
+
+ yang_init(true, explicit_compile);
+
+ /* Load YANG modules and their corresponding northbound callbacks. */
+ for (size_t i = 0; i < nmodules; i++) {
+ DEBUGD(&nb_dbg_events, "northbound: loading %s.yang",
+ modules[i]->name);
+ *loadedp++ = yang_module_load(modules[i]->name);
+ }
+
+ if (explicit_compile)
+ yang_init_loading_complete();
+
+ /* Initialize the compiled nodes with northbound data */
+ for (size_t i = 0; i < nmodules; i++) {
+ yang_snodes_iterate(loaded[i]->info, nb_node_new_cb, 0, NULL);
+ nb_load_callbacks(modules[i]);
+ }
+
+ /* Validate northbound callbacks. */
+ nb_validate_callbacks();
+
+ /* Create an empty running configuration. */
+ running_config = nb_config_new(NULL);
+ running_config_entries = hash_create(running_config_entry_key_make,
+ running_config_entry_cmp,
+ "Running Configuration Entries");
+ pthread_mutex_init(&running_config_mgmt_lock.mtx, NULL);
+
+ /* Initialize the northbound CLI. */
+ nb_cli_init(tm);
+}
+
+void nb_terminate(void)
+{
+ /* Terminate the northbound CLI. */
+ nb_cli_terminate();
+
+ /* Delete all nb_node's from all YANG modules. */
+ nb_nodes_delete();
+
+ /* Delete the running configuration. */
+ hash_clean(running_config_entries, running_config_entry_free);
+ hash_free(running_config_entries);
+ nb_config_free(running_config);
+ pthread_mutex_destroy(&running_config_mgmt_lock.mtx);
+}
diff --git a/lib/northbound.h b/lib/northbound.h
new file mode 100644
index 0000000..a330bd1
--- /dev/null
+++ b/lib/northbound.h
@@ -0,0 +1,1316 @@
+/*
+ * 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 "thread.h"
+#include "hook.h"
+#include "linklist.h"
+#include "openbsd-tree.h"
+#include "yang.h"
+#include "yang_translator.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Forward declaration(s). */
+struct vty;
+struct debug;
+
+/* 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_DESTROY,
+ NB_OP_MOVE,
+ NB_OP_PRE_VALIDATE,
+ 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;
+};
+
+/*
+ * Northbound callbacks parameters.
+ */
+
+struct nb_cb_create_args {
+ /* Context of the configuration transaction. */
+ struct nb_context *context;
+
+ /*
+ * The transaction phase. Refer to the documentation comments of
+ * nb_event for more details.
+ */
+ enum nb_event event;
+
+ /* libyang data node that is being created. */
+ const struct lyd_node *dnode;
+
+ /*
+ * 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.
+ */
+ union nb_resource *resource;
+
+ /* Buffer to store human-readable error message in case of error. */
+ char *errmsg;
+
+ /* Size of errmsg. */
+ size_t errmsg_len;
+};
+
+struct nb_cb_modify_args {
+ /* Context of the configuration transaction. */
+ struct nb_context *context;
+
+ /*
+ * The transaction phase. Refer to the documentation comments of
+ * nb_event for more details.
+ */
+ enum nb_event event;
+
+ /* libyang data node that is being modified. */
+ const struct lyd_node *dnode;
+
+ /*
+ * 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.
+ */
+ union nb_resource *resource;
+
+ /* Buffer to store human-readable error message in case of error. */
+ char *errmsg;
+
+ /* Size of errmsg. */
+ size_t errmsg_len;
+};
+
+struct nb_cb_destroy_args {
+ /* Context of the configuration transaction. */
+ struct nb_context *context;
+
+ /*
+ * The transaction phase. Refer to the documentation comments of
+ * nb_event for more details.
+ */
+ enum nb_event event;
+
+ /* libyang data node that is being deleted. */
+ const struct lyd_node *dnode;
+
+ /* Buffer to store human-readable error message in case of error. */
+ char *errmsg;
+
+ /* Size of errmsg. */
+ size_t errmsg_len;
+};
+
+struct nb_cb_move_args {
+ /* Context of the configuration transaction. */
+ struct nb_context *context;
+
+ /*
+ * The transaction phase. Refer to the documentation comments of
+ * nb_event for more details.
+ */
+ enum nb_event event;
+
+ /* libyang data node that is being moved. */
+ const struct lyd_node *dnode;
+
+ /* Buffer to store human-readable error message in case of error. */
+ char *errmsg;
+
+ /* Size of errmsg. */
+ size_t errmsg_len;
+};
+
+struct nb_cb_pre_validate_args {
+ /* Context of the configuration transaction. */
+ struct nb_context *context;
+
+ /* libyang data node associated with the 'pre_validate' callback. */
+ const struct lyd_node *dnode;
+
+ /* Buffer to store human-readable error message in case of error. */
+ char *errmsg;
+
+ /* Size of errmsg. */
+ size_t errmsg_len;
+};
+
+struct nb_cb_apply_finish_args {
+ /* Context of the configuration transaction. */
+ struct nb_context *context;
+
+ /* libyang data node associated with the 'apply_finish' callback. */
+ const struct lyd_node *dnode;
+
+ /* Buffer to store human-readable error message in case of error. */
+ char *errmsg;
+
+ /* Size of errmsg. */
+ size_t errmsg_len;
+};
+
+struct nb_cb_get_elem_args {
+ /* YANG data path of the data we want to get. */
+ const char *xpath;
+
+ /* Pointer to list entry (might be NULL). */
+ const void *list_entry;
+};
+
+struct nb_cb_get_next_args {
+ /* Pointer to parent list entry. */
+ const void *parent_list_entry;
+
+ /* Pointer to (leaf-)list entry. */
+ const void *list_entry;
+};
+
+struct nb_cb_get_keys_args {
+ /* Pointer to list entry. */
+ const void *list_entry;
+
+ /*
+ * Structure to be filled based on the attributes of the provided list
+ * entry.
+ */
+ struct yang_list_keys *keys;
+};
+
+struct nb_cb_lookup_entry_args {
+ /* Pointer to parent list entry. */
+ const void *parent_list_entry;
+
+ /* Structure containing the keys of the list entry. */
+ const struct yang_list_keys *keys;
+};
+
+struct nb_cb_rpc_args {
+ /* XPath of the YANG RPC or action. */
+ const char *xpath;
+
+ /* Read-only list of input parameters. */
+ const struct list *input;
+
+ /* List of output parameters to be populated by the callback. */
+ struct list *output;
+
+ /* Buffer to store human-readable error message in case of error. */
+ char *errmsg;
+
+ /* Size of errmsg. */
+ size_t errmsg_len;
+};
+
+/*
+ * Set of configuration callbacks that can be associated to a northbound node.
+ */
+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.
+ *
+ * args
+ * Refer to the documentation comments of nb_cb_create_args for
+ * details.
+ *
+ * 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)(struct nb_cb_create_args *args);
+
+ /*
+ * 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.
+ *
+ * args
+ * Refer to the documentation comments of nb_cb_modify_args for
+ * details.
+ *
+ * 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)(struct nb_cb_modify_args *args);
+
+ /*
+ * 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.
+ *
+ * args
+ * Refer to the documentation comments of nb_cb_destroy_args for
+ * details.
+ *
+ * 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 (*destroy)(struct nb_cb_destroy_args *args);
+
+ /*
+ * Configuration callback.
+ *
+ * A list entry or leaf-list entry has been moved. Only applicable when
+ * the "ordered-by user" statement is present.
+ *
+ * args
+ * Refer to the documentation comments of nb_cb_move_args for
+ * details.
+ *
+ * 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)(struct nb_cb_move_args *args);
+
+ /*
+ * Optional configuration callback.
+ *
+ * This callback can be used to validate subsections of the
+ * configuration being committed before validating the configuration
+ * changes themselves. It's useful to perform more complex validations
+ * that depend on the relationship between multiple nodes.
+ *
+ * args
+ * Refer to the documentation comments of nb_cb_pre_validate_args for
+ * details.
+ *
+ * Returns:
+ * - NB_OK on success.
+ * - NB_ERR_VALIDATION when a validation error occurred.
+ */
+ int (*pre_validate)(struct nb_cb_pre_validate_args *args);
+
+ /*
+ * 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.
+ *
+ * args
+ * Refer to the documentation comments of nb_cb_apply_finish_args for
+ * details.
+ */
+ void (*apply_finish)(struct nb_cb_apply_finish_args *args);
+
+ /*
+ * Operational data callback.
+ *
+ * The callback function should return the value of a specific leaf,
+ * leaf-list entry or inform if a typeless value (presence containers or
+ * leafs of type empty) exists or not.
+ *
+ * args
+ * Refer to the documentation comments of nb_cb_get_elem_args for
+ * details.
+ *
+ * Returns:
+ * Pointer to newly created yang_data structure, or NULL to indicate
+ * the absence of data.
+ */
+ struct yang_data *(*get_elem)(struct nb_cb_get_elem_args *args);
+
+ /*
+ * Operational data callback for YANG lists and leaf-lists.
+ *
+ * The callback function should return the next entry in the list or
+ * leaf-list. The 'list_entry' parameter will be NULL on the first
+ * invocation.
+ *
+ * args
+ * Refer to the documentation comments of nb_cb_get_next_args for
+ * details.
+ *
+ * Returns:
+ * Pointer to the next entry in the (leaf-)list, or NULL to signal
+ * that the end of the (leaf-)list was reached.
+ */
+ const void *(*get_next)(struct nb_cb_get_next_args *args);
+
+ /*
+ * Operational data callback for YANG lists.
+ *
+ * The callback function should fill the 'keys' parameter based on the
+ * given list_entry. Keyless lists don't need to implement this
+ * callback.
+ *
+ * args
+ * Refer to the documentation comments of nb_cb_get_keys_args for
+ * details.
+ *
+ * Returns:
+ * NB_OK on success, NB_ERR otherwise.
+ */
+ int (*get_keys)(struct nb_cb_get_keys_args *args);
+
+ /*
+ * Operational data callback for YANG lists.
+ *
+ * The callback function should return a list entry based on the list
+ * keys given as a parameter. Keyless lists don't need to implement this
+ * callback.
+ *
+ * args
+ * Refer to the documentation comments of nb_cb_lookup_entry_args for
+ * details.
+ *
+ * Returns:
+ * Pointer to the list entry if found, or NULL if not found.
+ */
+ const void *(*lookup_entry)(struct nb_cb_lookup_entry_args *args);
+
+ /*
+ * 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.
+ *
+ * args
+ * Refer to the documentation comments of nb_cb_rpc_args for details.
+ *
+ * Returns:
+ * NB_OK on success, NB_ERR otherwise.
+ */
+ int (*rpc)(struct nb_cb_rpc_args *args);
+
+ /*
+ * Optional callback to compare the data nodes when printing
+ * the CLI commands associated with them.
+ *
+ * dnode1
+ * The first data node to compare.
+ *
+ * dnode2
+ * The second data node to compare.
+ *
+ * Returns:
+ * <0 when the CLI command for the dnode1 should be printed first
+ * >0 when the CLI command for the dnode2 should be printed first
+ * 0 when there is no difference
+ */
+ int (*cli_cmp)(const struct lyd_node *dnode1,
+ const struct lyd_node *dnode2);
+
+ /*
+ * 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, const struct lyd_node *dnode,
+ bool show_defaults);
+
+ /*
+ * Optional callback to show the CLI node end for lists or containers.
+ *
+ * vty
+ * The vty terminal to dump the configuration to.
+ *
+ * dnode
+ * libyang data node that should be shown in the form of a CLI
+ * command.
+ */
+ void (*cli_show_end)(struct vty *vty, const struct lyd_node *dnode);
+};
+
+struct nb_dependency_callbacks {
+ void (*get_dependant_xpath)(const struct lyd_node *dnode, char *xpath);
+ void (*get_dependency_xpath)(const struct lyd_node *dnode, char *xpath);
+};
+
+/*
+ * 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 lysc_node *snode;
+
+ /* Data path of this YANG node. */
+ char xpath[XPATH_MAXLEN];
+
+ /* Priority - lower priorities are processed first. */
+ uint32_t priority;
+
+ struct nb_dependency_callbacks dep_cbs;
+
+ /* 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;
+
+ /* Flags. */
+ uint8_t flags;
+
+#ifdef HAVE_CONFD
+ /* ConfD hash value corresponding to this YANG path. */
+ int confd_hash;
+#endif
+};
+/* The YANG container or list contains only config data. */
+#define F_NB_NODE_CONFIG_ONLY 0x01
+/* The YANG list doesn't contain key leafs. */
+#define F_NB_NODE_KEYLESS_LIST 0x02
+
+/*
+ * HACK: old gcc versions (< 5.x) have a bug that prevents C99 flexible arrays
+ * from working properly on shared libraries. For those compilers, use a fixed
+ * size array to work around the problem.
+ */
+#define YANG_MODULE_MAX_NODES 2000
+
+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;
+#if defined(__GNUC__) && ((__GNUC__ - 0) < 5) && !defined(__clang__)
+ } nodes[YANG_MODULE_MAX_NODES + 1];
+#else
+ } nodes[];
+#endif
+};
+
+/* 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_NONE = 0,
+ NB_CLIENT_CLI,
+ NB_CLIENT_CONFD,
+ NB_CLIENT_SYSREPO,
+ NB_CLIENT_GRPC,
+ NB_CLIENT_PCEP,
+};
+
+/* Northbound context. */
+struct nb_context {
+ /* Northbound client. */
+ enum nb_client client;
+
+ /* Northbound user (can be NULL). */
+ const void *user;
+
+ /* Client-specific data. */
+#if 0
+ union {
+ struct {
+ } cli;
+ struct {
+ } confd;
+ struct {
+ } sysrepo;
+ struct {
+ } grpc;
+ struct {
+ } pcep;
+ } client_data;
+#endif
+};
+
+/* 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;
+ uint32_t seq;
+ 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 {
+ struct nb_context *context;
+ char comment[80];
+ struct nb_config *config;
+ struct nb_config_cbs changes;
+};
+
+/* Callback function used by nb_oper_data_iterate(). */
+typedef int (*nb_oper_data_cb)(const struct lysc_node *snode,
+ struct yang_translator *translator,
+ struct yang_data *data, void *arg);
+
+/* Iterate over direct child nodes only. */
+#define NB_OPER_DATA_ITER_NORECURSE 0x0001
+
+/* Hooks. */
+DECLARE_HOOK(nb_notification_send, (const char *xpath, struct list *arguments),
+ (xpath, arguments));
+DECLARE_HOOK(nb_client_debug_config_write, (struct vty *vty), (vty));
+DECLARE_HOOK(nb_client_debug_set_all, (uint32_t flags, bool set), (flags, set));
+
+/* Northbound debugging records */
+extern struct debug nb_dbg_cbs_config;
+extern struct debug nb_dbg_cbs_state;
+extern struct debug nb_dbg_cbs_rpc;
+extern struct debug nb_dbg_notif;
+extern struct debug nb_dbg_events;
+extern struct debug nb_dbg_libyang;
+
+/* Global running configuration. */
+extern struct nb_config *running_config;
+
+/* Wrappers for the northbound callbacks. */
+extern struct yang_data *nb_callback_get_elem(const struct nb_node *nb_node,
+ const char *xpath,
+ const void *list_entry);
+extern const void *nb_callback_get_next(const struct nb_node *nb_node,
+ const void *parent_list_entry,
+ const void *list_entry);
+extern int nb_callback_get_keys(const struct nb_node *nb_node,
+ const void *list_entry,
+ struct yang_list_keys *keys);
+extern const void *nb_callback_lookup_entry(const struct nb_node *nb_node,
+ const void *parent_list_entry,
+ const struct yang_list_keys *keys);
+extern int nb_callback_rpc(const struct nb_node *nb_node, const char *xpath,
+ const struct list *input, struct list *output,
+ char *errmsg, size_t errmsg_len);
+
+/*
+ * Create a northbound node for all YANG schema nodes.
+ */
+void nb_nodes_create(void);
+
+/*
+ * Delete all northbound nodes from all YANG schema nodes.
+ */
+void nb_nodes_delete(void);
+
+/*
+ * 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);
+
+extern void nb_node_set_dependency_cbs(const char *dependency_xpath,
+ const char *dependant_xpath,
+ struct nb_dependency_callbacks *cbs);
+
+bool nb_node_has_dependency(struct nb_node *node);
+
+/*
+ * 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).
+ *
+ * context
+ * Context of the northbound transaction.
+ *
+ * candidate
+ * Candidate configuration to validate.
+ *
+ * errmsg
+ * Buffer to store human-readable error message in case of error.
+ *
+ * errmsg_len
+ * Size of errmsg.
+ *
+ * Returns:
+ * NB_OK on success, NB_ERR_VALIDATION otherwise.
+ */
+extern int nb_candidate_validate(struct nb_context *context,
+ struct nb_config *candidate, char *errmsg,
+ size_t errmsg_len);
+
+/*
+ * 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.
+ *
+ * context
+ * Context of the northbound transaction.
+ *
+ * candidate
+ * Candidate configuration to 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().
+ *
+ * errmsg
+ * Buffer to store human-readable error message in case of error.
+ *
+ * errmsg_len
+ * Size of errmsg.
+ *
+ * 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_context *context,
+ struct nb_config *candidate,
+ const char *comment,
+ struct nb_transaction **transaction,
+ char *errmsg, size_t errmsg_len);
+
+/*
+ * 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.
+ *
+ * errmsg
+ * Buffer to store human-readable error message in case of error.
+ *
+ * errmsg_len
+ * Size of errmsg.
+ */
+extern void nb_candidate_commit_abort(struct nb_transaction *transaction,
+ char *errmsg, size_t errmsg_len);
+
+/*
+ * 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.
+ *
+ * errmsg
+ * Buffer to store human-readable error message in case of error.
+ *
+ * errmsg_len
+ * Size of errmsg.
+ */
+extern void nb_candidate_commit_apply(struct nb_transaction *transaction,
+ bool save_transaction,
+ uint32_t *transaction_id, char *errmsg,
+ size_t errmsg_len);
+
+/*
+ * 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.
+ *
+ * context
+ * Context of the northbound transaction.
+ *
+ * candidate
+ * Candidate configuration to commit. It's preserved regardless if the commit
+ * operation fails or not.
+ *
+ * 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.
+ *
+ * errmsg
+ * Buffer to store human-readable error message in case of error.
+ *
+ * errmsg_len
+ * Size of errmsg.
+ *
+ * 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_context *context,
+ struct nb_config *candidate,
+ bool save_transaction, const char *comment,
+ uint32_t *transaction_id, char *errmsg,
+ size_t errmsg_len);
+
+/*
+ * Lock the running configuration.
+ *
+ * client
+ * Northbound client.
+ *
+ * user
+ * Northbound user (can be NULL).
+ *
+ * Returns:
+ * 0 on success, -1 when the running configuration is already locked.
+ */
+extern int nb_running_lock(enum nb_client client, const void *user);
+
+/*
+ * Unlock the running configuration.
+ *
+ * client
+ * Northbound client.
+ *
+ * user
+ * Northbound user (can be NULL).
+ *
+ * Returns:
+ * 0 on success, -1 when the running configuration is already unlocked or
+ * locked by another client/user.
+ */
+extern int nb_running_unlock(enum nb_client client, const void *user);
+
+/*
+ * Check if the running configuration is locked or not for the given
+ * client/user.
+ *
+ * client
+ * Northbound client.
+ *
+ * user
+ * Northbound user (can be NULL).
+ *
+ * Returns:
+ * 0 if the running configuration is unlocked or if the client/user owns the
+ * lock, -1 otherwise.
+ */
+extern int nb_running_lock_check(enum nb_client client, const void *user);
+
+/*
+ * Iterate over operational data.
+ *
+ * xpath
+ * Data path of the YANG data we want to iterate over.
+ *
+ * translator
+ * YANG module translator (might be NULL).
+ *
+ * flags
+ * NB_OPER_DATA_ITER_ flags to control how the iteration is performed.
+ *
+ * cb
+ * Function to call with each data node.
+ *
+ * arg
+ * Arbitrary argument passed as the fourth parameter in each call to 'cb'.
+ *
+ * Returns:
+ * NB_OK on success, NB_ERR otherwise.
+ */
+extern int nb_oper_data_iterate(const char *xpath,
+ struct yang_translator *translator,
+ uint32_t flags, nb_oper_data_cb cb, void *arg);
+
+/*
+ * 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 lysc_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);
+
+/*
+ * Associate a user pointer to a configuration node.
+ *
+ * This should be called by northbound 'create' callbacks in the NB_EV_APPLY
+ * phase only.
+ *
+ * dnode
+ * libyang data node - only its XPath is used.
+ *
+ * entry
+ * Arbitrary user-specified pointer.
+ */
+extern void nb_running_set_entry(const struct lyd_node *dnode, void *entry);
+
+/*
+ * Move an entire tree of user pointer nodes.
+ *
+ * Suppose we have xpath A/B/C/D, with user pointers associated to C and D. We
+ * need to move B to be under Z, so the new xpath is Z/B/C/D. Because user
+ * pointers are indexed with their absolute path, We need to move all user
+ * pointers at and below B to their new absolute paths; this function does
+ * that.
+ *
+ * xpath_from
+ * base xpath of tree to move (A/B)
+ *
+ * xpath_to
+ * base xpath of new location of tree (Z/B)
+ */
+extern void nb_running_move_tree(const char *xpath_from, const char *xpath_to);
+
+/*
+ * Unset the user pointer associated to a configuration node.
+ *
+ * This should be called by northbound 'destroy' callbacks in the NB_EV_APPLY
+ * phase only.
+ *
+ * dnode
+ * libyang data node - only its XPath is used.
+ *
+ * Returns:
+ * The user pointer that was unset.
+ */
+extern void *nb_running_unset_entry(const struct lyd_node *dnode);
+
+/*
+ * Find the user pointer (if any) associated to a configuration node.
+ *
+ * The XPath associated to the configuration node can be provided directly or
+ * indirectly through a libyang data node.
+ *
+ * If an user point is not found, this function follows the parent nodes in the
+ * running configuration until an user pointer is found or until the root node
+ * is reached.
+ *
+ * dnode
+ * libyang data node - only its XPath is used (can be NULL if 'xpath' is
+ * provided).
+ *
+ * xpath
+ * XPath of the configuration node (can be NULL if 'dnode' is provided).
+ *
+ * abort_if_not_found
+ * When set to true, abort the program if no user pointer is found.
+ *
+ * As a rule of thumb, this parameter should be set to true in the following
+ * scenarios:
+ * - Calling this function from any northbound configuration callback during
+ * the NB_EV_APPLY phase.
+ * - Calling this function from a 'delete' northbound configuration callback
+ * during any phase.
+ *
+ * In both the above cases, the given configuration node should contain an
+ * user pointer except when there's a bug in the code, in which case it's
+ * better to abort the program right away and eliminate the need for
+ * unnecessary NULL checks.
+ *
+ * In all other cases, this parameter should be set to false and the caller
+ * should check if the function returned NULL or not.
+ *
+ * Returns:
+ * User pointer if found, NULL otherwise.
+ */
+extern void *nb_running_get_entry(const struct lyd_node *dnode,
+ const char *xpath, bool abort_if_not_found);
+
+/*
+ * Same as 'nb_running_get_entry', but doesn't search within parent nodes
+ * recursively if an user point is not found.
+ */
+extern void *nb_running_get_entry_non_rec(const struct lyd_node *dnode,
+ const char *xpath,
+ bool abort_if_not_found);
+
+/*
+ * 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);
+
+/*
+ * Validate all northbound callbacks.
+ *
+ * Some errors, like missing callbacks or invalid priorities, are fatal and
+ * can't be recovered from. Other errors, like unneeded callbacks, are logged
+ * but otherwise ignored.
+ *
+ * Whenever a YANG module is loaded after startup, *all* northbound callbacks
+ * need to be validated and not only the callbacks from the newly loaded module.
+ * This is because augmentations can change the properties of the augmented
+ * module, making mandatory the implementation of additional callbacks.
+ */
+void nb_validate_callbacks(void);
+
+/*
+ * 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.
+ *
+ * db_enabled
+ * Set this to record the transactions in the transaction log.
+ */
+extern void nb_init(struct thread_master *tm,
+ const struct frr_yang_module_info *const modules[],
+ size_t nmodules, bool db_enabled);
+
+/*
+ * Finish the northbound layer gracefully. Should be called only when the daemon
+ * is exiting.
+ */
+extern void nb_terminate(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRR_NORTHBOUND_H_ */
diff --git a/lib/northbound_cli.c b/lib/northbound_cli.c
new file mode 100644
index 0000000..56eac9d
--- /dev/null
+++ b/lib/northbound_cli.c
@@ -0,0 +1,1970 @@
+/*
+ * 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 "lib/version.h"
+#include "defaults.h"
+#include "log.h"
+#include "lib_errors.h"
+#include "command.h"
+#include "termtable.h"
+#include "db.h"
+#include "debug.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
+
+struct debug nb_dbg_cbs_config = {0, "Northbound callbacks: configuration"};
+struct debug nb_dbg_cbs_state = {0, "Northbound callbacks: state"};
+struct debug nb_dbg_cbs_rpc = {0, "Northbound callbacks: RPCs"};
+struct debug nb_dbg_notif = {0, "Northbound notifications"};
+struct debug nb_dbg_events = {0, "Northbound events"};
+struct debug nb_dbg_libyang = {0, "libyang debugging"};
+
+struct nb_config *vty_shared_candidate_config;
+static struct thread_master *master;
+
+static void vty_show_nb_errors(struct vty *vty, int error, const char *errmsg)
+{
+ vty_out(vty, "Error type: %s\n", nb_err_name(error));
+ if (strlen(errmsg) > 0)
+ vty_out(vty, "Error description: %s\n", errmsg);
+}
+
+static int nb_cli_classic_commit(struct vty *vty)
+{
+ struct nb_context context = {};
+ char errmsg[BUFSIZ] = {0};
+ int ret;
+
+ context.client = NB_CLIENT_CLI;
+ context.user = vty;
+ ret = nb_candidate_commit(&context, vty->candidate_config, true, NULL,
+ NULL, errmsg, sizeof(errmsg));
+ switch (ret) {
+ case NB_OK:
+ /* Successful commit. Print warnings (if any). */
+ if (strlen(errmsg) > 0)
+ vty_out(vty, "%s\n", errmsg);
+ break;
+ case NB_ERR_NO_CHANGES:
+ break;
+ default:
+ vty_out(vty, "%% Configuration failed.\n\n");
+ vty_show_nb_errors(vty, ret, errmsg);
+ if (vty->pending_commit)
+ vty_out(vty,
+ "The following commands were dynamically grouped into the same transaction and rejected:\n%s",
+ vty->pending_cmds_buf);
+
+ /* Regenerate candidate for consistency. */
+ nb_config_replace(vty->candidate_config, running_config, true);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ return CMD_SUCCESS;
+}
+
+static void nb_cli_pending_commit_clear(struct vty *vty)
+{
+ vty->pending_commit = 0;
+ XFREE(MTYPE_TMP, vty->pending_cmds_buf);
+ vty->pending_cmds_buflen = 0;
+ vty->pending_cmds_bufpos = 0;
+}
+
+int nb_cli_pending_commit_check(struct vty *vty)
+{
+ int ret = CMD_SUCCESS;
+
+ if (vty->pending_commit) {
+ ret = nb_cli_classic_commit(vty);
+ nb_cli_pending_commit_clear(vty);
+ }
+
+ return ret;
+}
+
+static int nb_cli_schedule_command(struct vty *vty)
+{
+ /* Append command to dynamically sized buffer of scheduled commands. */
+ if (!vty->pending_cmds_buf) {
+ vty->pending_cmds_buflen = 4096;
+ vty->pending_cmds_buf =
+ XCALLOC(MTYPE_TMP, vty->pending_cmds_buflen);
+ }
+ if ((strlen(vty->buf) + 3)
+ > (vty->pending_cmds_buflen - vty->pending_cmds_bufpos)) {
+ vty->pending_cmds_buflen *= 2;
+ vty->pending_cmds_buf =
+ XREALLOC(MTYPE_TMP, vty->pending_cmds_buf,
+ vty->pending_cmds_buflen);
+ }
+ strlcat(vty->pending_cmds_buf, "- ", vty->pending_cmds_buflen);
+ vty->pending_cmds_bufpos = strlcat(vty->pending_cmds_buf, vty->buf,
+ vty->pending_cmds_buflen);
+
+ /* Schedule the commit operation. */
+ vty->pending_commit = 1;
+
+ return CMD_SUCCESS;
+}
+
+void nb_cli_enqueue_change(struct vty *vty, const char *xpath,
+ enum nb_operation operation, const char *value)
+{
+ struct vty_cfg_change *change;
+
+ if (vty->num_cfg_changes == VTY_MAXCFGCHANGES) {
+ /* Not expected to happen. */
+ vty_out(vty,
+ "%% Exceeded the maximum number of changes (%u) for a single command\n\n",
+ VTY_MAXCFGCHANGES);
+ return;
+ }
+
+ change = &vty->cfg_changes[vty->num_cfg_changes++];
+ strlcpy(change->xpath, xpath, sizeof(change->xpath));
+ change->operation = operation;
+ change->value = value;
+}
+
+static int nb_cli_apply_changes_internal(struct vty *vty,
+ const char *xpath_base,
+ bool clear_pending)
+{
+ bool error = false;
+
+ if (xpath_base == NULL)
+ xpath_base = "";
+
+ VTY_CHECK_XPATH;
+
+ /* Edit candidate configuration. */
+ for (size_t i = 0; i < vty->num_cfg_changes; i++) {
+ struct vty_cfg_change *change = &vty->cfg_changes[i];
+ struct nb_node *nb_node;
+ char xpath[XPATH_MAXLEN];
+ struct yang_data *data;
+ int ret;
+
+ /* Handle relative XPaths. */
+ memset(xpath, 0, sizeof(xpath));
+ if (vty->xpath_index > 0
+ && (xpath_base[0] == '.' || change->xpath[0] == '.'))
+ strlcpy(xpath, VTY_CURR_XPATH, sizeof(xpath));
+ if (xpath_base[0]) {
+ if (xpath_base[0] == '.')
+ strlcat(xpath, xpath_base + 1, sizeof(xpath));
+ else
+ strlcat(xpath, xpath_base, sizeof(xpath));
+ }
+ if (change->xpath[0] == '.')
+ strlcat(xpath, change->xpath + 1, sizeof(xpath));
+ else
+ strlcpy(xpath, change->xpath, sizeof(xpath));
+
+ /* Find the northbound node associated to the data path. */
+ 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;
+ continue;
+ }
+
+ /* 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(vty->candidate_config, 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;
+ continue;
+ }
+ }
+
+ if (error) {
+ char buf[BUFSIZ];
+
+ /*
+ * Failure to edit the candidate configuration should never
+ * happen in practice, unless there's a bug in the code. When
+ * that happens, log the error but otherwise ignore it.
+ */
+ vty_out(vty, "%% Failed to edit configuration.\n\n");
+ vty_out(vty, "%s",
+ yang_print_errors(ly_native_ctx, buf, sizeof(buf)));
+ }
+
+ /*
+ * Maybe do an implicit commit when using the classic CLI mode.
+ *
+ * NOTE: the implicit commit might be scheduled to run later when
+ * too many commands are being sent at the same time. This is a
+ * protection mechanism where multiple commands are grouped into the
+ * same configuration transaction, allowing them to be processed much
+ * faster.
+ */
+ if (frr_get_cli_mode() == FRR_CLI_CLASSIC) {
+ if (clear_pending) {
+ if (vty->pending_commit)
+ return nb_cli_pending_commit_check(vty);
+ } else if (vty->pending_allowed)
+ return nb_cli_schedule_command(vty);
+ assert(!vty->pending_commit);
+ return nb_cli_classic_commit(vty);
+ }
+
+ return CMD_SUCCESS;
+}
+
+int nb_cli_apply_changes(struct vty *vty, const char *xpath_base_fmt, ...)
+{
+ char xpath_base[XPATH_MAXLEN] = {};
+
+ /* Parse the base XPath format string. */
+ if (xpath_base_fmt) {
+ va_list ap;
+
+ va_start(ap, xpath_base_fmt);
+ vsnprintf(xpath_base, sizeof(xpath_base), xpath_base_fmt, ap);
+ va_end(ap);
+ }
+ return nb_cli_apply_changes_internal(vty, xpath_base, false);
+}
+
+int nb_cli_apply_changes_clear_pending(struct vty *vty,
+ const char *xpath_base_fmt, ...)
+{
+ char xpath_base[XPATH_MAXLEN] = {};
+
+ /* Parse the base XPath format string. */
+ if (xpath_base_fmt) {
+ va_list ap;
+
+ va_start(ap, xpath_base_fmt);
+ vsnprintf(xpath_base, sizeof(xpath_base), xpath_base_fmt, ap);
+ va_end(ap);
+ }
+ return nb_cli_apply_changes_internal(vty, xpath_base, true);
+}
+
+int nb_cli_rpc(struct vty *vty, const char *xpath, struct list *input,
+ struct list *output)
+{
+ struct nb_node *nb_node;
+ int ret;
+ char errmsg[BUFSIZ] = {0};
+
+ 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_callback_rpc(nb_node, xpath, input, output, errmsg,
+ sizeof(errmsg));
+ switch (ret) {
+ case NB_OK:
+ return CMD_SUCCESS;
+ default:
+ if (strlen(errmsg))
+ vty_show_nb_errors(vty, ret, errmsg);
+ return CMD_WARNING;
+ }
+}
+
+void nb_cli_confirmed_commit_clean(struct vty *vty)
+{
+ thread_cancel(&vty->t_confirmed_commit_timeout);
+ nb_config_free(vty->confirmed_commit_rollback);
+ vty->confirmed_commit_rollback = NULL;
+}
+
+int nb_cli_confirmed_commit_rollback(struct vty *vty)
+{
+ struct nb_context context = {};
+ uint32_t transaction_id;
+ char errmsg[BUFSIZ] = {0};
+ int ret;
+
+ /* Perform the rollback. */
+ context.client = NB_CLIENT_CLI;
+ context.user = vty;
+ ret = nb_candidate_commit(
+ &context, vty->confirmed_commit_rollback, true,
+ "Rollback to previous configuration - confirmed commit has timed out",
+ &transaction_id, errmsg, sizeof(errmsg));
+ if (ret == NB_OK) {
+ vty_out(vty,
+ "Rollback performed successfully (Transaction ID #%u).\n",
+ transaction_id);
+ /* Print warnings (if any). */
+ if (strlen(errmsg) > 0)
+ vty_out(vty, "%s\n", errmsg);
+ } else {
+ vty_out(vty,
+ "Failed to rollback to previous configuration.\n\n");
+ vty_show_nb_errors(vty, ret, errmsg);
+ }
+
+ return ret;
+}
+
+static void nb_cli_confirmed_commit_timeout(struct thread *thread)
+{
+ struct vty *vty = THREAD_ARG(thread);
+
+ /* XXX: broadcast this message to all logged-in users? */
+ vty_out(vty,
+ "\nConfirmed commit has timed out, rolling back to previous configuration.\n\n");
+
+ nb_cli_confirmed_commit_rollback(vty);
+ nb_cli_confirmed_commit_clean(vty);
+}
+
+static int nb_cli_commit(struct vty *vty, bool force,
+ unsigned int confirmed_timeout, char *comment)
+{
+ struct nb_context context = {};
+ uint32_t transaction_id = 0;
+ char errmsg[BUFSIZ] = {0};
+ int ret;
+
+ /* Check if there's a pending confirmed commit. */
+ if (vty->t_confirmed_commit_timeout) {
+ if (confirmed_timeout) {
+ /* Reset timeout if "commit confirmed" is used again. */
+ vty_out(vty,
+ "%% Resetting confirmed-commit timeout to %u minute(s)\n\n",
+ confirmed_timeout);
+
+ thread_cancel(&vty->t_confirmed_commit_timeout);
+ thread_add_timer(master,
+ nb_cli_confirmed_commit_timeout, vty,
+ confirmed_timeout * 60,
+ &vty->t_confirmed_commit_timeout);
+ } else {
+ /* Accept commit confirmation. */
+ vty_out(vty, "%% Commit complete.\n\n");
+ nb_cli_confirmed_commit_clean(vty);
+ }
+ return CMD_SUCCESS;
+ }
+
+ /* "force" parameter. */
+ 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;
+ }
+
+ /* "confirm" parameter. */
+ if (confirmed_timeout) {
+ vty->confirmed_commit_rollback = nb_config_dup(running_config);
+
+ vty->t_confirmed_commit_timeout = NULL;
+ thread_add_timer(master, nb_cli_confirmed_commit_timeout, vty,
+ confirmed_timeout * 60,
+ &vty->t_confirmed_commit_timeout);
+ }
+
+ context.client = NB_CLIENT_CLI;
+ context.user = vty;
+ ret = nb_candidate_commit(&context, vty->candidate_config, true,
+ comment, &transaction_id, errmsg,
+ sizeof(errmsg));
+
+ /* 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);
+ /* Print warnings (if any). */
+ if (strlen(errmsg) > 0)
+ vty_out(vty, "%s\n", errmsg);
+ 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.\n\n");
+ vty_show_nb_errors(vty, ret, errmsg);
+ 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;
+ char buf[BUFSIZ];
+ LY_ERR err;
+
+ 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;
+ err = lyd_parse_data_path(ly_ctx, path, ly_format,
+ LYD_PARSE_ONLY | LYD_PARSE_NO_STATE,
+ 0, &dnode);
+ if (err || !dnode) {
+ flog_warn(EC_LIB_LIBYANG, "%s: lyd_parse_path() failed",
+ __func__);
+ vty_out(vty, "%% Failed to load configuration:\n\n");
+ vty_out(vty, "%s",
+ yang_print_errors(ly_native_ctx, buf,
+ sizeof(buf)));
+ 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_out(vty, "%s",
+ yang_print_errors(ly_native_ctx, buf, sizeof(buf)));
+ 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;
+ char buf[BUFSIZ];
+
+ 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_out(vty, "%s",
+ yang_print_errors(ly_native_ctx, buf, sizeof(buf)));
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+/* Prepare the configuration for display. */
+void nb_cli_show_config_prepare(struct nb_config *config, bool with_defaults)
+{
+ /* Nothing to do for daemons that don't implement any YANG module. */
+ if (config->dnode == NULL)
+ return;
+
+ /*
+ * Call lyd_validate() only to create default child nodes, ignoring
+ * any possible validation error. This doesn't need to be done when
+ * displaying the running configuration since it's always fully
+ * validated.
+ */
+ if (config != running_config)
+ (void)lyd_validate_all(&config->dnode, ly_native_ctx,
+ LYD_VALIDATE_NO_STATE, NULL);
+}
+
+static int lyd_node_cmp(const struct lyd_node **dnode1,
+ const struct lyd_node **dnode2)
+{
+ struct nb_node *nb_node = (*dnode1)->schema->priv;
+
+ return nb_node->cbs.cli_cmp(*dnode1, *dnode2);
+}
+
+static void show_dnode_children_cmds(struct vty *vty,
+ const struct lyd_node *root,
+ bool with_defaults)
+{
+ struct nb_node *nb_node, *sort_node = NULL;
+ struct listnode *listnode;
+ struct lyd_node *child;
+ struct list *sort_list;
+ void *data;
+
+ LY_LIST_FOR (lyd_child(root), child) {
+ nb_node = child->schema->priv;
+
+ /*
+ * We finished processing current list,
+ * it's time to print the config.
+ */
+ if (sort_node && nb_node != sort_node) {
+ list_sort(sort_list,
+ (int (*)(const void **,
+ const void **))lyd_node_cmp);
+
+ for (ALL_LIST_ELEMENTS_RO(sort_list, listnode, data))
+ nb_cli_show_dnode_cmds(vty, data,
+ with_defaults);
+
+ list_delete(&sort_list);
+ sort_node = NULL;
+ }
+
+ /*
+ * If the config needs to be sorted,
+ * then add the dnode to the sorting
+ * list for later processing.
+ */
+ if (nb_node && nb_node->cbs.cli_cmp) {
+ if (!sort_node) {
+ sort_node = nb_node;
+ sort_list = list_new();
+ }
+
+ listnode_add(sort_list, child);
+ continue;
+ }
+
+ nb_cli_show_dnode_cmds(vty, child, with_defaults);
+ }
+
+ if (sort_node) {
+ list_sort(sort_list,
+ (int (*)(const void **, const void **))lyd_node_cmp);
+
+ for (ALL_LIST_ELEMENTS_RO(sort_list, listnode, data))
+ nb_cli_show_dnode_cmds(vty, data, with_defaults);
+
+ list_delete(&sort_list);
+ sort_node = NULL;
+ }
+}
+
+void nb_cli_show_dnode_cmds(struct vty *vty, const struct lyd_node *root,
+ bool with_defaults)
+{
+ struct nb_node *nb_node;
+
+ if (!with_defaults && yang_dnode_is_default_recursive(root))
+ return;
+
+ nb_node = root->schema->priv;
+
+ if (nb_node && nb_node->cbs.cli_show)
+ (*nb_node->cbs.cli_show)(vty, root, with_defaults);
+
+ if (!(root->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST | LYS_ANYDATA)))
+ show_dnode_children_cmds(vty, root, with_defaults);
+
+ if (nb_node && nb_node->cbs.cli_show_end)
+ (*nb_node->cbs.cli_show_end)(vty, root);
+}
+
+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", frr_defaults_profile());
+
+ LY_LIST_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 = 0;
+
+ 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;
+ }
+
+ SET_FLAG(options, LYD_PRINT_WITHSIBLINGS);
+ if (with_defaults)
+ SET_FLAG(options, LYD_PRINT_WD_ALL);
+ else
+ SET_FLAG(options, LYD_PRINT_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)
+{
+ nb_cli_show_config_prepare(config, 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;
+ }
+ if (fchmod(fd, CONFIGFILE_MASK) != 0) {
+ flog_warn(EC_LIB_SYSTEM_CALL,
+ "%s: fchmod() failed: %s(%d):", __func__,
+ safe_strerror(errno), 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")
+{
+ return vty_config_enter(vty, true, true);
+}
+
+/* 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")
+{
+ return vty_config_enter(vty, true, false);
+}
+
+DEFPY (config_commit,
+ config_commit_cmd,
+ "commit [{force$force|confirmed (1-60)}]",
+ "Commit changes into the running configuration\n"
+ "Force commit even if the candidate is outdated\n"
+ "Rollback this commit unless there is a confirming commit\n"
+ "Timeout in minutes for the commit to be confirmed\n")
+{
+ return nb_cli_commit(vty, !!force, confirmed, NULL);
+}
+
+DEFPY (config_commit_comment,
+ config_commit_comment_cmd,
+ "commit [{force$force|confirmed (1-60)}] comment LINE...",
+ "Commit changes into the running configuration\n"
+ "Force commit even if the candidate is outdated\n"
+ "Rollback this commit unless there is a confirming commit\n"
+ "Timeout in minutes for the commit to be confirmed\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, confirmed, 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")
+{
+ struct nb_context context = {};
+ char errmsg[BUFSIZ] = {0};
+ int ret;
+
+ context.client = NB_CLIENT_CLI;
+ context.user = vty;
+ ret = nb_candidate_validate(&context, vty->candidate_config, errmsg,
+ sizeof(errmsg));
+ if (ret != NB_OK) {
+ vty_out(vty,
+ "%% Failed to validate candidate configuration.\n\n");
+ vty_show_nb_errors(vty, ret, errmsg);
+ 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-4294967295)$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_candidate_section,
+ show_config_candidate_section_cmd,
+ "show",
+ SHOW_STR)
+{
+ struct lyd_node *dnode;
+
+ /* Top-level configuration node, display everything. */
+ if (vty->xpath_index == 0)
+ return nb_cli_show_config(vty, vty->candidate_config,
+ NB_CFG_FMT_CMDS, NULL, false);
+
+ /* Display only the current section of the candidate configuration. */
+ dnode = yang_dnode_get(vty->candidate_config->dnode, VTY_CURR_XPATH);
+ if (!dnode)
+ /* Shouldn't happen. */
+ return CMD_WARNING;
+
+ nb_cli_show_dnode_cmds(vty, dnode, 0);
+ vty_out(vty, "!\n");
+
+ return CMD_SUCCESS;
+}
+
+DEFPY (show_config_compare,
+ show_config_compare_cmd,
+ "show configuration compare\
+ <\
+ candidate$c1_candidate\
+ |running$c1_running\
+ |transaction (1-4294967295)$c1_tid\
+ >\
+ <\
+ candidate$c2_candidate\
+ |running$c2_running\
+ |transaction (1-4294967295)$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-4294967295)$c1_tid\
+ >\
+ <\
+ running$c2_running\
+ |transaction (1-4294967295)$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-4294967295)$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 */
+}
+
+static int nb_cli_oper_data_cb(const struct lysc_node *snode,
+ struct yang_translator *translator,
+ struct yang_data *data, void *arg)
+{
+ struct lyd_node *dnode = arg;
+ struct ly_ctx *ly_ctx;
+
+ if (translator) {
+ int ret;
+
+ ret = yang_translate_xpath(translator,
+ YANG_TRANSLATE_FROM_NATIVE,
+ data->xpath, sizeof(data->xpath));
+ switch (ret) {
+ case YANG_TRANSLATE_SUCCESS:
+ break;
+ case YANG_TRANSLATE_NOTFOUND:
+ goto exit;
+ case YANG_TRANSLATE_FAILURE:
+ goto error;
+ }
+
+ ly_ctx = translator->ly_ctx;
+ } else
+ ly_ctx = ly_native_ctx;
+
+ LY_ERR err =
+ lyd_new_path(dnode, ly_ctx, data->xpath, (void *)data->value,
+ LYD_NEW_PATH_UPDATE, &dnode);
+ if (err) {
+ flog_warn(EC_LIB_LIBYANG, "%s: lyd_new_path(%s) failed: %s",
+ __func__, data->xpath, ly_errmsg(ly_native_ctx));
+ goto error;
+ }
+
+exit:
+ yang_data_free(data);
+ return NB_OK;
+
+error:
+ yang_data_free(data);
+ return NB_ERR;
+}
+
+DEFPY (show_yang_operational_data,
+ show_yang_operational_data_cmd,
+ "show yang operational-data XPATH$xpath\
+ [{\
+ format <json$json|xml$xml>\
+ |translate WORD$translator_family\
+ |with-config$with_config\
+ }]",
+ SHOW_STR
+ "YANG information\n"
+ "Show YANG operational data\n"
+ "XPath expression specifying the YANG data path\n"
+ "Set the output format\n"
+ "JavaScript Object Notation\n"
+ "Extensible Markup Language\n"
+ "Translate operational data\n"
+ "YANG module translator\n"
+ "Merge configuration data\n")
+{
+ LYD_FORMAT format;
+ struct yang_translator *translator = NULL;
+ struct ly_ctx *ly_ctx;
+ struct lyd_node *dnode;
+ char *strp;
+ uint32_t print_options = LYD_PRINT_WITHSIBLINGS;
+
+ if (xml)
+ format = LYD_XML;
+ else
+ format = LYD_JSON;
+
+ 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;
+
+ /* Obtain data. */
+ dnode = yang_dnode_new(ly_ctx, false);
+ if (nb_oper_data_iterate(xpath, translator, 0, nb_cli_oper_data_cb,
+ dnode)
+ != NB_OK) {
+ vty_out(vty, "%% Failed to fetch operational data.\n");
+ yang_dnode_free(dnode);
+ return CMD_WARNING;
+ }
+
+ if (with_config && yang_dnode_exists(running_config->dnode, xpath)) {
+ struct lyd_node *config_dnode =
+ yang_dnode_get(running_config->dnode, xpath);
+ if (config_dnode != NULL) {
+ lyd_merge_tree(&dnode, yang_dnode_dup(config_dnode),
+ LYD_MERGE_DESTRUCT);
+ print_options |= LYD_PRINT_WD_ALL;
+ }
+ }
+
+ (void)lyd_validate_all(&dnode, ly_ctx, 0, NULL);
+
+ /* Display the data. */
+ if (lyd_print_mem(&strp, dnode, format, print_options) != 0 || !strp) {
+ vty_out(vty, "%% Failed to display operational data.\n");
+ yang_dnode_free(dnode);
+ return CMD_WARNING;
+ }
+ vty_out(vty, "%s", strp);
+ free(strp);
+ yang_dnode_free(dnode);
+
+ return CMD_SUCCESS;
+}
+
+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' : ' ',
+ LY_ARRAY_COUNT(module->deviated_by) ? 'D' : ' ');
+
+ ttable_add_row(tt, "%s|%s|%s|%s|%s", module->name,
+ (module->parsed->version == 2) ? "1.1" : "1.0",
+ module->revision ? module->revision : "-", 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 <compiled$compiled|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 compiled module in YANG format\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_latest(ly_ctx, module_name);
+ 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 (compiled)
+ format = LYS_OUT_YANG_COMPILED;
+ else if (tree)
+ format = LYS_OUT_TREE;
+ else {
+ vty_out(vty,
+ "%% libyang v2 does not currently support summary\n");
+ return CMD_WARNING;
+ }
+
+ if (lys_print_mem(&strp, module, format, 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_context context = {};
+ struct nb_config *candidate;
+ char comment[80];
+ char errmsg[BUFSIZ] = {0};
+ 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);
+
+ context.client = NB_CLIENT_CLI;
+ context.user = vty;
+ ret = nb_candidate_commit(&context, candidate, true, comment, NULL,
+ errmsg, sizeof(errmsg));
+ nb_config_free(candidate);
+ switch (ret) {
+ case NB_OK:
+ vty_out(vty,
+ "%% Configuration was successfully rolled back.\n\n");
+ /* Print warnings (if any). */
+ if (strlen(errmsg) > 0)
+ vty_out(vty, "%s\n", errmsg);
+ 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_show_nb_errors(vty, ret, errmsg);
+ return CMD_WARNING;
+ }
+}
+#endif /* HAVE_CONFIG_ROLLBACKS */
+
+DEFPY (rollback_config,
+ rollback_config_cmd,
+ "rollback configuration (1-4294967295)$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. */
+static struct debug *nb_debugs[] = {
+ &nb_dbg_cbs_config, &nb_dbg_cbs_state, &nb_dbg_cbs_rpc,
+ &nb_dbg_notif, &nb_dbg_events, &nb_dbg_libyang,
+};
+
+static const char *const nb_debugs_conflines[] = {
+ "debug northbound callbacks configuration",
+ "debug northbound callbacks state",
+ "debug northbound callbacks rpc",
+ "debug northbound notifications",
+ "debug northbound events",
+ "debug northbound libyang",
+};
+
+DEFINE_HOOK(nb_client_debug_set_all, (uint32_t flags, bool set), (flags, set));
+
+static void nb_debug_set_all(uint32_t flags, bool set)
+{
+ for (unsigned int i = 0; i < array_size(nb_debugs); i++) {
+ DEBUG_FLAGS_SET(nb_debugs[i], flags, set);
+
+ /* If all modes have been turned off, don't preserve options. */
+ if (!DEBUG_MODE_CHECK(nb_debugs[i], DEBUG_MODE_ALL))
+ DEBUG_CLEAR(nb_debugs[i]);
+ }
+
+ hook_call(nb_client_debug_set_all, flags, set);
+}
+
+DEFPY (debug_nb,
+ debug_nb_cmd,
+ "[no] debug northbound\
+ [<\
+ callbacks$cbs [{configuration$cbs_cfg|state$cbs_state|rpc$cbs_rpc}]\
+ |notifications$notifications\
+ |events$events\
+ |libyang$libyang\
+ >]",
+ NO_STR
+ DEBUG_STR
+ "Northbound debugging\n"
+ "Callbacks\n"
+ "Configuration\n"
+ "State\n"
+ "RPC\n"
+ "Notifications\n"
+ "Events\n"
+ "libyang debugging\n")
+{
+ uint32_t mode = DEBUG_NODE2MODE(vty->node);
+
+ if (cbs) {
+ bool none = (!cbs_cfg && !cbs_state && !cbs_rpc);
+
+ if (none || cbs_cfg)
+ DEBUG_MODE_SET(&nb_dbg_cbs_config, mode, !no);
+ if (none || cbs_state)
+ DEBUG_MODE_SET(&nb_dbg_cbs_state, mode, !no);
+ if (none || cbs_rpc)
+ DEBUG_MODE_SET(&nb_dbg_cbs_rpc, mode, !no);
+ }
+ if (notifications)
+ DEBUG_MODE_SET(&nb_dbg_notif, mode, !no);
+ if (events)
+ DEBUG_MODE_SET(&nb_dbg_events, mode, !no);
+ if (libyang) {
+ DEBUG_MODE_SET(&nb_dbg_libyang, mode, !no);
+ yang_debugging_set(!no);
+ }
+
+ /* no specific debug --> act on all of them */
+ if (strmatch(argv[argc - 1]->text, "northbound")) {
+ nb_debug_set_all(mode, !no);
+ yang_debugging_set(!no);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFINE_HOOK(nb_client_debug_config_write, (struct vty *vty), (vty));
+
+static int nb_debug_config_write(struct vty *vty)
+{
+ for (unsigned int i = 0; i < array_size(nb_debugs); i++)
+ if (DEBUG_MODE_CHECK(nb_debugs[i], DEBUG_MODE_CONF))
+ vty_out(vty, "%s\n", nb_debugs_conflines[i]);
+
+ hook_call(nb_client_debug_config_write, vty);
+
+ return 1;
+}
+
+static struct debug_callbacks nb_dbg_cbs = {.debug_set_all = nb_debug_set_all};
+static struct cmd_node nb_debug_node = {
+ .name = "northbound debug",
+ .node = NORTHBOUND_DEBUG_NODE,
+ .prompt = "",
+ .config_write = nb_debug_config_write,
+};
+
+void nb_cli_install_default(int node)
+{
+ _install_element(node, &show_config_candidate_section_cmd);
+
+ 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(struct thread_master *tm)
+{
+ master = tm;
+
+ /* Initialize the shared candidate configuration. */
+ vty_shared_candidate_config = nb_config_new(NULL);
+
+ debug_init(&nb_dbg_cbs);
+
+ install_node(&nb_debug_node);
+ install_element(ENABLE_NODE, &debug_nb_cmd);
+ install_element(CONFIG_NODE, &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_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(ENABLE_NODE, &show_config_running_cmd);
+ install_element(CONFIG_NODE, &yang_module_translator_load_cmd);
+ install_element(CONFIG_NODE, &yang_module_translator_unload_cmd);
+ install_element(ENABLE_NODE, &show_yang_operational_data_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);
+}
diff --git a/lib/northbound_cli.h b/lib/northbound_cli.h
new file mode 100644
index 0000000..e472425
--- /dev/null
+++ b/lib/northbound_cli.h
@@ -0,0 +1,159 @@
+/*
+ * 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"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* 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;
+
+/*
+ * Enqueue change to be applied in the candidate configuration.
+ *
+ * vty
+ * The vty context.
+ *
+ * xpath
+ * XPath (absolute or relative) of the configuration option being edited.
+ *
+ * operation
+ * Operation to apply (either NB_OP_CREATE, NB_OP_MODIFY or NB_OP_DELETE).
+ *
+ * value
+ * 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.
+ */
+extern void nb_cli_enqueue_change(struct vty *vty, const char *xpath,
+ enum nb_operation operation,
+ const char *value);
+
+/*
+ * Apply enqueued changes to the candidate configuration, do not batch,
+ * and apply any pending commits along with the currently enqueued.
+ *
+ * vty
+ * The vty context.
+ *
+ * xpath_base_fmt
+ * Prepend the given XPath (absolute or relative) to all enqueued
+ * configuration changes. This is an optional parameter.
+ *
+ * Returns:
+ * CMD_SUCCESS on success, CMD_WARNING_CONFIG_FAILED otherwise.
+ */
+extern int nb_cli_apply_changes_clear_pending(struct vty *vty,
+ const char *xpath_base_fmt, ...);
+
+/*
+ * Apply enqueued changes to the candidate configuration, this function
+ * may not immediately apply the changes, instead adding them to a pending
+ * queue.
+ *
+ * vty
+ * The vty context.
+ *
+ * xpath_base_fmt
+ * Prepend the given XPath (absolute or relative) to all enqueued
+ * configuration changes. This is an optional parameter.
+ *
+ * Returns:
+ * CMD_SUCCESS on success, CMD_WARNING_CONFIG_FAILED otherwise.
+ */
+extern int nb_cli_apply_changes(struct vty *vty, const char *xpath_base_fmt,
+ ...);
+
+/*
+ * Execute a YANG RPC or Action.
+ *
+ * vty
+ * The vty terminal to dump any error.
+ *
+ * xpath
+ * XPath of the YANG RPC or Action node.
+ *
+ * input
+ * List of 'yang_data' structures containing the RPC input parameters. It
+ * can be set to NULL when there are no input parameters.
+ *
+ * output
+ * List of 'yang_data' structures used to retrieve the RPC output parameters.
+ * It can be set to NULL when it's known that the given YANG RPC or Action
+ * doesn't have any output parameters.
+ *
+ * Returns:
+ * CMD_SUCCESS on success, CMD_WARNING otherwise.
+ */
+extern int nb_cli_rpc(struct vty *vty, const char *xpath, struct list *input,
+ struct list *output);
+
+/*
+ * Show CLI commands 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 CLI commands.
+ *
+ * show_defaults
+ * Specify whether to display default configuration values or not.
+ */
+extern void nb_cli_show_dnode_cmds(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+
+/*
+ * Perform pending commit, if any.
+ *
+ * vty
+ * The vty context.
+ *
+ * Returns
+ * CMD_SUCCESS on success (or no pending), CMD_WARNING_CONFIG_FAILED
+ * otherwise.
+ */
+extern int nb_cli_pending_commit_check(struct vty *vty);
+
+/* Prototypes of internal functions. */
+extern void nb_cli_show_config_prepare(struct nb_config *config,
+ bool with_defaults);
+extern void nb_cli_confirmed_commit_clean(struct vty *vty);
+extern int nb_cli_confirmed_commit_rollback(struct vty *vty);
+extern void nb_cli_install_default(int node);
+extern void nb_cli_init(struct thread_master *tm);
+extern void nb_cli_terminate(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRR_NORTHBOUND_CLI_H_ */
diff --git a/lib/northbound_confd.c b/lib/northbound_confd.c
new file mode 100644
index 0000000..27eaefd
--- /dev/null
+++ b/lib/northbound_confd.c
@@ -0,0 +1,1505 @@
+/*
+ * 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 "command.h"
+#include "debug.h"
+#include "libfrr.h"
+#include "lib/version.h"
+#include "northbound.h"
+
+#include <confd_lib.h>
+#include <confd_cdb.h>
+#include <confd_dp.h>
+#include <confd_maapi.h>
+
+DEFINE_MTYPE_STATIC(LIB, CONFD, "ConfD module");
+
+static struct debug nb_dbg_client_confd = {0, "Northbound client: ConfD"};
+
+static struct thread_master *master;
+static struct sockaddr confd_addr;
+static int cdb_sub_sock, dp_ctl_sock, dp_worker_sock;
+static struct thread *t_cdb_sub, *t_dp_ctl, *t_dp_worker;
+static struct confd_daemon_ctx *dctx;
+static struct confd_notification_ctx *live_ctx;
+static bool confd_connected;
+static struct list *confd_spoints;
+static struct nb_transaction *transaction;
+
+static void frr_confd_finish_cdb(void);
+static void frr_confd_finish_dp(void);
+static int frr_confd_finish(void);
+
+#define flog_err_confd(funcname) \
+ flog_err(EC_LIB_LIBCONFD, "%s: %s() failed: %s (%d): %s", __func__, \
+ (funcname), confd_strerror(confd_errno), confd_errno, \
+ confd_lasterr())
+
+
+/* ------------ Utils ------------ */
+
+/* Get XPath string from ConfD hashed keypath. */
+static void frr_confd_get_xpath(const confd_hkeypath_t *kp, char *xpath,
+ size_t len)
+{
+ char *p;
+
+ confd_xpath_pp_kpath(xpath, len, 0, kp);
+
+ /*
+ * Replace double quotes by single quotes (the format accepted by the
+ * northbound API).
+ */
+ p = xpath;
+ while ((p = strchr(p, '"')) != NULL)
+ *p++ = '\'';
+}
+
+/* Convert ConfD binary value to a string. */
+static int frr_confd_val2str(const char *xpath, const confd_value_t *value,
+ char *string, size_t string_size)
+{
+ struct confd_cs_node *csp;
+
+ csp = confd_cs_node_cd(NULL, xpath);
+ if (!csp) {
+ flog_err_confd("confd_cs_node_cd");
+ return -1;
+ }
+ if (confd_val2str(csp->info.type, value, string, string_size)
+ == CONFD_ERR) {
+ flog_err_confd("confd_val2str");
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Obtain list entry from ConfD hashed keypath. */
+static int frr_confd_hkeypath_get_list_entry(const confd_hkeypath_t *kp,
+ struct nb_node *nb_node,
+ const void **list_entry)
+{
+ struct nb_node *nb_node_list;
+ int parent_lists = 0;
+ int curr_list = 0;
+
+ *list_entry = NULL;
+
+ /*
+ * Count the number of YANG lists in the path, disconsidering the
+ * last element.
+ */
+ nb_node_list = nb_node;
+ while (nb_node_list->parent_list) {
+ nb_node_list = nb_node_list->parent_list;
+ parent_lists++;
+ }
+ if (nb_node->snode->nodetype != LYS_LIST && parent_lists == 0)
+ return 0;
+
+ /* Start from the beginning and move down the tree. */
+ for (int i = kp->len; i >= 0; i--) {
+ struct yang_list_keys keys;
+
+ /* Not a YANG list. */
+ if (kp->v[i][0].type != C_BUF)
+ continue;
+
+ /* Obtain list keys. */
+ memset(&keys, 0, sizeof(keys));
+ for (int j = 0; kp->v[i][j].type != C_NOEXISTS; j++) {
+ strlcpy(keys.key[keys.num],
+ (char *)kp->v[i][j].val.buf.ptr,
+ sizeof(keys.key[keys.num]));
+ keys.num++;
+ }
+
+ /* Obtain northbound node associated to the YANG list. */
+ nb_node_list = nb_node;
+ for (int j = curr_list; j < parent_lists; j++)
+ nb_node_list = nb_node_list->parent_list;
+
+ /* Obtain list entry. */
+ if (!CHECK_FLAG(nb_node_list->flags, F_NB_NODE_KEYLESS_LIST)) {
+ *list_entry = nb_callback_lookup_entry(
+ nb_node, *list_entry, &keys);
+ if (*list_entry == NULL)
+ return -1;
+ } else {
+ unsigned long ptr_ulong;
+
+ /* Retrieve list entry from pseudo-key (string). */
+ if (sscanf(keys.key[0], "%lu", &ptr_ulong) != 1)
+ return -1;
+ *list_entry = (const void *)ptr_ulong;
+ }
+
+ curr_list++;
+ }
+
+ return 0;
+}
+
+/* Fill the current date and time into a confd_datetime structure. */
+static void getdatetime(struct confd_datetime *datetime)
+{
+ struct tm tm;
+ struct timeval tv;
+
+ gettimeofday(&tv, NULL);
+ gmtime_r(&tv.tv_sec, &tm);
+
+ memset(datetime, 0, sizeof(*datetime));
+ datetime->year = 1900 + tm.tm_year;
+ datetime->month = tm.tm_mon + 1;
+ datetime->day = tm.tm_mday;
+ datetime->sec = tm.tm_sec;
+ datetime->micro = tv.tv_usec;
+ datetime->timezone = 0;
+ datetime->timezone_minutes = 0;
+ datetime->hour = tm.tm_hour;
+ datetime->min = tm.tm_min;
+}
+
+/* ------------ CDB code ------------ */
+
+struct cdb_iter_args {
+ struct nb_config *candidate;
+ bool error;
+};
+
+static enum cdb_iter_ret
+frr_confd_cdb_diff_iter(confd_hkeypath_t *kp, enum cdb_iter_op cdb_op,
+ confd_value_t *oldv, confd_value_t *newv, void *args)
+{
+ char xpath[XPATH_MAXLEN];
+ struct nb_node *nb_node;
+ enum nb_operation nb_op;
+ struct cdb_iter_args *iter_args = args;
+ char value_str[YANG_VALUE_MAXLEN];
+ struct yang_data *data;
+ char *sb1, *sb2;
+ int ret;
+
+ frr_confd_get_xpath(kp, xpath, sizeof(xpath));
+
+ /*
+ * HACK: obtain value of leaf-list elements from the XPath due to
+ * a bug in the ConfD API.
+ */
+ value_str[0] = '\0';
+ sb1 = strrchr(xpath, '[');
+ sb2 = strrchr(xpath, ']');
+ if (sb1 && sb2 && !strchr(sb1, '=')) {
+ *sb2 = '\0';
+ strlcpy(value_str, sb1 + 1, sizeof(value_str));
+ *sb1 = '\0';
+ }
+
+ nb_node = nb_node_find(xpath);
+ if (!nb_node) {
+ flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
+ "%s: unknown data path: %s", __func__, xpath);
+ iter_args->error = true;
+ return ITER_STOP;
+ }
+
+ /* Map operation values. */
+ switch (cdb_op) {
+ case MOP_CREATED:
+ nb_op = NB_OP_CREATE;
+ break;
+ case MOP_DELETED:
+ nb_op = NB_OP_DESTROY;
+ break;
+ case MOP_VALUE_SET:
+ if (nb_operation_is_valid(NB_OP_MODIFY, nb_node->snode))
+ nb_op = NB_OP_MODIFY;
+ else
+ /* Ignore list keys modifications. */
+ return ITER_RECURSE;
+ break;
+ case MOP_MOVED_AFTER:
+ nb_op = NB_OP_MOVE;
+ break;
+ case MOP_MODIFIED:
+ /* We're not interested on this. */
+ return ITER_RECURSE;
+ default:
+ flog_err(EC_LIB_DEVELOPMENT,
+ "%s: unexpected operation %u [xpath %s]", __func__,
+ cdb_op, xpath);
+ iter_args->error = true;
+ return ITER_STOP;
+ }
+
+ /* Convert ConfD value to a string. */
+ if (nb_node->snode->nodetype != LYS_LEAFLIST && newv
+ && frr_confd_val2str(nb_node->xpath, newv, value_str,
+ sizeof(value_str))
+ != 0) {
+ flog_err(EC_LIB_CONFD_DATA_CONVERT,
+ "%s: failed to convert ConfD value to a string",
+ __func__);
+ iter_args->error = true;
+ return ITER_STOP;
+ }
+
+ /* Edit the candidate configuration. */
+ data = yang_data_new(xpath, value_str);
+ ret = nb_candidate_edit(iter_args->candidate, nb_node, nb_op, xpath,
+ NULL, data);
+ yang_data_free(data);
+ if (ret != NB_OK) {
+ flog_warn(
+ EC_LIB_NB_CANDIDATE_EDIT_ERROR,
+ "%s: failed to edit candidate configuration: operation [%s] xpath [%s]",
+ __func__, nb_operation_name(nb_op), xpath);
+ iter_args->error = true;
+ return ITER_STOP;
+ }
+
+ return ITER_RECURSE;
+}
+
+static void frr_confd_cdb_read_cb_prepare(int fd, int *subp, int reslen)
+{
+ struct nb_context context = {};
+ struct nb_config *candidate;
+ struct cdb_iter_args iter_args;
+ char errmsg[BUFSIZ] = {0};
+ int ret;
+
+ candidate = nb_config_dup(running_config);
+
+ /* Iterate over all configuration changes. */
+ iter_args.candidate = candidate;
+ iter_args.error = false;
+ for (int i = 0; i < reslen; i++) {
+ if (cdb_diff_iterate(fd, subp[i], frr_confd_cdb_diff_iter,
+ ITER_WANT_PREV, &iter_args)
+ != CONFD_OK) {
+ flog_err_confd("cdb_diff_iterate");
+ }
+ }
+ free(subp);
+
+ if (iter_args.error) {
+ nb_config_free(candidate);
+
+ if (cdb_sub_abort_trans(
+ cdb_sub_sock, CONFD_ERRCODE_APPLICATION_INTERNAL, 0,
+ 0, "Couldn't apply configuration changes")
+ != CONFD_OK) {
+ flog_err_confd("cdb_sub_abort_trans");
+ return;
+ }
+ return;
+ }
+
+ /*
+ * Validate the configuration changes and allocate all resources
+ * required to apply them.
+ */
+ transaction = NULL;
+ context.client = NB_CLIENT_CONFD;
+ ret = nb_candidate_commit_prepare(&context, candidate, NULL,
+ &transaction, errmsg, sizeof(errmsg));
+ if (ret != NB_OK && ret != NB_ERR_NO_CHANGES) {
+ enum confd_errcode errcode;
+
+ switch (ret) {
+ case NB_ERR_LOCKED:
+ errcode = CONFD_ERRCODE_IN_USE;
+ break;
+ case NB_ERR_RESOURCE:
+ errcode = CONFD_ERRCODE_RESOURCE_DENIED;
+ break;
+ default:
+ errcode = CONFD_ERRCODE_APPLICATION;
+ break;
+ }
+
+ /* Reject the configuration changes. */
+ if (cdb_sub_abort_trans(cdb_sub_sock, errcode, 0, 0, "%s",
+ errmsg)
+ != CONFD_OK) {
+ flog_err_confd("cdb_sub_abort_trans");
+ return;
+ }
+ } else {
+ /* Acknowledge the notification. */
+ if (cdb_sync_subscription_socket(fd, CDB_DONE_PRIORITY)
+ != CONFD_OK) {
+ flog_err_confd("cdb_sync_subscription_socket");
+ return;
+ }
+
+ /* No configuration changes. */
+ if (!transaction)
+ nb_config_free(candidate);
+ }
+}
+
+static void frr_confd_cdb_read_cb_commit(int fd, int *subp, int reslen)
+{
+ /*
+ * No need to process the configuration changes again as we're already
+ * keeping track of them in the "transaction" variable.
+ */
+ free(subp);
+
+ /* Apply the transaction. */
+ if (transaction) {
+ struct nb_config *candidate = transaction->config;
+ char errmsg[BUFSIZ] = {0};
+
+ nb_candidate_commit_apply(transaction, true, NULL, errmsg,
+ sizeof(errmsg));
+ nb_config_free(candidate);
+ }
+
+ /* Acknowledge the notification. */
+ if (cdb_sync_subscription_socket(fd, CDB_DONE_PRIORITY) != CONFD_OK) {
+ flog_err_confd("cdb_sync_subscription_socket");
+ return;
+ }
+}
+
+static int frr_confd_cdb_read_cb_abort(int fd, int *subp, int reslen)
+{
+ /*
+ * No need to process the configuration changes again as we're already
+ * keeping track of them in the "transaction" variable.
+ */
+ free(subp);
+
+ /* Abort the transaction. */
+ if (transaction) {
+ struct nb_config *candidate = transaction->config;
+ char errmsg[BUFSIZ] = {0};
+
+ nb_candidate_commit_abort(transaction, errmsg, sizeof(errmsg));
+ nb_config_free(candidate);
+ }
+
+ /* Acknowledge the notification. */
+ if (cdb_sync_subscription_socket(fd, CDB_DONE_PRIORITY) != CONFD_OK) {
+ flog_err_confd("cdb_sync_subscription_socket");
+ return -1;
+ }
+
+ return 0;
+}
+
+static void frr_confd_cdb_read_cb(struct thread *thread)
+{
+ int fd = THREAD_FD(thread);
+ enum cdb_sub_notification cdb_ev;
+ int flags;
+ int *subp = NULL;
+ int reslen = 0;
+
+ thread_add_read(master, frr_confd_cdb_read_cb, NULL, fd, &t_cdb_sub);
+
+ if (cdb_read_subscription_socket2(fd, &cdb_ev, &flags, &subp, &reslen)
+ != CONFD_OK) {
+ flog_err_confd("cdb_read_subscription_socket2");
+ return;
+ }
+
+ switch (cdb_ev) {
+ case CDB_SUB_PREPARE:
+ frr_confd_cdb_read_cb_prepare(fd, subp, reslen);
+ break;
+ case CDB_SUB_COMMIT:
+ frr_confd_cdb_read_cb_commit(fd, subp, reslen);
+ break;
+ case CDB_SUB_ABORT:
+ frr_confd_cdb_read_cb_abort(fd, subp, reslen);
+ break;
+ default:
+ flog_err_confd("unknown CDB event");
+ break;
+ }
+}
+
+/* Trigger CDB subscriptions to read the startup configuration. */
+static void *thread_cdb_trigger_subscriptions(void *data)
+{
+ int sock;
+ int *sub_points = NULL, len = 0;
+ struct listnode *node;
+ int *spoint;
+ int i = 0;
+
+ /* Create CDB data socket. */
+ sock = socket(PF_INET, SOCK_STREAM, 0);
+ if (sock < 0) {
+ flog_err(EC_LIB_SOCKET, "%s: failed to create socket: %s",
+ __func__, safe_strerror(errno));
+ return NULL;
+ }
+
+ if (cdb_connect(sock, CDB_DATA_SOCKET, &confd_addr,
+ sizeof(struct sockaddr_in))
+ != CONFD_OK) {
+ flog_err_confd("cdb_connect");
+ return NULL;
+ }
+
+ /*
+ * Fill array containing the subscription point of all loaded YANG
+ * modules.
+ */
+ len = listcount(confd_spoints);
+ sub_points = XCALLOC(MTYPE_CONFD, len * sizeof(int));
+ for (ALL_LIST_ELEMENTS_RO(confd_spoints, node, spoint))
+ sub_points[i++] = *spoint;
+
+ if (cdb_trigger_subscriptions(sock, sub_points, len) != CONFD_OK) {
+ flog_err_confd("cdb_trigger_subscriptions");
+ return NULL;
+ }
+
+ /* Cleanup and exit thread. */
+ XFREE(MTYPE_CONFD, sub_points);
+ cdb_close(sock);
+
+ return NULL;
+}
+
+static int frr_confd_subscribe(const struct lysc_node *snode, void *arg)
+{
+ struct yang_module *module = arg;
+ struct nb_node *nb_node;
+ int *spoint;
+ int ret;
+
+ switch (snode->nodetype) {
+ case LYS_CONTAINER:
+ case LYS_LEAF:
+ case LYS_LEAFLIST:
+ case LYS_LIST:
+ break;
+ default:
+ return YANG_ITER_CONTINUE;
+ }
+
+ if (CHECK_FLAG(snode->flags, LYS_CONFIG_R))
+ return YANG_ITER_CONTINUE;
+
+ nb_node = snode->priv;
+ if (!nb_node)
+ return YANG_ITER_CONTINUE;
+
+ DEBUGD(&nb_dbg_client_confd, "%s: subscribing to '%s'", __func__,
+ nb_node->xpath);
+
+ spoint = XMALLOC(MTYPE_CONFD, sizeof(*spoint));
+ ret = cdb_subscribe2(cdb_sub_sock, CDB_SUB_RUNNING_TWOPHASE,
+ CDB_SUB_WANT_ABORT_ON_ABORT, 3, spoint,
+ module->confd_hash, nb_node->xpath);
+ if (ret != CONFD_OK) {
+ flog_err_confd("cdb_subscribe2");
+ XFREE(MTYPE_CONFD, spoint);
+ return YANG_ITER_CONTINUE;
+ }
+
+ listnode_add(confd_spoints, spoint);
+ return YANG_ITER_CONTINUE;
+}
+
+static int frr_confd_init_cdb(void)
+{
+ struct yang_module *module;
+ pthread_t cdb_trigger_thread;
+
+ /* Create CDB subscription socket. */
+ cdb_sub_sock = socket(PF_INET, SOCK_STREAM, 0);
+ if (cdb_sub_sock < 0) {
+ flog_err(EC_LIB_SOCKET, "%s: failed to create socket: %s",
+ __func__, safe_strerror(errno));
+ return -1;
+ }
+
+ if (cdb_connect(cdb_sub_sock, CDB_SUBSCRIPTION_SOCKET, &confd_addr,
+ sizeof(struct sockaddr_in))
+ != CONFD_OK) {
+ flog_err_confd("cdb_connect");
+ goto error;
+ }
+
+ /* Subscribe to all loaded YANG data modules. */
+ confd_spoints = list_new();
+ RB_FOREACH (module, yang_modules, &yang_modules) {
+ module->confd_hash = confd_str2hash(module->info->ns);
+ if (module->confd_hash == 0) {
+ flog_err(
+ EC_LIB_LIBCONFD,
+ "%s: failed to find hash value for namespace %s",
+ __func__, module->info->ns);
+ goto error;
+ }
+
+ /*
+ * The CDB API doesn't provide a mechanism to subscribe to an
+ * entire YANG module. So we have to find the top level
+ * nodes ourselves and subscribe to their paths.
+ */
+ yang_snodes_iterate(module->info, frr_confd_subscribe, 0,
+ module);
+ }
+
+ if (cdb_subscribe_done(cdb_sub_sock) != CONFD_OK) {
+ flog_err_confd("cdb_subscribe_done");
+ goto error;
+ }
+
+ /* Create short lived pthread to trigger the CDB subscriptions. */
+ if (pthread_create(&cdb_trigger_thread, NULL,
+ thread_cdb_trigger_subscriptions, NULL)) {
+ flog_err(EC_LIB_SYSTEM_CALL, "%s: error creating pthread: %s",
+ __func__, safe_strerror(errno));
+ goto error;
+ }
+ pthread_detach(cdb_trigger_thread);
+
+ thread_add_read(master, frr_confd_cdb_read_cb, NULL, cdb_sub_sock,
+ &t_cdb_sub);
+
+ return 0;
+
+error:
+ frr_confd_finish_cdb();
+
+ return -1;
+}
+
+static void frr_confd_finish_cdb(void)
+{
+ if (cdb_sub_sock > 0) {
+ THREAD_OFF(t_cdb_sub);
+ cdb_close(cdb_sub_sock);
+ }
+}
+
+/* ------------ DP code ------------ */
+
+static int frr_confd_transaction_init(struct confd_trans_ctx *tctx)
+{
+ confd_trans_set_fd(tctx, dp_worker_sock);
+
+ return CONFD_OK;
+}
+
+#define CONFD_MAX_CHILD_NODES 32
+
+static int frr_confd_data_get_elem(struct confd_trans_ctx *tctx,
+ confd_hkeypath_t *kp)
+{
+ struct nb_node *nb_node;
+ char xpath[XPATH_MAXLEN];
+ struct yang_data *data;
+ confd_value_t v;
+ const void *list_entry = NULL;
+
+ frr_confd_get_xpath(kp, 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);
+ confd_data_reply_not_found(tctx);
+ return CONFD_OK;
+ }
+
+ if (frr_confd_hkeypath_get_list_entry(kp, nb_node, &list_entry) != 0) {
+ confd_data_reply_not_found(tctx);
+ return CONFD_OK;
+ }
+
+ data = nb_callback_get_elem(nb_node, xpath, list_entry);
+ if (data) {
+ if (data->value) {
+ CONFD_SET_STR(&v, data->value);
+ confd_data_reply_value(tctx, &v);
+ } else
+ confd_data_reply_found(tctx);
+ yang_data_free(data);
+ } else
+ confd_data_reply_not_found(tctx);
+
+ return CONFD_OK;
+}
+
+static int frr_confd_data_get_next(struct confd_trans_ctx *tctx,
+ confd_hkeypath_t *kp, long next)
+{
+ struct nb_node *nb_node;
+ char xpath[XPATH_MAXLEN];
+ struct yang_data *data;
+ const void *parent_list_entry, *nb_next;
+ confd_value_t v[LIST_MAXKEYS];
+
+ frr_confd_get_xpath(kp, 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);
+ confd_data_reply_next_key(tctx, NULL, -1, -1);
+ return CONFD_OK;
+ }
+
+ if (frr_confd_hkeypath_get_list_entry(kp, nb_node, &parent_list_entry)
+ != 0) {
+ /* List entry doesn't exist anymore. */
+ confd_data_reply_next_key(tctx, NULL, -1, -1);
+ return CONFD_OK;
+ }
+
+ nb_next = nb_callback_get_next(nb_node, parent_list_entry,
+ (next == -1) ? NULL : (void *)next);
+ if (!nb_next) {
+ /* End of the list or leaf-list. */
+ confd_data_reply_next_key(tctx, NULL, -1, -1);
+ return CONFD_OK;
+ }
+
+ switch (nb_node->snode->nodetype) {
+ case LYS_LIST:
+ if (!CHECK_FLAG(nb_node->flags, F_NB_NODE_KEYLESS_LIST)) {
+ struct yang_list_keys keys;
+
+ memset(&keys, 0, sizeof(keys));
+ if (nb_callback_get_keys(nb_node, nb_next, &keys)
+ != NB_OK) {
+ flog_warn(EC_LIB_NB_CB_STATE,
+ "%s: failed to get list keys",
+ __func__);
+ confd_data_reply_next_key(tctx, NULL, -1, -1);
+ return CONFD_OK;
+ }
+
+ /* Feed keys to ConfD. */
+ for (size_t i = 0; i < keys.num; i++)
+ CONFD_SET_STR(&v[i], keys.key[i]);
+ confd_data_reply_next_key(tctx, v, keys.num,
+ (long)nb_next);
+ } else {
+ char pointer_str[32];
+
+ /*
+ * ConfD 6.6 user guide, chapter 6.11 (Operational data
+ * lists without keys):
+ * "To support this without having completely separate
+ * APIs, we use a "pseudo" key in the ConfD APIs for
+ * this type of list. This key is not part of the data
+ * model, and completely hidden in the northbound agent
+ * interfaces, but is used with e.g. the get_next() and
+ * get_elem() callbacks as if it were a normal key. This
+ * "pseudo" key is always a single signed 64-bit
+ * integer, i.e. the confd_value_t type is C_INT64. The
+ * values can be chosen arbitrarily by the application,
+ * as long as a key value returned by get_next() can be
+ * used to get the data for the corresponding list entry
+ * with get_elem() or get_object() as usual. It could
+ * e.g. be an index into an array that holds the data,
+ * or even a memory address in integer form".
+ *
+ * Since we're using the CONFD_DAEMON_FLAG_STRINGSONLY
+ * option, we must convert our pseudo-key (a void
+ * pointer) to a string before sending it to confd.
+ */
+ snprintf(pointer_str, sizeof(pointer_str), "%lu",
+ (unsigned long)nb_next);
+ CONFD_SET_STR(&v[0], pointer_str);
+ confd_data_reply_next_key(tctx, v, 1, (long)nb_next);
+ }
+ break;
+ case LYS_LEAFLIST:
+ data = nb_callback_get_elem(nb_node, xpath, nb_next);
+ if (data) {
+ if (data->value) {
+ CONFD_SET_STR(&v[0], data->value);
+ confd_data_reply_next_key(tctx, v, 1,
+ (long)nb_next);
+ }
+ yang_data_free(data);
+ } else
+ confd_data_reply_next_key(tctx, NULL, -1, -1);
+ break;
+ default:
+ break;
+ }
+
+ return CONFD_OK;
+}
+
+/*
+ * Optional callback - implemented for performance reasons.
+ */
+static int frr_confd_data_get_object(struct confd_trans_ctx *tctx,
+ confd_hkeypath_t *kp)
+{
+ struct nb_node *nb_node;
+ const struct lysc_node *child;
+ char xpath[XPATH_MAXLEN];
+ char xpath_child[XPATH_MAXLEN * 2];
+ struct list *elements;
+ struct yang_data *data;
+ const void *list_entry;
+ confd_value_t values[CONFD_MAX_CHILD_NODES];
+ size_t nvalues = 0;
+
+ frr_confd_get_xpath(kp, 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);
+ confd_data_reply_not_found(tctx);
+ return CONFD_ERR;
+ }
+
+ if (frr_confd_hkeypath_get_list_entry(kp, nb_node, &list_entry) != 0) {
+ confd_data_reply_not_found(tctx);
+ return CONFD_OK;
+ }
+
+ elements = yang_data_list_new();
+
+ /* Loop through list child nodes. */
+ LY_LIST_FOR (lysc_node_child(nb_node->snode), child) {
+ struct nb_node *nb_node_child = child->priv;
+ confd_value_t *v;
+
+ if (nvalues > CONFD_MAX_CHILD_NODES)
+ break;
+
+ v = &values[nvalues++];
+
+ /* Non-presence containers, lists and leaf-lists. */
+ if (!nb_node_child->cbs.get_elem) {
+ CONFD_SET_NOEXISTS(v);
+ continue;
+ }
+
+ snprintf(xpath_child, sizeof(xpath_child), "%s/%s", xpath,
+ child->name);
+ data = nb_callback_get_elem(nb_node_child, xpath_child,
+ list_entry);
+ if (data) {
+ if (data->value)
+ CONFD_SET_STR(v, data->value);
+ else {
+ /* Presence containers and empty leafs. */
+ CONFD_SET_XMLTAG(
+ v, nb_node_child->confd_hash,
+ confd_str2hash(nb_node_child->snode
+ ->module->ns));
+ }
+ listnode_add(elements, data);
+ } else
+ CONFD_SET_NOEXISTS(v);
+ }
+
+ confd_data_reply_value_array(tctx, values, nvalues);
+
+ /* Release memory. */
+ list_delete(&elements);
+
+ return CONFD_OK;
+}
+
+/*
+ * Optional callback - implemented for performance reasons.
+ */
+static int frr_confd_data_get_next_object(struct confd_trans_ctx *tctx,
+ confd_hkeypath_t *kp, long next)
+{
+ char xpath[XPATH_MAXLEN];
+ struct nb_node *nb_node;
+ struct list *elements;
+ const void *parent_list_entry;
+ const void *nb_next;
+#define CONFD_OBJECTS_PER_TIME 100
+ struct confd_next_object objects[CONFD_OBJECTS_PER_TIME + 1];
+ char pseudo_keys[CONFD_OBJECTS_PER_TIME][32];
+ int nobjects = 0;
+
+ frr_confd_get_xpath(kp, 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);
+ confd_data_reply_next_object_array(tctx, NULL, 0, 0);
+ return CONFD_OK;
+ }
+
+ if (frr_confd_hkeypath_get_list_entry(kp, nb_node, &parent_list_entry)
+ != 0) {
+ confd_data_reply_next_object_array(tctx, NULL, 0, 0);
+ return CONFD_OK;
+ }
+
+ elements = yang_data_list_new();
+ nb_next = (next == -1) ? NULL : (void *)next;
+
+ memset(objects, 0, sizeof(objects));
+ for (int j = 0; j < CONFD_OBJECTS_PER_TIME; j++) {
+ struct confd_next_object *object;
+ const struct lysc_node *child;
+ struct yang_data *data;
+ size_t nvalues = 0;
+
+ object = &objects[j];
+
+ nb_next = nb_callback_get_next(nb_node, parent_list_entry,
+ nb_next);
+ if (!nb_next)
+ /* End of the list. */
+ break;
+
+ object->next = (long)nb_next;
+
+ /* Leaf-lists require special handling. */
+ if (nb_node->snode->nodetype == LYS_LEAFLIST) {
+ object->v = XMALLOC(MTYPE_CONFD, sizeof(confd_value_t));
+ data = nb_callback_get_elem(nb_node, xpath, nb_next);
+ assert(data && data->value);
+ CONFD_SET_STR(object->v, data->value);
+ nvalues++;
+ listnode_add(elements, data);
+ goto next;
+ }
+
+ object->v =
+ XMALLOC(MTYPE_CONFD,
+ CONFD_MAX_CHILD_NODES * sizeof(confd_value_t));
+
+ /*
+ * ConfD 6.6 user guide, chapter 6.11 (Operational data lists
+ * without keys):
+ * "In the response to the get_next_object() callback, the data
+ * provider is expected to provide the key values along with the
+ * other leafs in an array that is populated according to the
+ * data model. This must be done also for this type of list,
+ * even though the key isn't actually in the data model. The
+ * "pseudo" key must always be the first element in the array".
+ */
+ if (CHECK_FLAG(nb_node->flags, F_NB_NODE_KEYLESS_LIST)) {
+ confd_value_t *v;
+
+ snprintf(pseudo_keys[j], sizeof(pseudo_keys[j]), "%lu",
+ (unsigned long)nb_next);
+
+ v = &object->v[nvalues++];
+ CONFD_SET_STR(v, pseudo_keys[j]);
+ }
+
+ /* Loop through list child nodes. */
+ LY_LIST_FOR (lysc_node_child(nb_node->snode), child) {
+ struct nb_node *nb_node_child = child->priv;
+ char xpath_child[XPATH_MAXLEN * 2];
+ confd_value_t *v;
+
+ if (nvalues > CONFD_MAX_CHILD_NODES)
+ break;
+
+ v = &object->v[nvalues++];
+
+ /* Non-presence containers, lists and leaf-lists. */
+ if (!nb_node_child->cbs.get_elem) {
+ CONFD_SET_NOEXISTS(v);
+ continue;
+ }
+
+ snprintf(xpath_child, sizeof(xpath_child), "%s/%s",
+ xpath, child->name);
+ data = nb_callback_get_elem(nb_node_child, xpath_child,
+ nb_next);
+ if (data) {
+ if (data->value)
+ CONFD_SET_STR(v, data->value);
+ else {
+ /*
+ * Presence containers and empty leafs.
+ */
+ CONFD_SET_XMLTAG(
+ v, nb_node_child->confd_hash,
+ confd_str2hash(
+ nb_node_child->snode
+ ->module->ns));
+ }
+ listnode_add(elements, data);
+ } else
+ CONFD_SET_NOEXISTS(v);
+ }
+ next:
+ object->n = nvalues;
+ nobjects++;
+ }
+
+ if (nobjects == 0) {
+ confd_data_reply_next_object_array(tctx, NULL, 0, 0);
+ list_delete(&elements);
+ return CONFD_OK;
+ }
+
+ /* Detect end of the list. */
+ if (!nb_next) {
+ nobjects++;
+ objects[nobjects].v = NULL;
+ }
+
+ /* Reply to ConfD. */
+ confd_data_reply_next_object_arrays(tctx, objects, nobjects, 0);
+ if (!nb_next)
+ nobjects--;
+
+ /* Release memory. */
+ list_delete(&elements);
+ for (int j = 0; j < nobjects; j++) {
+ struct confd_next_object *object;
+
+ object = &objects[j];
+ XFREE(MTYPE_CONFD, object->v);
+ }
+
+ return CONFD_OK;
+}
+
+static int frr_confd_notification_send(const char *xpath,
+ struct list *arguments)
+{
+ struct nb_node *nb_node;
+ struct yang_module *module;
+ struct confd_datetime now;
+ confd_tag_value_t *values;
+ int nvalues;
+ int i = 0;
+ struct yang_data *data;
+ struct listnode *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 -1;
+ }
+ module = yang_module_find(nb_node->snode->module->name);
+ assert(module);
+
+ nvalues = 2;
+ if (arguments)
+ nvalues += listcount(arguments);
+
+ values = XMALLOC(MTYPE_CONFD, nvalues * sizeof(*values));
+
+ CONFD_SET_TAG_XMLBEGIN(&values[i++], nb_node->confd_hash,
+ module->confd_hash);
+ for (ALL_LIST_ELEMENTS_RO(arguments, node, data)) {
+ struct nb_node *nb_node_arg;
+
+ nb_node_arg = nb_node_find(data->xpath);
+ if (!nb_node_arg) {
+ flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
+ "%s: unknown data path: %s", __func__,
+ data->xpath);
+ XFREE(MTYPE_CONFD, values);
+ return NB_ERR;
+ }
+
+ CONFD_SET_TAG_STR(&values[i++], nb_node_arg->confd_hash,
+ data->value);
+ }
+ CONFD_SET_TAG_XMLEND(&values[i++], nb_node->confd_hash,
+ module->confd_hash);
+
+ getdatetime(&now);
+ ret = confd_notification_send(live_ctx, &now, values, nvalues);
+
+ /* Release memory. */
+ XFREE(MTYPE_CONFD, values);
+
+ /* Map ConfD return code to northbound return code. */
+ switch (ret) {
+ case CONFD_OK:
+ return NB_OK;
+ default:
+ return NB_ERR;
+ }
+}
+
+static int frr_confd_action_init(struct confd_user_info *uinfo)
+{
+ confd_action_set_fd(uinfo, dp_worker_sock);
+
+ return CONFD_OK;
+}
+
+static int frr_confd_action_execute(struct confd_user_info *uinfo,
+ struct xml_tag *name, confd_hkeypath_t *kp,
+ confd_tag_value_t *params, int nparams)
+{
+ char xpath[XPATH_MAXLEN];
+ struct nb_node *nb_node;
+ struct list *input;
+ struct list *output;
+ struct yang_data *data;
+ confd_tag_value_t *reply;
+ int ret = CONFD_OK;
+ char errmsg[BUFSIZ] = {0};
+
+ /* Getting the XPath is tricky. */
+ if (kp) {
+ /* This is a YANG RPC. */
+ frr_confd_get_xpath(kp, xpath, sizeof(xpath));
+ strlcat(xpath, "/", sizeof(xpath));
+ strlcat(xpath, confd_hash2str(name->tag), sizeof(xpath));
+ } else {
+ /* This is a YANG action. */
+ snprintf(xpath, sizeof(xpath), "/%s:%s",
+ confd_ns2prefix(name->ns), confd_hash2str(name->tag));
+ }
+
+ 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 CONFD_ERR;
+ }
+
+ input = yang_data_list_new();
+ output = yang_data_list_new();
+
+ /* Process input nodes. */
+ for (int i = 0; i < nparams; i++) {
+ char xpath_input[XPATH_MAXLEN * 2];
+ char value_str[YANG_VALUE_MAXLEN];
+
+ snprintf(xpath_input, sizeof(xpath_input), "%s/%s", xpath,
+ confd_hash2str(params[i].tag.tag));
+
+ if (frr_confd_val2str(xpath_input, &params[i].v, value_str,
+ sizeof(value_str))
+ != 0) {
+ flog_err(
+ EC_LIB_CONFD_DATA_CONVERT,
+ "%s: failed to convert ConfD value to a string",
+ __func__);
+ ret = CONFD_ERR;
+ goto exit;
+ }
+
+ data = yang_data_new(xpath_input, value_str);
+ listnode_add(input, data);
+ }
+
+ /* Execute callback registered for this XPath. */
+ if (nb_callback_rpc(nb_node, xpath, input, output, errmsg,
+ sizeof(errmsg))
+ != NB_OK) {
+ flog_warn(EC_LIB_NB_CB_RPC, "%s: rpc callback failed: %s",
+ __func__, xpath);
+ ret = CONFD_ERR;
+ goto exit;
+ }
+
+ /* Process output nodes. */
+ if (listcount(output) > 0) {
+ struct listnode *node;
+ int i = 0;
+
+ reply = XMALLOC(MTYPE_CONFD,
+ listcount(output) * sizeof(*reply));
+
+ for (ALL_LIST_ELEMENTS_RO(output, node, data)) {
+ struct nb_node *nb_node_output;
+ int hash;
+
+ nb_node_output = nb_node_find(data->xpath);
+ if (!nb_node_output) {
+ flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
+ "%s: unknown data path: %s", __func__,
+ data->xpath);
+ goto exit;
+ }
+
+ hash = confd_str2hash(nb_node_output->snode->name);
+ CONFD_SET_TAG_STR(&reply[i++], hash, data->value);
+ }
+ confd_action_reply_values(uinfo, reply, listcount(output));
+ XFREE(MTYPE_CONFD, reply);
+ }
+
+exit:
+ /* Release memory. */
+ list_delete(&input);
+ list_delete(&output);
+
+ return ret;
+}
+
+
+static int frr_confd_dp_read(struct confd_daemon_ctx *dctx, int fd)
+{
+ int ret;
+
+ ret = confd_fd_ready(dctx, fd);
+ if (ret == CONFD_EOF) {
+ flog_err_confd("confd_fd_ready");
+ frr_confd_finish();
+ return -1;
+ } else if (ret == CONFD_ERR && confd_errno != CONFD_ERR_EXTERNAL) {
+ flog_err_confd("confd_fd_ready");
+ frr_confd_finish();
+ return -1;
+ }
+
+ return 0;
+}
+
+static void frr_confd_dp_ctl_read(struct thread *thread)
+{
+ struct confd_daemon_ctx *dctx = THREAD_ARG(thread);
+ int fd = THREAD_FD(thread);
+
+ thread_add_read(master, frr_confd_dp_ctl_read, dctx, fd, &t_dp_ctl);
+
+ frr_confd_dp_read(dctx, fd);
+}
+
+static void frr_confd_dp_worker_read(struct thread *thread)
+{
+ struct confd_daemon_ctx *dctx = THREAD_ARG(thread);
+ int fd = THREAD_FD(thread);
+
+ thread_add_read(master, frr_confd_dp_worker_read, dctx, fd, &t_dp_worker);
+
+ frr_confd_dp_read(dctx, fd);
+}
+
+static int frr_confd_subscribe_state(const struct lysc_node *snode, void *arg)
+{
+ struct nb_node *nb_node = snode->priv;
+ struct confd_data_cbs *data_cbs = arg;
+
+ if (!nb_node || !CHECK_FLAG(snode->flags, LYS_CONFIG_R))
+ return YANG_ITER_CONTINUE;
+ /* We only need to subscribe to the root of the state subtrees. */
+ if (snode->parent && CHECK_FLAG(snode->parent->flags, LYS_CONFIG_R))
+ return YANG_ITER_CONTINUE;
+
+ DEBUGD(&nb_dbg_client_confd,
+ "%s: providing data to '%s' (callpoint %s)", __func__,
+ nb_node->xpath, snode->name);
+
+ strlcpy(data_cbs->callpoint, snode->name, sizeof(data_cbs->callpoint));
+ if (confd_register_data_cb(dctx, data_cbs) != CONFD_OK)
+ flog_err_confd("confd_register_data_cb");
+
+ return YANG_ITER_CONTINUE;
+}
+
+static int frr_confd_init_dp(const char *program_name)
+{
+ struct confd_trans_cbs trans_cbs;
+ struct confd_data_cbs data_cbs;
+ struct confd_notification_stream_cbs ncbs;
+ struct confd_action_cbs acbs;
+
+ /* Initialize daemon context. */
+ dctx = confd_init_daemon(program_name);
+ if (!dctx) {
+ flog_err_confd("confd_init_daemon");
+ goto error;
+ }
+
+ /*
+ * Inform we want to receive YANG values as raw strings, and that we
+ * want to provide only strings in the reply functions, regardless of
+ * the YANG type.
+ */
+ confd_set_daemon_flags(dctx, CONFD_DAEMON_FLAG_STRINGSONLY);
+
+ /* Establish a control socket. */
+ dp_ctl_sock = socket(PF_INET, SOCK_STREAM, 0);
+ if (dp_ctl_sock < 0) {
+ flog_err(EC_LIB_SOCKET, "%s: failed to create socket: %s",
+ __func__, safe_strerror(errno));
+ goto error;
+ }
+
+ if (confd_connect(dctx, dp_ctl_sock, CONTROL_SOCKET, &confd_addr,
+ sizeof(struct sockaddr_in))
+ != CONFD_OK) {
+ flog_err_confd("confd_connect");
+ goto error;
+ }
+
+ /*
+ * Establish a worker socket (only one since this plugin runs on a
+ * single thread).
+ */
+ dp_worker_sock = socket(PF_INET, SOCK_STREAM, 0);
+ if (dp_worker_sock < 0) {
+ flog_err(EC_LIB_SOCKET, "%s: failed to create socket: %s",
+ __func__, safe_strerror(errno));
+ goto error;
+ }
+ if (confd_connect(dctx, dp_worker_sock, WORKER_SOCKET, &confd_addr,
+ sizeof(struct sockaddr_in))
+ != CONFD_OK) {
+ flog_err_confd("confd_connect");
+ goto error;
+ }
+
+ /* Register transaction callback functions. */
+ memset(&trans_cbs, 0, sizeof(trans_cbs));
+ trans_cbs.init = frr_confd_transaction_init;
+ confd_register_trans_cb(dctx, &trans_cbs);
+
+ /* Register our read/write callbacks. */
+ memset(&data_cbs, 0, sizeof(data_cbs));
+ data_cbs.get_elem = frr_confd_data_get_elem;
+ data_cbs.exists_optional = frr_confd_data_get_elem;
+ data_cbs.get_next = frr_confd_data_get_next;
+ data_cbs.get_object = frr_confd_data_get_object;
+ data_cbs.get_next_object = frr_confd_data_get_next_object;
+
+ /*
+ * Iterate over all loaded YANG modules and subscribe to the paths
+ * referent to state data.
+ */
+ yang_snodes_iterate(NULL, frr_confd_subscribe_state, 0, &data_cbs);
+
+ /* Register notification stream. */
+ memset(&ncbs, 0, sizeof(ncbs));
+ ncbs.fd = dp_worker_sock;
+ /*
+ * RFC 5277 - Section 3.2.3:
+ * A NETCONF server implementation supporting the notification
+ * capability MUST support the "NETCONF" notification event
+ * stream. This stream contains all NETCONF XML event notifications
+ * supported by the NETCONF server.
+ */
+ strlcpy(ncbs.streamname, "NETCONF", sizeof(ncbs.streamname));
+ if (confd_register_notification_stream(dctx, &ncbs, &live_ctx)
+ != CONFD_OK) {
+ flog_err_confd("confd_register_notification_stream");
+ goto error;
+ }
+
+ /* Register the action handler callback. */
+ memset(&acbs, 0, sizeof(acbs));
+ strlcpy(acbs.actionpoint, "actionpoint", sizeof(acbs.actionpoint));
+ acbs.init = frr_confd_action_init;
+ acbs.action = frr_confd_action_execute;
+ if (confd_register_action_cbs(dctx, &acbs) != CONFD_OK) {
+ flog_err_confd("confd_register_action_cbs");
+ goto error;
+ }
+
+ /* Notify we registered all callbacks we wanted. */
+ if (confd_register_done(dctx) != CONFD_OK) {
+ flog_err_confd("confd_register_done");
+ goto error;
+ }
+
+ thread_add_read(master, frr_confd_dp_ctl_read, dctx, dp_ctl_sock,
+ &t_dp_ctl);
+ thread_add_read(master, frr_confd_dp_worker_read, dctx, dp_worker_sock,
+ &t_dp_worker);
+
+ return 0;
+
+error:
+ frr_confd_finish_dp();
+
+ return -1;
+}
+
+static void frr_confd_finish_dp(void)
+{
+ if (dp_worker_sock > 0) {
+ THREAD_OFF(t_dp_worker);
+ close(dp_worker_sock);
+ }
+ if (dp_ctl_sock > 0) {
+ THREAD_OFF(t_dp_ctl);
+ close(dp_ctl_sock);
+ }
+ if (dctx != NULL)
+ confd_release_daemon(dctx);
+}
+
+/* ------------ CLI ------------ */
+
+DEFUN (debug_nb_confd,
+ debug_nb_confd_cmd,
+ "[no] debug northbound client confd",
+ NO_STR
+ DEBUG_STR
+ "Northbound debugging\n"
+ "Client\n"
+ "ConfD\n")
+{
+ uint32_t mode = DEBUG_NODE2MODE(vty->node);
+ bool no = strmatch(argv[0]->text, "no");
+
+ DEBUG_MODE_SET(&nb_dbg_client_confd, mode, !no);
+
+ return CMD_SUCCESS;
+}
+
+static int frr_confd_debug_config_write(struct vty *vty)
+{
+ if (DEBUG_MODE_CHECK(&nb_dbg_client_confd, DEBUG_MODE_CONF))
+ vty_out(vty, "debug northbound client confd\n");
+
+ return 0;
+}
+
+static int frr_confd_debug_set_all(uint32_t flags, bool set)
+{
+ DEBUG_FLAGS_SET(&nb_dbg_client_confd, flags, set);
+
+ /* If all modes have been turned off, don't preserve options. */
+ if (!DEBUG_MODE_CHECK(&nb_dbg_client_confd, DEBUG_MODE_ALL))
+ DEBUG_CLEAR(&nb_dbg_client_confd);
+
+ return 0;
+}
+
+static void frr_confd_cli_init(void)
+{
+ hook_register(nb_client_debug_config_write,
+ frr_confd_debug_config_write);
+ hook_register(nb_client_debug_set_all, frr_confd_debug_set_all);
+
+ install_element(ENABLE_NODE, &debug_nb_confd_cmd);
+ install_element(CONFIG_NODE, &debug_nb_confd_cmd);
+}
+
+/* ------------ Main ------------ */
+
+static int frr_confd_calculate_snode_hash(const struct lysc_node *snode,
+ void *arg)
+{
+ struct nb_node *nb_node = snode->priv;
+
+ if (nb_node)
+ nb_node->confd_hash = confd_str2hash(snode->name);
+
+ return YANG_ITER_CONTINUE;
+}
+
+static int frr_confd_init(const char *program_name)
+{
+ struct sockaddr_in *confd_addr4 = (struct sockaddr_in *)&confd_addr;
+ int debuglevel = CONFD_SILENT;
+ int ret = -1;
+
+ /* Initialize ConfD library. */
+ confd_init(program_name, stderr, debuglevel);
+
+ confd_addr4->sin_family = AF_INET;
+ confd_addr4->sin_addr.s_addr = inet_addr("127.0.0.1");
+ confd_addr4->sin_port = htons(CONFD_PORT);
+ if (confd_load_schemas(&confd_addr, sizeof(struct sockaddr_in))
+ != CONFD_OK) {
+ flog_err_confd("confd_load_schemas");
+ return -1;
+ }
+
+ ret = frr_confd_init_cdb();
+ if (ret != 0)
+ goto error;
+
+ ret = frr_confd_init_dp(program_name);
+ if (ret != 0) {
+ frr_confd_finish_cdb();
+ goto error;
+ }
+
+ yang_snodes_iterate(NULL, frr_confd_calculate_snode_hash, 0, NULL);
+
+ hook_register(nb_notification_send, frr_confd_notification_send);
+
+ confd_connected = true;
+ return 0;
+
+error:
+ confd_free_schemas();
+
+ return ret;
+}
+
+static int frr_confd_finish(void)
+{
+ if (!confd_connected)
+ return 0;
+
+ frr_confd_finish_cdb();
+ frr_confd_finish_dp();
+
+ confd_free_schemas();
+
+ confd_connected = false;
+
+ return 0;
+}
+
+static int frr_confd_module_late_init(struct thread_master *tm)
+{
+ master = tm;
+
+ if (frr_confd_init(frr_get_progname()) < 0) {
+ flog_err(EC_LIB_CONFD_INIT,
+ "failed to initialize the ConfD module");
+ return -1;
+ }
+
+ hook_register(frr_fini, frr_confd_finish);
+ frr_confd_cli_init();
+
+ return 0;
+}
+
+static int frr_confd_module_init(void)
+{
+ hook_register(frr_late_init, frr_confd_module_late_init);
+
+ return 0;
+}
+
+FRR_MODULE_SETUP(.name = "frr_confd", .version = FRR_VERSION,
+ .description = "FRR ConfD integration module",
+ .init = frr_confd_module_init,
+);
diff --git a/lib/northbound_db.c b/lib/northbound_db.c
new file mode 100644
index 0000000..dce9b2e
--- /dev/null
+++ b/lib/northbound_db.c
@@ -0,0 +1,293 @@
+/*
+ * 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->context->client);
+ /*
+ * Always record configurations in the XML format, save the default
+ * values too, as this covers the case where defaults may change.
+ */
+ if (lyd_print_mem(&config_str, transaction->config->dnode, LYD_XML,
+ LYD_PRINT_WITHSIBLINGS | LYD_PRINT_WD_ALL)
+ != 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;
+ LY_ERR err;
+
+ 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;
+
+ err = lyd_parse_data_mem(ly_native_ctx, config_str, LYD_XML,
+ LYD_PARSE_STRICT | LYD_PARSE_NO_STATE,
+ LYD_VALIDATE_NO_STATE, &dnode);
+ if (err || !dnode)
+ flog_warn(EC_LIB_LIBYANG, "%s: lyd_parse_data_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;
+}
diff --git a/lib/northbound_db.h b/lib/northbound_db.h
new file mode 100644
index 0000000..14df09c
--- /dev/null
+++ b/lib/northbound_db.h
@@ -0,0 +1,112 @@
+/*
+ * 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"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * 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);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRR_NORTHBOUND_DB_H_ */
diff --git a/lib/northbound_grpc.cpp b/lib/northbound_grpc.cpp
new file mode 100644
index 0000000..95721ff
--- /dev/null
+++ b/lib/northbound_grpc.cpp
@@ -0,0 +1,1331 @@
+//
+// Copyright (c) 2021-2022, LabN Consulting, L.L.C
+// Copyright (C) 2019 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 <grpcpp/grpcpp.h>
+#include "grpc/frr-northbound.grpc.pb.h"
+
+#include "log.h"
+#include "libfrr.h"
+#include "lib/version.h"
+#include "lib/thread.h"
+#include "command.h"
+#include "lib_errors.h"
+#include "northbound.h"
+#include "northbound_db.h"
+#include "frr_pthread.h"
+
+#include <iostream>
+#include <sstream>
+#include <memory>
+#include <string>
+
+#define GRPC_DEFAULT_PORT 50051
+
+
+// ------------------------------------------------------
+// File Local Variables
+// ------------------------------------------------------
+
+/*
+ * NOTE: we can't use the FRR debugging infrastructure here since it uses
+ * atomics and C++ has a different atomics API. Enable gRPC debugging
+ * unconditionally until we figure out a way to solve this problem.
+ */
+static bool nb_dbg_client_grpc = 0;
+
+static struct thread_master *main_master;
+
+static struct frr_pthread *fpt;
+
+static bool grpc_running;
+
+#define grpc_debug(...) \
+ do { \
+ if (nb_dbg_client_grpc) \
+ zlog_debug(__VA_ARGS__); \
+ } while (0)
+
+// ------------------------------------------------------
+// New Types
+// ------------------------------------------------------
+
+enum CallState { CREATE, PROCESS, MORE, FINISH, DELETED };
+const char *call_states[] = {"CREATE", "PROCESS", "MORE", "FINISH", "DELETED"};
+
+struct candidate {
+ uint64_t id;
+ struct nb_config *config;
+ struct nb_transaction *transaction;
+};
+
+class Candidates
+{
+ public:
+ ~Candidates(void)
+ {
+ // Delete candidates.
+ for (auto it = _cdb.begin(); it != _cdb.end(); it++)
+ delete_candidate(it->first);
+ }
+
+ struct candidate *create_candidate(void)
+ {
+ uint64_t id = ++_next_id;
+ assert(id); // TODO: implement an algorithm for unique reusable
+ // IDs.
+ struct candidate *c = &_cdb[id];
+ c->id = id;
+ c->config = nb_config_dup(running_config);
+ c->transaction = NULL;
+
+ return c;
+ }
+
+ bool contains(uint64_t candidate_id)
+ {
+ return _cdb.count(candidate_id) > 0;
+ }
+
+ void delete_candidate(uint64_t candidate_id)
+ {
+ struct candidate *c = &_cdb[candidate_id];
+ char errmsg[BUFSIZ] = {0};
+
+ nb_config_free(c->config);
+ if (c->transaction)
+ nb_candidate_commit_abort(c->transaction, errmsg,
+ sizeof(errmsg));
+ _cdb.erase(c->id);
+ }
+
+ struct candidate *get_candidate(uint64_t id)
+ {
+ return _cdb.count(id) == 0 ? NULL : &_cdb[id];
+ }
+
+ private:
+ uint64_t _next_id = 0;
+ std::map<uint64_t, struct candidate> _cdb;
+};
+
+/*
+ * RpcStateBase is the common base class used to track a gRPC RPC.
+ */
+class RpcStateBase
+{
+ public:
+ virtual void do_request(::frr::Northbound::AsyncService *service,
+ ::grpc::ServerCompletionQueue *cq,
+ bool no_copy) = 0;
+
+ RpcStateBase(const char *name) : name(name){};
+
+ virtual ~RpcStateBase() = default;
+
+ CallState get_state() const
+ {
+ return state;
+ }
+
+ bool is_initial_process() const
+ {
+ /* Will always be true for Unary */
+ return entered_state == CREATE;
+ }
+
+ // Returns "more" status, if false caller can delete
+ bool run(frr::Northbound::AsyncService *service,
+ grpc::ServerCompletionQueue *cq)
+ {
+ /*
+ * We enter in either CREATE or MORE state, and transition to
+ * PROCESS state.
+ */
+ this->entered_state = this->state;
+ this->state = PROCESS;
+ grpc_debug("%s RPC: %s -> %s on grpc-io-thread", name,
+ call_states[this->entered_state],
+ call_states[this->state]);
+ /*
+ * We schedule the callback on the main pthread, and wait for
+ * the state to transition out of the PROCESS state. The new
+ * state will either be MORE or FINISH. It will always be FINISH
+ * for Unary RPCs.
+ */
+ thread_add_event(main_master, c_callback, (void *)this, 0,
+ NULL);
+
+ pthread_mutex_lock(&this->cmux);
+ while (this->state == PROCESS)
+ pthread_cond_wait(&this->cond, &this->cmux);
+ pthread_mutex_unlock(&this->cmux);
+
+ grpc_debug("%s RPC in %s on grpc-io-thread", name,
+ call_states[this->state]);
+
+ if (this->state == FINISH) {
+ /*
+ * Server is done (FINISH) so prep to receive a new
+ * request of this type. We could do this earlier but
+ * that would mean we could be handling multiple same
+ * type requests in parallel without limit.
+ */
+ this->do_request(service, cq, false);
+ }
+ return true;
+ }
+
+ protected:
+ virtual CallState run_mainthread(struct thread *thread) = 0;
+
+ static void c_callback(struct thread *thread)
+ {
+ auto _tag = static_cast<RpcStateBase *>(THREAD_ARG(thread));
+ /*
+ * We hold the lock until the callback finishes and has updated
+ * _tag->state, then we signal done and release.
+ */
+ pthread_mutex_lock(&_tag->cmux);
+
+ CallState enter_state = _tag->state;
+ grpc_debug("%s RPC: running %s on main thread", _tag->name,
+ call_states[enter_state]);
+
+ _tag->state = _tag->run_mainthread(thread);
+
+ grpc_debug("%s RPC: %s -> %s [main thread]", _tag->name,
+ call_states[enter_state], call_states[_tag->state]);
+
+ pthread_cond_signal(&_tag->cond);
+ pthread_mutex_unlock(&_tag->cmux);
+ return;
+ }
+
+ grpc::ServerContext ctx;
+ pthread_mutex_t cmux = PTHREAD_MUTEX_INITIALIZER;
+ pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
+ CallState state = CREATE;
+ CallState entered_state = CREATE;
+
+ public:
+ const char *name;
+};
+
+/*
+ * The UnaryRpcState class is used to track the execution of a Unary RPC.
+ *
+ * Template Args:
+ * Q - the request type for a given unary RPC
+ * S - the response type for a given unary RPC
+ */
+template <typename Q, typename S> class UnaryRpcState : public RpcStateBase
+{
+ public:
+ typedef void (frr::Northbound::AsyncService::*reqfunc_t)(
+ ::grpc::ServerContext *, Q *,
+ ::grpc::ServerAsyncResponseWriter<S> *,
+ ::grpc::CompletionQueue *, ::grpc::ServerCompletionQueue *,
+ void *);
+
+ UnaryRpcState(Candidates *cdb, reqfunc_t rfunc,
+ grpc::Status (*cb)(UnaryRpcState<Q, S> *),
+ const char *name)
+ : RpcStateBase(name), cdb(cdb), requestf(rfunc), callback(cb),
+ responder(&ctx){};
+
+ void do_request(::frr::Northbound::AsyncService *service,
+ ::grpc::ServerCompletionQueue *cq,
+ bool no_copy) override
+ {
+ grpc_debug("%s, posting a request for: %s", __func__, name);
+ auto copy = no_copy ? this
+ : new UnaryRpcState(cdb, requestf, callback,
+ name);
+ (service->*requestf)(&copy->ctx, &copy->request,
+ &copy->responder, cq, cq, copy);
+ }
+
+ CallState run_mainthread(struct thread *thread) override
+ {
+ // Unary RPC are always finished, see "Unary" :)
+ grpc::Status status = this->callback(this);
+ responder.Finish(response, status, this);
+ return FINISH;
+ }
+
+ Candidates *cdb;
+
+ Q request;
+ S response;
+ grpc::ServerAsyncResponseWriter<S> responder;
+
+ grpc::Status (*callback)(UnaryRpcState<Q, S> *);
+ reqfunc_t requestf = NULL;
+};
+
+/*
+ * The StreamRpcState class is used to track the execution of a Streaming RPC.
+ *
+ * Template Args:
+ * Q - the request type for a given streaming RPC
+ * S - the response type for a given streaming RPC
+ * X - the type used to track the streaming state
+ */
+template <typename Q, typename S, typename X>
+class StreamRpcState : public RpcStateBase
+{
+ public:
+ typedef void (frr::Northbound::AsyncService::*reqsfunc_t)(
+ ::grpc::ServerContext *, Q *, ::grpc::ServerAsyncWriter<S> *,
+ ::grpc::CompletionQueue *, ::grpc::ServerCompletionQueue *,
+ void *);
+
+ StreamRpcState(reqsfunc_t rfunc, bool (*cb)(StreamRpcState<Q, S, X> *),
+ const char *name)
+ : RpcStateBase(name), requestsf(rfunc), callback(cb),
+ async_responder(&ctx){};
+
+ void do_request(::frr::Northbound::AsyncService *service,
+ ::grpc::ServerCompletionQueue *cq,
+ bool no_copy) override
+ {
+ grpc_debug("%s, posting a request for: %s", __func__, name);
+ auto copy =
+ no_copy ? this
+ : new StreamRpcState(requestsf, callback, name);
+ (service->*requestsf)(&copy->ctx, &copy->request,
+ &copy->async_responder, cq, cq, copy);
+ }
+
+ CallState run_mainthread(struct thread *thread) override
+ {
+ if (this->callback(this))
+ return MORE;
+ else
+ return FINISH;
+ }
+
+ Q request;
+ S response;
+ grpc::ServerAsyncWriter<S> async_responder;
+
+ bool (*callback)(StreamRpcState<Q, S, X> *);
+ reqsfunc_t requestsf = NULL;
+
+ X context;
+};
+
+// ------------------------------------------------------
+// Utility Functions
+// ------------------------------------------------------
+
+static LYD_FORMAT encoding2lyd_format(enum frr::Encoding encoding)
+{
+ switch (encoding) {
+ case frr::JSON:
+ return LYD_JSON;
+ case frr::XML:
+ return LYD_XML;
+ default:
+ flog_err(EC_LIB_DEVELOPMENT,
+ "%s: unknown data encoding format (%u)", __func__,
+ encoding);
+ exit(1);
+ }
+}
+
+static int yang_dnode_edit(struct lyd_node *dnode, const std::string &path,
+ const char *value)
+{
+ LY_ERR err = lyd_new_path(dnode, ly_native_ctx, path.c_str(), value,
+ LYD_NEW_PATH_UPDATE, &dnode);
+ if (err != LY_SUCCESS) {
+ flog_warn(EC_LIB_LIBYANG, "%s: lyd_new_path() failed: %s",
+ __func__, ly_errmsg(ly_native_ctx));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int yang_dnode_delete(struct lyd_node *dnode, const std::string &path)
+{
+ dnode = yang_dnode_get(dnode, path.c_str());
+ if (!dnode)
+ return -1;
+
+ lyd_free_tree(dnode);
+
+ return 0;
+}
+
+static LY_ERR data_tree_from_dnode(frr::DataTree *dt,
+ const struct lyd_node *dnode,
+ LYD_FORMAT lyd_format, bool with_defaults)
+{
+ char *strp;
+ int options = 0;
+
+ SET_FLAG(options, LYD_PRINT_WITHSIBLINGS);
+ if (with_defaults)
+ SET_FLAG(options, LYD_PRINT_WD_ALL);
+ else
+ SET_FLAG(options, LYD_PRINT_WD_TRIM);
+
+ LY_ERR err = lyd_print_mem(&strp, dnode, lyd_format, options);
+ if (err == LY_SUCCESS) {
+ if (strp) {
+ dt->set_data(strp);
+ free(strp);
+ }
+ }
+ return err;
+}
+
+static struct lyd_node *dnode_from_data_tree(const frr::DataTree *dt,
+ bool config_only)
+{
+ struct lyd_node *dnode;
+ int options, opt2;
+ LY_ERR err;
+
+ if (config_only) {
+ options = LYD_PARSE_NO_STATE;
+ opt2 = LYD_VALIDATE_NO_STATE;
+ } else {
+ options = LYD_PARSE_STRICT;
+ opt2 = 0;
+ }
+
+ err = lyd_parse_data_mem(ly_native_ctx, dt->data().c_str(),
+ encoding2lyd_format(dt->encoding()), options,
+ opt2, &dnode);
+ if (err != LY_SUCCESS) {
+ flog_warn(EC_LIB_LIBYANG, "%s: lyd_parse_mem() failed: %s",
+ __func__, ly_errmsg(ly_native_ctx));
+ }
+ return dnode;
+}
+
+static struct lyd_node *get_dnode_config(const std::string &path)
+{
+ struct lyd_node *dnode;
+
+ if (!yang_dnode_exists(running_config->dnode,
+ path.empty() ? NULL : path.c_str()))
+ return NULL;
+
+ dnode = yang_dnode_get(running_config->dnode,
+ path.empty() ? NULL : path.c_str());
+ if (dnode)
+ dnode = yang_dnode_dup(dnode);
+
+ return dnode;
+}
+
+static int get_oper_data_cb(const struct lysc_node *snode,
+ struct yang_translator *translator,
+ struct yang_data *data, void *arg)
+{
+ struct lyd_node *dnode = static_cast<struct lyd_node *>(arg);
+ int ret = yang_dnode_edit(dnode, data->xpath, data->value);
+ yang_data_free(data);
+
+ return (ret == 0) ? NB_OK : NB_ERR;
+}
+
+static struct lyd_node *get_dnode_state(const std::string &path)
+{
+ struct lyd_node *dnode = yang_dnode_new(ly_native_ctx, false);
+ if (nb_oper_data_iterate(path.c_str(), NULL, 0, get_oper_data_cb, dnode)
+ != NB_OK) {
+ yang_dnode_free(dnode);
+ return NULL;
+ }
+
+ return dnode;
+}
+
+static grpc::Status get_path(frr::DataTree *dt, const std::string &path,
+ int type, LYD_FORMAT lyd_format,
+ bool with_defaults)
+{
+ struct lyd_node *dnode_config = NULL;
+ struct lyd_node *dnode_state = NULL;
+ struct lyd_node *dnode_final;
+
+ // Configuration data.
+ if (type == frr::GetRequest_DataType_ALL
+ || type == frr::GetRequest_DataType_CONFIG) {
+ dnode_config = get_dnode_config(path);
+ if (!dnode_config)
+ return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
+ "Data path not found");
+ }
+
+ // Operational data.
+ if (type == frr::GetRequest_DataType_ALL
+ || type == frr::GetRequest_DataType_STATE) {
+ dnode_state = get_dnode_state(path);
+ if (!dnode_state) {
+ if (dnode_config)
+ yang_dnode_free(dnode_config);
+ return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
+ "Failed to fetch operational data");
+ }
+ }
+
+ switch (type) {
+ case frr::GetRequest_DataType_ALL:
+ //
+ // Combine configuration and state data into a single
+ // dnode.
+ //
+ if (lyd_merge_siblings(&dnode_state, dnode_config,
+ LYD_MERGE_DESTRUCT)
+ != LY_SUCCESS) {
+ yang_dnode_free(dnode_state);
+ yang_dnode_free(dnode_config);
+ return grpc::Status(
+ grpc::StatusCode::INTERNAL,
+ "Failed to merge configuration and state data",
+ ly_errmsg(ly_native_ctx));
+ }
+
+ dnode_final = dnode_state;
+ break;
+ case frr::GetRequest_DataType_CONFIG:
+ dnode_final = dnode_config;
+ break;
+ case frr::GetRequest_DataType_STATE:
+ dnode_final = dnode_state;
+ break;
+ }
+
+ // Validate data to create implicit default nodes if necessary.
+ int validate_opts = 0;
+ if (type == frr::GetRequest_DataType_CONFIG)
+ validate_opts = LYD_VALIDATE_NO_STATE;
+ else
+ validate_opts = 0;
+
+ LY_ERR err = lyd_validate_all(&dnode_final, ly_native_ctx,
+ validate_opts, NULL);
+
+ if (err)
+ flog_warn(EC_LIB_LIBYANG, "%s: lyd_validate_all() failed: %s",
+ __func__, ly_errmsg(ly_native_ctx));
+ // Dump data using the requested format.
+ if (!err)
+ err = data_tree_from_dnode(dt, dnode_final, lyd_format,
+ with_defaults);
+ yang_dnode_free(dnode_final);
+ if (err)
+ return grpc::Status(grpc::StatusCode::INTERNAL,
+ "Failed to dump data");
+ return grpc::Status::OK;
+}
+
+
+// ------------------------------------------------------
+// RPC Callback Functions: run on main thread
+// ------------------------------------------------------
+
+grpc::Status HandleUnaryGetCapabilities(
+ UnaryRpcState<frr::GetCapabilitiesRequest, frr::GetCapabilitiesResponse>
+ *tag)
+{
+ grpc_debug("%s: entered", __func__);
+
+ // Response: string frr_version = 1;
+ tag->response.set_frr_version(FRR_VERSION);
+
+ // Response: bool rollback_support = 2;
+#ifdef HAVE_CONFIG_ROLLBACKS
+ tag->response.set_rollback_support(true);
+#else
+ tag->response.set_rollback_support(false);
+#endif
+ // Response: repeated ModuleData supported_modules = 3;
+ struct yang_module *module;
+ RB_FOREACH (module, yang_modules, &yang_modules) {
+ auto m = tag->response.add_supported_modules();
+
+ m->set_name(module->name);
+ if (module->info->revision)
+ m->set_revision(module->info->revision);
+ m->set_organization(module->info->org);
+ }
+
+ // Response: repeated Encoding supported_encodings = 4;
+ tag->response.add_supported_encodings(frr::JSON);
+ tag->response.add_supported_encodings(frr::XML);
+
+ return grpc::Status::OK;
+}
+
+// Define the context variable type for this streaming handler
+typedef std::list<std::string> GetContextType;
+
+bool HandleStreamingGet(
+ StreamRpcState<frr::GetRequest, frr::GetResponse, GetContextType> *tag)
+{
+ grpc_debug("%s: entered", __func__);
+
+ auto mypathps = &tag->context;
+ if (tag->is_initial_process()) {
+ // Fill our context container first time through
+ grpc_debug("%s: initialize streaming state", __func__);
+ auto paths = tag->request.path();
+ for (const std::string &path : paths) {
+ mypathps->push_back(std::string(path));
+ }
+ }
+
+ // Request: DataType type = 1;
+ int type = tag->request.type();
+ // Request: Encoding encoding = 2;
+ frr::Encoding encoding = tag->request.encoding();
+ // Request: bool with_defaults = 3;
+ bool with_defaults = tag->request.with_defaults();
+
+ if (mypathps->empty()) {
+ tag->async_responder.Finish(grpc::Status::OK, tag);
+ return false;
+ }
+
+ frr::GetResponse response;
+ grpc::Status status;
+
+ // Response: int64 timestamp = 1;
+ response.set_timestamp(time(NULL));
+
+ // Response: DataTree data = 2;
+ auto *data = response.mutable_data();
+ data->set_encoding(tag->request.encoding());
+ status = get_path(data, mypathps->back().c_str(), type,
+ encoding2lyd_format(encoding), with_defaults);
+
+ if (!status.ok()) {
+ tag->async_responder.WriteAndFinish(
+ response, grpc::WriteOptions(), status, tag);
+ return false;
+ }
+
+ mypathps->pop_back();
+ if (mypathps->empty()) {
+ tag->async_responder.WriteAndFinish(
+ response, grpc::WriteOptions(), grpc::Status::OK, tag);
+ return false;
+ } else {
+ tag->async_responder.Write(response, tag);
+ return true;
+ }
+}
+
+grpc::Status HandleUnaryCreateCandidate(
+ UnaryRpcState<frr::CreateCandidateRequest, frr::CreateCandidateResponse>
+ *tag)
+{
+ grpc_debug("%s: entered", __func__);
+
+ struct candidate *candidate = tag->cdb->create_candidate();
+ if (!candidate)
+ return grpc::Status(grpc::StatusCode::RESOURCE_EXHAUSTED,
+ "Can't create candidate configuration");
+ tag->response.set_candidate_id(candidate->id);
+ return grpc::Status::OK;
+}
+
+grpc::Status HandleUnaryDeleteCandidate(
+ UnaryRpcState<frr::DeleteCandidateRequest, frr::DeleteCandidateResponse>
+ *tag)
+{
+ grpc_debug("%s: entered", __func__);
+
+ uint32_t candidate_id = tag->request.candidate_id();
+
+ grpc_debug("%s(candidate_id: %u)", __func__, candidate_id);
+
+ if (!tag->cdb->contains(candidate_id))
+ return grpc::Status(grpc::StatusCode::NOT_FOUND,
+ "candidate configuration not found");
+ tag->cdb->delete_candidate(candidate_id);
+ return grpc::Status::OK;
+}
+
+grpc::Status HandleUnaryUpdateCandidate(
+ UnaryRpcState<frr::UpdateCandidateRequest, frr::UpdateCandidateResponse>
+ *tag)
+{
+ grpc_debug("%s: entered", __func__);
+
+ uint32_t candidate_id = tag->request.candidate_id();
+
+ grpc_debug("%s(candidate_id: %u)", __func__, candidate_id);
+
+ struct candidate *candidate = tag->cdb->get_candidate(candidate_id);
+
+ if (!candidate)
+ return grpc::Status(grpc::StatusCode::NOT_FOUND,
+ "candidate configuration not found");
+ if (candidate->transaction)
+ return grpc::Status(
+ grpc::StatusCode::FAILED_PRECONDITION,
+ "candidate is in the middle of a transaction");
+ if (nb_candidate_update(candidate->config) != NB_OK)
+ return grpc::Status(grpc::StatusCode::INTERNAL,
+ "failed to update candidate configuration");
+
+ return grpc::Status::OK;
+}
+
+grpc::Status HandleUnaryEditCandidate(
+ UnaryRpcState<frr::EditCandidateRequest, frr::EditCandidateResponse>
+ *tag)
+{
+ grpc_debug("%s: entered", __func__);
+
+ uint32_t candidate_id = tag->request.candidate_id();
+
+ grpc_debug("%s(candidate_id: %u)", __func__, candidate_id);
+
+ struct candidate *candidate = tag->cdb->get_candidate(candidate_id);
+ if (!candidate)
+ return grpc::Status(grpc::StatusCode::NOT_FOUND,
+ "candidate configuration not found");
+
+ struct nb_config *candidate_tmp = nb_config_dup(candidate->config);
+
+ auto pvs = tag->request.update();
+ for (const frr::PathValue &pv : pvs) {
+ if (yang_dnode_edit(candidate_tmp->dnode, pv.path(),
+ pv.value().c_str()) != 0) {
+ nb_config_free(candidate_tmp);
+
+ return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
+ "Failed to update \"" + pv.path() +
+ "\"");
+ }
+ }
+
+ pvs = tag->request.delete_();
+ for (const frr::PathValue &pv : pvs) {
+ if (yang_dnode_delete(candidate_tmp->dnode, pv.path()) != 0) {
+ nb_config_free(candidate_tmp);
+ return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
+ "Failed to remove \"" + pv.path() +
+ "\"");
+ }
+ }
+
+ // No errors, accept all changes.
+ nb_config_replace(candidate->config, candidate_tmp, false);
+ return grpc::Status::OK;
+}
+
+grpc::Status HandleUnaryLoadToCandidate(
+ UnaryRpcState<frr::LoadToCandidateRequest, frr::LoadToCandidateResponse>
+ *tag)
+{
+ grpc_debug("%s: entered", __func__);
+
+ uint32_t candidate_id = tag->request.candidate_id();
+
+ grpc_debug("%s(candidate_id: %u)", __func__, candidate_id);
+
+ // Request: LoadType type = 2;
+ int load_type = tag->request.type();
+ // Request: DataTree config = 3;
+ auto config = tag->request.config();
+
+ struct candidate *candidate = tag->cdb->get_candidate(candidate_id);
+ if (!candidate)
+ return grpc::Status(grpc::StatusCode::NOT_FOUND,
+ "candidate configuration not found");
+
+ struct lyd_node *dnode = dnode_from_data_tree(&config, true);
+ if (!dnode)
+ return grpc::Status(grpc::StatusCode::INTERNAL,
+ "Failed to parse the configuration");
+
+ struct nb_config *loaded_config = nb_config_new(dnode);
+ if (load_type == frr::LoadToCandidateRequest::REPLACE)
+ nb_config_replace(candidate->config, loaded_config, false);
+ else if (nb_config_merge(candidate->config, loaded_config, false) !=
+ NB_OK)
+ return grpc::Status(grpc::StatusCode::INTERNAL,
+ "Failed to merge the loaded configuration");
+
+ return grpc::Status::OK;
+}
+
+grpc::Status
+HandleUnaryCommit(UnaryRpcState<frr::CommitRequest, frr::CommitResponse> *tag)
+{
+ grpc_debug("%s: entered", __func__);
+
+ // Request: uint32 candidate_id = 1;
+ uint32_t candidate_id = tag->request.candidate_id();
+
+ grpc_debug("%s(candidate_id: %u)", __func__, candidate_id);
+
+ // Request: Phase phase = 2;
+ int phase = tag->request.phase();
+ // Request: string comment = 3;
+ const std::string comment = tag->request.comment();
+
+ // Find candidate configuration.
+ struct candidate *candidate = tag->cdb->get_candidate(candidate_id);
+ if (!candidate)
+ return grpc::Status(grpc::StatusCode::NOT_FOUND,
+ "candidate configuration not found");
+
+ int ret = NB_OK;
+ uint32_t transaction_id = 0;
+
+ // Check for misuse of the two-phase commit protocol.
+ switch (phase) {
+ case frr::CommitRequest::PREPARE:
+ case frr::CommitRequest::ALL:
+ if (candidate->transaction)
+ return grpc::Status(
+ grpc::StatusCode::FAILED_PRECONDITION,
+ "candidate is in the middle of a transaction");
+ break;
+ case frr::CommitRequest::ABORT:
+ case frr::CommitRequest::APPLY:
+ if (!candidate->transaction)
+ return grpc::Status(
+ grpc::StatusCode::FAILED_PRECONDITION,
+ "no transaction in progress");
+ break;
+ default:
+ break;
+ }
+
+
+ // Execute the user request.
+ struct nb_context context = {};
+ context.client = NB_CLIENT_GRPC;
+ char errmsg[BUFSIZ] = {0};
+
+ switch (phase) {
+ case frr::CommitRequest::VALIDATE:
+ grpc_debug("`-> Performing VALIDATE");
+ ret = nb_candidate_validate(&context, candidate->config, errmsg,
+ sizeof(errmsg));
+ break;
+ case frr::CommitRequest::PREPARE:
+ grpc_debug("`-> Performing PREPARE");
+ ret = nb_candidate_commit_prepare(
+ &context, candidate->config, comment.c_str(),
+ &candidate->transaction, errmsg, sizeof(errmsg));
+ break;
+ case frr::CommitRequest::ABORT:
+ grpc_debug("`-> Performing ABORT");
+ nb_candidate_commit_abort(candidate->transaction, errmsg,
+ sizeof(errmsg));
+ break;
+ case frr::CommitRequest::APPLY:
+ grpc_debug("`-> Performing APPLY");
+ nb_candidate_commit_apply(candidate->transaction, true,
+ &transaction_id, errmsg,
+ sizeof(errmsg));
+ break;
+ case frr::CommitRequest::ALL:
+ grpc_debug("`-> Performing ALL");
+ ret = nb_candidate_commit(&context, candidate->config, true,
+ comment.c_str(), &transaction_id,
+ errmsg, sizeof(errmsg));
+ break;
+ }
+
+ // Map northbound error codes to gRPC status codes.
+ grpc::Status status;
+ switch (ret) {
+ case NB_OK:
+ status = grpc::Status::OK;
+ break;
+ case NB_ERR_NO_CHANGES:
+ status = grpc::Status(grpc::StatusCode::ABORTED, errmsg);
+ break;
+ case NB_ERR_LOCKED:
+ status = grpc::Status(grpc::StatusCode::UNAVAILABLE, errmsg);
+ break;
+ case NB_ERR_VALIDATION:
+ status = grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
+ errmsg);
+ break;
+ case NB_ERR_RESOURCE:
+ status = grpc::Status(grpc::StatusCode::RESOURCE_EXHAUSTED,
+ errmsg);
+ break;
+ case NB_ERR:
+ default:
+ status = grpc::Status(grpc::StatusCode::INTERNAL, errmsg);
+ break;
+ }
+
+ grpc_debug("`-> Result: %s (message: '%s')",
+ nb_err_name((enum nb_error)ret), errmsg);
+
+ if (ret == NB_OK) {
+ // Response: uint32 transaction_id = 1;
+ if (transaction_id)
+ tag->response.set_transaction_id(transaction_id);
+ }
+ if (strlen(errmsg) > 0)
+ tag->response.set_error_message(errmsg);
+
+ return status;
+}
+
+grpc::Status HandleUnaryLockConfig(
+ UnaryRpcState<frr::LockConfigRequest, frr::LockConfigResponse> *tag)
+{
+ grpc_debug("%s: entered", __func__);
+
+ if (nb_running_lock(NB_CLIENT_GRPC, NULL))
+ return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION,
+ "running configuration is locked already");
+ return grpc::Status::OK;
+}
+
+grpc::Status HandleUnaryUnlockConfig(
+ UnaryRpcState<frr::UnlockConfigRequest, frr::UnlockConfigResponse> *tag)
+{
+ grpc_debug("%s: entered", __func__);
+
+ if (nb_running_unlock(NB_CLIENT_GRPC, NULL))
+ return grpc::Status(
+ grpc::StatusCode::FAILED_PRECONDITION,
+ "failed to unlock the running configuration");
+ return grpc::Status::OK;
+}
+
+static void list_transactions_cb(void *arg, int transaction_id,
+ const char *client_name, const char *date,
+ const char *comment)
+{
+ auto list = static_cast<std::list<
+ std::tuple<int, std::string, std::string, std::string>> *>(arg);
+ list->push_back(
+ std::make_tuple(transaction_id, std::string(client_name),
+ std::string(date), std::string(comment)));
+}
+
+// Define the context variable type for this streaming handler
+typedef std::list<std::tuple<int, std::string, std::string, std::string>>
+ ListTransactionsContextType;
+
+bool HandleStreamingListTransactions(
+ StreamRpcState<frr::ListTransactionsRequest,
+ frr::ListTransactionsResponse,
+ ListTransactionsContextType> *tag)
+{
+ grpc_debug("%s: entered", __func__);
+
+ auto list = &tag->context;
+ if (tag->is_initial_process()) {
+ grpc_debug("%s: initialize streaming state", __func__);
+ // Fill our context container first time through
+ nb_db_transactions_iterate(list_transactions_cb, list);
+ list->push_back(std::make_tuple(
+ 0xFFFF, std::string("fake client"),
+ std::string("fake date"), std::string("fake comment")));
+ list->push_back(std::make_tuple(0xFFFE,
+ std::string("fake client2"),
+ std::string("fake date"),
+ std::string("fake comment2")));
+ }
+
+ if (list->empty()) {
+ tag->async_responder.Finish(grpc::Status::OK, tag);
+ return false;
+ }
+
+ auto item = list->back();
+
+ frr::ListTransactionsResponse response;
+
+ // Response: uint32 id = 1;
+ response.set_id(std::get<0>(item));
+
+ // Response: string client = 2;
+ response.set_client(std::get<1>(item).c_str());
+
+ // Response: string date = 3;
+ response.set_date(std::get<2>(item).c_str());
+
+ // Response: string comment = 4;
+ response.set_comment(std::get<3>(item).c_str());
+
+ list->pop_back();
+ if (list->empty()) {
+ tag->async_responder.WriteAndFinish(
+ response, grpc::WriteOptions(), grpc::Status::OK, tag);
+ return false;
+ } else {
+ tag->async_responder.Write(response, tag);
+ return true;
+ }
+}
+
+grpc::Status HandleUnaryGetTransaction(
+ UnaryRpcState<frr::GetTransactionRequest, frr::GetTransactionResponse>
+ *tag)
+{
+ grpc_debug("%s: entered", __func__);
+
+ // Request: uint32 transaction_id = 1;
+ uint32_t transaction_id = tag->request.transaction_id();
+ // Request: Encoding encoding = 2;
+ frr::Encoding encoding = tag->request.encoding();
+ // Request: bool with_defaults = 3;
+ bool with_defaults = tag->request.with_defaults();
+
+ grpc_debug("%s(transaction_id: %u, encoding: %u)", __func__,
+ transaction_id, encoding);
+
+ struct nb_config *nb_config;
+
+ // Load configuration from the transactions database.
+ nb_config = nb_db_transaction_load(transaction_id);
+ if (!nb_config)
+ return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
+ "Transaction not found");
+
+ // Response: DataTree config = 1;
+ auto config = tag->response.mutable_config();
+ config->set_encoding(encoding);
+
+ // Dump data using the requested format.
+ if (data_tree_from_dnode(config, nb_config->dnode,
+ encoding2lyd_format(encoding), with_defaults)
+ != 0) {
+ nb_config_free(nb_config);
+ return grpc::Status(grpc::StatusCode::INTERNAL,
+ "Failed to dump data");
+ }
+
+ nb_config_free(nb_config);
+
+ return grpc::Status::OK;
+}
+
+grpc::Status HandleUnaryExecute(
+ UnaryRpcState<frr::ExecuteRequest, frr::ExecuteResponse> *tag)
+{
+ grpc_debug("%s: entered", __func__);
+
+ struct nb_node *nb_node;
+ struct list *input_list;
+ struct list *output_list;
+ struct listnode *node;
+ struct yang_data *data;
+ const char *xpath;
+ char errmsg[BUFSIZ] = {0};
+
+ // Request: string path = 1;
+ xpath = tag->request.path().c_str();
+
+ grpc_debug("%s(path: \"%s\")", __func__, xpath);
+
+ if (tag->request.path().empty())
+ return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
+ "Data path is empty");
+
+ nb_node = nb_node_find(xpath);
+ if (!nb_node)
+ return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
+ "Unknown data path");
+
+ input_list = yang_data_list_new();
+ output_list = yang_data_list_new();
+
+ // Read input parameters.
+ auto input = tag->request.input();
+ for (const frr::PathValue &pv : input) {
+ // Request: repeated PathValue input = 2;
+ data = yang_data_new(pv.path().c_str(), pv.value().c_str());
+ listnode_add(input_list, data);
+ }
+
+ // Execute callback registered for this XPath.
+ if (nb_callback_rpc(nb_node, xpath, input_list, output_list, errmsg,
+ sizeof(errmsg))
+ != NB_OK) {
+ flog_warn(EC_LIB_NB_CB_RPC, "%s: rpc callback failed: %s",
+ __func__, xpath);
+ list_delete(&input_list);
+ list_delete(&output_list);
+
+ return grpc::Status(grpc::StatusCode::INTERNAL, "RPC failed");
+ }
+
+ // Process output parameters.
+ for (ALL_LIST_ELEMENTS_RO(output_list, node, data)) {
+ // Response: repeated PathValue output = 1;
+ frr::PathValue *pv = tag->response.add_output();
+ pv->set_path(data->xpath);
+ pv->set_value(data->value);
+ }
+
+ // Release memory.
+ list_delete(&input_list);
+ list_delete(&output_list);
+
+ return grpc::Status::OK;
+}
+
+// ------------------------------------------------------
+// Thread Initialization and Run Functions
+// ------------------------------------------------------
+
+
+#define REQUEST_NEWRPC(NAME, cdb) \
+ do { \
+ auto _rpcState = new UnaryRpcState<frr::NAME##Request, \
+ frr::NAME##Response>( \
+ (cdb), &frr::Northbound::AsyncService::Request##NAME, \
+ &HandleUnary##NAME, #NAME); \
+ _rpcState->do_request(&service, cq.get(), true); \
+ } while (0)
+
+#define REQUEST_NEWRPC_STREAMING(NAME) \
+ do { \
+ auto _rpcState = new StreamRpcState<frr::NAME##Request, \
+ frr::NAME##Response, \
+ NAME##ContextType>( \
+ &frr::Northbound::AsyncService::Request##NAME, \
+ &HandleStreaming##NAME, #NAME); \
+ _rpcState->do_request(&service, cq.get(), true); \
+ } while (0)
+
+struct grpc_pthread_attr {
+ struct frr_pthread_attr attr;
+ unsigned long port;
+};
+
+// Capture these objects so we can try to shut down cleanly
+static pthread_mutex_t s_server_lock = PTHREAD_MUTEX_INITIALIZER;
+static grpc::Server *s_server;
+
+static void *grpc_pthread_start(void *arg)
+{
+ struct frr_pthread *fpt = static_cast<frr_pthread *>(arg);
+ uint port = (uint) reinterpret_cast<intptr_t>(fpt->data);
+
+ Candidates candidates;
+ grpc::ServerBuilder builder;
+ std::stringstream server_address;
+ frr::Northbound::AsyncService service;
+
+ frr_pthread_set_name(fpt);
+
+ server_address << "0.0.0.0:" << port;
+ builder.AddListeningPort(server_address.str(),
+ grpc::InsecureServerCredentials());
+ builder.RegisterService(&service);
+ builder.AddChannelArgument(
+ GRPC_ARG_HTTP2_MIN_RECV_PING_INTERVAL_WITHOUT_DATA_MS, 5000);
+ std::unique_ptr<grpc::ServerCompletionQueue> cq =
+ builder.AddCompletionQueue();
+ std::unique_ptr<grpc::Server> server = builder.BuildAndStart();
+ s_server = server.get();
+
+ pthread_mutex_lock(&s_server_lock); // Make coverity happy
+ grpc_running = true;
+ pthread_mutex_unlock(&s_server_lock); // Make coverity happy
+
+ /* Schedule unary RPC handlers */
+ REQUEST_NEWRPC(GetCapabilities, NULL);
+ REQUEST_NEWRPC(CreateCandidate, &candidates);
+ REQUEST_NEWRPC(DeleteCandidate, &candidates);
+ REQUEST_NEWRPC(UpdateCandidate, &candidates);
+ REQUEST_NEWRPC(EditCandidate, &candidates);
+ REQUEST_NEWRPC(LoadToCandidate, &candidates);
+ REQUEST_NEWRPC(Commit, &candidates);
+ REQUEST_NEWRPC(GetTransaction, NULL);
+ REQUEST_NEWRPC(LockConfig, NULL);
+ REQUEST_NEWRPC(UnlockConfig, NULL);
+ REQUEST_NEWRPC(Execute, NULL);
+
+ /* Schedule streaming RPC handlers */
+ REQUEST_NEWRPC_STREAMING(Get);
+ REQUEST_NEWRPC_STREAMING(ListTransactions);
+
+ zlog_notice("gRPC server listening on %s",
+ server_address.str().c_str());
+
+ /* Process inbound RPCs */
+ bool ok;
+ void *tag;
+ while (true) {
+ if (!cq->Next(&tag, &ok)) {
+ grpc_debug("%s: CQ empty exiting", __func__);
+ break;
+ }
+
+ grpc_debug("%s: got next from CQ tag: %p ok: %d", __func__, tag,
+ ok);
+
+ if (!ok) {
+ delete static_cast<RpcStateBase *>(tag);
+ break;
+ }
+
+ RpcStateBase *rpc = static_cast<RpcStateBase *>(tag);
+ if (rpc->get_state() != FINISH)
+ rpc->run(&service, cq.get());
+ else {
+ grpc_debug("%s RPC FINISH -> [delete]", rpc->name);
+ delete rpc;
+ }
+ }
+
+ /* This was probably done for us to get here, but let's be safe */
+ pthread_mutex_lock(&s_server_lock);
+ grpc_running = false;
+ if (s_server) {
+ grpc_debug("%s: shutdown server and CQ", __func__);
+ server->Shutdown();
+ s_server = NULL;
+ }
+ pthread_mutex_unlock(&s_server_lock);
+
+ grpc_debug("%s: shutting down CQ", __func__);
+ cq->Shutdown();
+
+ grpc_debug("%s: draining the CQ", __func__);
+ while (cq->Next(&tag, &ok)) {
+ grpc_debug("%s: drain tag %p", __func__, tag);
+ delete static_cast<RpcStateBase *>(tag);
+ }
+
+ zlog_info("%s: exiting from grpc pthread", __func__);
+ return NULL;
+}
+
+
+static int frr_grpc_init(uint port)
+{
+ struct frr_pthread_attr attr = {
+ .start = grpc_pthread_start,
+ .stop = NULL,
+ };
+
+ grpc_debug("%s: entered", __func__);
+
+ fpt = frr_pthread_new(&attr, "frr-grpc", "frr-grpc");
+ fpt->data = reinterpret_cast<void *>((intptr_t)port);
+
+ /* Create a pthread for gRPC since it runs its own event loop. */
+ if (frr_pthread_run(fpt, NULL) < 0) {
+ flog_err(EC_LIB_SYSTEM_CALL, "%s: error creating pthread: %s",
+ __func__, safe_strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int frr_grpc_finish(void)
+{
+ grpc_debug("%s: entered", __func__);
+
+ if (!fpt)
+ return 0;
+
+ /*
+ * Shut the server down here in main thread. This will cause the wait on
+ * the completion queue (cq.Next()) to exit and cleanup everything else.
+ */
+ pthread_mutex_lock(&s_server_lock);
+ grpc_running = false;
+ if (s_server) {
+ grpc_debug("%s: shutdown server", __func__);
+ s_server->Shutdown();
+ s_server = NULL;
+ }
+ pthread_mutex_unlock(&s_server_lock);
+
+ grpc_debug("%s: joining and destroy grpc thread", __func__);
+ pthread_join(fpt->thread, NULL);
+ frr_pthread_destroy(fpt);
+
+ // Fix protobuf 'memory leaks' during shutdown.
+ // https://groups.google.com/g/protobuf/c/4y_EmQiCGgs
+ google::protobuf::ShutdownProtobufLibrary();
+
+ return 0;
+}
+
+/*
+ * This is done this way because module_init and module_late_init are both
+ * called during daemon pre-fork initialization. Because the GRPC library
+ * spawns threads internally, we need to delay initializing it until after
+ * fork. This is done by scheduling this init function as an event task, since
+ * the event loop doesn't run until after fork.
+ */
+static void frr_grpc_module_very_late_init(struct thread *thread)
+{
+ const char *args = THIS_MODULE->load_args;
+ uint port = GRPC_DEFAULT_PORT;
+
+ if (args) {
+ port = std::stoul(args);
+ if (port < 1024 || port > UINT16_MAX) {
+ flog_err(EC_LIB_GRPC_INIT,
+ "%s: port number must be between 1025 and %d",
+ __func__, UINT16_MAX);
+ goto error;
+ }
+ }
+
+ if (frr_grpc_init(port) < 0)
+ goto error;
+
+ return;
+
+error:
+ flog_err(EC_LIB_GRPC_INIT, "failed to initialize the gRPC module");
+}
+
+static int frr_grpc_module_late_init(struct thread_master *tm)
+{
+ main_master = tm;
+ hook_register(frr_fini, frr_grpc_finish);
+ thread_add_event(tm, frr_grpc_module_very_late_init, NULL, 0, NULL);
+ return 0;
+}
+
+static int frr_grpc_module_init(void)
+{
+ hook_register(frr_late_init, frr_grpc_module_late_init);
+
+ return 0;
+}
+
+FRR_MODULE_SETUP(.name = "frr_grpc", .version = FRR_VERSION,
+ .description = "FRR gRPC northbound module",
+ .init = frr_grpc_module_init, );
diff --git a/lib/northbound_sysrepo.c b/lib/northbound_sysrepo.c
new file mode 100644
index 0000000..f035ac8
--- /dev/null
+++ b/lib/northbound_sysrepo.c
@@ -0,0 +1,769 @@
+/*
+ * 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 "command.h"
+#include "debug.h"
+#include "memory.h"
+#include "libfrr.h"
+#include "lib/version.h"
+#include "northbound.h"
+
+#include <sysrepo.h>
+#include <sysrepo/values.h>
+#include <sysrepo/xpath.h>
+
+DEFINE_MTYPE_STATIC(LIB, SYSREPO, "Sysrepo module");
+
+static struct debug nb_dbg_client_sysrepo = {0, "Northbound client: Sysrepo"};
+
+static struct thread_master *master;
+static sr_session_ctx_t *session;
+static sr_conn_ctx_t *connection;
+static struct nb_transaction *transaction;
+
+static void frr_sr_read_cb(struct thread *thread);
+static int frr_sr_finish(void);
+
+/* Convert FRR YANG data value to sysrepo YANG data value. */
+static int yang_data_frr2sr(struct yang_data *frr_data, sr_val_t *sr_data)
+{
+ struct nb_node *nb_node;
+ const struct lysc_node *snode;
+ struct lysc_node_container *scontainer;
+ struct lysc_node_leaf *sleaf;
+ struct lysc_node_leaflist *sleaflist;
+ LY_DATA_TYPE type;
+
+ sr_val_set_xpath(sr_data, frr_data->xpath);
+
+ nb_node = nb_node_find(frr_data->xpath);
+ if (!nb_node) {
+ flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
+ "%s: unknown data path: %s", __func__,
+ frr_data->xpath);
+ return -1;
+ }
+
+ snode = nb_node->snode;
+ switch (snode->nodetype) {
+ case LYS_CONTAINER:
+ scontainer = (struct lysc_node_container *)snode;
+ if (!CHECK_FLAG(scontainer->flags, LYS_PRESENCE))
+ return -1;
+ sr_data->type = SR_CONTAINER_PRESENCE_T;
+ return 0;
+ case LYS_LIST:
+ sr_data->type = SR_LIST_T;
+ return 0;
+ case LYS_LEAF:
+ sleaf = (struct lysc_node_leaf *)snode;
+ type = sleaf->type->basetype;
+ break;
+ case LYS_LEAFLIST:
+ sleaflist = (struct lysc_node_leaflist *)snode;
+ type = sleaflist->type->basetype;
+ break;
+ default:
+ return -1;
+ }
+
+ switch (type) {
+ case LY_TYPE_BINARY:
+ sr_val_set_str_data(sr_data, SR_BINARY_T, frr_data->value);
+ break;
+ case LY_TYPE_BITS:
+ sr_val_set_str_data(sr_data, SR_BITS_T, frr_data->value);
+ break;
+ case LY_TYPE_BOOL:
+ sr_data->type = SR_BOOL_T;
+ sr_data->data.bool_val = yang_str2bool(frr_data->value);
+ break;
+ case LY_TYPE_DEC64:
+ sr_data->type = SR_DECIMAL64_T;
+ sr_data->data.decimal64_val =
+ yang_str2dec64(frr_data->xpath, frr_data->value);
+ break;
+ case LY_TYPE_EMPTY:
+ sr_data->type = SR_LEAF_EMPTY_T;
+ break;
+ case LY_TYPE_ENUM:
+ sr_val_set_str_data(sr_data, SR_ENUM_T, frr_data->value);
+ break;
+ case LY_TYPE_IDENT:
+ sr_val_set_str_data(sr_data, SR_IDENTITYREF_T, frr_data->value);
+ break;
+ case LY_TYPE_INST:
+ sr_val_set_str_data(sr_data, SR_INSTANCEID_T, frr_data->value);
+ break;
+ case LY_TYPE_INT8:
+ sr_data->type = SR_INT8_T;
+ sr_data->data.int8_val = yang_str2int8(frr_data->value);
+ break;
+ case LY_TYPE_INT16:
+ sr_data->type = SR_INT16_T;
+ sr_data->data.int16_val = yang_str2int16(frr_data->value);
+ break;
+ case LY_TYPE_INT32:
+ sr_data->type = SR_INT32_T;
+ sr_data->data.int32_val = yang_str2int32(frr_data->value);
+ break;
+ case LY_TYPE_INT64:
+ sr_data->type = SR_INT64_T;
+ sr_data->data.int64_val = yang_str2int64(frr_data->value);
+ break;
+ case LY_TYPE_STRING:
+ sr_val_set_str_data(sr_data, SR_STRING_T, frr_data->value);
+ break;
+ case LY_TYPE_UINT8:
+ sr_data->type = SR_UINT8_T;
+ sr_data->data.uint8_val = yang_str2uint8(frr_data->value);
+ break;
+ case LY_TYPE_UINT16:
+ sr_data->type = SR_UINT16_T;
+ sr_data->data.uint16_val = yang_str2uint16(frr_data->value);
+ break;
+ case LY_TYPE_UINT32:
+ sr_data->type = SR_UINT32_T;
+ sr_data->data.uint32_val = yang_str2uint32(frr_data->value);
+ break;
+ case LY_TYPE_UINT64:
+ sr_data->type = SR_UINT64_T;
+ sr_data->data.uint64_val = yang_str2uint64(frr_data->value);
+ break;
+ default:
+ return -1;
+ }
+
+ return 0;
+}
+
+static int frr_sr_process_change(struct nb_config *candidate,
+ sr_change_oper_t sr_op, sr_val_t *sr_old_val,
+ sr_val_t *sr_new_val)
+{
+ struct nb_node *nb_node;
+ enum nb_operation nb_op;
+ sr_val_t *sr_data;
+ const char *xpath;
+ char value_str[YANG_VALUE_MAXLEN];
+ struct yang_data *data;
+ int ret;
+
+ sr_data = sr_new_val ? sr_new_val : sr_old_val;
+ assert(sr_data);
+
+ xpath = sr_data->xpath;
+
+ DEBUGD(&nb_dbg_client_sysrepo, "sysrepo: processing change [xpath %s]",
+ xpath);
+
+ /* Non-presence container - nothing to do. */
+ if (sr_data->type == SR_CONTAINER_T)
+ return NB_OK;
+
+ 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 NB_ERR;
+ }
+
+ /* Map operation values. */
+ switch (sr_op) {
+ case SR_OP_CREATED:
+ case SR_OP_MODIFIED:
+ if (nb_operation_is_valid(NB_OP_CREATE, nb_node->snode))
+ nb_op = NB_OP_CREATE;
+ else if (nb_operation_is_valid(NB_OP_MODIFY, nb_node->snode)) {
+ nb_op = NB_OP_MODIFY;
+ } else
+ /* Ignore list keys modifications. */
+ return NB_OK;
+ break;
+ case SR_OP_DELETED:
+ /*
+ * When a list is deleted or one of its keys is changed, we are
+ * notified about the removal of all of its leafs, even the ones
+ * that are non-optional. We need to ignore these notifications.
+ */
+ if (!nb_operation_is_valid(NB_OP_DESTROY, nb_node->snode))
+ return NB_OK;
+
+ nb_op = NB_OP_DESTROY;
+ break;
+ case SR_OP_MOVED:
+ nb_op = NB_OP_MOVE;
+ break;
+ default:
+ flog_err(EC_LIB_DEVELOPMENT,
+ "%s: unexpected operation %u [xpath %s]", __func__,
+ sr_op, xpath);
+ return NB_ERR;
+ }
+
+ sr_val_to_buff(sr_data, value_str, sizeof(value_str));
+ data = yang_data_new(xpath, value_str);
+
+ ret = nb_candidate_edit(candidate, nb_node, nb_op, 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(nb_op), xpath);
+ return NB_ERR;
+ }
+
+ return NB_OK;
+}
+
+static int frr_sr_config_change_cb_prepare(sr_session_ctx_t *session,
+ const char *module_name)
+{
+ sr_change_iter_t *it;
+ int ret;
+ sr_change_oper_t sr_op;
+ sr_val_t *sr_old_val, *sr_new_val;
+ struct nb_context context = {};
+ struct nb_config *candidate;
+ char errmsg[BUFSIZ] = {0};
+
+ ret = sr_get_changes_iter(session, "//*", &it);
+ if (ret != SR_ERR_OK) {
+ flog_err(EC_LIB_LIBSYSREPO,
+ "%s: sr_get_changes_iter() failed for \"%s\"",
+ __func__, module_name);
+ return ret;
+ }
+
+ candidate = nb_config_dup(running_config);
+
+ while ((ret = sr_get_change_next(session, it, &sr_op, &sr_old_val,
+ &sr_new_val))
+ == SR_ERR_OK) {
+ ret = frr_sr_process_change(candidate, sr_op, sr_old_val,
+ sr_new_val);
+ sr_free_val(sr_old_val);
+ sr_free_val(sr_new_val);
+ if (ret != NB_OK)
+ break;
+ }
+
+ sr_free_change_iter(it);
+ if (ret != NB_OK && ret != SR_ERR_NOT_FOUND) {
+ nb_config_free(candidate);
+ return SR_ERR_INTERNAL;
+ }
+
+ transaction = NULL;
+ context.client = NB_CLIENT_SYSREPO;
+ /*
+ * Validate the configuration changes and allocate all resources
+ * required to apply them.
+ */
+ ret = nb_candidate_commit_prepare(&context, candidate, NULL,
+ &transaction, errmsg, sizeof(errmsg));
+ if (ret != NB_OK && ret != NB_ERR_NO_CHANGES)
+ flog_warn(
+ EC_LIB_LIBSYSREPO,
+ "%s: failed to prepare configuration transaction: %s (%s)",
+ __func__, nb_err_name(ret), errmsg);
+
+ if (!transaction)
+ nb_config_free(candidate);
+
+ /* Map northbound return code to sysrepo return code. */
+ switch (ret) {
+ case NB_OK:
+ return SR_ERR_OK;
+ case NB_ERR_NO_CHANGES:
+ return SR_ERR_OK;
+ case NB_ERR_LOCKED:
+ return SR_ERR_LOCKED;
+ case NB_ERR_RESOURCE:
+ return SR_ERR_NO_MEMORY;
+ default:
+ return SR_ERR_VALIDATION_FAILED;
+ }
+}
+
+static int frr_sr_config_change_cb_apply(sr_session_ctx_t *session,
+ const char *module_name)
+{
+ /* Apply the transaction. */
+ if (transaction) {
+ struct nb_config *candidate = transaction->config;
+ char errmsg[BUFSIZ] = {0};
+
+ nb_candidate_commit_apply(transaction, true, NULL, errmsg,
+ sizeof(errmsg));
+ nb_config_free(candidate);
+ }
+
+ return SR_ERR_OK;
+}
+
+static int frr_sr_config_change_cb_abort(sr_session_ctx_t *session,
+ const char *module_name)
+{
+ /* Abort the transaction. */
+ if (transaction) {
+ struct nb_config *candidate = transaction->config;
+ char errmsg[BUFSIZ] = {0};
+
+ nb_candidate_commit_abort(transaction, errmsg, sizeof(errmsg));
+ nb_config_free(candidate);
+ }
+
+ return SR_ERR_OK;
+}
+
+/* Callback for changes in the running configuration. */
+static int frr_sr_config_change_cb(sr_session_ctx_t *session, uint32_t sub_id,
+ const char *module_name, const char *xpath,
+ sr_event_t sr_ev, uint32_t request_id,
+ void *private_data)
+{
+ switch (sr_ev) {
+ case SR_EV_ENABLED:
+ case SR_EV_CHANGE:
+ return frr_sr_config_change_cb_prepare(session, module_name);
+ case SR_EV_DONE:
+ return frr_sr_config_change_cb_apply(session, module_name);
+ case SR_EV_ABORT:
+ return frr_sr_config_change_cb_abort(session, module_name);
+ default:
+ flog_err(EC_LIB_LIBSYSREPO, "%s: unexpected sysrepo event: %u",
+ __func__, sr_ev);
+ return SR_ERR_INTERNAL;
+ }
+}
+
+static int frr_sr_state_data_iter_cb(const struct lysc_node *snode,
+ struct yang_translator *translator,
+ struct yang_data *data, void *arg)
+{
+ struct lyd_node *dnode = arg;
+ LY_ERR ly_errno;
+
+ ly_errno = 0;
+ ly_errno = lyd_new_path(NULL, ly_native_ctx, data->xpath, data->value,
+ 0, &dnode);
+ if (!dnode && ly_errno) {
+ flog_warn(EC_LIB_LIBYANG, "%s: lyd_new_path() failed",
+ __func__);
+ yang_data_free(data);
+ return NB_ERR;
+ }
+
+ yang_data_free(data);
+ return NB_OK;
+}
+
+/* Callback for state retrieval. */
+static int frr_sr_state_cb(sr_session_ctx_t *session, uint32_t sub_id,
+ const char *module_name, const char *xpath,
+ const char *request_xpath, uint32_t request_id,
+ struct lyd_node **parent, void *private_ctx)
+{
+ struct lyd_node *dnode;
+
+ dnode = *parent;
+ if (nb_oper_data_iterate(request_xpath, NULL, 0,
+ frr_sr_state_data_iter_cb, dnode)
+ != NB_OK) {
+ flog_warn(EC_LIB_NB_OPERATIONAL_DATA,
+ "%s: failed to obtain operational data [xpath %s]",
+ __func__, xpath);
+ return SR_ERR_INTERNAL;
+ }
+
+ *parent = dnode;
+
+ return SR_ERR_OK;
+}
+static int frr_sr_config_rpc_cb(sr_session_ctx_t *session, uint32_t sub_id,
+ const char *xpath, const sr_val_t *sr_input,
+ const size_t input_cnt, sr_event_t sr_ev,
+ uint32_t request_id, sr_val_t **sr_output,
+ size_t *sr_output_cnt, void *private_ctx)
+{
+ struct nb_node *nb_node;
+ struct list *input;
+ struct list *output;
+ struct yang_data *data;
+ size_t cb_output_cnt;
+ int ret = SR_ERR_OK;
+ char errmsg[BUFSIZ] = {0};
+
+ 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 SR_ERR_INTERNAL;
+ }
+
+ input = yang_data_list_new();
+ output = yang_data_list_new();
+
+ /* Process input. */
+ for (size_t i = 0; i < input_cnt; i++) {
+ char value_str[YANG_VALUE_MAXLEN];
+
+ sr_val_to_buff(&sr_input[i], value_str, sizeof(value_str));
+
+ data = yang_data_new(xpath, value_str);
+ listnode_add(input, data);
+ }
+
+ /* Execute callback registered for this XPath. */
+ if (nb_callback_rpc(nb_node, xpath, input, output, errmsg,
+ sizeof(errmsg))
+ != NB_OK) {
+ flog_warn(EC_LIB_NB_CB_RPC, "%s: rpc callback failed: %s",
+ __func__, xpath);
+ ret = SR_ERR_OPERATION_FAILED;
+ goto exit;
+ }
+
+ /* Process output. */
+ if (listcount(output) > 0) {
+ sr_val_t *values = NULL;
+ struct listnode *node;
+ int i = 0;
+
+ cb_output_cnt = listcount(output);
+ ret = sr_new_values(cb_output_cnt, &values);
+ if (ret != SR_ERR_OK) {
+ flog_err(EC_LIB_LIBSYSREPO, "%s: sr_new_values(): %s",
+ __func__, sr_strerror(ret));
+ goto exit;
+ }
+
+ for (ALL_LIST_ELEMENTS_RO(output, node, data)) {
+ if (yang_data_frr2sr(data, &values[i++]) != 0) {
+ flog_err(
+ EC_LIB_SYSREPO_DATA_CONVERT,
+ "%s: failed to convert data to Sysrepo format",
+ __func__);
+ ret = SR_ERR_INTERNAL;
+ sr_free_values(values, cb_output_cnt);
+ goto exit;
+ }
+ }
+
+ *sr_output = values;
+ *sr_output_cnt = cb_output_cnt;
+ }
+
+exit:
+ /* Release memory. */
+ list_delete(&input);
+ list_delete(&output);
+
+ return ret;
+}
+
+static int frr_sr_notification_send(const char *xpath, struct list *arguments)
+{
+ sr_val_t *values = NULL;
+ size_t values_cnt = 0;
+ int ret;
+
+ if (arguments && listcount(arguments) > 0) {
+ struct yang_data *data;
+ struct listnode *node;
+ int i = 0;
+
+ values_cnt = listcount(arguments);
+ ret = sr_new_values(values_cnt, &values);
+ if (ret != SR_ERR_OK) {
+ flog_err(EC_LIB_LIBSYSREPO, "%s: sr_new_values(): %s",
+ __func__, sr_strerror(ret));
+ return NB_ERR;
+ }
+
+ for (ALL_LIST_ELEMENTS_RO(arguments, node, data)) {
+ if (yang_data_frr2sr(data, &values[i++]) != 0) {
+ flog_err(
+ EC_LIB_SYSREPO_DATA_CONVERT,
+ "%s: failed to convert data to sysrepo format",
+ __func__);
+ sr_free_values(values, values_cnt);
+ return NB_ERR;
+ }
+ }
+ }
+
+ ret = sr_notif_send(session, xpath, values, values_cnt, 0, 0);
+ if (ret != SR_ERR_OK) {
+ flog_err(EC_LIB_LIBSYSREPO,
+ "%s: sr_event_notif_send() failed for xpath %s",
+ __func__, xpath);
+ return NB_ERR;
+ }
+
+ return NB_OK;
+}
+
+static void frr_sr_read_cb(struct thread *thread)
+{
+ struct yang_module *module = THREAD_ARG(thread);
+ int fd = THREAD_FD(thread);
+ int ret;
+
+ ret = sr_subscription_process_events(module->sr_subscription, session,
+ NULL);
+ if (ret != SR_ERR_OK) {
+ flog_err(EC_LIB_LIBSYSREPO, "%s: sr_fd_event_process(): %s",
+ __func__, sr_strerror(ret));
+ return;
+ }
+
+ thread_add_read(master, frr_sr_read_cb, module, fd, &module->sr_thread);
+}
+
+static void frr_sr_subscribe_config(struct yang_module *module)
+{
+ int ret;
+
+ DEBUGD(&nb_dbg_client_sysrepo,
+ "sysrepo: subscribing for configuration changes made in the '%s' module",
+ module->name);
+
+ ret = sr_module_change_subscribe(
+ session, module->name, NULL, frr_sr_config_change_cb, NULL, 0,
+ SR_SUBSCR_DEFAULT | SR_SUBSCR_ENABLED | SR_SUBSCR_NO_THREAD,
+ &module->sr_subscription);
+ if (ret != SR_ERR_OK)
+ flog_err(EC_LIB_LIBSYSREPO, "sr_module_change_subscribe(): %s",
+ sr_strerror(ret));
+}
+
+static int frr_sr_subscribe_state(const struct lysc_node *snode, void *arg)
+{
+ struct yang_module *module = arg;
+ struct nb_node *nb_node;
+ int ret;
+
+ if (!CHECK_FLAG(snode->flags, LYS_CONFIG_R))
+ return YANG_ITER_CONTINUE;
+ /* We only need to subscribe to the root of the state subtrees. */
+ if (snode->parent && CHECK_FLAG(snode->parent->flags, LYS_CONFIG_R))
+ return YANG_ITER_CONTINUE;
+
+ nb_node = snode->priv;
+ if (!nb_node)
+ return YANG_ITER_CONTINUE;
+
+ DEBUGD(&nb_dbg_client_sysrepo, "sysrepo: providing data to '%s'",
+ nb_node->xpath);
+
+ ret = sr_oper_get_subscribe(session, snode->module->name,
+ nb_node->xpath, frr_sr_state_cb, NULL, 0,
+ &module->sr_subscription);
+ if (ret != SR_ERR_OK)
+ flog_err(EC_LIB_LIBSYSREPO, "sr_oper_get_items_subscribe(): %s",
+ sr_strerror(ret));
+
+ return YANG_ITER_CONTINUE;
+}
+
+static int frr_sr_subscribe_rpc(const struct lysc_node *snode, void *arg)
+{
+ struct yang_module *module = arg;
+ struct nb_node *nb_node;
+ int ret;
+
+ if (snode->nodetype != LYS_RPC)
+ return YANG_ITER_CONTINUE;
+
+ nb_node = snode->priv;
+ if (!nb_node)
+ return YANG_ITER_CONTINUE;
+
+ DEBUGD(&nb_dbg_client_sysrepo, "sysrepo: providing RPC to '%s'",
+ nb_node->xpath);
+
+ ret = sr_rpc_subscribe(session, nb_node->xpath, frr_sr_config_rpc_cb,
+ NULL, 0, 0, &module->sr_subscription);
+ if (ret != SR_ERR_OK)
+ flog_err(EC_LIB_LIBSYSREPO, "sr_rpc_subscribe(): %s",
+ sr_strerror(ret));
+
+ return YANG_ITER_CONTINUE;
+}
+
+/* CLI commands. */
+DEFUN (debug_nb_sr,
+ debug_nb_sr_cmd,
+ "[no] debug northbound client sysrepo",
+ NO_STR
+ DEBUG_STR
+ "Northbound debugging\n"
+ "Northbound client\n"
+ "Sysrepo\n")
+{
+ uint32_t mode = DEBUG_NODE2MODE(vty->node);
+ bool no = strmatch(argv[0]->text, "no");
+
+ DEBUG_MODE_SET(&nb_dbg_client_sysrepo, mode, !no);
+
+ return CMD_SUCCESS;
+}
+
+static int frr_sr_debug_config_write(struct vty *vty)
+{
+ if (DEBUG_MODE_CHECK(&nb_dbg_client_sysrepo, DEBUG_MODE_CONF))
+ vty_out(vty, "debug northbound client sysrepo\n");
+
+ return 0;
+}
+
+static int frr_sr_debug_set_all(uint32_t flags, bool set)
+{
+ DEBUG_FLAGS_SET(&nb_dbg_client_sysrepo, flags, set);
+
+ /* If all modes have been turned off, don't preserve options. */
+ if (!DEBUG_MODE_CHECK(&nb_dbg_client_sysrepo, DEBUG_MODE_ALL))
+ DEBUG_CLEAR(&nb_dbg_client_sysrepo);
+
+ return 0;
+}
+
+static void frr_sr_cli_init(void)
+{
+ hook_register(nb_client_debug_config_write, frr_sr_debug_config_write);
+ hook_register(nb_client_debug_set_all, frr_sr_debug_set_all);
+
+ install_element(ENABLE_NODE, &debug_nb_sr_cmd);
+ install_element(CONFIG_NODE, &debug_nb_sr_cmd);
+}
+
+/* FRR's Sysrepo initialization. */
+static int frr_sr_init(void)
+{
+ struct yang_module *module;
+ int ret;
+
+ /* Connect to Sysrepo. */
+ ret = sr_connect(SR_CONN_DEFAULT, &connection);
+ if (ret != SR_ERR_OK) {
+ flog_err(EC_LIB_SYSREPO_INIT, "%s: sr_connect(): %s", __func__,
+ sr_strerror(ret));
+ goto cleanup;
+ }
+
+ /* Start session. */
+ ret = sr_session_start(connection, SR_DS_RUNNING, &session);
+ if (ret != SR_ERR_OK) {
+ flog_err(EC_LIB_SYSREPO_INIT, "%s: sr_session_start(): %s",
+ __func__, sr_strerror(ret));
+ goto cleanup;
+ }
+
+ /* Perform subscriptions. */
+ RB_FOREACH (module, yang_modules, &yang_modules) {
+ int event_pipe;
+
+ frr_sr_subscribe_config(module);
+ yang_snodes_iterate(module->info, frr_sr_subscribe_state, 0,
+ module);
+ yang_snodes_iterate(module->info, frr_sr_subscribe_rpc, 0,
+ module);
+
+ /* Watch subscriptions. */
+ ret = sr_get_event_pipe(module->sr_subscription, &event_pipe);
+ if (ret != SR_ERR_OK) {
+ flog_err(EC_LIB_SYSREPO_INIT,
+ "%s: sr_get_event_pipe(): %s", __func__,
+ sr_strerror(ret));
+ goto cleanup;
+ }
+ thread_add_read(master, frr_sr_read_cb, module,
+ event_pipe, &module->sr_thread);
+ }
+
+ hook_register(nb_notification_send, frr_sr_notification_send);
+
+ return 0;
+
+cleanup:
+ frr_sr_finish();
+
+ return -1;
+}
+
+static int frr_sr_finish(void)
+{
+ struct yang_module *module;
+
+ RB_FOREACH (module, yang_modules, &yang_modules) {
+ if (!module->sr_subscription)
+ continue;
+ sr_unsubscribe(module->sr_subscription);
+ THREAD_OFF(module->sr_thread);
+ }
+
+ if (session)
+ sr_session_stop(session);
+ if (connection)
+ sr_disconnect(connection);
+
+ return 0;
+}
+
+static int frr_sr_module_config_loaded(struct thread_master *tm)
+{
+ master = tm;
+
+ if (frr_sr_init() < 0) {
+ flog_err(EC_LIB_SYSREPO_INIT,
+ "failed to initialize the Sysrepo module");
+ return -1;
+ }
+
+ hook_register(frr_fini, frr_sr_finish);
+
+ return 0;
+}
+
+static int frr_sr_module_late_init(struct thread_master *tm)
+{
+ frr_sr_cli_init();
+
+ return 0;
+}
+
+static int frr_sr_module_init(void)
+{
+ hook_register(frr_late_init, frr_sr_module_late_init);
+ hook_register(frr_config_post, frr_sr_module_config_loaded);
+
+ return 0;
+}
+
+FRR_MODULE_SETUP(.name = "frr_sysrepo", .version = FRR_VERSION,
+ .description = "FRR sysrepo integration module",
+ .init = frr_sr_module_init,
+);
diff --git a/lib/ns.h b/lib/ns.h
new file mode 100644
index 0000000..93a3781
--- /dev/null
+++ b/lib/ns.h
@@ -0,0 +1,194 @@
+/*
+ * NS related header.
+ * Copyright (C) 2014 6WIND S.A.
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra 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 _ZEBRA_NS_H
+#define _ZEBRA_NS_H
+
+#include "openbsd-tree.h"
+#include "linklist.h"
+#include "vty.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef uint32_t ns_id_t;
+
+/* the default NS ID */
+#define NS_UNKNOWN UINT32_MAX
+
+/* Default netns directory (Linux) */
+#define NS_RUN_DIR "/var/run/netns"
+
+#ifdef HAVE_NETNS
+#define NS_DEFAULT_NAME "/proc/self/ns/net"
+#else /* !HAVE_NETNS */
+#define NS_DEFAULT_NAME "default-netns"
+#endif /* HAVE_NETNS */
+
+struct ns {
+ RB_ENTRY(ns) entry;
+
+ /* Identifier, same as the vector index */
+ ns_id_t ns_id;
+
+ /* Identifier, mapped on the NSID value */
+ ns_id_t internal_ns_id;
+
+ /* Identifier, value of NSID of default netns,
+ * relative value in that local netns
+ */
+ ns_id_t relative_default_ns;
+
+ /* Name */
+ char *name;
+
+ /* File descriptor */
+ int fd;
+
+ /* Master list of interfaces belonging to this NS */
+ struct list *iflist;
+
+ /* Back Pointer to VRF */
+ void *vrf_ctxt;
+
+ /* User data */
+ void *info;
+};
+RB_HEAD(ns_head, ns);
+RB_PROTOTYPE(ns_head, ns, entry, ns_compare)
+
+/*
+ * API for managing NETNS. eg from zebra daemon
+ * one want to manage the list of NETNS, etc...
+ */
+
+/*
+ * NS hooks
+ */
+
+#define NS_NEW_HOOK 0 /* a new netns is just created */
+#define NS_DELETE_HOOK 1 /* a netns is to be deleted */
+#define NS_ENABLE_HOOK 2 /* a netns is ready to use */
+#define NS_DISABLE_HOOK 3 /* a netns is to be unusable */
+
+/*
+ * Add a specific hook ns module.
+ * @param1: hook type
+ * @param2: the callback function
+ * - param 1: the NS ID
+ * - param 2: the address of the user data pointer (the user data
+ * can be stored in or freed from there)
+ */
+extern void ns_add_hook(int type, int (*)(struct ns *));
+
+
+/*
+ * NS initializer/destructor
+ */
+
+extern void ns_terminate(void);
+
+/* API to initialize NETNS managerment
+ * parameter is the default ns_id
+ */
+extern void ns_init_management(ns_id_t ns_id, ns_id_t internal_ns_idx);
+
+
+/*
+ * NS utilities
+ */
+
+/* Create a socket serving for the given NS
+ */
+int ns_socket(int domain, int type, int protocol, ns_id_t ns_id);
+
+/* return the path of the NETNS */
+extern char *ns_netns_pathname(struct vty *vty, const char *name);
+
+/* Parse and execute a function on all the NETNS */
+#define NS_WALK_CONTINUE 0
+#define NS_WALK_STOP 1
+
+extern void ns_walk_func(int (*func)(struct ns *,
+ void *,
+ void **),
+ void *param_in,
+ void **param_out);
+
+/* API to get the NETNS name, from the ns pointer */
+extern const char *ns_get_name(struct ns *ns);
+
+/* only called from vrf ( when removing netns from vrf)
+ * or at VRF termination
+ */
+extern void ns_delete(struct ns *ns);
+
+/* return > 0 if netns is available
+ * called by VRF to check netns backend is available for VRF
+ */
+extern int ns_have_netns(void);
+
+/* API to get context information of a NS */
+extern void *ns_info_lookup(ns_id_t ns_id);
+
+/* API to map internal ns id value with
+ * user friendly ns id external value
+ */
+extern ns_id_t ns_map_nsid_with_external(ns_id_t ns_id, bool map);
+
+/*
+ * NS init routine
+ * should be called from backendx
+ */
+extern void ns_init(void);
+
+#define NS_DEFAULT 0
+
+/* API that can be used to change from NS */
+extern int ns_switchback_to_initial(void);
+extern int ns_switch_to_netns(const char *netns_name);
+
+/*
+ * NS handling routines.
+ * called by modules that use NS backend
+ */
+
+/* API to search for already present NETNS */
+extern struct ns *ns_lookup(ns_id_t ns_id);
+extern struct ns *ns_lookup_name(const char *name);
+
+/* API to handle NS : creation, enable, disable
+ * for enable, a callback function is passed as parameter
+ * the callback belongs to the module that uses NS as backend
+ * upon enabling the NETNS, the upper layer is informed
+ */
+extern int ns_enable(struct ns *ns, void (*func)(ns_id_t, void *));
+extern struct ns *ns_get_created(struct ns *ns, char *name, ns_id_t ns_id);
+extern ns_id_t ns_id_get_absolute(ns_id_t ns_id_reference, ns_id_t link_nsid);
+extern void ns_disable(struct ns *ns);
+extern struct ns *ns_get_default(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /*_ZEBRA_NS_H*/
diff --git a/lib/ntop.c b/lib/ntop.c
new file mode 100644
index 0000000..1b2dd7a
--- /dev/null
+++ b/lib/ntop.c
@@ -0,0 +1,178 @@
+/*
+ * optimized ntop, about 10x faster than libc versions [as of 2019]
+ *
+ * Copyright (c) 2019 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include "compiler.h"
+
+#define pos (*posx)
+
+static inline void putbyte(uint8_t bytex, char **posx)
+ __attribute__((always_inline)) OPTIMIZE;
+
+static inline void putbyte(uint8_t bytex, char **posx)
+{
+ bool zero = false;
+ int byte = bytex, tmp, a, b;
+
+ tmp = byte - 200;
+ if (tmp >= 0) {
+ *pos++ = '2';
+ zero = true;
+ byte = tmp;
+ } else {
+ tmp = byte - 100;
+ if (tmp >= 0) {
+ *pos++ = '1';
+ zero = true;
+ byte = tmp;
+ }
+ }
+
+ /* make sure the compiler knows the value range of "byte" */
+ assume(byte < 100 && byte >= 0);
+
+ b = byte % 10;
+ a = byte / 10;
+ if (a || zero) {
+ *pos++ = '0' + a;
+ *pos++ = '0' + b;
+ } else
+ *pos++ = '0' + b;
+}
+
+static inline void puthex(uint16_t word, char **posx)
+ __attribute__((always_inline)) OPTIMIZE;
+
+static inline void puthex(uint16_t word, char **posx)
+{
+ const char *digits = "0123456789abcdef";
+ if (word >= 0x1000)
+ *pos++ = digits[(word >> 12) & 0xf];
+ if (word >= 0x100)
+ *pos++ = digits[(word >> 8) & 0xf];
+ if (word >= 0x10)
+ *pos++ = digits[(word >> 4) & 0xf];
+ *pos++ = digits[word & 0xf];
+}
+
+#undef pos
+
+const char *frr_inet_ntop(int af, const void * restrict src,
+ char * restrict dst, socklen_t size)
+ __attribute__((flatten)) OPTIMIZE;
+
+const char *frr_inet_ntop(int af, const void * restrict src,
+ char * restrict dst, socklen_t size)
+{
+ const uint8_t *b = src;
+ /* 8 * "abcd:" for IPv6
+ * note: the IPv4-embedded IPv6 syntax is only used for ::A.B.C.D,
+ * which isn't longer than 40 chars either. even with ::ffff:A.B.C.D
+ * it's shorter.
+ */
+ char buf[8 * 5], *o = buf;
+ size_t best = 0, bestlen = 0, curlen = 0, i;
+
+ switch (af) {
+ case AF_INET:
+inet4:
+ putbyte(b[0], &o);
+ *o++ = '.';
+ putbyte(b[1], &o);
+ *o++ = '.';
+ putbyte(b[2], &o);
+ *o++ = '.';
+ putbyte(b[3], &o);
+ *o++ = '\0';
+ break;
+ case AF_INET6:
+ for (i = 0; i < 8; i++) {
+ if (b[i * 2] || b[i * 2 + 1]) {
+ if (curlen && curlen > bestlen) {
+ best = i - curlen;
+ bestlen = curlen;
+ }
+ curlen = 0;
+ continue;
+ }
+ curlen++;
+ }
+ if (curlen && curlen > bestlen) {
+ best = i - curlen;
+ bestlen = curlen;
+ }
+ /* do we want ::ffff:A.B.C.D? */
+ if (best == 0 && bestlen == 6) {
+ *o++ = ':';
+ *o++ = ':';
+ b += 12;
+ goto inet4;
+ }
+ if (bestlen == 1)
+ bestlen = 0;
+
+ for (i = 0; i < 8; i++) {
+ if (bestlen && i == best) {
+ if (i == 0)
+ *o++ = ':';
+ *o++ = ':';
+ continue;
+ }
+ if (i > best && i < best + bestlen) {
+ continue;
+ }
+ puthex((b[i * 2] << 8) | b[i * 2 + 1], &o);
+
+ if (i < 7)
+ *o++ = ':';
+ }
+ *o++ = '\0';
+ break;
+ default:
+ return NULL;
+ }
+
+ i = o - buf;
+ if (i > size)
+ return NULL;
+ /* compiler might inline memcpy if it knows the length is short,
+ * although neither gcc nor clang actually do this currently [2019]
+ */
+ assume(i <= 8 * 5);
+ memcpy(dst, buf, i);
+ return dst;
+}
+
+#if !defined(INET_NTOP_NO_OVERRIDE) && !defined(__APPLE__)
+/* we want to override libc inet_ntop, but make sure it shows up in backtraces
+ * as frr_inet_ntop (to avoid confusion while debugging)
+ */
+const char *inet_ntop(int af, const void *src, char *dst, socklen_t size)
+ __attribute__((alias ("frr_inet_ntop")));
+#endif
diff --git a/lib/openbsd-queue.h b/lib/openbsd-queue.h
new file mode 100644
index 0000000..d4c08a3
--- /dev/null
+++ b/lib/openbsd-queue.h
@@ -0,0 +1,583 @@
+/* $OpenBSD: queue.h,v 1.43 2015/12/28 19:38:40 millert Exp $ */
+/* $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $ */
+
+/*
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)queue.h 8.5 (Berkeley) 8/20/94
+ */
+
+#ifndef _SYS_QUEUE_H_
+#define _SYS_QUEUE_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * This file defines five types of data structures: singly-linked lists,
+ * lists, simple queues, tail queues and XOR simple queues.
+ *
+ *
+ * A singly-linked list is headed by a single forward pointer. The elements
+ * are singly linked for minimum space and pointer manipulation overhead at
+ * the expense of O(n) removal for arbitrary elements. New elements can be
+ * added to the list after an existing element or at the head of the list.
+ * Elements being removed from the head of the list should use the explicit
+ * macro for this purpose for optimum efficiency. A singly-linked list may
+ * only be traversed in the forward direction. Singly-linked lists are ideal
+ * for applications with large datasets and few or no removals or for
+ * implementing a LIFO queue.
+ *
+ * A list is headed by a single forward pointer (or an array of forward
+ * pointers for a hash table header). The elements are doubly linked
+ * so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before
+ * or after an existing element or at the head of the list. A list
+ * may only be traversed in the forward direction.
+ *
+ * A simple queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are singly
+ * linked to save space, so elements can only be removed from the
+ * head of the list. New elements can be added to the list before or after
+ * an existing element, at the head of the list, or at the end of the
+ * list. A simple queue may only be traversed in the forward direction.
+ *
+ * A tail queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are doubly
+ * linked so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before or
+ * after an existing element, at the head of the list, or at the end of
+ * the list. A tail queue may be traversed in either direction.
+ *
+ * An XOR simple queue is used in the same way as a regular simple queue.
+ * The difference is that the head structure also includes a "cookie" that
+ * is XOR'd with the queue pointer (first, last or next) to generate the
+ * real pointer value.
+ *
+ * For details on the use of these macros, see the queue(3) manual page.
+ */
+
+#if defined(QUEUE_MACRO_DEBUG) || (defined(_KERNEL) && defined(DIAGNOSTIC))
+#define _Q_INVALIDATE(a) (a) = ((void *)-1)
+#else
+#define _Q_INVALIDATE(a)
+#endif
+
+/*
+ * Singly-linked List definitions.
+ */
+#define SLIST_HEAD(name, type) \
+ struct name { \
+ struct type *slh_first; /* first element */ \
+ }
+
+#define SLIST_HEAD_INITIALIZER(head) \
+ { \
+ NULL \
+ }
+
+#define SLIST_ENTRY(type) \
+ struct { \
+ struct type *sle_next; /* next element */ \
+ }
+
+/*
+ * Singly-linked List access methods.
+ */
+#define SLIST_FIRST(head) ((head)->slh_first)
+#define SLIST_END(head) NULL
+#define SLIST_EMPTY(head) (SLIST_FIRST(head) == SLIST_END(head))
+#define SLIST_NEXT(elm, field) ((elm)->field.sle_next)
+
+#define SLIST_FOREACH(var, head, field) \
+ for ((var) = SLIST_FIRST(head); (var) != SLIST_END(head); \
+ (var) = SLIST_NEXT(var, field))
+
+#define SLIST_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = SLIST_FIRST(head); \
+ (var) && ((tvar) = SLIST_NEXT(var, field), 1); (var) = (tvar))
+
+/*
+ * Singly-linked List functions.
+ */
+#define SLIST_INIT(head) \
+ { \
+ SLIST_FIRST(head) = SLIST_END(head); \
+ }
+
+#define SLIST_INSERT_AFTER(slistelm, elm, field) \
+ do { \
+ (elm)->field.sle_next = (slistelm)->field.sle_next; \
+ (slistelm)->field.sle_next = (elm); \
+ } while (0)
+
+#define SLIST_INSERT_HEAD(head, elm, field) \
+ do { \
+ (elm)->field.sle_next = (head)->slh_first; \
+ (head)->slh_first = (elm); \
+ } while (0)
+
+#define SLIST_REMOVE_AFTER(elm, field) \
+ do { \
+ (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next; \
+ } while (0)
+
+#define SLIST_REMOVE_HEAD(head, field) \
+ do { \
+ (head)->slh_first = (head)->slh_first->field.sle_next; \
+ } while (0)
+
+#define SLIST_REMOVE(head, elm, type, field) \
+ do { \
+ if ((head)->slh_first == (elm)) { \
+ SLIST_REMOVE_HEAD((head), field); \
+ } else { \
+ struct type *curelm = (head)->slh_first; \
+ \
+ while (curelm->field.sle_next != (elm)) \
+ curelm = curelm->field.sle_next; \
+ curelm->field.sle_next = \
+ curelm->field.sle_next->field.sle_next; \
+ } \
+ _Q_INVALIDATE((elm)->field.sle_next); \
+ } while (0)
+
+/*
+ * List definitions.
+ */
+#define LIST_HEAD(name, type) \
+ struct name { \
+ struct type *lh_first; /* first element */ \
+ }
+
+#define LIST_HEAD_INITIALIZER(head) \
+ { \
+ NULL \
+ }
+
+#define LIST_ENTRY(type) \
+ struct { \
+ struct type *le_next; /* next element */ \
+ struct type **le_prev; /* address of previous next element */ \
+ }
+
+/*
+ * List access methods.
+ */
+#define LIST_FIRST(head) ((head)->lh_first)
+#define LIST_END(head) NULL
+#define LIST_EMPTY(head) (LIST_FIRST(head) == LIST_END(head))
+#define LIST_NEXT(elm, field) ((elm)->field.le_next)
+
+#define LIST_FOREACH(var, head, field) \
+ for ((var) = LIST_FIRST(head); (var) != LIST_END(head); \
+ (var) = LIST_NEXT(var, field))
+
+#define LIST_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = LIST_FIRST(head); \
+ (var) && ((tvar) = LIST_NEXT(var, field), 1); (var) = (tvar))
+
+/*
+ * List functions.
+ */
+#define LIST_INIT(head) \
+ do { \
+ LIST_FIRST(head) = LIST_END(head); \
+ } while (0)
+
+#define LIST_INSERT_AFTER(listelm, elm, field) \
+ do { \
+ if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \
+ (listelm)->field.le_next->field.le_prev = \
+ &(elm)->field.le_next; \
+ (listelm)->field.le_next = (elm); \
+ (elm)->field.le_prev = &(listelm)->field.le_next; \
+ } while (0)
+
+#define LIST_INSERT_BEFORE(listelm, elm, field) \
+ do { \
+ (elm)->field.le_prev = (listelm)->field.le_prev; \
+ (elm)->field.le_next = (listelm); \
+ *(listelm)->field.le_prev = (elm); \
+ (listelm)->field.le_prev = &(elm)->field.le_next; \
+ } while (0)
+
+#define LIST_INSERT_HEAD(head, elm, field) \
+ do { \
+ if (((elm)->field.le_next = (head)->lh_first) != NULL) \
+ (head)->lh_first->field.le_prev = \
+ &(elm)->field.le_next; \
+ (head)->lh_first = (elm); \
+ (elm)->field.le_prev = &(head)->lh_first; \
+ } while (0)
+
+#define LIST_REMOVE(elm, field) \
+ do { \
+ if ((elm)->field.le_next != NULL) \
+ (elm)->field.le_next->field.le_prev = \
+ (elm)->field.le_prev; \
+ *(elm)->field.le_prev = (elm)->field.le_next; \
+ _Q_INVALIDATE((elm)->field.le_prev); \
+ _Q_INVALIDATE((elm)->field.le_next); \
+ } while (0)
+
+#define LIST_REPLACE(elm, elm2, field) \
+ do { \
+ if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \
+ (elm2)->field.le_next->field.le_prev = \
+ &(elm2)->field.le_next; \
+ (elm2)->field.le_prev = (elm)->field.le_prev; \
+ *(elm2)->field.le_prev = (elm2); \
+ _Q_INVALIDATE((elm)->field.le_prev); \
+ _Q_INVALIDATE((elm)->field.le_next); \
+ } while (0)
+
+/*
+ * Simple queue definitions.
+ */
+#define SIMPLEQ_HEAD(name, type) \
+ struct name { \
+ struct type *sqh_first; /* first element */ \
+ struct type **sqh_last; /* addr of last next element */ \
+ }
+
+#define SIMPLEQ_HEAD_INITIALIZER(head) \
+ { \
+ NULL, &(head).sqh_first \
+ }
+
+#define SIMPLEQ_ENTRY(type) \
+ struct { \
+ struct type *sqe_next; /* next element */ \
+ }
+
+/*
+ * Simple queue access methods.
+ */
+#define SIMPLEQ_FIRST(head) ((head)->sqh_first)
+#define SIMPLEQ_END(head) NULL
+#define SIMPLEQ_EMPTY(head) (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head))
+#define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next)
+
+#define SIMPLEQ_FOREACH(var, head, field) \
+ for ((var) = SIMPLEQ_FIRST(head); (var) != SIMPLEQ_END(head); \
+ (var) = SIMPLEQ_NEXT(var, field))
+
+#define SIMPLEQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = SIMPLEQ_FIRST(head); \
+ (var) && ((tvar) = SIMPLEQ_NEXT(var, field), 1); (var) = (tvar))
+
+/*
+ * Simple queue functions.
+ */
+#define SIMPLEQ_INIT(head) \
+ do { \
+ (head)->sqh_first = NULL; \
+ (head)->sqh_last = &(head)->sqh_first; \
+ } while (0)
+
+#define SIMPLEQ_INSERT_HEAD(head, elm, field) \
+ do { \
+ if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \
+ (head)->sqh_last = &(elm)->field.sqe_next; \
+ (head)->sqh_first = (elm); \
+ } while (0)
+
+#define SIMPLEQ_INSERT_TAIL(head, elm, field) \
+ do { \
+ (elm)->field.sqe_next = NULL; \
+ *(head)->sqh_last = (elm); \
+ (head)->sqh_last = &(elm)->field.sqe_next; \
+ } while (0)
+
+#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) \
+ do { \
+ if (((elm)->field.sqe_next = (listelm)->field.sqe_next) \
+ == NULL) \
+ (head)->sqh_last = &(elm)->field.sqe_next; \
+ (listelm)->field.sqe_next = (elm); \
+ } while (0)
+
+#define SIMPLEQ_REMOVE_HEAD(head, field) \
+ do { \
+ if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) \
+ == NULL) \
+ (head)->sqh_last = &(head)->sqh_first; \
+ } while (0)
+
+#define SIMPLEQ_REMOVE_AFTER(head, elm, field) \
+ do { \
+ if (((elm)->field.sqe_next = \
+ (elm)->field.sqe_next->field.sqe_next) \
+ == NULL) \
+ (head)->sqh_last = &(elm)->field.sqe_next; \
+ } while (0)
+
+#define SIMPLEQ_CONCAT(head1, head2) \
+ do { \
+ if (!SIMPLEQ_EMPTY((head2))) { \
+ *(head1)->sqh_last = (head2)->sqh_first; \
+ (head1)->sqh_last = (head2)->sqh_last; \
+ SIMPLEQ_INIT((head2)); \
+ } \
+ } while (0)
+
+/*
+ * XOR Simple queue definitions.
+ */
+#define XSIMPLEQ_HEAD(name, type) \
+ struct name { \
+ struct type *sqx_first; /* first element */ \
+ struct type **sqx_last; /* addr of last next element */ \
+ unsigned long sqx_cookie; \
+ }
+
+#define XSIMPLEQ_ENTRY(type) \
+ struct { \
+ struct type *sqx_next; /* next element */ \
+ }
+
+/*
+ * XOR Simple queue access methods.
+ */
+#define XSIMPLEQ_XOR(head, ptr) \
+ ((__typeof(ptr))((head)->sqx_cookie ^ (unsigned long)(ptr)))
+#define XSIMPLEQ_FIRST(head) XSIMPLEQ_XOR(head, ((head)->sqx_first))
+#define XSIMPLEQ_END(head) NULL
+#define XSIMPLEQ_EMPTY(head) (XSIMPLEQ_FIRST(head) == XSIMPLEQ_END(head))
+#define XSIMPLEQ_NEXT(head, elm, field) XSIMPLEQ_XOR(head, ((elm)->field.sqx_next))
+
+#define XSIMPLEQ_FOREACH(var, head, field) \
+ for ((var) = XSIMPLEQ_FIRST(head); (var) != XSIMPLEQ_END(head); \
+ (var) = XSIMPLEQ_NEXT(head, var, field))
+
+#define XSIMPLEQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = XSIMPLEQ_FIRST(head); \
+ (var) && ((tvar) = XSIMPLEQ_NEXT(head, var, field), 1); \
+ (var) = (tvar))
+
+/*
+ * XOR Simple queue functions.
+ */
+#define XSIMPLEQ_INIT(head) \
+ do { \
+ arc4random_buf(&(head)->sqx_cookie, \
+ sizeof((head)->sqx_cookie)); \
+ (head)->sqx_first = XSIMPLEQ_XOR(head, NULL); \
+ (head)->sqx_last = XSIMPLEQ_XOR(head, &(head)->sqx_first); \
+ } while (0)
+
+#define XSIMPLEQ_INSERT_HEAD(head, elm, field) \
+ do { \
+ if (((elm)->field.sqx_next = (head)->sqx_first) \
+ == XSIMPLEQ_XOR(head, NULL)) \
+ (head)->sqx_last = \
+ XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \
+ (head)->sqx_first = XSIMPLEQ_XOR(head, (elm)); \
+ } while (0)
+
+#define XSIMPLEQ_INSERT_TAIL(head, elm, field) \
+ do { \
+ (elm)->field.sqx_next = XSIMPLEQ_XOR(head, NULL); \
+ *(XSIMPLEQ_XOR(head, (head)->sqx_last)) = \
+ XSIMPLEQ_XOR(head, (elm)); \
+ (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \
+ } while (0)
+
+#define XSIMPLEQ_INSERT_AFTER(head, listelm, elm, field) \
+ do { \
+ if (((elm)->field.sqx_next = (listelm)->field.sqx_next) \
+ == XSIMPLEQ_XOR(head, NULL)) \
+ (head)->sqx_last = \
+ XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \
+ (listelm)->field.sqx_next = XSIMPLEQ_XOR(head, (elm)); \
+ } while (0)
+
+#define XSIMPLEQ_REMOVE_HEAD(head, field) \
+ do { \
+ if (((head)->sqx_first = XSIMPLEQ_XOR(head, (head)->sqx_first) \
+ ->field.sqx_next) \
+ == XSIMPLEQ_XOR(head, NULL)) \
+ (head)->sqx_last = \
+ XSIMPLEQ_XOR(head, &(head)->sqx_first); \
+ } while (0)
+
+#define XSIMPLEQ_REMOVE_AFTER(head, elm, field) \
+ do { \
+ if (((elm)->field.sqx_next = \
+ XSIMPLEQ_XOR(head, (elm)->field.sqx_next) \
+ ->field.sqx_next) \
+ == XSIMPLEQ_XOR(head, NULL)) \
+ (head)->sqx_last = \
+ XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \
+ } while (0)
+
+
+/*
+ * Tail queue definitions.
+ */
+#define TAILQ_HEAD(name, type) \
+ struct name { \
+ struct type *tqh_first; /* first element */ \
+ struct type **tqh_last; /* addr of last next element */ \
+ }
+
+#define TAILQ_HEAD_INITIALIZER(head) \
+ { \
+ NULL, &(head).tqh_first \
+ }
+
+#define TAILQ_ENTRY(type) \
+ struct { \
+ struct type *tqe_next; /* next element */ \
+ struct type **tqe_prev; /* address of previous next element */ \
+ }
+
+/*
+ * Tail queue access methods.
+ */
+#define TAILQ_FIRST(head) ((head)->tqh_first)
+#define TAILQ_END(head) NULL
+#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
+#define TAILQ_LAST(head, headname) \
+ (*(((struct headname *)((head)->tqh_last))->tqh_last))
+/* XXX */
+#define TAILQ_PREV(elm, headname, field) \
+ (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
+#define TAILQ_EMPTY(head) (TAILQ_FIRST(head) == TAILQ_END(head))
+
+#define TAILQ_FOREACH(var, head, field) \
+ for ((var) = TAILQ_FIRST(head); (var) != TAILQ_END(head); \
+ (var) = TAILQ_NEXT(var, field))
+
+#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = TAILQ_FIRST(head); \
+ (var) != TAILQ_END(head) && ((tvar) = TAILQ_NEXT(var, field), 1); \
+ (var) = (tvar))
+
+
+#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \
+ for ((var) = TAILQ_LAST(head, headname); (var) != TAILQ_END(head); \
+ (var) = TAILQ_PREV(var, headname, field))
+
+#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \
+ for ((var) = TAILQ_LAST(head, headname); \
+ (var) != TAILQ_END(head) \
+ && ((tvar) = TAILQ_PREV(var, headname, field), 1); \
+ (var) = (tvar))
+
+/*
+ * Tail queue functions.
+ */
+#define TAILQ_INIT(head) \
+ do { \
+ (head)->tqh_first = NULL; \
+ (head)->tqh_last = &(head)->tqh_first; \
+ } while (0)
+
+#define TAILQ_INSERT_HEAD(head, elm, field) \
+ do { \
+ if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \
+ (head)->tqh_first->field.tqe_prev = \
+ &(elm)->field.tqe_next; \
+ else \
+ (head)->tqh_last = &(elm)->field.tqe_next; \
+ (head)->tqh_first = (elm); \
+ (elm)->field.tqe_prev = &(head)->tqh_first; \
+ } while (0)
+
+#define TAILQ_INSERT_TAIL(head, elm, field) \
+ do { \
+ (elm)->field.tqe_next = NULL; \
+ (elm)->field.tqe_prev = (head)->tqh_last; \
+ *(head)->tqh_last = (elm); \
+ (head)->tqh_last = &(elm)->field.tqe_next; \
+ } while (0)
+
+#define TAILQ_INSERT_AFTER(head, listelm, elm, field) \
+ do { \
+ if (((elm)->field.tqe_next = (listelm)->field.tqe_next) \
+ != NULL) \
+ (elm)->field.tqe_next->field.tqe_prev = \
+ &(elm)->field.tqe_next; \
+ else \
+ (head)->tqh_last = &(elm)->field.tqe_next; \
+ (listelm)->field.tqe_next = (elm); \
+ (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \
+ } while (0)
+
+#define TAILQ_INSERT_BEFORE(listelm, elm, field) \
+ do { \
+ (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \
+ (elm)->field.tqe_next = (listelm); \
+ *(listelm)->field.tqe_prev = (elm); \
+ (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \
+ } while (0)
+
+#define TAILQ_REMOVE(head, elm, field) \
+ do { \
+ if (((elm)->field.tqe_next) != NULL) \
+ (elm)->field.tqe_next->field.tqe_prev = \
+ (elm)->field.tqe_prev; \
+ else \
+ (head)->tqh_last = (elm)->field.tqe_prev; \
+ *(elm)->field.tqe_prev = (elm)->field.tqe_next; \
+ _Q_INVALIDATE((elm)->field.tqe_prev); \
+ _Q_INVALIDATE((elm)->field.tqe_next); \
+ } while (0)
+
+#define TAILQ_REPLACE(head, elm, elm2, field) \
+ do { \
+ if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \
+ (elm2)->field.tqe_next->field.tqe_prev = \
+ &(elm2)->field.tqe_next; \
+ else \
+ (head)->tqh_last = &(elm2)->field.tqe_next; \
+ (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \
+ *(elm2)->field.tqe_prev = (elm2); \
+ _Q_INVALIDATE((elm)->field.tqe_prev); \
+ _Q_INVALIDATE((elm)->field.tqe_next); \
+ } while (0)
+
+#define TAILQ_CONCAT(head1, head2, field) \
+ do { \
+ if (!TAILQ_EMPTY(head2)) { \
+ *(head1)->tqh_last = (head2)->tqh_first; \
+ (head2)->tqh_first->field.tqe_prev = \
+ (head1)->tqh_last; \
+ (head1)->tqh_last = (head2)->tqh_last; \
+ TAILQ_INIT((head2)); \
+ } \
+ } while (0)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !_SYS_QUEUE_H_ */
diff --git a/lib/openbsd-tree.c b/lib/openbsd-tree.c
new file mode 100644
index 0000000..98d2e15
--- /dev/null
+++ b/lib/openbsd-tree.c
@@ -0,0 +1,620 @@
+/* $OpenBSD: subr_tree.c,v 1.9 2017/06/08 03:30:52 dlg Exp $ */
+
+/*
+ * Copyright 2002 Niels Provos <provos@citi.umich.edu>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 2016 David Gwynne <dlg@openbsd.org>
+ *
+ * Permission to use, copy, modify, and 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+
+#include <lib/openbsd-tree.h>
+
+static inline struct rb_entry *rb_n2e(const struct rb_type *t, void *node)
+{
+ unsigned long addr = (unsigned long)node;
+
+ return ((struct rb_entry *)(addr + t->t_offset));
+}
+
+static inline void *rb_e2n(const struct rb_type *t, struct rb_entry *rbe)
+{
+ unsigned long addr = (unsigned long)rbe;
+
+ return ((void *)(addr - t->t_offset));
+}
+
+#define RBE_LEFT(_rbe) (_rbe)->rbt_left
+#define RBE_RIGHT(_rbe) (_rbe)->rbt_right
+#define RBE_PARENT(_rbe) (_rbe)->rbt_parent
+#define RBE_COLOR(_rbe) (_rbe)->rbt_color
+
+#define RBH_ROOT(_rbt) (_rbt)->rbt_root
+
+static inline void rbe_set(struct rb_entry *rbe, struct rb_entry *parent)
+{
+ RBE_PARENT(rbe) = parent;
+ RBE_LEFT(rbe) = RBE_RIGHT(rbe) = NULL;
+ RBE_COLOR(rbe) = RB_RED;
+}
+
+static inline void rbe_set_blackred(struct rb_entry *black,
+ struct rb_entry *red)
+{
+ RBE_COLOR(black) = RB_BLACK;
+ RBE_COLOR(red) = RB_RED;
+}
+
+static inline void rbe_augment(const struct rb_type *t, struct rb_entry *rbe)
+{
+ (*t->t_augment)(rb_e2n(t, rbe));
+}
+
+static inline void rbe_if_augment(const struct rb_type *t, struct rb_entry *rbe)
+{
+ if (t->t_augment != NULL)
+ rbe_augment(t, rbe);
+}
+
+static inline void rbe_rotate_left(const struct rb_type *t,
+ struct rbt_tree *rbt, struct rb_entry *rbe)
+{
+ struct rb_entry *parent;
+ struct rb_entry *tmp;
+
+ tmp = RBE_RIGHT(rbe);
+ RBE_RIGHT(rbe) = RBE_LEFT(tmp);
+ if (RBE_RIGHT(rbe) != NULL)
+ RBE_PARENT(RBE_LEFT(tmp)) = rbe;
+
+ parent = RBE_PARENT(rbe);
+ RBE_PARENT(tmp) = parent;
+ if (parent != NULL) {
+ if (rbe == RBE_LEFT(parent))
+ RBE_LEFT(parent) = tmp;
+ else
+ RBE_RIGHT(parent) = tmp;
+ } else
+ RBH_ROOT(rbt) = tmp;
+
+ RBE_LEFT(tmp) = rbe;
+ RBE_PARENT(rbe) = tmp;
+
+ if (t->t_augment != NULL) {
+ rbe_augment(t, rbe);
+ rbe_augment(t, tmp);
+ parent = RBE_PARENT(tmp);
+ if (parent != NULL)
+ rbe_augment(t, parent);
+ }
+}
+
+static inline void rbe_rotate_right(const struct rb_type *t,
+ struct rbt_tree *rbt, struct rb_entry *rbe)
+{
+ struct rb_entry *parent;
+ struct rb_entry *tmp;
+
+ tmp = RBE_LEFT(rbe);
+ RBE_LEFT(rbe) = RBE_RIGHT(tmp);
+ if (RBE_LEFT(rbe) != NULL)
+ RBE_PARENT(RBE_RIGHT(tmp)) = rbe;
+
+ parent = RBE_PARENT(rbe);
+ RBE_PARENT(tmp) = parent;
+ if (parent != NULL) {
+ if (rbe == RBE_LEFT(parent))
+ RBE_LEFT(parent) = tmp;
+ else
+ RBE_RIGHT(parent) = tmp;
+ } else
+ RBH_ROOT(rbt) = tmp;
+
+ RBE_RIGHT(tmp) = rbe;
+ RBE_PARENT(rbe) = tmp;
+
+ if (t->t_augment != NULL) {
+ rbe_augment(t, rbe);
+ rbe_augment(t, tmp);
+ parent = RBE_PARENT(tmp);
+ if (parent != NULL)
+ rbe_augment(t, parent);
+ }
+}
+
+static inline void rbe_insert_color(const struct rb_type *t,
+ struct rbt_tree *rbt, struct rb_entry *rbe)
+{
+ struct rb_entry *parent, *gparent, *tmp;
+
+ while ((parent = RBE_PARENT(rbe)) != NULL
+ && RBE_COLOR(parent) == RB_RED) {
+ gparent = RBE_PARENT(parent);
+
+ if (parent == RBE_LEFT(gparent)) {
+ tmp = RBE_RIGHT(gparent);
+ if (tmp != NULL && RBE_COLOR(tmp) == RB_RED) {
+ RBE_COLOR(tmp) = RB_BLACK;
+ rbe_set_blackred(parent, gparent);
+ rbe = gparent;
+ continue;
+ }
+
+ if (RBE_RIGHT(parent) == rbe) {
+ rbe_rotate_left(t, rbt, parent);
+ tmp = parent;
+ parent = rbe;
+ rbe = tmp;
+ }
+
+ rbe_set_blackred(parent, gparent);
+ rbe_rotate_right(t, rbt, gparent);
+ } else {
+ tmp = RBE_LEFT(gparent);
+ if (tmp != NULL && RBE_COLOR(tmp) == RB_RED) {
+ RBE_COLOR(tmp) = RB_BLACK;
+ rbe_set_blackred(parent, gparent);
+ rbe = gparent;
+ continue;
+ }
+
+ if (RBE_LEFT(parent) == rbe) {
+ rbe_rotate_right(t, rbt, parent);
+ tmp = parent;
+ parent = rbe;
+ rbe = tmp;
+ }
+
+ rbe_set_blackred(parent, gparent);
+ rbe_rotate_left(t, rbt, gparent);
+ }
+ }
+
+ RBE_COLOR(RBH_ROOT(rbt)) = RB_BLACK;
+}
+
+static inline void rbe_remove_color(const struct rb_type *t,
+ struct rbt_tree *rbt,
+ struct rb_entry *parent,
+ struct rb_entry *rbe)
+{
+ struct rb_entry *tmp;
+
+ while ((rbe == NULL || RBE_COLOR(rbe) == RB_BLACK)
+ && rbe != RBH_ROOT(rbt) && parent) {
+ if (RBE_LEFT(parent) == rbe) {
+ tmp = RBE_RIGHT(parent);
+ if (RBE_COLOR(tmp) == RB_RED) {
+ rbe_set_blackred(tmp, parent);
+ rbe_rotate_left(t, rbt, parent);
+ tmp = RBE_RIGHT(parent);
+ }
+ if ((RBE_LEFT(tmp) == NULL
+ || RBE_COLOR(RBE_LEFT(tmp)) == RB_BLACK)
+ && (RBE_RIGHT(tmp) == NULL
+ || RBE_COLOR(RBE_RIGHT(tmp)) == RB_BLACK)) {
+ RBE_COLOR(tmp) = RB_RED;
+ rbe = parent;
+ parent = RBE_PARENT(rbe);
+ } else {
+ if (RBE_RIGHT(tmp) == NULL
+ || RBE_COLOR(RBE_RIGHT(tmp)) == RB_BLACK) {
+ struct rb_entry *oleft;
+
+ oleft = RBE_LEFT(tmp);
+ if (oleft != NULL)
+ RBE_COLOR(oleft) = RB_BLACK;
+
+ RBE_COLOR(tmp) = RB_RED;
+ rbe_rotate_right(t, rbt, tmp);
+ tmp = RBE_RIGHT(parent);
+ }
+
+ RBE_COLOR(tmp) = RBE_COLOR(parent);
+ RBE_COLOR(parent) = RB_BLACK;
+ if (RBE_RIGHT(tmp))
+ RBE_COLOR(RBE_RIGHT(tmp)) = RB_BLACK;
+
+ rbe_rotate_left(t, rbt, parent);
+ rbe = RBH_ROOT(rbt);
+ break;
+ }
+ } else {
+ tmp = RBE_LEFT(parent);
+ if (RBE_COLOR(tmp) == RB_RED) {
+ rbe_set_blackred(tmp, parent);
+ rbe_rotate_right(t, rbt, parent);
+ tmp = RBE_LEFT(parent);
+ }
+
+ if ((RBE_LEFT(tmp) == NULL
+ || RBE_COLOR(RBE_LEFT(tmp)) == RB_BLACK)
+ && (RBE_RIGHT(tmp) == NULL
+ || RBE_COLOR(RBE_RIGHT(tmp)) == RB_BLACK)) {
+ RBE_COLOR(tmp) = RB_RED;
+ rbe = parent;
+ parent = RBE_PARENT(rbe);
+ } else {
+ if (RBE_LEFT(tmp) == NULL
+ || RBE_COLOR(RBE_LEFT(tmp)) == RB_BLACK) {
+ struct rb_entry *oright;
+
+ oright = RBE_RIGHT(tmp);
+ if (oright != NULL)
+ RBE_COLOR(oright) = RB_BLACK;
+
+ RBE_COLOR(tmp) = RB_RED;
+ rbe_rotate_left(t, rbt, tmp);
+ tmp = RBE_LEFT(parent);
+ }
+
+ RBE_COLOR(tmp) = RBE_COLOR(parent);
+ RBE_COLOR(parent) = RB_BLACK;
+ if (RBE_LEFT(tmp) != NULL)
+ RBE_COLOR(RBE_LEFT(tmp)) = RB_BLACK;
+
+ rbe_rotate_right(t, rbt, parent);
+ rbe = RBH_ROOT(rbt);
+ break;
+ }
+ }
+ }
+
+ if (rbe != NULL)
+ RBE_COLOR(rbe) = RB_BLACK;
+}
+
+static inline struct rb_entry *
+rbe_remove(const struct rb_type *t, struct rbt_tree *rbt, struct rb_entry *rbe)
+{
+ struct rb_entry *child, *parent, *old = rbe;
+ unsigned int color;
+
+ if (RBE_LEFT(rbe) == NULL)
+ child = RBE_RIGHT(rbe);
+ else if (RBE_RIGHT(rbe) == NULL)
+ child = RBE_LEFT(rbe);
+ else {
+ struct rb_entry *tmp;
+
+ rbe = RBE_RIGHT(rbe);
+ while ((tmp = RBE_LEFT(rbe)) != NULL)
+ rbe = tmp;
+
+ child = RBE_RIGHT(rbe);
+ parent = RBE_PARENT(rbe);
+ color = RBE_COLOR(rbe);
+ if (child != NULL)
+ RBE_PARENT(child) = parent;
+ if (parent != NULL) {
+ if (RBE_LEFT(parent) == rbe)
+ RBE_LEFT(parent) = child;
+ else
+ RBE_RIGHT(parent) = child;
+
+ rbe_if_augment(t, parent);
+ } else
+ RBH_ROOT(rbt) = child;
+ if (RBE_PARENT(rbe) == old)
+ parent = rbe;
+ *rbe = *old;
+
+ tmp = RBE_PARENT(old);
+ if (tmp != NULL) {
+ if (RBE_LEFT(tmp) == old)
+ RBE_LEFT(tmp) = rbe;
+ else
+ RBE_RIGHT(tmp) = rbe;
+
+ rbe_if_augment(t, tmp);
+ } else
+ RBH_ROOT(rbt) = rbe;
+
+ RBE_PARENT(RBE_LEFT(old)) = rbe;
+ if (RBE_RIGHT(old))
+ RBE_PARENT(RBE_RIGHT(old)) = rbe;
+
+ if (t->t_augment != NULL && parent != NULL) {
+ tmp = parent;
+ do {
+ rbe_augment(t, tmp);
+ tmp = RBE_PARENT(tmp);
+ } while (tmp != NULL);
+ }
+
+ goto color;
+ }
+
+ parent = RBE_PARENT(rbe);
+ color = RBE_COLOR(rbe);
+
+ if (child != NULL)
+ RBE_PARENT(child) = parent;
+ if (parent != NULL) {
+ if (RBE_LEFT(parent) == rbe)
+ RBE_LEFT(parent) = child;
+ else
+ RBE_RIGHT(parent) = child;
+
+ rbe_if_augment(t, parent);
+ } else
+ RBH_ROOT(rbt) = child;
+color:
+ if (color == RB_BLACK)
+ rbe_remove_color(t, rbt, parent, child);
+
+ return (old);
+}
+
+void *_rb_remove(const struct rb_type *t, struct rbt_tree *rbt, void *elm)
+{
+ struct rb_entry *rbe = rb_n2e(t, elm);
+ struct rb_entry *old;
+
+ old = rbe_remove(t, rbt, rbe);
+
+ return (old == NULL ? NULL : rb_e2n(t, old));
+}
+
+void *_rb_insert(const struct rb_type *t, struct rbt_tree *rbt, void *elm)
+{
+ struct rb_entry *rbe = rb_n2e(t, elm);
+ struct rb_entry *tmp;
+ struct rb_entry *parent = NULL;
+ void *node;
+ int comp = 0;
+
+ tmp = RBH_ROOT(rbt);
+ while (tmp != NULL) {
+ parent = tmp;
+
+ node = rb_e2n(t, tmp);
+ comp = (*t->t_compare)(elm, node);
+ if (comp < 0)
+ tmp = RBE_LEFT(tmp);
+ else if (comp > 0)
+ tmp = RBE_RIGHT(tmp);
+ else
+ return (node);
+ }
+
+ rbe_set(rbe, parent);
+
+ if (parent != NULL) {
+ if (comp < 0)
+ RBE_LEFT(parent) = rbe;
+ else
+ RBE_RIGHT(parent) = rbe;
+
+ rbe_if_augment(t, parent);
+ } else
+ RBH_ROOT(rbt) = rbe;
+
+ rbe_insert_color(t, rbt, rbe);
+
+ return NULL;
+}
+
+/* Finds the node with the same key as elm */
+void *_rb_find(const struct rb_type *t, const struct rbt_tree *rbt,
+ const void *key)
+{
+ struct rb_entry *tmp = RBH_ROOT(rbt);
+ void *node;
+ int comp;
+
+ while (tmp != NULL) {
+ node = rb_e2n(t, tmp);
+ comp = (*t->t_compare)(key, node);
+ if (comp < 0)
+ tmp = RBE_LEFT(tmp);
+ else if (comp > 0)
+ tmp = RBE_RIGHT(tmp);
+ else
+ return (node);
+ }
+
+ return NULL;
+}
+
+/* Finds the first node greater than or equal to the search key */
+void *_rb_nfind(const struct rb_type *t, const struct rbt_tree *rbt,
+ const void *key)
+{
+ struct rb_entry *tmp = RBH_ROOT(rbt);
+ void *node;
+ void *res = NULL;
+ int comp;
+
+ while (tmp != NULL) {
+ node = rb_e2n(t, tmp);
+ comp = (*t->t_compare)(key, node);
+ if (comp < 0) {
+ res = node;
+ tmp = RBE_LEFT(tmp);
+ } else if (comp > 0)
+ tmp = RBE_RIGHT(tmp);
+ else
+ return (node);
+ }
+
+ return (res);
+}
+
+void *_rb_next(const struct rb_type *t, void *elm)
+{
+ struct rb_entry *rbe = rb_n2e(t, elm);
+
+ if (RBE_RIGHT(rbe) != NULL) {
+ rbe = RBE_RIGHT(rbe);
+ while (RBE_LEFT(rbe) != NULL)
+ rbe = RBE_LEFT(rbe);
+ } else {
+ if (RBE_PARENT(rbe) && (rbe == RBE_LEFT(RBE_PARENT(rbe))))
+ rbe = RBE_PARENT(rbe);
+ else {
+ while (RBE_PARENT(rbe)
+ && (rbe == RBE_RIGHT(RBE_PARENT(rbe))))
+ rbe = RBE_PARENT(rbe);
+ rbe = RBE_PARENT(rbe);
+ }
+ }
+
+ return (rbe == NULL ? NULL : rb_e2n(t, rbe));
+}
+
+void *_rb_prev(const struct rb_type *t, void *elm)
+{
+ struct rb_entry *rbe = rb_n2e(t, elm);
+
+ if (RBE_LEFT(rbe)) {
+ rbe = RBE_LEFT(rbe);
+ while (RBE_RIGHT(rbe))
+ rbe = RBE_RIGHT(rbe);
+ } else {
+ if (RBE_PARENT(rbe) && (rbe == RBE_RIGHT(RBE_PARENT(rbe))))
+ rbe = RBE_PARENT(rbe);
+ else {
+ while (RBE_PARENT(rbe)
+ && (rbe == RBE_LEFT(RBE_PARENT(rbe))))
+ rbe = RBE_PARENT(rbe);
+ rbe = RBE_PARENT(rbe);
+ }
+ }
+
+ return (rbe == NULL ? NULL : rb_e2n(t, rbe));
+}
+
+void *_rb_root(const struct rb_type *t, const struct rbt_tree *rbt)
+{
+ struct rb_entry *rbe = RBH_ROOT(rbt);
+
+ return (rbe == NULL ? rbe : rb_e2n(t, rbe));
+}
+
+void *_rb_min(const struct rb_type *t, const struct rbt_tree *rbt)
+{
+ struct rb_entry *rbe = RBH_ROOT(rbt);
+ struct rb_entry *parent = NULL;
+
+ while (rbe != NULL) {
+ parent = rbe;
+ rbe = RBE_LEFT(rbe);
+ }
+
+ return (parent == NULL ? NULL : rb_e2n(t, parent));
+}
+
+void *_rb_max(const struct rb_type *t, const struct rbt_tree *rbt)
+{
+ struct rb_entry *rbe = RBH_ROOT(rbt);
+ struct rb_entry *parent = NULL;
+
+ while (rbe != NULL) {
+ parent = rbe;
+ rbe = RBE_RIGHT(rbe);
+ }
+
+ return (parent == NULL ? NULL : rb_e2n(t, parent));
+}
+
+void *_rb_left(const struct rb_type *t, void *node)
+{
+ struct rb_entry *rbe = rb_n2e(t, node);
+ rbe = RBE_LEFT(rbe);
+ return (rbe == NULL ? NULL : rb_e2n(t, rbe));
+}
+
+void *_rb_right(const struct rb_type *t, void *node)
+{
+ struct rb_entry *rbe = rb_n2e(t, node);
+ rbe = RBE_RIGHT(rbe);
+ return (rbe == NULL ? NULL : rb_e2n(t, rbe));
+}
+
+void *_rb_parent(const struct rb_type *t, void *node)
+{
+ struct rb_entry *rbe = rb_n2e(t, node);
+ rbe = RBE_PARENT(rbe);
+ return (rbe == NULL ? NULL : rb_e2n(t, rbe));
+}
+
+void _rb_set_left(const struct rb_type *t, void *node, void *left)
+{
+ struct rb_entry *rbe = rb_n2e(t, node);
+ struct rb_entry *rbl = (left == NULL) ? NULL : rb_n2e(t, left);
+
+ RBE_LEFT(rbe) = rbl;
+}
+
+void _rb_set_right(const struct rb_type *t, void *node, void *right)
+{
+ struct rb_entry *rbe = rb_n2e(t, node);
+ struct rb_entry *rbr = (right == NULL) ? NULL : rb_n2e(t, right);
+
+ RBE_RIGHT(rbe) = rbr;
+}
+
+void _rb_set_parent(const struct rb_type *t, void *node, void *parent)
+{
+ struct rb_entry *rbe = rb_n2e(t, node);
+ struct rb_entry *rbp = (parent == NULL) ? NULL : rb_n2e(t, parent);
+
+ RBE_PARENT(rbe) = rbp;
+}
+
+void _rb_poison(const struct rb_type *t, void *node, unsigned long poison)
+{
+ struct rb_entry *rbe = rb_n2e(t, node);
+
+ RBE_PARENT(rbe) = RBE_LEFT(rbe) = RBE_RIGHT(rbe) =
+ (struct rb_entry *)poison;
+}
+
+int _rb_check(const struct rb_type *t, void *node, unsigned long poison)
+{
+ struct rb_entry *rbe = rb_n2e(t, node);
+
+ return ((unsigned long)RBE_PARENT(rbe) == poison
+ && (unsigned long)RBE_LEFT(rbe) == poison
+ && (unsigned long)RBE_RIGHT(rbe) == poison);
+}
diff --git a/lib/openbsd-tree.h b/lib/openbsd-tree.h
new file mode 100644
index 0000000..832a101
--- /dev/null
+++ b/lib/openbsd-tree.h
@@ -0,0 +1,580 @@
+/* $OpenBSD: tree.h,v 1.14 2015/05/25 03:07:49 deraadt Exp $ */
+/*
+ * Copyright 2002 Niels Provos <provos@citi.umich.edu>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _SYS_TREE_H_
+#define _SYS_TREE_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * This file defines data structures for different types of trees:
+ * splay trees and red-black trees.
+ *
+ * A splay tree is a self-organizing data structure. Every operation
+ * on the tree causes a splay to happen. The splay moves the requested
+ * node to the root of the tree and partly rebalances it.
+ *
+ * This has the benefit that request locality causes faster lookups as
+ * the requested nodes move to the top of the tree. On the other hand,
+ * every lookup causes memory writes.
+ *
+ * The Balance Theorem bounds the total access time for m operations
+ * and n inserts on an initially empty tree as O((m + n)lg n). The
+ * amortized cost for a sequence of m accesses to a splay tree is O(lg n);
+ *
+ * A red-black tree is a binary search tree with the node color as an
+ * extra attribute. It fulfills a set of conditions:
+ * - every search path from the root to a leaf consists of the
+ * same number of black nodes,
+ * - each red node (except for the root) has a black parent,
+ * - each leaf node is black.
+ *
+ * Every operation on a red-black tree is bounded as O(lg n).
+ * The maximum height of a red-black tree is 2lg (n+1).
+ */
+
+#define SPLAY_HEAD(name, type) \
+ struct name { \
+ struct type *sph_root; /* root of the tree */ \
+ }
+
+#define SPLAY_INITIALIZER(root) \
+ { \
+ NULL \
+ }
+
+#define SPLAY_INIT(root) \
+ do { \
+ (root)->sph_root = NULL; \
+ } while (0)
+
+#define SPLAY_ENTRY(type) \
+ struct { \
+ struct type *spe_left; /* left element */ \
+ struct type *spe_right; /* right element */ \
+ }
+
+#define SPLAY_LEFT(elm, field) (elm)->field.spe_left
+#define SPLAY_RIGHT(elm, field) (elm)->field.spe_right
+#define SPLAY_ROOT(head) (head)->sph_root
+#define SPLAY_EMPTY(head) (SPLAY_ROOT(head) == NULL)
+
+/* SPLAY_ROTATE_{LEFT,RIGHT} expect that tmp hold SPLAY_{RIGHT,LEFT} */
+#define SPLAY_ROTATE_RIGHT(head, tmp, field) \
+ do { \
+ SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(tmp, field); \
+ SPLAY_RIGHT(tmp, field) = (head)->sph_root; \
+ (head)->sph_root = tmp; \
+ } while (0)
+
+#define SPLAY_ROTATE_LEFT(head, tmp, field) \
+ do { \
+ SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(tmp, field); \
+ SPLAY_LEFT(tmp, field) = (head)->sph_root; \
+ (head)->sph_root = tmp; \
+ } while (0)
+
+#define SPLAY_LINKLEFT(head, tmp, field) \
+ do { \
+ SPLAY_LEFT(tmp, field) = (head)->sph_root; \
+ tmp = (head)->sph_root; \
+ (head)->sph_root = SPLAY_LEFT((head)->sph_root, field); \
+ } while (0)
+
+#define SPLAY_LINKRIGHT(head, tmp, field) \
+ do { \
+ SPLAY_RIGHT(tmp, field) = (head)->sph_root; \
+ tmp = (head)->sph_root; \
+ (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field); \
+ } while (0)
+
+#define SPLAY_ASSEMBLE(head, node, left, right, field) \
+ do { \
+ SPLAY_RIGHT(left, field) = \
+ SPLAY_LEFT((head)->sph_root, field); \
+ SPLAY_LEFT(right, field) = \
+ SPLAY_RIGHT((head)->sph_root, field); \
+ SPLAY_LEFT((head)->sph_root, field) = \
+ SPLAY_RIGHT(node, field); \
+ SPLAY_RIGHT((head)->sph_root, field) = \
+ SPLAY_LEFT(node, field); \
+ } while (0)
+
+/* Generates prototypes and inline functions */
+
+#define SPLAY_PROTOTYPE(name, type, field, cmp) \
+ void name##_SPLAY(struct name *, struct type *); \
+ void name##_SPLAY_MINMAX(struct name *, int); \
+ struct type *name##_SPLAY_INSERT(struct name *, struct type *); \
+ struct type *name##_SPLAY_REMOVE(struct name *, struct type *); \
+ \
+ /* Finds the node with the same key as elm */ \
+ static __inline struct type *name##_SPLAY_FIND(struct name *head, \
+ struct type *elm) \
+ { \
+ if (SPLAY_EMPTY(head)) \
+ return (NULL); \
+ name##_SPLAY(head, elm); \
+ if ((cmp)(elm, (head)->sph_root) == 0) \
+ return (head->sph_root); \
+ return (NULL); \
+ } \
+ \
+ static __inline struct type *name##_SPLAY_NEXT(struct name *head, \
+ struct type *elm) \
+ { \
+ name##_SPLAY(head, elm); \
+ if (SPLAY_RIGHT(elm, field) != NULL) { \
+ elm = SPLAY_RIGHT(elm, field); \
+ while (SPLAY_LEFT(elm, field) != NULL) { \
+ elm = SPLAY_LEFT(elm, field); \
+ } \
+ } else \
+ elm = NULL; \
+ return (elm); \
+ } \
+ \
+ static __inline struct type *name##_SPLAY_MIN_MAX(struct name *head, \
+ int val) \
+ { \
+ name##_SPLAY_MINMAX(head, val); \
+ return (SPLAY_ROOT(head)); \
+ }
+
+/* Main splay operation.
+ * Moves node close to the key of elm to top
+ */
+#define SPLAY_GENERATE(name, type, field, cmp) \
+ struct type *name##_SPLAY_INSERT(struct name *head, struct type *elm) \
+ { \
+ if (SPLAY_EMPTY(head)) { \
+ SPLAY_LEFT(elm, field) = SPLAY_RIGHT(elm, field) = \
+ NULL; \
+ } else { \
+ int __comp; \
+ name##_SPLAY(head, elm); \
+ __comp = (cmp)(elm, (head)->sph_root); \
+ if (__comp < 0) { \
+ SPLAY_LEFT(elm, field) = \
+ SPLAY_LEFT((head)->sph_root, field); \
+ SPLAY_RIGHT(elm, field) = (head)->sph_root; \
+ SPLAY_LEFT((head)->sph_root, field) = NULL; \
+ } else if (__comp > 0) { \
+ SPLAY_RIGHT(elm, field) = \
+ SPLAY_RIGHT((head)->sph_root, field); \
+ SPLAY_LEFT(elm, field) = (head)->sph_root; \
+ SPLAY_RIGHT((head)->sph_root, field) = NULL; \
+ } else \
+ return ((head)->sph_root); \
+ } \
+ (head)->sph_root = (elm); \
+ return (NULL); \
+ } \
+ \
+ struct type *name##_SPLAY_REMOVE(struct name *head, struct type *elm) \
+ { \
+ struct type *__tmp; \
+ if (SPLAY_EMPTY(head)) \
+ return (NULL); \
+ name##_SPLAY(head, elm); \
+ if ((cmp)(elm, (head)->sph_root) == 0) { \
+ if (SPLAY_LEFT((head)->sph_root, field) == NULL) { \
+ (head)->sph_root = \
+ SPLAY_RIGHT((head)->sph_root, field); \
+ } else { \
+ __tmp = SPLAY_RIGHT((head)->sph_root, field); \
+ (head)->sph_root = \
+ SPLAY_LEFT((head)->sph_root, field); \
+ name##_SPLAY(head, elm); \
+ SPLAY_RIGHT((head)->sph_root, field) = __tmp; \
+ } \
+ return (elm); \
+ } \
+ return (NULL); \
+ } \
+ \
+ void name##_SPLAY(struct name *head, struct type *elm) \
+ { \
+ struct type __node, *__left, *__right, *__tmp; \
+ int __comp; \
+ \
+ SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = \
+ NULL; \
+ __left = __right = &__node; \
+ \
+ while ((__comp = (cmp)(elm, (head)->sph_root))) { \
+ if (__comp < 0) { \
+ __tmp = SPLAY_LEFT((head)->sph_root, field); \
+ if (__tmp == NULL) \
+ break; \
+ if ((cmp)(elm, __tmp) < 0) { \
+ SPLAY_ROTATE_RIGHT(head, __tmp, \
+ field); \
+ if (SPLAY_LEFT((head)->sph_root, \
+ field) \
+ == NULL) \
+ break; \
+ } \
+ SPLAY_LINKLEFT(head, __right, field); \
+ } else if (__comp > 0) { \
+ __tmp = SPLAY_RIGHT((head)->sph_root, field); \
+ if (__tmp == NULL) \
+ break; \
+ if ((cmp)(elm, __tmp) > 0) { \
+ SPLAY_ROTATE_LEFT(head, __tmp, field); \
+ if (SPLAY_RIGHT((head)->sph_root, \
+ field) \
+ == NULL) \
+ break; \
+ } \
+ SPLAY_LINKRIGHT(head, __left, field); \
+ } \
+ } \
+ SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \
+ } \
+ \
+ /* Splay with either the minimum or the maximum element \
+ * Used to find minimum or maximum element in tree. \
+ */ \
+ void name##_SPLAY_MINMAX(struct name *head, int __comp) \
+ { \
+ struct type __node, *__left, *__right, *__tmp; \
+ \
+ SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = \
+ NULL; \
+ __left = __right = &__node; \
+ \
+ while (1) { \
+ if (__comp < 0) { \
+ __tmp = SPLAY_LEFT((head)->sph_root, field); \
+ if (__tmp == NULL) \
+ break; \
+ if (__comp < 0) { \
+ SPLAY_ROTATE_RIGHT(head, __tmp, \
+ field); \
+ if (SPLAY_LEFT((head)->sph_root, \
+ field) \
+ == NULL) \
+ break; \
+ } \
+ SPLAY_LINKLEFT(head, __right, field); \
+ } else if (__comp > 0) { \
+ __tmp = SPLAY_RIGHT((head)->sph_root, field); \
+ if (__tmp == NULL) \
+ break; \
+ if (__comp > 0) { \
+ SPLAY_ROTATE_LEFT(head, __tmp, field); \
+ if (SPLAY_RIGHT((head)->sph_root, \
+ field) \
+ == NULL) \
+ break; \
+ } \
+ SPLAY_LINKRIGHT(head, __left, field); \
+ } \
+ } \
+ SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \
+ }
+
+#define SPLAY_NEGINF -1
+#define SPLAY_INF 1
+
+#define SPLAY_INSERT(name, x, y) name##_SPLAY_INSERT(x, y)
+#define SPLAY_REMOVE(name, x, y) name##_SPLAY_REMOVE(x, y)
+#define SPLAY_FIND(name, x, y) name##_SPLAY_FIND(x, y)
+#define SPLAY_NEXT(name, x, y) name##_SPLAY_NEXT(x, y)
+#define SPLAY_MIN(name, x) \
+ (SPLAY_EMPTY(x) ? NULL : name##_SPLAY_MIN_MAX(x, SPLAY_NEGINF))
+#define SPLAY_MAX(name, x) \
+ (SPLAY_EMPTY(x) ? NULL : name##_SPLAY_MIN_MAX(x, SPLAY_INF))
+
+#define SPLAY_FOREACH(x, name, head) \
+ for ((x) = SPLAY_MIN(name, head); (x) != NULL; \
+ (x) = SPLAY_NEXT(name, head, x))
+
+/*
+ * Copyright (c) 2016 David Gwynne <dlg@openbsd.org>
+ *
+ * Permission to use, copy, modify, and 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.
+ */
+
+#define RB_BLACK 0
+#define RB_RED 1
+
+struct rb_type {
+ int (*t_compare)(const void *, const void *);
+ void (*t_augment)(void *);
+ unsigned int t_offset; /* offset of rb_entry in type */
+};
+
+struct rbt_tree {
+ struct rb_entry *rbt_root;
+};
+
+struct rb_entry {
+ struct rb_entry *rbt_parent;
+ struct rb_entry *rbt_left;
+ struct rb_entry *rbt_right;
+ unsigned int rbt_color;
+};
+
+#define RB_HEAD(_name, _type) \
+ struct _name { \
+ struct rbt_tree rbh_root; \
+ }
+
+#define RB_ENTRY(_type) struct rb_entry
+
+static inline void _rb_init(struct rbt_tree *rbt)
+{
+ rbt->rbt_root = NULL;
+}
+
+static inline int _rb_empty(const struct rbt_tree *rbt)
+{
+ return (rbt->rbt_root == NULL);
+}
+
+void *_rb_insert(const struct rb_type *, struct rbt_tree *, void *);
+void *_rb_remove(const struct rb_type *, struct rbt_tree *, void *);
+void *_rb_find(const struct rb_type *, const struct rbt_tree *, const void *);
+void *_rb_nfind(const struct rb_type *, const struct rbt_tree *, const void *);
+void *_rb_root(const struct rb_type *, const struct rbt_tree *);
+void *_rb_min(const struct rb_type *, const struct rbt_tree *);
+void *_rb_max(const struct rb_type *, const struct rbt_tree *);
+void *_rb_next(const struct rb_type *, void *);
+void *_rb_prev(const struct rb_type *, void *);
+void *_rb_left(const struct rb_type *, void *);
+void *_rb_right(const struct rb_type *, void *);
+void *_rb_parent(const struct rb_type *, void *);
+void _rb_set_left(const struct rb_type *, void *, void *);
+void _rb_set_right(const struct rb_type *, void *, void *);
+void _rb_set_parent(const struct rb_type *, void *, void *);
+void _rb_poison(const struct rb_type *, void *, unsigned long);
+int _rb_check(const struct rb_type *, void *, unsigned long);
+
+#define RB_INITIALIZER(_head) { { NULL } }
+
+#define RB_PROTOTYPE(_name, _type, _field, _cmp) \
+ extern const struct rb_type *const _name##_RB_TYPE; \
+ \
+ __attribute__((__unused__)) static inline void _name##_RB_INIT( \
+ struct _name *head) \
+ { \
+ _rb_init(&head->rbh_root); \
+ } \
+ \
+ __attribute__((__unused__)) static inline struct _type \
+ *_name##_RB_INSERT(struct _name *head, struct _type *elm) \
+ { \
+ return (struct _type *)_rb_insert(_name##_RB_TYPE, \
+ &head->rbh_root, elm); \
+ } \
+ \
+ __attribute__((__unused__)) static inline struct _type \
+ *_name##_RB_REMOVE(struct _name *head, struct _type *elm) \
+ { \
+ return (struct _type *)_rb_remove(_name##_RB_TYPE, \
+ &head->rbh_root, elm); \
+ } \
+ \
+ __attribute__((__unused__)) static inline struct _type \
+ *_name##_RB_FIND(const struct _name *head, \
+ const struct _type *key) \
+ { \
+ return (struct _type *)_rb_find(_name##_RB_TYPE, \
+ &head->rbh_root, key); \
+ } \
+ \
+ __attribute__((__unused__)) static inline struct _type \
+ *_name##_RB_NFIND(const struct _name *head, \
+ const struct _type *key) \
+ { \
+ return (struct _type *)_rb_nfind(_name##_RB_TYPE, \
+ &head->rbh_root, key); \
+ } \
+ \
+ __attribute__((__unused__)) static inline struct _type \
+ *_name##_RB_ROOT(const struct _name *head) \
+ { \
+ return (struct _type *)_rb_root(_name##_RB_TYPE, \
+ &head->rbh_root); \
+ } \
+ \
+ __attribute__((__unused__)) static inline int _name##_RB_EMPTY( \
+ const struct _name *head) \
+ { \
+ return _rb_empty(&head->rbh_root); \
+ } \
+ \
+ __attribute__((__unused__)) static inline struct _type \
+ *_name##_RB_MIN(const struct _name *head) \
+ { \
+ return (struct _type *)_rb_min(_name##_RB_TYPE, \
+ &head->rbh_root); \
+ } \
+ \
+ __attribute__((__unused__)) static inline struct _type \
+ *_name##_RB_MAX(const struct _name *head) \
+ { \
+ return (struct _type *)_rb_max(_name##_RB_TYPE, \
+ &head->rbh_root); \
+ } \
+ \
+ __attribute__((__unused__)) static inline struct _type \
+ *_name##_RB_NEXT(struct _type *elm) \
+ { \
+ return (struct _type *)_rb_next(_name##_RB_TYPE, elm); \
+ } \
+ \
+ __attribute__((__unused__)) static inline struct _type \
+ *_name##_RB_PREV(struct _type *elm) \
+ { \
+ return (struct _type *)_rb_prev(_name##_RB_TYPE, elm); \
+ } \
+ \
+ __attribute__((__unused__)) static inline struct _type \
+ *_name##_RB_LEFT(struct _type *elm) \
+ { \
+ return (struct _type *)_rb_left(_name##_RB_TYPE, elm); \
+ } \
+ \
+ __attribute__((__unused__)) static inline struct _type \
+ *_name##_RB_RIGHT(struct _type *elm) \
+ { \
+ return (struct _type *)_rb_right(_name##_RB_TYPE, elm); \
+ } \
+ \
+ __attribute__((__unused__)) static inline struct _type \
+ *_name##_RB_PARENT(struct _type *elm) \
+ { \
+ return (struct _type *)_rb_parent(_name##_RB_TYPE, elm); \
+ } \
+ \
+ __attribute__((__unused__)) static inline void _name##_RB_SET_LEFT( \
+ struct _type *elm, struct _type *left) \
+ { \
+ _rb_set_left(_name##_RB_TYPE, elm, left); \
+ } \
+ \
+ __attribute__((__unused__)) static inline void _name##_RB_SET_RIGHT( \
+ struct _type *elm, struct _type *right) \
+ { \
+ _rb_set_right(_name##_RB_TYPE, elm, right); \
+ } \
+ \
+ __attribute__((__unused__)) static inline void _name##_RB_SET_PARENT( \
+ struct _type *elm, struct _type *parent) \
+ { \
+ _rb_set_parent(_name##_RB_TYPE, elm, parent); \
+ } \
+ \
+ __attribute__((__unused__)) static inline void _name##_RB_POISON( \
+ struct _type *elm, unsigned long poison) \
+ { \
+ _rb_poison(_name##_RB_TYPE, elm, poison); \
+ } \
+ \
+ __attribute__((__unused__)) static inline int _name##_RB_CHECK( \
+ struct _type *elm, unsigned long poison) \
+ { \
+ return _rb_check(_name##_RB_TYPE, elm, poison); \
+ }
+
+#define RB_GENERATE_INTERNAL(_name, _type, _field, _cmp, _aug) \
+ static int _name##_RB_COMPARE(const void *lptr, const void *rptr) \
+ { \
+ const struct _type *l = lptr, *r = rptr; \
+ return _cmp(l, r); \
+ } \
+ static const struct rb_type _name##_RB_INFO = { \
+ _name##_RB_COMPARE, _aug, offsetof(struct _type, _field), \
+ }; \
+ const struct rb_type *const _name##_RB_TYPE = &_name##_RB_INFO;
+
+#define RB_GENERATE_AUGMENT(_name, _type, _field, _cmp, _aug) \
+ static void _name##_RB_AUGMENT(void *ptr) \
+ { \
+ struct _type *p = ptr; \
+ return _aug(p); \
+ } \
+ RB_GENERATE_INTERNAL(_name, _type, _field, _cmp, _name##_RB_AUGMENT)
+
+#define RB_GENERATE(_name, _type, _field, _cmp) \
+ RB_GENERATE_INTERNAL(_name, _type, _field, _cmp, NULL)
+
+#define RB_INIT(_name, _head) _name##_RB_INIT(_head)
+#define RB_INSERT(_name, _head, _elm) _name##_RB_INSERT(_head, _elm)
+#define RB_REMOVE(_name, _head, _elm) _name##_RB_REMOVE(_head, _elm)
+#define RB_FIND(_name, _head, _key) _name##_RB_FIND(_head, _key)
+#define RB_NFIND(_name, _head, _key) _name##_RB_NFIND(_head, _key)
+#define RB_ROOT(_name, _head) _name##_RB_ROOT(_head)
+#define RB_EMPTY(_name, _head) _name##_RB_EMPTY(_head)
+#define RB_MIN(_name, _head) _name##_RB_MIN(_head)
+#define RB_MAX(_name, _head) _name##_RB_MAX(_head)
+#define RB_NEXT(_name, _elm) _name##_RB_NEXT(_elm)
+#define RB_PREV(_name, _elm) _name##_RB_PREV(_elm)
+#define RB_LEFT(_name, _elm) _name##_RB_LEFT(_elm)
+#define RB_RIGHT(_name, _elm) _name##_RB_RIGHT(_elm)
+#define RB_PARENT(_name, _elm) _name##_RB_PARENT(_elm)
+#define RB_SET_LEFT(_name, _elm, _l) _name##_RB_SET_LEFT(_elm, _l)
+#define RB_SET_RIGHT(_name, _elm, _r) _name##_RB_SET_RIGHT(_elm, _r)
+#define RB_SET_PARENT(_name, _elm, _p) _name##_RB_SET_PARENT(_elm, _p)
+#define RB_POISON(_name, _elm, _p) _name##_RB_POISON(_elm, _p)
+#define RB_CHECK(_name, _elm, _p) _name##_RB_CHECK(_elm, _p)
+
+#define RB_FOREACH(_e, _name, _head) \
+ for ((_e) = RB_MIN(_name, (_head)); (_e) != NULL; \
+ (_e) = RB_NEXT(_name, (_e)))
+
+#define RB_FOREACH_SAFE(_e, _name, _head, _n) \
+ for ((_e) = RB_MIN(_name, (_head)); \
+ (_e) != NULL && ((_n) = RB_NEXT(_name, (_e)), 1); (_e) = (_n))
+
+#define RB_FOREACH_REVERSE(_e, _name, _head) \
+ for ((_e) = RB_MAX(_name, (_head)); (_e) != NULL; \
+ (_e) = RB_PREV(_name, (_e)))
+
+#define RB_FOREACH_REVERSE_SAFE(_e, _name, _head, _n) \
+ for ((_e) = RB_MAX(_name, (_head)); \
+ (_e) != NULL && ((_n) = RB_PREV(_name, (_e)), 1); (_e) = (_n))
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SYS_TREE_H_ */
diff --git a/lib/pbr.h b/lib/pbr.h
new file mode 100644
index 0000000..b14ba07
--- /dev/null
+++ b/lib/pbr.h
@@ -0,0 +1,154 @@
+/* Policy Based Routing (PBR) main header
+ * Copyright (C) 2018 6WIND
+ *
+ * FRR 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, or (at your option) any
+ * later version.
+ *
+ * FRR 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 FRR; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef _PBR_H
+#define _PBR_H
+
+#include <zebra.h>
+#include "stream.h"
+#include "prefix.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define PBR_STR "Policy Based Routing\n"
+
+/*
+ * A PBR filter
+ *
+ * The filter or match criteria in a PBR rule.
+ * For simplicity, all supported filters are grouped into a structure rather
+ * than delineating further. A bitmask denotes which filters are actually
+ * specified.
+ */
+struct pbr_filter {
+ uint32_t filter_bm; /* not encoded by zapi
+ */
+#define PBR_FILTER_SRC_IP (1 << 0)
+#define PBR_FILTER_DST_IP (1 << 1)
+#define PBR_FILTER_SRC_PORT (1 << 2)
+#define PBR_FILTER_DST_PORT (1 << 3)
+#define PBR_FILTER_FWMARK (1 << 4)
+#define PBR_FILTER_PROTO (1 << 5)
+#define PBR_FILTER_SRC_PORT_RANGE (1 << 6)
+#define PBR_FILTER_DST_PORT_RANGE (1 << 7)
+#define PBR_FILTER_DSFIELD (1 << 8)
+#define PBR_FILTER_IP_PROTOCOL (1 << 9)
+
+#define PBR_DSFIELD_DSCP (0xfc) /* Upper 6 bits of DS field: DSCP */
+#define PBR_DSFIELD_ECN (0x03) /* Lower 2 bits of DS field: BCN */
+
+ /* Source and Destination IP address with masks. */
+ struct prefix src_ip;
+ struct prefix dst_ip;
+
+ /* Source and Destination higher-layer (TCP/UDP) port numbers. */
+ uint16_t src_port;
+ uint16_t dst_port;
+
+ /* Filter by Differentiated Services field */
+ uint8_t dsfield; /* DSCP (6 bits) & ECN (2 bits) */
+
+ /* Filter with fwmark */
+ uint32_t fwmark;
+
+ /* Filter with the ip protocol */
+ uint8_t ip_proto;
+};
+
+/*
+ * A PBR action
+ *
+ * The action corresponding to a PBR rule.
+ * While the user specifies the action in a particular way, the forwarding
+ * plane implementation (Linux only) requires that to be encoded into a
+ * route table and the rule then point to that route table; in some cases,
+ * the user criteria may directly point to a table too.
+ */
+struct pbr_action {
+ /* VLAN */
+ uint8_t pcp;
+ uint16_t vlan_id;
+ uint16_t vlan_flags;
+
+ uint32_t queue_id;
+
+ uint32_t table;
+};
+
+/*
+ * A PBR rule
+ *
+ * This is a combination of the filter criteria and corresponding action.
+ * Rules also have a user-defined sequence number which defines the relative
+ * order amongst rules.
+ */
+struct pbr_rule {
+ vrf_id_t vrf_id;
+
+ uint32_t seq;
+ uint32_t priority;
+ uint32_t unique;
+ struct pbr_filter filter;
+ struct pbr_action action;
+
+ char ifname[INTERFACE_NAMSIZ + 1];
+};
+
+/* TCP flags value shared
+ * those are values of byte 13 of TCP header
+ * as mentioned in rfc793
+ */
+#define TCP_HEADER_FIN (0x01)
+#define TCP_HEADER_SYN (0x02)
+#define TCP_HEADER_RST (0x04)
+#define TCP_HEADER_PSH (0x08)
+#define TCP_HEADER_ACK (0x10)
+#define TCP_HEADER_URG (0x20)
+#define TCP_HEADER_ALL_FLAGS (TCP_HEADER_FIN | TCP_HEADER_SYN \
+ | TCP_HEADER_RST | TCP_HEADER_PSH \
+ | TCP_HEADER_ACK | TCP_HEADER_URG)
+
+/* Pbr IPTable defines
+ * those are common flags shared between BGP and Zebra
+ */
+#define MATCH_IP_SRC_SET (1 << 0)
+#define MATCH_IP_DST_SET (1 << 1)
+#define MATCH_PORT_SRC_SET (1 << 2)
+#define MATCH_PORT_DST_SET (1 << 3)
+#define MATCH_PORT_SRC_RANGE_SET (1 << 4)
+#define MATCH_PORT_DST_RANGE_SET (1 << 5)
+#define MATCH_DSCP_SET (1 << 6)
+#define MATCH_DSCP_INVERSE_SET (1 << 7)
+#define MATCH_PKT_LEN_INVERSE_SET (1 << 8)
+#define MATCH_FRAGMENT_INVERSE_SET (1 << 9)
+#define MATCH_ICMP_SET (1 << 10)
+#define MATCH_PROTOCOL_SET (1 << 11)
+#define MATCH_FLOW_LABEL_SET (1 << 12)
+#define MATCH_FLOW_LABEL_INVERSE_SET (1 << 13)
+
+extern int zapi_pbr_rule_encode(uint8_t cmd, struct stream *s,
+ struct pbr_rule *zrule);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _PBR_H */
diff --git a/lib/pid_output.c b/lib/pid_output.c
new file mode 100644
index 0000000..b82dde8
--- /dev/null
+++ b/lib/pid_output.c
@@ -0,0 +1,83 @@
+/*
+ * Process id output.
+ * Copyright (C) 1998, 1999 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 <fcntl.h>
+#include <log.h>
+#include "lib/version.h"
+#include "network.h"
+#include "lib_errors.h"
+
+#define PIDFILE_MASK 0644
+
+pid_t pid_output(const char *path)
+{
+ int tmp;
+ int fd;
+ pid_t pid;
+ char buf[16];
+ struct flock lock;
+ mode_t oldumask;
+
+ pid = getpid();
+
+ oldumask = umask(0777 & ~PIDFILE_MASK);
+ fd = open(path, O_RDWR | O_CREAT, PIDFILE_MASK);
+ if (fd < 0) {
+ flog_err_sys(EC_LIB_SYSTEM_CALL,
+ "Can't create pid lock file %s (%s), exiting",
+ path, safe_strerror(errno));
+ umask(oldumask);
+ exit(1);
+ } else {
+ size_t pidsize;
+
+ umask(oldumask);
+ memset(&lock, 0, sizeof(lock));
+
+ set_cloexec(fd);
+
+ lock.l_type = F_WRLCK;
+ lock.l_whence = SEEK_SET;
+
+ if (fcntl(fd, F_SETLK, &lock) < 0) {
+ flog_err_sys(EC_LIB_SYSTEM_CALL,
+ "Could not lock pid_file %s (%s), exiting. Please ensure that the daemon is not already running",
+ path, safe_strerror(errno));
+ exit(1);
+ }
+
+ snprintf(buf, sizeof(buf), "%d\n", (int)pid);
+ pidsize = strlen(buf);
+ if ((tmp = write(fd, buf, pidsize)) != (int)pidsize)
+ flog_err_sys(
+ EC_LIB_SYSTEM_CALL,
+ "Could not write pid %d to pid_file %s, rc was %d: %s",
+ (int)pid, path, tmp, safe_strerror(errno));
+ else if (ftruncate(fd, pidsize) < 0)
+ flog_err_sys(
+ EC_LIB_SYSTEM_CALL,
+ "Could not truncate pid_file %s to %u bytes: %s",
+ path, (unsigned int)pidsize,
+ safe_strerror(errno));
+ }
+ return pid;
+}
diff --git a/lib/plist.c b/lib/plist.c
new file mode 100644
index 0000000..ff2a59b
--- /dev/null
+++ b/lib/plist.c
@@ -0,0 +1,1665 @@
+/* Prefix list functions.
+ * Copyright (C) 1999 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra 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 "command.h"
+#include "memory.h"
+#include "plist.h"
+#include "sockunion.h"
+#include "buffer.h"
+#include "log.h"
+#include "routemap.h"
+#include "lib/json.h"
+#include "libfrr.h"
+
+#include <typesafe.h>
+#include "plist_int.h"
+
+DEFINE_MTYPE_STATIC(LIB, PREFIX_LIST, "Prefix List");
+DEFINE_MTYPE_STATIC(LIB, MPREFIX_LIST_STR, "Prefix List Str");
+DEFINE_MTYPE_STATIC(LIB, PREFIX_LIST_ENTRY, "Prefix List Entry");
+DEFINE_MTYPE_STATIC(LIB, PREFIX_LIST_TRIE, "Prefix List Trie Table");
+
+/* not currently changeable, code assumes bytes further down */
+#define PLC_BITS 8
+#define PLC_LEN (1 << PLC_BITS)
+#define PLC_MAXLEVELV4 2 /* /24 for IPv4 */
+#define PLC_MAXLEVELV6 4 /* /48 for IPv6 */
+#define PLC_MAXLEVEL 4 /* max(v4,v6) */
+
+struct pltrie_entry {
+ union {
+ struct pltrie_table *next_table;
+ struct prefix_list_entry *final_chain;
+ };
+
+ struct prefix_list_entry *up_chain;
+};
+
+struct pltrie_table {
+ struct pltrie_entry entries[PLC_LEN];
+};
+
+/* Master structure of prefix_list. */
+struct prefix_master {
+ /* The latest update. */
+ struct prefix_list *recent;
+
+ /* Hook function which is executed when new prefix_list is added. */
+ void (*add_hook)(struct prefix_list *);
+
+ /* Hook function which is executed when prefix_list is deleted. */
+ void (*delete_hook)(struct prefix_list *);
+
+ /* number of bytes that have a trie level */
+ size_t trie_depth;
+
+ struct plist_head str;
+};
+static int prefix_list_compare_func(const struct prefix_list *a,
+ const struct prefix_list *b);
+DECLARE_RBTREE_UNIQ(plist, struct prefix_list, plist_item,
+ prefix_list_compare_func);
+
+/* Static structure of IPv4 prefix_list's master. */
+static struct prefix_master prefix_master_ipv4 = {
+ NULL, NULL, NULL, PLC_MAXLEVELV4,
+};
+
+/* Static structure of IPv6 prefix-list's master. */
+static struct prefix_master prefix_master_ipv6 = {
+ NULL, NULL, NULL, PLC_MAXLEVELV6,
+};
+
+/* Static structure of BGP ORF prefix_list's master. */
+static struct prefix_master prefix_master_orf_v4 = {
+ NULL, NULL, NULL, PLC_MAXLEVELV4,
+};
+
+/* Static structure of BGP ORF prefix_list's master. */
+static struct prefix_master prefix_master_orf_v6 = {
+ NULL, NULL, NULL, PLC_MAXLEVELV6,
+};
+
+static struct prefix_master *prefix_master_get(afi_t afi, int orf)
+{
+ if (afi == AFI_IP)
+ return orf ? &prefix_master_orf_v4 : &prefix_master_ipv4;
+ if (afi == AFI_IP6)
+ return orf ? &prefix_master_orf_v6 : &prefix_master_ipv6;
+ return NULL;
+}
+
+const char *prefix_list_name(struct prefix_list *plist)
+{
+ return plist->name;
+}
+
+afi_t prefix_list_afi(struct prefix_list *plist)
+{
+ if (plist->master == &prefix_master_ipv4
+ || plist->master == &prefix_master_orf_v4)
+ return AFI_IP;
+ return AFI_IP6;
+}
+
+static int prefix_list_compare_func(const struct prefix_list *a,
+ const struct prefix_list *b)
+{
+ return strcmp(a->name, b->name);
+}
+
+/* Lookup prefix_list from list of prefix_list by name. */
+static struct prefix_list *prefix_list_lookup_do(afi_t afi, int orf,
+ const char *name)
+{
+ struct prefix_list *plist, lookup;
+ struct prefix_master *master;
+
+ if (name == NULL)
+ return NULL;
+
+ master = prefix_master_get(afi, orf);
+ if (master == NULL)
+ return NULL;
+
+ lookup.name = XSTRDUP(MTYPE_TMP, name);
+ plist = plist_find(&master->str, &lookup);
+ XFREE(MTYPE_TMP, lookup.name);
+ return plist;
+}
+
+struct prefix_list *prefix_list_lookup(afi_t afi, const char *name)
+{
+ return prefix_list_lookup_do(afi, 0, name);
+}
+
+struct prefix_list *prefix_bgp_orf_lookup(afi_t afi, const char *name)
+{
+ return prefix_list_lookup_do(afi, 1, name);
+}
+
+static struct prefix_list *prefix_list_new(void)
+{
+ struct prefix_list *new;
+
+ new = XCALLOC(MTYPE_PREFIX_LIST, sizeof(struct prefix_list));
+ return new;
+}
+
+static void prefix_list_free(struct prefix_list *plist)
+{
+ XFREE(MTYPE_PREFIX_LIST, plist);
+}
+
+struct prefix_list_entry *prefix_list_entry_new(void)
+{
+ struct prefix_list_entry *new;
+
+ new = XCALLOC(MTYPE_PREFIX_LIST_ENTRY,
+ sizeof(struct prefix_list_entry));
+ return new;
+}
+
+void prefix_list_entry_free(struct prefix_list_entry *pentry)
+{
+ XFREE(MTYPE_PREFIX_LIST_ENTRY, pentry);
+}
+
+/* Insert new prefix list to list of prefix_list. Each prefix_list
+ is sorted by the name. */
+static struct prefix_list *prefix_list_insert(afi_t afi, int orf,
+ const char *name)
+{
+ struct prefix_list *plist;
+ struct prefix_master *master;
+
+ master = prefix_master_get(afi, orf);
+ if (master == NULL)
+ return NULL;
+
+ /* Allocate new prefix_list and copy given name. */
+ plist = prefix_list_new();
+ plist->name = XSTRDUP(MTYPE_MPREFIX_LIST_STR, name);
+ plist->master = master;
+ plist->trie =
+ XCALLOC(MTYPE_PREFIX_LIST_TRIE, sizeof(struct pltrie_table));
+
+ plist_add(&master->str, plist);
+
+ return plist;
+}
+
+struct prefix_list *prefix_list_get(afi_t afi, int orf, const char *name)
+{
+ struct prefix_list *plist;
+
+ plist = prefix_list_lookup_do(afi, orf, name);
+
+ if (plist == NULL)
+ plist = prefix_list_insert(afi, orf, name);
+ return plist;
+}
+
+static void prefix_list_trie_del(struct prefix_list *plist,
+ struct prefix_list_entry *pentry);
+
+/* Delete prefix-list from prefix_list_master and free it. */
+void prefix_list_delete(struct prefix_list *plist)
+{
+ struct prefix_master *master;
+ struct prefix_list_entry *pentry;
+ struct prefix_list_entry *next;
+
+ /* If prefix-list contain prefix_list_entry free all of it. */
+ for (pentry = plist->head; pentry; pentry = next) {
+ route_map_notify_pentry_dependencies(plist->name, pentry,
+ RMAP_EVENT_PLIST_DELETED);
+ next = pentry->next;
+ prefix_list_trie_del(plist, pentry);
+ prefix_list_entry_free(pentry);
+ plist->count--;
+ }
+
+ master = plist->master;
+
+ plist_del(&master->str, plist);
+
+ XFREE(MTYPE_TMP, plist->desc);
+
+ /* Make sure master's recent changed prefix-list information is
+ cleared. */
+ master->recent = NULL;
+
+ route_map_notify_dependencies(plist->name, RMAP_EVENT_PLIST_DELETED);
+
+ if (master->delete_hook)
+ (*master->delete_hook)(plist);
+
+ XFREE(MTYPE_MPREFIX_LIST_STR, plist->name);
+
+ XFREE(MTYPE_PREFIX_LIST_TRIE, plist->trie);
+
+ prefix_list_free(plist);
+}
+
+static struct prefix_list_entry *
+prefix_list_entry_make(struct prefix *prefix, enum prefix_list_type type,
+ int64_t seq, int le, int ge, bool any)
+{
+ struct prefix_list_entry *pentry;
+
+ pentry = prefix_list_entry_new();
+
+ if (any)
+ pentry->any = true;
+
+ prefix_copy(&pentry->prefix, prefix);
+ pentry->type = type;
+ pentry->seq = seq;
+ pentry->le = le;
+ pentry->ge = ge;
+
+ return pentry;
+}
+
+/* Add hook function. */
+void prefix_list_add_hook(void (*func)(struct prefix_list *plist))
+{
+ prefix_master_ipv4.add_hook = func;
+ prefix_master_ipv6.add_hook = func;
+}
+
+/* Delete hook function. */
+void prefix_list_delete_hook(void (*func)(struct prefix_list *plist))
+{
+ prefix_master_ipv4.delete_hook = func;
+ prefix_master_ipv6.delete_hook = func;
+}
+
+/* Calculate new sequential number. */
+int64_t prefix_new_seq_get(struct prefix_list *plist)
+{
+ int64_t maxseq;
+ int64_t newseq;
+ struct prefix_list_entry *pentry;
+
+ maxseq = 0;
+
+ for (pentry = plist->head; pentry; pentry = pentry->next) {
+ if (maxseq < pentry->seq)
+ maxseq = pentry->seq;
+ }
+
+ newseq = ((maxseq / 5) * 5) + 5;
+
+ return (newseq > UINT_MAX) ? UINT_MAX : newseq;
+}
+
+/* Return prefix list entry which has same seq number. */
+static struct prefix_list_entry *prefix_seq_check(struct prefix_list *plist,
+ int64_t seq)
+{
+ struct prefix_list_entry *pentry;
+
+ for (pentry = plist->head; pentry; pentry = pentry->next)
+ if (pentry->seq == seq)
+ return pentry;
+ return NULL;
+}
+
+struct prefix_list_entry *
+prefix_list_entry_lookup(struct prefix_list *plist, struct prefix *prefix,
+ enum prefix_list_type type, int64_t seq,
+ int le, int ge)
+{
+ struct prefix_list_entry *pentry;
+
+ for (pentry = plist->head; pentry; pentry = pentry->next)
+ if (prefix_same(&pentry->prefix, prefix)
+ && pentry->type == type) {
+ if (seq >= 0 && pentry->seq != seq)
+ continue;
+
+ if (pentry->le != le)
+ continue;
+ if (pentry->ge != ge)
+ continue;
+
+ return pentry;
+ }
+
+ return NULL;
+}
+
+static void trie_walk_affected(size_t validbits, struct pltrie_table *table,
+ uint8_t byte, struct prefix_list_entry *object,
+ void (*fn)(struct prefix_list_entry *object,
+ struct prefix_list_entry **updptr))
+{
+ uint8_t mask;
+ uint16_t bwalk;
+
+ if (validbits > PLC_BITS) {
+ fn(object, &table->entries[byte].final_chain);
+ return;
+ }
+
+ mask = (1 << (8 - validbits)) - 1;
+ for (bwalk = byte & ~mask; bwalk <= byte + mask; bwalk++) {
+ fn(object, &table->entries[bwalk].up_chain);
+ }
+}
+
+static void trie_uninstall_fn(struct prefix_list_entry *object,
+ struct prefix_list_entry **updptr)
+{
+ for (; *updptr; updptr = &(*updptr)->next_best)
+ if (*updptr == object) {
+ *updptr = object->next_best;
+ break;
+ }
+}
+
+static int trie_table_empty(struct pltrie_table *table)
+{
+ size_t i;
+ for (i = 0; i < PLC_LEN; i++)
+ if (table->entries[i].next_table || table->entries[i].up_chain)
+ return 0;
+ return 1;
+}
+
+static void prefix_list_trie_del(struct prefix_list *plist,
+ struct prefix_list_entry *pentry)
+{
+ size_t depth, maxdepth = plist->master->trie_depth;
+ uint8_t *bytes = pentry->prefix.u.val;
+ size_t validbits = pentry->prefix.prefixlen;
+ struct pltrie_table *table, **tables[PLC_MAXLEVEL];
+
+ table = plist->trie;
+ for (depth = 0; validbits > PLC_BITS && depth < maxdepth - 1; depth++) {
+ uint8_t byte = bytes[depth];
+ assert(table->entries[byte].next_table);
+
+ tables[depth + 1] = &table->entries[byte].next_table;
+ table = table->entries[byte].next_table;
+
+ validbits -= PLC_BITS;
+ }
+
+ trie_walk_affected(validbits, table, bytes[depth], pentry,
+ trie_uninstall_fn);
+
+ for (; depth > 0; depth--)
+ if (trie_table_empty(*tables[depth])) {
+ XFREE(MTYPE_PREFIX_LIST_TRIE, *tables[depth]);
+ }
+}
+
+
+void prefix_list_entry_delete(struct prefix_list *plist,
+ struct prefix_list_entry *pentry,
+ int update_list)
+{
+ if (plist == NULL || pentry == NULL)
+ return;
+
+ prefix_list_trie_del(plist, pentry);
+
+ if (pentry->prev)
+ pentry->prev->next = pentry->next;
+ else
+ plist->head = pentry->next;
+ if (pentry->next)
+ pentry->next->prev = pentry->prev;
+ else
+ plist->tail = pentry->prev;
+
+ route_map_notify_pentry_dependencies(plist->name, pentry,
+ RMAP_EVENT_PLIST_DELETED);
+ prefix_list_entry_free(pentry);
+
+ plist->count--;
+
+ if (update_list) {
+ route_map_notify_dependencies(plist->name,
+ RMAP_EVENT_PLIST_DELETED);
+ if (plist->master->delete_hook)
+ (*plist->master->delete_hook)(plist);
+
+ if (plist->head == NULL && plist->tail == NULL
+ && plist->desc == NULL)
+ prefix_list_delete(plist);
+ else
+ plist->master->recent = plist;
+ }
+}
+
+static void trie_install_fn(struct prefix_list_entry *object,
+ struct prefix_list_entry **updptr)
+{
+ while (*updptr) {
+ if (*updptr == object)
+ return;
+ if ((*updptr)->prefix.prefixlen < object->prefix.prefixlen)
+ break;
+ if ((*updptr)->prefix.prefixlen == object->prefix.prefixlen
+ && (*updptr)->seq > object->seq)
+ break;
+ updptr = &(*updptr)->next_best;
+ }
+
+ if (!object->next_best)
+ object->next_best = *updptr;
+ else
+ assert(object->next_best == *updptr || !*updptr);
+
+ *updptr = object;
+}
+
+static void prefix_list_trie_add(struct prefix_list *plist,
+ struct prefix_list_entry *pentry)
+{
+ size_t depth = plist->master->trie_depth;
+ uint8_t *bytes = pentry->prefix.u.val;
+ size_t validbits = pentry->prefix.prefixlen;
+ struct pltrie_table *table;
+
+ table = plist->trie;
+ while (validbits > PLC_BITS && depth > 1) {
+ if (!table->entries[*bytes].next_table)
+ table->entries[*bytes].next_table =
+ XCALLOC(MTYPE_PREFIX_LIST_TRIE,
+ sizeof(struct pltrie_table));
+ table = table->entries[*bytes].next_table;
+ bytes++;
+ depth--;
+ validbits -= PLC_BITS;
+ }
+
+ trie_walk_affected(validbits, table, *bytes, pentry, trie_install_fn);
+}
+
+static void prefix_list_entry_add(struct prefix_list *plist,
+ struct prefix_list_entry *pentry)
+{
+ struct prefix_list_entry *replace;
+ struct prefix_list_entry *point;
+
+ /* Automatic asignment of seq no. */
+ if (pentry->seq == -1)
+ pentry->seq = prefix_new_seq_get(plist);
+
+ if (plist->tail && pentry->seq > plist->tail->seq)
+ point = NULL;
+ else {
+ /* Is there any same seq prefix list entry? */
+ replace = prefix_seq_check(plist, pentry->seq);
+ if (replace)
+ prefix_list_entry_delete(plist, replace, 0);
+
+ /* Check insert point. */
+ for (point = plist->head; point; point = point->next)
+ if (point->seq >= pentry->seq)
+ break;
+ }
+
+ /* In case of this is the first element of the list. */
+ pentry->next = point;
+
+ if (point) {
+ if (point->prev)
+ point->prev->next = pentry;
+ else
+ plist->head = pentry;
+
+ pentry->prev = point->prev;
+ point->prev = pentry;
+ } else {
+ if (plist->tail)
+ plist->tail->next = pentry;
+ else
+ plist->head = pentry;
+
+ pentry->prev = plist->tail;
+ plist->tail = pentry;
+ }
+
+ prefix_list_trie_add(plist, pentry);
+
+ /* Increment count. */
+ plist->count++;
+
+ route_map_notify_pentry_dependencies(plist->name, pentry,
+ RMAP_EVENT_PLIST_ADDED);
+
+ /* Run hook function. */
+ if (plist->master->add_hook)
+ (*plist->master->add_hook)(plist);
+
+ route_map_notify_dependencies(plist->name, RMAP_EVENT_PLIST_ADDED);
+ plist->master->recent = plist;
+}
+
+/**
+ * Prefix list entry update start procedure:
+ * Remove entry from previosly installed master list, tries and notify
+ * observers.
+ *
+ * \param[in] ple prefix list entry.
+ */
+void prefix_list_entry_update_start(struct prefix_list_entry *ple)
+{
+ struct prefix_list *pl = ple->pl;
+
+ /* Not installed, nothing to do. */
+ if (!ple->installed)
+ return;
+
+ prefix_list_trie_del(pl, ple);
+
+ /* List manipulation: shameless copy from `prefix_list_entry_delete`. */
+ if (ple->prev)
+ ple->prev->next = ple->next;
+ else
+ pl->head = ple->next;
+ if (ple->next)
+ ple->next->prev = ple->prev;
+ else
+ pl->tail = ple->prev;
+
+ route_map_notify_pentry_dependencies(pl->name, ple,
+ RMAP_EVENT_PLIST_DELETED);
+ pl->count--;
+
+ route_map_notify_dependencies(pl->name, RMAP_EVENT_PLIST_DELETED);
+ if (pl->master->delete_hook)
+ (*pl->master->delete_hook)(pl);
+
+ if (pl->head || pl->tail || pl->desc)
+ pl->master->recent = pl;
+
+ ple->next_best = NULL;
+ ple->installed = false;
+}
+
+/**
+ * Prefix list entry update finish procedure:
+ * Add entry back master list, to the trie, notify observers and call master
+ * hook.
+ *
+ * \param[in] ple prefix list entry.
+ */
+void prefix_list_entry_update_finish(struct prefix_list_entry *ple)
+{
+ struct prefix_list *pl = ple->pl;
+ struct prefix_list_entry *point;
+
+ /* Already installed, nothing to do. */
+ if (ple->installed)
+ return;
+
+ /*
+ * Check if the entry is installable:
+ * We can only install entry if at least the prefix is provided (IPv4
+ * or IPv6).
+ */
+ if (ple->prefix.family != AF_INET && ple->prefix.family != AF_INET6)
+ return;
+
+ /* List manipulation: shameless copy from `prefix_list_entry_add`. */
+ if (pl->tail && ple->seq > pl->tail->seq)
+ point = NULL;
+ else {
+ /* Check insert point. */
+ for (point = pl->head; point; point = point->next)
+ if (point->seq >= ple->seq)
+ break;
+ }
+
+ /* In case of this is the first element of the list. */
+ ple->next = point;
+
+ if (point) {
+ if (point->prev)
+ point->prev->next = ple;
+ else
+ pl->head = ple;
+
+ ple->prev = point->prev;
+ point->prev = ple;
+ } else {
+ if (pl->tail)
+ pl->tail->next = ple;
+ else
+ pl->head = ple;
+
+ ple->prev = pl->tail;
+ pl->tail = ple;
+ }
+
+ prefix_list_trie_add(pl, ple);
+ pl->count++;
+
+ route_map_notify_pentry_dependencies(pl->name, ple,
+ RMAP_EVENT_PLIST_ADDED);
+
+ /* Run hook function. */
+ if (pl->master->add_hook)
+ (*pl->master->add_hook)(pl);
+
+ route_map_notify_dependencies(pl->name, RMAP_EVENT_PLIST_ADDED);
+ pl->master->recent = pl;
+
+ ple->installed = true;
+}
+
+/**
+ * Same as `prefix_list_entry_delete` but without `free()`ing the list if its
+ * empty.
+ *
+ * \param[in] ple prefix list entry.
+ */
+void prefix_list_entry_delete2(struct prefix_list_entry *ple)
+{
+ /* Does the boiler plate list removal and entry removal notification. */
+ prefix_list_entry_update_start(ple);
+
+ /* Effective `free()` memory. */
+ prefix_list_entry_free(ple);
+}
+
+/* Return string of prefix_list_type. */
+static const char *prefix_list_type_str(struct prefix_list_entry *pentry)
+{
+ switch (pentry->type) {
+ case PREFIX_PERMIT:
+ return "permit";
+ case PREFIX_DENY:
+ return "deny";
+ default:
+ return "";
+ }
+}
+
+static int prefix_list_entry_match(struct prefix_list_entry *pentry,
+ const struct prefix *p, bool address_mode)
+{
+ int ret;
+
+ if (pentry->prefix.family != p->family)
+ return 0;
+
+ ret = prefix_match(&pentry->prefix, p);
+ if (!ret)
+ return 0;
+
+ if (address_mode)
+ return 1;
+
+ /* In case of le nor ge is specified, exact match is performed. */
+ if (!pentry->le && !pentry->ge) {
+ if (pentry->prefix.prefixlen != p->prefixlen)
+ return 0;
+ } else {
+ if (pentry->le)
+ if (p->prefixlen > pentry->le)
+ return 0;
+
+ if (pentry->ge)
+ if (p->prefixlen < pentry->ge)
+ return 0;
+ }
+ return 1;
+}
+
+enum prefix_list_type prefix_list_apply_ext(
+ struct prefix_list *plist,
+ const struct prefix_list_entry **which,
+ union prefixconstptr object,
+ bool address_mode)
+{
+ struct prefix_list_entry *pentry, *pbest = NULL;
+
+ const struct prefix *p = object.p;
+ const uint8_t *byte = p->u.val;
+ size_t depth;
+ size_t validbits = p->prefixlen;
+ struct pltrie_table *table;
+
+ if (plist == NULL) {
+ if (which)
+ *which = NULL;
+ return PREFIX_DENY;
+ }
+
+ if (plist->count == 0) {
+ if (which)
+ *which = NULL;
+ return PREFIX_PERMIT;
+ }
+
+ depth = plist->master->trie_depth;
+ table = plist->trie;
+ while (1) {
+ for (pentry = table->entries[*byte].up_chain; pentry;
+ pentry = pentry->next_best) {
+ if (pbest && pbest->seq < pentry->seq)
+ continue;
+ if (prefix_list_entry_match(pentry, p, address_mode))
+ pbest = pentry;
+ }
+
+ if (validbits <= PLC_BITS)
+ break;
+ validbits -= PLC_BITS;
+
+ if (--depth) {
+ if (!table->entries[*byte].next_table)
+ break;
+
+ table = table->entries[*byte].next_table;
+ byte++;
+ continue;
+ }
+
+ for (pentry = table->entries[*byte].final_chain; pentry;
+ pentry = pentry->next_best) {
+ if (pbest && pbest->seq < pentry->seq)
+ continue;
+ if (prefix_list_entry_match(pentry, p, address_mode))
+ pbest = pentry;
+ }
+ break;
+ }
+
+ if (which) {
+ if (pbest)
+ *which = pbest;
+ else
+ *which = NULL;
+ }
+
+ if (pbest == NULL)
+ return PREFIX_DENY;
+
+ pbest->hitcnt++;
+ return pbest->type;
+}
+
+static void __attribute__((unused)) prefix_list_print(struct prefix_list *plist)
+{
+ struct prefix_list_entry *pentry;
+
+ if (plist == NULL)
+ return;
+
+ printf("ip prefix-list %s: %d entries\n", plist->name, plist->count);
+
+ for (pentry = plist->head; pentry; pentry = pentry->next) {
+ if (pentry->any)
+ printf("any %s\n", prefix_list_type_str(pentry));
+ else {
+ struct prefix *p;
+
+ p = &pentry->prefix;
+
+ printf(" seq %lld %s %pFX", (long long)pentry->seq,
+ prefix_list_type_str(pentry), p);
+ if (pentry->ge)
+ printf(" ge %d", pentry->ge);
+ if (pentry->le)
+ printf(" le %d", pentry->le);
+ printf("\n");
+ }
+ }
+}
+
+/* Return 1 when plist already include pentry policy. */
+static struct prefix_list_entry *
+prefix_entry_dup_check(struct prefix_list *plist, struct prefix_list_entry *new)
+{
+ size_t depth, maxdepth = plist->master->trie_depth;
+ uint8_t byte, *bytes = new->prefix.u.val;
+ size_t validbits = new->prefix.prefixlen;
+ struct pltrie_table *table;
+ struct prefix_list_entry *pentry;
+ int64_t seq = 0;
+
+ if (new->seq == -1)
+ seq = prefix_new_seq_get(plist);
+ else
+ seq = new->seq;
+
+ table = plist->trie;
+ for (depth = 0; validbits > PLC_BITS && depth < maxdepth - 1; depth++) {
+ byte = bytes[depth];
+ if (!table->entries[byte].next_table)
+ return NULL;
+
+ table = table->entries[byte].next_table;
+ validbits -= PLC_BITS;
+ }
+
+ byte = bytes[depth];
+ if (validbits > PLC_BITS)
+ pentry = table->entries[byte].final_chain;
+ else
+ pentry = table->entries[byte].up_chain;
+
+ for (; pentry; pentry = pentry->next_best) {
+ if (prefix_same(&pentry->prefix, &new->prefix)
+ && pentry->type == new->type && pentry->le == new->le
+ && pentry->ge == new->ge && pentry->seq != seq)
+ return pentry;
+ }
+ return NULL;
+}
+
+enum display_type {
+ normal_display,
+ summary_display,
+ detail_display,
+ sequential_display,
+ longer_display,
+ first_match_display
+};
+
+static void vty_show_prefix_entry(struct vty *vty, json_object *json, afi_t afi,
+ struct prefix_list *plist,
+ struct prefix_master *master,
+ enum display_type dtype, int seqnum)
+{
+ struct prefix_list_entry *pentry;
+ json_object *json_pl = NULL;
+
+ /* Print the name of the protocol */
+ if (json) {
+ json_pl = json_object_new_object();
+ json_object_object_add(json, plist->name, json_pl);
+ } else
+ vty_out(vty, "%s: ", frr_protoname);
+
+ if (dtype == normal_display) {
+ if (json) {
+ json_object_string_add(json_pl, "addressFamily",
+ afi2str(afi));
+ json_object_int_add(json_pl, "entries", plist->count);
+ if (plist->desc)
+ json_object_string_add(json_pl, "description",
+ plist->desc);
+ } else {
+ vty_out(vty, "ip%s prefix-list %s: %d entries\n",
+ afi == AFI_IP ? "" : "v6", plist->name,
+ plist->count);
+ if (plist->desc)
+ vty_out(vty, " Description: %s\n",
+ plist->desc);
+ }
+ } else if (dtype == summary_display || dtype == detail_display) {
+ if (json) {
+ json_object_string_add(json_pl, "addressFamily",
+ afi2str(afi));
+ if (plist->desc)
+ json_object_string_add(json_pl, "description",
+ plist->desc);
+ json_object_int_add(json_pl, "count", plist->count);
+ json_object_int_add(json_pl, "rangeEntries",
+ plist->rangecount);
+ json_object_int_add(json_pl, "sequenceStart",
+ plist->head ? plist->head->seq : 0);
+ json_object_int_add(json_pl, "sequenceEnd",
+ plist->tail ? plist->tail->seq : 0);
+ } else {
+ vty_out(vty, "ip%s prefix-list %s:\n",
+ afi == AFI_IP ? "" : "v6", plist->name);
+
+ if (plist->desc)
+ vty_out(vty, " Description: %s\n",
+ plist->desc);
+
+ vty_out(vty,
+ " count: %d, range entries: %d, sequences: %" PRId64
+ " - %" PRId64 "\n",
+ plist->count, plist->rangecount,
+ plist->head ? plist->head->seq : 0,
+ plist->tail ? plist->tail->seq : 0);
+ }
+ }
+
+ if (dtype != summary_display) {
+ json_object *json_entries = NULL;
+
+ if (json) {
+ json_entries = json_object_new_array();
+ json_object_object_add(json_pl, "entries",
+ json_entries);
+ }
+
+ for (pentry = plist->head; pentry; pentry = pentry->next) {
+ if (dtype == sequential_display
+ && pentry->seq != seqnum)
+ continue;
+
+ if (json) {
+ json_object *json_entry;
+
+ json_entry = json_object_new_object();
+ json_object_array_add(json_entries, json_entry);
+
+ json_object_int_add(json_entry,
+ "sequenceNumber",
+ pentry->seq);
+ json_object_string_add(
+ json_entry, "type",
+ prefix_list_type_str(pentry));
+ json_object_string_addf(json_entry, "prefix",
+ "%pFX",
+ &pentry->prefix);
+
+ if (pentry->ge)
+ json_object_int_add(
+ json_entry,
+ "minimumPrefixLength",
+ pentry->ge);
+ if (pentry->le)
+ json_object_int_add(
+ json_entry,
+ "maximumPrefixLength",
+ pentry->le);
+
+ if (dtype == detail_display
+ || dtype == sequential_display) {
+ json_object_int_add(json_entry,
+ "hitCount",
+ pentry->hitcnt);
+ json_object_int_add(json_entry,
+ "referenceCount",
+ pentry->refcnt);
+ }
+ } else {
+ vty_out(vty, " ");
+
+ vty_out(vty, "seq %" PRId64 " ", pentry->seq);
+
+ vty_out(vty, "%s ",
+ prefix_list_type_str(pentry));
+
+ if (pentry->any)
+ vty_out(vty, "any");
+ else {
+ struct prefix *p = &pentry->prefix;
+
+ vty_out(vty, "%pFX", p);
+
+ if (pentry->ge)
+ vty_out(vty, " ge %d",
+ pentry->ge);
+ if (pentry->le)
+ vty_out(vty, " le %d",
+ pentry->le);
+ }
+
+ if (dtype == detail_display
+ || dtype == sequential_display)
+ vty_out(vty,
+ " (hit count: %ld, refcount: %ld)",
+ pentry->hitcnt, pentry->refcnt);
+
+ vty_out(vty, "\n");
+ }
+ }
+ }
+}
+
+static int vty_show_prefix_list(struct vty *vty, afi_t afi, const char *name,
+ const char *seq, enum display_type dtype,
+ bool uj)
+{
+ struct prefix_list *plist;
+ struct prefix_master *master;
+ int64_t seqnum = 0;
+ json_object *json = NULL;
+ json_object *json_proto = NULL;
+
+ master = prefix_master_get(afi, 0);
+ if (master == NULL)
+ return CMD_WARNING;
+
+ if (uj) {
+ json = json_object_new_object();
+ json_proto = json_object_new_object();
+ json_object_object_add(json, frr_protoname, json_proto);
+ }
+
+ if (seq)
+ seqnum = (int64_t)atol(seq);
+
+ if (name) {
+ plist = prefix_list_lookup(afi, name);
+ if (!plist) {
+ if (!uj)
+ vty_out(vty,
+ "%% Can't find specified prefix-list\n");
+ return CMD_WARNING;
+ }
+ vty_show_prefix_entry(vty, json_proto, afi, plist, master,
+ dtype, seqnum);
+ } else {
+ if (dtype == detail_display || dtype == summary_display) {
+ if (master->recent && !uj)
+ vty_out(vty,
+ "Prefix-list with the last deletion/insertion: %s\n",
+ master->recent->name);
+ }
+
+ frr_each (plist, &master->str, plist)
+ vty_show_prefix_entry(vty, json_proto, afi, plist,
+ master, dtype, seqnum);
+ }
+
+ return vty_json(vty, json);
+}
+
+static int vty_show_prefix_list_prefix(struct vty *vty, afi_t afi,
+ const char *name, const char *prefix,
+ enum display_type type)
+{
+ struct prefix_list *plist;
+ struct prefix_list_entry *pentry;
+ struct prefix p;
+ int ret;
+ int match;
+
+ plist = prefix_list_lookup(afi, name);
+ if (!plist) {
+ vty_out(vty, "%% Can't find specified prefix-list\n");
+ return CMD_WARNING;
+ }
+
+ ret = str2prefix(prefix, &p);
+ if (ret <= 0) {
+ vty_out(vty, "%% prefix is malformed\n");
+ return CMD_WARNING;
+ }
+
+ for (pentry = plist->head; pentry; pentry = pentry->next) {
+ match = 0;
+
+ if (type == normal_display || type == first_match_display)
+ if (prefix_same(&p, &pentry->prefix))
+ match = 1;
+
+ if (type == longer_display) {
+ if ((p.family == pentry->prefix.family)
+ && (prefix_match(&p, &pentry->prefix)))
+ match = 1;
+ }
+
+ if (match) {
+ vty_out(vty, " seq %" PRId64 " %s ", pentry->seq,
+ prefix_list_type_str(pentry));
+
+ if (pentry->any)
+ vty_out(vty, "any");
+ else {
+ struct prefix *pf = &pentry->prefix;
+
+ vty_out(vty, "%pFX", pf);
+
+ if (pentry->ge)
+ vty_out(vty, " ge %d", pentry->ge);
+ if (pentry->le)
+ vty_out(vty, " le %d", pentry->le);
+ }
+
+ if (type == normal_display
+ || type == first_match_display)
+ vty_out(vty, " (hit count: %ld, refcount: %ld)",
+ pentry->hitcnt, pentry->refcnt);
+
+ vty_out(vty, "\n");
+
+ if (type == first_match_display)
+ return CMD_SUCCESS;
+ }
+ }
+ return CMD_SUCCESS;
+}
+
+static int vty_clear_prefix_list(struct vty *vty, afi_t afi, const char *name,
+ const char *prefix)
+{
+ struct prefix_master *master;
+ struct prefix_list *plist;
+ struct prefix_list_entry *pentry;
+ int ret;
+ struct prefix p;
+
+ master = prefix_master_get(afi, 0);
+ if (master == NULL)
+ return CMD_WARNING;
+
+ if (name == NULL && prefix == NULL) {
+ frr_each (plist, &master->str, plist)
+ for (pentry = plist->head; pentry;
+ pentry = pentry->next)
+ pentry->hitcnt = 0;
+ } else {
+ plist = prefix_list_lookup(afi, name);
+ if (!plist) {
+ vty_out(vty, "%% Can't find specified prefix-list\n");
+ return CMD_WARNING;
+ }
+
+ if (prefix) {
+ ret = str2prefix(prefix, &p);
+ if (ret <= 0) {
+ vty_out(vty, "%% prefix is malformed\n");
+ return CMD_WARNING;
+ }
+ }
+
+ for (pentry = plist->head; pentry; pentry = pentry->next) {
+ if (prefix) {
+ if (pentry->prefix.family == p.family
+ && prefix_match(&pentry->prefix, &p))
+ pentry->hitcnt = 0;
+ } else
+ pentry->hitcnt = 0;
+ }
+ }
+ return CMD_SUCCESS;
+}
+
+#ifndef VTYSH_EXTRACT_PL
+#include "lib/plist_clippy.c"
+#endif
+
+DEFPY (show_ip_prefix_list,
+ show_ip_prefix_list_cmd,
+ "show ip prefix-list [WORD [seq$dseq (1-4294967295)$arg]] [json$uj]",
+ SHOW_STR
+ IP_STR
+ PREFIX_LIST_STR
+ "Name of a prefix list\n"
+ "sequence number of an entry\n"
+ "Sequence number\n"
+ JSON_STR)
+{
+ enum display_type dtype = normal_display;
+ if (dseq)
+ dtype = sequential_display;
+
+ return vty_show_prefix_list(vty, AFI_IP, prefix_list, arg_str, dtype,
+ !!uj);
+}
+
+DEFPY (show_ip_prefix_list_prefix,
+ show_ip_prefix_list_prefix_cmd,
+ "show ip prefix-list WORD A.B.C.D/M$prefix [longer$dl|first-match$dfm]",
+ SHOW_STR
+ IP_STR
+ PREFIX_LIST_STR
+ "Name of a prefix list\n"
+ "IP prefix <network>/<length>, e.g., 35.0.0.0/8\n"
+ "Lookup longer prefix\n"
+ "First matched prefix\n")
+{
+ enum display_type dtype = normal_display;
+ if (dl)
+ dtype = longer_display;
+ else if (dfm)
+ dtype = first_match_display;
+
+ return vty_show_prefix_list_prefix(vty, AFI_IP, prefix_list, prefix_str,
+ dtype);
+}
+
+DEFPY (show_ip_prefix_list_summary,
+ show_ip_prefix_list_summary_cmd,
+ "show ip prefix-list summary [WORD$prefix_list] [json$uj]",
+ SHOW_STR
+ IP_STR
+ PREFIX_LIST_STR
+ "Summary of prefix lists\n"
+ "Name of a prefix list\n"
+ JSON_STR)
+{
+ return vty_show_prefix_list(vty, AFI_IP, prefix_list, NULL,
+ summary_display, !!uj);
+}
+
+DEFPY (show_ip_prefix_list_detail,
+ show_ip_prefix_list_detail_cmd,
+ "show ip prefix-list detail [WORD$prefix_list] [json$uj]",
+ SHOW_STR
+ IP_STR
+ PREFIX_LIST_STR
+ "Detail of prefix lists\n"
+ "Name of a prefix list\n"
+ JSON_STR)
+{
+ return vty_show_prefix_list(vty, AFI_IP, prefix_list, NULL,
+ detail_display, !!uj);
+}
+
+DEFPY (clear_ip_prefix_list,
+ clear_ip_prefix_list_cmd,
+ "clear ip prefix-list [WORD [A.B.C.D/M$prefix]]",
+ CLEAR_STR
+ IP_STR
+ PREFIX_LIST_STR
+ "Name of a prefix list\n"
+ "IP prefix <network>/<length>, e.g., 35.0.0.0/8\n")
+{
+ return vty_clear_prefix_list(vty, AFI_IP, prefix_list, prefix_str);
+}
+
+DEFPY (show_ipv6_prefix_list,
+ show_ipv6_prefix_list_cmd,
+ "show ipv6 prefix-list [WORD [seq$dseq (1-4294967295)$arg]] [json$uj]",
+ SHOW_STR
+ IPV6_STR
+ PREFIX_LIST_STR
+ "Name of a prefix list\n"
+ "sequence number of an entry\n"
+ "Sequence number\n"
+ JSON_STR)
+{
+ enum display_type dtype = normal_display;
+ if (dseq)
+ dtype = sequential_display;
+
+ return vty_show_prefix_list(vty, AFI_IP6, prefix_list, arg_str, dtype,
+ !!uj);
+}
+
+DEFPY (show_ipv6_prefix_list_prefix,
+ show_ipv6_prefix_list_prefix_cmd,
+ "show ipv6 prefix-list WORD X:X::X:X/M$prefix [longer$dl|first-match$dfm]",
+ SHOW_STR
+ IPV6_STR
+ PREFIX_LIST_STR
+ "Name of a prefix list\n"
+ "IPv6 prefix <network>/<length>, e.g., 3ffe::/16\n"
+ "Lookup longer prefix\n"
+ "First matched prefix\n")
+{
+ enum display_type dtype = normal_display;
+ if (dl)
+ dtype = longer_display;
+ else if (dfm)
+ dtype = first_match_display;
+
+ return vty_show_prefix_list_prefix(vty, AFI_IP6, prefix_list,
+ prefix_str, dtype);
+}
+
+DEFPY (show_ipv6_prefix_list_summary,
+ show_ipv6_prefix_list_summary_cmd,
+ "show ipv6 prefix-list summary [WORD$prefix-list] [json$uj]",
+ SHOW_STR
+ IPV6_STR
+ PREFIX_LIST_STR
+ "Summary of prefix lists\n"
+ "Name of a prefix list\n"
+ JSON_STR)
+{
+ return vty_show_prefix_list(vty, AFI_IP6, prefix_list, NULL,
+ summary_display, !!uj);
+}
+
+DEFPY (show_ipv6_prefix_list_detail,
+ show_ipv6_prefix_list_detail_cmd,
+ "show ipv6 prefix-list detail [WORD$prefix-list] [json$uj]",
+ SHOW_STR
+ IPV6_STR
+ PREFIX_LIST_STR
+ "Detail of prefix lists\n"
+ "Name of a prefix list\n"
+ JSON_STR)
+{
+ return vty_show_prefix_list(vty, AFI_IP6, prefix_list, NULL,
+ detail_display, !!uj);
+}
+
+DEFPY (clear_ipv6_prefix_list,
+ clear_ipv6_prefix_list_cmd,
+ "clear ipv6 prefix-list [WORD [X:X::X:X/M$prefix]]",
+ CLEAR_STR
+ IPV6_STR
+ PREFIX_LIST_STR
+ "Name of a prefix list\n"
+ "IPv6 prefix <network>/<length>, e.g., 3ffe::/16\n")
+{
+ return vty_clear_prefix_list(vty, AFI_IP6, prefix_list, prefix_str);
+}
+
+DEFPY (debug_prefix_list_match,
+ debug_prefix_list_match_cmd,
+ "debug prefix-list WORD$prefix-list match <A.B.C.D/M|X:X::X:X/M>"
+ " [address-mode$addr_mode]",
+ DEBUG_STR
+ "Prefix-list test access\n"
+ "Name of a prefix list\n"
+ "Test prefix for prefix list result\n"
+ "Prefix to test in ip prefix-list\n"
+ "Prefix to test in ipv6 prefix-list\n"
+ "Use address matching mode (PIM RP)\n")
+{
+ struct prefix_list *plist;
+ const struct prefix_list_entry *entry = NULL;
+ enum prefix_list_type ret;
+
+ plist = prefix_list_lookup(family2afi(match->family), prefix_list);
+ if (!plist) {
+ vty_out(vty, "%% no prefix list named %s for AFI %s\n",
+ prefix_list, afi2str(family2afi(match->family)));
+ return CMD_WARNING;
+ }
+
+ ret = prefix_list_apply_ext(plist, &entry, match, !!addr_mode);
+
+ vty_out(vty, "%s prefix list %s yields %s for %pFX, ",
+ afi2str(family2afi(match->family)), prefix_list,
+ ret == PREFIX_DENY ? "DENY" : "PERMIT", match);
+
+ if (!entry)
+ vty_out(vty, "no match found\n");
+ else {
+ vty_out(vty, "matching entry #%"PRId64": %pFX", entry->seq,
+ &entry->prefix);
+ if (entry->ge)
+ vty_out(vty, " ge %d", entry->ge);
+ if (entry->le)
+ vty_out(vty, " le %d", entry->le);
+ vty_out(vty, "\n");
+ }
+
+ /* allow using this in scripts for quick prefix-list member tests */
+ return (ret == PREFIX_PERMIT) ? CMD_SUCCESS : CMD_WARNING;
+}
+
+struct stream *prefix_bgp_orf_entry(struct stream *s, struct prefix_list *plist,
+ uint8_t init_flag, uint8_t permit_flag,
+ uint8_t deny_flag)
+{
+ struct prefix_list_entry *pentry;
+
+ if (!plist)
+ return s;
+
+ for (pentry = plist->head; pentry; pentry = pentry->next) {
+ uint8_t flag = init_flag;
+ struct prefix *p = &pentry->prefix;
+
+ flag |= (pentry->type == PREFIX_PERMIT ? permit_flag
+ : deny_flag);
+ stream_putc(s, flag);
+ stream_putl(s, (uint32_t)pentry->seq);
+ stream_putc(s, (uint8_t)pentry->ge);
+ stream_putc(s, (uint8_t)pentry->le);
+ stream_put_prefix(s, p);
+ }
+
+ return s;
+}
+
+int prefix_bgp_orf_set(char *name, afi_t afi, struct orf_prefix *orfp,
+ int permit, int set)
+{
+ struct prefix_list *plist;
+ struct prefix_list_entry *pentry;
+
+ /* ge and le value check */
+ if (orfp->ge && orfp->ge < orfp->p.prefixlen)
+ return CMD_WARNING_CONFIG_FAILED;
+ if (orfp->le && orfp->le < orfp->p.prefixlen)
+ return CMD_WARNING_CONFIG_FAILED;
+ if (orfp->le && orfp->ge > orfp->le)
+ return CMD_WARNING_CONFIG_FAILED;
+
+ if (orfp->ge && orfp->le == (afi == AFI_IP ? 32 : 128))
+ orfp->le = 0;
+
+ plist = prefix_list_get(afi, 1, name);
+ if (!plist)
+ return CMD_WARNING_CONFIG_FAILED;
+
+ apply_mask(&orfp->p);
+
+ if (set) {
+ pentry = prefix_list_entry_make(
+ &orfp->p, (permit ? PREFIX_PERMIT : PREFIX_DENY),
+ orfp->seq, orfp->le, orfp->ge, false);
+
+ if (prefix_entry_dup_check(plist, pentry)) {
+ prefix_list_entry_free(pentry);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ prefix_list_entry_add(plist, pentry);
+ } else {
+ pentry = prefix_list_entry_lookup(
+ plist, &orfp->p, (permit ? PREFIX_PERMIT : PREFIX_DENY),
+ orfp->seq, orfp->le, orfp->ge);
+
+ if (!pentry)
+ return CMD_WARNING_CONFIG_FAILED;
+
+ prefix_list_entry_delete(plist, pentry, 1);
+ }
+
+ return CMD_SUCCESS;
+}
+
+void prefix_bgp_orf_remove_all(afi_t afi, char *name)
+{
+ struct prefix_list *plist;
+
+ plist = prefix_bgp_orf_lookup(afi, name);
+ if (plist)
+ prefix_list_delete(plist);
+}
+
+/* return prefix count */
+int prefix_bgp_show_prefix_list(struct vty *vty, afi_t afi, char *name,
+ bool use_json)
+{
+ struct prefix_list *plist;
+ struct prefix_list_entry *pentry;
+ json_object *json = NULL;
+ json_object *json_prefix = NULL;
+ json_object *json_list = NULL;
+
+ plist = prefix_bgp_orf_lookup(afi, name);
+ if (!plist)
+ return 0;
+
+ if (!vty)
+ return plist->count;
+
+ if (use_json) {
+ json = json_object_new_object();
+ json_prefix = json_object_new_object();
+ json_list = json_object_new_object();
+
+ json_object_int_add(json_prefix, "prefixListCounter",
+ plist->count);
+ json_object_string_add(json_prefix, "prefixListName",
+ plist->name);
+
+ for (pentry = plist->head; pentry; pentry = pentry->next) {
+ struct prefix *p = &pentry->prefix;
+ char buf_a[BUFSIZ];
+
+ snprintf(buf_a, sizeof(buf_a), "%pFX", p);
+
+ json_object_int_add(json_list, "seq", pentry->seq);
+ json_object_string_add(json_list, "seqPrefixListType",
+ prefix_list_type_str(pentry));
+
+ if (pentry->ge)
+ json_object_int_add(json_list, "ge",
+ pentry->ge);
+ if (pentry->le)
+ json_object_int_add(json_list, "le",
+ pentry->le);
+
+ json_object_object_add(json_prefix, buf_a, json_list);
+ }
+ if (afi == AFI_IP)
+ json_object_object_add(json, "ipPrefixList",
+ json_prefix);
+ else
+ json_object_object_add(json, "ipv6PrefixList",
+ json_prefix);
+
+ vty_json(vty, json);
+ } else {
+ vty_out(vty, "ip%s prefix-list %s: %d entries\n",
+ afi == AFI_IP ? "" : "v6", plist->name, plist->count);
+
+ for (pentry = plist->head; pentry; pentry = pentry->next) {
+ struct prefix *p = &pentry->prefix;
+
+ vty_out(vty, " seq %" PRId64 " %s %pFX", pentry->seq,
+ prefix_list_type_str(pentry), p);
+
+ if (pentry->ge)
+ vty_out(vty, " ge %d", pentry->ge);
+ if (pentry->le)
+ vty_out(vty, " le %d", pentry->le);
+
+ vty_out(vty, "\n");
+ }
+ }
+ return plist->count;
+}
+
+static void prefix_list_reset_afi(afi_t afi, int orf)
+{
+ struct prefix_list *plist;
+ struct prefix_master *master;
+
+ master = prefix_master_get(afi, orf);
+ if (master == NULL)
+ return;
+
+ while ((plist = plist_first(&master->str))) {
+ prefix_list_delete(plist);
+ }
+
+ master->recent = NULL;
+}
+
+/* Prefix-list node. */
+static struct cmd_node prefix_node = {
+ .name = "ipv4 prefix list",
+ .node = PREFIX_NODE,
+ .prompt = "",
+};
+
+static void plist_autocomplete_afi(afi_t afi, vector comps,
+ struct cmd_token *token)
+{
+ struct prefix_list *plist;
+ struct prefix_master *master;
+
+ master = prefix_master_get(afi, 0);
+ if (master == NULL)
+ return;
+
+ frr_each (plist, &master->str, plist)
+ vector_set(comps, XSTRDUP(MTYPE_COMPLETION, plist->name));
+}
+
+static void plist_autocomplete(vector comps, struct cmd_token *token)
+{
+ plist_autocomplete_afi(AFI_IP, comps, token);
+ plist_autocomplete_afi(AFI_IP6, comps, token);
+}
+
+static const struct cmd_variable_handler plist_var_handlers[] = {
+ {/* "prefix-list WORD" */
+ .varname = "prefix_list",
+ .completions = plist_autocomplete},
+ {.tokenname = "PREFIXLIST_NAME",
+ .completions = plist_autocomplete},
+ {.completions = NULL}};
+
+
+static void prefix_list_init_ipv4(void)
+{
+ install_node(&prefix_node);
+
+ install_element(VIEW_NODE, &show_ip_prefix_list_cmd);
+ install_element(VIEW_NODE, &show_ip_prefix_list_prefix_cmd);
+ install_element(VIEW_NODE, &show_ip_prefix_list_summary_cmd);
+ install_element(VIEW_NODE, &show_ip_prefix_list_detail_cmd);
+
+ install_element(ENABLE_NODE, &clear_ip_prefix_list_cmd);
+}
+
+/* Prefix-list node. */
+static struct cmd_node prefix_ipv6_node = {
+ .name = "ipv6 prefix list",
+ .node = PREFIX_IPV6_NODE,
+ .prompt = "",
+};
+
+static void prefix_list_init_ipv6(void)
+{
+ install_node(&prefix_ipv6_node);
+
+ install_element(VIEW_NODE, &show_ipv6_prefix_list_cmd);
+ install_element(VIEW_NODE, &show_ipv6_prefix_list_prefix_cmd);
+ install_element(VIEW_NODE, &show_ipv6_prefix_list_summary_cmd);
+ install_element(VIEW_NODE, &show_ipv6_prefix_list_detail_cmd);
+ install_element(VIEW_NODE, &debug_prefix_list_match_cmd);
+
+ install_element(ENABLE_NODE, &clear_ipv6_prefix_list_cmd);
+}
+
+void prefix_list_init(void)
+{
+ plist_init(&prefix_master_ipv4.str);
+ plist_init(&prefix_master_orf_v4.str);
+ plist_init(&prefix_master_ipv6.str);
+ plist_init(&prefix_master_orf_v6.str);
+
+ cmd_variable_handler_register(plist_var_handlers);
+
+ prefix_list_init_ipv4();
+ prefix_list_init_ipv6();
+}
+
+void prefix_list_reset(void)
+{
+ prefix_list_reset_afi(AFI_IP, 0);
+ prefix_list_reset_afi(AFI_IP6, 0);
+ prefix_list_reset_afi(AFI_IP, 1);
+ prefix_list_reset_afi(AFI_IP6, 1);
+}
diff --git a/lib/plist.h b/lib/plist.h
new file mode 100644
index 0000000..c9507df
--- /dev/null
+++ b/lib/plist.h
@@ -0,0 +1,107 @@
+/*
+ * Prefix list functions.
+ * Copyright (C) 1999 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra 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 _QUAGGA_PLIST_H
+#define _QUAGGA_PLIST_H
+
+#include <zebra.h>
+
+#include "stream.h"
+#include "vty.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum prefix_list_type {
+ PREFIX_DENY,
+ PREFIX_PERMIT,
+};
+
+struct prefix_list;
+struct prefix_list_entry;
+
+struct orf_prefix {
+ uint32_t seq;
+ uint8_t ge;
+ uint8_t le;
+ struct prefix p;
+};
+
+/* Prototypes. */
+extern void prefix_list_init(void);
+extern void prefix_list_reset(void);
+extern void prefix_list_add_hook(void (*func)(struct prefix_list *));
+extern void prefix_list_delete_hook(void (*func)(struct prefix_list *));
+
+extern const char *prefix_list_name(struct prefix_list *);
+extern afi_t prefix_list_afi(struct prefix_list *);
+extern struct prefix_list *prefix_list_lookup(afi_t, const char *);
+
+/*
+ * prefix_list_apply_which_prefix
+ *
+ * Allow calling function to learn which prefix
+ * caused the DENY or PERMIT.
+ *
+ * If no pointer is sent in, do not return anything.
+ * If it is a empty plist return a NULL pointer.
+ *
+ * address_mode = the "prefix" being passed in is really an address, match
+ * regardless of prefix length (i.e. ge/le are ignored.) prefix->prefixlen
+ * must be /32.
+ */
+extern enum prefix_list_type
+prefix_list_apply_ext(struct prefix_list *plist,
+ const struct prefix_list_entry **matches,
+ union prefixconstptr prefix,
+ bool address_mode);
+#define prefix_list_apply(A, B) \
+ prefix_list_apply_ext((A), NULL, (B), false)
+
+extern struct prefix_list *prefix_bgp_orf_lookup(afi_t, const char *);
+extern struct stream *prefix_bgp_orf_entry(struct stream *,
+ struct prefix_list *, uint8_t,
+ uint8_t, uint8_t);
+extern int prefix_bgp_orf_set(char *, afi_t, struct orf_prefix *, int, int);
+extern void prefix_bgp_orf_remove_all(afi_t, char *);
+extern int prefix_bgp_show_prefix_list(struct vty *vty, afi_t afi, char *name,
+ bool use_json);
+
+extern struct prefix_list *prefix_list_get(afi_t afi, int orf,
+ const char *name);
+extern void prefix_list_delete(struct prefix_list *plist);
+extern int64_t prefix_new_seq_get(struct prefix_list *plist);
+
+extern struct prefix_list_entry *prefix_list_entry_new(void);
+extern void prefix_list_entry_delete(struct prefix_list *plist,
+ struct prefix_list_entry *pentry,
+ int update_list);
+extern struct prefix_list_entry *
+prefix_list_entry_lookup(struct prefix_list *plist, struct prefix *prefix,
+ enum prefix_list_type type, int64_t seq, int le,
+ int ge);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QUAGGA_PLIST_H */
diff --git a/lib/plist_int.h b/lib/plist_int.h
new file mode 100644
index 0000000..397557b
--- /dev/null
+++ b/lib/plist_int.h
@@ -0,0 +1,86 @@
+/*
+ * Prefix list internal definitions.
+ * Copyright (C) 1999 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra 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 _QUAGGA_PLIST_INT_H
+#define _QUAGGA_PLIST_INT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct pltrie_table;
+
+PREDECL_RBTREE_UNIQ(plist);
+
+struct prefix_list {
+ char *name;
+ char *desc;
+
+ struct prefix_master *master;
+
+ int count;
+ int rangecount;
+
+ struct plist_item plist_item;
+
+ struct prefix_list_entry *head;
+ struct prefix_list_entry *tail;
+
+ struct pltrie_table *trie;
+};
+
+/* Each prefix-list's entry. */
+struct prefix_list_entry {
+ int64_t seq;
+
+ int le;
+ int ge;
+
+ enum prefix_list_type type;
+
+ bool any;
+ struct prefix prefix;
+
+ unsigned long refcnt;
+ unsigned long hitcnt;
+
+ struct prefix_list *pl;
+
+ struct prefix_list_entry *next;
+ struct prefix_list_entry *prev;
+
+ /* up the chain for best match search */
+ struct prefix_list_entry *next_best;
+
+ /* Flag to track trie/list installation status. */
+ bool installed;
+};
+
+extern void prefix_list_entry_free(struct prefix_list_entry *pentry);
+extern void prefix_list_entry_delete2(struct prefix_list_entry *ple);
+extern void prefix_list_entry_update_start(struct prefix_list_entry *ple);
+extern void prefix_list_entry_update_finish(struct prefix_list_entry *ple);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QUAGGA_PLIST_INT_H */
diff --git a/lib/prefix.c b/lib/prefix.c
new file mode 100644
index 0000000..e64b10b
--- /dev/null
+++ b/lib/prefix.c
@@ -0,0 +1,1553 @@
+/*
+ * Prefix related functions.
+ * Copyright (C) 1997, 98, 99 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 "command.h"
+#include "prefix.h"
+#include "ipaddr.h"
+#include "vty.h"
+#include "sockunion.h"
+#include "memory.h"
+#include "log.h"
+#include "jhash.h"
+#include "lib_errors.h"
+#include "printfrr.h"
+#include "vxlan.h"
+
+DEFINE_MTYPE_STATIC(LIB, PREFIX, "Prefix");
+DEFINE_MTYPE_STATIC(LIB, PREFIX_FLOWSPEC, "Prefix Flowspec");
+
+/* Maskbit. */
+static const uint8_t maskbit[] = {0x00, 0x80, 0xc0, 0xe0, 0xf0,
+ 0xf8, 0xfc, 0xfe, 0xff};
+
+/* Number of bits in prefix type. */
+#ifndef PNBBY
+#define PNBBY 8
+#endif /* PNBBY */
+
+#define MASKBIT(offset) ((0xff << (PNBBY - (offset))) & 0xff)
+
+int is_zero_mac(const struct ethaddr *mac)
+{
+ int i = 0;
+
+ for (i = 0; i < ETH_ALEN; i++) {
+ if (mac->octet[i])
+ return 0;
+ }
+
+ return 1;
+}
+
+bool is_bcast_mac(const struct ethaddr *mac)
+{
+ int i = 0;
+
+ for (i = 0; i < ETH_ALEN; i++)
+ if (mac->octet[i] != 0xFF)
+ return false;
+
+ return true;
+}
+
+bool is_mcast_mac(const struct ethaddr *mac)
+{
+ if ((mac->octet[0] & 0x01) == 0x01)
+ return true;
+
+ return false;
+}
+
+unsigned int prefix_bit(const uint8_t *prefix, const uint16_t bit_index)
+{
+ unsigned int offset = bit_index / 8;
+ unsigned int shift = 7 - (bit_index % 8);
+
+ return (prefix[offset] >> shift) & 1;
+}
+
+int str2family(const char *string)
+{
+ if (!strcmp("ipv4", string))
+ return AF_INET;
+ else if (!strcmp("ipv6", string))
+ return AF_INET6;
+ else if (!strcmp("ethernet", string))
+ return AF_ETHERNET;
+ else if (!strcmp("evpn", string))
+ return AF_EVPN;
+ return -1;
+}
+
+const char *family2str(int family)
+{
+ switch (family) {
+ case AF_INET:
+ return "IPv4";
+ case AF_INET6:
+ return "IPv6";
+ case AF_ETHERNET:
+ return "Ethernet";
+ case AF_EVPN:
+ return "Evpn";
+ }
+ return "?";
+}
+
+/* Address Family Identifier to Address Family converter. */
+int afi2family(afi_t afi)
+{
+ if (afi == AFI_IP)
+ return AF_INET;
+ else if (afi == AFI_IP6)
+ return AF_INET6;
+ else if (afi == AFI_L2VPN)
+ return AF_ETHERNET;
+ /* NOTE: EVPN code should NOT use this interface. */
+ return 0;
+}
+
+afi_t family2afi(int family)
+{
+ if (family == AF_INET)
+ return AFI_IP;
+ else if (family == AF_INET6)
+ return AFI_IP6;
+ else if (family == AF_ETHERNET || family == AF_EVPN)
+ return AFI_L2VPN;
+ return 0;
+}
+
+const char *afi2str(afi_t afi)
+{
+ switch (afi) {
+ case AFI_IP:
+ return "IPv4";
+ case AFI_IP6:
+ return "IPv6";
+ case AFI_L2VPN:
+ return "l2vpn";
+ case AFI_MAX:
+ return "bad-value";
+ default:
+ break;
+ }
+ return NULL;
+}
+
+const char *safi2str(safi_t safi)
+{
+ switch (safi) {
+ case SAFI_UNICAST:
+ return "unicast";
+ case SAFI_MULTICAST:
+ return "multicast";
+ case SAFI_MPLS_VPN:
+ return "vpn";
+ case SAFI_ENCAP:
+ return "encap";
+ case SAFI_EVPN:
+ return "evpn";
+ case SAFI_LABELED_UNICAST:
+ return "labeled-unicast";
+ case SAFI_FLOWSPEC:
+ return "flowspec";
+ default:
+ return "unknown";
+ }
+}
+
+/* If n includes p prefix then return 1 else return 0. */
+int prefix_match(union prefixconstptr unet, union prefixconstptr upfx)
+{
+ const struct prefix *n = unet.p;
+ const struct prefix *p = upfx.p;
+ int offset;
+ int shift;
+ const uint8_t *np, *pp;
+
+ /* If n's prefix is longer than p's one return 0. */
+ if (n->prefixlen > p->prefixlen)
+ return 0;
+
+ if (n->family == AF_FLOWSPEC) {
+ /* prefixlen is unused. look at fs prefix len */
+ if (n->u.prefix_flowspec.family !=
+ p->u.prefix_flowspec.family)
+ return 0;
+
+ if (n->u.prefix_flowspec.prefixlen >
+ p->u.prefix_flowspec.prefixlen)
+ return 0;
+
+ /* Set both prefix's head pointer. */
+ np = (const uint8_t *)&n->u.prefix_flowspec.ptr;
+ pp = (const uint8_t *)&p->u.prefix_flowspec.ptr;
+
+ offset = n->u.prefix_flowspec.prefixlen;
+
+ while (offset--)
+ if (np[offset] != pp[offset])
+ return 0;
+ return 1;
+ }
+
+ /* Set both prefix's head pointer. */
+ np = n->u.val;
+ pp = p->u.val;
+
+ offset = n->prefixlen / PNBBY;
+ shift = n->prefixlen % PNBBY;
+
+ if (shift)
+ if (maskbit[shift] & (np[offset] ^ pp[offset]))
+ return 0;
+
+ while (offset--)
+ if (np[offset] != pp[offset])
+ return 0;
+ return 1;
+
+}
+
+/*
+ * n is a type5 evpn prefix. This function tries to see if there is an
+ * ip-prefix within n which matches prefix p
+ * If n includes p prefix then return 1 else return 0.
+ */
+int evpn_type5_prefix_match(const struct prefix *n, const struct prefix *p)
+{
+ int offset;
+ int shift;
+ int prefixlen;
+ const uint8_t *np, *pp;
+ struct prefix_evpn *evp;
+
+ if (n->family != AF_EVPN)
+ return 0;
+
+ evp = (struct prefix_evpn *)n;
+ pp = p->u.val;
+
+ if ((evp->prefix.route_type != 5) ||
+ (p->family == AF_INET6 && !is_evpn_prefix_ipaddr_v6(evp)) ||
+ (p->family == AF_INET && !is_evpn_prefix_ipaddr_v4(evp)) ||
+ (is_evpn_prefix_ipaddr_none(evp)))
+ return 0;
+
+ prefixlen = evp->prefix.prefix_addr.ip_prefix_length;
+ np = &evp->prefix.prefix_addr.ip.ip.addr;
+
+ /* If n's prefix is longer than p's one return 0. */
+ if (prefixlen > p->prefixlen)
+ return 0;
+
+ offset = prefixlen / PNBBY;
+ shift = prefixlen % PNBBY;
+
+ if (shift)
+ if (maskbit[shift] & (np[offset] ^ pp[offset]))
+ return 0;
+
+ while (offset--)
+ if (np[offset] != pp[offset])
+ return 0;
+ return 1;
+
+}
+
+/* If n includes p then return 1 else return 0. Prefix mask is not considered */
+int prefix_match_network_statement(union prefixconstptr unet,
+ union prefixconstptr upfx)
+{
+ const struct prefix *n = unet.p;
+ const struct prefix *p = upfx.p;
+ int offset;
+ int shift;
+ const uint8_t *np, *pp;
+
+ /* Set both prefix's head pointer. */
+ np = n->u.val;
+ pp = p->u.val;
+
+ offset = n->prefixlen / PNBBY;
+ shift = n->prefixlen % PNBBY;
+
+ if (shift)
+ if (maskbit[shift] & (np[offset] ^ pp[offset]))
+ return 0;
+
+ while (offset--)
+ if (np[offset] != pp[offset])
+ return 0;
+ return 1;
+}
+
+#ifdef __clang_analyzer__
+#undef prefix_copy /* cf. prefix.h */
+#endif
+
+void prefix_copy(union prefixptr udest, union prefixconstptr usrc)
+{
+ struct prefix *dest = udest.p;
+ const struct prefix *src = usrc.p;
+
+ dest->family = src->family;
+ dest->prefixlen = src->prefixlen;
+
+ if (src->family == AF_INET)
+ dest->u.prefix4 = src->u.prefix4;
+ else if (src->family == AF_INET6)
+ dest->u.prefix6 = src->u.prefix6;
+ else if (src->family == AF_ETHERNET) {
+ memcpy(&dest->u.prefix_eth, &src->u.prefix_eth,
+ sizeof(struct ethaddr));
+ } else if (src->family == AF_EVPN) {
+ memcpy(&dest->u.prefix_evpn, &src->u.prefix_evpn,
+ sizeof(struct evpn_addr));
+ } else if (src->family == AF_UNSPEC) {
+ dest->u.lp.id = src->u.lp.id;
+ dest->u.lp.adv_router = src->u.lp.adv_router;
+ } else if (src->family == AF_FLOWSPEC) {
+ void *temp;
+ int len;
+
+ len = src->u.prefix_flowspec.prefixlen;
+ dest->u.prefix_flowspec.prefixlen =
+ src->u.prefix_flowspec.prefixlen;
+ dest->u.prefix_flowspec.family =
+ src->u.prefix_flowspec.family;
+ dest->family = src->family;
+ temp = XCALLOC(MTYPE_PREFIX_FLOWSPEC, len);
+ dest->u.prefix_flowspec.ptr = (uintptr_t)temp;
+ memcpy((void *)dest->u.prefix_flowspec.ptr,
+ (void *)src->u.prefix_flowspec.ptr, len);
+ } else {
+ flog_err(EC_LIB_DEVELOPMENT,
+ "prefix_copy(): Unknown address family %d",
+ src->family);
+ assert(0);
+ }
+}
+
+/*
+ * Return 1 if the address/netmask contained in the prefix structure
+ * is the same, and else return 0. For this routine, 'same' requires
+ * that not only the prefix length and the network part be the same,
+ * but also the host part. Thus, 10.0.0.1/8 and 10.0.0.2/8 are not
+ * the same. Note that this routine has the same return value sense
+ * as '==' (which is different from prefix_cmp).
+ */
+int prefix_same(union prefixconstptr up1, union prefixconstptr up2)
+{
+ const struct prefix *p1 = up1.p;
+ const struct prefix *p2 = up2.p;
+
+ if ((p1 && !p2) || (!p1 && p2))
+ return 0;
+
+ if (!p1 && !p2)
+ return 1;
+
+ if (p1->family == p2->family && p1->prefixlen == p2->prefixlen) {
+ if (p1->family == AF_INET)
+ if (IPV4_ADDR_SAME(&p1->u.prefix4, &p2->u.prefix4))
+ return 1;
+ if (p1->family == AF_INET6)
+ if (IPV6_ADDR_SAME(&p1->u.prefix6.s6_addr,
+ &p2->u.prefix6.s6_addr))
+ return 1;
+ if (p1->family == AF_ETHERNET)
+ if (!memcmp(&p1->u.prefix_eth, &p2->u.prefix_eth,
+ sizeof(struct ethaddr)))
+ return 1;
+ if (p1->family == AF_EVPN)
+ if (!memcmp(&p1->u.prefix_evpn, &p2->u.prefix_evpn,
+ sizeof(struct evpn_addr)))
+ return 1;
+ if (p1->family == AF_FLOWSPEC) {
+ if (p1->u.prefix_flowspec.family !=
+ p2->u.prefix_flowspec.family)
+ return 0;
+ if (p1->u.prefix_flowspec.prefixlen !=
+ p2->u.prefix_flowspec.prefixlen)
+ return 0;
+ if (!memcmp(&p1->u.prefix_flowspec.ptr,
+ &p2->u.prefix_flowspec.ptr,
+ p2->u.prefix_flowspec.prefixlen))
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Return -1/0/1 comparing the prefixes in a way that gives a full/linear
+ * order.
+ *
+ * Network prefixes are considered the same if the prefix lengths are equal
+ * and the network parts are the same. Host bits (which are considered masked
+ * by the prefix length) are not significant. Thus, 10.0.0.1/8 and
+ * 10.0.0.2/8 are considered equivalent by this routine. Note that
+ * this routine has the same return sense as strcmp (which is different
+ * from prefix_same).
+ */
+int prefix_cmp(union prefixconstptr up1, union prefixconstptr up2)
+{
+ const struct prefix *p1 = up1.p;
+ const struct prefix *p2 = up2.p;
+ int offset;
+ int shift;
+ int i;
+
+ /* Set both prefix's head pointer. */
+ const uint8_t *pp1;
+ const uint8_t *pp2;
+
+ if (p1->family != p2->family)
+ return numcmp(p1->family, p2->family);
+ if (p1->family == AF_FLOWSPEC) {
+ pp1 = (const uint8_t *)p1->u.prefix_flowspec.ptr;
+ pp2 = (const uint8_t *)p2->u.prefix_flowspec.ptr;
+
+ if (p1->u.prefix_flowspec.family !=
+ p2->u.prefix_flowspec.family)
+ return 1;
+
+ if (p1->u.prefix_flowspec.prefixlen !=
+ p2->u.prefix_flowspec.prefixlen)
+ return numcmp(p1->u.prefix_flowspec.prefixlen,
+ p2->u.prefix_flowspec.prefixlen);
+
+ offset = p1->u.prefix_flowspec.prefixlen;
+ while (offset--)
+ if (pp1[offset] != pp2[offset])
+ return numcmp(pp1[offset], pp2[offset]);
+ return 0;
+ }
+ pp1 = p1->u.val;
+ pp2 = p2->u.val;
+
+ if (p1->prefixlen != p2->prefixlen)
+ return numcmp(p1->prefixlen, p2->prefixlen);
+ offset = p1->prefixlen / PNBBY;
+ shift = p1->prefixlen % PNBBY;
+
+ i = memcmp(pp1, pp2, offset);
+ if (i)
+ return i;
+
+ /*
+ * At this point offset was the same, if we have shift
+ * that means we still have data to compare, if shift is
+ * 0 then we are at the end of the data structure
+ * and should just return, as that we will be accessing
+ * memory beyond the end of the party zone
+ */
+ if (shift)
+ return numcmp(pp1[offset] & maskbit[shift],
+ pp2[offset] & maskbit[shift]);
+
+ return 0;
+}
+
+/*
+ * Count the number of common bits in 2 prefixes. The prefix length is
+ * ignored for this function; the whole prefix is compared. If the prefix
+ * address families don't match, return -1; otherwise the return value is
+ * in range 0 ... maximum prefix length for the address family.
+ */
+int prefix_common_bits(union prefixconstptr ua, union prefixconstptr ub)
+{
+ const struct prefix *p1 = ua.p;
+ const struct prefix *p2 = ub.p;
+ int pos, bit;
+ int length = 0;
+ uint8_t xor ;
+
+ /* Set both prefix's head pointer. */
+ const uint8_t *pp1 = p1->u.val;
+ const uint8_t *pp2 = p2->u.val;
+
+ if (p1->family == AF_INET)
+ length = IPV4_MAX_BYTELEN;
+ if (p1->family == AF_INET6)
+ length = IPV6_MAX_BYTELEN;
+ if (p1->family == AF_ETHERNET)
+ length = ETH_ALEN;
+ if (p1->family == AF_EVPN)
+ length = 8 * sizeof(struct evpn_addr);
+
+ if (p1->family != p2->family || !length)
+ return -1;
+
+ for (pos = 0; pos < length; pos++)
+ if (pp1[pos] != pp2[pos])
+ break;
+ if (pos == length)
+ return pos * 8;
+
+ xor = pp1[pos] ^ pp2[pos];
+ for (bit = 0; bit < 8; bit++)
+ if (xor&(1 << (7 - bit)))
+ break;
+
+ return pos * 8 + bit;
+}
+
+/* Return prefix family type string. */
+const char *prefix_family_str(union prefixconstptr pu)
+{
+ const struct prefix *p = pu.p;
+
+ if (p->family == AF_INET)
+ return "inet";
+ if (p->family == AF_INET6)
+ return "inet6";
+ if (p->family == AF_ETHERNET)
+ return "ether";
+ if (p->family == AF_EVPN)
+ return "evpn";
+ return "unspec";
+}
+
+/* Allocate new prefix_ipv4 structure. */
+struct prefix_ipv4 *prefix_ipv4_new(void)
+{
+ struct prefix_ipv4 *p;
+
+ /* Call prefix_new to allocate a full-size struct prefix to avoid
+ problems
+ where the struct prefix_ipv4 is cast to struct prefix and unallocated
+ bytes were being referenced (e.g. in structure assignments). */
+ p = (struct prefix_ipv4 *)prefix_new();
+ p->family = AF_INET;
+ return p;
+}
+
+/* Free prefix_ipv4 structure. */
+void prefix_ipv4_free(struct prefix_ipv4 **p)
+{
+ prefix_free((struct prefix **)p);
+}
+
+/* If given string is valid return 1 else return 0 */
+int str2prefix_ipv4(const char *str, struct prefix_ipv4 *p)
+{
+ int ret;
+ int plen;
+ char *pnt;
+ char *cp;
+
+ /* Find slash inside string. */
+ pnt = strchr(str, '/');
+
+ /* String doesn't contail slash. */
+ if (pnt == NULL) {
+ /* Convert string to prefix. */
+ ret = inet_pton(AF_INET, str, &p->prefix);
+ if (ret == 0)
+ return 0;
+
+ /* If address doesn't contain slash we assume it host address.
+ */
+ p->family = AF_INET;
+ p->prefixlen = IPV4_MAX_BITLEN;
+
+ return ret;
+ } else {
+ cp = XMALLOC(MTYPE_TMP, (pnt - str) + 1);
+ memcpy(cp, str, pnt - str);
+ *(cp + (pnt - str)) = '\0';
+ ret = inet_pton(AF_INET, cp, &p->prefix);
+ XFREE(MTYPE_TMP, cp);
+ if (ret == 0)
+ return 0;
+
+ /* Get prefix length. */
+ plen = (uint8_t)atoi(++pnt);
+ if (plen > IPV4_MAX_BITLEN)
+ return 0;
+
+ p->family = AF_INET;
+ p->prefixlen = plen;
+ }
+
+ return ret;
+}
+
+/* When string format is invalid return 0. */
+int str2prefix_eth(const char *str, struct prefix_eth *p)
+{
+ int ret = 0;
+ int plen = 48;
+ char *pnt;
+ char *cp = NULL;
+ const char *str_addr = str;
+ unsigned int a[6];
+ int i;
+ bool slash = false;
+
+ if (!strcmp(str, "any")) {
+ memset(p, 0, sizeof(*p));
+ p->family = AF_ETHERNET;
+ return 1;
+ }
+
+ /* Find slash inside string. */
+ pnt = strchr(str, '/');
+
+ if (pnt) {
+ /* Get prefix length. */
+ plen = (uint8_t)atoi(++pnt);
+ if (plen > 48) {
+ ret = 0;
+ goto done;
+ }
+
+ cp = XMALLOC(MTYPE_TMP, (pnt - str) + 1);
+ memcpy(cp, str, pnt - str);
+ *(cp + (pnt - str)) = '\0';
+
+ str_addr = cp;
+ slash = true;
+ }
+
+ /* Convert string to prefix. */
+ if (sscanf(str_addr, "%2x:%2x:%2x:%2x:%2x:%2x", a + 0, a + 1, a + 2,
+ a + 3, a + 4, a + 5)
+ != 6) {
+ ret = 0;
+ goto done;
+ }
+ for (i = 0; i < 6; ++i) {
+ p->eth_addr.octet[i] = a[i] & 0xff;
+ }
+ p->prefixlen = plen;
+ p->family = AF_ETHERNET;
+
+ /*
+ * special case to allow old configurations to work
+ * Since all zero's is implicitly meant to allow
+ * a comparison to zero, let's assume
+ */
+ if (!slash && is_zero_mac(&(p->eth_addr)))
+ p->prefixlen = 0;
+
+ ret = 1;
+
+done:
+ XFREE(MTYPE_TMP, cp);
+
+ return ret;
+}
+
+/* Convert masklen into IP address's netmask (network byte order). */
+void masklen2ip(const int masklen, struct in_addr *netmask)
+{
+ assert(masklen >= 0 && masklen <= IPV4_MAX_BITLEN);
+
+ /* left shift is only defined for less than the size of the type.
+ * we unconditionally use long long in case the target platform
+ * has defined behaviour for << 32 (or has a 64-bit left shift) */
+
+ if (sizeof(unsigned long long) > 4)
+ netmask->s_addr = htonl(0xffffffffULL << (32 - masklen));
+ else
+ netmask->s_addr =
+ htonl(masklen ? 0xffffffffU << (32 - masklen) : 0);
+}
+
+/* Convert IP address's netmask into integer. We assume netmask is
+ * sequential one. Argument netmask should be network byte order. */
+uint8_t ip_masklen(struct in_addr netmask)
+{
+ uint32_t tmp = ~ntohl(netmask.s_addr);
+
+ /*
+ * clz: count leading zeroes. sadly, the behaviour of this builtin is
+ * undefined for a 0 argument, even though most CPUs give 32
+ */
+ return tmp ? __builtin_clz(tmp) : 32;
+}
+
+/* Apply mask to IPv4 prefix (network byte order). */
+void apply_mask_ipv4(struct prefix_ipv4 *p)
+{
+ struct in_addr mask;
+ masklen2ip(p->prefixlen, &mask);
+ p->prefix.s_addr &= mask.s_addr;
+}
+
+/* If prefix is 0.0.0.0/0 then return 1 else return 0. */
+int prefix_ipv4_any(const struct prefix_ipv4 *p)
+{
+ return (p->prefix.s_addr == INADDR_ANY && p->prefixlen == 0);
+}
+
+/* Allocate a new ip version 6 route */
+struct prefix_ipv6 *prefix_ipv6_new(void)
+{
+ struct prefix_ipv6 *p;
+
+ /* Allocate a full-size struct prefix to avoid problems with structure
+ size mismatches. */
+ p = (struct prefix_ipv6 *)prefix_new();
+ p->family = AF_INET6;
+ return p;
+}
+
+/* Free prefix for IPv6. */
+void prefix_ipv6_free(struct prefix_ipv6 **p)
+{
+ prefix_free((struct prefix **)p);
+}
+
+/* If given string is valid return 1 else return 0 */
+int str2prefix_ipv6(const char *str, struct prefix_ipv6 *p)
+{
+ char *pnt;
+ char *cp;
+ int ret;
+
+ pnt = strchr(str, '/');
+
+ /* If string doesn't contain `/' treat it as host route. */
+ if (pnt == NULL) {
+ ret = inet_pton(AF_INET6, str, &p->prefix);
+ if (ret == 0)
+ return 0;
+ p->prefixlen = IPV6_MAX_BITLEN;
+ } else {
+ int plen;
+
+ cp = XMALLOC(MTYPE_TMP, (pnt - str) + 1);
+ memcpy(cp, str, pnt - str);
+ *(cp + (pnt - str)) = '\0';
+ ret = inet_pton(AF_INET6, cp, &p->prefix);
+ XFREE(MTYPE_TMP, cp);
+ if (ret == 0)
+ return 0;
+ plen = (uint8_t)atoi(++pnt);
+ if (plen > IPV6_MAX_BITLEN)
+ return 0;
+ p->prefixlen = plen;
+ }
+ p->family = AF_INET6;
+
+ return ret;
+}
+
+/* Convert struct in6_addr netmask into integer.
+ * FIXME return uint8_t as ip_maskleni() does. */
+int ip6_masklen(struct in6_addr netmask)
+{
+ if (netmask.s6_addr32[0] != 0xffffffffU)
+ return __builtin_clz(~ntohl(netmask.s6_addr32[0]));
+ if (netmask.s6_addr32[1] != 0xffffffffU)
+ return __builtin_clz(~ntohl(netmask.s6_addr32[1])) + 32;
+ if (netmask.s6_addr32[2] != 0xffffffffU)
+ return __builtin_clz(~ntohl(netmask.s6_addr32[2])) + 64;
+ if (netmask.s6_addr32[3] != 0xffffffffU)
+ return __builtin_clz(~ntohl(netmask.s6_addr32[3])) + 96;
+ /* note __builtin_clz(0) is undefined */
+ return 128;
+}
+
+void masklen2ip6(const int masklen, struct in6_addr *netmask)
+{
+ assert(masklen >= 0 && masklen <= IPV6_MAX_BITLEN);
+
+ if (masklen == 0) {
+ /* note << 32 is undefined */
+ memset(netmask, 0, sizeof(*netmask));
+ } else if (masklen <= 32) {
+ netmask->s6_addr32[0] = htonl(0xffffffffU << (32 - masklen));
+ netmask->s6_addr32[1] = 0;
+ netmask->s6_addr32[2] = 0;
+ netmask->s6_addr32[3] = 0;
+ } else if (masklen <= 64) {
+ netmask->s6_addr32[0] = 0xffffffffU;
+ netmask->s6_addr32[1] = htonl(0xffffffffU << (64 - masklen));
+ netmask->s6_addr32[2] = 0;
+ netmask->s6_addr32[3] = 0;
+ } else if (masklen <= 96) {
+ netmask->s6_addr32[0] = 0xffffffffU;
+ netmask->s6_addr32[1] = 0xffffffffU;
+ netmask->s6_addr32[2] = htonl(0xffffffffU << (96 - masklen));
+ netmask->s6_addr32[3] = 0;
+ } else {
+ netmask->s6_addr32[0] = 0xffffffffU;
+ netmask->s6_addr32[1] = 0xffffffffU;
+ netmask->s6_addr32[2] = 0xffffffffU;
+ netmask->s6_addr32[3] = htonl(0xffffffffU << (128 - masklen));
+ }
+}
+
+void apply_mask_ipv6(struct prefix_ipv6 *p)
+{
+ uint8_t *pnt;
+ int index;
+ int offset;
+
+ index = p->prefixlen / 8;
+
+ if (index < 16) {
+ pnt = (uint8_t *)&p->prefix;
+ offset = p->prefixlen % 8;
+
+ pnt[index] &= maskbit[offset];
+ index++;
+
+ while (index < 16)
+ pnt[index++] = 0;
+ }
+}
+
+void apply_mask(union prefixptr pu)
+{
+ struct prefix *p = pu.p;
+
+ switch (p->family) {
+ case AF_INET:
+ apply_mask_ipv4(pu.p4);
+ break;
+ case AF_INET6:
+ apply_mask_ipv6(pu.p6);
+ break;
+ default:
+ break;
+ }
+ return;
+}
+
+/* Utility function of convert between struct prefix <=> union sockunion. */
+struct prefix *sockunion2hostprefix(const union sockunion *su,
+ struct prefix *prefix)
+{
+ if (su->sa.sa_family == AF_INET) {
+ struct prefix_ipv4 *p;
+
+ p = prefix ? (struct prefix_ipv4 *)prefix : prefix_ipv4_new();
+ p->family = AF_INET;
+ p->prefix = su->sin.sin_addr;
+ p->prefixlen = IPV4_MAX_BITLEN;
+ return (struct prefix *)p;
+ }
+ if (su->sa.sa_family == AF_INET6) {
+ struct prefix_ipv6 *p;
+
+ p = prefix ? (struct prefix_ipv6 *)prefix : prefix_ipv6_new();
+ p->family = AF_INET6;
+ p->prefixlen = IPV6_MAX_BITLEN;
+ memcpy(&p->prefix, &su->sin6.sin6_addr,
+ sizeof(struct in6_addr));
+ return (struct prefix *)p;
+ }
+ return NULL;
+}
+
+void prefix2sockunion(const struct prefix *p, union sockunion *su)
+{
+ memset(su, 0, sizeof(*su));
+
+ su->sa.sa_family = p->family;
+ if (p->family == AF_INET)
+ su->sin.sin_addr = p->u.prefix4;
+ if (p->family == AF_INET6)
+ memcpy(&su->sin6.sin6_addr, &p->u.prefix6,
+ sizeof(struct in6_addr));
+}
+
+int prefix_blen(union prefixconstptr pu)
+{
+ const struct prefix *p = pu.p;
+
+ switch (p->family) {
+ case AF_INET:
+ return IPV4_MAX_BYTELEN;
+ case AF_INET6:
+ return IPV6_MAX_BYTELEN;
+ case AF_ETHERNET:
+ return ETH_ALEN;
+ }
+ return 0;
+}
+
+/* Generic function for conversion string to struct prefix. */
+int str2prefix(const char *str, struct prefix *p)
+{
+ int ret;
+
+ if (!str || !p)
+ return 0;
+
+ /* First we try to convert string to struct prefix_ipv4. */
+ ret = str2prefix_ipv4(str, (struct prefix_ipv4 *)p);
+ if (ret)
+ return ret;
+
+ /* Next we try to convert string to struct prefix_ipv6. */
+ ret = str2prefix_ipv6(str, (struct prefix_ipv6 *)p);
+ if (ret)
+ return ret;
+
+ /* Next we try to convert string to struct prefix_eth. */
+ ret = str2prefix_eth(str, (struct prefix_eth *)p);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static const char *prefixevpn_ead2str(const struct prefix_evpn *p, char *str,
+ int size)
+{
+ uint8_t family;
+ char buf[ESI_STR_LEN];
+ char buf1[INET6_ADDRSTRLEN];
+
+ family = IS_IPADDR_V4(&p->prefix.ead_addr.ip) ? AF_INET : AF_INET6;
+ snprintf(str, size, "[%d]:[%u]:[%s]:[%d]:[%s]:[%u]",
+ p->prefix.route_type, p->prefix.ead_addr.eth_tag,
+ esi_to_str(&p->prefix.ead_addr.esi, buf, sizeof(buf)),
+ (family == AF_INET) ? IPV4_MAX_BITLEN : IPV6_MAX_BITLEN,
+ inet_ntop(family, &p->prefix.ead_addr.ip.ipaddr_v4, buf1,
+ sizeof(buf1)),
+ p->prefix.ead_addr.frag_id);
+ return str;
+}
+
+static const char *prefixevpn_macip2str(const struct prefix_evpn *p, char *str,
+ int size)
+{
+ uint8_t family;
+ char buf1[ETHER_ADDR_STRLEN];
+ char buf2[PREFIX2STR_BUFFER];
+
+ if (is_evpn_prefix_ipaddr_none(p))
+ snprintf(str, size, "[%d]:[%d]:[%d]:[%s]", p->prefix.route_type,
+ p->prefix.macip_addr.eth_tag, 8 * ETH_ALEN,
+ prefix_mac2str(&p->prefix.macip_addr.mac, buf1,
+ sizeof(buf1)));
+ else {
+ family = is_evpn_prefix_ipaddr_v4(p) ? AF_INET : AF_INET6;
+ snprintf(str, size, "[%d]:[%d]:[%d]:[%s]:[%d]:[%s]",
+ p->prefix.route_type, p->prefix.macip_addr.eth_tag,
+ 8 * ETH_ALEN,
+ prefix_mac2str(&p->prefix.macip_addr.mac, buf1,
+ sizeof(buf1)),
+ family == AF_INET ? IPV4_MAX_BITLEN : IPV6_MAX_BITLEN,
+ inet_ntop(family, &p->prefix.macip_addr.ip.ip.addr,
+ buf2, PREFIX2STR_BUFFER));
+ }
+ return str;
+}
+
+static const char *prefixevpn_imet2str(const struct prefix_evpn *p, char *str,
+ int size)
+{
+ uint8_t family;
+ char buf[INET6_ADDRSTRLEN];
+
+ family = IS_IPADDR_V4(&p->prefix.imet_addr.ip) ? AF_INET : AF_INET6;
+ snprintf(str, size, "[%d]:[%d]:[%d]:[%s]", p->prefix.route_type,
+ p->prefix.imet_addr.eth_tag,
+ (family == AF_INET) ? IPV4_MAX_BITLEN : IPV6_MAX_BITLEN,
+ inet_ntop(family, &p->prefix.imet_addr.ip.ipaddr_v4, buf,
+ sizeof(buf)));
+
+ return str;
+}
+
+static const char *prefixevpn_es2str(const struct prefix_evpn *p, char *str,
+ int size)
+{
+ uint8_t family;
+ char buf[ESI_STR_LEN];
+ char buf1[INET6_ADDRSTRLEN];
+
+ family = IS_IPADDR_V4(&p->prefix.es_addr.ip) ? AF_INET : AF_INET6;
+ snprintf(str, size, "[%d]:[%s]:[%d]:[%s]", p->prefix.route_type,
+ esi_to_str(&p->prefix.es_addr.esi, buf, sizeof(buf)),
+ (family == AF_INET) ? IPV4_MAX_BITLEN : IPV6_MAX_BITLEN,
+ inet_ntop(family, &p->prefix.es_addr.ip.ipaddr_v4, buf1,
+ sizeof(buf1)));
+
+ return str;
+}
+
+static const char *prefixevpn_prefix2str(const struct prefix_evpn *p, char *str,
+ int size)
+{
+ uint8_t family;
+ char buf[INET6_ADDRSTRLEN];
+
+ family = IS_IPADDR_V4(&p->prefix.prefix_addr.ip) ? AF_INET : AF_INET6;
+ snprintf(str, size, "[%d]:[%d]:[%d]:[%s]", p->prefix.route_type,
+ p->prefix.prefix_addr.eth_tag,
+ p->prefix.prefix_addr.ip_prefix_length,
+ inet_ntop(family, &p->prefix.prefix_addr.ip.ipaddr_v4, buf,
+ sizeof(buf)));
+ return str;
+}
+
+static const char *prefixevpn2str(const struct prefix_evpn *p, char *str,
+ int size)
+{
+ switch (p->prefix.route_type) {
+ case BGP_EVPN_AD_ROUTE:
+ return prefixevpn_ead2str(p, str, size);
+ case BGP_EVPN_MAC_IP_ROUTE:
+ return prefixevpn_macip2str(p, str, size);
+ case BGP_EVPN_IMET_ROUTE:
+ return prefixevpn_imet2str(p, str, size);
+ case BGP_EVPN_ES_ROUTE:
+ return prefixevpn_es2str(p, str, size);
+ case BGP_EVPN_IP_PREFIX_ROUTE:
+ return prefixevpn_prefix2str(p, str, size);
+ default:
+ snprintf(str, size, "Unsupported EVPN prefix");
+ break;
+ }
+ return str;
+}
+
+const char *prefix2str(union prefixconstptr pu, char *str, int size)
+{
+ const struct prefix *p = pu.p;
+ char buf[PREFIX2STR_BUFFER];
+ int byte, tmp, a, b;
+ bool z = false;
+ size_t l;
+
+ switch (p->family) {
+ case AF_INET:
+ case AF_INET6:
+ inet_ntop(p->family, &p->u.prefix, buf, sizeof(buf));
+ l = strlen(buf);
+ buf[l++] = '/';
+ byte = p->prefixlen;
+ tmp = p->prefixlen - 100;
+ if (tmp >= 0) {
+ buf[l++] = '1';
+ z = true;
+ byte = tmp;
+ }
+ b = byte % 10;
+ a = byte / 10;
+ if (a || z)
+ buf[l++] = '0' + a;
+ buf[l++] = '0' + b;
+ buf[l] = '\0';
+ strlcpy(str, buf, size);
+ break;
+
+ case AF_ETHERNET:
+ snprintf(str, size, "%s/%d",
+ prefix_mac2str(&p->u.prefix_eth, buf, sizeof(buf)),
+ p->prefixlen);
+ break;
+
+ case AF_EVPN:
+ prefixevpn2str((const struct prefix_evpn *)p, str, size);
+ break;
+
+ case AF_FLOWSPEC:
+ strlcpy(str, "FS prefix", size);
+ break;
+
+ default:
+ strlcpy(str, "UNK prefix", size);
+ break;
+ }
+
+ return str;
+}
+
+static ssize_t prefixhost2str(struct fbuf *fbuf, union prefixconstptr pu)
+{
+ const struct prefix *p = pu.p;
+ char buf[PREFIX2STR_BUFFER];
+
+ switch (p->family) {
+ case AF_INET:
+ case AF_INET6:
+ inet_ntop(p->family, &p->u.prefix, buf, sizeof(buf));
+ return bputs(fbuf, buf);
+
+ case AF_ETHERNET:
+ prefix_mac2str(&p->u.prefix_eth, buf, sizeof(buf));
+ return bputs(fbuf, buf);
+
+ default:
+ return bprintfrr(fbuf, "{prefix.af=%dPF}", p->family);
+ }
+}
+
+void prefix_mcast_inet4_dump(const char *onfail, struct in_addr addr,
+ char *buf, int buf_size)
+{
+ int save_errno = errno;
+
+ if (addr.s_addr == INADDR_ANY)
+ strlcpy(buf, "*", buf_size);
+ else {
+ if (!inet_ntop(AF_INET, &addr, buf, buf_size)) {
+ if (onfail)
+ snprintf(buf, buf_size, "%s", onfail);
+ }
+ }
+
+ errno = save_errno;
+}
+
+const char *prefix_sg2str(const struct prefix_sg *sg, char *sg_str)
+{
+ char src_str[INET_ADDRSTRLEN];
+ char grp_str[INET_ADDRSTRLEN];
+
+ prefix_mcast_inet4_dump("<src?>", sg->src, src_str, sizeof(src_str));
+ prefix_mcast_inet4_dump("<grp?>", sg->grp, grp_str, sizeof(grp_str));
+ snprintf(sg_str, PREFIX_SG_STR_LEN, "(%s,%s)", src_str, grp_str);
+
+ return sg_str;
+}
+
+struct prefix *prefix_new(void)
+{
+ struct prefix *p;
+
+ p = XCALLOC(MTYPE_PREFIX, sizeof(*p));
+ return p;
+}
+
+void prefix_free_lists(void *arg)
+{
+ struct prefix *p = arg;
+
+ prefix_free(&p);
+}
+
+/* Free prefix structure. */
+void prefix_free(struct prefix **p)
+{
+ XFREE(MTYPE_PREFIX, *p);
+}
+
+/* Utility function to convert ipv4 prefixes to Classful prefixes */
+void apply_classful_mask_ipv4(struct prefix_ipv4 *p)
+{
+
+ uint32_t destination;
+
+ destination = ntohl(p->prefix.s_addr);
+
+ if (p->prefixlen == IPV4_MAX_BITLEN)
+ ;
+ /* do nothing for host routes */
+ else if (IN_CLASSC(destination)) {
+ p->prefixlen = 24;
+ apply_mask_ipv4(p);
+ } else if (IN_CLASSB(destination)) {
+ p->prefixlen = 16;
+ apply_mask_ipv4(p);
+ } else {
+ p->prefixlen = 8;
+ apply_mask_ipv4(p);
+ }
+}
+
+in_addr_t ipv4_broadcast_addr(in_addr_t hostaddr, int masklen)
+{
+ struct in_addr mask;
+
+ masklen2ip(masklen, &mask);
+ return (masklen != IPV4_MAX_BITLEN - 1)
+ ?
+ /* normal case */
+ (hostaddr | ~mask.s_addr)
+ :
+ /* For prefix 31 return 255.255.255.255 (RFC3021) */
+ htonl(0xFFFFFFFF);
+}
+
+/* Utility function to convert ipv4 netmask to prefixes
+ ex.) "1.1.0.0" "255.255.0.0" => "1.1.0.0/16"
+ ex.) "1.0.0.0" NULL => "1.0.0.0/8" */
+int netmask_str2prefix_str(const char *net_str, const char *mask_str,
+ char *prefix_str, size_t prefix_str_len)
+{
+ struct in_addr network;
+ struct in_addr mask;
+ uint8_t prefixlen;
+ uint32_t destination;
+ int ret;
+
+ ret = inet_aton(net_str, &network);
+ if (!ret)
+ return 0;
+
+ if (mask_str) {
+ ret = inet_aton(mask_str, &mask);
+ if (!ret)
+ return 0;
+
+ prefixlen = ip_masklen(mask);
+ } else {
+ destination = ntohl(network.s_addr);
+
+ if (network.s_addr == INADDR_ANY)
+ prefixlen = 0;
+ else if (IN_CLASSC(destination))
+ prefixlen = 24;
+ else if (IN_CLASSB(destination))
+ prefixlen = 16;
+ else if (IN_CLASSA(destination))
+ prefixlen = 8;
+ else
+ return 0;
+ }
+
+ snprintf(prefix_str, prefix_str_len, "%s/%d", net_str, prefixlen);
+
+ return 1;
+}
+
+/* converts to internal representation of mac address
+ * returns 1 on success, 0 otherwise
+ * format accepted: AA:BB:CC:DD:EE:FF
+ * if mac parameter is null, then check only
+ */
+int prefix_str2mac(const char *str, struct ethaddr *mac)
+{
+ unsigned int a[6];
+ int i;
+
+ if (!str)
+ return 0;
+
+ if (sscanf(str, "%2x:%2x:%2x:%2x:%2x:%2x", a + 0, a + 1, a + 2, a + 3,
+ a + 4, a + 5)
+ != 6) {
+ /* error in incoming str length */
+ return 0;
+ }
+ /* valid mac address */
+ if (!mac)
+ return 1;
+ for (i = 0; i < 6; ++i)
+ mac->octet[i] = a[i] & 0xff;
+ return 1;
+}
+
+char *prefix_mac2str(const struct ethaddr *mac, char *buf, int size)
+{
+ char *ptr;
+
+ if (!mac)
+ return NULL;
+ if (!buf)
+ ptr = XMALLOC(MTYPE_TMP, ETHER_ADDR_STRLEN * sizeof(char));
+ else {
+ assert(size >= ETHER_ADDR_STRLEN);
+ ptr = buf;
+ }
+ snprintf(ptr, (ETHER_ADDR_STRLEN), "%02x:%02x:%02x:%02x:%02x:%02x",
+ (uint8_t)mac->octet[0], (uint8_t)mac->octet[1],
+ (uint8_t)mac->octet[2], (uint8_t)mac->octet[3],
+ (uint8_t)mac->octet[4], (uint8_t)mac->octet[5]);
+ return ptr;
+}
+
+unsigned prefix_hash_key(const void *pp)
+{
+ struct prefix copy;
+
+ if (((struct prefix *)pp)->family == AF_FLOWSPEC) {
+ uint32_t len;
+ void *temp;
+
+ /* make sure *all* unused bits are zero,
+ * particularly including alignment /
+ * padding and unused prefix bytes.
+ */
+ memset(&copy, 0, sizeof(copy));
+ prefix_copy(&copy, (struct prefix *)pp);
+ len = jhash((void *)copy.u.prefix_flowspec.ptr,
+ copy.u.prefix_flowspec.prefixlen,
+ 0x55aa5a5a);
+ temp = (void *)copy.u.prefix_flowspec.ptr;
+ XFREE(MTYPE_PREFIX_FLOWSPEC, temp);
+ copy.u.prefix_flowspec.ptr = (uintptr_t)NULL;
+ return len;
+ }
+ /* make sure *all* unused bits are zero, particularly including
+ * alignment /
+ * padding and unused prefix bytes. */
+ memset(&copy, 0, sizeof(copy));
+ prefix_copy(&copy, (struct prefix *)pp);
+ return jhash(&copy,
+ offsetof(struct prefix, u.prefix) + PSIZE(copy.prefixlen),
+ 0x55aa5a5a);
+}
+
+/* converts to internal representation of esi
+ * returns 1 on success, 0 otherwise
+ * format accepted: aa:aa:aa:aa:aa:aa:aa:aa:aa:aa
+ * if esi parameter is null, then check only
+ */
+int str_to_esi(const char *str, esi_t *esi)
+{
+ int i;
+ unsigned int a[ESI_BYTES];
+
+ if (!str)
+ return 0;
+
+ if (sscanf(str, "%2x:%2x:%2x:%2x:%2x:%2x:%2x:%2x:%2x:%2x",
+ a + 0, a + 1, a + 2, a + 3,
+ a + 4, a + 5, a + 6, a + 7,
+ a + 8, a + 9)
+ != ESI_BYTES) {
+ /* error in incoming str length */
+ return 0;
+ }
+
+ /* valid ESI */
+ if (!esi)
+ return 1;
+ for (i = 0; i < ESI_BYTES; ++i)
+ esi->val[i] = a[i] & 0xff;
+ return 1;
+}
+
+char *esi_to_str(const esi_t *esi, char *buf, int size)
+{
+ char *ptr;
+
+ if (!esi)
+ return NULL;
+ if (!buf)
+ ptr = XMALLOC(MTYPE_TMP, ESI_STR_LEN * sizeof(char));
+ else {
+ assert(size >= ESI_STR_LEN);
+ ptr = buf;
+ }
+
+ snprintf(ptr, ESI_STR_LEN,
+ "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
+ esi->val[0], esi->val[1], esi->val[2],
+ esi->val[3], esi->val[4], esi->val[5],
+ esi->val[6], esi->val[7], esi->val[8],
+ esi->val[9]);
+ return ptr;
+}
+
+char *evpn_es_df_alg2str(uint8_t df_alg, char *buf, int buf_len)
+{
+ switch (df_alg) {
+ case EVPN_MH_DF_ALG_SERVICE_CARVING:
+ snprintf(buf, buf_len, "service-carving");
+ break;
+
+ case EVPN_MH_DF_ALG_HRW:
+ snprintf(buf, buf_len, "HRW");
+ break;
+
+ case EVPN_MH_DF_ALG_PREF:
+ snprintf(buf, buf_len, "preference");
+ break;
+
+ default:
+ snprintf(buf, buf_len, "unknown %u", df_alg);
+ break;
+ }
+
+ return buf;
+}
+
+bool ipv4_unicast_valid(const struct in_addr *addr)
+{
+ in_addr_t ip = ntohl(addr->s_addr);
+
+ if (IPV4_CLASS_D(ip))
+ return false;
+
+ if (IPV4_CLASS_E(ip)) {
+ if (cmd_allow_reserved_ranges_get())
+ return true;
+ else
+ return false;
+ }
+
+ return true;
+}
+
+printfrr_ext_autoreg_p("EA", printfrr_ea);
+static ssize_t printfrr_ea(struct fbuf *buf, struct printfrr_eargs *ea,
+ const void *ptr)
+{
+ const struct ethaddr *mac = ptr;
+ char cbuf[ETHER_ADDR_STRLEN];
+
+ if (!mac)
+ return bputs(buf, "(null)");
+
+ /* need real length even if buffer is too short */
+ prefix_mac2str(mac, cbuf, sizeof(cbuf));
+ return bputs(buf, cbuf);
+}
+
+printfrr_ext_autoreg_p("IA", printfrr_ia);
+static ssize_t printfrr_ia(struct fbuf *buf, struct printfrr_eargs *ea,
+ const void *ptr)
+{
+ const struct ipaddr *ipa = ptr;
+ char cbuf[INET6_ADDRSTRLEN];
+ bool use_star = false;
+
+ if (ea->fmt[0] == 's') {
+ use_star = true;
+ ea->fmt++;
+ }
+
+ if (!ipa)
+ return bputs(buf, "(null)");
+
+ if (use_star) {
+ struct in_addr zero4 = {};
+ struct in6_addr zero6 = {};
+
+ switch (ipa->ipa_type) {
+ case IPADDR_V4:
+ if (!memcmp(&ipa->ip.addr, &zero4, sizeof(zero4)))
+ return bputch(buf, '*');
+ break;
+
+ case IPADDR_V6:
+ if (!memcmp(&ipa->ip.addr, &zero6, sizeof(zero6)))
+ return bputch(buf, '*');
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ ipaddr2str(ipa, cbuf, sizeof(cbuf));
+ return bputs(buf, cbuf);
+}
+
+printfrr_ext_autoreg_p("I4", printfrr_i4);
+static ssize_t printfrr_i4(struct fbuf *buf, struct printfrr_eargs *ea,
+ const void *ptr)
+{
+ char cbuf[INET_ADDRSTRLEN];
+ bool use_star = false;
+ struct in_addr zero = {};
+
+ if (ea->fmt[0] == 's') {
+ use_star = true;
+ ea->fmt++;
+ }
+
+ if (!ptr)
+ return bputs(buf, "(null)");
+
+ if (use_star && !memcmp(ptr, &zero, sizeof(zero)))
+ return bputch(buf, '*');
+
+ inet_ntop(AF_INET, ptr, cbuf, sizeof(cbuf));
+ return bputs(buf, cbuf);
+}
+
+printfrr_ext_autoreg_p("I6", printfrr_i6);
+static ssize_t printfrr_i6(struct fbuf *buf, struct printfrr_eargs *ea,
+ const void *ptr)
+{
+ char cbuf[INET6_ADDRSTRLEN];
+ bool use_star = false;
+ struct in6_addr zero = {};
+
+ if (ea->fmt[0] == 's') {
+ use_star = true;
+ ea->fmt++;
+ }
+
+ if (!ptr)
+ return bputs(buf, "(null)");
+
+ if (use_star && !memcmp(ptr, &zero, sizeof(zero)))
+ return bputch(buf, '*');
+
+ inet_ntop(AF_INET6, ptr, cbuf, sizeof(cbuf));
+ return bputs(buf, cbuf);
+}
+
+printfrr_ext_autoreg_p("FX", printfrr_pfx);
+static ssize_t printfrr_pfx(struct fbuf *buf, struct printfrr_eargs *ea,
+ const void *ptr)
+{
+ bool host_only = false;
+
+ if (ea->fmt[0] == 'h') {
+ ea->fmt++;
+ host_only = true;
+ }
+
+ if (!ptr)
+ return bputs(buf, "(null)");
+
+ if (host_only)
+ return prefixhost2str(buf, (struct prefix *)ptr);
+ else {
+ char cbuf[PREFIX_STRLEN];
+
+ prefix2str(ptr, cbuf, sizeof(cbuf));
+ return bputs(buf, cbuf);
+ }
+}
+
+printfrr_ext_autoreg_p("PSG4", printfrr_psg);
+static ssize_t printfrr_psg(struct fbuf *buf, struct printfrr_eargs *ea,
+ const void *ptr)
+{
+ const struct prefix_sg *sg = ptr;
+ ssize_t ret = 0;
+
+ if (!sg)
+ return bputs(buf, "(null)");
+
+ if (sg->src.s_addr == INADDR_ANY)
+ ret += bputs(buf, "(*,");
+ else
+ ret += bprintfrr(buf, "(%pI4,", &sg->src);
+
+ if (sg->grp.s_addr == INADDR_ANY)
+ ret += bputs(buf, "*)");
+ else
+ ret += bprintfrr(buf, "%pI4)", &sg->grp);
+
+ return ret;
+}
diff --git a/lib/prefix.h b/lib/prefix.h
new file mode 100644
index 0000000..b904311
--- /dev/null
+++ b/lib/prefix.h
@@ -0,0 +1,686 @@
+/*
+ * Prefix structure.
+ * Copyright (C) 1998 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 _ZEBRA_PREFIX_H
+#define _ZEBRA_PREFIX_H
+
+#ifdef GNU_LINUX
+#include <net/ethernet.h>
+#else
+#include <netinet/if_ether.h>
+#endif
+#include "sockunion.h"
+#include "ipaddr.h"
+#include "compiler.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef ETH_ALEN
+#define ETH_ALEN 6
+#endif
+
+/* EVPN route types. */
+typedef enum {
+ BGP_EVPN_AD_ROUTE = 1, /* Ethernet Auto-Discovery (A-D) route */
+ BGP_EVPN_MAC_IP_ROUTE, /* MAC/IP Advertisement route */
+ BGP_EVPN_IMET_ROUTE, /* Inclusive Multicast Ethernet Tag route */
+ BGP_EVPN_ES_ROUTE, /* Ethernet Segment route */
+ BGP_EVPN_IP_PREFIX_ROUTE, /* IP Prefix route */
+} bgp_evpn_route_type;
+
+/* value of first byte of ESI */
+#define ESI_TYPE_ARBITRARY 0 /* */
+#define ESI_TYPE_LACP 1 /* <> */
+#define ESI_TYPE_BRIDGE 2 /* <Root bridge Mac-6B>:<Root Br Priority-2B>:00 */
+#define ESI_TYPE_MAC 3 /* <Syst Mac Add-6B>:<Local Discriminator Value-3B> */
+#define ESI_TYPE_ROUTER 4 /* <RouterId-4B>:<Local Discriminator Value-4B> */
+#define ESI_TYPE_AS 5 /* <AS-4B>:<Local Discriminator Value-4B> */
+
+#define MAX_ESI {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
+
+
+#define EVPN_ETH_TAG_BYTES 4
+#define ESI_BYTES 10
+#define ESI_STR_LEN (3 * ESI_BYTES)
+#define EVPN_DF_ALG_STR_LEN 24
+
+/* Maximum number of VTEPs per-ES -
+ * XXX - temporary limit for allocating strings etc.
+ */
+#define ES_VTEP_MAX_CNT 10
+#define ES_VTEP_LIST_STR_SZ (ES_VTEP_MAX_CNT * 16)
+
+#define ETHER_ADDR_STRLEN (3*ETH_ALEN)
+/*
+ * there isn't a portable ethernet address type. We define our
+ * own to simplify internal handling
+ */
+struct ethaddr {
+ uint8_t octet[ETH_ALEN];
+} __attribute__((packed));
+
+
+/* length is the number of valuable bits of prefix structure
+* 18 bytes is current length in structure, if address is ipv4
+* 30 bytes is in case of ipv6
+*/
+#define PREFIX_LEN_ROUTE_TYPE_5_IPV4 (18*8)
+#define PREFIX_LEN_ROUTE_TYPE_5_IPV6 (30*8)
+
+typedef struct esi_t_ {
+ uint8_t val[ESI_BYTES];
+} esi_t;
+
+struct evpn_ead_addr {
+ esi_t esi;
+ uint32_t eth_tag;
+ struct ipaddr ip;
+ uint16_t frag_id;
+};
+
+struct evpn_macip_addr {
+ uint32_t eth_tag;
+ uint8_t ip_prefix_length;
+ struct ethaddr mac;
+ struct ipaddr ip;
+};
+
+struct evpn_imet_addr {
+ uint32_t eth_tag;
+ uint8_t ip_prefix_length;
+ struct ipaddr ip;
+};
+
+struct evpn_es_addr {
+ esi_t esi;
+ uint8_t ip_prefix_length;
+ struct ipaddr ip;
+};
+
+struct evpn_prefix_addr {
+ uint32_t eth_tag;
+ uint8_t ip_prefix_length;
+ struct ipaddr ip;
+};
+
+/* EVPN address (RFC 7432) */
+struct evpn_addr {
+ uint8_t route_type;
+ union {
+ struct evpn_ead_addr _ead_addr;
+ struct evpn_macip_addr _macip_addr;
+ struct evpn_imet_addr _imet_addr;
+ struct evpn_es_addr _es_addr;
+ struct evpn_prefix_addr _prefix_addr;
+ } u;
+#define ead_addr u._ead_addr
+#define macip_addr u._macip_addr
+#define imet_addr u._imet_addr
+#define es_addr u._es_addr
+#define prefix_addr u._prefix_addr
+};
+
+/*
+ * A struct prefix contains an address family, a prefix length, and an
+ * address. This can represent either a 'network prefix' as defined
+ * by CIDR, where the 'host bits' of the prefix are 0
+ * (e.g. AF_INET:10.0.0.0/8), or an address and netmask
+ * (e.g. AF_INET:10.0.0.9/8), such as might be configured on an
+ * interface.
+ */
+
+/* different OSes use different names */
+#if defined(AF_PACKET)
+#define AF_ETHERNET AF_PACKET
+#else
+#if defined(AF_LINK)
+#define AF_ETHERNET AF_LINK
+#endif
+#endif
+
+/* The 'family' in the prefix structure is internal to FRR and need not
+ * map to standard OS AF_ definitions except where needed for interacting
+ * with the kernel. However, AF_ definitions are currently in use and
+ * prevalent across the code. Define a new FRR-specific AF for EVPN to
+ * distinguish between 'ethernet' (MAC-only) and 'evpn' prefixes and
+ * ensure it does not conflict with any OS AF_ definition.
+ */
+#if !defined(AF_EVPN)
+#define AF_EVPN (AF_MAX + 1)
+#endif
+
+#if !defined(AF_FLOWSPEC)
+#define AF_FLOWSPEC (AF_MAX + 2)
+#endif
+
+struct flowspec_prefix {
+ uint8_t family;
+ uint16_t prefixlen; /* length in bytes */
+ uintptr_t ptr;
+};
+
+/* FRR generic prefix structure. */
+struct prefix {
+ uint8_t family;
+ uint16_t prefixlen;
+ union {
+ uint8_t prefix;
+ struct in_addr prefix4;
+ struct in6_addr prefix6;
+ struct {
+ struct in_addr id;
+ struct in_addr adv_router;
+ } lp;
+ struct ethaddr prefix_eth; /* AF_ETHERNET */
+ uint8_t val[16];
+ uint32_t val32[4];
+ uintptr_t ptr;
+ struct evpn_addr prefix_evpn; /* AF_EVPN */
+ struct flowspec_prefix prefix_flowspec; /* AF_FLOWSPEC */
+ } u __attribute__((aligned(8)));
+};
+
+/* IPv4 prefix structure. */
+struct prefix_ipv4 {
+ uint8_t family;
+ uint16_t prefixlen;
+ struct in_addr prefix __attribute__((aligned(8)));
+};
+
+/* IPv6 prefix structure. */
+struct prefix_ipv6 {
+ uint8_t family;
+ uint16_t prefixlen;
+ struct in6_addr prefix __attribute__((aligned(8)));
+};
+
+struct prefix_ls {
+ uint8_t family;
+ uint16_t prefixlen;
+ struct in_addr id __attribute__((aligned(8)));
+ struct in_addr adv_router;
+};
+
+/* Prefix for routing distinguisher. */
+struct prefix_rd {
+ uint8_t family;
+ uint16_t prefixlen;
+ uint8_t val[8] __attribute__((aligned(8)));
+};
+
+/* Prefix for ethernet. */
+struct prefix_eth {
+ uint8_t family;
+ uint16_t prefixlen;
+ struct ethaddr eth_addr __attribute__((aligned(8))); /* AF_ETHERNET */
+};
+
+/* EVPN prefix structure. */
+struct prefix_evpn {
+ uint8_t family;
+ uint16_t prefixlen;
+ struct evpn_addr prefix __attribute__((aligned(8)));
+};
+
+static inline int is_evpn_prefix_ipaddr_none(const struct prefix_evpn *evp)
+{
+ if (evp->prefix.route_type == BGP_EVPN_AD_ROUTE)
+ return IS_IPADDR_NONE(&(evp)->prefix.ead_addr.ip);
+ if (evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE)
+ return IS_IPADDR_NONE(&(evp)->prefix.macip_addr.ip);
+ if (evp->prefix.route_type == BGP_EVPN_IMET_ROUTE)
+ return IS_IPADDR_NONE(&(evp)->prefix.imet_addr.ip);
+ if (evp->prefix.route_type == BGP_EVPN_ES_ROUTE)
+ return IS_IPADDR_NONE(&(evp)->prefix.es_addr.ip);
+ if (evp->prefix.route_type == BGP_EVPN_IP_PREFIX_ROUTE)
+ return IS_IPADDR_NONE(&(evp)->prefix.prefix_addr.ip);
+ return 0;
+}
+
+static inline int is_evpn_prefix_ipaddr_v4(const struct prefix_evpn *evp)
+{
+ if (evp->prefix.route_type == BGP_EVPN_AD_ROUTE)
+ return IS_IPADDR_V4(&(evp)->prefix.ead_addr.ip);
+ if (evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE)
+ return IS_IPADDR_V4(&(evp)->prefix.macip_addr.ip);
+ if (evp->prefix.route_type == BGP_EVPN_IMET_ROUTE)
+ return IS_IPADDR_V4(&(evp)->prefix.imet_addr.ip);
+ if (evp->prefix.route_type == BGP_EVPN_ES_ROUTE)
+ return IS_IPADDR_V4(&(evp)->prefix.es_addr.ip);
+ if (evp->prefix.route_type == BGP_EVPN_IP_PREFIX_ROUTE)
+ return IS_IPADDR_V4(&(evp)->prefix.prefix_addr.ip);
+ return 0;
+}
+
+static inline int is_evpn_prefix_ipaddr_v6(const struct prefix_evpn *evp)
+{
+ if (evp->prefix.route_type == BGP_EVPN_AD_ROUTE)
+ return IS_IPADDR_V6(&(evp)->prefix.ead_addr.ip);
+ if (evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE)
+ return IS_IPADDR_V6(&(evp)->prefix.macip_addr.ip);
+ if (evp->prefix.route_type == BGP_EVPN_IMET_ROUTE)
+ return IS_IPADDR_V6(&(evp)->prefix.imet_addr.ip);
+ if (evp->prefix.route_type == BGP_EVPN_ES_ROUTE)
+ return IS_IPADDR_V6(&(evp)->prefix.es_addr.ip);
+ if (evp->prefix.route_type == BGP_EVPN_IP_PREFIX_ROUTE)
+ return IS_IPADDR_V6(&(evp)->prefix.prefix_addr.ip);
+ return 0;
+}
+
+/* Prefix for a Flowspec entry */
+struct prefix_fs {
+ uint8_t family;
+ uint16_t prefixlen; /* unused */
+ struct flowspec_prefix prefix __attribute__((aligned(8)));
+};
+
+struct prefix_sg {
+ uint8_t family;
+ uint16_t prefixlen;
+ struct in_addr src __attribute__((aligned(8)));
+ struct in_addr grp;
+};
+
+/* helper to get type safety/avoid casts on calls
+ * (w/o this, functions accepting all prefix types need casts on the caller
+ * side, which strips type safety since the cast will accept any pointer
+ * type.)
+ */
+#ifndef __cplusplus
+#define prefixtype(uname, typename, fieldname) \
+ typename *fieldname;
+#define TRANSPARENT_UNION __attribute__((transparent_union))
+#else
+#define prefixtype(uname, typename, fieldname) \
+ typename *fieldname; \
+ uname(typename *x) { this->fieldname = x; }
+#define TRANSPARENT_UNION
+#endif
+
+union prefixptr {
+ prefixtype(prefixptr, struct prefix, p)
+ prefixtype(prefixptr, struct prefix_ipv4, p4)
+ prefixtype(prefixptr, struct prefix_ipv6, p6)
+ prefixtype(prefixptr, struct prefix_evpn, evp)
+ prefixtype(prefixptr, struct prefix_fs, fs)
+ prefixtype(prefixptr, struct prefix_rd, rd)
+} TRANSPARENT_UNION;
+
+union prefixconstptr {
+ prefixtype(prefixconstptr, const struct prefix, p)
+ prefixtype(prefixconstptr, const struct prefix_ipv4, p4)
+ prefixtype(prefixconstptr, const struct prefix_ipv6, p6)
+ prefixtype(prefixconstptr, const struct prefix_evpn, evp)
+ prefixtype(prefixconstptr, const struct prefix_fs, fs)
+ prefixtype(prefixconstptr, const struct prefix_rd, rd)
+} TRANSPARENT_UNION;
+
+#ifndef INET_ADDRSTRLEN
+#define INET_ADDRSTRLEN 16
+#endif /* INET_ADDRSTRLEN */
+
+#ifndef INET6_ADDRSTRLEN
+/* dead:beef:dead:beef:dead:beef:dead:beef + \0 */
+#define INET6_ADDRSTRLEN 46
+#endif /* INET6_ADDRSTRLEN */
+
+#ifndef INET6_BUFSIZ
+#define INET6_BUFSIZ 53
+#endif /* INET6_BUFSIZ */
+
+/* Maximum string length of the result of prefix2str */
+#define PREFIX_STRLEN 80
+
+/*
+ * Longest possible length of a (S,G) string is 34 bytes
+ * 123.123.123.123 = 15 * 2
+ * (,) = 3
+ * NULL Character at end = 1
+ * (123.123.123.123,123.123.123.123)
+ */
+#define PREFIX_SG_STR_LEN 34
+
+/* Max bit/byte length of IPv4 address. */
+#define IPV4_MAX_BYTELEN 4
+#define IPV4_MAX_BITLEN 32
+#define IPV4_ADDR_CMP(D,S) memcmp ((D), (S), IPV4_MAX_BYTELEN)
+
+static inline bool ipv4_addr_same(const struct in_addr *a,
+ const struct in_addr *b)
+{
+ return (a->s_addr == b->s_addr);
+}
+#define IPV4_ADDR_SAME(A,B) ipv4_addr_same((A), (B))
+
+static inline void ipv4_addr_copy(struct in_addr *dst,
+ const struct in_addr *src)
+{
+ dst->s_addr = src->s_addr;
+}
+#define IPV4_ADDR_COPY(D,S) ipv4_addr_copy((D), (S))
+
+#define IPV4_NET0(a) ((((uint32_t)(a)) & 0xff000000) == 0x00000000)
+#define IPV4_NET127(a) ((((uint32_t)(a)) & 0xff000000) == 0x7f000000)
+#define IPV4_LINKLOCAL(a) ((((uint32_t)(a)) & 0xffff0000) == 0xa9fe0000)
+#define IPV4_CLASS_D(a) ((((uint32_t)(a)) & 0xf0000000) == 0xe0000000)
+#define IPV4_CLASS_E(a) ((((uint32_t)(a)) & 0xf0000000) == 0xf0000000)
+#define IPV4_CLASS_DE(a) ((((uint32_t)(a)) & 0xe0000000) == 0xe0000000)
+#define IPV4_MC_LINKLOCAL(a) ((((uint32_t)(a)) & 0xffffff00) == 0xe0000000)
+
+/* Max bit/byte length of IPv6 address. */
+#define IPV6_MAX_BYTELEN 16
+#define IPV6_MAX_BITLEN 128
+#define IPV6_ADDR_CMP(D,S) memcmp ((D), (S), IPV6_MAX_BYTELEN)
+#define IPV6_ADDR_SAME(D,S) (memcmp ((D), (S), IPV6_MAX_BYTELEN) == 0)
+#define IPV6_ADDR_COPY(D,S) memcpy ((D), (S), IPV6_MAX_BYTELEN)
+
+/* Count prefix size from mask length */
+#define PSIZE(a) (((a) + 7) / (8))
+
+#define BSIZE(a) ((a) * (8))
+
+/* Prefix's family member. */
+#define PREFIX_FAMILY(p) ((p)->family)
+
+/* glibc defines s6_addr32 to __in6_u.__u6_addr32 if __USE_{MISC || GNU} */
+#ifndef s6_addr32
+#define s6_addr32 __u6_addr.__u6_addr32
+#endif /*s6_addr32*/
+
+/* Prototypes. */
+extern int str2family(const char *);
+extern int afi2family(afi_t);
+extern afi_t family2afi(int);
+extern const char *family2str(int family);
+extern const char *safi2str(safi_t safi);
+extern const char *afi2str(afi_t afi);
+
+static inline afi_t prefix_afi(union prefixconstptr pu)
+{
+ return family2afi(pu.p->family);
+}
+
+/*
+ * Check bit of the prefix.
+ *
+ * prefix
+ * byte buffer
+ *
+ * bit_index
+ * which bit to fetch from byte buffer, 0 indexed.
+ */
+extern unsigned int prefix_bit(const uint8_t *prefix, const uint16_t bit_index);
+
+extern struct prefix *prefix_new(void);
+extern void prefix_free(struct prefix **p);
+/*
+ * Function to handle prefix_free being used as a del function.
+ */
+extern void prefix_free_lists(void *arg);
+extern const char *prefix_family_str(union prefixconstptr pu);
+extern int prefix_blen(union prefixconstptr pu);
+extern int str2prefix(const char *, struct prefix *);
+
+#define PREFIX2STR_BUFFER PREFIX_STRLEN
+
+extern void prefix_mcast_inet4_dump(const char *onfail, struct in_addr addr,
+ char *buf, int buf_size);
+extern const char *prefix_sg2str(const struct prefix_sg *sg, char *str);
+extern const char *prefix2str(union prefixconstptr, char *, int);
+extern int evpn_type5_prefix_match(const struct prefix *evpn_pfx,
+ const struct prefix *match_pfx);
+extern int prefix_match(union prefixconstptr unet, union prefixconstptr upfx);
+extern int prefix_match_network_statement(union prefixconstptr unet,
+ union prefixconstptr upfx);
+extern int prefix_same(union prefixconstptr ua, union prefixconstptr ub);
+extern int prefix_cmp(union prefixconstptr ua, union prefixconstptr ub);
+extern int prefix_common_bits(union prefixconstptr ua, union prefixconstptr ub);
+extern void prefix_copy(union prefixptr udst, union prefixconstptr usrc);
+extern void apply_mask(union prefixptr pu);
+
+#ifdef __clang_analyzer__
+/* clang-SA doesn't understand transparent unions, making it think that the
+ * target of prefix_copy is uninitialized. So just memset the target.
+ * cf. https://bugs.llvm.org/show_bug.cgi?id=42811
+ */
+#define prefix_copy(a, b) ({ memset(a, 0, sizeof(*a)); prefix_copy(a, b); })
+#endif
+
+extern struct prefix *sockunion2hostprefix(const union sockunion *,
+ struct prefix *p);
+extern void prefix2sockunion(const struct prefix *, union sockunion *);
+
+extern int str2prefix_eth(const char *, struct prefix_eth *);
+
+extern struct prefix_ipv4 *prefix_ipv4_new(void);
+extern void prefix_ipv4_free(struct prefix_ipv4 **p);
+extern int str2prefix_ipv4(const char *, struct prefix_ipv4 *);
+extern void apply_mask_ipv4(struct prefix_ipv4 *);
+
+extern int prefix_ipv4_any(const struct prefix_ipv4 *);
+extern void apply_classful_mask_ipv4(struct prefix_ipv4 *);
+
+extern uint8_t ip_masklen(struct in_addr);
+extern void masklen2ip(const int, struct in_addr *);
+/* given the address of a host on a network and the network mask length,
+ * calculate the broadcast address for that network;
+ * special treatment for /31 according to RFC3021 section 3.3 */
+extern in_addr_t ipv4_broadcast_addr(in_addr_t hostaddr, int masklen);
+
+extern int netmask_str2prefix_str(const char *, const char *, char *, size_t);
+
+extern struct prefix_ipv6 *prefix_ipv6_new(void);
+extern void prefix_ipv6_free(struct prefix_ipv6 **p);
+extern int str2prefix_ipv6(const char *, struct prefix_ipv6 *);
+extern void apply_mask_ipv6(struct prefix_ipv6 *);
+
+extern int ip6_masklen(struct in6_addr);
+extern void masklen2ip6(const int, struct in6_addr *);
+
+extern int is_zero_mac(const struct ethaddr *mac);
+extern bool is_mcast_mac(const struct ethaddr *mac);
+extern bool is_bcast_mac(const struct ethaddr *mac);
+extern int prefix_str2mac(const char *str, struct ethaddr *mac);
+extern char *prefix_mac2str(const struct ethaddr *mac, char *buf, int size);
+
+extern unsigned prefix_hash_key(const void *pp);
+
+extern int str_to_esi(const char *str, esi_t *esi);
+extern char *esi_to_str(const esi_t *esi, char *buf, int size);
+extern char *evpn_es_df_alg2str(uint8_t df_alg, char *buf, int buf_len);
+extern void prefix_evpn_hexdump(const struct prefix_evpn *p);
+extern bool ipv4_unicast_valid(const struct in_addr *addr);
+
+static inline int ipv6_martian(const struct in6_addr *addr)
+{
+ struct in6_addr localhost_addr;
+
+ inet_pton(AF_INET6, "::1", &localhost_addr);
+
+ if (IPV6_ADDR_SAME(&localhost_addr, addr))
+ return 1;
+
+ return 0;
+}
+
+extern int macstr2prefix_evpn(const char *str, struct prefix_evpn *p);
+
+/* NOTE: This routine expects the address argument in network byte order. */
+static inline bool ipv4_martian(const struct in_addr *addr)
+{
+ in_addr_t ip = ntohl(addr->s_addr);
+
+ if (IPV4_NET0(ip) || IPV4_NET127(ip) || !ipv4_unicast_valid(addr)) {
+ return true;
+ }
+ return false;
+}
+
+static inline bool is_default_prefix4(const struct prefix_ipv4 *p)
+{
+ return p && p->family == AF_INET && p->prefixlen == 0
+ && p->prefix.s_addr == INADDR_ANY;
+}
+
+static inline bool is_default_prefix6(const struct prefix_ipv6 *p)
+{
+ return p && p->family == AF_INET6 && p->prefixlen == 0
+ && memcmp(&p->prefix, &in6addr_any, sizeof(struct in6_addr))
+ == 0;
+}
+
+static inline bool is_default_prefix(const struct prefix *p)
+{
+ if (p == NULL)
+ return false;
+
+ switch (p->family) {
+ case AF_INET:
+ return is_default_prefix4((const struct prefix_ipv4 *)p);
+ case AF_INET6:
+ return is_default_prefix6((const struct prefix_ipv6 *)p);
+ }
+
+ return false;
+}
+
+static inline int is_host_route(const struct prefix *p)
+{
+ if (p->family == AF_INET)
+ return (p->prefixlen == IPV4_MAX_BITLEN);
+ else if (p->family == AF_INET6)
+ return (p->prefixlen == IPV6_MAX_BITLEN);
+ return 0;
+}
+
+static inline int is_default_host_route(const struct prefix *p)
+{
+ if (p->family == AF_INET) {
+ return (p->u.prefix4.s_addr == INADDR_ANY &&
+ p->prefixlen == IPV4_MAX_BITLEN);
+ } else if (p->family == AF_INET6) {
+ return ((!memcmp(&p->u.prefix6, &in6addr_any,
+ sizeof(struct in6_addr))) &&
+ p->prefixlen == IPV6_MAX_BITLEN);
+ }
+ return 0;
+}
+
+static inline bool is_ipv6_global_unicast(const struct in6_addr *p)
+{
+ if (IN6_IS_ADDR_UNSPECIFIED(p) || IN6_IS_ADDR_LOOPBACK(p) ||
+ IN6_IS_ADDR_LINKLOCAL(p) || IN6_IS_ADDR_MULTICAST(p))
+ return false;
+
+ return true;
+}
+
+/* IPv6 scope values, usable for IPv4 too (cf. below) */
+/* clang-format off */
+enum {
+ /* 0: reserved */
+ MCAST_SCOPE_IFACE = 0x1,
+ MCAST_SCOPE_LINK = 0x2,
+ MCAST_SCOPE_REALM = 0x3,
+ MCAST_SCOPE_ADMIN = 0x4,
+ MCAST_SCOPE_SITE = 0x5,
+ /* 6-7: unassigned */
+ MCAST_SCOPE_ORG = 0x8,
+ /* 9-d: unassigned */
+ MCAST_SCOPE_GLOBAL = 0xe,
+ /* f: reserved */
+};
+/* clang-format on */
+
+static inline uint8_t ipv6_mcast_scope(const struct in6_addr *addr)
+{
+ return addr->s6_addr[1] & 0xf;
+}
+
+static inline bool ipv6_mcast_nofwd(const struct in6_addr *addr)
+{
+ return (addr->s6_addr[1] & 0xf) <= MCAST_SCOPE_LINK;
+}
+
+static inline bool ipv6_mcast_ssm(const struct in6_addr *addr)
+{
+ uint32_t bits = ntohl(addr->s6_addr32[0]);
+
+ /* ff3x:0000::/32 */
+ return (bits & 0xfff0ffff) == 0xff300000;
+}
+
+static inline uint8_t ipv4_mcast_scope(const struct in_addr *addr)
+{
+ uint32_t bits = ntohl(addr->s_addr);
+
+ /* 224.0.0.0/24 - link scope */
+ if ((bits & 0xffffff00) == 0xe0000000)
+ return MCAST_SCOPE_LINK;
+ /* 239.0.0.0/8 - org scope */
+ if ((bits & 0xff000000) == 0xef000000)
+ return MCAST_SCOPE_ORG;
+
+ return MCAST_SCOPE_GLOBAL;
+}
+
+static inline bool ipv4_mcast_nofwd(const struct in_addr *addr)
+{
+ uint32_t bits = ntohl(addr->s_addr);
+
+ /* 224.0.0.0/24 */
+ return (bits & 0xffffff00) == 0xe0000000;
+}
+
+static inline bool ipv4_mcast_ssm(const struct in_addr *addr)
+{
+ uint32_t bits = ntohl(addr->s_addr);
+
+ /* 232.0.0.0/8 */
+ return (bits & 0xff000000) == 0xe8000000;
+}
+
+#ifdef _FRR_ATTRIBUTE_PRINTFRR
+#pragma FRR printfrr_ext "%pEA" (struct ethaddr *)
+
+#pragma FRR printfrr_ext "%pI4" (struct in_addr *)
+#pragma FRR printfrr_ext "%pI4" (in_addr_t *)
+
+#pragma FRR printfrr_ext "%pI6" (struct in6_addr *)
+
+#pragma FRR printfrr_ext "%pFX" (struct prefix *)
+#pragma FRR printfrr_ext "%pFX" (struct prefix_ipv4 *)
+#pragma FRR printfrr_ext "%pFX" (struct prefix_ipv6 *)
+#pragma FRR printfrr_ext "%pFX" (struct prefix_eth *)
+#pragma FRR printfrr_ext "%pFX" (struct prefix_evpn *)
+#pragma FRR printfrr_ext "%pFX" (struct prefix_fs *)
+#pragma FRR printfrr_ext "%pRD" (struct prefix_rd *)
+
+#pragma FRR printfrr_ext "%pPSG4" (struct prefix_sg *)
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ZEBRA_PREFIX_H */
diff --git a/lib/printf/README b/lib/printf/README
new file mode 100644
index 0000000..ac28364
--- /dev/null
+++ b/lib/printf/README
@@ -0,0 +1,9 @@
+This is the printf implementation from FreeBSD. It was imported on 2019-05-12,
+from SVN revision 347514 (but the code hadn't been touched for 2 years before
+that.)
+
+Please don't reindent or otherwise mangle the files to make importing fixes
+easy (not that there are significant changes likely to happen...)
+
+The changes to this code are published under FreeBSD's license as listed in the
+file headers. If you change license, please make that as obvious as possible.
diff --git a/lib/printf/glue.c b/lib/printf/glue.c
new file mode 100644
index 0000000..6e39c2d
--- /dev/null
+++ b/lib/printf/glue.c
@@ -0,0 +1,297 @@
+/*
+ * Copyright (c) 2019 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+#include <string.h>
+#include <wchar.h>
+
+#include "printfrr.h"
+#include "printflocal.h"
+
+ssize_t bprintfrr(struct fbuf *out, const char *fmt, ...)
+{
+ ssize_t ret;
+ va_list ap;
+
+ va_start(ap, fmt);
+ ret = vbprintfrr(out, fmt, ap);
+ va_end(ap);
+ return ret;
+}
+
+ssize_t vsnprintfrr(char *out, size_t outsz, const char *fmt, va_list ap)
+{
+ struct fbuf fbb = { .buf = out, .pos = out, .len = outsz - 1, };
+ struct fbuf *fb = (out && outsz) ? &fbb : NULL;
+ ssize_t ret;
+
+ ret = vbprintfrr(fb, fmt, ap);
+ if (fb)
+ fb->pos[0] = '\0';
+ return ret;
+}
+
+ssize_t snprintfrr(char *out, size_t outsz, const char *fmt, ...)
+{
+ struct fbuf fbb = { .buf = out, .pos = out, .len = outsz - 1, };
+ struct fbuf *fb = (out && outsz) ? &fbb : NULL;
+ ssize_t ret;
+ va_list ap;
+
+ va_start(ap, fmt);
+ ret = vbprintfrr(fb, fmt, ap);
+ va_end(ap);
+ if (fb)
+ fb->pos[0] = '\0';
+ return ret;
+}
+
+ssize_t vcsnprintfrr(char *out, size_t outsz, const char *fmt, va_list ap)
+{
+ if (!out || !outsz)
+ return vbprintfrr(NULL, fmt, ap);
+
+ struct fbuf fbb = { .buf = out, .pos = out, .len = outsz - 1, };
+ ssize_t ret;
+ size_t pos;
+
+ pos = strnlen(out, outsz);
+ fbb.pos += pos;
+
+ ret = vbprintfrr(&fbb, fmt, ap);
+ fbb.pos[0] = '\0';
+ return ret >= 0 ? ret + (ssize_t)pos : ret;
+}
+
+ssize_t csnprintfrr(char *out, size_t outsz, const char *fmt, ...)
+{
+ ssize_t ret;
+ va_list ap;
+
+ va_start(ap, fmt);
+ ret = vcsnprintfrr(out, outsz, fmt, ap);
+ va_end(ap);
+ return ret;
+}
+
+char *vasnprintfrr(struct memtype *mt, char *out, size_t outsz, const char *fmt,
+ va_list ap)
+{
+ struct fbuf fb = { .buf = out, .pos = out, .len = outsz - 1, };
+ ssize_t len;
+ va_list ap2;
+ char *ret = out;
+
+ va_copy(ap2, ap);
+ len = vbprintfrr(&fb, fmt, ap);
+ if (len < 0) {
+ va_end(ap2);
+ /* error = malformed format string => try something useful */
+ return qstrdup(mt, fmt);
+ }
+
+ if ((size_t)len >= outsz - 1) {
+ ret = qmalloc(mt, len + 1);
+ fb.buf = fb.pos = ret;
+ fb.len = len;
+
+ vbprintfrr(&fb, fmt, ap2);
+ }
+
+ va_end(ap2);
+ ret[len] = '\0';
+ return ret;
+}
+
+char *asnprintfrr(struct memtype *mt, char *out, size_t outsz, const char *fmt,
+ ...)
+{
+ va_list ap;
+ char *ret;
+
+ va_start(ap, fmt);
+ ret = vasnprintfrr(mt, out, outsz, fmt, ap);
+ va_end(ap);
+ return ret;
+}
+
+char *vasprintfrr(struct memtype *mt, const char *fmt, va_list ap)
+{
+ char buf[256];
+ char *ret;
+
+ ret = vasnprintfrr(mt, buf, sizeof(buf), fmt, ap);
+
+ if (ret == buf)
+ ret = qstrdup(mt, ret);
+ return ret;
+}
+
+char *asprintfrr(struct memtype *mt, const char *fmt, ...)
+{
+ char buf[256];
+ va_list ap;
+ char *ret;
+
+ va_start(ap, fmt);
+ ret = vasnprintfrr(mt, buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ if (ret == buf)
+ ret = qstrdup(mt, ret);
+ return ret;
+}
+
+/* Q: WTF?
+ * A: since printf should be reasonably fast (think debugging logs), the idea
+ * here is to keep things close by each other in a cacheline. That's why
+ * ext_quick just has the first 2 characters of an extension, and we do a
+ * nice linear continuous sweep. Only if we find something, we go do more
+ * expensive things.
+ *
+ * Q: doesn't this need a mutex/lock?
+ * A: theoretically, yes, but that's quite expensive and I rather elide that
+ * necessity by putting down some usage rules. Just call this at startup
+ * while singlethreaded and all is fine. Ideally, just use constructors
+ * (and make sure dlopen() doesn't mess things up...)
+ */
+#define MAXEXT 64
+
+struct ext_quick {
+ char fmt[2];
+};
+
+static uint8_t ext_offsets[26] __attribute__((aligned(32)));
+static struct ext_quick entries[MAXEXT] __attribute__((aligned(64)));
+static const struct printfrr_ext *exts[MAXEXT] __attribute__((aligned(64)));
+
+void printfrr_ext_reg(const struct printfrr_ext *ext)
+{
+ uint8_t o;
+ ptrdiff_t i;
+
+ if (!printfrr_ext_char(ext->match[0]))
+ return;
+
+ o = ext->match[0] - 'A';
+ for (i = ext_offsets[o];
+ i < MAXEXT && entries[i].fmt[0] &&
+ memcmp(entries[i].fmt, ext->match, 2) < 0;
+ i++)
+ ;
+ if (i == MAXEXT)
+ return;
+ for (o++; o <= 'Z' - 'A'; o++)
+ ext_offsets[o]++;
+
+ memmove(entries + i + 1, entries + i,
+ (MAXEXT - i - 1) * sizeof(entries[0]));
+ memmove(exts + i + 1, exts + i,
+ (MAXEXT - i - 1) * sizeof(exts[0]));
+
+ memcpy(entries[i].fmt, ext->match, 2);
+ exts[i] = ext;
+}
+
+ssize_t printfrr_extp(struct fbuf *buf, struct printfrr_eargs *ea,
+ const void *ptr)
+{
+ const char *fmt = ea->fmt;
+ const struct printfrr_ext *ext;
+ size_t i;
+
+ for (i = ext_offsets[fmt[0] - 'A']; i < MAXEXT; i++) {
+ if (!entries[i].fmt[0] || entries[i].fmt[0] > fmt[0])
+ return -1;
+ if (entries[i].fmt[1] && entries[i].fmt[1] != fmt[1])
+ continue;
+ ext = exts[i];
+ if (!ext->print_ptr)
+ continue;
+ if (strncmp(ext->match, fmt, strlen(ext->match)))
+ continue;
+ ea->fmt += strlen(ext->match);
+ return ext->print_ptr(buf, ea, ptr);
+ }
+ return -1;
+}
+
+ssize_t printfrr_exti(struct fbuf *buf, struct printfrr_eargs *ea,
+ uintmax_t num)
+{
+ const char *fmt = ea->fmt;
+ const struct printfrr_ext *ext;
+ size_t i;
+
+ for (i = ext_offsets[fmt[0] - 'A']; i < MAXEXT; i++) {
+ if (!entries[i].fmt[0] || entries[i].fmt[0] > fmt[0])
+ return -1;
+ if (entries[i].fmt[1] && entries[i].fmt[1] != fmt[1])
+ continue;
+ ext = exts[i];
+ if (!ext->print_int)
+ continue;
+ if (strncmp(ext->match, fmt, strlen(ext->match)))
+ continue;
+ ea->fmt += strlen(ext->match);
+ return ext->print_int(buf, ea, num);
+ }
+ return -1;
+}
+
+printfrr_ext_autoreg_p("FB", printfrr_fb);
+static ssize_t printfrr_fb(struct fbuf *out, struct printfrr_eargs *ea,
+ const void *ptr)
+{
+ const struct fbuf *in = ptr;
+ ptrdiff_t copy_len;
+
+ if (!in)
+ return bputs(out, "NULL");
+
+ if (out) {
+ copy_len = MIN(in->pos - in->buf,
+ out->buf + out->len - out->pos);
+ if (copy_len > 0) {
+ memcpy(out->pos, in->buf, copy_len);
+ out->pos += copy_len;
+ }
+ }
+
+ return in->pos - in->buf;
+}
+
+printfrr_ext_autoreg_p("VA", printfrr_va);
+static ssize_t printfrr_va(struct fbuf *buf, struct printfrr_eargs *ea,
+ const void *ptr)
+{
+ const struct va_format *vaf = ptr;
+ va_list ap;
+
+ if (!vaf || !vaf->fmt || !vaf->va)
+ return bputs(buf, "NULL");
+
+ /* make sure we don't alter the data passed in - especially since
+ * bprintfrr (and thus this) might be called on the same format twice,
+ * when allocating a larger buffer in asnprintfrr()
+ */
+ va_copy(ap, *vaf->va);
+ return vbprintfrr(buf, vaf->fmt, ap);
+}
diff --git a/lib/printf/printf-pos.c b/lib/printf/printf-pos.c
new file mode 100644
index 0000000..ac775be
--- /dev/null
+++ b/lib/printf/printf-pos.c
@@ -0,0 +1,791 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_SYS_CDEFS_H
+#include <sys/cdefs.h>
+#endif
+
+/*
+ * This is the code responsible for handling positional arguments
+ * (%m$ and %m$.n$) for vfprintf() and vfwprintf().
+ */
+
+#include <sys/types.h>
+
+#include <limits.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <wchar.h>
+
+#include "printflocal.h"
+
+#ifdef NL_ARGMAX
+#define MAX_POSARG NL_ARGMAX
+#else
+#define MAX_POSARG 65536
+#endif
+
+/*
+ * Type ids for argument type table.
+ */
+enum typeid {
+ T_UNUSED, TP_SHORT, T_INT, T_U_INT, TP_INT,
+ T_LONG, T_U_LONG, TP_LONG, T_LLONG, T_U_LLONG, TP_LLONG,
+ T_PTRDIFFT, TP_PTRDIFFT, T_SSIZET, T_SIZET, TP_SSIZET,
+ T_INT64T, T_UINT64T, T_INTMAXT, T_UINTMAXT, TP_INTMAXT, TP_VOID,
+ TP_CHAR, TP_SCHAR, T_DOUBLE, T_LONG_DOUBLE, T_WINT, TP_WCHAR
+};
+
+/* An expandable array of types. */
+struct typetable {
+ enum typeid *table; /* table of types */
+ enum typeid stattable[STATIC_ARG_TBL_SIZE];
+ u_int tablesize; /* current size of type table */
+ u_int tablemax; /* largest used index in table */
+ u_int nextarg; /* 1-based argument index */
+};
+
+static int __grow_type_table(struct typetable *);
+static void build_arg_table (struct typetable *, va_list, union arg **);
+
+/*
+ * Initialize a struct typetable.
+ */
+static inline void
+inittypes(struct typetable *types)
+{
+ u_int n;
+
+ types->table = types->stattable;
+ types->tablesize = STATIC_ARG_TBL_SIZE;
+ types->tablemax = 0;
+ types->nextarg = 1;
+ for (n = 0; n < STATIC_ARG_TBL_SIZE; n++)
+ types->table[n] = T_UNUSED;
+}
+
+/*
+ * struct typetable destructor.
+ */
+static inline void
+freetypes(struct typetable *types)
+{
+
+ if (types->table != types->stattable)
+ free (types->table);
+}
+
+/*
+ * Ensure that there is space to add a new argument type to the type table.
+ * Expand the table if necessary. Returns 0 on success.
+ */
+static inline int
+_ensurespace(struct typetable *types)
+{
+
+ if (types->nextarg >= types->tablesize) {
+ if (__grow_type_table(types))
+ return -1;
+ }
+ if (types->nextarg > types->tablemax)
+ types->tablemax = types->nextarg;
+ return 0;
+}
+
+/*
+ * Add an argument type to the table, expanding if necessary.
+ * Returns 0 on success.
+ */
+static inline int
+addtype(struct typetable *types, enum typeid type)
+{
+
+ if (_ensurespace(types))
+ return -1;
+ types->table[types->nextarg++] = type;
+ return 0;
+}
+
+static inline int
+addsarg(struct typetable *types, int flags)
+{
+
+ if (_ensurespace(types))
+ return -1;
+ if (flags & LONGDBL)
+ types->table[types->nextarg++] = T_INT64T;
+ else if (flags & INTMAXT)
+ types->table[types->nextarg++] = T_INTMAXT;
+ else if (flags & SIZET)
+ types->table[types->nextarg++] = T_SSIZET;
+ else if (flags & PTRDIFFT)
+ types->table[types->nextarg++] = T_PTRDIFFT;
+ else if (flags & LLONGINT)
+ types->table[types->nextarg++] = T_LLONG;
+ else if (flags & LONGINT)
+ types->table[types->nextarg++] = T_LONG;
+ else
+ types->table[types->nextarg++] = T_INT;
+ return 0;
+}
+
+static inline int
+adduarg(struct typetable *types, int flags)
+{
+
+ if (_ensurespace(types))
+ return -1;
+ if (flags & LONGDBL)
+ types->table[types->nextarg++] = T_UINT64T;
+ else if (flags & INTMAXT)
+ types->table[types->nextarg++] = T_UINTMAXT;
+ else if (flags & SIZET)
+ types->table[types->nextarg++] = T_SIZET;
+ else if (flags & PTRDIFFT)
+ types->table[types->nextarg++] = T_SIZET;
+ else if (flags & LLONGINT)
+ types->table[types->nextarg++] = T_U_LLONG;
+ else if (flags & LONGINT)
+ types->table[types->nextarg++] = T_U_LONG;
+ else
+ types->table[types->nextarg++] = T_U_INT;
+ return 0;
+}
+
+/*
+ * Add * arguments to the type array.
+ */
+static inline int
+addaster(struct typetable *types, char **fmtp)
+{
+ char *cp;
+ u_int n2;
+
+ n2 = 0;
+ cp = *fmtp;
+ while (is_digit(*cp)) {
+ n2 = 10 * n2 + to_digit(*cp);
+ cp++;
+ }
+ if (*cp == '$') {
+ u_int hold = types->nextarg;
+ types->nextarg = n2;
+ if (addtype(types, T_INT))
+ return -1;
+ types->nextarg = hold;
+ *fmtp = ++cp;
+ } else {
+ if (addtype(types, T_INT))
+ return -1;
+ }
+ return 0;
+}
+
+#ifdef WCHAR_SUPPORT
+static inline int
+addwaster(struct typetable *types, wchar_t **fmtp)
+{
+ wchar_t *cp;
+ u_int n2;
+
+ n2 = 0;
+ cp = *fmtp;
+ while (is_digit(*cp)) {
+ n2 = 10 * n2 + to_digit(*cp);
+ cp++;
+ }
+ if (*cp == '$') {
+ u_int hold = types->nextarg;
+ types->nextarg = n2;
+ if (addtype(types, T_INT))
+ return -1;
+ types->nextarg = hold;
+ *fmtp = ++cp;
+ } else {
+ if (addtype(types, T_INT))
+ return -1;
+ }
+ return 0;
+}
+#endif /* WCHAR_SUPPORT */
+
+/*
+ * Find all arguments when a positional parameter is encountered. Returns a
+ * table, indexed by argument number, of pointers to each arguments. The
+ * initial argument table should be an array of STATIC_ARG_TBL_SIZE entries.
+ * It will be replaces with a malloc-ed one if it overflows.
+ * Returns 0 on success. On failure, returns nonzero and sets errno.
+ */
+int
+_frr_find_arguments (const char *fmt0, va_list ap, union arg **argtable)
+{
+ char *fmt; /* format string */
+ int ch; /* character from fmt */
+ u_int n; /* handy integer (short term usage) */
+ int error;
+ int flags; /* flags as above */
+ struct typetable types; /* table of types */
+
+ fmt = (char *)fmt0;
+ inittypes(&types);
+ error = 0;
+
+ /*
+ * Scan the format for conversions (`%' character).
+ */
+ for (;;) {
+ while ((ch = *fmt) != '\0' && ch != '%')
+ fmt++;
+ if (ch == '\0')
+ goto done;
+ fmt++; /* skip over '%' */
+
+ flags = 0;
+
+rflag: ch = *fmt++;
+reswitch: switch (ch) {
+ case ' ':
+ case '#':
+ goto rflag;
+ case '*':
+ if ((error = addaster(&types, &fmt)))
+ goto error;
+ goto rflag;
+ case '-':
+ case '+':
+ case '\'':
+ goto rflag;
+ case '.':
+ if ((ch = *fmt++) == '*') {
+ if ((error = addaster(&types, &fmt)))
+ goto error;
+ goto rflag;
+ }
+ while (is_digit(ch)) {
+ ch = *fmt++;
+ }
+ goto reswitch;
+ case '0':
+ goto rflag;
+ case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ n = 0;
+ do {
+ n = 10 * n + to_digit(ch);
+ /* Detect overflow */
+ if (n > MAX_POSARG) {
+ error = -1;
+ goto error;
+ }
+ ch = *fmt++;
+ } while (is_digit(ch));
+ if (ch == '$') {
+ types.nextarg = n;
+ goto rflag;
+ }
+ goto reswitch;
+ case 'L':
+ flags |= LONGDBL;
+ goto rflag;
+ case 'h':
+ if (flags & SHORTINT) {
+ flags &= ~SHORTINT;
+ flags |= CHARINT;
+ } else
+ flags |= SHORTINT;
+ goto rflag;
+ case 'j':
+ flags |= INTMAXT;
+ goto rflag;
+ case 'l':
+ if (flags & LONGINT) {
+ flags &= ~LONGINT;
+ flags |= LLONGINT;
+ } else
+ flags |= LONGINT;
+ goto rflag;
+ case 'q':
+ flags |= LLONGINT; /* not necessarily */
+ goto rflag;
+ case 't':
+ flags |= PTRDIFFT;
+ goto rflag;
+ case 'z':
+ flags |= SIZET;
+ goto rflag;
+ case 'C':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 'c':
+ error = addtype(&types,
+ (flags & LONGINT) ? T_WINT : T_INT);
+ if (error)
+ goto error;
+ break;
+ case 'D':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 'd':
+ case 'i':
+ if ((error = addsarg(&types, flags)))
+ goto error;
+ break;
+#ifndef NO_FLOATING_POINT
+ case 'a':
+ case 'A':
+ case 'e':
+ case 'E':
+ case 'f':
+ case 'g':
+ case 'G':
+ error = addtype(&types,
+ (flags & LONGDBL) ? T_LONG_DOUBLE : T_DOUBLE);
+ if (error)
+ goto error;
+ break;
+#endif /* !NO_FLOATING_POINT */
+#ifdef DANGEROUS_PERCENT_N
+ case 'n':
+ if (flags & INTMAXT)
+ error = addtype(&types, TP_INTMAXT);
+ else if (flags & PTRDIFFT)
+ error = addtype(&types, TP_PTRDIFFT);
+ else if (flags & SIZET)
+ error = addtype(&types, TP_SSIZET);
+ else if (flags & LLONGINT)
+ error = addtype(&types, TP_LLONG);
+ else if (flags & LONGINT)
+ error = addtype(&types, TP_LONG);
+ else if (flags & SHORTINT)
+ error = addtype(&types, TP_SHORT);
+ else if (flags & CHARINT)
+ error = addtype(&types, TP_SCHAR);
+ else
+ error = addtype(&types, TP_INT);
+ if (error)
+ goto error;
+ continue; /* no output */
+#endif
+ case 'O':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 'o':
+ if ((error = adduarg(&types, flags)))
+ goto error;
+ break;
+ case 'p':
+ if ((error = addtype(&types, TP_VOID)))
+ goto error;
+ break;
+ case 'S':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 's':
+ error = addtype(&types,
+ (flags & LONGINT) ? TP_WCHAR : TP_CHAR);
+ if (error)
+ goto error;
+ break;
+ case 'U':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 'u':
+ case 'X':
+ case 'x':
+ if ((error = adduarg(&types, flags)))
+ goto error;
+ break;
+ default: /* "%?" prints ?, unless ? is NUL */
+ if (ch == '\0')
+ goto done;
+ break;
+ }
+ }
+done:
+ build_arg_table(&types, ap, argtable);
+error:
+ freetypes(&types);
+ return (error || *argtable == NULL);
+}
+
+#ifdef WCHAR_SUPPORT
+/* wchar version of __find_arguments. */
+int
+_frr_find_warguments (const wchar_t *fmt0, va_list ap, union arg **argtable)
+{
+ wchar_t *fmt; /* format string */
+ wchar_t ch; /* character from fmt */
+ u_int n; /* handy integer (short term usage) */
+ int error;
+ int flags; /* flags as above */
+ struct typetable types; /* table of types */
+
+ fmt = (wchar_t *)fmt0;
+ inittypes(&types);
+ error = 0;
+
+ /*
+ * Scan the format for conversions (`%' character).
+ */
+ for (;;) {
+ while ((ch = *fmt) != '\0' && ch != '%')
+ fmt++;
+ if (ch == '\0')
+ goto done;
+ fmt++; /* skip over '%' */
+
+ flags = 0;
+
+rflag: ch = *fmt++;
+reswitch: switch (ch) {
+ case ' ':
+ case '#':
+ goto rflag;
+ case '*':
+ if ((error = addwaster(&types, &fmt)))
+ goto error;
+ goto rflag;
+ case '-':
+ case '+':
+ case '\'':
+ goto rflag;
+ case '.':
+ if ((ch = *fmt++) == '*') {
+ if ((error = addwaster(&types, &fmt)))
+ goto error;
+ goto rflag;
+ }
+ while (is_digit(ch)) {
+ ch = *fmt++;
+ }
+ goto reswitch;
+ case '0':
+ goto rflag;
+ case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ n = 0;
+ do {
+ n = 10 * n + to_digit(ch);
+ /* Detect overflow */
+ if (n > MAX_POSARG) {
+ error = -1;
+ goto error;
+ }
+ ch = *fmt++;
+ } while (is_digit(ch));
+ if (ch == '$') {
+ types.nextarg = n;
+ goto rflag;
+ }
+ goto reswitch;
+ case 'L':
+ flags |= LONGDBL;
+ goto rflag;
+ case 'h':
+ if (flags & SHORTINT) {
+ flags &= ~SHORTINT;
+ flags |= CHARINT;
+ } else
+ flags |= SHORTINT;
+ goto rflag;
+ case 'j':
+ flags |= INTMAXT;
+ goto rflag;
+ case 'l':
+ if (flags & LONGINT) {
+ flags &= ~LONGINT;
+ flags |= LLONGINT;
+ } else
+ flags |= LONGINT;
+ goto rflag;
+ case 'q':
+ flags |= LLONGINT; /* not necessarily */
+ goto rflag;
+ case 't':
+ flags |= PTRDIFFT;
+ goto rflag;
+ case 'z':
+ flags |= SIZET;
+ goto rflag;
+ case 'C':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 'c':
+ error = addtype(&types,
+ (flags & LONGINT) ? T_WINT : T_INT);
+ if (error)
+ goto error;
+ break;
+ case 'D':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 'd':
+ case 'i':
+ if ((error = addsarg(&types, flags)))
+ goto error;
+ break;
+#ifndef NO_FLOATING_POINT
+ case 'a':
+ case 'A':
+ case 'e':
+ case 'E':
+ case 'f':
+ case 'g':
+ case 'G':
+ error = addtype(&types,
+ (flags & LONGDBL) ? T_LONG_DOUBLE : T_DOUBLE);
+ if (error)
+ goto error;
+ break;
+#endif /* !NO_FLOATING_POINT */
+#ifdef DANGEROUS_PERCENT_N
+ case 'n':
+ if (flags & INTMAXT)
+ error = addtype(&types, TP_INTMAXT);
+ else if (flags & PTRDIFFT)
+ error = addtype(&types, TP_PTRDIFFT);
+ else if (flags & SIZET)
+ error = addtype(&types, TP_SSIZET);
+ else if (flags & LLONGINT)
+ error = addtype(&types, TP_LLONG);
+ else if (flags & LONGINT)
+ error = addtype(&types, TP_LONG);
+ else if (flags & SHORTINT)
+ error = addtype(&types, TP_SHORT);
+ else if (flags & CHARINT)
+ error = addtype(&types, TP_SCHAR);
+ else
+ error = addtype(&types, TP_INT);
+ if (error)
+ goto error;
+ continue; /* no output */
+#endif
+ case 'O':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 'o':
+ if ((error = adduarg(&types, flags)))
+ goto error;
+ break;
+ case 'p':
+ if ((error = addtype(&types, TP_VOID)))
+ goto error;
+ break;
+ case 'S':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 's':
+ error = addtype(&types,
+ (flags & LONGINT) ? TP_WCHAR : TP_CHAR);
+ if (error)
+ goto error;
+ break;
+ case 'U':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 'u':
+ case 'X':
+ case 'x':
+ if ((error = adduarg(&types, flags)))
+ goto error;
+ break;
+ default: /* "%?" prints ?, unless ? is NUL */
+ if (ch == '\0')
+ goto done;
+ break;
+ }
+ }
+done:
+ build_arg_table(&types, ap, argtable);
+error:
+ freetypes(&types);
+ return (error || *argtable == NULL);
+}
+#endif /* WCHAR_SUPPORT */
+
+/*
+ * Increase the size of the type table. Returns 0 on success.
+ */
+static int
+__grow_type_table(struct typetable *types)
+{
+ enum typeid *const oldtable = types->table;
+ const int oldsize = types->tablesize;
+ enum typeid *newtable;
+ u_int n, newsize;
+
+ /* Detect overflow */
+ if (types->nextarg > MAX_POSARG)
+ return -1;
+
+ newsize = oldsize * 2;
+ if (newsize < types->nextarg + 1)
+ newsize = types->nextarg + 1;
+ if (oldsize == STATIC_ARG_TBL_SIZE) {
+ if ((newtable = malloc(newsize * sizeof(enum typeid))) == NULL)
+ return -1;
+ bcopy(oldtable, newtable, oldsize * sizeof(enum typeid));
+ } else {
+ newtable = realloc(oldtable, newsize * sizeof(enum typeid));
+ if (newtable == NULL)
+ return -1;
+ }
+ for (n = oldsize; n < newsize; n++)
+ newtable[n] = T_UNUSED;
+
+ types->table = newtable;
+ types->tablesize = newsize;
+
+ return 0;
+}
+
+/*
+ * Build the argument table from the completed type table.
+ * On malloc failure, *argtable is set to NULL.
+ */
+static void
+build_arg_table(struct typetable *types, va_list ap, union arg **argtable)
+{
+ u_int n;
+
+ if (types->tablemax >= STATIC_ARG_TBL_SIZE) {
+ *argtable = (union arg *)
+ malloc (sizeof(union arg) * (types->tablemax + 1));
+ if (*argtable == NULL)
+ return;
+ }
+
+ (*argtable) [0].intarg = 0;
+ for (n = 1; n <= types->tablemax; n++) {
+ switch (types->table[n]) {
+ case T_UNUSED: /* whoops! */
+ (*argtable) [n].intarg = va_arg (ap, int);
+ break;
+ case TP_SCHAR:
+ (*argtable) [n].pschararg = va_arg (ap, signed char *);
+ break;
+ case TP_SHORT:
+ (*argtable) [n].pshortarg = va_arg (ap, short *);
+ break;
+ case T_INT:
+ (*argtable) [n].intarg = va_arg (ap, int);
+ break;
+ case T_U_INT:
+ (*argtable) [n].uintarg = va_arg (ap, unsigned int);
+ break;
+ case TP_INT:
+ (*argtable) [n].pintarg = va_arg (ap, int *);
+ break;
+ case T_LONG:
+ (*argtable) [n].longarg = va_arg (ap, long);
+ break;
+ case T_U_LONG:
+ (*argtable) [n].ulongarg = va_arg (ap, unsigned long);
+ break;
+ case TP_LONG:
+ (*argtable) [n].plongarg = va_arg (ap, long *);
+ break;
+ case T_LLONG:
+ (*argtable) [n].longlongarg = va_arg (ap, long long);
+ break;
+ case T_U_LLONG:
+ (*argtable) [n].ulonglongarg = va_arg (ap, unsigned long long);
+ break;
+ case TP_LLONG:
+ (*argtable) [n].plonglongarg = va_arg (ap, long long *);
+ break;
+ case T_PTRDIFFT:
+ (*argtable) [n].ptrdiffarg = va_arg (ap, ptrdiff_t);
+ break;
+ case TP_PTRDIFFT:
+ (*argtable) [n].pptrdiffarg = va_arg (ap, ptrdiff_t *);
+ break;
+ case T_SIZET:
+ (*argtable) [n].sizearg = va_arg (ap, size_t);
+ break;
+ case T_SSIZET:
+ (*argtable) [n].sizearg = va_arg (ap, ssize_t);
+ break;
+ case TP_SSIZET:
+ (*argtable) [n].pssizearg = va_arg (ap, ssize_t *);
+ break;
+ case T_INTMAXT:
+ (*argtable) [n].intmaxarg = va_arg (ap, intmax_t);
+ break;
+ case T_UINTMAXT:
+ (*argtable) [n].uintmaxarg = va_arg (ap, uintmax_t);
+ break;
+ case TP_INTMAXT:
+ (*argtable) [n].pintmaxarg = va_arg (ap, intmax_t *);
+ break;
+ case T_INT64T:
+ (*argtable) [n].intmaxarg = va_arg (ap, int64_t);
+ break;
+ case T_UINT64T:
+ (*argtable) [n].uintmaxarg = va_arg (ap, uint64_t);
+ break;
+ case T_DOUBLE:
+#ifndef NO_FLOATING_POINT
+ (*argtable) [n].doublearg = va_arg (ap, double);
+#endif
+ break;
+ case T_LONG_DOUBLE:
+#ifndef NO_FLOATING_POINT
+ (*argtable) [n].longdoublearg = va_arg (ap, long double);
+#endif
+ break;
+ case TP_CHAR:
+ (*argtable) [n].pchararg = va_arg (ap, char *);
+ break;
+ case TP_VOID:
+ (*argtable) [n].pvoidarg = va_arg (ap, void *);
+ break;
+ case T_WINT:
+ (*argtable) [n].wintarg = va_arg (ap, wint_t);
+ break;
+ case TP_WCHAR:
+ (*argtable) [n].pwchararg = va_arg (ap, wchar_t *);
+ break;
+ }
+ }
+}
diff --git a/lib/printf/printfcommon.h b/lib/printf/printfcommon.h
new file mode 100644
index 0000000..5c45520
--- /dev/null
+++ b/lib/printf/printfcommon.h
@@ -0,0 +1,244 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * Copyright (c) 2011 The FreeBSD Foundation
+ * All rights reserved.
+ * Portions of this software were developed by David Chisnall
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+/*
+ * This file defines common routines used by both printf and wprintf.
+ * You must define CHAR to either char or wchar_t prior to including this.
+ */
+
+
+static CHAR *__ujtoa(uintmax_t, CHAR *, int, int, const char *);
+static CHAR *__ultoa(u_long, CHAR *, int, int, const char *);
+
+#define NIOV 8
+struct io_state {
+ struct fbuf *cb;
+ size_t avail;
+};
+
+static inline void
+io_init(struct io_state *iop, struct fbuf *cb)
+{
+ iop->cb = cb;
+ iop->avail = cb ? cb->len - (cb->pos - cb->buf) : 0;
+}
+
+/*
+ * WARNING: The buffer passed to io_print() is not copied immediately; it must
+ * remain valid until io_flush() is called.
+ */
+static inline int
+io_print(struct io_state *iop, const CHAR * __restrict ptr, size_t len)
+{
+ size_t copylen = len;
+
+ if (!iop->cb)
+ return 0;
+ if (iop->avail < copylen)
+ copylen = iop->avail;
+
+ memcpy(iop->cb->pos, ptr, copylen);
+ iop->avail -= copylen;
+ iop->cb->pos += copylen;
+ return 0;
+}
+
+/*
+ * Choose PADSIZE to trade efficiency vs. size. If larger printf
+ * fields occur frequently, increase PADSIZE and make the initialisers
+ * below longer.
+ */
+#define PADSIZE 16 /* pad chunk size */
+static const CHAR blanks[PADSIZE] =
+{' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '};
+static const CHAR zeroes[PADSIZE] =
+{'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'};
+
+/*
+ * Pad with blanks or zeroes. 'with' should point to either the blanks array
+ * or the zeroes array.
+ */
+static inline int
+io_pad(struct io_state *iop, int howmany, const CHAR * __restrict with)
+{
+ int n;
+
+ while (howmany > 0) {
+ n = (howmany >= PADSIZE) ? PADSIZE : howmany;
+ if (io_print(iop, with, n))
+ return (-1);
+ howmany -= n;
+ }
+ return (0);
+}
+
+/*
+ * Print exactly len characters of the string spanning p to ep, truncating
+ * or padding with 'with' as necessary.
+ */
+static inline int
+io_printandpad(struct io_state *iop, const CHAR *p, const CHAR *ep,
+ int len, const CHAR * __restrict with)
+{
+ int p_len;
+
+ p_len = ep - p;
+ if (p_len > len)
+ p_len = len;
+ if (p_len > 0) {
+ if (io_print(iop, p, p_len))
+ return (-1);
+ } else {
+ p_len = 0;
+ }
+ return (io_pad(iop, len - p_len, with));
+}
+
+/*
+ * Convert an unsigned long to ASCII for printf purposes, returning
+ * a pointer to the first character of the string representation.
+ * Octal numbers can be forced to have a leading zero; hex numbers
+ * use the given digits.
+ */
+static CHAR *
+__ultoa(u_long val, CHAR *endp, int base, int octzero, const char *xdigs)
+{
+ CHAR *cp = endp;
+ long sval;
+
+ /*
+ * Handle the three cases separately, in the hope of getting
+ * better/faster code.
+ */
+ switch (base) {
+ case 10:
+ if (val < 10) { /* many numbers are 1 digit */
+ *--cp = to_char(val);
+ return (cp);
+ }
+ /*
+ * On many machines, unsigned arithmetic is harder than
+ * signed arithmetic, so we do at most one unsigned mod and
+ * divide; this is sufficient to reduce the range of
+ * the incoming value to where signed arithmetic works.
+ */
+ if (val > LONG_MAX) {
+ *--cp = to_char(val % 10);
+ sval = val / 10;
+ } else
+ sval = val;
+ do {
+ *--cp = to_char(sval % 10);
+ sval /= 10;
+ } while (sval != 0);
+ break;
+
+ case 8:
+ do {
+ *--cp = to_char(val & 7);
+ val >>= 3;
+ } while (val);
+ if (octzero && *cp != '0')
+ *--cp = '0';
+ break;
+
+ case 16:
+ do {
+ *--cp = xdigs[val & 15];
+ val >>= 4;
+ } while (val);
+ break;
+
+ default: /* oops */
+ abort();
+ }
+ return (cp);
+}
+
+/* Identical to __ultoa, but for intmax_t. */
+static CHAR *
+__ujtoa(uintmax_t val, CHAR *endp, int base, int octzero, const char *xdigs)
+{
+ CHAR *cp = endp;
+ intmax_t sval;
+
+ /* quick test for small values; __ultoa is typically much faster */
+ /* (perhaps instead we should run until small, then call __ultoa?) */
+ if (val <= ULONG_MAX)
+ return (__ultoa((u_long)val, endp, base, octzero, xdigs));
+ switch (base) {
+ case 10:
+ if (val < 10) {
+ *--cp = to_char(val % 10);
+ return (cp);
+ }
+ if (val > INTMAX_MAX) {
+ *--cp = to_char(val % 10);
+ sval = val / 10;
+ } else
+ sval = val;
+ do {
+ *--cp = to_char(sval % 10);
+ sval /= 10;
+ } while (sval != 0);
+ break;
+
+ case 8:
+ do {
+ *--cp = to_char(val & 7);
+ val >>= 3;
+ } while (val);
+ if (octzero && *cp != '0')
+ *--cp = '0';
+ break;
+
+ case 16:
+ do {
+ *--cp = xdigs[val & 15];
+ val >>= 4;
+ } while (val);
+ break;
+
+ default:
+ abort();
+ }
+ return (cp);
+}
diff --git a/lib/printf/printflocal.h b/lib/printf/printflocal.h
new file mode 100644
index 0000000..bac80e8
--- /dev/null
+++ b/lib/printf/printflocal.h
@@ -0,0 +1,107 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include "compiler.h"
+#include "printfrr.h"
+
+/*
+ * Flags used during conversion.
+ */
+#define ALT 0x001 /* alternate form */
+#define LADJUST 0x004 /* left adjustment */
+#define LONGDBL 0x008 /* long double */
+#define LONGINT 0x010 /* long integer */
+#define LLONGINT 0x020 /* long long integer */
+#define SHORTINT 0x040 /* short integer */
+#define ZEROPAD 0x080 /* zero (as opposed to blank) pad */
+#define FPT 0x100 /* Floating point number */
+#define GROUPING 0x200 /* use grouping ("'" flag) */
+ /* C99 additional size modifiers: */
+#define SIZET 0x400 /* size_t */
+#define PTRDIFFT 0x800 /* ptrdiff_t */
+#define INTMAXT 0x1000 /* intmax_t */
+#define CHARINT 0x2000 /* print char using int format */
+
+/*
+ * Macros for converting digits to letters and vice versa
+ */
+#define to_digit(c) ((c) - '0')
+#define is_digit(c) ((unsigned)to_digit(c) <= 9)
+#define to_char(n) ((n) + '0')
+
+/* Size of the static argument table. */
+#define STATIC_ARG_TBL_SIZE 8
+
+union arg {
+ int intarg;
+ u_int uintarg;
+ long longarg;
+ u_long ulongarg;
+ long long longlongarg;
+ unsigned long long ulonglongarg;
+ ptrdiff_t ptrdiffarg;
+ size_t sizearg;
+ intmax_t intmaxarg;
+ uintmax_t uintmaxarg;
+ void *pvoidarg;
+ char *pchararg;
+ signed char *pschararg;
+ short *pshortarg;
+ int *pintarg;
+ long *plongarg;
+ long long *plonglongarg;
+ ptrdiff_t *pptrdiffarg;
+ ssize_t *pssizearg;
+ intmax_t *pintmaxarg;
+#ifndef NO_FLOATING_POINT
+ double doublearg;
+ long double longdoublearg;
+#endif
+ wint_t wintarg;
+ wchar_t *pwchararg;
+};
+
+/* Handle positional parameters. */
+int _frr_find_arguments(const char *, va_list, union arg **) DSO_LOCAL;
+#ifdef WCHAR_SUPPORT
+int _frr_find_warguments(const wchar_t *, va_list, union arg **) DSO_LOCAL;
+#endif
+
+/* returns number of bytes needed for full output, or -1 */
+ssize_t printfrr_extp(struct fbuf *, struct printfrr_eargs *ea, const void *)
+ DSO_LOCAL;
+ssize_t printfrr_exti(struct fbuf *, struct printfrr_eargs *ea, uintmax_t)
+ DSO_LOCAL;
diff --git a/lib/printf/vfprintf.c b/lib/printf/vfprintf.c
new file mode 100644
index 0000000..49fa2b7
--- /dev/null
+++ b/lib/printf/vfprintf.c
@@ -0,0 +1,821 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * Copyright (c) 2011 The FreeBSD Foundation
+ * All rights reserved.
+ * Portions of this software were developed by David Chisnall
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_SYS_CDEFS_H
+#include <sys/cdefs.h>
+#endif
+
+/*
+ * Actual printf innards.
+ *
+ * This code is large and complicated...
+ */
+
+#include <sys/types.h>
+#include <sys/uio.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+
+#include <stdarg.h>
+
+#include "printflocal.h"
+
+#define CHAR char
+#include "printfcommon.h"
+
+#ifdef WCHAR_SUPPORT
+/*
+ * Convert a wide character string argument for the %ls format to a multibyte
+ * string representation. If not -1, prec specifies the maximum number of
+ * bytes to output, and also means that we can't assume that the wide char.
+ * string ends is null-terminated.
+ */
+static char *
+__wcsconv(wchar_t *wcsarg, int prec)
+{
+ static const mbstate_t initial;
+ mbstate_t mbs;
+ char buf[MB_LEN_MAX];
+ wchar_t *p;
+ char *convbuf;
+ size_t clen, nbytes;
+
+ /* Allocate space for the maximum number of bytes we could output. */
+ if (prec < 0) {
+ p = wcsarg;
+ mbs = initial;
+ nbytes = wcsrtombs(NULL, (const wchar_t **)&p, 0, &mbs);
+ if (nbytes == (size_t)-1)
+ return NULL;
+ } else {
+ /*
+ * Optimisation: if the output precision is small enough,
+ * just allocate enough memory for the maximum instead of
+ * scanning the string.
+ */
+ if (prec < 128)
+ nbytes = prec;
+ else {
+ nbytes = 0;
+ p = wcsarg;
+ mbs = initial;
+ for (;;) {
+ clen = wcrtomb(buf, *p++, &mbs);
+ if (clen == 0 || clen == (size_t)-1 ||
+ nbytes + clen > (size_t)prec)
+ break;
+ nbytes += clen;
+ }
+ }
+ }
+ if ((convbuf = malloc(nbytes + 1)) == NULL)
+ return NULL;
+
+ /* Fill the output buffer. */
+ p = wcsarg;
+ mbs = initial;
+ if ((nbytes = wcsrtombs(convbuf, (const wchar_t **)&p,
+ nbytes, &mbs)) == (size_t)-1) {
+ free(convbuf);
+ return NULL;
+ }
+ convbuf[nbytes] = '\0';
+ return (convbuf);
+}
+#endif /* WCHAR_SUPPORT */
+
+/*
+ * The size of the buffer we use as scratch space for integer
+ * conversions, among other things. We need enough space to
+ * write a uintmax_t in octal (plus one byte).
+ */
+#if UINTMAX_MAX <= UINT64_MAX
+#define BUF 80
+#else
+#error "BUF must be large enough to format a uintmax_t"
+#endif
+
+/*
+ * Non-MT-safe version
+ */
+ssize_t
+vbprintfrr(struct fbuf *cb_in, const char *fmt0, va_list ap)
+{
+ const char *fmt; /* format string */
+ int ch; /* character from fmt */
+ int n, n2; /* handy integer (short term usage) */
+ const char *cp; /* handy char pointer (short term usage) */
+ int flags; /* flags as above */
+ int ret; /* return value accumulator */
+ int width; /* width from format (%8d), or 0 */
+ int prec; /* precision from format; <0 for N/A */
+ int saved_errno;
+ char sign; /* sign prefix (' ', '+', '-', or \0) */
+
+ u_long ulval = 0; /* integer arguments %[diouxX] */
+ uintmax_t ujval = 0; /* %j, %ll, %q, %t, %z integers */
+ void *ptrval; /* %p */
+ int base; /* base for [diouxX] conversion */
+ int dprec; /* a copy of prec if [diouxX], 0 otherwise */
+ int realsz; /* field size expanded by dprec, sign, etc */
+ int size; /* size of converted field or string */
+ int prsize; /* max size of printed field */
+ const char *xdigs; /* digits for %[xX] conversion */
+ struct io_state io; /* I/O buffering state */
+ char buf[BUF]; /* buffer with space for digits of uintmax_t */
+ char ox[2]; /* space for 0x; ox[1] is either x, X, or \0 */
+ union arg *argtable; /* args, built due to positional arg */
+ union arg statargtable [STATIC_ARG_TBL_SIZE];
+ int nextarg; /* 1-based argument index */
+ va_list orgap; /* original argument pointer */
+ char *convbuf; /* wide to multibyte conversion result */
+ char *extstart = NULL; /* where printfrr_ext* started printing */
+ struct fbuf cb_copy, *cb;
+ struct fmt_outpos *opos;
+
+ static const char xdigs_lower[16] = "0123456789abcdef";
+ static const char xdigs_upper[16] = "0123456789ABCDEF";
+
+ /* BEWARE, these `goto error' on error. */
+#define PRINT(ptr, len) { \
+ if (io_print(&io, (ptr), (len))) \
+ goto error; \
+}
+#define PAD(howmany, with) { \
+ if (io_pad(&io, (howmany), (with))) \
+ goto error; \
+}
+#define PRINTANDPAD(p, ep, len, with) { \
+ if (io_printandpad(&io, (p), (ep), (len), (with))) \
+ goto error; \
+}
+#define FLUSH() do { } while (0)
+
+ /*
+ * Get the argument indexed by nextarg. If the argument table is
+ * built, use it to get the argument. If its not, get the next
+ * argument (and arguments must be gotten sequentially).
+ */
+#define GETARG(type) \
+ ((argtable != NULL) ? *((type*)(&argtable[nextarg++])) : \
+ (nextarg++, va_arg(ap, type)))
+
+ /*
+ * To extend shorts properly, we need both signed and unsigned
+ * argument extraction methods.
+ */
+#define SARG() \
+ (flags&LONGINT ? GETARG(long) : \
+ flags&SHORTINT ? (long)(short)GETARG(int) : \
+ flags&CHARINT ? (long)(signed char)GETARG(int) : \
+ (long)GETARG(int))
+#define UARG() \
+ (flags&LONGINT ? GETARG(u_long) : \
+ flags&SHORTINT ? (u_long)(u_short)GETARG(int) : \
+ flags&CHARINT ? (u_long)(u_char)GETARG(int) : \
+ (u_long)GETARG(u_int))
+#define INTMAX_SIZE (INTMAXT|SIZET|PTRDIFFT|LLONGINT|LONGDBL)
+#define SJARG() \
+ (flags&LONGDBL ? GETARG(int64_t) : \
+ flags&INTMAXT ? GETARG(intmax_t) : \
+ flags&SIZET ? (intmax_t)GETARG(ssize_t) : \
+ flags&PTRDIFFT ? (intmax_t)GETARG(ptrdiff_t) : \
+ (intmax_t)GETARG(long long))
+#define UJARG() \
+ (flags&LONGDBL ? GETARG(uint64_t) : \
+ flags&INTMAXT ? GETARG(uintmax_t) : \
+ flags&SIZET ? (uintmax_t)GETARG(size_t) : \
+ flags&PTRDIFFT ? (uintmax_t)GETARG(ptrdiff_t) : \
+ (uintmax_t)GETARG(unsigned long long))
+
+ /*
+ * Get * arguments, including the form *nn$. Preserve the nextarg
+ * that the argument can be gotten once the type is determined.
+ */
+#define GETASTER(val) \
+ n2 = 0; \
+ cp = fmt; \
+ while (is_digit(*cp)) { \
+ n2 = 10 * n2 + to_digit(*cp); \
+ cp++; \
+ } \
+ if (*cp == '$') { \
+ int hold = nextarg; \
+ if (argtable == NULL) { \
+ argtable = statargtable; \
+ if (_frr_find_arguments (fmt0, orgap, &argtable)) { \
+ ret = EOF; \
+ goto error; \
+ } \
+ } \
+ nextarg = n2; \
+ val = GETARG (int); \
+ nextarg = hold; \
+ fmt = ++cp; \
+ } else { \
+ val = GETARG (int); \
+ }
+
+ xdigs = xdigs_lower;
+ saved_errno = errno;
+ convbuf = NULL;
+ fmt = (char *)fmt0;
+ argtable = NULL;
+ nextarg = 1;
+ va_copy(orgap, ap);
+
+ if (cb_in) {
+ /* prevent printfrr exts from polluting cb->outpos */
+ cb_copy = *cb_in;
+ cb_copy.outpos = NULL;
+ cb_copy.outpos_n = cb_copy.outpos_i = 0;
+ cb = &cb_copy;
+ } else
+ cb = NULL;
+
+ io_init(&io, cb);
+ ret = 0;
+
+ /*
+ * Scan the format for conversions (`%' character).
+ */
+ for (;;) {
+ for (cp = fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++)
+ /* void */;
+ if ((n = fmt - cp) != 0) {
+ if ((unsigned)ret + n > INT_MAX) {
+ ret = EOF;
+ errno = EOVERFLOW;
+ goto error;
+ }
+ PRINT(cp, n);
+ ret += n;
+ }
+ if (ch == '\0')
+ goto done;
+ fmt++; /* skip over '%' */
+
+ flags = 0;
+ dprec = 0;
+ width = -1;
+ prec = -1;
+ sign = '\0';
+ ox[1] = '\0';
+
+ if (cb_in && cb_in->outpos_i < cb_in->outpos_n)
+ opos = &cb_in->outpos[cb_in->outpos_i];
+ else
+ opos = NULL;
+
+rflag: ch = *fmt++;
+reswitch: switch (ch) {
+ case ' ':
+ /*-
+ * ``If the space and + flags both appear, the space
+ * flag will be ignored.''
+ * -- ANSI X3J11
+ */
+ if (!sign)
+ sign = ' ';
+ goto rflag;
+ case '#':
+ flags |= ALT;
+ goto rflag;
+ case '*':
+ /*-
+ * ``A negative field width argument is taken as a
+ * - flag followed by a positive field width.''
+ * -- ANSI X3J11
+ * They don't exclude field widths read from args.
+ */
+ GETASTER (width);
+ if (width >= 0)
+ goto rflag;
+ width = -width;
+ /* FALLTHROUGH */
+ case '-':
+ flags |= LADJUST;
+ goto rflag;
+ case '+':
+ sign = '+';
+ goto rflag;
+ case '\'':
+ flags |= GROUPING;
+ goto rflag;
+ case '.':
+ if ((ch = *fmt++) == '*') {
+ GETASTER (prec);
+ goto rflag;
+ }
+ prec = 0;
+ while (is_digit(ch)) {
+ prec = 10 * prec + to_digit(ch);
+ ch = *fmt++;
+ }
+ goto reswitch;
+ case '0':
+ /*-
+ * ``Note that 0 is taken as a flag, not as the
+ * beginning of a field width.''
+ * -- ANSI X3J11
+ */
+ flags |= ZEROPAD;
+ goto rflag;
+ case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ n = 0;
+ do {
+ n = 10 * n + to_digit(ch);
+ ch = *fmt++;
+ } while (is_digit(ch));
+ if (ch == '$') {
+ nextarg = n;
+ if (argtable == NULL) {
+ argtable = statargtable;
+ if (_frr_find_arguments (fmt0, orgap,
+ &argtable)) {
+ ret = EOF;
+ goto error;
+ }
+ }
+ goto rflag;
+ }
+ width = n;
+ goto reswitch;
+ case 'L':
+ flags |= LONGDBL;
+ goto rflag;
+ case 'h':
+ if (flags & SHORTINT) {
+ flags &= ~SHORTINT;
+ flags |= CHARINT;
+ } else
+ flags |= SHORTINT;
+ goto rflag;
+ case 'j':
+ flags |= INTMAXT;
+ goto rflag;
+ case 'l':
+ if (flags & LONGINT) {
+ flags &= ~LONGINT;
+ flags |= LLONGINT;
+ } else
+ flags |= LONGINT;
+ goto rflag;
+ case 'q':
+ flags |= LLONGINT; /* not necessarily */
+ goto rflag;
+ case 't':
+ flags |= PTRDIFFT;
+ goto rflag;
+ case 'z':
+ flags |= SIZET;
+ goto rflag;
+ case 'C':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 'c':
+#ifdef WCHAR_SUPPORT
+ if (flags & LONGINT) {
+ static const mbstate_t initial;
+ mbstate_t mbs;
+ size_t mbseqlen;
+
+ mbs = initial;
+ mbseqlen = wcrtomb(cp = buf,
+ (wchar_t)GETARG(wint_t), &mbs);
+ if (mbseqlen == (size_t)-1) {
+ goto error;
+ }
+ size = (int)mbseqlen;
+ } else
+#endif /* WCHAR_SUPPORT */
+ {
+ buf[0] = GETARG(int);
+ cp = buf;
+ size = 1;
+ }
+ sign = '\0';
+ break;
+ case 'D':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 'd':
+ case 'i':
+ if (flags & INTMAX_SIZE)
+ ujval = SJARG();
+ else
+ ulval = SARG();
+
+ if (printfrr_ext_char(fmt[0])) {
+ struct printfrr_eargs ea = {
+ .fmt = fmt,
+ .precision = prec,
+ .width = width,
+ .alt_repr = !!(flags & ALT),
+ .leftadj = !!(flags & LADJUST),
+ };
+
+ if (cb)
+ extstart = cb->pos;
+
+ size = printfrr_exti(cb, &ea,
+ (flags & INTMAX_SIZE) ? ujval
+ : (uintmax_t)ulval);
+ if (size >= 0) {
+ fmt = ea.fmt;
+ width = ea.width;
+ goto ext_printed;
+ }
+ }
+ if (flags & INTMAX_SIZE) {
+ if ((intmax_t)ujval < 0) {
+ ujval = -ujval;
+ sign = '-';
+ }
+ } else {
+ if ((long)ulval < 0) {
+ ulval = -ulval;
+ sign = '-';
+ }
+ }
+ base = 10;
+ goto number;
+#ifndef NO_FLOATING_POINT
+ case 'a':
+ case 'A':
+ case 'e':
+ case 'E':
+ case 'f':
+ case 'F':
+ case 'g':
+ case 'G':
+ if (flags & LONGDBL) {
+ long double arg = GETARG(long double);
+ char fmt[6] = "%.*L";
+ fmt[4] = ch;
+ fmt[5] = '\0';
+
+ snprintf(buf, sizeof(buf), fmt, prec, arg);
+ } else {
+ double arg = GETARG(double);
+ char fmt[5] = "%.*";
+ fmt[3] = ch;
+ fmt[4] = '\0';
+
+ snprintf(buf, sizeof(buf), fmt, prec, arg);
+ }
+ cp = buf;
+ /* for proper padding */
+ if (*cp == '-') {
+ cp++;
+ sign = '-';
+ }
+ /* "inf" */
+ if (!is_digit(*cp) && *cp != '.')
+ flags &= ~ZEROPAD;
+ size = strlen(buf);
+ break;
+#endif
+ case 'm':
+ cp = strerror(saved_errno);
+ size = (prec >= 0) ? strnlen(cp, prec) : strlen(cp);
+ sign = '\0';
+ break;
+ case 'O':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 'o':
+ if (flags & INTMAX_SIZE)
+ ujval = UJARG();
+ else
+ ulval = UARG();
+ base = 8;
+ goto nosign;
+ case 'p':
+ /*-
+ * ``The argument shall be a pointer to void. The
+ * value of the pointer is converted to a sequence
+ * of printable characters, in an implementation-
+ * defined manner.''
+ * -- ANSI X3J11
+ */
+ ptrval = GETARG(void *);
+ if (printfrr_ext_char(fmt[0])) {
+ struct printfrr_eargs ea = {
+ .fmt = fmt,
+ .precision = prec,
+ .width = width,
+ .alt_repr = !!(flags & ALT),
+ .leftadj = !!(flags & LADJUST),
+ };
+
+ if (cb)
+ extstart = cb->pos;
+
+ size = printfrr_extp(cb, &ea, ptrval);
+ if (size >= 0) {
+ fmt = ea.fmt;
+ width = ea.width;
+ goto ext_printed;
+ }
+ }
+ ujval = (uintmax_t)(uintptr_t)ptrval;
+ base = 16;
+ xdigs = xdigs_lower;
+ flags = flags | INTMAXT;
+ ox[1] = 'x';
+ goto nosign;
+ case 'S':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 's':
+#ifdef WCHAR_SUPPORT
+ if (flags & LONGINT) {
+ wchar_t *wcp;
+
+ if (convbuf != NULL)
+ free(convbuf);
+ if ((wcp = GETARG(wchar_t *)) == NULL)
+ cp = "(null)";
+ else {
+ convbuf = __wcsconv(wcp, prec);
+ if (convbuf == NULL) {
+ goto error;
+ }
+ cp = convbuf;
+ }
+ } else
+#endif
+ if ((cp = GETARG(char *)) == NULL)
+ cp = "(null)";
+ size = (prec >= 0) ? strnlen(cp, prec) : strlen(cp);
+ sign = '\0';
+ break;
+ case 'U':
+ flags |= LONGINT;
+ /*FALLTHROUGH*/
+ case 'u':
+ if (flags & INTMAX_SIZE)
+ ujval = UJARG();
+ else
+ ulval = UARG();
+ base = 10;
+ goto nosign;
+ case 'X':
+ xdigs = xdigs_upper;
+ goto hex;
+ case 'x':
+ xdigs = xdigs_lower;
+hex:
+ if (flags & INTMAX_SIZE)
+ ujval = UJARG();
+ else
+ ulval = UARG();
+ base = 16;
+ /* leading 0x/X only if non-zero */
+ if (flags & ALT &&
+ (flags & INTMAX_SIZE ? ujval != 0 : ulval != 0))
+ ox[1] = ch;
+
+ flags &= ~GROUPING;
+ /* unsigned conversions */
+nosign: sign = '\0';
+ /*-
+ * ``... diouXx conversions ... if a precision is
+ * specified, the 0 flag will be ignored.''
+ * -- ANSI X3J11
+ */
+number: if ((dprec = prec) >= 0)
+ flags &= ~ZEROPAD;
+
+ /*-
+ * ``The result of converting a zero value with an
+ * explicit precision of zero is no characters.''
+ * -- ANSI X3J11
+ *
+ * ``The C Standard is clear enough as is. The call
+ * printf("%#.0o", 0) should print 0.''
+ * -- Defect Report #151
+ */
+ cp = buf + BUF;
+ if (flags & INTMAX_SIZE) {
+ if (ujval != 0 || prec != 0 ||
+ (flags & ALT && base == 8))
+ cp = __ujtoa(ujval, buf + BUF, base,
+ flags & ALT, xdigs);
+ } else {
+ if (ulval != 0 || prec != 0 ||
+ (flags & ALT && base == 8))
+ cp = __ultoa(ulval, buf + BUF, base,
+ flags & ALT, xdigs);
+ }
+ size = buf + BUF - cp;
+ if (size > BUF) /* should never happen */
+ abort();
+ break;
+ default: /* "%?" prints ?, unless ? is NUL */
+ if (ch == '\0')
+ goto done;
+ /* pretend it was %c with argument ch */
+ buf[0] = ch;
+ cp = buf;
+ size = 1;
+ sign = '\0';
+ opos = NULL;
+ break;
+ }
+
+ /*
+ * All reasonable formats wind up here. At this point, `cp'
+ * points to a string which (if not flags&LADJUST) should be
+ * padded out to `width' places. If flags&ZEROPAD, it should
+ * first be prefixed by any sign or other prefix; otherwise,
+ * it should be blank padded before the prefix is emitted.
+ * After any left-hand padding and prefixing, emit zeroes
+ * required by a decimal [diouxX] precision, then print the
+ * string proper, then emit zeroes required by any leftover
+ * floating precision; finally, if LADJUST, pad with blanks.
+ *
+ * Compute actual size, so we know how much to pad.
+ * size excludes decimal prec; realsz includes it.
+ */
+ if (width < 0)
+ width = 0;
+
+ realsz = dprec > size ? dprec : size;
+ if (sign)
+ realsz++;
+ if (ox[1])
+ realsz += 2;
+
+ prsize = width > realsz ? width : realsz;
+ if ((unsigned int)ret + prsize > INT_MAX) {
+ ret = EOF;
+ errno = EOVERFLOW;
+ goto error;
+ }
+
+ /* right-adjusting blank padding */
+ if ((flags & (LADJUST|ZEROPAD)) == 0)
+ PAD(width - realsz, blanks);
+
+ if (opos)
+ opos->off_start = cb->pos - cb->buf;
+
+ /* prefix */
+ if (sign)
+ PRINT(&sign, 1);
+
+ if (ox[1]) { /* ox[1] is either x, X, or \0 */
+ ox[0] = '0';
+ PRINT(ox, 2);
+ }
+
+ /* right-adjusting zero padding */
+ if ((flags & (LADJUST|ZEROPAD)) == ZEROPAD)
+ PAD(width - realsz, zeroes);
+
+ /* the string or number proper */
+ /* leading zeroes from decimal precision */
+ PAD(dprec - size, zeroes);
+ PRINT(cp, size);
+
+ if (opos) {
+ opos->off_end = cb->pos - cb->buf;
+ cb_in->outpos_i++;
+ }
+
+ /* left-adjusting padding (always blank) */
+ if (flags & LADJUST)
+ PAD(width - realsz, blanks);
+
+ /* finally, adjust ret */
+ ret += prsize;
+
+ FLUSH(); /* copy out the I/O vectors */
+ continue;
+
+ext_printed:
+ /* when we arrive here, a printfrr extension has written to cb
+ * (if non-NULL), but we still need to handle padding. The
+ * original cb->pos is in extstart; the return value from the
+ * ext is in size.
+ *
+ * Keep analogous to code above please.
+ */
+
+ if (width < 0)
+ width = 0;
+
+ realsz = size;
+ prsize = width > realsz ? width : realsz;
+ if ((unsigned int)ret + prsize > INT_MAX) {
+ ret = EOF;
+ errno = EOVERFLOW;
+ goto error;
+ }
+
+ /* right-adjusting blank padding - need to move the chars
+ * that the extension has already written. Should be very
+ * rare.
+ */
+ if (cb && width > size && (flags & (LADJUST|ZEROPAD)) == 0) {
+ size_t nwritten = cb->pos - extstart;
+ size_t navail = cb->buf + cb->len - extstart;
+ size_t npad = width - realsz;
+ size_t nmove;
+
+ if (navail < npad)
+ navail = 0;
+ else
+ navail -= npad;
+ nmove = MIN(nwritten, navail);
+
+ memmove(extstart + npad, extstart, nmove);
+
+ cb->pos = extstart;
+ PAD(npad, blanks);
+ cb->pos += nmove;
+ extstart += npad;
+ }
+
+ io.avail = cb ? cb->len - (cb->pos - cb->buf) : 0;
+
+ if (opos && extstart <= cb->pos) {
+ opos->off_start = extstart - cb->buf;
+ opos->off_end = cb->pos - cb->buf;
+ cb_in->outpos_i++;
+ }
+
+ /* left-adjusting padding (always blank) */
+ if (flags & LADJUST)
+ PAD(width - realsz, blanks);
+
+ /* finally, adjust ret */
+ ret += prsize;
+
+ FLUSH(); /* copy out the I/O vectors */
+ }
+done:
+ FLUSH();
+error:
+ va_end(orgap);
+ if (convbuf != NULL)
+ free(convbuf);
+ if ((argtable != NULL) && (argtable != statargtable))
+ free (argtable);
+ if (cb_in)
+ cb_in->pos = cb->pos;
+ return (ret);
+ /* NOTREACHED */
+}
+
diff --git a/lib/printfrr.h b/lib/printfrr.h
new file mode 100644
index 0000000..a2d113b
--- /dev/null
+++ b/lib/printfrr.h
@@ -0,0 +1,326 @@
+/*
+ * Copyright (c) 2019 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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_PRINTFRR_H
+#define _FRR_PRINTFRR_H
+
+#include <stddef.h>
+#include <stdarg.h>
+#include <stdint.h>
+
+#include "compiler.h"
+#include "memory.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct fmt_outpos {
+ unsigned int off_start, off_end;
+};
+
+struct fbuf {
+ char *buf;
+ char *pos;
+ size_t len;
+
+ struct fmt_outpos *outpos;
+ size_t outpos_n, outpos_i;
+};
+
+#define at(a, b) PRINTFRR(a, b)
+#define atn(a, b) \
+ at(a, b) __attribute__((nonnull(1) _RET_NONNULL))
+#define atm(a, b) \
+ atn(a, b) __attribute__((malloc))
+
+/* return value is length needed for the full string (excluding \0) in all
+ * cases. The functions write as much as they can, but continue regardless,
+ * so the return value is independent of buffer length. Both bprintfrr and
+ * snprintf also accept NULL as output buffer.
+ */
+
+/* bprintfrr does NOT null terminate! use sparingly (only provided since it's
+ * the most direct interface) - useful for incrementally building long text
+ * (call bprintfrr repeatedly with the same buffer)
+ */
+ssize_t vbprintfrr(struct fbuf *out, const char *fmt, va_list) at(2, 0);
+ssize_t bprintfrr(struct fbuf *out, const char *fmt, ...) at(2, 3);
+
+/* these do null terminate like their snprintf cousins */
+ssize_t vsnprintfrr(char *out, size_t sz, const char *fmt, va_list) at(3, 0);
+ssize_t snprintfrr(char *out, size_t sz, const char *fmt, ...) at(3, 4);
+
+/* c = continue / concatenate (append at the end of the string)
+ * return value is would-be string length (regardless of buffer length),
+ * i.e. includes already written chars */
+ssize_t vcsnprintfrr(char *out, size_t sz, const char *fmt, va_list) at(3, 0);
+ssize_t csnprintfrr(char *out, size_t sz, const char *fmt, ...) at(3, 4);
+
+/* memory allocations don't fail in FRR, so you always get something here.
+ * (in case of error, returns a strdup of the format string) */
+char *vasprintfrr(struct memtype *mt, const char *fmt, va_list) atm(2, 0);
+char *asprintfrr(struct memtype *mt, const char *fmt, ...) atm(2, 3);
+
+/* try to use provided buffer (presumably from stack), allocate if it's too
+ * short. Must call XFREE(mt, return value) if return value != out.
+ */
+char *vasnprintfrr(struct memtype *mt, char *out, size_t sz,
+ const char *fmt, va_list) atn(4, 0);
+char *asnprintfrr(struct memtype *mt, char *out, size_t sz,
+ const char *fmt, ...) atn(4, 5);
+
+#define printfrr(fmt, ...) \
+ do { \
+ char buf[256], *out; \
+ out = asnprintfrr(MTYPE_TMP, buf, sizeof(buf), fmt, \
+ ##__VA_ARGS__); \
+ fputs(out, stdout); \
+ if (out != buf) \
+ XFREE(MTYPE_TMP, out); \
+ } while (0)
+
+#undef at
+#undef atm
+#undef atn
+
+/* extension specs must start with a capital letter (this is a restriction
+ * for both performance's and human understanding's sake.)
+ *
+ * Note that the entire thing mostly works because a letter directly following
+ * a %p print specifier is extremely unlikely to occur (why would you want to
+ * print "0x12345678HELLO"?) Normally, you'd expect spacing or punctuation
+ * after a placeholder. That also means that neither of those works well for
+ * extension purposes, e.g. "%p{foo}" is reasonable to see actually used.
+ *
+ * TODO: would be nice to support a "%pF%dF" specifier that consumes 2
+ * arguments, e.g. to pass an integer + a list of known values... can be
+ * done, but a bit tricky.
+ */
+#define printfrr_ext_char(ch) ((ch) >= 'A' && (ch) <= 'Z')
+
+struct printfrr_eargs;
+
+struct printfrr_ext {
+ /* embedded string to minimize cache line pollution */
+ char match[8];
+
+ /* both can be given, if not the code continues searching
+ * (you can do %pX and %dX in 2 different entries)
+ *
+ * return value: number of bytes that would be printed if the buffer
+ * was large enough. be careful about not under-reporting this;
+ * otherwise asnprintf() & co. will get broken. Returning -1 means
+ * something went wrong & default %p/%d handling should be executed.
+ *
+ * to consume extra input flags after %pXY, increment *fmt. It points
+ * at the first character after %pXY at entry. Convention is to make
+ * those flags lowercase letters or numbers.
+ */
+ ssize_t (*print_ptr)(struct fbuf *buf, struct printfrr_eargs *info,
+ const void *);
+ ssize_t (*print_int)(struct fbuf *buf, struct printfrr_eargs *info,
+ uintmax_t);
+};
+
+/* additional information passed to extended formatters */
+
+struct printfrr_eargs {
+ /* position in the format string. Points to directly after the
+ * extension specifier. Increment when consuming extra "flag
+ * characters".
+ */
+ const char *fmt;
+
+ /* %.1234x / %.*x
+ * not POSIX compatible when used with %p, will cause warnings from
+ * GCC & clang. Usable with %d. Not used by the printfrr() itself
+ * for extension specifiers, so essentially available as a "free"
+ * parameter. -1 if not specified. Value in the format string
+ * cannot be negative, but negative values can be passed with %.*x
+ */
+ int precision;
+
+ /* %1234x / %*x
+ * regular width specification. Internally handled by printfrr(), set
+ * to 0 if consumed by the extension in order to suppress standard
+ * width/padding behavior. 0 if not specified.
+ *
+ * NB: always positive, even if a negative value is passed in with
+ * %*x. (The sign is used for the - flag.)
+ */
+ int width;
+
+ /* %#x
+ * "alternate representation" flag, not POSIX compatible when used
+ * with %p or %d, will cause warnings from GCC & clang. Not used by
+ * printfrr() itself for extension specifiers.
+ */
+ bool alt_repr;
+
+ /* %-x
+ * left-pad flag. Internally handled by printfrr() if width is
+ * nonzero. Only use if the extension sets width to 0.
+ */
+ bool leftadj;
+};
+
+/* for any extension that needs a buffer length */
+
+static inline ssize_t printfrr_ext_len(struct printfrr_eargs *ea)
+{
+ ssize_t rv;
+
+ if (ea->precision >= 0)
+ rv = ea->precision;
+ else if (ea->width >= 0) {
+ rv = ea->width;
+ ea->width = -1;
+ } else
+ rv = -1;
+
+ return rv;
+}
+
+/* no locking - must be called when single threaded (e.g. at startup.)
+ * this restriction hopefully won't be a huge bother considering normal usage
+ * scenarios...
+ */
+void printfrr_ext_reg(const struct printfrr_ext *);
+
+#define printfrr_ext_autoreg_p(matchs, print_fn) \
+ static ssize_t print_fn(struct fbuf *, struct printfrr_eargs *, \
+ const void *); \
+ static const struct printfrr_ext _printext_##print_fn = { \
+ .match = matchs, \
+ .print_ptr = print_fn, \
+ }; \
+ static void _printreg_##print_fn(void) __attribute__((constructor)); \
+ static void _printreg_##print_fn(void) \
+ { \
+ printfrr_ext_reg(&_printext_##print_fn); \
+ } \
+ MACRO_REQUIRE_SEMICOLON()
+
+#define printfrr_ext_autoreg_i(matchs, print_fn) \
+ static ssize_t print_fn(struct fbuf *, struct printfrr_eargs *, \
+ uintmax_t); \
+ static const struct printfrr_ext _printext_##print_fn = { \
+ .match = matchs, \
+ .print_int = print_fn, \
+ }; \
+ static void _printreg_##print_fn(void) __attribute__((constructor)); \
+ static void _printreg_##print_fn(void) \
+ { \
+ printfrr_ext_reg(&_printext_##print_fn); \
+ } \
+ MACRO_REQUIRE_SEMICOLON()
+
+/* fbuf helper functions - note all 3 of these return the length that would
+ * be written regardless of how much space was available in the buffer, as
+ * needed for implementing printfrr extensions. (They also accept NULL buf
+ * for that.)
+ */
+
+static inline ssize_t bputs(struct fbuf *buf, const char *str)
+{
+ size_t len = strlen(str);
+ size_t ncopy;
+
+ if (!buf)
+ return len;
+
+ ncopy = MIN(len, (size_t)(buf->buf + buf->len - buf->pos));
+ memcpy(buf->pos, str, ncopy);
+ buf->pos += ncopy;
+
+ return len;
+}
+
+static inline ssize_t bputch(struct fbuf *buf, char ch)
+{
+ if (buf && buf->pos < buf->buf + buf->len)
+ *buf->pos++ = ch;
+ return 1;
+}
+
+static inline ssize_t bputhex(struct fbuf *buf, uint8_t val)
+{
+ static const char hexch[] = "0123456789abcdef";
+
+ if (buf && buf->pos < buf->buf + buf->len)
+ *buf->pos++ = hexch[(val >> 4) & 0xf];
+ if (buf && buf->pos < buf->buf + buf->len)
+ *buf->pos++ = hexch[val & 0xf];
+ return 2;
+}
+
+/* %pVA extension, equivalent to Linux kernel %pV */
+
+struct va_format {
+ const char *fmt;
+ va_list *va;
+};
+
+#ifdef _FRR_ATTRIBUTE_PRINTFRR
+#pragma FRR printfrr_ext "%pFB" (struct fbuf *)
+#pragma FRR printfrr_ext "%pVA" (struct va_format *)
+
+#pragma FRR printfrr_ext "%pHX" (signed char *)
+#pragma FRR printfrr_ext "%pHX" (unsigned char *)
+#pragma FRR printfrr_ext "%pHX" (void *)
+#pragma FRR printfrr_ext "%pHS" (signed char *)
+#pragma FRR printfrr_ext "%pHS" (unsigned char *)
+#pragma FRR printfrr_ext "%pHS" (void *)
+
+#pragma FRR printfrr_ext "%pSE" (char *)
+#pragma FRR printfrr_ext "%pSQ" (char *)
+
+#pragma FRR printfrr_ext "%pTS" (struct timespec *)
+#pragma FRR printfrr_ext "%pTV" (struct timeval *)
+#pragma FRR printfrr_ext "%pTT" (time_t *)
+#endif
+
+/* when using non-ISO-C compatible extension specifiers... */
+
+#ifdef _FRR_ATTRIBUTE_PRINTFRR
+#define FMT_NSTD_BEGIN
+#define FMT_NSTD_END
+#else /* !_FRR_ATTRIBUTE_PRINTFRR */
+#define FMT_NSTD_BEGIN \
+ _Pragma("GCC diagnostic push") \
+ _Pragma("GCC diagnostic ignored \"-Wformat\"") \
+ /* end */
+#define FMT_NSTD_END \
+ _Pragma("GCC diagnostic pop") \
+ /* end */
+#endif
+
+#define FMT_NSTD(expr) \
+ ({ \
+ FMT_NSTD_BEGIN \
+ typeof(expr) _v; \
+ _v = expr; \
+ FMT_NSTD_END \
+ _v; \
+ }) \
+ /* end */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/privs.c b/lib/privs.c
new file mode 100644
index 0000000..71416be
--- /dev/null
+++ b/lib/privs.c
@@ -0,0 +1,762 @@
+/*
+ * Zebra privileges.
+ *
+ * Copyright (C) 2003 Paul Jakma.
+ * Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved.
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 "privs.h"
+#include "memory.h"
+#include "frr_pthread.h"
+#include "lib_errors.h"
+#include "lib/queue.h"
+
+DEFINE_MTYPE_STATIC(LIB, PRIVS, "Privilege information");
+
+/*
+ * Different capabilities/privileges apis have different characteristics: some
+ * are process-wide, and some are per-thread.
+ */
+#ifdef HAVE_CAPABILITIES
+#ifdef HAVE_LCAPS
+static const bool privs_per_process; /* = false */
+#else
+static const bool privs_per_process = true;
+#endif /* HAVE_LCAPS */
+#else /* HAVE_CAPABILITIES */
+static const bool privs_per_process = true;
+#endif
+
+#ifdef HAVE_CAPABILITIES
+
+/* sort out some generic internal types for:
+ *
+ * privilege values (cap_value_t, priv_t) -> pvalue_t
+ * privilege set (..., priv_set_t) -> pset_t
+ * privilege working storage (cap_t, ...) -> pstorage_t
+ *
+ * values we think of as numeric (they're ints really, but we dont know)
+ * sets are mostly opaque, to hold a set of privileges, related in some way.
+ * storage binds together a set of sets we're interested in.
+ * (in reality: cap_value_t and priv_t are ints)
+ */
+#ifdef HAVE_LCAPS
+/* Linux doesn't have a 'set' type: a set of related privileges */
+struct _pset {
+ int num;
+ cap_value_t *caps;
+};
+typedef cap_value_t pvalue_t;
+typedef struct _pset pset_t;
+typedef cap_t pstorage_t;
+
+#else /* no LCAPS */
+#error "HAVE_CAPABILITIES defined, but neither LCAPS nor Solaris Capabilties!"
+#endif /* HAVE_LCAPS */
+#endif /* HAVE_CAPABILITIES */
+
+/* the default NULL state we report is RAISED, but could be LOWERED if
+ * zprivs_terminate is called and the NULL handler is installed.
+ */
+static zebra_privs_current_t zprivs_null_state = ZPRIVS_RAISED;
+
+/* internal privileges state */
+static struct _zprivs_t {
+#ifdef HAVE_CAPABILITIES
+ pstorage_t caps; /* working storage */
+ pset_t *syscaps_p; /* system-type requested permitted caps */
+ pset_t *syscaps_i; /* system-type requested inheritable caps */
+#endif /* HAVE_CAPABILITIES */
+ uid_t zuid, /* uid to run as */
+ zsuid; /* saved uid */
+ gid_t zgid; /* gid to run as */
+ gid_t vtygrp; /* gid for vty sockets */
+} zprivs_state;
+
+/* externally exported but not directly accessed functions */
+#ifdef HAVE_CAPABILITIES
+int zprivs_change_caps(zebra_privs_ops_t);
+zebra_privs_current_t zprivs_state_caps(void);
+#endif /* HAVE_CAPABILITIES */
+int zprivs_change_uid(zebra_privs_ops_t);
+zebra_privs_current_t zprivs_state_uid(void);
+int zprivs_change_null(zebra_privs_ops_t);
+zebra_privs_current_t zprivs_state_null(void);
+
+#ifdef HAVE_CAPABILITIES
+/* internal capability API */
+static pset_t *zcaps2sys(zebra_capabilities_t *, int);
+static void zprivs_caps_init(struct zebra_privs_t *);
+static void zprivs_caps_terminate(void);
+
+/* Map of Quagga abstract capabilities to system capabilities */
+static struct {
+ int num;
+ pvalue_t *system_caps;
+} cap_map[ZCAP_MAX] = {
+#ifdef HAVE_LCAPS /* Quagga -> Linux capabilities mappings */
+ [ZCAP_SETID] =
+ {
+ 2, (pvalue_t[]){CAP_SETGID, CAP_SETUID},
+ },
+ [ZCAP_BIND] =
+ {
+ 1, (pvalue_t[]){CAP_NET_BIND_SERVICE},
+ },
+ [ZCAP_NET_ADMIN] =
+ {
+ 1, (pvalue_t[]){CAP_NET_ADMIN},
+ },
+ [ZCAP_NET_RAW] =
+ {
+ 1, (pvalue_t[]){CAP_NET_RAW},
+ },
+ [ZCAP_CHROOT] =
+ {
+ 1,
+ (pvalue_t[]){
+ CAP_SYS_CHROOT,
+ },
+ },
+ [ZCAP_NICE] =
+ {
+ 1, (pvalue_t[]){CAP_SYS_NICE},
+ },
+ [ZCAP_PTRACE] =
+ {
+ 1, (pvalue_t[]){CAP_SYS_PTRACE},
+ },
+ [ZCAP_DAC_OVERRIDE] =
+ {
+ 1, (pvalue_t[]){CAP_DAC_OVERRIDE},
+ },
+ [ZCAP_READ_SEARCH] =
+ {
+ 1, (pvalue_t[]){CAP_DAC_READ_SEARCH},
+ },
+ [ZCAP_SYS_ADMIN] =
+ {
+ 1, (pvalue_t[]){CAP_SYS_ADMIN},
+ },
+ [ZCAP_FOWNER] =
+ {
+ 1, (pvalue_t[]){CAP_FOWNER},
+ },
+ [ZCAP_IPC_LOCK] =
+ {
+ 1, (pvalue_t[]){CAP_IPC_LOCK},
+ },
+ [ZCAP_SYS_RAWIO] =
+ {
+ 1, (pvalue_t[]){CAP_SYS_RAWIO},
+ },
+#endif /* HAVE_LCAPS */
+};
+
+#ifdef HAVE_LCAPS
+/* Linux forms of capabilities methods */
+/* convert zebras privileges to system capabilities */
+static pset_t *zcaps2sys(zebra_capabilities_t *zcaps, int num)
+{
+ pset_t *syscaps;
+ int i, j = 0, count = 0;
+
+ if (!num)
+ return NULL;
+
+ /* first count up how many system caps we have */
+ for (i = 0; i < num; i++)
+ count += cap_map[zcaps[i]].num;
+
+ if ((syscaps = XCALLOC(MTYPE_PRIVS, (sizeof(pset_t) * num))) == NULL) {
+ fprintf(stderr, "%s: could not allocate syscaps!", __func__);
+ return NULL;
+ }
+
+ syscaps->caps = XCALLOC(MTYPE_PRIVS, (sizeof(pvalue_t) * count));
+
+ if (!syscaps->caps) {
+ fprintf(stderr, "%s: could not XCALLOC caps!", __func__);
+ return NULL;
+ }
+
+ /* copy the capabilities over */
+ count = 0;
+ for (i = 0; i < num; i++)
+ for (j = 0; j < cap_map[zcaps[i]].num; j++)
+ syscaps->caps[count++] =
+ cap_map[zcaps[i]].system_caps[j];
+
+ /* iterations above should be exact same as previous count, obviously..
+ */
+ syscaps->num = count;
+
+ return syscaps;
+}
+
+/* set or clear the effective capabilities to/from permitted */
+int zprivs_change_caps(zebra_privs_ops_t op)
+{
+ cap_flag_value_t cflag;
+
+ /* should be no possibility of being called without valid caps */
+ assert(zprivs_state.syscaps_p && zprivs_state.caps);
+ if (!(zprivs_state.syscaps_p && zprivs_state.caps))
+ exit(1);
+
+ if (op == ZPRIVS_RAISE)
+ cflag = CAP_SET;
+ else if (op == ZPRIVS_LOWER)
+ cflag = CAP_CLEAR;
+ else
+ return -1;
+
+ if (!cap_set_flag(zprivs_state.caps, CAP_EFFECTIVE,
+ zprivs_state.syscaps_p->num,
+ zprivs_state.syscaps_p->caps, cflag))
+ return cap_set_proc(zprivs_state.caps);
+ return -1;
+}
+
+zebra_privs_current_t zprivs_state_caps(void)
+{
+ int i;
+ cap_flag_value_t val;
+
+ /* should be no possibility of being called without valid caps */
+ assert(zprivs_state.syscaps_p && zprivs_state.caps);
+ if (!(zprivs_state.syscaps_p && zprivs_state.caps))
+ exit(1);
+
+ for (i = 0; i < zprivs_state.syscaps_p->num; i++) {
+ if (cap_get_flag(zprivs_state.caps,
+ zprivs_state.syscaps_p->caps[i], CAP_EFFECTIVE,
+ &val)) {
+ flog_err(
+ EC_LIB_SYSTEM_CALL,
+ "zprivs_state_caps: could not cap_get_flag, %s",
+ safe_strerror(errno));
+ return ZPRIVS_UNKNOWN;
+ }
+ if (val == CAP_SET)
+ return ZPRIVS_RAISED;
+ }
+ return ZPRIVS_LOWERED;
+}
+
+static void zprivs_caps_init(struct zebra_privs_t *zprivs)
+{
+ zprivs_state.syscaps_p = zcaps2sys(zprivs->caps_p, zprivs->cap_num_p);
+ zprivs_state.syscaps_i = zcaps2sys(zprivs->caps_i, zprivs->cap_num_i);
+
+ /* Tell kernel we want caps maintained across uid changes */
+ if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) {
+ fprintf(stderr,
+ "privs_init: could not set PR_SET_KEEPCAPS, %s\n",
+ safe_strerror(errno));
+ exit(1);
+ }
+
+ /* we have caps, we have no need to ever change back the original user
+ */
+ /* only change uid if we don't have the correct one */
+ if ((zprivs_state.zuid) && (zprivs_state.zsuid != zprivs_state.zuid)) {
+ if (setreuid(zprivs_state.zuid, zprivs_state.zuid)) {
+ fprintf(stderr,
+ "zprivs_init (cap): could not setreuid, %s\n",
+ safe_strerror(errno));
+ exit(1);
+ }
+ }
+
+ if (!(zprivs_state.caps = cap_init())) {
+ fprintf(stderr, "privs_init: failed to cap_init, %s\n",
+ safe_strerror(errno));
+ exit(1);
+ }
+
+ if (cap_clear(zprivs_state.caps)) {
+ fprintf(stderr, "privs_init: failed to cap_clear, %s\n",
+ safe_strerror(errno));
+ exit(1);
+ }
+
+ /* set permitted caps, if any */
+ if (zprivs_state.syscaps_p && zprivs_state.syscaps_p->num) {
+ cap_set_flag(zprivs_state.caps, CAP_PERMITTED,
+ zprivs_state.syscaps_p->num,
+ zprivs_state.syscaps_p->caps, CAP_SET);
+ }
+
+ /* set inheritable caps, if any */
+ if (zprivs_state.syscaps_i && zprivs_state.syscaps_i->num) {
+ cap_set_flag(zprivs_state.caps, CAP_INHERITABLE,
+ zprivs_state.syscaps_i->num,
+ zprivs_state.syscaps_i->caps, CAP_SET);
+ }
+
+ /* apply caps. CAP_EFFECTIVE is cleared. we'll raise the caps as
+ * and when, and only when, they are needed.
+ */
+ if (cap_set_proc(zprivs_state.caps)) {
+ cap_t current_caps;
+ char *current_caps_text = NULL;
+ char *wanted_caps_text = NULL;
+
+ fprintf(stderr, "privs_init: initial cap_set_proc failed: %s\n",
+ safe_strerror(errno));
+
+ current_caps = cap_get_proc();
+ if (current_caps) {
+ current_caps_text = cap_to_text(current_caps, NULL);
+ cap_free(current_caps);
+ }
+
+ wanted_caps_text = cap_to_text(zprivs_state.caps, NULL);
+ fprintf(stderr, "Wanted caps: %s\n",
+ wanted_caps_text ? wanted_caps_text : "???");
+ fprintf(stderr, "Have caps: %s\n",
+ current_caps_text ? current_caps_text : "???");
+ if (current_caps_text)
+ cap_free(current_caps_text);
+ if (wanted_caps_text)
+ cap_free(wanted_caps_text);
+
+ exit(1);
+ }
+
+ /* set methods for the caller to use */
+ zprivs->change = zprivs_change_caps;
+ zprivs->current_state = zprivs_state_caps;
+}
+
+static void zprivs_caps_terminate(void)
+{
+ /* Clear all capabilities, if we have any. */
+ if (zprivs_state.caps)
+ cap_clear(zprivs_state.caps);
+ else
+ return;
+
+ /* and boom, capabilities are gone forever */
+ if (cap_set_proc(zprivs_state.caps)) {
+ fprintf(stderr, "privs_terminate: cap_set_proc failed, %s",
+ safe_strerror(errno));
+ exit(1);
+ }
+
+ /* free up private state */
+ if (zprivs_state.syscaps_p && zprivs_state.syscaps_p->num) {
+ XFREE(MTYPE_PRIVS, zprivs_state.syscaps_p->caps);
+ XFREE(MTYPE_PRIVS, zprivs_state.syscaps_p);
+ }
+
+ if (zprivs_state.syscaps_i && zprivs_state.syscaps_i->num) {
+ XFREE(MTYPE_PRIVS, zprivs_state.syscaps_i->caps);
+ XFREE(MTYPE_PRIVS, zprivs_state.syscaps_i);
+ }
+
+ cap_free(zprivs_state.caps);
+}
+#else /* !HAVE_LCAPS */
+#error "no Linux capabilities, dazed and confused..."
+#endif /* HAVE_LCAPS */
+#endif /* HAVE_CAPABILITIES */
+
+int zprivs_change_uid(zebra_privs_ops_t op)
+{
+ if (zprivs_state.zsuid == zprivs_state.zuid)
+ return 0;
+ if (op == ZPRIVS_RAISE)
+ return seteuid(zprivs_state.zsuid);
+ else if (op == ZPRIVS_LOWER)
+ return seteuid(zprivs_state.zuid);
+ else
+ return -1;
+}
+
+zebra_privs_current_t zprivs_state_uid(void)
+{
+ return ((zprivs_state.zuid == geteuid()) ? ZPRIVS_LOWERED
+ : ZPRIVS_RAISED);
+}
+
+int zprivs_change_null(zebra_privs_ops_t op)
+{
+ return 0;
+}
+
+zebra_privs_current_t zprivs_state_null(void)
+{
+ return zprivs_null_state;
+}
+
+#ifndef HAVE_GETGROUPLIST
+/* Solaris 11 has no getgrouplist() */
+static int getgrouplist(const char *user, gid_t group, gid_t *groups,
+ int *ngroups)
+{
+ struct group *grp;
+ size_t usridx;
+ int pos = 0, ret;
+
+ if (pos < *ngroups)
+ groups[pos] = group;
+ pos++;
+
+ setgrent();
+ while ((grp = getgrent())) {
+ if (grp->gr_gid == group)
+ continue;
+ for (usridx = 0; grp->gr_mem[usridx] != NULL; usridx++)
+ if (!strcmp(grp->gr_mem[usridx], user)) {
+ if (pos < *ngroups)
+ groups[pos] = grp->gr_gid;
+ pos++;
+ break;
+ }
+ }
+ endgrent();
+
+ ret = (pos <= *ngroups) ? pos : -1;
+ *ngroups = pos;
+ return ret;
+}
+#endif /* HAVE_GETGROUPLIST */
+
+/*
+ * Helper function that locates a refcounting object to use: a process-wide
+ * object or a per-pthread object.
+ */
+static struct zebra_privs_refs_t *get_privs_refs(struct zebra_privs_t *privs)
+{
+ struct zebra_privs_refs_t *temp, *refs = NULL;
+ pthread_t tid;
+
+ if (privs_per_process)
+ refs = &(privs->process_refs);
+ else {
+ /* Locate - or create - the object for the current pthread. */
+ tid = pthread_self();
+
+ STAILQ_FOREACH(temp, &(privs->thread_refs), entry) {
+ if (pthread_equal(temp->tid, tid)) {
+ refs = temp;
+ break;
+ }
+ }
+
+ /* Need to create a new refcounting object. */
+ if (refs == NULL) {
+ refs = XCALLOC(MTYPE_PRIVS,
+ sizeof(struct zebra_privs_refs_t));
+ refs->tid = tid;
+ STAILQ_INSERT_TAIL(&(privs->thread_refs), refs, entry);
+ }
+ }
+
+ return refs;
+}
+
+struct zebra_privs_t *_zprivs_raise(struct zebra_privs_t *privs,
+ const char *funcname)
+{
+ int save_errno = errno;
+ struct zebra_privs_refs_t *refs;
+
+ if (!privs)
+ return NULL;
+
+ /*
+ * Serialize 'raise' operations; particularly important for
+ * OSes where privs are process-wide.
+ */
+ frr_with_mutex (&(privs->mutex)) {
+ /* Locate ref-counting object to use */
+ refs = get_privs_refs(privs);
+
+ if (++(refs->refcount) == 1) {
+ errno = 0;
+ if (privs->change(ZPRIVS_RAISE)) {
+ zlog_err("%s: Failed to raise privileges (%s)",
+ funcname, safe_strerror(errno));
+ }
+ errno = save_errno;
+ refs->raised_in_funcname = funcname;
+ }
+ }
+
+ return privs;
+}
+
+void _zprivs_lower(struct zebra_privs_t **privs)
+{
+ int save_errno = errno;
+ struct zebra_privs_refs_t *refs;
+
+ if (!*privs)
+ return;
+
+ /* Serialize 'lower privs' operation - particularly important
+ * when OS privs are process-wide.
+ */
+ frr_with_mutex (&(*privs)->mutex) {
+ refs = get_privs_refs(*privs);
+
+ if (--(refs->refcount) == 0) {
+ errno = 0;
+ if ((*privs)->change(ZPRIVS_LOWER)) {
+ zlog_err("%s: Failed to lower privileges (%s)",
+ refs->raised_in_funcname,
+ safe_strerror(errno));
+ }
+ errno = save_errno;
+ refs->raised_in_funcname = NULL;
+ }
+ }
+
+ *privs = NULL;
+}
+
+void zprivs_preinit(struct zebra_privs_t *zprivs)
+{
+ struct passwd *pwentry = NULL;
+ struct group *grentry = NULL;
+
+ if (!zprivs) {
+ fprintf(stderr, "zprivs_init: called with NULL arg!\n");
+ exit(1);
+ }
+
+ pthread_mutex_init(&(zprivs->mutex), NULL);
+ zprivs->process_refs.refcount = 0;
+ zprivs->process_refs.raised_in_funcname = NULL;
+ STAILQ_INIT(&zprivs->thread_refs);
+
+ if (zprivs->vty_group) {
+ /* in a "NULL" setup, this is allowed to fail too, but still
+ * try. */
+ if ((grentry = getgrnam(zprivs->vty_group)))
+ zprivs_state.vtygrp = grentry->gr_gid;
+ else
+ zprivs_state.vtygrp = (gid_t)-1;
+ }
+
+ /* NULL privs */
+ if (!(zprivs->user || zprivs->group || zprivs->cap_num_p
+ || zprivs->cap_num_i)) {
+ zprivs->change = zprivs_change_null;
+ zprivs->current_state = zprivs_state_null;
+ return;
+ }
+
+ if (zprivs->user) {
+ if ((pwentry = getpwnam(zprivs->user)) == NULL) {
+ /* cant use log.h here as it depends on vty */
+ fprintf(stderr,
+ "privs_init: could not lookup user %s\n",
+ zprivs->user);
+ exit(1);
+ }
+
+ zprivs_state.zuid = pwentry->pw_uid;
+ zprivs_state.zgid = pwentry->pw_gid;
+ }
+
+ grentry = NULL;
+
+ if (zprivs->group) {
+ if ((grentry = getgrnam(zprivs->group)) == NULL) {
+ fprintf(stderr,
+ "privs_init: could not lookup group %s\n",
+ zprivs->group);
+ exit(1);
+ }
+
+ zprivs_state.zgid = grentry->gr_gid;
+ }
+}
+
+struct zebra_privs_t *lib_privs;
+
+void zprivs_init(struct zebra_privs_t *zprivs)
+{
+ gid_t groups[NGROUPS_MAX] = {};
+ int i, ngroups = 0;
+ int found = 0;
+
+ /* NULL privs */
+ if (!(zprivs->user || zprivs->group || zprivs->cap_num_p
+ || zprivs->cap_num_i))
+ return;
+
+ lib_privs = zprivs;
+
+ if (zprivs->user) {
+ ngroups = array_size(groups);
+ if (getgrouplist(zprivs->user, zprivs_state.zgid, groups,
+ &ngroups)
+ < 0) {
+ /* cant use log.h here as it depends on vty */
+ fprintf(stderr,
+ "privs_init: could not getgrouplist for user %s\n",
+ zprivs->user);
+ exit(1);
+ }
+ }
+
+ if (zprivs->vty_group)
+ /* Add the vty_group to the supplementary groups so it can be chowned to
+ */
+ {
+ if (zprivs_state.vtygrp == (gid_t)-1) {
+ fprintf(stderr,
+ "privs_init: could not lookup vty group %s\n",
+ zprivs->vty_group);
+ exit(1);
+ }
+
+ for (i = 0; i < ngroups; i++)
+ if (groups[i] == zprivs_state.vtygrp) {
+ found++;
+ break;
+ }
+
+ if (!found) {
+ fprintf(stderr,
+ "privs_init: user(%s) is not part of vty group specified(%s)\n",
+ zprivs->user, zprivs->vty_group);
+ exit(1);
+ }
+ if (i >= ngroups && ngroups < (int)array_size(groups)) {
+ groups[i] = zprivs_state.vtygrp;
+ }
+ }
+
+ zprivs_state.zsuid = geteuid(); /* initial uid */
+ /* add groups only if we changed uid - otherwise skip */
+ if ((ngroups) && (zprivs_state.zsuid != zprivs_state.zuid)) {
+ if (setgroups(ngroups, groups)) {
+ fprintf(stderr, "privs_init: could not setgroups, %s\n",
+ safe_strerror(errno));
+ exit(1);
+ }
+ }
+
+ /* change gid only if we changed uid - otherwise skip */
+ if ((zprivs_state.zgid) && (zprivs_state.zsuid != zprivs_state.zuid)) {
+ /* change group now, forever. uid we do later */
+ if (setregid(zprivs_state.zgid, zprivs_state.zgid)) {
+ fprintf(stderr, "zprivs_init: could not setregid, %s\n",
+ safe_strerror(errno));
+ exit(1);
+ }
+ }
+
+#ifdef HAVE_CAPABILITIES
+ zprivs_caps_init(zprivs);
+
+ /*
+ * If we have initialized the system with no requested
+ * capabilities, change will not have been set
+ * to anything by zprivs_caps_init, As such
+ * we should make sure that when we attempt
+ * to raize privileges that we actually have
+ * a do nothing function to call instead of a
+ * crash :).
+ */
+ if (!zprivs->change)
+ zprivs->change = zprivs_change_null;
+
+#else /* !HAVE_CAPABILITIES */
+ /* we dont have caps. we'll need to maintain rid and saved uid
+ * and change euid back to saved uid (who we presume has all necessary
+ * privileges) whenever we are asked to raise our privileges.
+ *
+ * This is not worth that much security wise, but all we can do.
+ */
+ zprivs_state.zsuid = geteuid();
+ /* only change uid if we don't have the correct one */
+ if ((zprivs_state.zuid) && (zprivs_state.zsuid != zprivs_state.zuid)) {
+ if (setreuid(-1, zprivs_state.zuid)) {
+ fprintf(stderr,
+ "privs_init (uid): could not setreuid, %s\n",
+ safe_strerror(errno));
+ exit(1);
+ }
+ }
+
+ zprivs->change = zprivs_change_uid;
+ zprivs->current_state = zprivs_state_uid;
+#endif /* HAVE_CAPABILITIES */
+}
+
+void zprivs_terminate(struct zebra_privs_t *zprivs)
+{
+ struct zebra_privs_refs_t *refs;
+
+ lib_privs = NULL;
+
+ if (!zprivs) {
+ fprintf(stderr, "%s: no privs struct given, terminating",
+ __func__);
+ exit(0);
+ }
+
+#ifdef HAVE_CAPABILITIES
+ if (zprivs->user || zprivs->group || zprivs->cap_num_p
+ || zprivs->cap_num_i)
+ zprivs_caps_terminate();
+#else /* !HAVE_CAPABILITIES */
+ /* only change uid if we don't have the correct one */
+ if ((zprivs_state.zuid) && (zprivs_state.zsuid != zprivs_state.zuid)) {
+ if (setreuid(zprivs_state.zuid, zprivs_state.zuid)) {
+ fprintf(stderr,
+ "privs_terminate: could not setreuid, %s",
+ safe_strerror(errno));
+ exit(1);
+ }
+ }
+#endif /* HAVE_LCAPS */
+
+ while ((refs = STAILQ_FIRST(&(zprivs->thread_refs))) != NULL) {
+ STAILQ_REMOVE_HEAD(&(zprivs->thread_refs), entry);
+ XFREE(MTYPE_PRIVS, refs);
+ }
+
+ zprivs->change = zprivs_change_null;
+ zprivs->current_state = zprivs_state_null;
+ zprivs_null_state = ZPRIVS_LOWERED;
+ return;
+}
+
+void zprivs_get_ids(struct zprivs_ids_t *ids)
+{
+
+ ids->uid_priv = getuid();
+ (zprivs_state.zuid) ? (ids->uid_normal = zprivs_state.zuid)
+ : (ids->uid_normal = (uid_t)-1);
+ (zprivs_state.zgid) ? (ids->gid_normal = zprivs_state.zgid)
+ : (ids->gid_normal = (uid_t)-1);
+ (zprivs_state.vtygrp) ? (ids->gid_vty = zprivs_state.vtygrp)
+ : (ids->gid_vty = (uid_t)-1);
+
+ return;
+}
diff --git a/lib/privs.h b/lib/privs.h
new file mode 100644
index 0000000..bc07861
--- /dev/null
+++ b/lib/privs.h
@@ -0,0 +1,157 @@
+/*
+ * Zebra privileges header.
+ *
+ * Copyright (C) 2003 Paul Jakma.
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 _ZEBRA_PRIVS_H
+#define _ZEBRA_PRIVS_H
+
+#include <pthread.h>
+#include <stdint.h>
+#include "lib/queue.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* list of zebra capabilities */
+typedef enum {
+ ZCAP_SETID,
+ ZCAP_BIND,
+ ZCAP_NET_ADMIN,
+ ZCAP_SYS_ADMIN,
+ ZCAP_NET_RAW,
+ ZCAP_CHROOT,
+ ZCAP_NICE,
+ ZCAP_PTRACE,
+ ZCAP_DAC_OVERRIDE,
+ ZCAP_READ_SEARCH,
+ ZCAP_FOWNER,
+ ZCAP_IPC_LOCK,
+ ZCAP_SYS_RAWIO,
+ ZCAP_MAX
+} zebra_capabilities_t;
+
+typedef enum {
+ ZPRIVS_LOWERED,
+ ZPRIVS_RAISED,
+ ZPRIVS_UNKNOWN,
+} zebra_privs_current_t;
+
+typedef enum {
+ ZPRIVS_RAISE,
+ ZPRIVS_LOWER,
+} zebra_privs_ops_t;
+
+struct zebra_privs_refs_t {
+ STAILQ_ENTRY(zebra_privs_refs_t) entry;
+ pthread_t tid;
+ uint32_t refcount;
+ const char *raised_in_funcname;
+};
+
+struct zebra_privs_t {
+ zebra_capabilities_t *caps_p; /* caps required for operation */
+ zebra_capabilities_t *caps_i; /* caps to allow inheritance of */
+ int cap_num_p; /* number of caps in arrays */
+ int cap_num_i;
+
+ /* Mutex and counter used to avoid race conditions in multi-threaded
+ * processes. If privs status is process-wide, we need to
+ * control changes to the privilege status among threads.
+ * If privs changes are per-thread, we need to be able to
+ * manage that too.
+ */
+ pthread_mutex_t mutex;
+ struct zebra_privs_refs_t process_refs;
+
+ STAILQ_HEAD(thread_refs_q, zebra_privs_refs_t) thread_refs;
+
+ const char *user; /* user and group to run as */
+ const char *group;
+ const char *vty_group; /* group to chown vty socket to */
+ /* methods */
+ int (*change)(zebra_privs_ops_t); /* change privileges, 0 on success */
+ zebra_privs_current_t (*current_state)(
+ void); /* current privilege state */
+};
+
+struct zprivs_ids_t {
+ /* -1 is undefined */
+ uid_t uid_priv; /* privileged uid */
+ uid_t uid_normal; /* normal uid */
+ gid_t gid_priv; /* privileged uid */
+ gid_t gid_normal; /* normal uid */
+ gid_t gid_vty; /* vty gid */
+};
+
+extern struct zebra_privs_t *lib_privs;
+
+/* initialise zebra privileges */
+extern void zprivs_preinit(struct zebra_privs_t *zprivs);
+extern void zprivs_init(struct zebra_privs_t *zprivs);
+/* drop all and terminate privileges */
+extern void zprivs_terminate(struct zebra_privs_t *);
+/* query for runtime uid's and gid's, eg vty needs this */
+extern void zprivs_get_ids(struct zprivs_ids_t *);
+
+/*
+ * Wrapper around zprivs, to be used as:
+ * frr_with_privs(&privs) {
+ * ... code ...
+ * if (error)
+ * break; -- break can be used to get out of the block
+ * ... code ...
+ * }
+ *
+ * The argument to frr_with_privs() can be NULL to leave privileges as-is
+ * (mostly useful for conditional privilege-raising, i.e.:)
+ * frr_with_privs(cond ? &privs : NULL) {}
+ *
+ * NB: The code block is always executed, regardless of whether privileges
+ * could be raised or not, or whether NULL was given or not. This is fully
+ * intentional; the user may have configured some RBAC or similar that we
+ * are not aware of, but that allows our code to proceed without privileges.
+ *
+ * The point of this wrapper is to prevent accidental bugs where privileges
+ * are elevated but then not dropped. This can happen when, for example, a
+ * "return", "goto" or "break" in the middle of the elevated-privilege code
+ * skips past the privilege dropping call.
+ *
+ * The macro below uses variable cleanup to drop privileges as soon as the
+ * code block is left in any way (and thus the _privs variable goes out of
+ * scope.) _once is just a trick to run the loop exactly once.
+ */
+extern struct zebra_privs_t *_zprivs_raise(struct zebra_privs_t *privs,
+ const char *funcname);
+extern void _zprivs_lower(struct zebra_privs_t **privs);
+
+#define frr_with_privs(privs) \
+ for (struct zebra_privs_t *_once = NULL, \
+ *_privs __attribute__( \
+ (unused, cleanup(_zprivs_lower))) = \
+ _zprivs_raise(privs, __func__); \
+ _once == NULL; _once = (void *)1)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ZEBRA_PRIVS_H */
diff --git a/lib/ptm_lib.c b/lib/ptm_lib.c
new file mode 100644
index 0000000..e00dd54
--- /dev/null
+++ b/lib/ptm_lib.c
@@ -0,0 +1,487 @@
+/* PTM Library
+ * Copyright (C) 2015 Cumulus Networks, Inc.
+ *
+ * This file is part of Quagga.
+ *
+ * Quagga 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, or (at your option) any
+ * later version.
+ *
+ * Quagga 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
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include "csv.h"
+#include "ptm_lib.h"
+
+#define DEBUG_E 0
+#define DEBUG_V 0
+
+#define ERRLOG(fmt, ...) \
+ do { \
+ if (DEBUG_E) \
+ fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \
+ __LINE__, __func__, ##__VA_ARGS__); \
+ } while (0)
+
+#define DLOG(fmt, ...) \
+ do { \
+ if (DEBUG_V) \
+ fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \
+ __LINE__, __func__, ##__VA_ARGS__); \
+ } while (0)
+
+typedef struct ptm_lib_msg_ctxt_s {
+ int cmd_id;
+ csv_t *csv;
+ ptmlib_msg_type type;
+} ptm_lib_msg_ctxt_t;
+
+static csv_record_t *_ptm_lib_encode_header(csv_t *csv, csv_record_t *rec,
+ int msglen, int version, int type,
+ int cmd_id, char *client_name)
+{
+ char msglen_buf[16], vers_buf[16], type_buf[16], cmdid_buf[16];
+ char client_buf[32];
+ csv_record_t *rec1;
+
+ snprintf(msglen_buf, sizeof(msglen_buf), "%4d", msglen);
+ snprintf(vers_buf, sizeof(vers_buf), "%4d", version);
+ snprintf(type_buf, sizeof(type_buf), "%4d", type);
+ snprintf(cmdid_buf, sizeof(cmdid_buf), "%4d", cmd_id);
+ snprintf(client_buf, 17, "%16.16s", client_name);
+ if (rec) {
+ rec1 = csv_encode_record(csv, rec, 5, msglen_buf, vers_buf,
+ type_buf, cmdid_buf, client_buf);
+ } else {
+ rec1 = csv_encode(csv, 5, msglen_buf, vers_buf, type_buf,
+ cmdid_buf, client_buf);
+ }
+ return (rec1);
+}
+
+static int _ptm_lib_decode_header(csv_t *csv, int *msglen, int *version,
+ int *type, int *cmd_id, char *client_name)
+{
+ char *hdr;
+ csv_record_t *rec;
+ csv_field_t *fld;
+ int i, j;
+
+ csv_decode(csv, NULL);
+ rec = csv_record_iter(csv);
+ if (rec == NULL) {
+ DLOG("malformed CSV\n");
+ return -1;
+ }
+ hdr = csv_field_iter(rec, &fld);
+ if (hdr == NULL) {
+ DLOG("malformed CSV\n");
+ return -1;
+ }
+ *msglen = atoi(hdr);
+ hdr = csv_field_iter_next(&fld);
+ if (hdr == NULL) {
+ DLOG("malformed CSV\n");
+ return -1;
+ }
+ *version = atoi(hdr);
+ hdr = csv_field_iter_next(&fld);
+ if (hdr == NULL) {
+ DLOG("malformed CSV\n");
+ return -1;
+ }
+ *type = atoi(hdr);
+ hdr = csv_field_iter_next(&fld);
+ if (hdr == NULL) {
+ DLOG("malformed CSV\n");
+ return -1;
+ }
+ *cmd_id = atoi(hdr);
+ hdr = csv_field_iter_next(&fld);
+ if (hdr == NULL) {
+ DLOG("malformed CSV\n");
+ return -1;
+ }
+ /* remove leading spaces */
+ for (i = j = 0; i < csv_field_len(fld); i++) {
+ if (!isspace((unsigned char)hdr[i])) {
+ client_name[j] = hdr[i];
+ j++;
+ }
+ }
+ client_name[j] = '\0';
+
+ return 0;
+}
+
+int ptm_lib_append_msg(ptm_lib_handle_t *hdl, void *ctxt, const char *key,
+ const char *val)
+{
+ ptm_lib_msg_ctxt_t *p_ctxt = ctxt;
+ csv_t *csv;
+ csv_record_t *mh_rec, *rec;
+
+ if (!p_ctxt) {
+ ERRLOG("%s: no context \n", __func__);
+ return -1;
+ }
+
+ csv = p_ctxt->csv;
+ mh_rec = csv_record_iter(csv);
+ rec = csv_record_iter_next(mh_rec);
+
+ /* append to the hdr record */
+ rec = csv_append_record(csv, rec, 1, key);
+ if (!rec) {
+ ERRLOG("%s: Could not append key \n", __func__);
+ return -1;
+ }
+
+ rec = csv_record_iter_next(rec);
+ /* append to the data record */
+ rec = csv_append_record(csv, rec, 1, val);
+ if (!rec) {
+ ERRLOG("%s: Could not append val \n", __func__);
+ return -1;
+ }
+
+ /* update the msg hdr */
+ _ptm_lib_encode_header(csv, mh_rec, (csvlen(csv) - PTMLIB_MSG_HDR_LEN),
+ PTMLIB_MSG_VERSION, p_ctxt->type, p_ctxt->cmd_id,
+ hdl->client_name);
+
+ return 0;
+}
+
+int ptm_lib_init_msg(ptm_lib_handle_t *hdl, int cmd_id, int type, void *in_ctxt,
+ void **out_ctxt)
+{
+ ptm_lib_msg_ctxt_t *p_ctxt;
+ ptm_lib_msg_ctxt_t *p_in_ctxt = in_ctxt;
+ csv_t *csv;
+ csv_record_t *rec, *d_rec;
+
+ /* Initialize csv for using discrete record buffers */
+ csv = csv_init(NULL, NULL, PTMLIB_MSG_SZ);
+
+ if (!csv) {
+ ERRLOG("%s: Could not allocate csv \n", __func__);
+ return -1;
+ }
+
+ rec = _ptm_lib_encode_header(csv, NULL, 0, PTMLIB_MSG_VERSION, type,
+ cmd_id, hdl->client_name);
+
+ if (!rec) {
+ ERRLOG("%s: Could not allocate record \n", __func__);
+ csv_clean(csv);
+ csv_free(csv);
+ return -1;
+ }
+
+ p_ctxt = calloc(1, sizeof(*p_ctxt));
+ if (!p_ctxt) {
+ ERRLOG("%s: Could not allocate context \n", __func__);
+ csv_clean(csv);
+ csv_free(csv);
+ return -1;
+ }
+
+ p_ctxt->csv = csv;
+ p_ctxt->cmd_id = cmd_id;
+ p_ctxt->type = type;
+
+ *(ptm_lib_msg_ctxt_t **)out_ctxt = p_ctxt;
+
+ /* caller supplied a context to initialize with? */
+ if (p_in_ctxt) {
+ /* insert the hdr rec */
+ rec = csv_record_iter(p_in_ctxt->csv);
+ csv_clone_record(p_in_ctxt->csv, rec, &d_rec);
+ csv_insert_record(csv, d_rec);
+ /* insert the data rec */
+ rec = csv_record_iter_next(rec);
+ csv_clone_record(p_in_ctxt->csv, rec, &d_rec);
+ csv_insert_record(csv, d_rec);
+ }
+ return 0;
+}
+
+int ptm_lib_cleanup_msg(ptm_lib_handle_t *hdl, void *ctxt)
+{
+ ptm_lib_msg_ctxt_t *p_ctxt = ctxt;
+ csv_t *csv;
+
+ if (!p_ctxt) {
+ ERRLOG("%s: no context \n", __func__);
+ return -1;
+ }
+
+ csv = p_ctxt->csv;
+
+ csv_clean(csv);
+ csv_free(csv);
+ free(p_ctxt);
+
+ return 0;
+}
+
+int ptm_lib_complete_msg(ptm_lib_handle_t *hdl, void *ctxt, char *buf, int *len)
+{
+ ptm_lib_msg_ctxt_t *p_ctxt = ctxt;
+ csv_t *csv;
+ csv_record_t *rec;
+
+ if (!p_ctxt) {
+ ERRLOG("%s: no context \n", __func__);
+ return -1;
+ }
+
+ csv = p_ctxt->csv;
+ rec = csv_record_iter(csv);
+
+ _ptm_lib_encode_header(csv, rec, (csvlen(csv) - PTMLIB_MSG_HDR_LEN),
+ PTMLIB_MSG_VERSION, p_ctxt->type, p_ctxt->cmd_id,
+ hdl->client_name);
+
+ /* parse csv contents into string */
+ if (buf && len) {
+ if (csv_serialize(csv, buf, *len)) {
+ ERRLOG("%s: cannot serialize\n", __func__);
+ return -1;
+ }
+ *len = csvlen(csv);
+ }
+
+ csv_clean(csv);
+ csv_free(csv);
+ free(p_ctxt);
+
+ return 0;
+}
+
+int ptm_lib_find_key_in_msg(void *ctxt, const char *key, char *val)
+{
+ ptm_lib_msg_ctxt_t *p_ctxt = ctxt;
+ csv_t *csv = p_ctxt->csv;
+ csv_record_t *hrec, *drec;
+ csv_field_t *hfld, *dfld;
+ char *hstr, *dstr;
+
+ /**
+ * skip over ptm hdr if present
+ * The next hdr is the keys (column name)
+ * The next hdr is the data
+ */
+ if (csv_num_records(csv) > 2) {
+ hrec = csv_record_iter(csv);
+ hrec = csv_record_iter_next(hrec);
+ } else {
+ hrec = csv_record_iter(csv);
+ }
+ drec = csv_record_iter_next(hrec);
+ val[0] = '\0';
+ for (hstr = csv_field_iter(hrec, &hfld),
+ dstr = csv_field_iter(drec, &dfld);
+ (hstr && dstr); hstr = csv_field_iter_next(&hfld),
+ dstr = csv_field_iter_next(&dfld)) {
+ if (!strncmp(hstr, key, csv_field_len(hfld))) {
+ snprintf(val, csv_field_len(dfld) + 1, "%s", dstr);
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+static int _ptm_lib_read_ptm_socket(int fd, char *buf, int len)
+{
+ int retries = 0, rc;
+ int bytes_read = 0;
+
+ while (bytes_read != len) {
+ rc = recv(fd, (void *)(buf + bytes_read), (len - bytes_read),
+ MSG_DONTWAIT);
+ if (rc <= 0) {
+ if (errno && (errno != EAGAIN)
+ && (errno != EWOULDBLOCK)) {
+ ERRLOG("fatal recv error(%s), closing connection, rc %d\n",
+ strerror(errno), rc);
+ return (rc);
+ } else {
+ if (retries++ < 2) {
+ usleep(10000);
+ continue;
+ }
+ DLOG("max retries - recv error(%d - %s) bytes read %d (%d)\n",
+ errno, strerror(errno), bytes_read, len);
+ return (bytes_read);
+ }
+ break;
+ } else {
+ bytes_read += rc;
+ }
+ }
+
+ return bytes_read;
+}
+
+int ptm_lib_process_msg(ptm_lib_handle_t *hdl, int fd, char *inbuf, int inlen,
+ void *arg)
+{
+ int rc, len;
+ char client_name[32];
+ int cmd_id = 0, type = 0, ver = 0, msglen = 0;
+ csv_t *csv;
+ ptm_lib_msg_ctxt_t *p_ctxt = NULL;
+
+ len = _ptm_lib_read_ptm_socket(fd, inbuf, PTMLIB_MSG_HDR_LEN);
+ if (len <= 0)
+ return (len);
+
+ csv = csv_init(NULL, inbuf, PTMLIB_MSG_HDR_LEN);
+
+ if (!csv) {
+ DLOG("Cannot allocate csv for hdr\n");
+ return -1;
+ }
+
+ rc = _ptm_lib_decode_header(csv, &msglen, &ver, &type, &cmd_id,
+ client_name);
+
+ csv_clean(csv);
+ csv_free(csv);
+
+ if (rc < 0) {
+ /* could not decode the CSV - maybe its legacy cmd?
+ * get the entire cmd from the socket and see if we can process
+ * it
+ */
+ if (len == PTMLIB_MSG_HDR_LEN) {
+ len += _ptm_lib_read_ptm_socket(
+ fd, (inbuf + PTMLIB_MSG_HDR_LEN),
+ inlen - PTMLIB_MSG_HDR_LEN);
+ if (len <= 0)
+ return (len);
+ }
+
+ inbuf[len] = '\0';
+ /* we only support the get-status cmd */
+ if (strcmp(inbuf, PTMLIB_CMD_GET_STATUS)) {
+ DLOG("unsupported legacy cmd %s\n", inbuf);
+ return -1;
+ }
+ /* internally create a csv-style cmd */
+ ptm_lib_init_msg(hdl, 0, PTMLIB_MSG_TYPE_CMD, NULL,
+ (void *)&p_ctxt);
+ if (!p_ctxt) {
+ DLOG("couldnt allocate context\n");
+ return -1;
+ }
+ ptm_lib_append_msg(hdl, p_ctxt, "cmd", PTMLIB_CMD_GET_STATUS);
+
+ } else {
+
+ if (msglen > inlen) {
+ DLOG("msglen [%d] > inlen [%d]\n", msglen, inlen);
+ return -1;
+ }
+
+ /* read the rest of the msg */
+ len = _ptm_lib_read_ptm_socket(fd, inbuf, msglen);
+ if (len <= 0) {
+ return (len);
+ }
+
+ inbuf[len] = '\0';
+
+ csv = csv_init(NULL, NULL, PTMLIB_MSG_SZ);
+ if (!csv) {
+ ERRLOG("Cannot allocate csv for msg\n");
+ return -1;
+ }
+
+ csv_decode(csv, inbuf);
+ p_ctxt = calloc(1, sizeof(*p_ctxt));
+ if (!p_ctxt) {
+ ERRLOG("%s: Could not allocate context \n", __func__);
+ csv_clean(csv);
+ csv_free(csv);
+ return -1;
+ }
+
+ p_ctxt->csv = csv;
+ p_ctxt->cmd_id = cmd_id;
+ p_ctxt->type = type;
+ }
+
+ switch (p_ctxt->type) {
+ case PTMLIB_MSG_TYPE_NOTIFICATION:
+ if (hdl->notify_cb)
+ hdl->notify_cb(arg, p_ctxt);
+ break;
+ case PTMLIB_MSG_TYPE_CMD:
+ if (hdl->cmd_cb)
+ hdl->cmd_cb(arg, p_ctxt);
+ break;
+ case PTMLIB_MSG_TYPE_RESPONSE:
+ if (hdl->response_cb)
+ hdl->response_cb(arg, p_ctxt);
+ break;
+ default:
+ return -1;
+ }
+
+ csv_clean(p_ctxt->csv);
+ csv_free(p_ctxt->csv);
+ free(p_ctxt);
+
+ return len;
+}
+
+ptm_lib_handle_t *ptm_lib_register(char *client_name, ptm_cmd_cb cmd_cb,
+ ptm_notify_cb notify_cb,
+ ptm_response_cb response_cb)
+{
+ ptm_lib_handle_t *hdl;
+
+ hdl = calloc(1, sizeof(*hdl));
+
+ if (hdl) {
+ strncpy(hdl->client_name, client_name, PTMLIB_MAXNAMELEN - 1);
+ hdl->cmd_cb = cmd_cb;
+ hdl->notify_cb = notify_cb;
+ hdl->response_cb = response_cb;
+ }
+
+ return hdl;
+}
+
+void ptm_lib_deregister(ptm_lib_handle_t *hdl)
+{
+ if (hdl) {
+ memset(hdl, 0x00, sizeof(*hdl));
+ free(hdl);
+ }
+}
diff --git a/lib/ptm_lib.h b/lib/ptm_lib.h
new file mode 100644
index 0000000..c217040
--- /dev/null
+++ b/lib/ptm_lib.h
@@ -0,0 +1,76 @@
+/* PTM Library
+ * Copyright (C) 2015 Cumulus Networks, Inc.
+ *
+ * This file is part of Quagga.
+ *
+ * Quagga 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, or (at your option) any
+ * later version.
+ *
+ * Quagga 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 __PTM_LIB_H__
+#define __PTM_LIB_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define PTMLIB_MSG_SZ 1024
+#define PTMLIB_MSG_HDR_LEN 37
+#define PTMLIB_MSG_VERSION 2
+#define PTMLIB_MAXNAMELEN 32
+
+#define PTMLIB_CMD_GET_STATUS "get-status"
+#define PTMLIB_CMD_GET_BFD_CLIENT "get-bfd-client"
+#define PTMLIB_CMD_START_BFD_SESS "start-bfd-sess"
+#define PTMLIB_CMD_STOP_BFD_SESS "stop-bfd-sess"
+
+typedef enum {
+ PTMLIB_MSG_TYPE_NOTIFICATION = 1,
+ PTMLIB_MSG_TYPE_CMD,
+ PTMLIB_MSG_TYPE_RESPONSE,
+} ptmlib_msg_type;
+
+typedef enum {
+ MODULE_BFD = 0,
+ MODULE_LLDP,
+ MODULE_MAX,
+} ptmlib_mod_type;
+
+typedef int (*ptm_cmd_cb)(void *data, void *arg);
+typedef int (*ptm_notify_cb)(void *data, void *arg);
+typedef int (*ptm_response_cb)(void *data, void *arg);
+typedef int (*ptm_log_cb)(void *data, void *arg, ...);
+
+typedef struct ptm_lib_handle_s {
+ char client_name[PTMLIB_MAXNAMELEN];
+ ptm_cmd_cb cmd_cb;
+ ptm_notify_cb notify_cb;
+ ptm_response_cb response_cb;
+} ptm_lib_handle_t;
+
+/* Prototypes */
+int ptm_lib_process_msg(ptm_lib_handle_t *, int, char *, int, void *);
+ptm_lib_handle_t *ptm_lib_register(char *, ptm_cmd_cb, ptm_notify_cb,
+ ptm_response_cb);
+void ptm_lib_deregister(ptm_lib_handle_t *);
+int ptm_lib_find_key_in_msg(void *, const char *, char *);
+int ptm_lib_init_msg(ptm_lib_handle_t *, int, int, void *, void **);
+int ptm_lib_append_msg(ptm_lib_handle_t *, void *, const char *, const char *);
+int ptm_lib_complete_msg(ptm_lib_handle_t *, void *, char *, int *);
+int ptm_lib_cleanup_msg(ptm_lib_handle_t *, void *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/pullwr.c b/lib/pullwr.c
new file mode 100644
index 0000000..5e83698
--- /dev/null
+++ b/lib/pullwr.c
@@ -0,0 +1,274 @@
+/*
+ * Pull-driven write event handler
+ * Copyright (C) 2019 David Lamparter
+ *
+ * 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 "pullwr.h"
+#include "memory.h"
+#include "monotime.h"
+
+/* defaults */
+#define PULLWR_THRESH 16384 /* size at which we start to call write() */
+#define PULLWR_MAXSPIN 2500 /* max µs to spend grabbing more data */
+
+struct pullwr {
+ int fd;
+ struct thread_master *tm;
+ /* writer == NULL <=> we're idle */
+ struct thread *writer;
+
+ void *arg;
+ void (*fill)(void *, struct pullwr *);
+ void (*err)(void *, struct pullwr *, bool);
+
+ /* ring buffer (although it's "un-ringed" on resizing, it WILL wrap
+ * around if data is trickling in while keeping it at a constant size)
+ */
+ size_t bufsz, valid, pos;
+ uint64_t total_written;
+ char *buffer;
+
+ size_t thresh; /* PULLWR_THRESH */
+ int64_t maxspin; /* PULLWR_MAXSPIN */
+};
+
+DEFINE_MTYPE_STATIC(LIB, PULLWR_HEAD, "pull-driven write controller");
+DEFINE_MTYPE_STATIC(LIB, PULLWR_BUF, "pull-driven write buffer");
+
+static void pullwr_run(struct thread *t);
+
+struct pullwr *_pullwr_new(struct thread_master *tm, int fd,
+ void *arg,
+ void (*fill)(void *, struct pullwr *),
+ void (*err)(void *, struct pullwr *, bool))
+{
+ struct pullwr *pullwr;
+
+ pullwr = XCALLOC(MTYPE_PULLWR_HEAD, sizeof(*pullwr));
+ pullwr->fd = fd;
+ pullwr->tm = tm;
+ pullwr->arg = arg;
+ pullwr->fill = fill;
+ pullwr->err = err;
+
+ pullwr->thresh = PULLWR_THRESH;
+ pullwr->maxspin = PULLWR_MAXSPIN;
+
+ return pullwr;
+}
+
+void pullwr_del(struct pullwr *pullwr)
+{
+ THREAD_OFF(pullwr->writer);
+
+ XFREE(MTYPE_PULLWR_BUF, pullwr->buffer);
+ XFREE(MTYPE_PULLWR_HEAD, pullwr);
+}
+
+void pullwr_cfg(struct pullwr *pullwr, int64_t max_spin_usec,
+ size_t write_threshold)
+{
+ pullwr->maxspin = max_spin_usec ?: PULLWR_MAXSPIN;
+ pullwr->thresh = write_threshold ?: PULLWR_THRESH;
+}
+
+void pullwr_bump(struct pullwr *pullwr)
+{
+ if (pullwr->writer)
+ return;
+
+ thread_add_timer(pullwr->tm, pullwr_run, pullwr, 0, &pullwr->writer);
+}
+
+static size_t pullwr_iov(struct pullwr *pullwr, struct iovec *iov)
+{
+ size_t len1;
+
+ if (pullwr->valid == 0)
+ return 0;
+
+ if (pullwr->pos + pullwr->valid <= pullwr->bufsz) {
+ iov[0].iov_base = pullwr->buffer + pullwr->pos;
+ iov[0].iov_len = pullwr->valid;
+ return 1;
+ }
+
+ len1 = pullwr->bufsz - pullwr->pos;
+
+ iov[0].iov_base = pullwr->buffer + pullwr->pos;
+ iov[0].iov_len = len1;
+ iov[1].iov_base = pullwr->buffer;
+ iov[1].iov_len = pullwr->valid - len1;
+ return 2;
+}
+
+static void pullwr_resize(struct pullwr *pullwr, size_t need)
+{
+ struct iovec iov[2];
+ size_t niov, newsize;
+ char *newbuf;
+
+ /* the buffer is maintained at pullwr->thresh * 2 since we'll be
+ * trying to fill it as long as it's anywhere below pullwr->thresh.
+ * That means we frequently end up a little short of it and then write
+ * something that goes over the threshold. So, just use double.
+ */
+ if (need) {
+ /* resize up */
+ if (pullwr->bufsz - pullwr->valid >= need)
+ return;
+
+ newsize = MAX((pullwr->valid + need) * 2, pullwr->thresh * 2);
+ newbuf = XMALLOC(MTYPE_PULLWR_BUF, newsize);
+ } else if (!pullwr->valid) {
+ /* resize down, buffer empty */
+ newsize = 0;
+ newbuf = NULL;
+ } else {
+ /* resize down */
+ if (pullwr->bufsz - pullwr->valid < pullwr->thresh)
+ return;
+ newsize = MAX(pullwr->valid, pullwr->thresh * 2);
+ newbuf = XMALLOC(MTYPE_PULLWR_BUF, newsize);
+ }
+
+ niov = pullwr_iov(pullwr, iov);
+ if (niov >= 1) {
+ memcpy(newbuf, iov[0].iov_base, iov[0].iov_len);
+ if (niov >= 2)
+ memcpy(newbuf + iov[0].iov_len,
+ iov[1].iov_base, iov[1].iov_len);
+ }
+
+ XFREE(MTYPE_PULLWR_BUF, pullwr->buffer);
+ pullwr->buffer = newbuf;
+ pullwr->bufsz = newsize;
+ pullwr->pos = 0;
+}
+
+void pullwr_write(struct pullwr *pullwr, const void *data, size_t len)
+{
+ pullwr_resize(pullwr, len);
+
+ if (pullwr->pos + pullwr->valid > pullwr->bufsz) {
+ size_t pos;
+
+ pos = (pullwr->pos + pullwr->valid) % pullwr->bufsz;
+ memcpy(pullwr->buffer + pos, data, len);
+ } else {
+ size_t max1, len1;
+ max1 = pullwr->bufsz - (pullwr->pos + pullwr->valid);
+ max1 = MIN(max1, len);
+
+ memcpy(pullwr->buffer + pullwr->pos + pullwr->valid,
+ data, max1);
+ len1 = len - max1;
+
+ if (len1)
+ memcpy(pullwr->buffer, (char *)data + max1, len1);
+
+ }
+ pullwr->valid += len;
+
+ pullwr_bump(pullwr);
+}
+
+static void pullwr_run(struct thread *t)
+{
+ struct pullwr *pullwr = THREAD_ARG(t);
+ struct iovec iov[2];
+ size_t niov, lastvalid;
+ ssize_t nwr;
+ struct timeval t0;
+ bool maxspun = false;
+
+ monotime(&t0);
+
+ do {
+ lastvalid = pullwr->valid - 1;
+ while (pullwr->valid < pullwr->thresh
+ && pullwr->valid != lastvalid
+ && !maxspun) {
+ lastvalid = pullwr->valid;
+ pullwr->fill(pullwr->arg, pullwr);
+
+ /* check after doing at least one fill() call so we
+ * don't spin without making progress on slow boxes
+ */
+ if (!maxspun && monotime_since(&t0, NULL)
+ >= pullwr->maxspin)
+ maxspun = true;
+ }
+
+ if (pullwr->valid == 0) {
+ /* we made a fill() call above that didn't feed any
+ * data in, and we have nothing more queued, so we go
+ * into idle, i.e. no calling thread_add_write()
+ */
+ pullwr_resize(pullwr, 0);
+ return;
+ }
+
+ niov = pullwr_iov(pullwr, iov);
+ assert(niov);
+
+ nwr = writev(pullwr->fd, iov, niov);
+ if (nwr < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK)
+ break;
+ pullwr->err(pullwr->arg, pullwr, false);
+ return;
+ }
+
+ if (nwr == 0) {
+ pullwr->err(pullwr->arg, pullwr, true);
+ return;
+ }
+
+ pullwr->total_written += nwr;
+ pullwr->valid -= nwr;
+ pullwr->pos += nwr;
+ pullwr->pos %= pullwr->bufsz;
+ } while (pullwr->valid == 0 && !maxspun);
+ /* pullwr->valid != 0 implies we did an incomplete write, i.e. socket
+ * is full and we go wait until it's available for writing again.
+ */
+
+ thread_add_write(pullwr->tm, pullwr_run, pullwr, pullwr->fd,
+ &pullwr->writer);
+
+ /* if we hit the time limit, just keep the buffer, we'll probably need
+ * it anyway & another run is already coming up.
+ */
+ if (!maxspun)
+ pullwr_resize(pullwr, 0);
+}
+
+void pullwr_stats(struct pullwr *pullwr, uint64_t *total_written,
+ size_t *pending, size_t *kernel_pending)
+{
+ int tmp;
+
+ *total_written = pullwr->total_written;
+ *pending = pullwr->valid;
+
+ if (ioctl(pullwr->fd, TIOCOUTQ, &tmp) != 0)
+ tmp = 0;
+ *kernel_pending = tmp;
+}
diff --git a/lib/pullwr.h b/lib/pullwr.h
new file mode 100644
index 0000000..a0e89e0
--- /dev/null
+++ b/lib/pullwr.h
@@ -0,0 +1,118 @@
+/*
+ * Pull-driven write event handler
+ * Copyright (C) 2019 David Lamparter
+ *
+ * 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 _WRITEPOLL_H
+#define _WRITEPOLL_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "thread.h"
+#include "stream.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct pullwr;
+
+/* This is a "pull-driven" write event handler. Instead of having some buffer
+ * or being driven by the availability of data, it triggers on the space being
+ * available on the socket for data to be written on and then calls fill() to
+ * get data to be sent.
+ *
+ * pullwr_* maintains an "idle" vs. "active" state, going into idle when a
+ * fill() call completes without feeing more data into it. The overall
+ * semantics are:
+ * - to put data out, call pullwr_write(). This is possible from both inside
+ * fill() callbacks or anywhere else. Doing so puts the pullwr into
+ * active state.
+ * - in active state, the fill() callback will be called and should feed more
+ * data in. It should NOT loop to push out more than one "unit" of data;
+ * the pullwr code handles this by calling fill() until it has enough data.
+ * - if there's nothing more to be sent, fill() returns without doing anything
+ * and pullwr goes into idle state after flushing all buffered data out.
+ * - when new data becomes available, pullwr_bump() should be called to put
+ * the pullwr back into active mode so it will collect data from fill(),
+ * or you can directly call pullwr_write().
+ * - only calling pullwr_write() from within fill() is the cleanest way of
+ * doing things.
+ *
+ * When the err() callback is called, the pullwr should be considered unusable
+ * and released with pullwr_del(). This can be done from inside the callback,
+ * the pullwr code holds no more references on it when calling err().
+ */
+extern struct pullwr *_pullwr_new(struct thread_master *tm, int fd,
+ void *arg,
+ void (*fill)(void *, struct pullwr *),
+ void (*err)(void *, struct pullwr *, bool eof));
+extern void pullwr_del(struct pullwr *pullwr);
+
+/* type-checking wrapper. makes sure fill() and err() take a first argument
+ * whose type is identical to the type of arg.
+ * => use "void fill(struct mystruct *arg, ...)" - no "void *arg"
+ */
+#define pullwr_new(tm, fd, arg, fill, err) ({ \
+ void (*fill_typechk)(typeof(arg), struct pullwr *) = fill; \
+ void (*err_typechk)(typeof(arg), struct pullwr *, bool) = err; \
+ _pullwr_new(tm, fd, arg, (void *)fill_typechk, (void *)err_typechk); \
+})
+
+/* max_spin_usec is the time after which the pullwr event handler will stop
+ * trying to get more data from fill() and yield control back to the
+ * thread_master. It does reschedule itself to continue later; this is
+ * only to make sure we don't freeze the entire process if we're piping a
+ * lot of data to a local endpoint that reads quickly (i.e. no backpressure)
+ *
+ * default: 2500 (2.5 ms)
+ *
+ * write_threshold is the amount of data buffered from fill() calls at which
+ * the pullwr code starts calling write(). But this is not a "limit".
+ * pullwr will keep poking fill() for more data until
+ * (a) max_spin_usec is reached; fill() will be called again later after
+ * returning to the thread_master to give other events a chance to run
+ * (b) fill() returns without pushing any data onto the pullwr with
+ * pullwr_write(), so fill() will NOT be called again until a call to
+ * pullwr_bump() or pullwr_write() comes in.
+ *
+ * default: 16384 (16 kB)
+ *
+ * passing 0 for either value (or not calling it at all) uses the default.
+ */
+extern void pullwr_cfg(struct pullwr *pullwr, int64_t max_spin_usec,
+ size_t write_threshold);
+
+extern void pullwr_bump(struct pullwr *pullwr);
+extern void pullwr_write(struct pullwr *pullwr,
+ const void *data, size_t len);
+
+static inline void pullwr_write_stream(struct pullwr *pullwr,
+ struct stream *s)
+{
+ pullwr_write(pullwr, s->data, stream_get_endp(s));
+}
+
+extern void pullwr_stats(struct pullwr *pullwr, uint64_t *total_written,
+ size_t *pending, size_t *kernel_pending);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _WRITEPOLL_H */
diff --git a/lib/pw.h b/lib/pw.h
new file mode 100644
index 0000000..2fc4a61
--- /dev/null
+++ b/lib/pw.h
@@ -0,0 +1,61 @@
+/* Pseudowire definitions
+ * Copyright (C) 2016 Volta Networks, Inc.
+ *
+ * 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_PW_H
+#define _FRR_PW_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* L2VPN name length. */
+#define L2VPN_NAME_LEN 32
+
+/* Pseudowire type - LDP and BGP use the same values. */
+#define PW_TYPE_ETHERNET_TAGGED 0x0004 /* RFC 4446 */
+#define PW_TYPE_ETHERNET 0x0005 /* RFC 4446 */
+#define PW_TYPE_WILDCARD 0x7FFF /* RFC 4863, RFC 6668 */
+
+/* Pseudowire flags. */
+#define F_PSEUDOWIRE_CWORD 0x01
+
+/* Pseudowire status TLV */
+#define PW_FORWARDING 0
+#define PW_NOT_FORWARDING (1 << 0)
+#define PW_LOCAL_RX_FAULT (1 << 1)
+#define PW_LOCAL_TX_FAULT (1 << 2)
+#define PW_PSN_RX_FAULT (1 << 3)
+#define PW_PSN_TX_FAULT (1 << 4)
+
+/*
+ * Protocol-specific information about the pseudowire.
+ */
+union pw_protocol_fields {
+ struct {
+ struct in_addr lsr_id;
+ uint32_t pwid;
+ char vpn_name[L2VPN_NAME_LEN];
+ } ldp;
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRR_PW_H */
diff --git a/lib/qobj.c b/lib/qobj.c
new file mode 100644
index 0000000..c6cb36c
--- /dev/null
+++ b/lib/qobj.c
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2015-16 David Lamparter, for NetDEF, Inc.
+ *
+ * This file is part of Quagga
+ *
+ * Quagga 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, or (at your option) any
+ * later version.
+ *
+ * Quagga 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 "thread.h"
+#include "memory.h"
+#include "hash.h"
+#include "log.h"
+#include "qobj.h"
+#include "jhash.h"
+#include "network.h"
+
+static uint32_t qobj_hash(const struct qobj_node *node)
+{
+ return (uint32_t)node->nid;
+}
+
+static int qobj_cmp(const struct qobj_node *na, const struct qobj_node *nb)
+{
+ if (na->nid < nb->nid)
+ return -1;
+ if (na->nid > nb->nid)
+ return 1;
+ return 0;
+}
+
+DECLARE_HASH(qobj_nodes, struct qobj_node, nodehash,
+ qobj_cmp, qobj_hash);
+
+static pthread_rwlock_t nodes_lock;
+static struct qobj_nodes_head nodes = { };
+
+
+void qobj_reg(struct qobj_node *node, const struct qobj_nodetype *type)
+{
+ node->type = type;
+ pthread_rwlock_wrlock(&nodes_lock);
+ do {
+ node->nid = (uint64_t)frr_weak_random();
+ node->nid ^= (uint64_t)frr_weak_random() << 32;
+ } while (!node->nid || qobj_nodes_find(&nodes, node));
+ qobj_nodes_add(&nodes, node);
+ pthread_rwlock_unlock(&nodes_lock);
+}
+
+void qobj_unreg(struct qobj_node *node)
+{
+ pthread_rwlock_wrlock(&nodes_lock);
+ qobj_nodes_del(&nodes, node);
+ pthread_rwlock_unlock(&nodes_lock);
+}
+
+struct qobj_node *qobj_get(uint64_t id)
+{
+ struct qobj_node dummy = {.nid = id}, *rv;
+ pthread_rwlock_rdlock(&nodes_lock);
+ rv = qobj_nodes_find(&nodes, &dummy);
+ pthread_rwlock_unlock(&nodes_lock);
+ return rv;
+}
+
+void *qobj_get_typed(uint64_t id, const struct qobj_nodetype *type)
+{
+ struct qobj_node dummy = {.nid = id};
+ struct qobj_node *node;
+ void *rv;
+
+ pthread_rwlock_rdlock(&nodes_lock);
+ node = qobj_nodes_find(&nodes, &dummy);
+
+ /* note: we explicitly hold the lock until after we have checked the
+ * type.
+ * if the caller holds a lock that for example prevents the deletion of
+ * route-maps, we can still race against a delete of something that
+ * isn't
+ * a route-map. */
+ if (!node || node->type != type)
+ rv = NULL;
+ else
+ rv = (char *)node - node->type->node_member_offset;
+
+ pthread_rwlock_unlock(&nodes_lock);
+ return rv;
+}
+
+void qobj_init(void)
+{
+ pthread_rwlock_init(&nodes_lock, NULL);
+ qobj_nodes_init(&nodes);
+}
+
+void qobj_finish(void)
+{
+ struct qobj_node *node;
+ while ((node = qobj_nodes_pop(&nodes)))
+ qobj_nodes_del(&nodes, node);
+ pthread_rwlock_destroy(&nodes_lock);
+}
diff --git a/lib/qobj.h b/lib/qobj.h
new file mode 100644
index 0000000..5012c98
--- /dev/null
+++ b/lib/qobj.h
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2015-16 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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 _QOBJ_H
+#define _QOBJ_H
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <stddef.h>
+
+#include "typesafe.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* reserve a specific amount of bytes for a struct, which can grow up to
+ * that size (or be dummy'd out if not needed)
+ *
+ * note the padding's array size will be an error if it gets negative or zero;
+ * this is intentional to prevent the struct from growing beyond the allocated
+ * space.
+ */
+#ifndef __cplusplus
+#define RESERVED_SPACE_STRUCT(name, fieldname, size) \
+ struct { \
+ struct name fieldname; \
+ char padding##fieldname[size - sizeof(struct name)]; \
+ };
+#else
+#define RESERVED_SPACE_STRUCT(name, fieldname, size) \
+ struct name fieldname; \
+ char padding##fieldname[size - sizeof(struct name)];
+#endif
+
+/* don't need struct definitions for these here. code actually using
+ * these needs to define the struct *before* including this header.
+ * HAVE_QOBJ_xxx should be defined to +1 in that case, like this:
+ *
+ * #if defined(HAVE_QOBJ_NODETYPE_CLI) && HAVE_QOBJ_NODETYPE_CLI < 0
+ * #error include files are in wrong order
+ * #else
+ * #define HAVE_QOBJ_NODETYPE_CLI 1
+ * struct qobj_nodetype_cli { ... }
+ * #endif
+ */
+#ifndef HAVE_QOBJ_NODETYPE_CLI
+#define HAVE_QOBJ_NODETYPE_CLI -1
+struct qobj_nodetype_cli {
+ int dummy;
+};
+#endif
+
+#ifndef HAVE_QOBJ_NODETYPE_CAPNP
+#define HAVE_QOBJ_NODETYPE_CAPNP -1
+struct qobj_nodetype_capnp {
+ int dummy;
+};
+#endif
+
+#include "typesafe.h"
+
+/* each different kind of object will have a global variable of this type,
+ * which can be used by various other pieces to store type-related bits.
+ * type equality can be tested as pointer equality. (cf. QOBJ_GET_TYPESAFE)
+ */
+struct qobj_nodetype {
+ ptrdiff_t node_member_offset;
+ RESERVED_SPACE_STRUCT(qobj_nodetype_cli, cli, 256)
+ RESERVED_SPACE_STRUCT(qobj_nodetype_capnp, capnp, 256)
+};
+
+PREDECL_HASH(qobj_nodes);
+
+/* anchor to be embedded somewhere in the object's struct */
+struct qobj_node {
+ uint64_t nid;
+ struct qobj_nodes_item nodehash;
+ const struct qobj_nodetype *type;
+};
+
+#define QOBJ_FIELDS struct qobj_node qobj_node
+
+/* call these at the end of any _create function (QOBJ_REG)
+ * and beginning of any _destroy function (QOBJ_UNREG) */
+#define QOBJ_REG(n, structname) qobj_reg(&n->qobj_node, &qobj_t_##structname)
+#define QOBJ_UNREG(n) qobj_unreg(&n->qobj_node)
+
+/* internals - should not be directly used without a good reason
+ *
+ * note: qobj_get is essentially never safe to use in MT context because
+ * the object could be deleted by another thread -- and worse, it could be
+ * of the "wrong" type and deleted.
+ *
+ * with qobj_get_typed, the type check is done under lock, which means that
+ * it can be used as long as another lock prevents the deletion of objects
+ * of the expected type.
+ *
+ * in the long this may need another touch, e.g. built-in per-object locking.
+ */
+void qobj_reg(struct qobj_node *node, const struct qobj_nodetype *type);
+void qobj_unreg(struct qobj_node *node);
+struct qobj_node *qobj_get(uint64_t id);
+void *qobj_get_typed(uint64_t id, const struct qobj_nodetype *type);
+
+/* type declarations */
+#define DECLARE_QOBJ_TYPE(structname) \
+ extern const struct qobj_nodetype qobj_t_##structname \
+ /* end */
+#define DEFINE_QOBJ_TYPE(structname) \
+ const struct qobj_nodetype qobj_t_##structname = { \
+ .node_member_offset = \
+ (ptrdiff_t)offsetof(struct structname, qobj_node)} \
+ /* end */
+#define DEFINE_QOBJ_TYPE_INIT(structname, ...) \
+ const struct qobj_nodetype qobj_t_##structname = { \
+ .node_member_offset = \
+ (ptrdiff_t)offsetof(struct structname, qobj_node), \
+ __VA_ARGS__} \
+ /* end */
+
+/* ID dereference with typecheck.
+ * will return NULL if id not found or wrong type. */
+#define QOBJ_GET_TYPESAFE(id, structname) \
+ ((struct structname *)qobj_get_typed((id), &qobj_t_##structname))
+
+#define QOBJ_ID(ptr) ((ptr)->qobj_node.nid)
+#define QOBJ_ID_0SAFE(ptr) \
+ ({ \
+ typeof(ptr) _ptr = (ptr); \
+ _ptr ? _ptr->qobj_node.nid : 0ULL; \
+ })
+
+void qobj_init(void);
+void qobj_finish(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QOBJ_H */
diff --git a/lib/queue.h b/lib/queue.h
new file mode 100644
index 0000000..dab43e3
--- /dev/null
+++ b/lib/queue.h
@@ -0,0 +1,96 @@
+/*
+ * lists and queues implementations
+ *
+ * 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_QUEUE_H
+#define _FRR_QUEUE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if defined(__OpenBSD__) && !defined(STAILQ_HEAD)
+#include "openbsd-queue.h"
+
+/* Try to map FreeBSD implementation to OpenBSD one. */
+#define STAILQ_HEAD(name, type) SIMPLEQ_HEAD(name, type)
+#define STAILQ_HEAD_INITIALIZER(head) SIMPLEQ_HEAD_INITIALIZER(head)
+#define STAILQ_ENTRY(entry) SIMPLEQ_ENTRY(entry)
+
+#define STAILQ_CONCAT(head1, head2) SIMPLEQ_CONCAT(head1, head2)
+#define STAILQ_EMPTY(head) SIMPLEQ_EMPTY(head)
+#define STAILQ_FIRST(head) SIMPLEQ_FIRST(head)
+#define STAILQ_FOREACH(var, head, field) SIMPLEQ_FOREACH(var, head, field)
+#define STAILQ_FOREACH_SAFE(var, head, field, tvar) SIMPLEQ_FOREACH_SAFE(var, head, field, tvar)
+#define STAILQ_INIT(head) SIMPLEQ_INIT(head)
+#define STAILQ_INSERT_AFTER(head, tqelm, elm, field) SIMPLEQ_INSERT_AFTER(head, tqelm, elm, field)
+#define STAILQ_INSERT_HEAD(head, elm, field) SIMPLEQ_INSERT_HEAD(head, elm, field)
+#define STAILQ_INSERT_TAIL(head, elm, field) SIMPLEQ_INSERT_TAIL(head, elm, field)
+#define STAILQ_LAST(head, type, field) \
+ (SIMPLEQ_EMPTY((head)) \
+ ? NULL \
+ : ((struct type *)(void *)((char *)((head)->sqh_last) \
+ - offsetof(struct type, field))))
+#define STAILQ_NEXT(elm, field) SIMPLEQ_NEXT(elm, field)
+#define STAILQ_REMOVE(head, elm, type, field) \
+ do { \
+ if (SIMPLEQ_FIRST((head)) == (elm)) { \
+ SIMPLEQ_REMOVE_HEAD((head), field); \
+ } else { \
+ struct type *curelm = SIMPLEQ_FIRST((head)); \
+ while (SIMPLEQ_NEXT(curelm, field) != (elm)) \
+ curelm = SIMPLEQ_NEXT(curelm, field); \
+ SIMPLEQ_REMOVE_AFTER(head, curelm, field); \
+ } \
+ } while (0)
+#define STAILQ_REMOVE_AFTER(head, elm, field) SIMPLEQ_REMOVE_AFTER(head, elm, field)
+#define STAILQ_REMOVE_HEAD(head, field) SIMPLEQ_REMOVE_HEAD(head, field)
+#define STAILQ_SWAP(head1, head2, type) \
+ do { \
+ struct type *swap_first = STAILQ_FIRST(head1); \
+ struct type **swap_last = (head1)->sqh_last; \
+ STAILQ_FIRST(head1) = STAILQ_FIRST(head2); \
+ (head1)->sqh_last = (head2)->sqh_last; \
+ STAILQ_FIRST(head2) = swap_first; \
+ (head2)->sqh_last = swap_last; \
+ if (STAILQ_EMPTY(head1)) \
+ (head1)->sqh_last = &STAILQ_FIRST(head1); \
+ if (STAILQ_EMPTY(head2)) \
+ (head2)->sqh_last = &STAILQ_FIRST(head2); \
+ } while (0)
+#else
+#include "freebsd-queue.h"
+#endif /* defined(__OpenBSD__) && !defined(STAILQ_HEAD) */
+
+#ifndef TAILQ_POP_FIRST
+#define TAILQ_POP_FIRST(head, field) \
+ ({ typeof((head)->tqh_first) _elm = TAILQ_FIRST(head); \
+ if (_elm) { \
+ if ((TAILQ_NEXT((_elm), field)) != NULL) \
+ TAILQ_NEXT((_elm), field)->field.tqe_prev = \
+ &TAILQ_FIRST(head); \
+ else \
+ (head)->tqh_last = &TAILQ_FIRST(head); \
+ TAILQ_FIRST(head) = TAILQ_NEXT((_elm), field); \
+ }; _elm; })
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRR_QUEUE_H */
diff --git a/lib/resolver.c b/lib/resolver.c
new file mode 100755
index 0000000..0d64ad8
--- /dev/null
+++ b/lib/resolver.c
@@ -0,0 +1,341 @@
+/* C-Ares integration to Quagga mainloop
+ * Copyright (c) 2014-2015 Timo Teräs
+ *
+ * This file is free software: you may copy, redistribute 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <ares.h>
+#include <ares_version.h>
+
+#include "typesafe.h"
+#include "jhash.h"
+#include "thread.h"
+#include "lib_errors.h"
+#include "resolver.h"
+#include "command.h"
+#include "xref.h"
+#include "vrf.h"
+
+XREF_SETUP();
+
+struct resolver_state {
+ ares_channel channel;
+ struct thread_master *master;
+ struct thread *timeout;
+};
+
+static struct resolver_state state;
+static bool resolver_debug;
+
+/* a FD doesn't necessarily map 1:1 to a request; we could be talking to
+ * multiple caches simultaneously, to see which responds fastest.
+ * Theoretically we could also be using the same fd for multiple lookups,
+ * but the c-ares API guarantees an n:1 mapping for fd => channel.
+ *
+ * Either way c-ares makes that decision and we just need to deal with
+ * whatever FDs it gives us.
+ */
+
+DEFINE_MTYPE_STATIC(LIB, ARES_FD, "c-ares (DNS) file descriptor information");
+PREDECL_HASH(resolver_fds);
+
+struct resolver_fd {
+ struct resolver_fds_item itm;
+
+ int fd;
+ struct resolver_state *state;
+ struct thread *t_read, *t_write;
+};
+
+static int resolver_fd_cmp(const struct resolver_fd *a,
+ const struct resolver_fd *b)
+{
+ return numcmp(a->fd, b->fd);
+}
+
+static uint32_t resolver_fd_hash(const struct resolver_fd *item)
+{
+ return jhash_1word(item->fd, 0xacd04c9e);
+}
+
+DECLARE_HASH(resolver_fds, struct resolver_fd, itm, resolver_fd_cmp,
+ resolver_fd_hash);
+
+static struct resolver_fds_head resfds[1] = {INIT_HASH(resfds[0])};
+
+static struct resolver_fd *resolver_fd_get(int fd,
+ struct resolver_state *newstate)
+{
+ struct resolver_fd ref = {.fd = fd}, *res;
+
+ res = resolver_fds_find(resfds, &ref);
+ if (!res && newstate) {
+ res = XCALLOC(MTYPE_ARES_FD, sizeof(*res));
+ res->fd = fd;
+ res->state = newstate;
+ resolver_fds_add(resfds, res);
+
+ if (resolver_debug)
+ zlog_debug("c-ares registered FD %d", fd);
+ }
+ return res;
+}
+
+static void resolver_fd_drop_maybe(struct resolver_fd *resfd)
+{
+ if (resfd->t_read || resfd->t_write)
+ return;
+
+ if (resolver_debug)
+ zlog_debug("c-ares unregistered FD %d", resfd->fd);
+
+ resolver_fds_del(resfds, resfd);
+ XFREE(MTYPE_ARES_FD, resfd);
+}
+
+/* end of FD housekeeping */
+
+static void resolver_update_timeouts(struct resolver_state *r);
+
+static void resolver_cb_timeout(struct thread *t)
+{
+ struct resolver_state *r = THREAD_ARG(t);
+
+ ares_process(r->channel, NULL, NULL);
+ resolver_update_timeouts(r);
+}
+
+static void resolver_cb_socket_readable(struct thread *t)
+{
+ struct resolver_fd *resfd = THREAD_ARG(t);
+ struct resolver_state *r = resfd->state;
+
+ thread_add_read(r->master, resolver_cb_socket_readable, resfd,
+ resfd->fd, &resfd->t_read);
+ /* ^ ordering important:
+ * ares_process_fd may transitively call THREAD_OFF(resfd->t_read)
+ * combined with resolver_fd_drop_maybe, so resfd may be free'd after!
+ */
+ ares_process_fd(r->channel, resfd->fd, ARES_SOCKET_BAD);
+ resolver_update_timeouts(r);
+}
+
+static void resolver_cb_socket_writable(struct thread *t)
+{
+ struct resolver_fd *resfd = THREAD_ARG(t);
+ struct resolver_state *r = resfd->state;
+
+ thread_add_write(r->master, resolver_cb_socket_writable, resfd,
+ resfd->fd, &resfd->t_write);
+ /* ^ ordering important:
+ * ares_process_fd may transitively call THREAD_OFF(resfd->t_write)
+ * combined with resolver_fd_drop_maybe, so resfd may be free'd after!
+ */
+ ares_process_fd(r->channel, ARES_SOCKET_BAD, resfd->fd);
+ resolver_update_timeouts(r);
+}
+
+static void resolver_update_timeouts(struct resolver_state *r)
+{
+ struct timeval *tv, tvbuf;
+
+ THREAD_OFF(r->timeout);
+ tv = ares_timeout(r->channel, NULL, &tvbuf);
+ if (tv) {
+ unsigned int timeoutms = tv->tv_sec * 1000 + tv->tv_usec / 1000;
+
+ thread_add_timer_msec(r->master, resolver_cb_timeout, r,
+ timeoutms, &r->timeout);
+ }
+}
+
+static void ares_socket_cb(void *data, ares_socket_t fd, int readable,
+ int writable)
+{
+ struct resolver_state *r = (struct resolver_state *)data;
+ struct resolver_fd *resfd;
+
+ resfd = resolver_fd_get(fd, (readable || writable) ? r : NULL);
+ if (!resfd)
+ return;
+
+ assert(resfd->state == r);
+
+ if (!readable)
+ THREAD_OFF(resfd->t_read);
+ else if (!resfd->t_read)
+ thread_add_read(r->master, resolver_cb_socket_readable, resfd,
+ fd, &resfd->t_read);
+
+ if (!writable)
+ THREAD_OFF(resfd->t_write);
+ else if (!resfd->t_write)
+ thread_add_write(r->master, resolver_cb_socket_writable, resfd,
+ fd, &resfd->t_write);
+
+ resolver_fd_drop_maybe(resfd);
+}
+
+
+static void ares_address_cb(void *arg, int status, int timeouts,
+ struct hostent *he)
+{
+ struct resolver_query *query = (struct resolver_query *)arg;
+ union sockunion addr[16];
+ void (*callback)(struct resolver_query *, const char *, int,
+ union sockunion *);
+ size_t i;
+
+ callback = query->callback;
+ query->callback = NULL;
+
+ if (status != ARES_SUCCESS) {
+ if (resolver_debug)
+ zlog_debug("[%p] Resolving failed (%s)",
+ query, ares_strerror(status));
+
+ callback(query, ares_strerror(status), -1, NULL);
+ return;
+ }
+
+ for (i = 0; i < array_size(addr) && he->h_addr_list[i] != NULL; i++) {
+ memset(&addr[i], 0, sizeof(addr[i]));
+ addr[i].sa.sa_family = he->h_addrtype;
+ switch (he->h_addrtype) {
+ case AF_INET:
+ memcpy(&addr[i].sin.sin_addr,
+ (uint8_t *)he->h_addr_list[i], he->h_length);
+ break;
+ case AF_INET6:
+ memcpy(&addr[i].sin6.sin6_addr,
+ (uint8_t *)he->h_addr_list[i], he->h_length);
+ break;
+ }
+ }
+
+ if (resolver_debug)
+ zlog_debug("[%p] Resolved with %d results", query, (int)i);
+
+ callback(query, NULL, i, &addr[0]);
+}
+
+static void resolver_cb_literal(struct thread *t)
+{
+ struct resolver_query *query = THREAD_ARG(t);
+ void (*callback)(struct resolver_query *, const char *, int,
+ union sockunion *);
+
+ callback = query->callback;
+ query->callback = NULL;
+
+ callback(query, ARES_SUCCESS, 1, &query->literal_addr);
+}
+
+void resolver_resolve(struct resolver_query *query, int af, vrf_id_t vrf_id,
+ const char *hostname,
+ void (*callback)(struct resolver_query *, const char *,
+ int, union sockunion *))
+{
+ int ret;
+
+ if (hostname == NULL)
+ return;
+
+ if (query->callback != NULL) {
+ flog_err(
+ EC_LIB_RESOLVER,
+ "Trying to resolve '%s', but previous query was not finished yet",
+ hostname);
+ return;
+ }
+
+ query->callback = callback;
+ query->literal_cb = NULL;
+
+ ret = str2sockunion(hostname, &query->literal_addr);
+ if (ret == 0) {
+ if (resolver_debug)
+ zlog_debug("[%p] Resolving '%s' (IP literal)",
+ query, hostname);
+
+ /* for consistency with proper name lookup, don't call the
+ * callback immediately; defer to thread loop
+ */
+ thread_add_timer_msec(state.master, resolver_cb_literal,
+ query, 0, &query->literal_cb);
+ return;
+ }
+
+ if (resolver_debug)
+ zlog_debug("[%p] Resolving '%s'", query, hostname);
+
+ ret = vrf_switch_to_netns(vrf_id);
+ if (ret < 0) {
+ flog_err_sys(EC_LIB_SOCKET, "%s: Can't switch to VRF %u (%s)",
+ __func__, vrf_id, safe_strerror(errno));
+ return;
+ }
+ ares_gethostbyname(state.channel, hostname, af, ares_address_cb, query);
+ ret = vrf_switchback_to_initial();
+ if (ret < 0)
+ flog_err_sys(EC_LIB_SOCKET,
+ "%s: Can't switchback from VRF %u (%s)", __func__,
+ vrf_id, safe_strerror(errno));
+ resolver_update_timeouts(&state);
+}
+
+DEFUN(debug_resolver,
+ debug_resolver_cmd,
+ "[no] debug resolver",
+ NO_STR
+ DEBUG_STR
+ "Debug DNS resolver actions\n")
+{
+ resolver_debug = (argc == 2);
+ return CMD_SUCCESS;
+}
+
+static int resolver_config_write_debug(struct vty *vty);
+static struct cmd_node resolver_debug_node = {
+ .name = "resolver debug",
+ .node = RESOLVER_DEBUG_NODE,
+ .prompt = "",
+ .config_write = resolver_config_write_debug,
+};
+
+static int resolver_config_write_debug(struct vty *vty)
+{
+ if (resolver_debug)
+ vty_out(vty, "debug resolver\n");
+ return 1;
+}
+
+
+void resolver_init(struct thread_master *tm)
+{
+ struct ares_options ares_opts;
+
+ state.master = tm;
+
+ ares_opts = (struct ares_options){
+ .sock_state_cb = &ares_socket_cb,
+ .sock_state_cb_data = &state,
+ .timeout = 2,
+ .tries = 3,
+ };
+
+ ares_init_options(&state.channel, &ares_opts,
+ ARES_OPT_SOCK_STATE_CB | ARES_OPT_TIMEOUT
+ | ARES_OPT_TRIES);
+
+ install_node(&resolver_debug_node);
+ install_element(CONFIG_NODE, &debug_resolver_cmd);
+ install_element(ENABLE_NODE, &debug_resolver_cmd);
+}
diff --git a/lib/resolver.h b/lib/resolver.h
new file mode 100644
index 0000000..9884496
--- /dev/null
+++ b/lib/resolver.h
@@ -0,0 +1,39 @@
+/* C-Ares integration to Quagga mainloop
+ * Copyright (c) 2014-2015 Timo Teräs
+ *
+ * This file is free software: you may copy, redistribute 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.
+ */
+
+#ifndef _FRR_RESOLVER_H
+#define _FRR_RESOLVER_H
+
+#include "thread.h"
+#include "sockunion.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct resolver_query {
+ void (*callback)(struct resolver_query *, const char *errstr, int n,
+ union sockunion *);
+
+ /* used to immediate provide the result if IP literal is passed in */
+ union sockunion literal_addr;
+ struct thread *literal_cb;
+};
+
+void resolver_init(struct thread_master *tm);
+void resolver_resolve(struct resolver_query *query, int af, vrf_id_t vrf_id,
+ const char *hostname,
+ void (*cb)(struct resolver_query *, const char *, int,
+ union sockunion *));
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRR_RESOLVER_H */
diff --git a/lib/ringbuf.c b/lib/ringbuf.c
new file mode 100644
index 0000000..6efa807
--- /dev/null
+++ b/lib/ringbuf.c
@@ -0,0 +1,133 @@
+/*
+ * Circular buffer implementation.
+ * Copyright (C) 2017 Cumulus Networks
+ * Quentin Young
+ *
+ * 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 "ringbuf.h"
+#include "memory.h"
+
+DEFINE_MTYPE_STATIC(LIB, RINGBUFFER, "Ring buffer");
+
+struct ringbuf *ringbuf_new(size_t size)
+{
+ struct ringbuf *buf = XCALLOC(MTYPE_RINGBUFFER, sizeof(struct ringbuf));
+ buf->data = XCALLOC(MTYPE_RINGBUFFER, size);
+ buf->size = size;
+ buf->empty = true;
+ return buf;
+}
+
+void ringbuf_del(struct ringbuf *buf)
+{
+ XFREE(MTYPE_RINGBUFFER, buf->data);
+ XFREE(MTYPE_RINGBUFFER, buf);
+}
+
+size_t ringbuf_remain(struct ringbuf *buf)
+{
+ ssize_t diff = buf->end - buf->start;
+ diff += ((diff == 0) && !buf->empty) ? buf->size : 0;
+ diff += (diff < 0) ? buf->size : 0;
+ return (size_t)diff;
+}
+
+size_t ringbuf_space(struct ringbuf *buf)
+{
+ return buf->size - ringbuf_remain(buf);
+}
+
+size_t ringbuf_put(struct ringbuf *buf, const void *data, size_t size)
+{
+ const uint8_t *dp = data;
+ size_t space = ringbuf_space(buf);
+ size_t copysize = MIN(size, space);
+ size_t tocopy = copysize;
+ if (tocopy >= buf->size - buf->end) {
+ size_t ts = buf->size - buf->end;
+ memcpy(buf->data + buf->end, dp, ts);
+ buf->end = 0;
+ tocopy -= ts;
+ dp += ts;
+ }
+ memcpy(buf->data + buf->end, dp, tocopy);
+ buf->end += tocopy;
+ buf->empty = (buf->start == buf->end) && (buf->empty && !copysize);
+ return copysize;
+}
+
+size_t ringbuf_get(struct ringbuf *buf, void *data, size_t size)
+{
+ uint8_t *dp = data;
+ size_t remain = ringbuf_remain(buf);
+ size_t copysize = MIN(remain, size);
+ size_t tocopy = copysize;
+ if (tocopy >= buf->size - buf->start) {
+ size_t ts = buf->size - buf->start;
+ memcpy(dp, buf->data + buf->start, ts);
+ buf->start = 0;
+ tocopy -= ts;
+ dp += ts;
+ }
+ memcpy(dp, buf->data + buf->start, tocopy);
+ buf->start = buf->start + tocopy;
+ buf->empty = (buf->start == buf->end) && (buf->empty || copysize);
+ return copysize;
+}
+
+size_t ringbuf_peek(struct ringbuf *buf, size_t offset, void *data, size_t size)
+{
+ uint8_t *dp = data;
+ size_t remain = ringbuf_remain(buf);
+ if (offset >= remain)
+ return 0;
+ size_t copysize = MAX(MIN(remain - offset, size), (size_t)0);
+ size_t tocopy = copysize;
+ size_t cstart = (buf->start + offset) % buf->size;
+ if (tocopy >= buf->size - cstart) {
+ size_t ts = buf->size - cstart;
+ memcpy(dp, buf->data + cstart, ts);
+ cstart = 0;
+ tocopy -= ts;
+ dp += ts;
+ }
+ memcpy(dp, buf->data + cstart, tocopy);
+ return copysize;
+}
+
+size_t ringbuf_copy(struct ringbuf *to, struct ringbuf *from, size_t size)
+{
+ size_t tocopy = MIN(ringbuf_space(to), size);
+ uint8_t *cbuf = XCALLOC(MTYPE_TMP, tocopy);
+ tocopy = ringbuf_peek(from, 0, cbuf, tocopy);
+ size_t put = ringbuf_put(to, cbuf, tocopy);
+ XFREE(MTYPE_TMP, cbuf);
+ return put;
+}
+
+void ringbuf_reset(struct ringbuf *buf)
+{
+ buf->start = buf->end = 0;
+ buf->empty = true;
+}
+
+void ringbuf_wipe(struct ringbuf *buf)
+{
+ memset(buf->data, 0x00, buf->size);
+ ringbuf_reset(buf);
+}
diff --git a/lib/ringbuf.h b/lib/ringbuf.h
new file mode 100644
index 0000000..b8f4d97
--- /dev/null
+++ b/lib/ringbuf.h
@@ -0,0 +1,133 @@
+/*
+ * Circular buffer implementation.
+ * Copyright (C) 2017 Cumulus Networks
+ * Quentin Young
+ *
+ * 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_RINGBUF_H_
+#define _FRR_RINGBUF_H_
+
+#include <zebra.h>
+#include <stdint.h>
+
+#include "memory.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct ringbuf {
+ size_t size;
+ ssize_t start;
+ ssize_t end;
+ bool empty;
+ uint8_t *data;
+};
+
+/*
+ * Creates a new ring buffer.
+ *
+ * @param size buffer size, in bytes
+ * @return the newly created buffer
+ */
+struct ringbuf *ringbuf_new(size_t size);
+
+/*
+ * Deletes a ring buffer and frees all associated resources.
+ *
+ * @param buf the ring buffer to destroy
+ */
+void ringbuf_del(struct ringbuf *buf);
+
+/*
+ * Get amount of data left to read from the buffer.
+ *
+ * @return number of readable bytes
+ */
+size_t ringbuf_remain(struct ringbuf *buf);
+
+/*
+ * Get amount of space left to write to the buffer
+ *
+ * @return number of writeable bytes
+ */
+size_t ringbuf_space(struct ringbuf *buf);
+
+
+/*
+ * Put data into the ring buffer.
+ *
+ * @param data the data to put in the buffer
+ * @param size how much of data to put in
+ * @return number of bytes written; will be less than size if there was not
+ * enough space
+ */
+size_t ringbuf_put(struct ringbuf *buf, const void *data, size_t size);
+
+/*
+ * Get data from the ring buffer.
+ *
+ * @param data where to put the data
+ * @param size how much of data to get
+ * @return number of bytes read into data; will be less than size if there was
+ * not enough data to read
+ */
+size_t ringbuf_get(struct ringbuf *buf, void *data, size_t size);
+
+/*
+ * Peek data from the ring buffer.
+ *
+ * @param offset where to get the data from, in bytes offset from the
+ * start of the data
+ * @param data where to put the data
+ * @param size how much data to get
+ * @return number of bytes read into data; will be less than size
+ * if there was not enough data to read; will be -1 if the
+ * offset exceeds the amount of data left in the ring
+ * buffer
+ */
+size_t ringbuf_peek(struct ringbuf *buf, size_t offset, void *data,
+ size_t size);
+
+/*
+ * Copy data from one ringbuf to another.
+ *
+ * @param to destination ringbuf
+ * @param from source ringbuf
+ * @param size how much data to copy
+ * @return amount of data copied
+ */
+size_t ringbuf_copy(struct ringbuf *to, struct ringbuf *from, size_t size);
+
+/*
+ * Reset buffer. Does not wipe.
+ *
+ * @param buf
+ */
+void ringbuf_reset(struct ringbuf *buf);
+
+/*
+ * Reset buffer. Wipes.
+ *
+ * @param buf
+ */
+void ringbuf_wipe(struct ringbuf *buf);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRR_RINGBUF_H_ */
diff --git a/lib/route_opaque.h b/lib/route_opaque.h
new file mode 100644
index 0000000..bf6081c
--- /dev/null
+++ b/lib/route_opaque.h
@@ -0,0 +1,61 @@
+/* Opaque data for Zebra from other daemons.
+ *
+ * Copyright (C) 2021 Donatas Abraitis <donatas.abraitis@gmail.com>
+ *
+ * This file is part of FRRouting (FRR).
+ *
+ * FRR 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, or (at your option) any later version.
+ *
+ * FRR 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_ROUTE_OPAQUE_H
+#define FRR_ROUTE_OPAQUE_H
+
+#include "assert.h"
+#include "zclient.h"
+
+/* copied from bgpd/bgp_community.h */
+#define COMMUNITY_SIZE 4
+/* copied from bgpd/bgp_lcommunity.h */
+#define LCOMMUNITY_SIZE 12
+/* copied from bgpd/bgp_route.h */
+#define BGP_MAX_SELECTION_REASON_STR_BUF 32
+
+struct bgp_zebra_opaque {
+ char aspath[256];
+
+ /* Show at least 10 communities AA:BB */
+ char community[COMMUNITY_SIZE * 20];
+
+ /* Show at least 10 large-communities AA:BB:CC */
+ char lcommunity[LCOMMUNITY_SIZE * 30];
+
+ /* 32 bytes seems enough because of
+ * bgp_path_selection_confed_as_path which is
+ * `Confederation based AS Path`.
+ */
+ char selection_reason[BGP_MAX_SELECTION_REASON_STR_BUF];
+};
+
+struct ospf_zebra_opaque {
+ char path_type[32];
+ char area_id[INET_ADDRSTRLEN];
+ char tag[16];
+};
+
+static_assert(sizeof(struct bgp_zebra_opaque) <= ZAPI_MESSAGE_OPAQUE_LENGTH,
+ "BGP opaque data shouldn't be larger than zebra's buffer");
+static_assert(sizeof(struct ospf_zebra_opaque) <= ZAPI_MESSAGE_OPAQUE_LENGTH,
+ "OSPF opaque data shouldn't be larger than zebra's buffer");
+
+#endif /* FRR_ROUTE_OPAQUE_H */
diff --git a/lib/route_types.pl b/lib/route_types.pl
new file mode 100755
index 0000000..8c216e1
--- /dev/null
+++ b/lib/route_types.pl
@@ -0,0 +1,228 @@
+#!/usr/bin/perl
+##
+## Scan a file of route-type definitions (see eg route_types.txt) and
+## generate a corresponding header file with:
+##
+## - enum of Zserv route-types
+## - redistribute strings for the various Quagga daemons
+##
+## See route_types.txt for the format.
+##
+##
+## Copyright (C) 2009 David Lamparter.
+## This file is part of GNU Zebra.
+##
+## GNU Zebra 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, or (at your option) any
+## later version.
+##
+## GNU Zebra 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 GNU Zebra; see the file COPYING. If not, write to the Free
+## Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+## 02111-1307, USA.
+##
+
+use strict;
+use Getopt::Long;
+
+# input processing
+#
+my @protos;
+my %protodetail;
+
+my %daemons;
+
+my @enabled;
+
+GetOptions ("enabled=s" => \@enabled);
+@enabled = split(/,/,join(',',@enabled));
+
+while (<STDIN>) {
+ # skip comments and empty lines
+ next if (/^\s*(#|$)/);
+
+ # strip whitespace
+ chomp;
+ $_ =~ s/^\s*//;
+ $_ =~ s/\s*$//;
+
+ # match help strings
+ if (/^(ZEBRA_ROUTE_[^\s]+)\s*,\s*"(.*)"$/) {
+ $protodetail{$1}->{'longhelp'} = $2;
+ next;
+ }
+
+ $_ =~ s/\s*,\s*/,/g;
+
+ # else: 8-field line
+ my @f = split(/,/, $_);
+ unless (@f == 9 || @f == 10) {
+ die "invalid input on route_types line $.\n";
+ }
+
+ my $proto = $f[0];
+ $f[3] = $1 if ($f[3] =~ /^'(.*)'$/);
+ $f[7] = $1 if ($f[7] =~ /^"(.*)"$/);
+
+ $protodetail{$proto} = {
+ "number" => scalar @protos,
+ "type" => $f[0],
+ "cname" => $f[1],
+ "daemon" => $f[2],
+ "char" => $f[3],
+ "ipv4" => int($f[4]),
+ "ipv6" => int($f[5]),
+ "redist" => int($f[6]),
+ "shorthelp" => $f[7],
+ "enabled" => $f[8],
+ "restrict2" => $f[9],
+ };
+ push @protos, $proto;
+ $daemons{$f[2]} = {
+ "ipv4" => int($f[4]),
+ "ipv6" => int($f[5])
+ } unless ($f[2] eq "NULL");
+}
+
+# output
+printf <<EOF, $ARGV[0];
+/* Auto-generated from route_types.txt by %s. */
+/* Do not edit! */
+
+#ifndef _FRR_ROUTE_TYPES_H
+#define _FRR_ROUTE_TYPES_H
+
+/* Zebra route's' types. */
+EOF
+
+push @protos, "ZEBRA_ROUTE_MAX";
+my (@protosv4, @protosv6) = ((), ());
+for (my $c = 0; $c < @protos; $c++) {
+ my $p = $protos[$c];
+ printf "#define %-32s %d\n", $p, $c;
+ push @protosv4, $p if ($protodetail{$p}->{"ipv4"});
+ push @protosv6, $p if ($protodetail{$p}->{"ipv6"});
+}
+pop @protos;
+
+sub codelist {
+ my (@protos) = @_;
+ my (@lines) = ();
+ my $str = " \"Codes: ";
+ for my $p (@protos) {
+ next unless (grep $_ eq $protodetail{$p}->{"enabled"}, @enabled);
+ my $s = sprintf("%s - %s, ",
+ $protodetail{$p}->{"char"},
+ $protodetail{$p}->{"shorthelp"});
+ if (length($str . $s) > 70) {
+ $str =~ s/ $//;
+ push @lines, $str . "\\n\" \\\n";
+ $str = " \" ";
+ }
+ $str .= $s;
+ }
+ $str =~ s/ $//;
+ push @lines, $str . "\\n\" \\\n";
+ push @lines, " \" > - selected route, * - FIB route, q - queued, r - rejected, b - backup\\n\"";
+ push @lines, " \" t - trapped, o - offload failure\\n\\n\"";
+
+
+ return join("", @lines);
+}
+
+print "\n";
+printf "#define SHOW_ROUTE_V4_HEADER \\\n%s\n", codelist(@protosv4);
+printf "#define SHOW_ROUTE_V6_HEADER \\\n%s\n", codelist(@protosv6);
+print "\n";
+
+sub collect {
+ my ($daemon, $ipv4, $ipv6, $any) = @_;
+ my (@names, @help) = ((), ());
+ for my $p (@protos) {
+ next if ($protodetail{$p}->{"daemon"} eq $daemon && $daemon ne "zebra");
+ next if ($protodetail{$p}->{"restrict2"} ne "" &&
+ $protodetail{$p}->{"restrict2"} ne $daemon);
+ next if ($protodetail{$p}->{"redist"} eq 0);
+ next unless (grep $_ eq $protodetail{$p}->{"enabled"}, @enabled);
+ next unless (($ipv4 && $protodetail{$p}->{"ipv4"})
+ || ($ipv6 && $protodetail{$p}->{"ipv6"}));
+ push @names, $protodetail{$p}->{"cname"};
+ push @help, " \"".$protodetail{$p}->{"longhelp"}."\\n\"";
+ }
+ if ($any == 1) {
+ push @names, "any";
+ push @help, " \"Any of the above protocols\\n\"";
+ }
+ return ("\"<" . join("|", @names) . ">\"", join(" \\\n", @help));
+}
+
+for my $daemon (sort keys %daemons) {
+ next unless ($daemons{$daemon}->{"ipv4"} || $daemons{$daemon}->{"ipv6"});
+ printf "/* %s */\n", $daemon;
+ if ($daemons{$daemon}->{"ipv4"} && $daemons{$daemon}->{"ipv6"}) {
+ my ($names, $help) = collect($daemon, 1, 1, 0);
+ printf "#define FRR_REDIST_STR_%s \\\n %s\n", uc $daemon, $names;
+ printf "#define FRR_REDIST_HELP_STR_%s \\\n%s\n", uc $daemon, $help;
+
+ ($names, $help) = collect($daemon, 1, 0, 0);
+ printf "#define FRR_IP_REDIST_STR_%s \\\n %s\n", uc $daemon, $names;
+ printf "#define FRR_IP_REDIST_HELP_STR_%s \\\n%s\n", uc $daemon, $help;
+
+ ($names, $help) = collect($daemon, 0, 1, 0);
+ printf "#define FRR_IP6_REDIST_STR_%s \\\n %s\n", uc $daemon, $names;
+ printf "#define FRR_IP6_REDIST_HELP_STR_%s \\\n%s\n", uc $daemon, $help;
+
+ if ($daemon eq "zebra") {
+ ($names, $help) = collect($daemon, 1, 0, 1);
+ printf "#define FRR_IP_PROTOCOL_MAP_STR_%s \\\n %s\n", uc $daemon, $names;
+ printf "#define FRR_IP_PROTOCOL_MAP_HELP_STR_%s \\\n%s\n", uc $daemon, $help;
+
+ ($names, $help) = collect($daemon, 0, 1, 1);
+ printf "#define FRR_IP6_PROTOCOL_MAP_STR_%s \\\n %s\n", uc $daemon, $names;
+ printf "#define FRR_IP6_PROTOCOL_MAP_HELP_STR_%s \\\n%s\n", uc $daemon, $help;
+ }
+ } else {
+ my ($names, $help) = collect($daemon,
+ $daemons{$daemon}->{"ipv4"}, $daemons{$daemon}->{"ipv6"}, 0);
+ printf "#define FRR_REDIST_STR_%s \\\n %s\n", uc $daemon, $names;
+ printf "#define FRR_REDIST_HELP_STR_%s \\\n%s\n", uc $daemon, $help;
+ }
+ print "\n";
+}
+
+print <<EOF;
+
+#ifdef FRR_DEFINE_DESC_TABLE
+
+struct zebra_desc_table
+{
+ unsigned int type;
+ const char *string;
+ char chr;
+};
+
+#define DESC_ENTRY(T,S,C) [(T)] = { (T), (S), (C) }
+static const struct zebra_desc_table route_types[] = {
+EOF
+
+for (my $c = 0; $c < @protos; $c++) {
+ my $p = $protos[$c];
+ printf " DESC_ENTRY\t(%s\t \"%s\",\t'%s' ),\n",
+ $p.",", $protodetail{$p}->{"cname"}, $protodetail{$p}->{"char"};
+}
+
+print <<EOF;
+};
+#undef DESC_ENTRY
+
+#endif /* FRR_DEFINE_DESC_TABLE */
+
+#endif /* _FRR_ROUTE_TYPES_H */
+EOF
+
diff --git a/lib/route_types.txt b/lib/route_types.txt
new file mode 100644
index 0000000..a82273a
--- /dev/null
+++ b/lib/route_types.txt
@@ -0,0 +1,118 @@
+# Canonical Zserv route types information registry for FRR.
+#
+# Used to construct route_types.c and route_types.h
+#
+# comma-seperated fields of either 2 fields (help strings) or 7 fields.
+# White space before and after the comma separators is stripped.
+# Lines /beginning/ with # are comments.
+#
+####
+# 9 field line has format:
+# ZServ route type, canonical name, daemon, route char, ipv4, ipv6, redist, short desc, Restrictions
+#
+# Zserv route type: Corresponding with zebra.h. Key field.
+# canonical name: Typically derived from the route type definition.
+# Used in 'redistribute' commands in daemons.
+# Key field.
+# daemon: The daemon which may originates this route type
+# for redistribution to other daemons.
+# NULL if not applicable.
+# M:N definitions of type:daemon are allowed.
+# Used to construct vty command strings.
+# route char: Single character to denote the route, if applicable.
+# Used to denote route type where space is tight,
+# e.g. 'show ip route' / 'show ipv6 route'.
+# 'X' is reserved as the 'not needed' placeholder.
+# ipv4: IPv4 capable? yes/no, or 1/0.
+# ipv6: IPv6 capable? ditto.
+# redist: Allow this protocol to be used in redistribution statements
+# short desc: Very brief description. Used in header of
+# 'show ip route'. May be specified as NULL
+# if the canonical name suffices.
+# Restriction: If this cannot be used with the listed protocol for redistribution events
+#
+# Key fields obviously must be a unique ASCII alpha-numeric word.
+# Lower-case is required, brevity is optional but highly desirable.
+#
+####
+# 2 field format:
+#
+# Zserv route type, Long description
+#
+# Long description: Full description, but should try fit on a line.
+####
+#
+# If you add a new routing protocol here, make sure you also update
+# meta_queue_map in zebra_rib.c
+#
+## type cname daemon C 4 6 Redist short help Enabled Restrictions
+ZEBRA_ROUTE_SYSTEM, system, NULL, 'X', 0, 0, 0, "Reserved", none
+ZEBRA_ROUTE_KERNEL, kernel, zebra, 'K', 1, 1, 1, "kernel route", zebra
+ZEBRA_ROUTE_CONNECT, connected, zebra, 'C', 1, 1, 1, "connected", zebra
+ZEBRA_ROUTE_STATIC, static, zebra, 'S', 1, 1, 1, "static", zebra
+ZEBRA_ROUTE_RIP, rip, ripd, 'R', 1, 0, 1, "RIP", ripd
+ZEBRA_ROUTE_RIPNG, ripng, ripngd, 'R', 0, 1, 1, "RIPng", ripngd
+ZEBRA_ROUTE_OSPF, ospf, ospfd, 'O', 1, 0, 1, "OSPF", ospfd
+ZEBRA_ROUTE_OSPF6, ospf6, ospf6d, 'O', 0, 1, 1, "OSPFv3", ospf6d
+ZEBRA_ROUTE_ISIS, isis, isisd, 'I', 1, 1, 1, "IS-IS", isisd
+ZEBRA_ROUTE_BGP, bgp, bgpd, 'B', 1, 1, 1, "BGP", bgpd
+ZEBRA_ROUTE_PIM, pim, pimd, 'P', 0, 0, 0, "PIM", pimd
+ZEBRA_ROUTE_EIGRP, eigrp, eigrpd, 'E', 1, 0, 1, "EIGRP", eigrpd
+ZEBRA_ROUTE_NHRP, nhrp, nhrpd, 'N', 1, 1, 1, "NHRP", nhrpd
+# HSLS and OLSR both are AFI independent (so: 1, 1), however
+# we want to disable for them for general FRR distribution.
+# This at least makes it trivial for users of these protocols
+# to 'switch on' redist support (direct numeric entry remaining
+# possible).
+ZEBRA_ROUTE_HSLS, hsls, hslsd, 'H', 0, 0, 0, "HSLS", hslsd
+ZEBRA_ROUTE_OLSR, olsr, olsrd, 'o', 0, 0, 0, "OLSR", olsrd
+ZEBRA_ROUTE_TABLE, table, zebra, 'T', 1, 1, 1, "Table", zebra
+ZEBRA_ROUTE_LDP, ldp, ldpd, 'L', 0, 0, 0, "LDP", ldpd
+#vnc when sent to zebra
+ZEBRA_ROUTE_VNC, vnc, NULL, 'v', 1, 1, 1, "VNC", bgpd-vnc
+# vnc when sent to bgp
+ZEBRA_ROUTE_VNC_DIRECT, vnc-direct,NULL, 'V', 1, 1, 1, "VNC-Direct", bgpd-vnc, bgpd
+# vnc when sent to bgp (resolve NVE mode)
+ZEBRA_ROUTE_VNC_DIRECT_RH, vnc-rn, NULL, 'V', 0, 0, 0, "VNC-RN", bgpd-vnc
+# bgp unicast -> vnc
+ZEBRA_ROUTE_BGP_DIRECT, bgp-direct, NULL, 'b', 0, 0, 0, "BGP-Direct", bgpd-vnc
+# bgp unicast -> vnc
+ZEBRA_ROUTE_BGP_DIRECT_EXT, bgp-direct-to-nve-groups, NULL, 'e', 0, 0, 0, "BGP2VNC", bgpd-vnc
+ZEBRA_ROUTE_BABEL, babel, babeld, 'A', 1, 1, 1, "Babel", babeld
+ZEBRA_ROUTE_SHARP, sharp, sharpd, 'D', 1, 1, 1, "SHARP", sharpd
+ZEBRA_ROUTE_PBR, pbr, pbrd, 'F', 1, 1, 0, "PBR", pbrd
+ZEBRA_ROUTE_BFD, bfd, bfdd, '-', 0, 0, 0, "BFD", bfdd
+ZEBRA_ROUTE_OPENFABRIC, openfabric, fabricd, 'f', 1, 1, 1, "OpenFabric", fabricd
+ZEBRA_ROUTE_VRRP, vrrp, vrrpd, '-', 0, 0, 0, "VRRP", vrrpd
+ZEBRA_ROUTE_NHG, zebra, none, '-', 0, 0, 0, "Nexthop Group", none
+ZEBRA_ROUTE_SRTE, srte, none, '-', 0, 0, 0, "SR-TE", none
+ZEBRA_ROUTE_ALL, wildcard, none, '-', 0, 0, 0, "-", none
+
+
+## help strings
+ZEBRA_ROUTE_SYSTEM, "Reserved route type, for internal use only"
+ZEBRA_ROUTE_KERNEL, "Kernel routes (not installed via the zebra RIB)"
+ZEBRA_ROUTE_CONNECT,"Connected routes (directly attached subnet or host)"
+ZEBRA_ROUTE_STATIC, "Statically configured routes"
+ZEBRA_ROUTE_RIP, "Routing Information Protocol (RIP)"
+ZEBRA_ROUTE_RIPNG, "Routing Information Protocol next-generation (IPv6) (RIPng)"
+ZEBRA_ROUTE_OSPF, "Open Shortest Path First (OSPFv2)"
+ZEBRA_ROUTE_OSPF6, "Open Shortest Path First (IPv6) (OSPFv3)"
+ZEBRA_ROUTE_ISIS, "Intermediate System to Intermediate System (IS-IS)"
+ZEBRA_ROUTE_BGP, "Border Gateway Protocol (BGP)"
+ZEBRA_ROUTE_PIM, "Protocol Independent Multicast (PIM)"
+ZEBRA_ROUTE_EIGRP, "Enhanced Interior Gateway Routing Protocol (EIGRP)"
+ZEBRA_ROUTE_NHRP, "Next Hop Resolution Protocol (NHRP)"
+ZEBRA_ROUTE_HSLS, "Hazy-Sighted Link State Protocol (HSLS)"
+ZEBRA_ROUTE_VNC, "Virtual Network Control (VNC)"
+ZEBRA_ROUTE_OLSR, "Optimised Link State Routing (OLSR)"
+ZEBRA_ROUTE_TABLE, "Non-main Kernel Routing Table"
+ZEBRA_ROUTE_LDP, "Label Distribution Protocol (LDP)"
+ZEBRA_ROUTE_VNC_DIRECT, "VNC direct (not via zebra) routes"
+ZEBRA_ROUTE_BABEL, "Babel routing protocol (Babel)"
+ZEBRA_ROUTE_SHARP, "Super Happy Advanced Routing Protocol (sharpd)"
+ZEBRA_ROUTE_PBR, "Policy Based Routing (PBR)"
+ZEBRA_ROUTE_BFD, "Bidirectional Fowarding Detection (BFD)"
+ZEBRA_ROUTE_VRRP, "Virtual Router Redundancy Protocol (VRRP)"
+ZEBRA_ROUTE_OPENFABRIC, "OpenFabric Routing Protocol"
+ZEBRA_ROUTE_NHG, "Zebra Nexthop Groups (NHG)"
diff --git a/lib/routemap.c b/lib/routemap.c
new file mode 100644
index 0000000..2b33478
--- /dev/null
+++ b/lib/routemap.c
@@ -0,0 +1,3400 @@
+/* Route map function.
+ * Copyright (C) 1998, 1999 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 "linklist.h"
+#include "memory.h"
+#include "command.h"
+#include "vector.h"
+#include "prefix.h"
+#include "vty.h"
+#include "routemap.h"
+#include "command.h"
+#include "log.h"
+#include "hash.h"
+#include "libfrr.h"
+#include "lib_errors.h"
+#include "table.h"
+#include "json.h"
+#include "jhash.h"
+
+DEFINE_MTYPE_STATIC(LIB, ROUTE_MAP, "Route map");
+DEFINE_MTYPE(LIB, ROUTE_MAP_NAME, "Route map name");
+DEFINE_MTYPE_STATIC(LIB, ROUTE_MAP_INDEX, "Route map index");
+DEFINE_MTYPE(LIB, ROUTE_MAP_RULE, "Route map rule");
+DEFINE_MTYPE_STATIC(LIB, ROUTE_MAP_RULE_STR, "Route map rule str");
+DEFINE_MTYPE(LIB, ROUTE_MAP_COMPILED, "Route map compiled");
+DEFINE_MTYPE_STATIC(LIB, ROUTE_MAP_DEP, "Route map dependency");
+DEFINE_MTYPE_STATIC(LIB, ROUTE_MAP_DEP_DATA, "Route map dependency data");
+
+DEFINE_QOBJ_TYPE(route_map_index);
+DEFINE_QOBJ_TYPE(route_map);
+
+static int rmap_cmd_name_cmp(const struct route_map_rule_cmd_proxy *a,
+ const struct route_map_rule_cmd_proxy *b)
+{
+ return strcmp(a->cmd->str, b->cmd->str);
+}
+
+static uint32_t rmap_cmd_name_hash(const struct route_map_rule_cmd_proxy *item)
+{
+ return jhash(item->cmd->str, strlen(item->cmd->str), 0xbfd69320);
+}
+
+DECLARE_HASH(rmap_cmd_name, struct route_map_rule_cmd_proxy, itm,
+ rmap_cmd_name_cmp, rmap_cmd_name_hash);
+
+static struct rmap_cmd_name_head rmap_match_cmds[1] = {
+ INIT_HASH(rmap_match_cmds[0]),
+};
+static struct rmap_cmd_name_head rmap_set_cmds[1] = {
+ INIT_HASH(rmap_set_cmds[0]),
+};
+
+#define IPv4_PREFIX_LIST "ip address prefix-list"
+#define IPv6_PREFIX_LIST "ipv6 address prefix-list"
+
+#define IS_RULE_IPv4_PREFIX_LIST(S) \
+ (strncmp(S, IPv4_PREFIX_LIST, strlen(IPv4_PREFIX_LIST)) == 0)
+#define IS_RULE_IPv6_PREFIX_LIST(S) \
+ (strncmp(S, IPv6_PREFIX_LIST, strlen(IPv6_PREFIX_LIST)) == 0)
+
+struct route_map_pentry_dep {
+ struct prefix_list_entry *pentry;
+ const char *plist_name;
+ route_map_event_t event;
+};
+
+static void route_map_pfx_tbl_update(route_map_event_t event,
+ struct route_map_index *index, afi_t afi,
+ const char *plist_name);
+static void route_map_pfx_table_add_default(afi_t afi,
+ struct route_map_index *index);
+static void route_map_pfx_table_del_default(afi_t afi,
+ struct route_map_index *index);
+static void route_map_add_plist_entries(afi_t afi,
+ struct route_map_index *index,
+ const char *plist_name,
+ struct prefix_list_entry *entry);
+static void route_map_del_plist_entries(afi_t afi,
+ struct route_map_index *index,
+ const char *plist_name,
+ struct prefix_list_entry *entry);
+
+static struct hash *route_map_get_dep_hash(route_map_event_t event);
+static void route_map_free_map(struct route_map *map);
+
+struct route_map_match_set_hooks rmap_match_set_hook;
+
+/* match interface */
+void route_map_match_interface_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.match_interface = func;
+}
+
+/* no match interface */
+void route_map_no_match_interface_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.no_match_interface = func;
+}
+
+/* match ip address */
+void route_map_match_ip_address_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.match_ip_address = func;
+}
+
+/* no match ip address */
+void route_map_no_match_ip_address_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.no_match_ip_address = func;
+}
+
+/* match ip address prefix list */
+void route_map_match_ip_address_prefix_list_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.match_ip_address_prefix_list = func;
+}
+
+/* no match ip address prefix list */
+void route_map_no_match_ip_address_prefix_list_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.no_match_ip_address_prefix_list = func;
+}
+
+/* match ip next hop */
+void route_map_match_ip_next_hop_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.match_ip_next_hop = func;
+}
+
+/* no match ip next hop */
+void route_map_no_match_ip_next_hop_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.no_match_ip_next_hop = func;
+}
+
+/* match ipv6 next-hop */
+void route_map_match_ipv6_next_hop_hook(int (*func)(
+ struct route_map_index *index, const char *command, const char *arg,
+ route_map_event_t type, char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.match_ipv6_next_hop = func;
+}
+
+/* no match ipv6 next-hop */
+void route_map_no_match_ipv6_next_hop_hook(int (*func)(
+ struct route_map_index *index, const char *command, const char *arg,
+ route_map_event_t type, char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.no_match_ipv6_next_hop = func;
+}
+
+/* match ip next hop prefix list */
+void route_map_match_ip_next_hop_prefix_list_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.match_ip_next_hop_prefix_list = func;
+}
+
+/* no match ip next hop prefix list */
+void route_map_no_match_ip_next_hop_prefix_list_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.no_match_ip_next_hop_prefix_list = func;
+}
+
+/* match ip next-hop type */
+void route_map_match_ip_next_hop_type_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.match_ip_next_hop_type = func;
+}
+
+/* no match ip next-hop type */
+void route_map_no_match_ip_next_hop_type_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.no_match_ip_next_hop_type = func;
+}
+
+/* match ipv6 address */
+void route_map_match_ipv6_address_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.match_ipv6_address = func;
+}
+
+/* no match ipv6 address */
+void route_map_no_match_ipv6_address_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.no_match_ipv6_address = func;
+}
+
+
+/* match ipv6 address prefix list */
+void route_map_match_ipv6_address_prefix_list_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.match_ipv6_address_prefix_list = func;
+}
+
+/* no match ipv6 address prefix list */
+void route_map_no_match_ipv6_address_prefix_list_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.no_match_ipv6_address_prefix_list = func;
+}
+
+/* match ipv6 next-hop type */
+void route_map_match_ipv6_next_hop_type_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.match_ipv6_next_hop_type = func;
+}
+
+/* no match ipv6 next-hop type */
+void route_map_no_match_ipv6_next_hop_type_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.no_match_ipv6_next_hop_type = func;
+}
+
+/* match ipv6 next-hop prefix-list */
+void route_map_match_ipv6_next_hop_prefix_list_hook(int (*func)(
+ struct route_map_index *index, const char *command, const char *arg,
+ route_map_event_t type, char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.match_ipv6_next_hop_prefix_list = func;
+}
+
+/* no match ipv6 next-hop prefix-list */
+void route_map_no_match_ipv6_next_hop_prefix_list_hook(int (*func)(
+ struct route_map_index *index, const char *command, const char *arg,
+ route_map_event_t type, char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.no_match_ipv6_next_hop_prefix_list = func;
+}
+
+/* match metric */
+void route_map_match_metric_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.match_metric = func;
+}
+
+/* no match metric */
+void route_map_no_match_metric_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.no_match_metric = func;
+}
+
+/* match tag */
+void route_map_match_tag_hook(int (*func)(struct route_map_index *index,
+ const char *command, const char *arg,
+ route_map_event_t type,
+ char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.match_tag = func;
+}
+
+/* no match tag */
+void route_map_no_match_tag_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.no_match_tag = func;
+}
+
+/* set sr-te color */
+void route_map_set_srte_color_hook(int (*func)(struct route_map_index *index,
+ const char *command,
+ const char *arg,
+ char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.set_srte_color = func;
+}
+
+/* no set sr-te color */
+void route_map_no_set_srte_color_hook(int (*func)(struct route_map_index *index,
+ const char *command,
+ const char *arg,
+ char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.no_set_srte_color = func;
+}
+
+/* set ip nexthop */
+void route_map_set_ip_nexthop_hook(int (*func)(struct route_map_index *index,
+ const char *command,
+ const char *arg,
+ char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.set_ip_nexthop = func;
+}
+
+/* no set ip nexthop */
+void route_map_no_set_ip_nexthop_hook(int (*func)(struct route_map_index *index,
+ const char *command,
+ const char *arg,
+ char *errmsg,
+ size_t errmsg_len))
+{
+ rmap_match_set_hook.no_set_ip_nexthop = func;
+}
+
+/* set ipv6 nexthop local */
+void route_map_set_ipv6_nexthop_local_hook(
+ int (*func)(struct route_map_index *index,
+ const char *command, const char *arg,
+ char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.set_ipv6_nexthop_local = func;
+}
+
+/* no set ipv6 nexthop local */
+void route_map_no_set_ipv6_nexthop_local_hook(
+ int (*func)(struct route_map_index *index,
+ const char *command, const char *arg,
+ char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.no_set_ipv6_nexthop_local = func;
+}
+
+/* set metric */
+void route_map_set_metric_hook(int (*func)(struct route_map_index *index,
+ const char *command,
+ const char *arg,
+ char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.set_metric = func;
+}
+
+/* no set metric */
+void route_map_no_set_metric_hook(int (*func)(struct route_map_index *index,
+ const char *command,
+ const char *arg,
+ char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.no_set_metric = func;
+}
+
+/* set tag */
+void route_map_set_tag_hook(int (*func)(struct route_map_index *index,
+ const char *command, const char *arg,
+ char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.set_tag = func;
+}
+
+/* no set tag */
+void route_map_no_set_tag_hook(int (*func)(struct route_map_index *index,
+ const char *command,
+ const char *arg,
+ char *errmsg, size_t errmsg_len))
+{
+ rmap_match_set_hook.no_set_tag = func;
+}
+
+int generic_match_add(struct route_map_index *index,
+ const char *command, const char *arg,
+ route_map_event_t type,
+ char *errmsg, size_t errmsg_len)
+{
+ enum rmap_compile_rets ret;
+
+ ret = route_map_add_match(index, command, arg, type);
+ switch (ret) {
+ case RMAP_RULE_MISSING:
+ snprintf(errmsg, errmsg_len, "%% [%s] Can't find rule.",
+ frr_protonameinst);
+ return CMD_WARNING_CONFIG_FAILED;
+ case RMAP_COMPILE_ERROR:
+ snprintf(errmsg, errmsg_len,
+ "%% [%s] Argument form is unsupported or malformed.",
+ frr_protonameinst);
+ return CMD_WARNING_CONFIG_FAILED;
+ case RMAP_COMPILE_SUCCESS:
+ /*
+ * Nothing to do here move along
+ */
+ break;
+ }
+
+ return CMD_SUCCESS;
+}
+
+int generic_match_delete(struct route_map_index *index,
+ const char *command, const char *arg,
+ route_map_event_t type,
+ char *errmsg, size_t errmsg_len)
+{
+ enum rmap_compile_rets ret;
+ int retval = CMD_SUCCESS;
+ char *dep_name = NULL;
+ const char *tmpstr;
+ char *rmap_name = NULL;
+
+ if (type != RMAP_EVENT_MATCH_DELETED) {
+ /* ignore the mundane, the types without any dependency */
+ if (arg == NULL) {
+ if ((tmpstr = route_map_get_match_arg(index, command))
+ != NULL)
+ dep_name =
+ XSTRDUP(MTYPE_ROUTE_MAP_RULE, tmpstr);
+ } else {
+ dep_name = XSTRDUP(MTYPE_ROUTE_MAP_RULE, arg);
+ }
+ rmap_name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, index->map->name);
+ }
+
+ ret = route_map_delete_match(index, command, dep_name, type);
+ switch (ret) {
+ case RMAP_RULE_MISSING:
+ snprintf(errmsg, errmsg_len, "%% [%s] Can't find rule.",
+ frr_protonameinst);
+ retval = CMD_WARNING_CONFIG_FAILED;
+ break;
+ case RMAP_COMPILE_ERROR:
+ snprintf(errmsg, errmsg_len,
+ "%% [%s] Argument form is unsupported or malformed.",
+ frr_protonameinst);
+ retval = CMD_WARNING_CONFIG_FAILED;
+ break;
+ case RMAP_COMPILE_SUCCESS:
+ /*
+ * Nothing to do here
+ */
+ break;
+ }
+
+ XFREE(MTYPE_ROUTE_MAP_RULE, dep_name);
+ XFREE(MTYPE_ROUTE_MAP_NAME, rmap_name);
+
+ return retval;
+}
+
+int generic_set_add(struct route_map_index *index,
+ const char *command, const char *arg,
+ char *errmsg, size_t errmsg_len)
+{
+ enum rmap_compile_rets ret;
+
+ ret = route_map_add_set(index, command, arg);
+ switch (ret) {
+ case RMAP_RULE_MISSING:
+ snprintf(errmsg, errmsg_len,
+ "%% [%s] Can't find rule.", frr_protonameinst);
+ return CMD_WARNING_CONFIG_FAILED;
+ case RMAP_COMPILE_ERROR:
+ snprintf(errmsg, errmsg_len,
+ "%% [%s] Argument form is unsupported or malformed.",
+ frr_protonameinst);
+ return CMD_WARNING_CONFIG_FAILED;
+ case RMAP_COMPILE_SUCCESS:
+ break;
+ }
+
+ return CMD_SUCCESS;
+}
+
+int generic_set_delete(struct route_map_index *index,
+ const char *command, const char *arg,
+ char *errmsg, size_t errmsg_len)
+{
+ enum rmap_compile_rets ret;
+
+ ret = route_map_delete_set(index, command, arg);
+ switch (ret) {
+ case RMAP_RULE_MISSING:
+ snprintf(errmsg, errmsg_len, "%% [%s] Can't find rule.",
+ frr_protonameinst);
+ return CMD_WARNING_CONFIG_FAILED;
+ case RMAP_COMPILE_ERROR:
+ snprintf(errmsg, errmsg_len,
+ "%% [%s] Argument form is unsupported or malformed.",
+ frr_protonameinst);
+ return CMD_WARNING_CONFIG_FAILED;
+ case RMAP_COMPILE_SUCCESS:
+ break;
+ }
+
+ return CMD_SUCCESS;
+}
+
+
+/* Master list of route map. */
+struct route_map_list route_map_master = {NULL, NULL, NULL, NULL, NULL};
+struct hash *route_map_master_hash = NULL;
+
+static unsigned int route_map_hash_key_make(const void *p)
+{
+ const struct route_map *map = p;
+ return string_hash_make(map->name);
+}
+
+static bool route_map_hash_cmp(const void *p1, const void *p2)
+{
+ const struct route_map *map1 = p1;
+ const struct route_map *map2 = p2;
+
+ if (!strcmp(map1->name, map2->name))
+ return true;
+
+ return false;
+}
+
+enum route_map_upd8_type {
+ ROUTE_MAP_ADD = 1,
+ ROUTE_MAP_DEL,
+};
+
+/* all possible route-map dependency types */
+enum route_map_dep_type {
+ ROUTE_MAP_DEP_RMAP = 1,
+ ROUTE_MAP_DEP_CLIST,
+ ROUTE_MAP_DEP_ECLIST,
+ ROUTE_MAP_DEP_LCLIST,
+ ROUTE_MAP_DEP_PLIST,
+ ROUTE_MAP_DEP_ASPATH,
+ ROUTE_MAP_DEP_FILTER,
+ ROUTE_MAP_DEP_MAX,
+};
+
+struct route_map_dep {
+ char *dep_name;
+ struct hash *dep_rmap_hash;
+ struct hash *this_hash; /* ptr to the hash structure this is part of */
+};
+
+struct route_map_dep_data {
+ /* Route-map name.
+ */
+ char *rname;
+ /* Count of number of sequences of this
+ * route-map that depend on the same entity.
+ */
+ uint16_t refcnt;
+};
+
+/* Hashes maintaining dependency between various sublists used by route maps */
+static struct hash *route_map_dep_hash[ROUTE_MAP_DEP_MAX];
+
+static unsigned int route_map_dep_hash_make_key(const void *p);
+static void route_map_clear_all_references(char *rmap_name);
+static void route_map_rule_delete(struct route_map_rule_list *,
+ struct route_map_rule *);
+static bool rmap_debug;
+
+/* New route map allocation. Please note route map's name must be
+ specified. */
+static struct route_map *route_map_new(const char *name)
+{
+ struct route_map *new;
+
+ new = XCALLOC(MTYPE_ROUTE_MAP, sizeof(struct route_map));
+ new->name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, name);
+ QOBJ_REG(new, route_map);
+ return new;
+}
+
+/* Add new name to route_map. */
+static struct route_map *route_map_add(const char *name)
+{
+ struct route_map *map, *exist;
+ struct route_map_list *list;
+
+ map = route_map_new(name);
+ list = &route_map_master;
+
+ /*
+ * Add map to the hash
+ *
+ * If the map already exists in the hash, then we know that
+ * FRR is now in a sequence of delete/create.
+ * All FRR needs to do here is set the to_be_processed
+ * bit (to inherit from the old one
+ */
+ exist = hash_release(route_map_master_hash, map);
+ if (exist) {
+ map->to_be_processed = exist->to_be_processed;
+ route_map_free_map(exist);
+ }
+ hash_get(route_map_master_hash, map, hash_alloc_intern);
+
+ /* Add new entry to the head of the list to match how it is added in the
+ * hash table. This is to ensure that if the same route-map has been
+ * created more than once and then marked for deletion (which can happen
+ * if prior deletions haven't completed as BGP hasn't yet done the
+ * route-map processing), the order of the entities is the same in both
+ * the list and the hash table. Otherwise, since there is nothing to
+ * distinguish between the two entries, the wrong entry could get freed.
+ * TODO: This needs to be re-examined to handle it better - e.g., revive
+ * a deleted entry if the route-map is created again.
+ */
+ map->prev = NULL;
+ map->next = list->head;
+ if (list->head)
+ list->head->prev = map;
+ list->head = map;
+ if (!list->tail)
+ list->tail = map;
+
+ /* Execute hook. */
+ if (route_map_master.add_hook) {
+ (*route_map_master.add_hook)(name);
+ route_map_notify_dependencies(name, RMAP_EVENT_CALL_ADDED);
+ }
+
+ if (!map->ipv4_prefix_table)
+ map->ipv4_prefix_table = route_table_init();
+
+ if (!map->ipv6_prefix_table)
+ map->ipv6_prefix_table = route_table_init();
+
+ if (rmap_debug)
+ zlog_debug("Add route-map %s", name);
+ return map;
+}
+
+/* this is supposed to be called post processing by
+ * the delete hook function. Don't invoke delete_hook
+ * again in this routine.
+ */
+static void route_map_free_map(struct route_map *map)
+{
+ struct route_map_list *list;
+ struct route_map_index *index;
+
+ if (map == NULL)
+ return;
+
+ while ((index = map->head) != NULL)
+ route_map_index_delete(index, 0);
+
+ if (rmap_debug)
+ zlog_debug("Deleting route-map %s", map->name);
+
+ list = &route_map_master;
+
+ QOBJ_UNREG(map);
+
+ if (map->next)
+ map->next->prev = map->prev;
+ else
+ list->tail = map->prev;
+
+ if (map->prev)
+ map->prev->next = map->next;
+ else
+ list->head = map->next;
+
+ hash_release(route_map_master_hash, map);
+ XFREE(MTYPE_ROUTE_MAP_NAME, map->name);
+ XFREE(MTYPE_ROUTE_MAP, map);
+}
+
+/* Route map delete from list. */
+void route_map_delete(struct route_map *map)
+{
+ struct route_map_index *index;
+ char *name;
+
+ while ((index = map->head) != NULL)
+ route_map_index_delete(index, 0);
+
+ name = map->name;
+ map->head = NULL;
+
+ /* Clear all dependencies */
+ route_map_clear_all_references(name);
+ map->deleted = true;
+ /* Execute deletion hook. */
+ if (route_map_master.delete_hook) {
+ (*route_map_master.delete_hook)(name);
+ route_map_notify_dependencies(name, RMAP_EVENT_CALL_DELETED);
+ }
+
+ if (!map->to_be_processed) {
+ route_map_free_map(map);
+ }
+}
+
+/* Lookup route map by route map name string. */
+struct route_map *route_map_lookup_by_name(const char *name)
+{
+ struct route_map *map;
+ struct route_map tmp_map;
+
+ if (!name)
+ return NULL;
+
+ // map.deleted is false via memset
+ memset(&tmp_map, 0, sizeof(tmp_map));
+ tmp_map.name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, name);
+ map = hash_lookup(route_map_master_hash, &tmp_map);
+ XFREE(MTYPE_ROUTE_MAP_NAME, tmp_map.name);
+
+ if (map && map->deleted)
+ return NULL;
+
+ return map;
+}
+
+/* Simple helper to warn if route-map does not exist. */
+struct route_map *route_map_lookup_warn_noexist(struct vty *vty, const char *name)
+{
+ struct route_map *route_map = route_map_lookup_by_name(name);
+
+ if (!route_map)
+ if (vty_shell_serv(vty))
+ vty_out(vty, "The route-map '%s' does not exist.\n", name);
+
+ return route_map;
+}
+
+int route_map_mark_updated(const char *name)
+{
+ struct route_map *map;
+ int ret = -1;
+ struct route_map tmp_map;
+
+ if (!name)
+ return (ret);
+
+ map = route_map_lookup_by_name(name);
+
+ /* If we did not find the routemap with deleted=false try again
+ * with deleted=true
+ */
+ if (!map) {
+ memset(&tmp_map, 0, sizeof(tmp_map));
+ tmp_map.name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, name);
+ tmp_map.deleted = true;
+ map = hash_lookup(route_map_master_hash, &tmp_map);
+ XFREE(MTYPE_ROUTE_MAP_NAME, tmp_map.name);
+ }
+
+ if (map) {
+ map->to_be_processed = true;
+ ret = 0;
+ }
+
+ return (ret);
+}
+
+static void route_map_clear_updated(struct route_map *map)
+{
+ if (map) {
+ map->to_be_processed = false;
+ if (map->deleted)
+ route_map_free_map(map);
+ }
+}
+
+/* Lookup route map. If there isn't route map create one and return
+ it. */
+struct route_map *route_map_get(const char *name)
+{
+ struct route_map *map;
+
+ map = route_map_lookup_by_name(name);
+ if (map == NULL)
+ map = route_map_add(name);
+
+ return map;
+}
+
+void route_map_walk_update_list(void (*route_map_update_fn)(char *name))
+{
+ struct route_map *node;
+ struct route_map *nnode = NULL;
+
+ for (node = route_map_master.head; node; node = nnode) {
+ if (node->to_be_processed) {
+ /* DD: Should we add any thread yield code here */
+ route_map_update_fn(node->name);
+ nnode = node->next;
+ route_map_clear_updated(node);
+ } else
+ nnode = node->next;
+ }
+}
+
+/* Return route map's type string. */
+static const char *route_map_type_str(enum route_map_type type)
+{
+ switch (type) {
+ case RMAP_PERMIT:
+ return "permit";
+ case RMAP_DENY:
+ return "deny";
+ case RMAP_ANY:
+ return "";
+ }
+
+ return "";
+}
+
+static const char *route_map_cmd_result_str(enum route_map_cmd_result_t res)
+{
+ switch (res) {
+ case RMAP_MATCH:
+ return "match";
+ case RMAP_NOMATCH:
+ return "no match";
+ case RMAP_NOOP:
+ return "noop";
+ case RMAP_ERROR:
+ return "error";
+ case RMAP_OKAY:
+ return "okay";
+ }
+
+ return "invalid";
+}
+
+static const char *route_map_result_str(route_map_result_t res)
+{
+ switch (res) {
+ case RMAP_DENYMATCH:
+ return "deny";
+ case RMAP_PERMITMATCH:
+ return "permit";
+ }
+
+ return "invalid";
+}
+
+/* show route-map */
+static void vty_show_route_map_entry(struct vty *vty, struct route_map *map,
+ json_object *json)
+{
+ struct route_map_index *index;
+ struct route_map_rule *rule;
+ json_object *json_rmap = NULL;
+ json_object *json_rules = NULL;
+
+ if (json) {
+ json_rmap = json_object_new_object();
+ json_object_object_add(json, map->name, json_rmap);
+
+ json_rules = json_object_new_array();
+ json_object_int_add(json_rmap, "invoked",
+ map->applied - map->applied_clear);
+ json_object_boolean_add(json_rmap, "disabledOptimization",
+ map->optimization_disabled);
+ json_object_boolean_add(json_rmap, "processedChange",
+ map->to_be_processed);
+ json_object_object_add(json_rmap, "rules", json_rules);
+ } else {
+ vty_out(vty,
+ "route-map: %s Invoked: %" PRIu64
+ " Optimization: %s Processed Change: %s\n",
+ map->name, map->applied - map->applied_clear,
+ map->optimization_disabled ? "disabled" : "enabled",
+ map->to_be_processed ? "true" : "false");
+ }
+
+ for (index = map->head; index; index = index->next) {
+ if (json) {
+ json_object *json_rule;
+ json_object *json_matches;
+ json_object *json_sets;
+ char action[BUFSIZ] = {};
+
+ json_rule = json_object_new_object();
+ json_object_array_add(json_rules, json_rule);
+
+ json_object_int_add(json_rule, "sequenceNumber",
+ index->pref);
+ json_object_string_add(json_rule, "type",
+ route_map_type_str(index->type));
+ json_object_int_add(json_rule, "invoked",
+ index->applied
+ - index->applied_clear);
+
+ /* Description */
+ if (index->description)
+ json_object_string_add(json_rule, "description",
+ index->description);
+
+ /* Match clauses */
+ json_matches = json_object_new_array();
+ json_object_object_add(json_rule, "matchClauses",
+ json_matches);
+ for (rule = index->match_list.head; rule;
+ rule = rule->next) {
+ char buf[BUFSIZ];
+
+ snprintf(buf, sizeof(buf), "%s %s",
+ rule->cmd->str, rule->rule_str);
+ json_array_string_add(json_matches, buf);
+ }
+
+ /* Set clauses */
+ json_sets = json_object_new_array();
+ json_object_object_add(json_rule, "setClauses",
+ json_sets);
+ for (rule = index->set_list.head; rule;
+ rule = rule->next) {
+ char buf[BUFSIZ];
+
+ snprintf(buf, sizeof(buf), "%s %s",
+ rule->cmd->str, rule->rule_str);
+ json_array_string_add(json_sets, buf);
+ }
+
+ /* Call clause */
+ if (index->nextrm)
+ json_object_string_add(json_rule, "callClause",
+ index->nextrm);
+
+ /* Exit Policy */
+ if (index->exitpolicy == RMAP_GOTO)
+ snprintf(action, sizeof(action), "Goto %d",
+ index->nextpref);
+ else if (index->exitpolicy == RMAP_NEXT)
+ snprintf(action, sizeof(action),
+ "Continue to next entry");
+ else if (index->exitpolicy == RMAP_EXIT)
+ snprintf(action, sizeof(action),
+ "Exit routemap");
+ if (action[0] != '\0')
+ json_object_string_add(json_rule, "action",
+ action);
+ } else {
+ vty_out(vty, " %s, sequence %d Invoked %" PRIu64 "\n",
+ route_map_type_str(index->type), index->pref,
+ index->applied - index->applied_clear);
+
+ /* Description */
+ if (index->description)
+ vty_out(vty, " Description:\n %s\n",
+ index->description);
+
+ /* Match clauses */
+ vty_out(vty, " Match clauses:\n");
+ for (rule = index->match_list.head; rule;
+ rule = rule->next)
+ vty_out(vty, " %s %s\n", rule->cmd->str,
+ rule->rule_str);
+
+ /* Set clauses */
+ vty_out(vty, " Set clauses:\n");
+ for (rule = index->set_list.head; rule;
+ rule = rule->next)
+ vty_out(vty, " %s %s\n", rule->cmd->str,
+ rule->rule_str);
+
+ /* Call clause */
+ vty_out(vty, " Call clause:\n");
+ if (index->nextrm)
+ vty_out(vty, " Call %s\n", index->nextrm);
+
+ /* Exit Policy */
+ vty_out(vty, " Action:\n");
+ if (index->exitpolicy == RMAP_GOTO)
+ vty_out(vty, " Goto %d\n", index->nextpref);
+ else if (index->exitpolicy == RMAP_NEXT)
+ vty_out(vty, " Continue to next entry\n");
+ else if (index->exitpolicy == RMAP_EXIT)
+ vty_out(vty, " Exit routemap\n");
+ }
+ }
+}
+
+static int sort_route_map(const void **map1, const void **map2)
+{
+ const struct route_map *m1 = *map1;
+ const struct route_map *m2 = *map2;
+
+ return strcmp(m1->name, m2->name);
+}
+
+static int vty_show_route_map(struct vty *vty, const char *name, bool use_json)
+{
+ struct route_map *map;
+ json_object *json = NULL;
+ json_object *json_proto = NULL;
+
+ if (use_json) {
+ json = json_object_new_object();
+ json_proto = json_object_new_object();
+ json_object_object_add(json, frr_protonameinst, json_proto);
+ } else
+ vty_out(vty, "%s:\n", frr_protonameinst);
+
+ if (name) {
+ map = route_map_lookup_by_name(name);
+
+ if (map) {
+ vty_show_route_map_entry(vty, map, json_proto);
+ } else if (!use_json) {
+ vty_out(vty, "%s: 'route-map %s' not found\n",
+ frr_protonameinst, name);
+ }
+ } else {
+
+ struct list *maplist = list_new();
+ struct listnode *ln;
+
+ for (map = route_map_master.head; map; map = map->next)
+ listnode_add(maplist, map);
+
+ list_sort(maplist, sort_route_map);
+
+ for (ALL_LIST_ELEMENTS_RO(maplist, ln, map))
+ vty_show_route_map_entry(vty, map, json_proto);
+
+ list_delete(&maplist);
+ }
+
+ return vty_json(vty, json);
+}
+
+/* Unused route map details */
+static int vty_show_unused_route_map(struct vty *vty)
+{
+ struct list *maplist = list_new();
+ struct listnode *ln;
+ struct route_map *map;
+
+ for (map = route_map_master.head; map; map = map->next) {
+ /* If use_count is zero, No protocol is using this routemap.
+ * so adding to the list.
+ */
+ if (!map->use_count)
+ listnode_add(maplist, map);
+ }
+
+ if (maplist->count > 0) {
+ vty_out(vty, "\n%s:\n", frr_protonameinst);
+ list_sort(maplist, sort_route_map);
+
+ for (ALL_LIST_ELEMENTS_RO(maplist, ln, map))
+ vty_show_route_map_entry(vty, map, NULL);
+ } else {
+ vty_out(vty, "\n%s: None\n", frr_protonameinst);
+ }
+
+ list_delete(&maplist);
+ return CMD_SUCCESS;
+}
+
+/* New route map allocation. Please note route map's name must be
+ specified. */
+static struct route_map_index *route_map_index_new(void)
+{
+ struct route_map_index *new;
+
+ new = XCALLOC(MTYPE_ROUTE_MAP_INDEX, sizeof(struct route_map_index));
+ new->exitpolicy = RMAP_EXIT; /* Default to Cisco-style */
+ TAILQ_INIT(&new->rhclist);
+ QOBJ_REG(new, route_map_index);
+ return new;
+}
+
+/* Free route map index. */
+void route_map_index_delete(struct route_map_index *index, int notify)
+{
+ struct routemap_hook_context *rhc;
+ struct route_map_rule *rule;
+
+ QOBJ_UNREG(index);
+
+ if (rmap_debug)
+ zlog_debug("Deleting route-map %s sequence %d",
+ index->map->name, index->pref);
+
+ /* Free route map entry description. */
+ XFREE(MTYPE_TMP, index->description);
+
+ /* Free route map northbound hook contexts. */
+ while ((rhc = TAILQ_FIRST(&index->rhclist)) != NULL)
+ routemap_hook_context_free(rhc);
+
+ /* Free route match. */
+ while ((rule = index->match_list.head) != NULL) {
+ if (IS_RULE_IPv4_PREFIX_LIST(rule->cmd->str))
+ route_map_pfx_tbl_update(RMAP_EVENT_PLIST_DELETED,
+ index, AFI_IP, rule->rule_str);
+ else if (IS_RULE_IPv6_PREFIX_LIST(rule->cmd->str))
+ route_map_pfx_tbl_update(RMAP_EVENT_PLIST_DELETED,
+ index, AFI_IP6,
+ rule->rule_str);
+
+ route_map_rule_delete(&index->match_list, rule);
+ }
+
+ /* Free route set. */
+ while ((rule = index->set_list.head) != NULL)
+ route_map_rule_delete(&index->set_list, rule);
+
+ /* Remove index from route map list. */
+ if (index->next)
+ index->next->prev = index->prev;
+ else
+ index->map->tail = index->prev;
+
+ if (index->prev)
+ index->prev->next = index->next;
+ else
+ index->map->head = index->next;
+
+ /* Free 'char *nextrm' if not NULL */
+ XFREE(MTYPE_ROUTE_MAP_NAME, index->nextrm);
+
+ route_map_pfx_tbl_update(RMAP_EVENT_INDEX_DELETED, index, 0, NULL);
+
+ /* Execute event hook. */
+ if (route_map_master.event_hook && notify) {
+ (*route_map_master.event_hook)(index->map->name);
+ route_map_notify_dependencies(index->map->name,
+ RMAP_EVENT_CALL_ADDED);
+ }
+ XFREE(MTYPE_ROUTE_MAP_INDEX, index);
+}
+
+/* Lookup index from route map. */
+static struct route_map_index *route_map_index_lookup(struct route_map *map,
+ enum route_map_type type,
+ int pref)
+{
+ struct route_map_index *index;
+
+ for (index = map->head; index; index = index->next)
+ if ((index->type == type || type == RMAP_ANY)
+ && index->pref == pref)
+ return index;
+ return NULL;
+}
+
+/* Add new index to route map. */
+static struct route_map_index *
+route_map_index_add(struct route_map *map, enum route_map_type type, int pref)
+{
+ struct route_map_index *index;
+ struct route_map_index *point;
+
+ /* Allocate new route map inex. */
+ index = route_map_index_new();
+ index->map = map;
+ index->type = type;
+ index->pref = pref;
+
+ /* Compare preference. */
+ for (point = map->head; point; point = point->next)
+ if (point->pref >= pref)
+ break;
+
+ if (map->head == NULL) {
+ map->head = map->tail = index;
+ } else if (point == NULL) {
+ index->prev = map->tail;
+ map->tail->next = index;
+ map->tail = index;
+ } else if (point == map->head) {
+ index->next = map->head;
+ map->head->prev = index;
+ map->head = index;
+ } else {
+ index->next = point;
+ index->prev = point->prev;
+ if (point->prev)
+ point->prev->next = index;
+ point->prev = index;
+ }
+
+ route_map_pfx_tbl_update(RMAP_EVENT_INDEX_ADDED, index, 0, NULL);
+
+ /* Execute event hook. */
+ if (route_map_master.event_hook) {
+ (*route_map_master.event_hook)(map->name);
+ route_map_notify_dependencies(map->name, RMAP_EVENT_CALL_ADDED);
+ }
+
+ if (rmap_debug)
+ zlog_debug("Route-map %s add sequence %d, type: %s",
+ map->name, pref, route_map_type_str(type));
+
+ return index;
+}
+
+/* Get route map index. */
+struct route_map_index *
+route_map_index_get(struct route_map *map, enum route_map_type type, int pref)
+{
+ struct route_map_index *index;
+
+ index = route_map_index_lookup(map, RMAP_ANY, pref);
+ if (index && index->type != type) {
+ /* Delete index from route map. */
+ route_map_index_delete(index, 1);
+ index = NULL;
+ }
+ if (index == NULL)
+ index = route_map_index_add(map, type, pref);
+ return index;
+}
+
+/* New route map rule */
+static struct route_map_rule *route_map_rule_new(void)
+{
+ struct route_map_rule *new;
+
+ new = XCALLOC(MTYPE_ROUTE_MAP_RULE, sizeof(struct route_map_rule));
+ return new;
+}
+
+/* Install rule command to the match list. */
+void _route_map_install_match(struct route_map_rule_cmd_proxy *proxy)
+{
+ rmap_cmd_name_add(rmap_match_cmds, proxy);
+}
+
+/* Install rule command to the set list. */
+void _route_map_install_set(struct route_map_rule_cmd_proxy *proxy)
+{
+ rmap_cmd_name_add(rmap_set_cmds, proxy);
+}
+
+/* Lookup rule command from match list. */
+static const struct route_map_rule_cmd *route_map_lookup_match(const char *name)
+{
+ struct route_map_rule_cmd refcmd = {.str = name};
+ struct route_map_rule_cmd_proxy ref = {.cmd = &refcmd};
+ struct route_map_rule_cmd_proxy *res;
+
+ res = rmap_cmd_name_find(rmap_match_cmds, &ref);
+ if (res)
+ return res->cmd;
+ return NULL;
+}
+
+/* Lookup rule command from set list. */
+static const struct route_map_rule_cmd *route_map_lookup_set(const char *name)
+{
+ struct route_map_rule_cmd refcmd = {.str = name};
+ struct route_map_rule_cmd_proxy ref = {.cmd = &refcmd};
+ struct route_map_rule_cmd_proxy *res;
+
+ res = rmap_cmd_name_find(rmap_set_cmds, &ref);
+ if (res)
+ return res->cmd;
+ return NULL;
+}
+
+/* Add match and set rule to rule list. */
+static void route_map_rule_add(struct route_map_rule_list *list,
+ struct route_map_rule *rule)
+{
+ rule->next = NULL;
+ rule->prev = list->tail;
+ if (list->tail)
+ list->tail->next = rule;
+ else
+ list->head = rule;
+ list->tail = rule;
+}
+
+/* Delete rule from rule list. */
+static void route_map_rule_delete(struct route_map_rule_list *list,
+ struct route_map_rule *rule)
+{
+ if (rule->cmd->func_free)
+ (*rule->cmd->func_free)(rule->value);
+
+ XFREE(MTYPE_ROUTE_MAP_RULE_STR, rule->rule_str);
+
+ if (rule->next)
+ rule->next->prev = rule->prev;
+ else
+ list->tail = rule->prev;
+ if (rule->prev)
+ rule->prev->next = rule->next;
+ else
+ list->head = rule->next;
+
+ XFREE(MTYPE_ROUTE_MAP_RULE, rule);
+}
+
+/* strcmp wrapper function which don't crush even argument is NULL. */
+static int rulecmp(const char *dst, const char *src)
+{
+ if (dst == NULL) {
+ if (src == NULL)
+ return 0;
+ else
+ return 1;
+ } else {
+ if (src == NULL)
+ return 1;
+ else
+ return strcmp(dst, src);
+ }
+ return 1;
+}
+
+/* Use this to return the already specified argument for this match. This is
+ * useful to get the specified argument with a route map match rule when the
+ * rule is being deleted and the argument is not provided.
+ */
+const char *route_map_get_match_arg(struct route_map_index *index,
+ const char *match_name)
+{
+ struct route_map_rule *rule;
+ const struct route_map_rule_cmd *cmd;
+
+ /* First lookup rule for add match statement. */
+ cmd = route_map_lookup_match(match_name);
+ if (cmd == NULL)
+ return NULL;
+
+ for (rule = index->match_list.head; rule; rule = rule->next)
+ if (rule->cmd == cmd && rule->rule_str != NULL)
+ return (rule->rule_str);
+
+ return NULL;
+}
+
+static route_map_event_t get_route_map_delete_event(route_map_event_t type)
+{
+ switch (type) {
+ case RMAP_EVENT_CALL_ADDED:
+ return RMAP_EVENT_CALL_DELETED;
+ case RMAP_EVENT_PLIST_ADDED:
+ return RMAP_EVENT_PLIST_DELETED;
+ case RMAP_EVENT_CLIST_ADDED:
+ return RMAP_EVENT_CLIST_DELETED;
+ case RMAP_EVENT_ECLIST_ADDED:
+ return RMAP_EVENT_ECLIST_DELETED;
+ case RMAP_EVENT_LLIST_ADDED:
+ return RMAP_EVENT_LLIST_DELETED;
+ case RMAP_EVENT_ASLIST_ADDED:
+ return RMAP_EVENT_ASLIST_DELETED;
+ case RMAP_EVENT_FILTER_ADDED:
+ return RMAP_EVENT_FILTER_DELETED;
+ case RMAP_EVENT_SET_ADDED:
+ case RMAP_EVENT_SET_DELETED:
+ case RMAP_EVENT_SET_REPLACED:
+ case RMAP_EVENT_MATCH_ADDED:
+ case RMAP_EVENT_MATCH_DELETED:
+ case RMAP_EVENT_MATCH_REPLACED:
+ case RMAP_EVENT_INDEX_ADDED:
+ case RMAP_EVENT_INDEX_DELETED:
+ case RMAP_EVENT_CALL_DELETED:
+ case RMAP_EVENT_PLIST_DELETED:
+ case RMAP_EVENT_CLIST_DELETED:
+ case RMAP_EVENT_ECLIST_DELETED:
+ case RMAP_EVENT_LLIST_DELETED:
+ case RMAP_EVENT_ASLIST_DELETED:
+ case RMAP_EVENT_FILTER_DELETED:
+ /* This function returns the appropriate 'deleted' event type
+ * for every 'added' event type passed to this function.
+ * This is done only for named entities used in the
+ * route-map match commands.
+ * This function is not to be invoked for any of the other event
+ * types.
+ */
+ assert(0);
+ }
+
+ assert(0);
+ /*
+ * Return to make c happy but if we get here something has gone
+ * terribly terribly wrong, so yes this return makes no sense.
+ */
+ return RMAP_EVENT_CALL_ADDED;
+}
+
+/* Add match statement to route map. */
+enum rmap_compile_rets route_map_add_match(struct route_map_index *index,
+ const char *match_name,
+ const char *match_arg,
+ route_map_event_t type)
+{
+ struct route_map_rule *rule;
+ struct route_map_rule *next;
+ const struct route_map_rule_cmd *cmd;
+ void *compile;
+ int8_t delete_rmap_event_type = 0;
+ const char *rule_key;
+
+ /* First lookup rule for add match statement. */
+ cmd = route_map_lookup_match(match_name);
+ if (cmd == NULL)
+ return RMAP_RULE_MISSING;
+
+ /* Next call compile function for this match statement. */
+ if (cmd->func_compile) {
+ compile = (*cmd->func_compile)(match_arg);
+ if (compile == NULL)
+ return RMAP_COMPILE_ERROR;
+ } else
+ compile = NULL;
+ /* use the compiled results if applicable */
+ if (compile && cmd->func_get_rmap_rule_key)
+ rule_key = (*cmd->func_get_rmap_rule_key)
+ (compile);
+ else
+ rule_key = match_arg;
+
+ /* If argument is completely same ignore it. */
+ for (rule = index->match_list.head; rule; rule = next) {
+ next = rule->next;
+ if (rule->cmd == cmd) {
+ /* If the configured route-map match rule is exactly
+ * the same as the existing configuration then,
+ * ignore the duplicate configuration.
+ */
+ if (rulecmp(match_arg, rule->rule_str) == 0) {
+ if (cmd->func_free)
+ (*cmd->func_free)(compile);
+
+ return RMAP_COMPILE_SUCCESS;
+ }
+
+ /* If IPv4 or IPv6 prefix-list match criteria
+ * has been delete to the route-map index, update
+ * the route-map's prefix table.
+ */
+ if (IS_RULE_IPv4_PREFIX_LIST(match_name))
+ route_map_pfx_tbl_update(
+ RMAP_EVENT_PLIST_DELETED, index, AFI_IP,
+ rule->rule_str);
+ else if (IS_RULE_IPv6_PREFIX_LIST(match_name))
+ route_map_pfx_tbl_update(
+ RMAP_EVENT_PLIST_DELETED, index,
+ AFI_IP6, rule->rule_str);
+
+ /* Remove the dependency of the route-map on the rule
+ * that is being replaced.
+ */
+ if (type >= RMAP_EVENT_CALL_ADDED) {
+ delete_rmap_event_type =
+ get_route_map_delete_event(type);
+ route_map_upd8_dependency(
+ delete_rmap_event_type,
+ rule->rule_str,
+ index->map->name);
+ }
+
+ route_map_rule_delete(&index->match_list, rule);
+ }
+ }
+
+ /* Add new route map match rule. */
+ rule = route_map_rule_new();
+ rule->cmd = cmd;
+ rule->value = compile;
+ if (match_arg)
+ rule->rule_str = XSTRDUP(MTYPE_ROUTE_MAP_RULE_STR, match_arg);
+ else
+ rule->rule_str = NULL;
+
+ /* Add new route match rule to linked list. */
+ route_map_rule_add(&index->match_list, rule);
+
+ /* If IPv4 or IPv6 prefix-list match criteria
+ * has been added to the route-map index, update
+ * the route-map's prefix table.
+ */
+ if (IS_RULE_IPv4_PREFIX_LIST(match_name)) {
+ route_map_pfx_tbl_update(RMAP_EVENT_PLIST_ADDED, index, AFI_IP,
+ match_arg);
+ } else if (IS_RULE_IPv6_PREFIX_LIST(match_name)) {
+ route_map_pfx_tbl_update(RMAP_EVENT_PLIST_ADDED, index, AFI_IP6,
+ match_arg);
+ }
+
+ /* Execute event hook. */
+ if (route_map_master.event_hook) {
+ (*route_map_master.event_hook)(index->map->name);
+ route_map_notify_dependencies(index->map->name,
+ RMAP_EVENT_CALL_ADDED);
+ }
+ if (type != RMAP_EVENT_MATCH_ADDED)
+ route_map_upd8_dependency(type, rule_key, index->map->name);
+
+ return RMAP_COMPILE_SUCCESS;
+}
+
+/* Delete specified route match rule. */
+enum rmap_compile_rets route_map_delete_match(struct route_map_index *index,
+ const char *match_name,
+ const char *match_arg,
+ route_map_event_t type)
+{
+ struct route_map_rule *rule;
+ const struct route_map_rule_cmd *cmd;
+ const char *rule_key;
+
+ cmd = route_map_lookup_match(match_name);
+ if (cmd == NULL)
+ return RMAP_RULE_MISSING;
+
+ for (rule = index->match_list.head; rule; rule = rule->next)
+ if (rule->cmd == cmd && (rulecmp(rule->rule_str, match_arg) == 0
+ || match_arg == NULL)) {
+ /* Execute event hook. */
+ if (route_map_master.event_hook) {
+ (*route_map_master.event_hook)(index->map->name);
+ route_map_notify_dependencies(
+ index->map->name,
+ RMAP_EVENT_CALL_ADDED);
+ }
+ if (cmd->func_get_rmap_rule_key)
+ rule_key = (*cmd->func_get_rmap_rule_key)
+ (rule->value);
+ else
+ rule_key = match_arg;
+
+ if (type != RMAP_EVENT_MATCH_DELETED && rule_key)
+ route_map_upd8_dependency(type, rule_key,
+ index->map->name);
+
+ route_map_rule_delete(&index->match_list, rule);
+
+ /* If IPv4 or IPv6 prefix-list match criteria
+ * has been delete from the route-map index, update
+ * the route-map's prefix table.
+ */
+ if (IS_RULE_IPv4_PREFIX_LIST(match_name)) {
+ route_map_pfx_tbl_update(
+ RMAP_EVENT_PLIST_DELETED, index, AFI_IP,
+ match_arg);
+ } else if (IS_RULE_IPv6_PREFIX_LIST(match_name)) {
+ route_map_pfx_tbl_update(
+ RMAP_EVENT_PLIST_DELETED, index,
+ AFI_IP6, match_arg);
+ }
+
+ return RMAP_COMPILE_SUCCESS;
+ }
+ /* Can't find matched rule. */
+ return RMAP_RULE_MISSING;
+}
+
+/* Add route-map set statement to the route map. */
+enum rmap_compile_rets route_map_add_set(struct route_map_index *index,
+ const char *set_name,
+ const char *set_arg)
+{
+ struct route_map_rule *rule;
+ struct route_map_rule *next;
+ const struct route_map_rule_cmd *cmd;
+ void *compile;
+
+ cmd = route_map_lookup_set(set_name);
+ if (cmd == NULL)
+ return RMAP_RULE_MISSING;
+
+ /* Next call compile function for this match statement. */
+ if (cmd->func_compile) {
+ compile = (*cmd->func_compile)(set_arg);
+ if (compile == NULL)
+ return RMAP_COMPILE_ERROR;
+ } else
+ compile = NULL;
+
+ /* Add by WJL. if old set command of same kind exist, delete it first
+ to ensure only one set command of same kind exist under a
+ route_map_index. */
+ for (rule = index->set_list.head; rule; rule = next) {
+ next = rule->next;
+ if (rule->cmd == cmd)
+ route_map_rule_delete(&index->set_list, rule);
+ }
+
+ /* Add new route map match rule. */
+ rule = route_map_rule_new();
+ rule->cmd = cmd;
+ rule->value = compile;
+ if (set_arg)
+ rule->rule_str = XSTRDUP(MTYPE_ROUTE_MAP_RULE_STR, set_arg);
+ else
+ rule->rule_str = NULL;
+
+ /* Add new route match rule to linked list. */
+ route_map_rule_add(&index->set_list, rule);
+
+ /* Execute event hook. */
+ if (route_map_master.event_hook) {
+ (*route_map_master.event_hook)(index->map->name);
+ route_map_notify_dependencies(index->map->name,
+ RMAP_EVENT_CALL_ADDED);
+ }
+ return RMAP_COMPILE_SUCCESS;
+}
+
+/* Delete route map set rule. */
+enum rmap_compile_rets route_map_delete_set(struct route_map_index *index,
+ const char *set_name,
+ const char *set_arg)
+{
+ struct route_map_rule *rule;
+ const struct route_map_rule_cmd *cmd;
+
+ cmd = route_map_lookup_set(set_name);
+ if (cmd == NULL)
+ return RMAP_RULE_MISSING;
+
+ for (rule = index->set_list.head; rule; rule = rule->next)
+ if ((rule->cmd == cmd) && (rulecmp(rule->rule_str, set_arg) == 0
+ || set_arg == NULL)) {
+ route_map_rule_delete(&index->set_list, rule);
+ /* Execute event hook. */
+ if (route_map_master.event_hook) {
+ (*route_map_master.event_hook)(index->map->name);
+ route_map_notify_dependencies(
+ index->map->name,
+ RMAP_EVENT_CALL_ADDED);
+ }
+ return RMAP_COMPILE_SUCCESS;
+ }
+ /* Can't find matched rule. */
+ return RMAP_RULE_MISSING;
+}
+
+static enum route_map_cmd_result_t
+route_map_apply_match(struct route_map_rule_list *match_list,
+ const struct prefix *prefix, void *object)
+{
+ enum route_map_cmd_result_t ret = RMAP_NOMATCH;
+ struct route_map_rule *match;
+ bool is_matched = false;
+
+
+ /* Check all match rule and if there is no match rule, go to the
+ set statement. */
+ if (!match_list->head)
+ ret = RMAP_MATCH;
+ else {
+ for (match = match_list->head; match; match = match->next) {
+ /*
+ * Try each match statement. If any match does not
+ * return RMAP_MATCH or RMAP_NOOP, return.
+ * Otherwise continue on to next match statement.
+ * All match statements must MATCH for
+ * end-result to be a match.
+ * (Exception:If match stmts result in a mix of
+ * MATCH/NOOP, then also end-result is a match)
+ * If all result in NOOP, end-result is NOOP.
+ */
+ ret = (*match->cmd->func_apply)(match->value, prefix,
+ object);
+
+ /*
+ * If the consolidated result of func_apply is:
+ * -----------------------------------------------
+ * | MATCH | NOMATCH | NOOP | Final Result |
+ * ------------------------------------------------
+ * | yes | yes | yes | NOMATCH |
+ * | no | no | yes | NOOP |
+ * | yes | no | yes | MATCH |
+ * | no | yes | yes | NOMATCH |
+ * |-----------------------------------------------
+ *
+ * Traditionally, all rules within route-map
+ * should match for it to MATCH.
+ * If there are noops within the route-map rules,
+ * it follows the above matrix.
+ *
+ * Eg: route-map rm1 permit 10
+ * match rule1
+ * match rule2
+ * match rule3
+ * ....
+ * route-map rm1 permit 20
+ * match ruleX
+ * match ruleY
+ * ...
+ */
+
+ switch (ret) {
+ case RMAP_MATCH:
+ is_matched = true;
+ break;
+
+ case RMAP_NOMATCH:
+ return ret;
+
+ case RMAP_NOOP:
+ if (is_matched)
+ ret = RMAP_MATCH;
+ break;
+
+ default:
+ break;
+ }
+
+ }
+ }
+ return ret;
+}
+
+static struct list *route_map_get_index_list(struct route_node **rn,
+ const struct prefix *prefix,
+ struct route_table *table)
+{
+ struct route_node *tmp_rn = NULL;
+
+ if (!(*rn)) {
+ *rn = route_node_match(table, prefix);
+
+ if (!(*rn))
+ return NULL;
+
+ if ((*rn)->info)
+ return (struct list *)((*rn)->info);
+
+ /* If rn->info is NULL, get the parent.
+ * Store the rn in tmp_rn and unlock it later.
+ */
+ tmp_rn = *rn;
+ }
+
+ do {
+ *rn = (*rn)->parent;
+ if (tmp_rn)
+ route_unlock_node(tmp_rn);
+
+ if (!(*rn))
+ break;
+
+ if ((*rn)->info) {
+ route_lock_node(*rn);
+ return (struct list *)((*rn)->info);
+ }
+ } while (!(*rn)->info);
+
+ return NULL;
+}
+
+/*
+ * This function returns the route-map index that best matches the prefix.
+ */
+static struct route_map_index *
+route_map_get_index(struct route_map *map, const struct prefix *prefix,
+ void *object, enum route_map_cmd_result_t *match_ret)
+{
+ enum route_map_cmd_result_t ret = RMAP_NOMATCH;
+ struct list *candidate_rmap_list = NULL;
+ struct route_node *rn = NULL;
+ struct listnode *ln = NULL, *nn = NULL;
+ struct route_map_index *index = NULL, *best_index = NULL;
+ struct route_map_index *head_index = NULL;
+ struct route_table *table = NULL;
+ unsigned char family = prefix->family;
+
+ if (family == AF_INET)
+ table = map->ipv4_prefix_table;
+ else
+ table = map->ipv6_prefix_table;
+
+ if (!table)
+ return NULL;
+
+ do {
+ candidate_rmap_list =
+ route_map_get_index_list(&rn, prefix, table);
+ if (!rn)
+ break;
+
+ /* If the index at the head of the list is of seq higher
+ * than that in best_index, ignore the list and get the
+ * parent node's list.
+ */
+ head_index = (struct route_map_index *)(listgetdata(
+ listhead(candidate_rmap_list)));
+ if (best_index && head_index
+ && (best_index->pref < head_index->pref)) {
+ route_unlock_node(rn);
+ continue;
+ }
+
+ for (ALL_LIST_ELEMENTS(candidate_rmap_list, ln, nn, index)) {
+ /* If the index is of seq higher than that in
+ * best_index, ignore the list and get the parent
+ * node's list.
+ */
+ if (best_index && (best_index->pref < index->pref))
+ break;
+
+ ret = route_map_apply_match(&index->match_list, prefix,
+ object);
+
+ if (ret == RMAP_MATCH) {
+ *match_ret = ret;
+ best_index = index;
+ break;
+ } else if (ret == RMAP_NOOP) {
+ /*
+ * If match_ret is denymatch, even if we see
+ * more noops, we retain this return value and
+ * return this eventually if there are no
+ * matches.
+ * If a best match route-map index already
+ * exists, do not reset the match_ret.
+ */
+ if (!best_index && (*match_ret != RMAP_NOMATCH))
+ *match_ret = ret;
+ } else {
+ /*
+ * ret is RMAP_NOMATCH.
+ * If a best match route-map index already
+ * exists, do not reset the match_ret.
+ */
+ if (!best_index)
+ *match_ret = ret;
+ }
+ }
+
+ route_unlock_node(rn);
+
+ } while (rn);
+
+ return best_index;
+}
+
+static int route_map_candidate_list_cmp(struct route_map_index *idx1,
+ struct route_map_index *idx2)
+{
+ return idx1->pref - idx2->pref;
+}
+
+/*
+ * This function adds the route-map index into the default route's
+ * route-node in the route-map's IPv4/IPv6 prefix-table.
+ */
+static void route_map_pfx_table_add_default(afi_t afi,
+ struct route_map_index *index)
+{
+ struct route_node *rn = NULL;
+ struct list *rmap_candidate_list = NULL;
+ struct prefix p;
+ bool updated_rn = false;
+ struct route_table *table = NULL;
+
+ memset(&p, 0, sizeof(p));
+ p.family = afi2family(afi);
+ p.prefixlen = 0;
+
+ if (p.family == AF_INET) {
+ table = index->map->ipv4_prefix_table;
+ if (!table)
+ index->map->ipv4_prefix_table = route_table_init();
+
+ table = index->map->ipv4_prefix_table;
+ } else {
+ table = index->map->ipv6_prefix_table;
+ if (!table)
+ index->map->ipv6_prefix_table = route_table_init();
+
+ table = index->map->ipv6_prefix_table;
+ }
+
+ /* Add default route to table */
+ rn = route_node_get(table, &p);
+
+ if (!rn)
+ return;
+
+ if (!rn->info) {
+ rmap_candidate_list = list_new();
+ rmap_candidate_list->cmp =
+ (int (*)(void *, void *))route_map_candidate_list_cmp;
+ rn->info = rmap_candidate_list;
+ } else {
+ rmap_candidate_list = (struct list *)rn->info;
+ updated_rn = true;
+ }
+
+ listnode_add_sort_nodup(rmap_candidate_list, index);
+ if (updated_rn)
+ route_unlock_node(rn);
+}
+
+/*
+ * This function removes the route-map index from the default route's
+ * route-node in the route-map's IPv4/IPv6 prefix-table.
+ */
+static void route_map_pfx_table_del_default(afi_t afi,
+ struct route_map_index *index)
+{
+ struct route_node *rn = NULL;
+ struct list *rmap_candidate_list = NULL;
+ struct prefix p;
+ struct route_table *table = NULL;
+
+ memset(&p, 0, sizeof(p));
+ p.family = afi2family(afi);
+ p.prefixlen = 0;
+
+ if (p.family == AF_INET)
+ table = index->map->ipv4_prefix_table;
+ else
+ table = index->map->ipv6_prefix_table;
+
+ /* Remove RMAP index from default route in table */
+ rn = route_node_lookup(table, &p);
+ if (!rn || !rn->info)
+ return;
+
+ rmap_candidate_list = (struct list *)rn->info;
+
+ listnode_delete(rmap_candidate_list, index);
+
+ if (listcount(rmap_candidate_list) == 0) {
+ list_delete(&rmap_candidate_list);
+ rn->info = NULL;
+ route_unlock_node(rn);
+ }
+ route_unlock_node(rn);
+}
+
+/*
+ * This function adds the route-map index to the route-node for
+ * the prefix-entry in the route-map's IPv4/IPv6 prefix-table.
+ */
+static void route_map_pfx_table_add(struct route_table *table,
+ struct route_map_index *index,
+ struct prefix_list_entry *pentry)
+{
+ struct route_node *rn = NULL;
+ struct list *rmap_candidate_list = NULL;
+ bool updated_rn = false;
+
+ rn = route_node_get(table, &pentry->prefix);
+ if (!rn)
+ return;
+
+ if (!rn->info) {
+ rmap_candidate_list = list_new();
+ rmap_candidate_list->cmp =
+ (int (*)(void *, void *))route_map_candidate_list_cmp;
+ rn->info = rmap_candidate_list;
+ } else {
+ rmap_candidate_list = (struct list *)rn->info;
+ updated_rn = true;
+ }
+
+ listnode_add_sort_nodup(rmap_candidate_list, index);
+ if (updated_rn)
+ route_unlock_node(rn);
+}
+
+/*
+ * This function removes the route-map index from the route-node for
+ * the prefix-entry in the route-map's IPv4/IPv6 prefix-table.
+ */
+static void route_map_pfx_table_del(struct route_table *table,
+ struct route_map_index *index,
+ struct prefix_list_entry *pentry)
+{
+ struct route_node *rn = NULL;
+ struct list *rmap_candidate_list = NULL;
+
+ rn = route_node_lookup(table, &pentry->prefix);
+ if (!rn || !rn->info)
+ return;
+
+ rmap_candidate_list = (struct list *)rn->info;
+
+ listnode_delete(rmap_candidate_list, index);
+
+ if (listcount(rmap_candidate_list) == 0) {
+ list_delete(&rmap_candidate_list);
+ rn->info = NULL;
+ route_unlock_node(rn);
+ }
+ route_unlock_node(rn);
+}
+
+/* This function checks for the presence of an IPv4 prefix-list
+ * match rule in the given route-map index.
+ */
+static bool route_map_is_ip_pfx_list_rule_present(struct route_map_index *index)
+{
+ struct route_map_rule_list *match_list = NULL;
+ struct route_map_rule *rule = NULL;
+
+ match_list = &index->match_list;
+ for (rule = match_list->head; rule; rule = rule->next)
+ if (IS_RULE_IPv4_PREFIX_LIST(rule->cmd->str))
+ return true;
+
+ return false;
+}
+
+/* This function checks for the presence of an IPv6 prefix-list
+ * match rule in the given route-map index.
+ */
+static bool
+route_map_is_ipv6_pfx_list_rule_present(struct route_map_index *index)
+{
+ struct route_map_rule_list *match_list = NULL;
+ struct route_map_rule *rule = NULL;
+
+ match_list = &index->match_list;
+ for (rule = match_list->head; rule; rule = rule->next)
+ if (IS_RULE_IPv6_PREFIX_LIST(rule->cmd->str))
+ return true;
+
+ return false;
+}
+
+/* This function does the following:
+ * 1) If plist_name is not present, search for a IPv4 or IPv6 prefix-list
+ * match clause (based on the afi passed to this foo) and get the
+ * prefix-list name.
+ * 2) Look up the prefix-list using the name.
+ * 3) If the prefix-list is not found then, add the index to the IPv4/IPv6
+ * default-route's node in the trie (based on the afi passed to this foo).
+ * 4) If the prefix-list is found then, remove the index from the IPv4/IPv6
+ * default-route's node in the trie (based on the afi passed to this foo).
+ * 5) If a prefix-entry is passed then, create a route-node for this entry and
+ * add this index to the route-node.
+ * 6) If prefix-entry is not passed then, for every prefix-entry in the
+ * prefix-list, create a route-node for this entry and
+ * add this index to the route-node.
+ */
+static void route_map_add_plist_entries(afi_t afi,
+ struct route_map_index *index,
+ const char *plist_name,
+ struct prefix_list_entry *entry)
+{
+ struct route_map_rule_list *match_list = NULL;
+ struct route_map_rule *match = NULL;
+ struct prefix_list *plist = NULL;
+ struct prefix_list_entry *pentry = NULL;
+ bool plist_rule_is_present = false;
+
+ if (!plist_name) {
+ match_list = &index->match_list;
+
+ for (match = match_list->head; match; match = match->next) {
+ if (afi == AFI_IP) {
+ if (IS_RULE_IPv4_PREFIX_LIST(match->cmd->str)) {
+ plist_rule_is_present = true;
+ break;
+ }
+ } else {
+ if (IS_RULE_IPv6_PREFIX_LIST(match->cmd->str)) {
+ plist_rule_is_present = true;
+ break;
+ }
+ }
+ }
+
+ if (plist_rule_is_present)
+ plist = prefix_list_lookup(afi, match->rule_str);
+ } else {
+ plist = prefix_list_lookup(afi, plist_name);
+ }
+
+ if (!plist) {
+ route_map_pfx_table_add_default(afi, index);
+ return;
+ }
+
+ /* Default entry should be deleted only if the first entry of the
+ * prefix-list is created.
+ */
+ if (entry) {
+ if (plist->count == 1)
+ route_map_pfx_table_del_default(afi, index);
+ } else {
+ route_map_pfx_table_del_default(afi, index);
+ }
+
+ if (entry) {
+ if (afi == AFI_IP) {
+ route_map_pfx_table_add(index->map->ipv4_prefix_table,
+ index, entry);
+ } else {
+ route_map_pfx_table_add(index->map->ipv6_prefix_table,
+ index, entry);
+ }
+ } else {
+ for (pentry = plist->head; pentry; pentry = pentry->next) {
+ if (afi == AFI_IP) {
+ route_map_pfx_table_add(
+ index->map->ipv4_prefix_table, index,
+ pentry);
+ } else {
+ route_map_pfx_table_add(
+ index->map->ipv6_prefix_table, index,
+ pentry);
+ }
+ }
+ }
+}
+
+/* This function does the following:
+ * 1) If plist_name is not present, search for a IPv4 or IPv6 prefix-list
+ * match clause (based on the afi passed to this foo) and get the
+ * prefix-list name.
+ * 2) Look up the prefix-list using the name.
+ * 3) If the prefix-list is not found then, delete the index from the IPv4/IPv6
+ * default-route's node in the trie (based on the afi passed to this foo).
+ * 4) If a prefix-entry is passed then, remove this index from the route-node
+ * for the prefix in this prefix-entry.
+ * 5) If prefix-entry is not passed then, for every prefix-entry in the
+ * prefix-list, remove this index from the route-node
+ * for the prefix in this prefix-entry.
+ */
+static void route_map_del_plist_entries(afi_t afi,
+ struct route_map_index *index,
+ const char *plist_name,
+ struct prefix_list_entry *entry)
+{
+ struct route_map_rule_list *match_list = NULL;
+ struct route_map_rule *match = NULL;
+ struct prefix_list *plist = NULL;
+ struct prefix_list_entry *pentry = NULL;
+ bool plist_rule_is_present = false;
+
+ if (!plist_name) {
+ match_list = &index->match_list;
+
+ for (match = match_list->head; match; match = match->next) {
+ if (afi == AFI_IP) {
+ if (IS_RULE_IPv4_PREFIX_LIST(match->cmd->str)) {
+ plist_rule_is_present = true;
+ break;
+ }
+ } else {
+ if (IS_RULE_IPv6_PREFIX_LIST(match->cmd->str)) {
+ plist_rule_is_present = true;
+ break;
+ }
+ }
+ }
+
+ if (plist_rule_is_present)
+ plist = prefix_list_lookup(afi, match->rule_str);
+ } else {
+ plist = prefix_list_lookup(afi, plist_name);
+ }
+
+ if (!plist) {
+ route_map_pfx_table_del_default(afi, index);
+ return;
+ }
+
+ if (entry) {
+ if (afi == AFI_IP) {
+ route_map_pfx_table_del(index->map->ipv4_prefix_table,
+ index, entry);
+ } else {
+ route_map_pfx_table_del(index->map->ipv6_prefix_table,
+ index, entry);
+ }
+ } else {
+ for (pentry = plist->head; pentry; pentry = pentry->next) {
+ if (afi == AFI_IP) {
+ route_map_pfx_table_del(
+ index->map->ipv4_prefix_table, index,
+ pentry);
+ } else {
+ route_map_pfx_table_del(
+ index->map->ipv6_prefix_table, index,
+ pentry);
+ }
+ }
+ }
+}
+
+/*
+ * This function handles the cases where a prefix-list is added/removed
+ * as a match command from a particular route-map index.
+ * It updates the prefix-table of the route-map accordingly.
+ */
+static void route_map_trie_update(afi_t afi, route_map_event_t event,
+ struct route_map_index *index,
+ const char *plist_name)
+{
+ if (event == RMAP_EVENT_PLIST_ADDED) {
+ if (afi == AFI_IP) {
+ if (!route_map_is_ipv6_pfx_list_rule_present(index)) {
+ route_map_pfx_table_del_default(AFI_IP6, index);
+ route_map_add_plist_entries(afi, index,
+ plist_name, NULL);
+ } else {
+ route_map_del_plist_entries(AFI_IP6, index,
+ NULL, NULL);
+ }
+ } else {
+ if (!route_map_is_ip_pfx_list_rule_present(index)) {
+ route_map_pfx_table_del_default(AFI_IP, index);
+ route_map_add_plist_entries(afi, index,
+ plist_name, NULL);
+ } else {
+ route_map_del_plist_entries(AFI_IP, index, NULL,
+ NULL);
+ }
+ }
+ } else if (event == RMAP_EVENT_PLIST_DELETED) {
+ if (afi == AFI_IP) {
+ route_map_del_plist_entries(afi, index, plist_name,
+ NULL);
+
+ /* If IPv6 prefix-list match rule is not present,
+ * add this index to the IPv4 default route's trie
+ * node.
+ * Also, add this index to the trie nodes created
+ * for each of the prefix-entries within the IPv6
+ * prefix-list, if the IPv6 prefix-list match rule
+ * is present. Else, add this index to the IPv6
+ * default route's trie node.
+ */
+ if (!route_map_is_ipv6_pfx_list_rule_present(index))
+ route_map_pfx_table_add_default(afi, index);
+
+ route_map_add_plist_entries(AFI_IP6, index, NULL, NULL);
+ } else {
+ route_map_del_plist_entries(afi, index, plist_name,
+ NULL);
+
+ /* If IPv4 prefix-list match rule is not present,
+ * add this index to the IPv6 default route's trie
+ * node.
+ * Also, add this index to the trie nodes created
+ * for each of the prefix-entries within the IPv4
+ * prefix-list, if the IPv4 prefix-list match rule
+ * is present. Else, add this index to the IPv4
+ * default route's trie node.
+ */
+ if (!route_map_is_ip_pfx_list_rule_present(index))
+ route_map_pfx_table_add_default(afi, index);
+
+ route_map_add_plist_entries(AFI_IP, index, NULL, NULL);
+ }
+ }
+}
+
+/*
+ * This function handles the cases where a route-map index and
+ * prefix-list is added/removed.
+ * It updates the prefix-table of the route-map accordingly.
+ */
+static void route_map_pfx_tbl_update(route_map_event_t event,
+ struct route_map_index *index, afi_t afi,
+ const char *plist_name)
+{
+ struct route_map *rmap = NULL;
+
+ if (!index)
+ return;
+
+ if (event == RMAP_EVENT_INDEX_ADDED) {
+ route_map_pfx_table_add_default(AFI_IP, index);
+ route_map_pfx_table_add_default(AFI_IP6, index);
+ return;
+ }
+
+ if (event == RMAP_EVENT_INDEX_DELETED) {
+ route_map_pfx_table_del_default(AFI_IP, index);
+ route_map_pfx_table_del_default(AFI_IP6, index);
+
+ if ((index->map->head == NULL) && (index->map->tail == NULL)) {
+ rmap = index->map;
+
+ if (rmap->ipv4_prefix_table) {
+ route_table_finish(rmap->ipv4_prefix_table);
+ rmap->ipv4_prefix_table = NULL;
+ }
+
+ if (rmap->ipv6_prefix_table) {
+ route_table_finish(rmap->ipv6_prefix_table);
+ rmap->ipv6_prefix_table = NULL;
+ }
+ }
+ return;
+ }
+
+ /* Handle prefix-list match rule addition/deletion.
+ */
+ route_map_trie_update(afi, event, index, plist_name);
+}
+
+/*
+ * This function handles the cases where a new prefix-entry is added to
+ * a prefix-list or, an existing prefix-entry is removed from the prefix-list.
+ * It updates the prefix-table of the route-map accordingly.
+ */
+static void route_map_pentry_update(route_map_event_t event,
+ const char *plist_name,
+ struct route_map_index *index,
+ struct prefix_list_entry *pentry)
+{
+ struct prefix_list *plist = NULL;
+ afi_t afi;
+ unsigned char family = pentry->prefix.family;
+
+ if (family == AF_INET) {
+ afi = AFI_IP;
+ plist = prefix_list_lookup(AFI_IP, plist_name);
+ } else {
+ afi = AFI_IP6;
+ plist = prefix_list_lookup(AFI_IP6, plist_name);
+ }
+
+ if (event == RMAP_EVENT_PLIST_ADDED) {
+ if (afi == AFI_IP) {
+ if (!route_map_is_ipv6_pfx_list_rule_present(index))
+ route_map_add_plist_entries(afi, index,
+ plist_name, pentry);
+ } else {
+ if (!route_map_is_ip_pfx_list_rule_present(index))
+ route_map_add_plist_entries(afi, index,
+ plist_name, pentry);
+ }
+ } else if (event == RMAP_EVENT_PLIST_DELETED) {
+ route_map_del_plist_entries(afi, index, plist_name, pentry);
+
+ if (plist->count == 1) {
+ if (afi == AFI_IP) {
+ if (!route_map_is_ipv6_pfx_list_rule_present(
+ index))
+ route_map_pfx_table_add_default(afi,
+ index);
+ } else {
+ if (!route_map_is_ip_pfx_list_rule_present(
+ index))
+ route_map_pfx_table_add_default(afi,
+ index);
+ }
+ }
+ }
+}
+
+static void route_map_pentry_process_dependency(struct hash_bucket *bucket,
+ void *data)
+{
+ char *rmap_name = NULL;
+ struct route_map *rmap = NULL;
+ struct route_map_index *index = NULL;
+ struct route_map_rule_list *match_list = NULL;
+ struct route_map_rule *match = NULL;
+ struct route_map_dep_data *dep_data = NULL;
+ struct route_map_pentry_dep *pentry_dep =
+ (struct route_map_pentry_dep *)data;
+ unsigned char family = pentry_dep->pentry->prefix.family;
+
+ dep_data = (struct route_map_dep_data *)bucket->data;
+ if (!dep_data)
+ return;
+
+ rmap_name = dep_data->rname;
+ rmap = route_map_lookup_by_name(rmap_name);
+ if (!rmap || !rmap->head)
+ return;
+
+ for (index = rmap->head; index; index = index->next) {
+ match_list = &index->match_list;
+
+ if (!match_list)
+ continue;
+
+ for (match = match_list->head; match; match = match->next) {
+ if (strcmp(match->rule_str, pentry_dep->plist_name)
+ == 0) {
+ if (IS_RULE_IPv4_PREFIX_LIST(match->cmd->str)
+ && family == AF_INET) {
+ route_map_pentry_update(
+ pentry_dep->event,
+ pentry_dep->plist_name, index,
+ pentry_dep->pentry);
+ } else if (IS_RULE_IPv6_PREFIX_LIST(
+ match->cmd->str)
+ && family == AF_INET6) {
+ route_map_pentry_update(
+ pentry_dep->event,
+ pentry_dep->plist_name, index,
+ pentry_dep->pentry);
+ }
+ }
+ }
+ }
+}
+
+void route_map_notify_pentry_dependencies(const char *affected_name,
+ struct prefix_list_entry *pentry,
+ route_map_event_t event)
+{
+ struct route_map_dep *dep = NULL;
+ struct hash *upd8_hash = NULL;
+ struct route_map_pentry_dep pentry_dep;
+
+ if (!affected_name || !pentry)
+ return;
+
+ upd8_hash = route_map_get_dep_hash(event);
+ if (!upd8_hash)
+ return;
+
+ dep = (struct route_map_dep *)hash_get(upd8_hash, (void *)affected_name,
+ NULL);
+ if (dep) {
+ if (!dep->this_hash)
+ dep->this_hash = upd8_hash;
+
+ memset(&pentry_dep, 0, sizeof(pentry_dep));
+ pentry_dep.pentry = pentry;
+ pentry_dep.plist_name = affected_name;
+ pentry_dep.event = event;
+
+ hash_iterate(dep->dep_rmap_hash,
+ route_map_pentry_process_dependency,
+ (void *)&pentry_dep);
+ }
+}
+
+/* Apply route map's each index to the object.
+
+ The matrix for a route-map looks like this:
+ (note, this includes the description for the "NEXT"
+ and "GOTO" frobs now
+
+ | Match | No Match | No op
+ |-----------|--------------|-------
+ permit | action | cont | cont.
+ | | default:deny | default:permit
+ -------------------+-----------------------
+ | deny | cont | cont.
+ deny | | default:deny | default:permit
+ |-----------|--------------|--------
+
+ action)
+ -Apply Set statements, accept route
+ -If Call statement is present jump to the specified route-map, if it
+ denies the route we finish.
+ -If NEXT is specified, goto NEXT statement
+ -If GOTO is specified, goto the first clause where pref > nextpref
+ -If nothing is specified, do as Cisco and finish
+ deny)
+ -Route is denied by route-map.
+ cont)
+ -Goto Next index
+
+ If we get no matches after we've processed all updates, then the route
+ is dropped too.
+
+ Some notes on the new "CALL", "NEXT" and "GOTO"
+ call WORD - If this clause is matched, then the set statements
+ are executed and then we jump to route-map 'WORD'. If
+ this route-map denies the route, we finish, in other
+ case we
+ do whatever the exit policy (EXIT, NEXT or GOTO) tells.
+ on-match next - If this clause is matched, then the set statements
+ are executed and then we drop through to the next clause
+ on-match goto n - If this clause is matched, then the set statements
+ are executed and then we goto the nth clause, or the
+ first clause greater than this. In order to ensure
+ route-maps *always* exit, you cannot jump backwards.
+ Sorry ;)
+
+ We need to make sure our route-map processing matches the above
+*/
+route_map_result_t route_map_apply_ext(struct route_map *map,
+ const struct prefix *prefix,
+ void *match_object, void *set_object,
+ int *pref)
+{
+ static int recursion = 0;
+ enum route_map_cmd_result_t match_ret = RMAP_NOMATCH;
+ route_map_result_t ret = RMAP_PERMITMATCH;
+ struct route_map_index *index = NULL;
+ struct route_map_rule *set = NULL;
+ bool skip_match_clause = false;
+
+ if (recursion > RMAP_RECURSION_LIMIT) {
+ flog_warn(
+ EC_LIB_RMAP_RECURSION_LIMIT,
+ "route-map recursion limit (%d) reached, discarding route",
+ RMAP_RECURSION_LIMIT);
+ recursion = 0;
+ return RMAP_DENYMATCH;
+ }
+
+ if (map == NULL || map->head == NULL) {
+ ret = RMAP_DENYMATCH;
+ goto route_map_apply_end;
+ }
+
+ map->applied++;
+
+ if ((!map->optimization_disabled)
+ && (map->ipv4_prefix_table || map->ipv6_prefix_table)) {
+ index = route_map_get_index(map, prefix, match_object,
+ &match_ret);
+ if (index) {
+ index->applied++;
+ if (rmap_debug)
+ zlog_debug(
+ "Best match route-map: %s, sequence: %d for pfx: %pFX, result: %s",
+ map->name, index->pref, prefix,
+ route_map_cmd_result_str(match_ret));
+ } else {
+ if (rmap_debug)
+ zlog_debug(
+ "No best match sequence for pfx: %pFX in route-map: %s, result: %s",
+ prefix, map->name,
+ route_map_cmd_result_str(match_ret));
+ /*
+ * No index matches this prefix. Return deny unless,
+ * match_ret = RMAP_NOOP.
+ */
+ if (match_ret == RMAP_NOOP)
+ ret = RMAP_PERMITMATCH;
+ else
+ ret = RMAP_DENYMATCH;
+ goto route_map_apply_end;
+ }
+ skip_match_clause = true;
+ } else {
+ index = map->head;
+ }
+
+ for (; index; index = index->next) {
+ if (!skip_match_clause) {
+ index->applied++;
+ /* Apply this index. */
+ match_ret = route_map_apply_match(&index->match_list,
+ prefix, match_object);
+ if (rmap_debug) {
+ zlog_debug(
+ "Route-map: %s, sequence: %d, prefix: %pFX, result: %s",
+ map->name, index->pref, prefix,
+ route_map_cmd_result_str(match_ret));
+ }
+ } else
+ skip_match_clause = false;
+
+
+ /* Now we apply the matrix from above */
+ if (match_ret == RMAP_NOOP)
+ /*
+ * Do not change the return value. Retain the previous
+ * return value. Previous values can be:
+ * 1)permitmatch (if a nomatch was never
+ * seen before in this route-map.)
+ * 2)denymatch (if a nomatch was seen earlier in one
+ * of the previous sequences)
+ */
+
+ /*
+ * 'cont' from matrix - continue to next route-map
+ * sequence
+ */
+ continue;
+ else if (match_ret == RMAP_NOMATCH) {
+
+ /*
+ * The return value is now changed to denymatch.
+ * So from here on out, even if we see more noops,
+ * we retain this return value and return this
+ * eventually if there are no matches.
+ */
+ ret = RMAP_DENYMATCH;
+
+ /*
+ * 'cont' from matrix - continue to next route-map
+ * sequence
+ */
+ continue;
+ } else if (match_ret == RMAP_MATCH) {
+ if (index->type == RMAP_PERMIT)
+ /* 'action' */
+ {
+ /* Match succeeded, rmap is of type permit */
+ ret = RMAP_PERMITMATCH;
+
+ /* permit+match must execute sets */
+ for (set = index->set_list.head; set;
+ set = set->next)
+ /*
+ * set cmds return RMAP_OKAY or
+ * RMAP_ERROR. We do not care if
+ * set succeeded or not. So, ignore
+ * return code.
+ */
+ (void)(*set->cmd->func_apply)(
+ set->value, prefix, set_object);
+
+ /* Call another route-map if available */
+ if (index->nextrm) {
+ struct route_map *nextrm =
+ route_map_lookup_by_name(
+ index->nextrm);
+
+ if (nextrm) /* Target route-map found,
+ jump to it */
+ {
+ recursion++;
+ ret = route_map_apply_ext(
+ nextrm, prefix,
+ match_object,
+ set_object, NULL);
+ recursion--;
+ }
+
+ /* If nextrm returned 'deny', finish. */
+ if (ret == RMAP_DENYMATCH)
+ goto route_map_apply_end;
+ }
+
+ switch (index->exitpolicy) {
+ case RMAP_EXIT:
+ goto route_map_apply_end;
+ case RMAP_NEXT:
+ continue;
+ case RMAP_GOTO: {
+ /* Find the next clause to jump to */
+ struct route_map_index *next =
+ index->next;
+ int nextpref = index->nextpref;
+
+ while (next && next->pref < nextpref) {
+ index = next;
+ next = next->next;
+ }
+ if (next == NULL) {
+ /* No clauses match! */
+ goto route_map_apply_end;
+ }
+ }
+ }
+ } else if (index->type == RMAP_DENY)
+ /* 'deny' */
+ {
+ ret = RMAP_DENYMATCH;
+ goto route_map_apply_end;
+ }
+ }
+ }
+
+route_map_apply_end:
+ if (rmap_debug)
+ zlog_debug("Route-map: %s, prefix: %pFX, result: %s",
+ (map ? map->name : "null"), prefix,
+ route_map_result_str(ret));
+
+ if (pref) {
+ if (index != NULL && ret == RMAP_PERMITMATCH)
+ *pref = index->pref;
+ else
+ *pref = 65536;
+ }
+
+ return (ret);
+}
+
+void route_map_add_hook(void (*func)(const char *))
+{
+ route_map_master.add_hook = func;
+}
+
+void route_map_delete_hook(void (*func)(const char *))
+{
+ route_map_master.delete_hook = func;
+}
+
+void route_map_event_hook(void (*func)(const char *name))
+{
+ route_map_master.event_hook = func;
+}
+
+/* Routines for route map dependency lists and dependency processing */
+static bool route_map_rmap_hash_cmp(const void *p1, const void *p2)
+{
+ return strcmp(((const struct route_map_dep_data *)p1)->rname,
+ ((const struct route_map_dep_data *)p2)->rname)
+ == 0;
+}
+
+static bool route_map_dep_hash_cmp(const void *p1, const void *p2)
+{
+
+ return (strcmp(((const struct route_map_dep *)p1)->dep_name,
+ (const char *)p2)
+ == 0);
+}
+
+static void route_map_clear_reference(struct hash_bucket *bucket, void *arg)
+{
+ struct route_map_dep *dep = bucket->data;
+ struct route_map_dep_data *dep_data = NULL, tmp_dep_data;
+
+ memset(&tmp_dep_data, 0, sizeof(tmp_dep_data));
+ tmp_dep_data.rname = arg;
+ dep_data = hash_release(dep->dep_rmap_hash, &tmp_dep_data);
+ if (dep_data) {
+ if (rmap_debug)
+ zlog_debug("Clearing reference for %s to %s count: %d",
+ dep->dep_name, tmp_dep_data.rname,
+ dep_data->refcnt);
+
+ XFREE(MTYPE_ROUTE_MAP_NAME, dep_data->rname);
+ XFREE(MTYPE_ROUTE_MAP_DEP_DATA, dep_data);
+ }
+ if (!dep->dep_rmap_hash->count) {
+ dep = hash_release(dep->this_hash, (void *)dep->dep_name);
+ hash_free(dep->dep_rmap_hash);
+ XFREE(MTYPE_ROUTE_MAP_NAME, dep->dep_name);
+ XFREE(MTYPE_ROUTE_MAP_DEP, dep);
+ }
+}
+
+static void route_map_clear_all_references(char *rmap_name)
+{
+ int i;
+
+ if (rmap_debug)
+ zlog_debug("Clearing references for %s", rmap_name);
+
+ for (i = 1; i < ROUTE_MAP_DEP_MAX; i++) {
+ hash_iterate(route_map_dep_hash[i], route_map_clear_reference,
+ (void *)rmap_name);
+ }
+}
+
+static unsigned int route_map_dep_data_hash_make_key(const void *p)
+{
+ const struct route_map_dep_data *dep_data = p;
+
+ return string_hash_make(dep_data->rname);
+}
+
+static void *route_map_dep_hash_alloc(void *p)
+{
+ char *dep_name = (char *)p;
+ struct route_map_dep *dep_entry;
+
+ dep_entry = XCALLOC(MTYPE_ROUTE_MAP_DEP, sizeof(struct route_map_dep));
+ dep_entry->dep_name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, dep_name);
+ dep_entry->dep_rmap_hash =
+ hash_create_size(8, route_map_dep_data_hash_make_key,
+ route_map_rmap_hash_cmp, "Route Map Dep Hash");
+ dep_entry->this_hash = NULL;
+
+ return dep_entry;
+}
+
+static void *route_map_name_hash_alloc(void *p)
+{
+ struct route_map_dep_data *dep_data = NULL, *tmp_dep_data = NULL;
+
+ dep_data = XCALLOC(MTYPE_ROUTE_MAP_DEP_DATA,
+ sizeof(struct route_map_dep_data));
+ tmp_dep_data = p;
+ dep_data->rname = XSTRDUP(MTYPE_ROUTE_MAP_NAME, tmp_dep_data->rname);
+ return dep_data;
+}
+
+static unsigned int route_map_dep_hash_make_key(const void *p)
+{
+ return (string_hash_make((char *)p));
+}
+
+static void route_map_print_dependency(struct hash_bucket *bucket, void *data)
+{
+ struct route_map_dep_data *dep_data = bucket->data;
+ char *rmap_name = dep_data->rname;
+ char *dep_name = data;
+
+ zlog_debug("%s: Dependency for %s: %s", __func__, dep_name, rmap_name);
+}
+
+static int route_map_dep_update(struct hash *dephash, const char *dep_name,
+ const char *rmap_name, route_map_event_t type)
+{
+ struct route_map_dep *dep = NULL;
+ char *dname, *rname;
+ int ret = 0;
+ struct route_map_dep_data *dep_data = NULL, *ret_dep_data = NULL;
+ struct route_map_dep_data tmp_dep_data;
+
+ dname = XSTRDUP(MTYPE_ROUTE_MAP_NAME, dep_name);
+ rname = XSTRDUP(MTYPE_ROUTE_MAP_NAME, rmap_name);
+
+ switch (type) {
+ case RMAP_EVENT_PLIST_ADDED:
+ case RMAP_EVENT_CLIST_ADDED:
+ case RMAP_EVENT_ECLIST_ADDED:
+ case RMAP_EVENT_ASLIST_ADDED:
+ case RMAP_EVENT_LLIST_ADDED:
+ case RMAP_EVENT_CALL_ADDED:
+ case RMAP_EVENT_FILTER_ADDED:
+ if (rmap_debug)
+ zlog_debug("Adding dependency for filter %s in route-map %s",
+ dep_name, rmap_name);
+ dep = (struct route_map_dep *)hash_get(
+ dephash, dname, route_map_dep_hash_alloc);
+ if (!dep) {
+ ret = -1;
+ goto out;
+ }
+
+ if (!dep->this_hash)
+ dep->this_hash = dephash;
+
+ memset(&tmp_dep_data, 0, sizeof(tmp_dep_data));
+ tmp_dep_data.rname = rname;
+ dep_data = hash_lookup(dep->dep_rmap_hash, &tmp_dep_data);
+ if (!dep_data)
+ dep_data = hash_get(dep->dep_rmap_hash, &tmp_dep_data,
+ route_map_name_hash_alloc);
+
+ dep_data->refcnt++;
+ break;
+ case RMAP_EVENT_PLIST_DELETED:
+ case RMAP_EVENT_CLIST_DELETED:
+ case RMAP_EVENT_ECLIST_DELETED:
+ case RMAP_EVENT_ASLIST_DELETED:
+ case RMAP_EVENT_LLIST_DELETED:
+ case RMAP_EVENT_CALL_DELETED:
+ case RMAP_EVENT_FILTER_DELETED:
+ if (rmap_debug)
+ zlog_debug("Deleting dependency for filter %s in route-map %s",
+ dep_name, rmap_name);
+ dep = (struct route_map_dep *)hash_get(dephash, dname, NULL);
+ if (!dep) {
+ goto out;
+ }
+
+ memset(&tmp_dep_data, 0, sizeof(tmp_dep_data));
+ tmp_dep_data.rname = rname;
+ dep_data = hash_lookup(dep->dep_rmap_hash, &tmp_dep_data);
+ /*
+ * If dep_data is NULL then something has gone seriously
+ * wrong in route-map handling. Note it and prevent
+ * the crash.
+ */
+ if (!dep_data) {
+ zlog_warn(
+ "route-map dependency for route-map %s: %s is not correct",
+ rmap_name, dep_name);
+ goto out;
+ }
+
+ dep_data->refcnt--;
+
+ if (!dep_data->refcnt) {
+ ret_dep_data = hash_release(dep->dep_rmap_hash,
+ &tmp_dep_data);
+ if (ret_dep_data) {
+ XFREE(MTYPE_ROUTE_MAP_NAME,
+ ret_dep_data->rname);
+ XFREE(MTYPE_ROUTE_MAP_DEP_DATA, ret_dep_data);
+ }
+ }
+
+ if (!dep->dep_rmap_hash->count) {
+ dep = hash_release(dephash, dname);
+ hash_free(dep->dep_rmap_hash);
+ XFREE(MTYPE_ROUTE_MAP_NAME, dep->dep_name);
+ XFREE(MTYPE_ROUTE_MAP_DEP, dep);
+ }
+ break;
+ case RMAP_EVENT_SET_ADDED:
+ case RMAP_EVENT_SET_DELETED:
+ case RMAP_EVENT_SET_REPLACED:
+ case RMAP_EVENT_MATCH_ADDED:
+ case RMAP_EVENT_MATCH_DELETED:
+ case RMAP_EVENT_MATCH_REPLACED:
+ case RMAP_EVENT_INDEX_ADDED:
+ case RMAP_EVENT_INDEX_DELETED:
+ break;
+ }
+
+ if (dep) {
+ if (rmap_debug)
+ hash_iterate(dep->dep_rmap_hash,
+ route_map_print_dependency, dname);
+ }
+
+out:
+ XFREE(MTYPE_ROUTE_MAP_NAME, rname);
+ XFREE(MTYPE_ROUTE_MAP_NAME, dname);
+ return ret;
+}
+
+static struct hash *route_map_get_dep_hash(route_map_event_t event)
+{
+ struct hash *upd8_hash = NULL;
+
+ switch (event) {
+ case RMAP_EVENT_PLIST_ADDED:
+ case RMAP_EVENT_PLIST_DELETED:
+ upd8_hash = route_map_dep_hash[ROUTE_MAP_DEP_PLIST];
+ break;
+ case RMAP_EVENT_CLIST_ADDED:
+ case RMAP_EVENT_CLIST_DELETED:
+ upd8_hash = route_map_dep_hash[ROUTE_MAP_DEP_CLIST];
+ break;
+ case RMAP_EVENT_ECLIST_ADDED:
+ case RMAP_EVENT_ECLIST_DELETED:
+ upd8_hash = route_map_dep_hash[ROUTE_MAP_DEP_ECLIST];
+ break;
+ case RMAP_EVENT_ASLIST_ADDED:
+ case RMAP_EVENT_ASLIST_DELETED:
+ upd8_hash = route_map_dep_hash[ROUTE_MAP_DEP_ASPATH];
+ break;
+ case RMAP_EVENT_LLIST_ADDED:
+ case RMAP_EVENT_LLIST_DELETED:
+ upd8_hash = route_map_dep_hash[ROUTE_MAP_DEP_LCLIST];
+ break;
+ case RMAP_EVENT_CALL_ADDED:
+ case RMAP_EVENT_CALL_DELETED:
+ case RMAP_EVENT_MATCH_ADDED:
+ case RMAP_EVENT_MATCH_DELETED:
+ upd8_hash = route_map_dep_hash[ROUTE_MAP_DEP_RMAP];
+ break;
+ case RMAP_EVENT_FILTER_ADDED:
+ case RMAP_EVENT_FILTER_DELETED:
+ upd8_hash = route_map_dep_hash[ROUTE_MAP_DEP_FILTER];
+ break;
+ /*
+ * Should we actually be ignoring these?
+ * I am not sure but at this point in time, let
+ * us get them into this switch and we can peel
+ * them into the appropriate place in the future
+ */
+ case RMAP_EVENT_SET_ADDED:
+ case RMAP_EVENT_SET_DELETED:
+ case RMAP_EVENT_SET_REPLACED:
+ case RMAP_EVENT_MATCH_REPLACED:
+ case RMAP_EVENT_INDEX_ADDED:
+ case RMAP_EVENT_INDEX_DELETED:
+ upd8_hash = NULL;
+ break;
+ }
+ return (upd8_hash);
+}
+
+static void route_map_process_dependency(struct hash_bucket *bucket, void *data)
+{
+ struct route_map_dep_data *dep_data = NULL;
+ char *rmap_name = NULL;
+
+ dep_data = bucket->data;
+ rmap_name = dep_data->rname;
+
+ if (rmap_debug)
+ zlog_debug("Notifying %s of dependency", rmap_name);
+ if (route_map_master.event_hook)
+ (*route_map_master.event_hook)(rmap_name);
+}
+
+void route_map_upd8_dependency(route_map_event_t type, const char *arg,
+ const char *rmap_name)
+{
+ struct hash *upd8_hash = NULL;
+
+ if ((upd8_hash = route_map_get_dep_hash(type))) {
+ route_map_dep_update(upd8_hash, arg, rmap_name, type);
+
+ if (type == RMAP_EVENT_CALL_ADDED) {
+ /* Execute hook. */
+ if (route_map_master.add_hook)
+ (*route_map_master.add_hook)(rmap_name);
+ } else if (type == RMAP_EVENT_CALL_DELETED) {
+ /* Execute hook. */
+ if (route_map_master.delete_hook)
+ (*route_map_master.delete_hook)(rmap_name);
+ }
+ }
+}
+
+void route_map_notify_dependencies(const char *affected_name,
+ route_map_event_t event)
+{
+ struct route_map_dep *dep;
+ struct hash *upd8_hash;
+ char *name;
+
+ if (!affected_name)
+ return;
+
+ name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, affected_name);
+
+ if ((upd8_hash = route_map_get_dep_hash(event)) == NULL) {
+ XFREE(MTYPE_ROUTE_MAP_NAME, name);
+ return;
+ }
+
+ dep = (struct route_map_dep *)hash_get(upd8_hash, name, NULL);
+ if (dep) {
+ if (!dep->this_hash)
+ dep->this_hash = upd8_hash;
+
+ if (rmap_debug)
+ zlog_debug("Filter %s updated", dep->dep_name);
+ hash_iterate(dep->dep_rmap_hash, route_map_process_dependency,
+ (void *)event);
+ }
+
+ XFREE(MTYPE_ROUTE_MAP_NAME, name);
+}
+
+/* VTY related functions. */
+static void clear_route_map_helper(struct route_map *map)
+{
+ struct route_map_index *index;
+
+ map->applied_clear = map->applied;
+ for (index = map->head; index; index = index->next)
+ index->applied_clear = index->applied;
+}
+
+DEFUN (rmap_clear_counters,
+ rmap_clear_counters_cmd,
+ "clear route-map counters [WORD]",
+ CLEAR_STR
+ "route-map information\n"
+ "counters associated with the specified route-map\n"
+ "route-map name\n")
+{
+ int idx_word = 2;
+ struct route_map *map;
+
+ const char *name = (argc == 3 ) ? argv[idx_word]->arg : NULL;
+
+ if (name) {
+ map = route_map_lookup_by_name(name);
+
+ if (map)
+ clear_route_map_helper(map);
+ else {
+ vty_out(vty, "%s: 'route-map %s' not found\n",
+ frr_protonameinst, name);
+ return CMD_SUCCESS;
+ }
+ } else {
+ for (map = route_map_master.head; map; map = map->next)
+ clear_route_map_helper(map);
+ }
+
+ return CMD_SUCCESS;
+
+}
+
+DEFUN (rmap_show_name,
+ rmap_show_name_cmd,
+ "show route-map [WORD] [json]",
+ SHOW_STR
+ "route-map information\n"
+ "route-map name\n"
+ JSON_STR)
+{
+ bool uj = use_json(argc, argv);
+ int idx = 0;
+ const char *name = NULL;
+
+ if (argv_find(argv, argc, "WORD", &idx))
+ name = argv[idx]->arg;
+
+ return vty_show_route_map(vty, name, uj);
+}
+
+DEFUN (rmap_show_unused,
+ rmap_show_unused_cmd,
+ "show route-map-unused",
+ SHOW_STR
+ "unused route-map information\n")
+{
+ return vty_show_unused_route_map(vty);
+}
+
+DEFUN (debug_rmap,
+ debug_rmap_cmd,
+ "debug route-map",
+ DEBUG_STR
+ "Debug option set for route-maps\n")
+{
+ rmap_debug = true;
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_rmap,
+ no_debug_rmap_cmd,
+ "no debug route-map",
+ NO_STR
+ DEBUG_STR
+ "Debug option set for route-maps\n")
+{
+ rmap_debug = false;
+ return CMD_SUCCESS;
+}
+
+/* Debug node. */
+static int rmap_config_write_debug(struct vty *vty);
+static struct cmd_node rmap_debug_node = {
+ .name = "route-map debug",
+ .node = RMAP_DEBUG_NODE,
+ .prompt = "",
+ .config_write = rmap_config_write_debug,
+};
+
+/* Configuration write function. */
+static int rmap_config_write_debug(struct vty *vty)
+{
+ int write = 0;
+
+ if (rmap_debug) {
+ vty_out(vty, "debug route-map\n");
+ write++;
+ }
+
+ return write;
+}
+
+/* Common route map rules */
+
+void *route_map_rule_tag_compile(const char *arg)
+{
+ unsigned long int tmp;
+ char *endptr;
+ route_tag_t *tag;
+
+ errno = 0;
+ tmp = strtoul(arg, &endptr, 0);
+ if (arg[0] == '\0' || *endptr != '\0' || errno || tmp > ROUTE_TAG_MAX)
+ return NULL;
+
+ tag = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(*tag));
+ *tag = tmp;
+
+ return tag;
+}
+
+void route_map_rule_tag_free(void *rule)
+{
+ XFREE(MTYPE_ROUTE_MAP_COMPILED, rule);
+}
+
+void route_map_finish(void)
+{
+ int i;
+ struct route_map_rule_cmd_proxy *proxy;
+
+ /* these 2 hash tables have INIT_HASH initializers, so the "default"
+ * state is "initialized & empty" => fini() followed by init() to
+ * return to that same state
+ */
+ while ((proxy = rmap_cmd_name_pop(rmap_match_cmds)))
+ (void)proxy;
+ rmap_cmd_name_fini(rmap_match_cmds);
+ rmap_cmd_name_init(rmap_match_cmds);
+
+ while ((proxy = rmap_cmd_name_pop(rmap_set_cmds)))
+ (void)proxy;
+ rmap_cmd_name_fini(rmap_set_cmds);
+ rmap_cmd_name_init(rmap_set_cmds);
+
+ /*
+ * All protocols are setting these to NULL
+ * by default on shutdown( route_map_finish )
+ * Why are we making them do this work?
+ */
+ route_map_master.add_hook = NULL;
+ route_map_master.delete_hook = NULL;
+ route_map_master.event_hook = NULL;
+
+ /* cleanup route_map */
+ while (route_map_master.head) {
+ struct route_map *map = route_map_master.head;
+ map->to_be_processed = false;
+ route_map_delete(map);
+ }
+
+ for (i = 1; i < ROUTE_MAP_DEP_MAX; i++) {
+ hash_free(route_map_dep_hash[i]);
+ route_map_dep_hash[i] = NULL;
+ }
+
+ hash_free(route_map_master_hash);
+ route_map_master_hash = NULL;
+}
+
+/* Increment the use_count counter while attaching the route map */
+void route_map_counter_increment(struct route_map *map)
+{
+ if (map)
+ map->use_count++;
+}
+
+/* Decrement the use_count counter while detaching the route map. */
+void route_map_counter_decrement(struct route_map *map)
+{
+ if (map) {
+ if (map->use_count <= 0)
+ return;
+ map->use_count--;
+ }
+}
+
+DEFUN_HIDDEN(show_route_map_pfx_tbl, show_route_map_pfx_tbl_cmd,
+ "show route-map RMAP_NAME prefix-table",
+ SHOW_STR
+ "route-map\n"
+ "route-map name\n"
+ "internal prefix-table\n")
+{
+ const char *rmap_name = argv[2]->arg;
+ struct route_map *rmap = NULL;
+ struct route_table *rm_pfx_tbl4 = NULL;
+ struct route_table *rm_pfx_tbl6 = NULL;
+ struct route_node *rn = NULL, *prn = NULL;
+ struct list *rmap_index_list = NULL;
+ struct listnode *ln = NULL, *nln = NULL;
+ struct route_map_index *index = NULL;
+ uint8_t len = 54;
+
+ vty_out(vty, "%s:\n", frr_protonameinst);
+ rmap = route_map_lookup_by_name(rmap_name);
+ if (rmap) {
+ rm_pfx_tbl4 = rmap->ipv4_prefix_table;
+ if (rm_pfx_tbl4) {
+ vty_out(vty, "\n%s%43s%s\n", "IPv4 Prefix", "",
+ "Route-map Index List");
+ vty_out(vty, "%s%39s%s\n", "_______________", "",
+ "____________________");
+ for (rn = route_top(rm_pfx_tbl4); rn;
+ rn = route_next(rn)) {
+ vty_out(vty, " %pRN (%d)\n", rn,
+ route_node_get_lock_count(rn));
+
+ vty_out(vty, "(P) ");
+ prn = rn->parent;
+ if (prn) {
+ vty_out(vty, "%pRN\n", prn);
+ }
+
+ vty_out(vty, "\n");
+ rmap_index_list = (struct list *)rn->info;
+ if (!rmap_index_list
+ || !listcount(rmap_index_list))
+ vty_out(vty, "%*s%s\n", len, "", "-");
+ else
+ for (ALL_LIST_ELEMENTS(rmap_index_list,
+ ln, nln,
+ index)) {
+ vty_out(vty, "%*s%s seq %d\n",
+ len, "",
+ index->map->name,
+ index->pref);
+ }
+ vty_out(vty, "\n");
+ }
+ }
+
+ rm_pfx_tbl6 = rmap->ipv6_prefix_table;
+ if (rm_pfx_tbl6) {
+ vty_out(vty, "\n%s%43s%s\n", "IPv6 Prefix", "",
+ "Route-map Index List");
+ vty_out(vty, "%s%39s%s\n", "_______________", "",
+ "____________________");
+ for (rn = route_top(rm_pfx_tbl6); rn;
+ rn = route_next(rn)) {
+ vty_out(vty, " %pRN (%d)\n", rn,
+ route_node_get_lock_count(rn));
+
+ vty_out(vty, "(P) ");
+ prn = rn->parent;
+ if (prn) {
+ vty_out(vty, "%pRN\n", prn);
+ }
+
+ vty_out(vty, "\n");
+ rmap_index_list = (struct list *)rn->info;
+ if (!rmap_index_list
+ || !listcount(rmap_index_list))
+ vty_out(vty, "%*s%s\n", len, "", "-");
+ else
+ for (ALL_LIST_ELEMENTS(rmap_index_list,
+ ln, nln,
+ index)) {
+ vty_out(vty, "%*s%s seq %d\n",
+ len, "",
+ index->map->name,
+ index->pref);
+ }
+ vty_out(vty, "\n");
+ }
+ }
+ }
+
+ vty_out(vty, "\n");
+ return CMD_SUCCESS;
+}
+
+/* Initialization of route map vector. */
+void route_map_init(void)
+{
+ int i;
+
+ route_map_master_hash =
+ hash_create_size(8, route_map_hash_key_make, route_map_hash_cmp,
+ "Route Map Master Hash");
+
+ for (i = 1; i < ROUTE_MAP_DEP_MAX; i++)
+ route_map_dep_hash[i] = hash_create_size(
+ 8, route_map_dep_hash_make_key, route_map_dep_hash_cmp,
+ "Route Map Dep Hash");
+
+ rmap_debug = false;
+
+ route_map_cli_init();
+
+ /* Install route map top node. */
+ install_node(&rmap_debug_node);
+
+ /* Install route map commands. */
+ install_element(CONFIG_NODE, &debug_rmap_cmd);
+ install_element(CONFIG_NODE, &no_debug_rmap_cmd);
+
+ /* Install show command */
+ install_element(ENABLE_NODE, &rmap_clear_counters_cmd);
+
+ install_element(ENABLE_NODE, &rmap_show_name_cmd);
+ install_element(ENABLE_NODE, &rmap_show_unused_cmd);
+
+ install_element(ENABLE_NODE, &debug_rmap_cmd);
+ install_element(ENABLE_NODE, &no_debug_rmap_cmd);
+
+ install_element(ENABLE_NODE, &show_route_map_pfx_tbl_cmd);
+}
diff --git a/lib/routemap.h b/lib/routemap.h
new file mode 100644
index 0000000..a365925
--- /dev/null
+++ b/lib/routemap.h
@@ -0,0 +1,1022 @@
+/* Route map function.
+ * Copyright (C) 1998 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 _ZEBRA_ROUTEMAP_H
+#define _ZEBRA_ROUTEMAP_H
+
+#include "typesafe.h"
+#include "prefix.h"
+#include "memory.h"
+#include "qobj.h"
+#include "vty.h"
+#include "lib/plist.h"
+#include "lib/plist_int.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+DECLARE_MTYPE(ROUTE_MAP_NAME);
+DECLARE_MTYPE(ROUTE_MAP_RULE);
+DECLARE_MTYPE(ROUTE_MAP_COMPILED);
+
+/* Route map's type. */
+enum route_map_type { RMAP_PERMIT, RMAP_DENY, RMAP_ANY };
+
+typedef enum {
+ RMAP_DENYMATCH,
+ RMAP_PERMITMATCH
+} route_map_result_t;
+
+/*
+ * Route-map match or set result "Eg: match evpn vni xx"
+ * route-map match cmd always returns match/nomatch/noop
+ * match--> found a match
+ * nomatch--> didnt find a match
+ * noop--> not applicable
+ * route-map set retuns okay/error
+ * okay --> set was successful
+ * error --> set was not successful
+ */
+enum route_map_cmd_result_t {
+ /*
+ * route-map match cmd results
+ */
+ RMAP_MATCH,
+ RMAP_NOMATCH,
+ RMAP_NOOP,
+ /*
+ * route-map set cmd results
+ */
+ RMAP_OKAY,
+ RMAP_ERROR
+};
+
+typedef enum { RMAP_EXIT, RMAP_GOTO, RMAP_NEXT } route_map_end_t;
+
+typedef enum {
+ RMAP_EVENT_SET_ADDED,
+ RMAP_EVENT_SET_DELETED,
+ RMAP_EVENT_SET_REPLACED,
+ RMAP_EVENT_MATCH_ADDED,
+ RMAP_EVENT_MATCH_DELETED,
+ RMAP_EVENT_MATCH_REPLACED,
+ RMAP_EVENT_INDEX_ADDED,
+ RMAP_EVENT_INDEX_DELETED,
+ RMAP_EVENT_CALL_ADDED, /* call to another routemap added */
+ RMAP_EVENT_CALL_DELETED,
+ RMAP_EVENT_PLIST_ADDED,
+ RMAP_EVENT_PLIST_DELETED,
+ RMAP_EVENT_CLIST_ADDED,
+ RMAP_EVENT_CLIST_DELETED,
+ RMAP_EVENT_ECLIST_ADDED,
+ RMAP_EVENT_ECLIST_DELETED,
+ RMAP_EVENT_LLIST_ADDED,
+ RMAP_EVENT_LLIST_DELETED,
+ RMAP_EVENT_ASLIST_ADDED,
+ RMAP_EVENT_ASLIST_DELETED,
+ RMAP_EVENT_FILTER_ADDED,
+ RMAP_EVENT_FILTER_DELETED,
+} route_map_event_t;
+
+/* Depth limit in RMAP recursion using RMAP_CALL. */
+#define RMAP_RECURSION_LIMIT 10
+
+/* Route map rule structure for matching and setting. */
+struct route_map_rule_cmd {
+ /* Route map rule name (e.g. as-path, metric) */
+ const char *str;
+
+ /* Function for value set or match. */
+ enum route_map_cmd_result_t (*func_apply)(void *rule,
+ const struct prefix *prefix,
+ void *object);
+
+ /* Compile argument and return result as void *. */
+ void *(*func_compile)(const char *);
+
+ /* Free allocated value by func_compile (). */
+ void (*func_free)(void *);
+
+ /** To get the rule key after Compilation **/
+ void *(*func_get_rmap_rule_key)(void *val);
+};
+
+/* Route map apply error. */
+enum rmap_compile_rets {
+ RMAP_COMPILE_SUCCESS,
+
+ /* Route map rule is missing. */
+ RMAP_RULE_MISSING,
+
+ /* Route map rule can't compile */
+ RMAP_COMPILE_ERROR,
+
+};
+
+/* Route map rule. This rule has both `match' rule and `set' rule. */
+struct route_map_rule {
+ /* Rule type. */
+ const struct route_map_rule_cmd *cmd;
+
+ /* For pretty printing. */
+ char *rule_str;
+
+ /* Pre-compiled match rule. */
+ void *value;
+
+ /* Linked list. */
+ struct route_map_rule *next;
+ struct route_map_rule *prev;
+};
+
+/* Route map rule list. */
+struct route_map_rule_list {
+ struct route_map_rule *head;
+ struct route_map_rule *tail;
+};
+
+/* Forward struct declaration: the complete can be found later this file. */
+struct routemap_hook_context;
+
+/* Route map index structure. */
+struct route_map_index {
+ struct route_map *map;
+ char *description;
+
+ /* Preference of this route map rule. */
+ int pref;
+
+ /* Route map type permit or deny. */
+ enum route_map_type type;
+
+ /* Do we follow old rules, or hop forward? */
+ route_map_end_t exitpolicy;
+
+ /* If we're using "GOTO", to where do we go? */
+ int nextpref;
+
+ /* If we're using "CALL", to which route-map do ew go? */
+ char *nextrm;
+
+ /* Matching rule list. */
+ struct route_map_rule_list match_list;
+ struct route_map_rule_list set_list;
+
+ /* Make linked list. */
+ struct route_map_index *next;
+ struct route_map_index *prev;
+
+ /* Keep track how many times we've try to apply */
+ uint64_t applied;
+ uint64_t applied_clear;
+
+ /* List of match/sets contexts. */
+ TAILQ_HEAD(, routemap_hook_context) rhclist;
+
+ QOBJ_FIELDS;
+};
+DECLARE_QOBJ_TYPE(route_map_index);
+
+/* route map maximum length. Not strictly the maximum xpath length but cannot be
+ * greater
+ */
+#define RMAP_NAME_MAXLEN XPATH_MAXLEN
+
+/* Route map list structure. */
+struct route_map {
+ /* Name of route map. */
+ char *name;
+
+ /* Route map's rule. */
+ struct route_map_index *head;
+ struct route_map_index *tail;
+
+ /* Make linked list. */
+ struct route_map *next;
+ struct route_map *prev;
+
+ /* Maintain update info */
+ bool to_be_processed; /* True if modification isn't acted on yet */
+ bool deleted; /* If 1, then this node will be deleted */
+ bool optimization_disabled;
+
+ /* How many times have we applied this route-map */
+ uint64_t applied;
+ uint64_t applied_clear;
+
+ /* Counter to track active usage of this route-map */
+ uint16_t use_count;
+
+ /* Tables to maintain IPv4 and IPv6 prefixes from
+ * the prefix-list match clause.
+ */
+ struct route_table *ipv4_prefix_table;
+ struct route_table *ipv6_prefix_table;
+
+ QOBJ_FIELDS;
+};
+DECLARE_QOBJ_TYPE(route_map);
+
+/* Route-map match conditions */
+#define IS_MATCH_INTERFACE(C) \
+ (strmatch(C, "frr-route-map:interface"))
+#define IS_MATCH_IPv4_ADDRESS_LIST(C) \
+ (strmatch(C, "frr-route-map:ipv4-address-list"))
+#define IS_MATCH_IPv6_ADDRESS_LIST(C) \
+ (strmatch(C, "frr-route-map:ipv6-address-list"))
+#define IS_MATCH_IPv4_NEXTHOP_LIST(C) \
+ (strmatch(C, "frr-route-map:ipv4-next-hop-list"))
+#define IS_MATCH_IPv6_NEXTHOP_LIST(C) \
+ (strmatch(C, "frr-route-map:ipv6-next-hop-list"))
+#define IS_MATCH_IPv4_PREFIX_LIST(C) \
+ (strmatch(C, "frr-route-map:ipv4-prefix-list"))
+#define IS_MATCH_IPv6_PREFIX_LIST(C) \
+ (strmatch(C, "frr-route-map:ipv6-prefix-list"))
+#define IS_MATCH_IPv4_NEXTHOP_PREFIX_LIST(C) \
+ (strmatch(C, "frr-route-map:ipv4-next-hop-prefix-list"))
+#define IS_MATCH_IPv6_NEXTHOP_PREFIX_LIST(C) \
+ (strmatch(C, "frr-route-map:ipv6-next-hop-prefix-list"))
+#define IS_MATCH_IPv4_NEXTHOP_TYPE(C) \
+ (strmatch(C, "frr-route-map:ipv4-next-hop-type"))
+#define IS_MATCH_IPv6_NEXTHOP_TYPE(C) \
+ (strmatch(C, "frr-route-map:ipv6-next-hop-type"))
+#define IS_MATCH_METRIC(C) \
+ (strmatch(C, "frr-route-map:match-metric"))
+#define IS_MATCH_TAG(C) (strmatch(C, "frr-route-map:match-tag"))
+/* Zebra route-map match conditions */
+#define IS_MATCH_IPv4_PREFIX_LEN(C) \
+ (strmatch(C, "frr-zebra-route-map:ipv4-prefix-length"))
+#define IS_MATCH_IPv6_PREFIX_LEN(C) \
+ (strmatch(C, "frr-zebra-route-map:ipv6-prefix-length"))
+#define IS_MATCH_IPv4_NH_PREFIX_LEN(C) \
+ (strmatch(C, "frr-zebra-route-map:ipv4-next-hop-prefix-length"))
+#define IS_MATCH_SRC_PROTO(C) \
+ (strmatch(C, "frr-zebra-route-map:source-protocol"))
+#define IS_MATCH_SRC_INSTANCE(C) \
+ (strmatch(C, "frr-zebra-route-map:source-instance"))
+/* BGP route-map match conditions */
+#define IS_MATCH_LOCAL_PREF(C) \
+ (strmatch(C, "frr-bgp-route-map:match-local-preference"))
+#define IS_MATCH_ALIAS(C) (strmatch(C, "frr-bgp-route-map:match-alias"))
+#define IS_MATCH_SCRIPT(C) (strmatch(C, "frr-bgp-route-map:match-script"))
+#define IS_MATCH_ORIGIN(C) \
+ (strmatch(C, "frr-bgp-route-map:match-origin"))
+#define IS_MATCH_RPKI(C) (strmatch(C, "frr-bgp-route-map:rpki"))
+#define IS_MATCH_RPKI_EXTCOMMUNITY(C) \
+ (strmatch(C, "frr-bgp-route-map:rpki-extcommunity"))
+#define IS_MATCH_PROBABILITY(C) \
+ (strmatch(C, "frr-bgp-route-map:probability"))
+#define IS_MATCH_SRC_VRF(C) \
+ (strmatch(C, "frr-bgp-route-map:source-vrf"))
+#define IS_MATCH_PEER(C) (strmatch(C, "frr-bgp-route-map:peer"))
+#define IS_MATCH_AS_LIST(C) \
+ (strmatch(C, "frr-bgp-route-map:as-path-list"))
+#define IS_MATCH_MAC_LIST(C) \
+ (strmatch(C, "frr-bgp-route-map:mac-address-list"))
+#define IS_MATCH_EVPN_ROUTE_TYPE(C) \
+ (strmatch(C, "frr-bgp-route-map:evpn-route-type"))
+#define IS_MATCH_EVPN_DEFAULT_ROUTE(C) \
+ (strmatch(C, "frr-bgp-route-map:evpn-default-route"))
+#define IS_MATCH_EVPN_VNI(C) \
+ (strmatch(C, "frr-bgp-route-map:evpn-vni"))
+#define IS_MATCH_EVPN_DEFAULT_ROUTE(C) \
+ (strmatch(C, "frr-bgp-route-map:evpn-default-route"))
+#define IS_MATCH_EVPN_RD(C) \
+ (strmatch(C, "frr-bgp-route-map:evpn-rd"))
+#define IS_MATCH_ROUTE_SRC(C) \
+ (strmatch(C, "frr-bgp-route-map:ip-route-source"))
+#define IS_MATCH_ROUTE_SRC_PL(C) \
+ (strmatch(C, "frr-bgp-route-map:ip-route-source-prefix-list"))
+#define IS_MATCH_COMMUNITY(C) \
+ (strmatch(C, "frr-bgp-route-map:match-community"))
+#define IS_MATCH_LCOMMUNITY(C) \
+ (strmatch(C, "frr-bgp-route-map:match-large-community"))
+#define IS_MATCH_EXTCOMMUNITY(C) \
+ (strmatch(C, "frr-bgp-route-map:match-extcommunity"))
+#define IS_MATCH_IPV4_NH(C) \
+ (strmatch(C, "frr-bgp-route-map:ipv4-nexthop"))
+#define IS_MATCH_IPV6_NH(C) \
+ (strmatch(C, "frr-bgp-route-map:ipv6-nexthop"))
+
+/* Route-map set actions */
+#define IS_SET_IPv4_NH(A) \
+ (strmatch(A, "frr-route-map:ipv4-next-hop"))
+#define IS_SET_IPv6_NH(A) \
+ (strmatch(A, "frr-route-map:ipv6-next-hop"))
+#define IS_SET_METRIC(A) \
+ (strmatch(A, "frr-route-map:set-metric"))
+#define IS_SET_TAG(A) (strmatch(A, "frr-route-map:set-tag"))
+#define IS_SET_SR_TE_COLOR(A) \
+ (strmatch(A, "frr-route-map:set-sr-te-color"))
+/* Zebra route-map set actions */
+#define IS_SET_SRC(A) \
+ (strmatch(A, "frr-zebra-route-map:src-address"))
+/* OSPF route-map set actions */
+#define IS_SET_METRIC_TYPE(A) \
+ (strmatch(A, "frr-ospf-route-map:metric-type"))
+#define IS_SET_FORWARDING_ADDR(A) \
+ (strmatch(A, "frr-ospf6-route-map:forwarding-address"))
+/* BGP route-map_set actions */
+#define IS_SET_WEIGHT(A) \
+ (strmatch(A, "frr-bgp-route-map:weight"))
+#define IS_SET_TABLE(A) (strmatch(A, "frr-bgp-route-map:table"))
+#define IS_SET_LOCAL_PREF(A) \
+ (strmatch(A, "frr-bgp-route-map:set-local-preference"))
+#define IS_SET_LABEL_INDEX(A) \
+ (strmatch(A, "frr-bgp-route-map:label-index"))
+#define IS_SET_DISTANCE(A) \
+ (strmatch(A, "frr-bgp-route-map:distance"))
+#define IS_SET_ORIGIN(A) \
+ (strmatch(A, "frr-bgp-route-map:set-origin"))
+#define IS_SET_ATOMIC_AGGREGATE(A) \
+ (strmatch(A, "frr-bgp-route-map:atomic-aggregate"))
+#define IS_SET_ORIGINATOR_ID(A) \
+ (strmatch(A, "frr-bgp-route-map:originator-id"))
+#define IS_SET_COMM_LIST_DEL(A) \
+ (strmatch(A, "frr-bgp-route-map:comm-list-delete"))
+#define IS_SET_LCOMM_LIST_DEL(A) \
+ (strmatch(A, "frr-bgp-route-map:large-comm-list-delete"))
+#define IS_SET_LCOMMUNITY(A) \
+ (strmatch(A, "frr-bgp-route-map:set-large-community"))
+#define IS_SET_COMMUNITY(A) \
+ (strmatch(A, "frr-bgp-route-map:set-community"))
+#define IS_SET_EXTCOMMUNITY_NONE(A) \
+ (strmatch(A, "frr-bgp-route-map:set-extcommunity-none"))
+#define IS_SET_EXTCOMMUNITY_RT(A) \
+ (strmatch(A, "frr-bgp-route-map:set-extcommunity-rt"))
+#define IS_SET_EXTCOMMUNITY_SOO(A) \
+ (strmatch(A, "frr-bgp-route-map:set-extcommunity-soo"))
+#define IS_SET_EXTCOMMUNITY_LB(A) \
+ (strmatch(A, "frr-bgp-route-map:set-extcommunity-lb"))
+#define IS_SET_AGGREGATOR(A) \
+ (strmatch(A, "frr-bgp-route-map:aggregator"))
+#define IS_SET_AS_PREPEND(A) \
+ (strmatch(A, "frr-bgp-route-map:as-path-prepend"))
+#define IS_SET_AS_EXCLUDE(A) \
+ (strmatch(A, "frr-bgp-route-map:as-path-exclude"))
+#define IS_SET_AS_REPLACE(A) (strmatch(A, "frr-bgp-route-map:as-path-replace"))
+#define IS_SET_IPV6_NH_GLOBAL(A) \
+ (strmatch(A, "frr-bgp-route-map:ipv6-nexthop-global"))
+#define IS_SET_IPV6_VPN_NH(A) \
+ (strmatch(A, "frr-bgp-route-map:ipv6-vpn-address"))
+#define IS_SET_IPV6_PEER_ADDR(A) \
+ (strmatch(A, "frr-bgp-route-map:ipv6-peer-address"))
+#define IS_SET_IPV6_PREFER_GLOBAL(A) \
+ (strmatch(A, "frr-bgp-route-map:ipv6-prefer-global"))
+#define IS_SET_IPV4_VPN_NH(A) \
+ (strmatch(A, "frr-bgp-route-map:ipv4-vpn-address"))
+#define IS_SET_BGP_IPV4_NH(A) \
+ (strmatch(A, "frr-bgp-route-map:set-ipv4-nexthop"))
+#define IS_SET_BGP_EVPN_GATEWAY_IP_IPV4(A) \
+ (strmatch(A, "frr-bgp-route-map:set-evpn-gateway-ip-ipv4"))
+#define IS_SET_BGP_EVPN_GATEWAY_IP_IPV6(A) \
+ (strmatch(A, "frr-bgp-route-map:set-evpn-gateway-ip-ipv6"))
+#define IS_SET_BGP_L3VPN_NEXTHOP_ENCAPSULATION(A) \
+ (strmatch(A, "frr-bgp-route-map:set-l3vpn-nexthop-encapsulation"))
+
+enum ecommunity_lb_type {
+ EXPLICIT_BANDWIDTH,
+ CUMULATIVE_BANDWIDTH,
+ COMPUTED_BANDWIDTH
+};
+
+/* Prototypes. */
+extern void route_map_init(void);
+
+/*
+ * This should only be called on shutdown
+ * Additionally this function sets the hooks to NULL
+ * before any processing is done.
+ */
+extern void route_map_finish(void);
+
+/* Add match statement to route map. */
+extern enum rmap_compile_rets route_map_add_match(struct route_map_index *index,
+ const char *match_name,
+ const char *match_arg,
+ route_map_event_t type);
+
+/* Delete specified route match rule. */
+extern enum rmap_compile_rets
+route_map_delete_match(struct route_map_index *index,
+ const char *match_name, const char *match_arg,
+ route_map_event_t type);
+
+extern const char *route_map_get_match_arg(struct route_map_index *index,
+ const char *match_name);
+
+/* Add route-map set statement to the route map. */
+extern enum rmap_compile_rets route_map_add_set(struct route_map_index *index,
+ const char *set_name,
+ const char *set_arg);
+
+/* Delete route map set rule. */
+extern enum rmap_compile_rets
+route_map_delete_set(struct route_map_index *index,
+ const char *set_name, const char *set_arg);
+
+/* struct route_map_rule_cmd is kept const in order to not have writable
+ * function pointers (which is a security benefit.) Hence, below struct is
+ * used as proxy for hashing these for by-name lookup.
+ */
+
+PREDECL_HASH(rmap_cmd_name);
+
+struct route_map_rule_cmd_proxy {
+ struct rmap_cmd_name_item itm;
+ const struct route_map_rule_cmd *cmd;
+};
+
+/* ... and just automatically create a proxy struct for each call location
+ * to route_map_install_{match,set} to avoid unnecessarily added boilerplate
+ * for each route-map user
+ */
+
+#define route_map_install_match(c) \
+ do { \
+ static struct route_map_rule_cmd_proxy proxy = {.cmd = c}; \
+ _route_map_install_match(&proxy); \
+ } while (0)
+
+#define route_map_install_set(c) \
+ do { \
+ static struct route_map_rule_cmd_proxy proxy = {.cmd = c}; \
+ _route_map_install_set(&proxy); \
+ } while (0)
+
+/* Install rule command to the match list. */
+extern void _route_map_install_match(struct route_map_rule_cmd_proxy *proxy);
+
+/*
+ * Install rule command to the set list.
+ *
+ * When installing a particular item, Allow a difference of handling
+ * of bad cli inputted(return NULL) -vs- this particular daemon cannot use
+ * this form of the command(return a pointer and handle it appropriately
+ * in the apply command). See 'set metric' command
+ * as it is handled in ripd/ripngd and ospfd.
+ */
+extern void _route_map_install_set(struct route_map_rule_cmd_proxy *proxy);
+
+/* Lookup route map by name. */
+extern struct route_map *route_map_lookup_by_name(const char *name);
+
+/* Simple helper to warn if route-map does not exist. */
+struct route_map *route_map_lookup_warn_noexist(struct vty *vty, const char *name);
+
+/* Apply route map to the object. */
+extern route_map_result_t route_map_apply_ext(struct route_map *map,
+ const struct prefix *prefix,
+ void *match_object,
+ void *set_object, int *pref);
+#define route_map_apply(map, prefix, object) \
+ route_map_apply_ext(map, prefix, object, object, NULL)
+
+extern void route_map_add_hook(void (*func)(const char *));
+extern void route_map_delete_hook(void (*func)(const char *));
+
+/*
+ * This is the callback for when something has changed about a
+ * route-map. The interested parties can register to receive
+ * this data.
+ *
+ * name - Is the name of the changed route-map
+ */
+extern void route_map_event_hook(void (*func)(const char *name));
+extern int route_map_mark_updated(const char *name);
+extern void route_map_walk_update_list(void (*update_fn)(char *name));
+extern void route_map_upd8_dependency(route_map_event_t type, const char *arg,
+ const char *rmap_name);
+extern void route_map_notify_dependencies(const char *affected_name,
+ route_map_event_t event);
+extern void
+route_map_notify_pentry_dependencies(const char *affected_name,
+ struct prefix_list_entry *pentry,
+ route_map_event_t event);
+extern int generic_match_add(struct route_map_index *index,
+ const char *command, const char *arg,
+ route_map_event_t type,
+ char *errmsg, size_t errmsg_len);
+extern int generic_match_delete(struct route_map_index *index,
+ const char *command, const char *arg,
+ route_map_event_t type,
+ char *errmsg, size_t errmsg_len);
+
+extern int generic_set_add(struct route_map_index *index,
+ const char *command, const char *arg,
+ char *errmsg, size_t errmsg_len);
+extern int generic_set_delete(struct route_map_index *index,
+ const char *command, const char *arg,
+ char *errmsg, size_t errmsg_len);
+
+
+/* match interface */
+extern void route_map_match_interface_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len));
+/* no match interface */
+extern void route_map_no_match_interface_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len));
+/* match ip address */
+extern void route_map_match_ip_address_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len));
+/* no match ip address */
+extern void route_map_no_match_ip_address_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len));
+/* match ip address prefix list */
+extern void route_map_match_ip_address_prefix_list_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len));
+/* no match ip address prefix list */
+extern void route_map_no_match_ip_address_prefix_list_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len));
+/* match ip next hop */
+extern void route_map_match_ip_next_hop_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len));
+/* no match ip next hop */
+extern void route_map_no_match_ip_next_hop_hook(int (*func)(
+ struct route_map_index *index, const char *command, const char *arg,
+ route_map_event_t type, char *errmsg, size_t errmsg_len));
+/* match ipv6 next hop */
+extern void route_map_match_ipv6_next_hop_hook(int (*func)(
+ struct route_map_index *index, const char *command, const char *arg,
+ route_map_event_t type, char *errmsg, size_t errmsg_len));
+/* no match ipv6 next hop */
+extern void route_map_no_match_ipv6_next_hop_hook(int (*func)(
+ struct route_map_index *index, const char *command, const char *arg,
+ route_map_event_t type, char *errmsg, size_t errmsg_len));
+/* match ip next hop prefix list */
+extern void route_map_match_ip_next_hop_prefix_list_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len));
+/* no match ip next hop prefix list */
+extern void route_map_no_match_ip_next_hop_prefix_list_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len));
+/* match ip next hop type */
+extern void route_map_match_ip_next_hop_type_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len));
+/* no match ip next hop type */
+extern void route_map_no_match_ip_next_hop_type_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len));
+/* match ipv6 address */
+extern void route_map_match_ipv6_address_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len));
+/* no match ipv6 address */
+extern void route_map_no_match_ipv6_address_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len));
+/* match ipv6 address prefix list */
+extern void route_map_match_ipv6_address_prefix_list_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len));
+/* no match ipv6 address prefix list */
+extern void route_map_no_match_ipv6_address_prefix_list_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len));
+/* match ipv6 next-hop type */
+extern void route_map_match_ipv6_next_hop_type_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len));
+/* no match ipv6 next-hop type */
+extern void route_map_no_match_ipv6_next_hop_type_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len));
+/* match ipv6 next-hop prefix-list */
+extern void route_map_match_ipv6_next_hop_prefix_list_hook(int (*func)(
+ struct route_map_index *index, const char *command, const char *arg,
+ route_map_event_t type, char *errmsg, size_t errmsg_len));
+/* no match ipv6 next-hop prefix-list */
+extern void route_map_no_match_ipv6_next_hop_prefix_list_hook(int (*func)(
+ struct route_map_index *index, const char *command, const char *arg,
+ route_map_event_t type, char *errmsg, size_t errmsg_len));
+/* match metric */
+extern void route_map_match_metric_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len));
+/* no match metric */
+extern void route_map_no_match_metric_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len));
+/* match tag */
+extern void route_map_match_tag_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len));
+/* no match tag */
+extern void route_map_no_match_tag_hook(int (*func)(
+ struct route_map_index *index, const char *command,
+ const char *arg, route_map_event_t type,
+ char *errmsg, size_t errmsg_len));
+/* set sr-te color */
+extern void route_map_set_srte_color_hook(
+ int (*func)(struct route_map_index *index,
+ const char *command, const char *arg,
+ char *errmsg, size_t errmsg_len));
+/* no set sr-te color */
+extern void route_map_no_set_srte_color_hook(
+ int (*func)(struct route_map_index *index,
+ const char *command, const char *arg,
+ char *errmsg, size_t errmsg_len));
+/* set ip nexthop */
+extern void route_map_set_ip_nexthop_hook(
+ int (*func)(struct route_map_index *index,
+ const char *command, const char *arg,
+ char *errmsg, size_t errmsg_len));
+/* no set ip nexthop */
+extern void route_map_no_set_ip_nexthop_hook(
+ int (*func)(struct route_map_index *index,
+ const char *command, const char *arg,
+ char *errmsg, size_t errmsg_len));
+/* set ipv6 nexthop local */
+extern void route_map_set_ipv6_nexthop_local_hook(
+ int (*func)(struct route_map_index *index,
+ const char *command, const char *arg,
+ char *errmsg, size_t errmsg_len));
+/* no set ipv6 nexthop local */
+extern void route_map_no_set_ipv6_nexthop_local_hook(
+ int (*func)(struct route_map_index *index,
+ const char *command, const char *arg,
+ char *errmsg, size_t errmsg_len));
+/* set metric */
+extern void route_map_set_metric_hook(int (*func)(struct route_map_index *index,
+ const char *command,
+ const char *arg,
+ char *errmsg,
+ size_t errmsg_len));
+/* no set metric */
+extern void route_map_no_set_metric_hook(
+ int (*func)(struct route_map_index *index,
+ const char *command, const char *arg,
+ char *errmsg, size_t errmsg_len));
+/* set tag */
+extern void route_map_set_tag_hook(int (*func)(struct route_map_index *index,
+ const char *command,
+ const char *arg,
+ char *errmsg,
+ size_t errmsg_len));
+/* no set tag */
+extern void route_map_no_set_tag_hook(int (*func)(struct route_map_index *index,
+ const char *command,
+ const char *arg,
+ char *errmsg,
+ size_t errmsg_len));
+
+extern void *route_map_rule_tag_compile(const char *arg);
+extern void route_map_rule_tag_free(void *rule);
+
+/* Increment the route-map used counter */
+extern void route_map_counter_increment(struct route_map *map);
+
+/* Decrement the route-map used counter */
+extern void route_map_counter_decrement(struct route_map *map);
+
+/* Route map hooks data structure. */
+struct route_map_match_set_hooks {
+ /* match interface */
+ int (*match_interface)(struct route_map_index *index,
+ const char *command, const char *arg,
+ route_map_event_t type,
+ char *errmsg, size_t errmsg_len);
+
+ /* no match interface */
+ int (*no_match_interface)(struct route_map_index *index,
+ const char *command, const char *arg,
+ route_map_event_t type,
+ char *errmsg, size_t errmsg_len);
+
+ /* match ip address */
+ int (*match_ip_address)(struct route_map_index *index,
+ const char *command, const char *arg,
+ route_map_event_t type,
+ char *errmsg, size_t errmsg_len);
+
+ /* no match ip address */
+ int (*no_match_ip_address)(struct route_map_index *index,
+ const char *command, const char *arg,
+ route_map_event_t type,
+ char *errmsg, size_t errmsg_len);
+
+ /* match ip address prefix list */
+ int (*match_ip_address_prefix_list)(struct route_map_index *index,
+ const char *command,
+ const char *arg,
+ route_map_event_t type,
+ char *errmsg, size_t errmsg_len);
+
+ /* no match ip address prefix list */
+ int (*no_match_ip_address_prefix_list)(struct route_map_index *index,
+ const char *command,
+ const char *arg,
+ route_map_event_t type,
+ char *errmsg, size_t errmsg_len);
+
+ /* match ip next hop */
+ int (*match_ip_next_hop)(struct route_map_index *index,
+ const char *command, const char *arg,
+ route_map_event_t type,
+ char *errmsg, size_t errmsg_len);
+
+ /* no match ip next hop */
+ int (*no_match_ip_next_hop)(struct route_map_index *index,
+ const char *command, const char *arg,
+ route_map_event_t type,
+ char *errmsg, size_t errmsg_len);
+
+ /* match ipv6 next hop */
+ int (*match_ipv6_next_hop)(struct route_map_index *index,
+ const char *command, const char *arg,
+ route_map_event_t type, char *errmsg,
+ size_t errmsg_len);
+
+ /* no match ipv6 next hop */
+ int (*no_match_ipv6_next_hop)(struct route_map_index *index,
+ const char *command, const char *arg,
+ route_map_event_t type, char *errmsg,
+ size_t errmsg_len);
+
+ /* match ipv6 next hop prefix-list */
+ int (*match_ipv6_next_hop_prefix_list)(struct route_map_index *index,
+ const char *command,
+ const char *arg,
+ route_map_event_t type,
+ char *errmsg, size_t errmsg_len);
+
+ /* no match ipv6 next-hop prefix-list */
+ int (*no_match_ipv6_next_hop_prefix_list)(struct route_map_index *index,
+ const char *command,
+ const char *arg,
+ route_map_event_t type,
+ char *errmsg,
+ size_t errmsg_len);
+
+ /* match ip next hop prefix list */
+ int (*match_ip_next_hop_prefix_list)(struct route_map_index *index,
+ const char *command,
+ const char *arg,
+ route_map_event_t type,
+ char *errmsg, size_t errmsg_len);
+
+ /* no match ip next hop prefix list */
+ int (*no_match_ip_next_hop_prefix_list)(struct route_map_index *index,
+ const char *command,
+ const char *arg,
+ route_map_event_t type,
+ char *errmsg,
+ size_t errmsg_len);
+
+ /* match ip next-hop type */
+ int (*match_ip_next_hop_type)(struct route_map_index *index,
+ const char *command,
+ const char *arg,
+ route_map_event_t type,
+ char *errmsg,
+ size_t errmsg_len);
+
+ /* no match ip next-hop type */
+ int (*no_match_ip_next_hop_type)(struct route_map_index *index,
+ const char *command,
+ const char *arg,
+ route_map_event_t type,
+ char *errmsg,
+ size_t errmsg_len);
+
+ /* match ipv6 address */
+ int (*match_ipv6_address)(struct route_map_index *index,
+ const char *command, const char *arg,
+ route_map_event_t type,
+ char *errmsg, size_t errmsg_len);
+
+ /* no match ipv6 address */
+ int (*no_match_ipv6_address)(struct route_map_index *index,
+ const char *command, const char *arg,
+ route_map_event_t type,
+ char *errmsg, size_t errmsg_len);
+
+
+ /* match ipv6 address prefix list */
+ int (*match_ipv6_address_prefix_list)(struct route_map_index *index,
+ const char *command,
+ const char *arg,
+ route_map_event_t type,
+ char *errmsg, size_t errmsg_len);
+
+ /* no match ipv6 address prefix list */
+ int (*no_match_ipv6_address_prefix_list)(struct route_map_index *index,
+ const char *command,
+ const char *arg,
+ route_map_event_t type,
+ char *errmsg,
+ size_t errmsg_len);
+
+ /* match ipv6 next-hop type */
+ int (*match_ipv6_next_hop_type)(struct route_map_index *index,
+ const char *command,
+ const char *arg,
+ route_map_event_t type,
+ char *errmsg, size_t errmsg_len);
+
+ /* no match ipv6 next-hop type */
+ int (*no_match_ipv6_next_hop_type)(struct route_map_index *index,
+ const char *command, const char *arg,
+ route_map_event_t type,
+ char *errmsg, size_t errmsg_len);
+
+ /* match metric */
+ int (*match_metric)(struct route_map_index *index,
+ const char *command, const char *arg,
+ route_map_event_t type,
+ char *errmsg, size_t errmsg_len);
+
+ /* no match metric */
+ int (*no_match_metric)(struct route_map_index *index,
+ const char *command, const char *arg,
+ route_map_event_t type,
+ char *errmsg, size_t errmsg_len);
+
+ /* match tag */
+ int (*match_tag)(struct route_map_index *index,
+ const char *command, const char *arg,
+ route_map_event_t type,
+ char *errmsg, size_t errmsg_len);
+
+ /* no match tag */
+ int (*no_match_tag)(struct route_map_index *index,
+ const char *command, const char *arg,
+ route_map_event_t type,
+ char *errmsg, size_t errmsg_len);
+
+ /* set sr-te color */
+ int (*set_srte_color)(struct route_map_index *index,
+ const char *command, const char *arg,
+ char *errmsg, size_t errmsg_len);
+
+ /* no set sr-te color */
+ int (*no_set_srte_color)(struct route_map_index *index,
+ const char *command, const char *arg,
+ char *errmsg, size_t errmsg_len);
+
+ /* set ip nexthop */
+ int (*set_ip_nexthop)(struct route_map_index *index,
+ const char *command, const char *arg,
+ char *errmsg, size_t errmsg_len);
+
+ /* no set ip nexthop */
+ int (*no_set_ip_nexthop)(struct route_map_index *index,
+ const char *command, const char *arg,
+ char *errmsg, size_t errmsg_len);
+
+ /* set ipv6 nexthop local */
+ int (*set_ipv6_nexthop_local)(struct route_map_index *index,
+ const char *command, const char *arg,
+ char *errmsg, size_t errmsg_len);
+
+ /* no set ipv6 nexthop local */
+ int (*no_set_ipv6_nexthop_local)(struct route_map_index *index,
+ const char *command, const char *arg,
+ char *errmsg, size_t errmsg_len);
+
+ /* set metric */
+ int (*set_metric)(struct route_map_index *index,
+ const char *command, const char *arg,
+ char *errmsg, size_t errmsg_len);
+
+ /* no set metric */
+ int (*no_set_metric)(struct route_map_index *index,
+ const char *command, const char *arg,
+ char *errmsg, size_t errmsg_len);
+
+ /* set tag */
+ int (*set_tag)(struct route_map_index *index,
+ const char *command, const char *arg,
+ char *errmsg, size_t errmsg_len);
+
+ /* no set tag */
+ int (*no_set_tag)(struct route_map_index *index,
+ const char *command, const char *arg,
+ char *errmsg, size_t errmsg_len);
+};
+
+extern struct route_map_match_set_hooks rmap_match_set_hook;
+
+/* Making route map list. */
+struct route_map_list {
+ struct route_map *head;
+ struct route_map *tail;
+
+ void (*add_hook)(const char *);
+ void (*delete_hook)(const char *);
+ void (*event_hook)(const char *);
+};
+
+extern struct route_map_list route_map_master;
+
+extern struct route_map *route_map_get(const char *name);
+extern void route_map_delete(struct route_map *map);
+extern struct route_map_index *route_map_index_get(struct route_map *map,
+ enum route_map_type type,
+ int pref);
+extern void route_map_index_delete(struct route_map_index *index, int notify);
+
+/* routemap_northbound.c */
+typedef int (*routemap_match_hook_fun)(struct route_map_index *rmi,
+ const char *command, const char *arg,
+ route_map_event_t event,
+ char *errmsg, size_t errmsg_len);
+typedef int (*routemap_set_hook_fun)(struct route_map_index *rmi,
+ const char *command, const char *arg,
+ char *errmsg, size_t errmsg_len);
+struct routemap_hook_context {
+ struct route_map_index *rhc_rmi;
+ const char *rhc_rule;
+ route_map_event_t rhc_event;
+ routemap_set_hook_fun rhc_shook;
+ routemap_match_hook_fun rhc_mhook;
+ TAILQ_ENTRY(routemap_hook_context) rhc_entry;
+};
+
+int lib_route_map_entry_match_destroy(struct nb_cb_destroy_args *args);
+int lib_route_map_entry_set_destroy(struct nb_cb_destroy_args *args);
+
+struct routemap_hook_context *
+routemap_hook_context_insert(struct route_map_index *rmi);
+void routemap_hook_context_free(struct routemap_hook_context *rhc);
+
+extern const struct frr_yang_module_info frr_route_map_info;
+
+/* routemap_cli.c */
+extern int route_map_instance_cmp(const struct lyd_node *dnode1,
+ const struct lyd_node *dnode2);
+extern void route_map_instance_show(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+extern void route_map_instance_show_end(struct vty *vty,
+ const struct lyd_node *dnode);
+extern void route_map_condition_show(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+extern void route_map_action_show(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+extern void route_map_exit_policy_show(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+extern void route_map_call_show(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+extern void route_map_description_show(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+extern void route_map_optimization_disabled_show(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+extern void route_map_cli_init(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ZEBRA_ROUTEMAP_H */
diff --git a/lib/routemap_cli.c b/lib/routemap_cli.c
new file mode 100644
index 0000000..6be5d15
--- /dev/null
+++ b/lib/routemap_cli.c
@@ -0,0 +1,1570 @@
+/*
+ * Route map northbound CLI implementation.
+ *
+ * Copyright (C) 2019 Network Device Education Foundation, Inc. ("NetDEF")
+ * Rafael Zalamena
+ *
+ * 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; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301 USA.
+ */
+
+#include <zebra.h>
+
+#include "lib/command.h"
+#include "lib/northbound_cli.h"
+#include "lib/routemap.h"
+
+#ifndef VTYSH_EXTRACT_PL
+#include "lib/routemap_cli_clippy.c"
+#endif /* VTYSH_EXTRACT_PL */
+
+#define ROUTE_MAP_CMD_STR \
+ "Create route-map or enter route-map command mode\n" \
+ "Route map tag\n"
+#define ROUTE_MAP_OP_CMD_STR \
+ "Route map denies set operations\n" \
+ "Route map permits set operations\n"
+#define ROUTE_MAP_SEQUENCE_CMD_STR \
+ "Sequence to insert to/delete from existing route-map entry\n"
+
+DEFPY_YANG_NOSH(
+ route_map, route_map_cmd,
+ "route-map RMAP_NAME$name <deny|permit>$action (1-65535)$sequence",
+ ROUTE_MAP_CMD_STR
+ ROUTE_MAP_OP_CMD_STR
+ ROUTE_MAP_SEQUENCE_CMD_STR)
+{
+ char xpath_action[XPATH_MAXLEN + 64];
+ char xpath_index[XPATH_MAXLEN + 32];
+ char xpath[XPATH_MAXLEN];
+ int rv;
+
+ snprintf(xpath, sizeof(xpath),
+ "/frr-route-map:lib/route-map[name='%s']", name);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
+
+ snprintf(xpath_index, sizeof(xpath_index), "%s/entry[sequence='%lu']",
+ xpath, sequence);
+ nb_cli_enqueue_change(vty, xpath_index, NB_OP_CREATE, NULL);
+
+ snprintf(xpath_action, sizeof(xpath_action), "%s/action", xpath_index);
+ nb_cli_enqueue_change(vty, xpath_action, NB_OP_MODIFY, action);
+
+ rv = nb_cli_apply_changes(vty, NULL);
+ if (rv == CMD_SUCCESS)
+ VTY_PUSH_XPATH(RMAP_NODE, xpath_index);
+
+ return rv;
+}
+
+DEFPY_YANG(
+ no_route_map_all, no_route_map_all_cmd,
+ "no route-map RMAP_NAME$name",
+ NO_STR
+ ROUTE_MAP_CMD_STR)
+{
+ char xpath[XPATH_MAXLEN];
+
+ snprintf(xpath, sizeof(xpath),
+ "/frr-route-map:lib/route-map[name='%s']", name);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ no_route_map, no_route_map_cmd,
+ "no route-map RMAP_NAME$name <deny|permit>$action (1-65535)$sequence",
+ NO_STR
+ ROUTE_MAP_CMD_STR
+ ROUTE_MAP_OP_CMD_STR
+ ROUTE_MAP_SEQUENCE_CMD_STR)
+{
+ char xpath[XPATH_MAXLEN];
+
+ snprintf(xpath, sizeof(xpath),
+ "/frr-route-map:lib/route-map[name='%s']/entry[sequence='%lu']",
+ name, sequence);
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+int route_map_instance_cmp(const struct lyd_node *dnode1,
+ const struct lyd_node *dnode2)
+{
+ uint16_t seq1 = yang_dnode_get_uint16(dnode1, "./sequence");
+ uint16_t seq2 = yang_dnode_get_uint16(dnode2, "./sequence");
+
+ return seq1 - seq2;
+}
+
+void route_map_instance_show(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ const char *name = yang_dnode_get_string(dnode, "../name");
+ const char *action = yang_dnode_get_string(dnode, "./action");
+ const char *sequence = yang_dnode_get_string(dnode, "./sequence");
+
+ vty_out(vty, "route-map %s %s %s\n", name, action, sequence);
+
+}
+
+void route_map_instance_show_end(struct vty *vty, const struct lyd_node *dnode)
+{
+ vty_out(vty, "exit\n");
+ vty_out(vty, "!\n");
+}
+
+DEFPY_YANG(
+ match_interface, match_interface_cmd,
+ "match interface IFNAME",
+ MATCH_STR
+ "Match first hop interface of route\n"
+ INTERFACE_STR)
+{
+ const char *xpath =
+ "./match-condition[condition='frr-route-map:interface']";
+ char xpath_value[XPATH_MAXLEN];
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
+ snprintf(xpath_value, sizeof(xpath_value),
+ "%s/rmap-match-condition/interface", xpath);
+ nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, ifname);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ no_match_interface, no_match_interface_cmd,
+ "no match interface [IFNAME]",
+ NO_STR
+ MATCH_STR
+ "Match first hop interface of route\n"
+ INTERFACE_STR)
+{
+ const char *xpath =
+ "./match-condition[condition='frr-route-map:interface']";
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ match_ip_address, match_ip_address_cmd,
+ "match ip address ACCESSLIST4_NAME$name",
+ MATCH_STR
+ IP_STR
+ "Match address of route\n"
+ "IP Access-list name\n")
+{
+ const char *xpath =
+ "./match-condition[condition='frr-route-map:ipv4-address-list']";
+ char xpath_value[XPATH_MAXLEN + 32];
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
+ snprintf(xpath_value, sizeof(xpath_value),
+ "%s/rmap-match-condition/list-name", xpath);
+ nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, name);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ no_match_ip_address, no_match_ip_address_cmd,
+ "no match ip address [ACCESSLIST4_NAME]",
+ NO_STR
+ MATCH_STR
+ IP_STR
+ "Match address of route\n"
+ "IP Access-list name\n")
+{
+ const char *xpath =
+ "./match-condition[condition='frr-route-map:ipv4-address-list']";
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ match_ip_address_prefix_list,
+ match_ip_address_prefix_list_cmd,
+ "match ip address prefix-list PREFIXLIST_NAME$name",
+ MATCH_STR
+ IP_STR
+ "Match address of route\n"
+ "Match entries of prefix-lists\n"
+ "IP prefix-list name\n")
+{
+ const char *xpath =
+ "./match-condition[condition='frr-route-map:ipv4-prefix-list']";
+ char xpath_value[XPATH_MAXLEN];
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
+ snprintf(xpath_value, sizeof(xpath_value),
+ "%s/rmap-match-condition/list-name", xpath);
+ nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, name);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ no_match_ip_address_prefix_list, no_match_ip_address_prefix_list_cmd,
+ "no match ip address prefix-list [PREFIXLIST_NAME]",
+ NO_STR
+ MATCH_STR
+ IP_STR
+ "Match address of route\n"
+ "Match entries of prefix-lists\n"
+ "IP prefix-list name\n")
+{
+ const char *xpath =
+ "./match-condition[condition='frr-route-map:ipv4-prefix-list']";
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ match_ip_next_hop, match_ip_next_hop_cmd,
+ "match ip next-hop ACCESSLIST4_NAME$name",
+ MATCH_STR
+ IP_STR
+ "Match next-hop address of route\n"
+ "IP Access-list name\n")
+{
+ const char *xpath =
+ "./match-condition[condition='frr-route-map:ipv4-next-hop-list']";
+ char xpath_value[XPATH_MAXLEN + 32];
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
+ snprintf(xpath_value, sizeof(xpath_value),
+ "%s/rmap-match-condition/list-name", xpath);
+ nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, name);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ no_match_ip_next_hop, no_match_ip_next_hop_cmd,
+ "no match ip next-hop [ACCESSLIST4_NAME]",
+ NO_STR
+ MATCH_STR
+ IP_STR
+ "Match address of route\n"
+ "IP Access-list name\n")
+{
+ const char *xpath =
+ "./match-condition[condition='frr-route-map:ipv4-next-hop-list']";
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ match_ip_next_hop_prefix_list,
+ match_ip_next_hop_prefix_list_cmd,
+ "match ip next-hop prefix-list PREFIXLIST_NAME$name",
+ MATCH_STR
+ IP_STR
+ "Match next-hop address of route\n"
+ "Match entries of prefix-lists\n"
+ "IP prefix-list name\n")
+{
+ const char *xpath =
+ "./match-condition[condition='frr-route-map:ipv4-next-hop-prefix-list']";
+ char xpath_value[XPATH_MAXLEN];
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
+ snprintf(xpath_value, sizeof(xpath_value),
+ "%s/rmap-match-condition/list-name", xpath);
+ nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, name);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ no_match_ip_next_hop_prefix_list,
+ no_match_ip_next_hop_prefix_list_cmd,
+ "no match ip next-hop prefix-list [PREFIXLIST_NAME]",
+ NO_STR
+ MATCH_STR
+ IP_STR
+ "Match next-hop address of route\n"
+ "Match entries of prefix-lists\n"
+ "IP prefix-list name\n")
+{
+ const char *xpath =
+ "./match-condition[condition='frr-route-map:ipv4-next-hop-prefix-list']";
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ match_ip_next_hop_type, match_ip_next_hop_type_cmd,
+ "match ip next-hop type <blackhole>$type",
+ MATCH_STR
+ IP_STR
+ "Match next-hop address of route\n"
+ "Match entries by type\n"
+ "Blackhole\n")
+{
+ const char *xpath =
+ "./match-condition[condition='frr-route-map:ipv4-next-hop-type']";
+ char xpath_value[XPATH_MAXLEN];
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
+ snprintf(xpath_value, sizeof(xpath_value),
+ "%s/rmap-match-condition/ipv4-next-hop-type", xpath);
+ nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, type);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ no_match_ip_next_hop_type, no_match_ip_next_hop_type_cmd,
+ "no match ip next-hop type [<blackhole>]",
+ NO_STR MATCH_STR IP_STR
+ "Match next-hop address of route\n"
+ "Match entries by type\n"
+ "Blackhole\n")
+{
+ const char *xpath =
+ "./match-condition[condition='frr-route-map:ipv4-next-hop-type']";
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ match_ipv6_address, match_ipv6_address_cmd,
+ "match ipv6 address ACCESSLIST6_NAME$name",
+ MATCH_STR
+ IPV6_STR
+ "Match IPv6 address of route\n"
+ "IPv6 access-list name\n")
+{
+ const char *xpath =
+ "./match-condition[condition='frr-route-map:ipv6-address-list']";
+ char xpath_value[XPATH_MAXLEN];
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
+ snprintf(xpath_value, sizeof(xpath_value),
+ "%s/rmap-match-condition/list-name", xpath);
+ nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, name);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ no_match_ipv6_address, no_match_ipv6_address_cmd,
+ "no match ipv6 address [ACCESSLIST6_NAME]",
+ NO_STR
+ MATCH_STR
+ IPV6_STR
+ "Match IPv6 address of route\n"
+ "IPv6 access-list name\n")
+{
+ const char *xpath =
+ "./match-condition[condition='frr-route-map:ipv6-address-list']";
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ match_ipv6_address_prefix_list, match_ipv6_address_prefix_list_cmd,
+ "match ipv6 address prefix-list PREFIXLIST_NAME$name",
+ MATCH_STR
+ IPV6_STR
+ "Match address of route\n"
+ "Match entries of prefix-lists\n"
+ "IP prefix-list name\n")
+{
+ const char *xpath =
+ "./match-condition[condition='frr-route-map:ipv6-prefix-list']";
+ char xpath_value[XPATH_MAXLEN];
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
+ snprintf(xpath_value, sizeof(xpath_value),
+ "%s/rmap-match-condition/list-name", xpath);
+ nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, name);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ no_match_ipv6_address_prefix_list,
+ no_match_ipv6_address_prefix_list_cmd,
+ "no match ipv6 address prefix-list [PREFIXLIST_NAME]",
+ NO_STR
+ MATCH_STR
+ IPV6_STR
+ "Match address of route\n"
+ "Match entries of prefix-lists\n"
+ "IP prefix-list name\n")
+{
+ const char *xpath =
+ "./match-condition[condition='frr-route-map:ipv6-prefix-list']";
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ match_ipv6_next_hop_type, match_ipv6_next_hop_type_cmd,
+ "match ipv6 next-hop type <blackhole>$type",
+ MATCH_STR IPV6_STR
+ "Match next-hop address of route\n"
+ "Match entries by type\n"
+ "Blackhole\n")
+{
+ const char *xpath =
+ "./match-condition[condition='frr-route-map:ipv6-next-hop-type']";
+ char xpath_value[XPATH_MAXLEN];
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
+ snprintf(xpath_value, sizeof(xpath_value),
+ "%s/rmap-match-condition/ipv6-next-hop-type", xpath);
+ nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, type);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ no_match_ipv6_next_hop_type, no_match_ipv6_next_hop_type_cmd,
+ "no match ipv6 next-hop type [<blackhole>]",
+ NO_STR MATCH_STR IPV6_STR
+ "Match address of route\n"
+ "Match entries by type\n"
+ "Blackhole\n")
+{
+ const char *xpath =
+ "./match-condition[condition='frr-route-map:ipv6-next-hop-type']";
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ match_metric, match_metric_cmd,
+ "match metric (0-4294967295)$metric",
+ MATCH_STR
+ "Match metric of route\n"
+ "Metric value\n")
+{
+ const char *xpath =
+ "./match-condition[condition='frr-route-map:match-metric']";
+ char xpath_value[XPATH_MAXLEN];
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
+ snprintf(xpath_value, sizeof(xpath_value),
+ "%s/rmap-match-condition/metric", xpath);
+ nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, metric_str);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ no_match_metric, no_match_metric_cmd,
+ "no match metric [(0-4294967295)]",
+ NO_STR
+ MATCH_STR
+ "Match metric of route\n"
+ "Metric value\n")
+{
+ const char *xpath =
+ "./match-condition[condition='frr-route-map:match-metric']";
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ match_tag, match_tag_cmd,
+ "match tag (1-4294967295)$tag",
+ MATCH_STR
+ "Match tag of route\n"
+ "Tag value\n")
+{
+ const char *xpath =
+ "./match-condition[condition='frr-route-map:match-tag']";
+ char xpath_value[XPATH_MAXLEN];
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
+ snprintf(xpath_value, sizeof(xpath_value),
+ "%s/rmap-match-condition/tag", xpath);
+ nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, tag_str);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ no_match_tag, no_match_tag_cmd,
+ "no match tag [(1-4294967295)]",
+ NO_STR
+ MATCH_STR
+ "Match tag of route\n"
+ "Tag value\n")
+{
+ const char *xpath =
+ "./match-condition[condition='frr-route-map:match-tag']";
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void route_map_condition_show(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ const char *condition = yang_dnode_get_string(dnode, "./condition");
+ const struct lyd_node *ln;
+ const char *acl;
+
+ if (IS_MATCH_INTERFACE(condition)) {
+ vty_out(vty, " match interface %s\n",
+ yang_dnode_get_string(
+ dnode, "./rmap-match-condition/interface"));
+ } else if (IS_MATCH_IPv4_ADDRESS_LIST(condition)) {
+ vty_out(vty, " match ip address %s\n",
+ yang_dnode_get_string(
+ dnode, "./rmap-match-condition/list-name"));
+ } else if (IS_MATCH_IPv4_NEXTHOP_LIST(condition)) {
+ vty_out(vty, " match ip next-hop %s\n",
+ yang_dnode_get_string(
+ dnode, "./rmap-match-condition/list-name"));
+ } else if (IS_MATCH_IPv6_NEXTHOP_LIST(condition)) {
+ vty_out(vty, " match ipv6 next-hop %s\n",
+ yang_dnode_get_string(
+ dnode, "./rmap-match-condition/list-name"));
+ } else if (IS_MATCH_IPv4_PREFIX_LIST(condition)) {
+ vty_out(vty, " match ip address prefix-list %s\n",
+ yang_dnode_get_string(
+ dnode, "./rmap-match-condition/list-name"));
+ } else if (IS_MATCH_IPv4_NEXTHOP_PREFIX_LIST(condition)) {
+ vty_out(vty, " match ip next-hop prefix-list %s\n",
+ yang_dnode_get_string(
+ dnode, "./rmap-match-condition/list-name"));
+ } else if (IS_MATCH_IPv6_NEXTHOP_PREFIX_LIST(condition)) {
+ vty_out(vty, " match ipv6 next-hop prefix-list %s\n",
+ yang_dnode_get_string(
+ dnode, "./rmap-match-condition/list-name"));
+ } else if (IS_MATCH_IPv6_ADDRESS_LIST(condition)) {
+ vty_out(vty, " match ipv6 address %s\n",
+ yang_dnode_get_string(
+ dnode, "./rmap-match-condition/list-name"));
+ } else if (IS_MATCH_IPv6_PREFIX_LIST(condition)) {
+ vty_out(vty, " match ipv6 address prefix-list %s\n",
+ yang_dnode_get_string(
+ dnode, "./rmap-match-condition/list-name"));
+ } else if (IS_MATCH_IPv4_NEXTHOP_TYPE(condition)) {
+ vty_out(vty, " match ip next-hop type %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-match-condition/ipv4-next-hop-type"));
+ } else if (IS_MATCH_IPv6_NEXTHOP_TYPE(condition)) {
+ vty_out(vty, " match ipv6 next-hop type %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-match-condition/ipv6-next-hop-type"));
+ } else if (IS_MATCH_METRIC(condition)) {
+ vty_out(vty, " match metric %s\n",
+ yang_dnode_get_string(dnode,
+ "./rmap-match-condition/metric"));
+ } else if (IS_MATCH_TAG(condition)) {
+ vty_out(vty, " match tag %s\n",
+ yang_dnode_get_string(dnode,
+ "./rmap-match-condition/tag"));
+ } else if (IS_MATCH_IPv4_PREFIX_LEN(condition)) {
+ vty_out(vty, " match ip address prefix-len %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-match-condition/frr-zebra-route-map:ipv4-prefix-length"));
+ } else if (IS_MATCH_IPv6_PREFIX_LEN(condition)) {
+ vty_out(vty, " match ipv6 address prefix-len %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-match-condition/frr-zebra-route-map:ipv6-prefix-length"));
+ } else if (IS_MATCH_IPv4_NH_PREFIX_LEN(condition)) {
+ vty_out(vty, " match ip next-hop prefix-len %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-match-condition/frr-zebra-route-map:ipv4-prefix-length"));
+ } else if (IS_MATCH_SRC_PROTO(condition)) {
+ vty_out(vty, " match source-protocol %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-match-condition/frr-zebra-route-map:source-protocol"));
+ } else if (IS_MATCH_SRC_INSTANCE(condition)) {
+ vty_out(vty, " match source-instance %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-match-condition/frr-zebra-route-map:source-instance"));
+ } else if (IS_MATCH_LOCAL_PREF(condition)) {
+ vty_out(vty, " match local-preference %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-match-condition/frr-bgp-route-map:local-preference"));
+ } else if (IS_MATCH_ALIAS(condition)) {
+ vty_out(vty, " match alias %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-match-condition/frr-bgp-route-map:alias"));
+ } else if (IS_MATCH_SCRIPT(condition)) {
+ vty_out(vty, " match script %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-match-condition/frr-bgp-route-map:script"));
+ } else if (IS_MATCH_ORIGIN(condition)) {
+ vty_out(vty, " match origin %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-match-condition/frr-bgp-route-map:origin"));
+ } else if (IS_MATCH_RPKI(condition)) {
+ vty_out(vty, " match rpki %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-match-condition/frr-bgp-route-map:rpki"));
+ } else if (IS_MATCH_RPKI_EXTCOMMUNITY(condition)) {
+ vty_out(vty, " match rpki-extcommunity %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-match-condition/frr-bgp-route-map:rpki-extcommunity"));
+ } else if (IS_MATCH_PROBABILITY(condition)) {
+ vty_out(vty, " match probability %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-match-condition/frr-bgp-route-map:probability"));
+ } else if (IS_MATCH_SRC_VRF(condition)) {
+ vty_out(vty, " match source-vrf %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-match-condition/frr-bgp-route-map:source-vrf"));
+ } else if (IS_MATCH_PEER(condition)) {
+ acl = NULL;
+ if ((ln = yang_dnode_get(
+ dnode,
+ "./rmap-match-condition/frr-bgp-route-map:peer-ipv4-address"))
+ != NULL)
+ acl = yang_dnode_get_string(ln, NULL);
+ else if (
+ (ln = yang_dnode_get(
+ dnode,
+ "./rmap-match-condition/frr-bgp-route-map:peer-ipv6-address"))
+ != NULL)
+ acl = yang_dnode_get_string(ln, NULL);
+ else if (
+ (ln = yang_dnode_get(
+ dnode,
+ "./rmap-match-condition/frr-bgp-route-map:peer-interface"))
+ != NULL)
+ acl = yang_dnode_get_string(ln, NULL);
+ else if (yang_dnode_get(
+ dnode,
+ "./rmap-match-condition/frr-bgp-route-map:peer-local")
+ != NULL)
+ acl = "local";
+
+ vty_out(vty, " match peer %s\n", acl);
+ } else if (IS_MATCH_AS_LIST(condition)) {
+ vty_out(vty, " match as-path %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-match-condition/frr-bgp-route-map:list-name"));
+ } else if (IS_MATCH_EVPN_ROUTE_TYPE(condition)) {
+ vty_out(vty, " match evpn route-type %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-match-condition/frr-bgp-route-map:evpn-route-type"));
+ } else if (IS_MATCH_EVPN_DEFAULT_ROUTE(condition)) {
+ vty_out(vty, " match evpn default-route\n");
+ } else if (IS_MATCH_EVPN_VNI(condition)) {
+ vty_out(vty, " match evpn vni %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-match-condition/frr-bgp-route-map:evpn-vni"));
+ } else if (IS_MATCH_EVPN_DEFAULT_ROUTE(condition)) {
+ vty_out(vty, " match evpn default-route %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-match-condition/frr-bgp-route-map:evpn-default-route"));
+ } else if (IS_MATCH_EVPN_RD(condition)) {
+ vty_out(vty, " match evpn rd %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-match-condition/frr-bgp-route-map:route-distinguisher"));
+ } else if (IS_MATCH_MAC_LIST(condition)) {
+ vty_out(vty, " match mac address %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-match-condition/frr-bgp-route-map:list-name"));
+ } else if (IS_MATCH_ROUTE_SRC(condition)) {
+ vty_out(vty, " match ip route-source %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-match-condition/frr-bgp-route-map:list-name"));
+ } else if (IS_MATCH_ROUTE_SRC_PL(condition)) {
+ vty_out(vty, " match ip route-source prefix-list %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-match-condition/frr-bgp-route-map:list-name"));
+ } else if (IS_MATCH_COMMUNITY(condition)) {
+ vty_out(vty, " match community %s",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-match-condition/frr-bgp-route-map:comm-list/comm-list-name"));
+ if (yang_dnode_get_bool(
+ dnode,
+ "./rmap-match-condition/frr-bgp-route-map:comm-list/comm-list-name-exact-match"))
+ vty_out(vty, " exact-match");
+ vty_out(vty, "\n");
+ } else if (IS_MATCH_LCOMMUNITY(condition)) {
+ vty_out(vty, " match large-community %s",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-match-condition/frr-bgp-route-map:comm-list/comm-list-name"));
+ if (yang_dnode_get_bool(
+ dnode,
+ "./rmap-match-condition/frr-bgp-route-map:comm-list/comm-list-name-exact-match"))
+ vty_out(vty, " exact-match");
+ vty_out(vty, "\n");
+ } else if (IS_MATCH_EXTCOMMUNITY(condition)) {
+ vty_out(vty, " match extcommunity %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-match-condition/frr-bgp-route-map:comm-list/comm-list-name"));
+ } else if (IS_MATCH_IPV4_NH(condition)) {
+ vty_out(vty, " match ip next-hop address %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-match-condition/frr-bgp-route-map:ipv4-address"));
+ } else if (IS_MATCH_IPV6_NH(condition)) {
+ vty_out(vty, " match ipv6 next-hop address %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-match-condition/frr-bgp-route-map:ipv6-address"));
+ }
+}
+
+DEFPY_YANG(
+ set_ip_nexthop, set_ip_nexthop_cmd,
+ "set ip next-hop A.B.C.D$addr",
+ SET_STR
+ IP_STR
+ "Next hop address\n"
+ "IP address of next hop\n")
+{
+ const char *xpath =
+ "./set-action[action='frr-route-map:ipv4-next-hop']";
+ char xpath_value[XPATH_MAXLEN];
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
+ snprintf(xpath_value, sizeof(xpath_value),
+ "%s/rmap-set-action/ipv4-address", xpath);
+ nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, addr_str);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ no_set_ip_nexthop, no_set_ip_nexthop_cmd,
+ "no set ip next-hop [A.B.C.D]",
+ NO_STR
+ SET_STR
+ IP_STR
+ "Next hop address\n"
+ "IP address of next hop\n")
+{
+ const char *xpath =
+ "./set-action[action='frr-route-map:ipv4-next-hop']";
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ set_ipv6_nexthop_local, set_ipv6_nexthop_local_cmd,
+ "set ipv6 next-hop local X:X::X:X$addr",
+ SET_STR
+ IPV6_STR
+ "IPv6 next-hop address\n"
+ "IPv6 local address\n"
+ "IPv6 address of next hop\n")
+{
+ const char *xpath =
+ "./set-action[action='frr-route-map:ipv6-next-hop']";
+ char xpath_value[XPATH_MAXLEN];
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
+ snprintf(xpath_value, sizeof(xpath_value),
+ "%s/rmap-set-action/ipv6-address", xpath);
+ nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, addr_str);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ no_set_ipv6_nexthop_local, no_set_ipv6_nexthop_local_cmd,
+ "no set ipv6 next-hop local [X:X::X:X]",
+ NO_STR
+ SET_STR
+ IPV6_STR
+ "IPv6 next-hop address\n"
+ "IPv6 local address\n"
+ "IPv6 address of next hop\n")
+{
+ const char *xpath =
+ "./set-action[action='frr-route-map:ipv6-next-hop']";
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ set_metric, set_metric_cmd,
+ "set metric <(-4294967295-4294967295)$metric|rtt$rtt|+rtt$artt|-rtt$srtt>",
+ SET_STR
+ "Metric value for destination routing protocol\n"
+ "Metric value (use +/- for additions or subtractions)\n"
+ "Assign round trip time\n"
+ "Add round trip time\n"
+ "Subtract round trip time\n")
+{
+ const char *xpath = "./set-action[action='frr-route-map:set-metric']";
+ char xpath_value[XPATH_MAXLEN];
+ char value[64];
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
+ if (rtt) {
+ snprintf(xpath_value, sizeof(xpath_value),
+ "%s/rmap-set-action/use-round-trip-time", xpath);
+ snprintf(value, sizeof(value), "true");
+ } else if (artt) {
+ snprintf(xpath_value, sizeof(xpath_value),
+ "%s/rmap-set-action/add-round-trip-time", xpath);
+ snprintf(value, sizeof(value), "true");
+ } else if (srtt) {
+ snprintf(xpath_value, sizeof(xpath_value),
+ "%s/rmap-set-action/subtract-round-trip-time", xpath);
+ snprintf(value, sizeof(value), "true");
+ } else if (metric_str && metric_str[0] == '+') {
+ snprintf(xpath_value, sizeof(xpath_value),
+ "%s/rmap-set-action/add-metric", xpath);
+ snprintf(value, sizeof(value), "%s", ++metric_str);
+ } else if (metric_str && metric_str[0] == '-') {
+ snprintf(xpath_value, sizeof(xpath_value),
+ "%s/rmap-set-action/subtract-metric", xpath);
+ snprintf(value, sizeof(value), "%s", ++metric_str);
+ } else {
+ snprintf(xpath_value, sizeof(xpath_value),
+ "%s/rmap-set-action/value", xpath);
+ snprintf(value, sizeof(value), "%s", metric_str);
+ }
+ nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, value);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ no_set_metric, no_set_metric_cmd,
+ "no set metric [OPTVAL]",
+ NO_STR
+ SET_STR
+ "Metric value for destination routing protocol\n"
+ "Metric value\n")
+{
+ const char *xpath = "./set-action[action='frr-route-map:set-metric']";
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ set_tag, set_tag_cmd,
+ "set tag (1-4294967295)$tag",
+ SET_STR
+ "Tag value for routing protocol\n"
+ "Tag value\n")
+{
+ const char *xpath = "./set-action[action='frr-route-map:set-tag']";
+ char xpath_value[XPATH_MAXLEN];
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
+ snprintf(xpath_value, sizeof(xpath_value), "%s/rmap-set-action/tag",
+ xpath);
+ nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, tag_str);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ no_set_tag, no_set_tag_cmd,
+ "no set tag [(1-4294967295)]",
+ NO_STR
+ SET_STR
+ "Tag value for routing protocol\n"
+ "Tag value\n")
+{
+ const char *xpath = "./set-action[action='frr-route-map:set-tag']";
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFUN_YANG (set_srte_color,
+ set_srte_color_cmd,
+ "set sr-te color (1-4294967295)",
+ SET_STR
+ SRTE_STR
+ SRTE_COLOR_STR
+ "Color of the SR-TE Policies to match with\n")
+{
+ const char *xpath =
+ "./set-action[action='frr-route-map:set-sr-te-color']";
+ char xpath_value[XPATH_MAXLEN];
+ int idx = 0;
+
+ char *arg = argv_find(argv, argc, "(1-4294967295)", &idx)
+ ? argv[idx]->arg
+ : NULL;
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
+ snprintf(xpath_value, sizeof(xpath_value),
+ "%s/rmap-set-action/policy", xpath);
+ nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, arg);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFUN_YANG (no_set_srte_color,
+ no_set_srte_color_cmd,
+ "no set sr-te color [(1-4294967295)]",
+ NO_STR
+ SET_STR
+ SRTE_STR
+ SRTE_COLOR_STR
+ "Color of the SR-TE Policies to match with\n")
+{
+ const char *xpath =
+ "./set-action[action='frr-route-map:set-sr-te-color']";
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+
+void route_map_action_show(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ const char *action = yang_dnode_get_string(dnode, "./action");
+ const struct lyd_node *ln;
+ const char *acl;
+
+ if (IS_SET_IPv4_NH(action)) {
+ vty_out(vty, " set ip next-hop %s\n",
+ yang_dnode_get_string(
+ dnode, "./rmap-set-action/ipv4-address"));
+ } else if (IS_SET_IPv6_NH(action)) {
+ vty_out(vty, " set ipv6 next-hop local %s\n",
+ yang_dnode_get_string(
+ dnode, "./rmap-set-action/ipv6-address"));
+ } else if (IS_SET_METRIC(action)) {
+ if (yang_dnode_get(dnode,
+ "./rmap-set-action/use-round-trip-time")) {
+ vty_out(vty, " set metric rtt\n");
+ } else if (yang_dnode_get(
+ dnode,
+ "./rmap-set-action/add-round-trip-time")) {
+ vty_out(vty, " set metric +rtt\n");
+ } else if (
+ yang_dnode_get(
+ dnode,
+ "./rmap-set-action/subtract-round-trip-time")) {
+ vty_out(vty, " set metric -rtt\n");
+ } else if (yang_dnode_get(dnode,
+ "./rmap-set-action/add-metric")) {
+ vty_out(vty, " set metric +%s\n",
+ yang_dnode_get_string(
+ dnode, "./rmap-set-action/add-metric"));
+ } else if (yang_dnode_get(
+ dnode,
+ "./rmap-set-action/subtract-metric")) {
+ vty_out(vty, " set metric -%s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-set-action/subtract-metric"));
+ } else {
+ vty_out(vty, " set metric %s\n",
+ yang_dnode_get_string(
+ dnode, "./rmap-set-action/value"));
+ }
+ } else if (IS_SET_TAG(action)) {
+ vty_out(vty, " set tag %s\n",
+ yang_dnode_get_string(dnode, "./rmap-set-action/tag"));
+ } else if (IS_SET_SR_TE_COLOR(action)) {
+ vty_out(vty, " set sr-te color %s\n",
+ yang_dnode_get_string(dnode,
+ "./rmap-set-action/policy"));
+ } else if (IS_SET_SRC(action)) {
+ if (yang_dnode_exists(
+ dnode,
+ "./rmap-set-action/frr-zebra-route-map:ipv4-src-address"))
+ vty_out(vty, " set src %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-set-action/frr-zebra-route-map:ipv4-src-address"));
+ else
+ vty_out(vty, " set src %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-set-action/frr-zebra-route-map:ipv6-src-address"));
+ } else if (IS_SET_METRIC_TYPE(action)) {
+ vty_out(vty, " set metric-type %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-set-action/frr-ospf-route-map:metric-type"));
+ } else if (IS_SET_FORWARDING_ADDR(action)) {
+ vty_out(vty, " set forwarding-address %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-set-action/frr-ospf6-route-map:ipv6-address"));
+ } else if (IS_SET_WEIGHT(action)) {
+ vty_out(vty, " set weight %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:weight"));
+ } else if (IS_SET_TABLE(action)) {
+ vty_out(vty, " set table %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:table"));
+ } else if (IS_SET_LOCAL_PREF(action)) {
+ vty_out(vty, " set local-preference %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:local-pref"));
+ } else if (IS_SET_LABEL_INDEX(action)) {
+ vty_out(vty, " set label-index %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:label-index"));
+ } else if (IS_SET_DISTANCE(action)) {
+ vty_out(vty, " set distance %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:distance"));
+ } else if (IS_SET_ORIGIN(action)) {
+ vty_out(vty, " set origin %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:origin"));
+ } else if (IS_SET_ATOMIC_AGGREGATE(action)) {
+ vty_out(vty, " set atomic-aggregate\n");
+ } else if (IS_SET_ORIGINATOR_ID(action)) {
+ vty_out(vty, " set originator-id %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:originator-id"));
+ } else if (IS_SET_COMM_LIST_DEL(action)) {
+ acl = NULL;
+ if ((ln = yang_dnode_get(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:comm-list-name"))
+ != NULL)
+ acl = yang_dnode_get_string(ln, NULL);
+
+ assert(acl);
+
+ vty_out(vty, " set comm-list %s delete\n", acl);
+ } else if (IS_SET_LCOMM_LIST_DEL(action)) {
+ acl = NULL;
+ if ((ln = yang_dnode_get(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:comm-list-name"))
+ != NULL)
+ acl = yang_dnode_get_string(ln, NULL);
+
+ assert(acl);
+
+ vty_out(vty, " set large-comm-list %s delete\n", acl);
+ } else if (IS_SET_LCOMMUNITY(action)) {
+ if (yang_dnode_exists(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:large-community-string"))
+ vty_out(vty, " set large-community %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:large-community-string"));
+ else {
+ if (true
+ == yang_dnode_get_bool(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:large-community-none"))
+ vty_out(vty, " set large-community none\n");
+ }
+ } else if (IS_SET_COMMUNITY(action)) {
+ if (yang_dnode_exists(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:community-string"))
+ vty_out(vty, " set community %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:community-string"));
+ else {
+ if (true
+ == yang_dnode_get_bool(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:community-none"))
+ vty_out(vty, " set community none\n");
+ }
+ } else if (IS_SET_EXTCOMMUNITY_RT(action)) {
+ vty_out(vty, " set extcommunity rt %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:extcommunity-rt"));
+ } else if (IS_SET_EXTCOMMUNITY_SOO(action)) {
+ vty_out(vty, " set extcommunity soo %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:extcommunity-soo"));
+ } else if (IS_SET_EXTCOMMUNITY_LB(action)) {
+ enum ecommunity_lb_type lb_type;
+ char str[VTY_BUFSIZ];
+ uint16_t bandwidth;
+
+ lb_type = yang_dnode_get_enum(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:extcommunity-lb/lb-type");
+ switch (lb_type) {
+ case EXPLICIT_BANDWIDTH:
+ bandwidth = yang_dnode_get_uint16(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:extcommunity-lb/bandwidth");
+ snprintf(str, sizeof(str), "%d", bandwidth);
+ break;
+ case CUMULATIVE_BANDWIDTH:
+ snprintf(str, sizeof(str), "%s", "cumulative");
+ break;
+ case COMPUTED_BANDWIDTH:
+ snprintf(str, sizeof(str), "%s", "num-multipaths");
+ }
+
+ if (yang_dnode_get_bool(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:extcommunity-lb/two-octet-as-specific"))
+ strlcat(str, " non-transitive", sizeof(str));
+
+ vty_out(vty, " set extcommunity bandwidth %s\n", str);
+ } else if (IS_SET_EXTCOMMUNITY_NONE(action)) {
+ if (yang_dnode_get_bool(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:extcommunity-none"))
+ vty_out(vty, " set extcommunity none\n");
+ } else if (IS_SET_AGGREGATOR(action)) {
+ vty_out(vty, " set aggregator as %s %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:aggregator/aggregator-asn"),
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:aggregator/aggregator-address"));
+ } else if (IS_SET_AS_EXCLUDE(action)) {
+ vty_out(vty, " set as-path exclude %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:exclude-as-path"));
+ } else if (IS_SET_AS_REPLACE(action)) {
+ vty_out(vty, " set as-path replace %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:replace-as-path"));
+ } else if (IS_SET_AS_PREPEND(action)) {
+ if (yang_dnode_exists(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:prepend-as-path"))
+ vty_out(vty, " set as-path prepend %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:prepend-as-path"));
+ else {
+ vty_out(vty, " set as-path prepend last-as %u\n",
+ yang_dnode_get_uint8(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:last-as"));
+ }
+ } else if (IS_SET_IPV6_NH_GLOBAL(action)) {
+ vty_out(vty, " set ipv6 next-hop global %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:ipv6-address"));
+ } else if (IS_SET_IPV6_VPN_NH(action)) {
+ vty_out(vty, " set ipv6 vpn next-hop %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:ipv6-address"));
+ } else if (IS_SET_IPV6_PEER_ADDR(action)) {
+ if (true
+ == yang_dnode_get_bool(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:preference"))
+ vty_out(vty, " set ipv6 next-hop peer-address\n");
+ } else if (IS_SET_IPV6_PREFER_GLOBAL(action)) {
+ if (true
+ == yang_dnode_get_bool(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:preference"))
+ vty_out(vty, " set ipv6 next-hop prefer-global\n");
+ } else if (IS_SET_IPV4_VPN_NH(action)) {
+ vty_out(vty, " set ipv4 vpn next-hop %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:ipv4-address"));
+ } else if (IS_SET_BGP_IPV4_NH(action)) {
+ vty_out(vty, " set ip next-hop %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:ipv4-nexthop"));
+ } else if (IS_SET_BGP_EVPN_GATEWAY_IP_IPV4(action)) {
+ vty_out(vty, " set evpn gateway-ip ipv4 %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:evpn-gateway-ip-ipv4"));
+ } else if (IS_SET_BGP_EVPN_GATEWAY_IP_IPV6(action)) {
+ vty_out(vty, " set evpn gateway-ip ipv6 %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:evpn-gateway-ip-ipv6"));
+ } else if (IS_SET_BGP_L3VPN_NEXTHOP_ENCAPSULATION(action)) {
+ vty_out(vty, " set l3vpn next-hop encapsulation %s\n",
+ yang_dnode_get_string(
+ dnode,
+ "./rmap-set-action/frr-bgp-route-map:l3vpn-nexthop-encapsulation"));
+ }
+}
+
+DEFPY_YANG(
+ rmap_onmatch_next, rmap_onmatch_next_cmd,
+ "on-match next",
+ "Exit policy on matches\n"
+ "Next clause\n")
+{
+ nb_cli_enqueue_change(vty, "./exit-policy", NB_OP_MODIFY, "next");
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ no_rmap_onmatch_next,
+ no_rmap_onmatch_next_cmd,
+ "no on-match next",
+ NO_STR
+ "Exit policy on matches\n"
+ "Next clause\n")
+{
+ nb_cli_enqueue_change(vty, "./exit-policy", NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ rmap_onmatch_goto, rmap_onmatch_goto_cmd,
+ "on-match goto (1-65535)$rm_num",
+ "Exit policy on matches\n"
+ "Goto Clause number\n"
+ "Number\n")
+{
+ nb_cli_enqueue_change(vty, "./exit-policy", NB_OP_MODIFY, "goto");
+ nb_cli_enqueue_change(vty, "./goto-value", NB_OP_MODIFY, rm_num_str);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ no_rmap_onmatch_goto, no_rmap_onmatch_goto_cmd,
+ "no on-match goto",
+ NO_STR
+ "Exit policy on matches\n"
+ "Goto Clause number\n")
+{
+ nb_cli_enqueue_change(vty, "./exit-policy", NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+/* Cisco/GNU Zebra compatibility aliases */
+ALIAS_YANG(
+ rmap_onmatch_goto, rmap_continue_cmd,
+ "continue (1-65535)$rm_num",
+ "Continue on a different entry within the route-map\n"
+ "Route-map entry sequence number\n")
+
+ALIAS_YANG(
+ no_rmap_onmatch_goto, no_rmap_continue_cmd,
+ "no continue [(1-65535)]",
+ NO_STR
+ "Continue on a different entry within the route-map\n"
+ "Route-map entry sequence number\n")
+
+void route_map_exit_policy_show(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ int exit_policy = yang_dnode_get_enum(dnode, NULL);
+
+ switch (exit_policy) {
+ case 0: /* permit-or-deny */
+ /* NOTHING: default option. */
+ break;
+ case 1: /* next */
+ vty_out(vty, " on-match next\n");
+ break;
+ case 2: /* goto */
+ vty_out(vty, " on-match goto %s\n",
+ yang_dnode_get_string(dnode, "../goto-value"));
+ break;
+ }
+}
+
+DEFPY_YANG(
+ rmap_call, rmap_call_cmd,
+ "call WORD$name",
+ "Jump to another Route-Map after match+set\n"
+ "Target route-map name\n")
+{
+ nb_cli_enqueue_change(vty, "./call", NB_OP_MODIFY, name);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ no_rmap_call, no_rmap_call_cmd,
+ "no call [NAME]",
+ NO_STR
+ "Jump to another Route-Map after match+set\n"
+ "Target route-map name\n")
+{
+ nb_cli_enqueue_change(vty, "./call", NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void route_map_call_show(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " call %s\n", yang_dnode_get_string(dnode, NULL));
+}
+
+DEFPY_YANG(
+ rmap_description, rmap_description_cmd,
+ "description LINE...",
+ "Route-map comment\n"
+ "Comment describing this route-map rule\n")
+{
+ char *desc;
+ int rv;
+
+ desc = argv_concat(argv, argc, 1);
+ nb_cli_enqueue_change(vty, "./description", NB_OP_MODIFY, desc);
+ rv = nb_cli_apply_changes(vty, NULL);
+ XFREE(MTYPE_TMP, desc);
+
+ return rv;
+}
+
+DEFUN_YANG (no_rmap_description,
+ no_rmap_description_cmd,
+ "no description",
+ NO_STR
+ "Route-map comment\n")
+{
+ nb_cli_enqueue_change(vty, "./description", NB_OP_DESTROY, NULL);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void route_map_description_show(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " description %s\n", yang_dnode_get_string(dnode, NULL));
+}
+
+DEFPY_YANG(
+ route_map_optimization, route_map_optimization_cmd,
+ "[no] route-map RMAP_NAME$name optimization",
+ NO_STR
+ ROUTE_MAP_CMD_STR
+ "Configure route-map optimization\n")
+{
+ char xpath[XPATH_MAXLEN];
+
+ snprintf(xpath, sizeof(xpath),
+ "/frr-route-map:lib/route-map[name='%s']", name);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
+
+ snprintf(
+ xpath, sizeof(xpath),
+ "/frr-route-map:lib/route-map[name='%s']/optimization-disabled",
+ name);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, no ? "true" : "false");
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void route_map_optimization_disabled_show(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ const char *name = yang_dnode_get_string(dnode, "../name");
+ const bool disabled = yang_dnode_get_bool(dnode, NULL);
+
+ vty_out(vty, "%sroute-map %s optimization\n", disabled ? "no " : "",
+ name);
+}
+
+static int route_map_config_write(struct vty *vty)
+{
+ const struct lyd_node *dnode;
+ int written = 0;
+
+ dnode = yang_dnode_get(running_config->dnode,
+ "/frr-route-map:lib");
+ if (dnode) {
+ nb_cli_show_dnode_cmds(vty, dnode, false);
+ written = 1;
+ }
+
+ return written;
+}
+
+/* Route map node structure. */
+static int route_map_config_write(struct vty *vty);
+static struct cmd_node rmap_node = {
+ .name = "routemap",
+ .node = RMAP_NODE,
+ .parent_node = CONFIG_NODE,
+ .prompt = "%s(config-route-map)# ",
+ .config_write = route_map_config_write,
+};
+
+static void rmap_autocomplete(vector comps, struct cmd_token *token)
+{
+ struct route_map *map;
+
+ for (map = route_map_master.head; map; map = map->next)
+ vector_set(comps, XSTRDUP(MTYPE_COMPLETION, map->name));
+}
+
+static const struct cmd_variable_handler rmap_var_handlers[] = {
+ {.varname = "route_map", .completions = rmap_autocomplete},
+ {.tokenname = "ROUTEMAP_NAME", .completions = rmap_autocomplete},
+ {.tokenname = "RMAP_NAME", .completions = rmap_autocomplete},
+ {.completions = NULL}
+};
+
+void route_map_cli_init(void)
+{
+ /* Auto complete handler. */
+ cmd_variable_handler_register(rmap_var_handlers);
+
+ /* CLI commands. */
+ install_node(&rmap_node);
+ install_default(RMAP_NODE);
+ install_element(CONFIG_NODE, &route_map_cmd);
+ install_element(CONFIG_NODE, &no_route_map_cmd);
+ install_element(CONFIG_NODE, &no_route_map_all_cmd);
+ install_element(CONFIG_NODE, &route_map_optimization_cmd);
+
+ /* Install the on-match stuff */
+ install_element(RMAP_NODE, &rmap_onmatch_next_cmd);
+ install_element(RMAP_NODE, &no_rmap_onmatch_next_cmd);
+ install_element(RMAP_NODE, &rmap_onmatch_goto_cmd);
+ install_element(RMAP_NODE, &no_rmap_onmatch_goto_cmd);
+ install_element(RMAP_NODE, &rmap_continue_cmd);
+ install_element(RMAP_NODE, &no_rmap_continue_cmd);
+
+ /* Install the call stuff. */
+ install_element(RMAP_NODE, &rmap_call_cmd);
+ install_element(RMAP_NODE, &no_rmap_call_cmd);
+
+ /* Install description commands. */
+ install_element(RMAP_NODE, &rmap_description_cmd);
+ install_element(RMAP_NODE, &no_rmap_description_cmd);
+
+ /* Install 'match' commands. */
+ install_element(RMAP_NODE, &match_interface_cmd);
+ install_element(RMAP_NODE, &no_match_interface_cmd);
+
+ install_element(RMAP_NODE, &match_ip_address_cmd);
+ install_element(RMAP_NODE, &no_match_ip_address_cmd);
+
+ install_element(RMAP_NODE, &match_ip_address_prefix_list_cmd);
+ install_element(RMAP_NODE, &no_match_ip_address_prefix_list_cmd);
+
+ install_element(RMAP_NODE, &match_ip_next_hop_cmd);
+ install_element(RMAP_NODE, &no_match_ip_next_hop_cmd);
+
+ install_element(RMAP_NODE, &match_ip_next_hop_prefix_list_cmd);
+ install_element(RMAP_NODE, &no_match_ip_next_hop_prefix_list_cmd);
+
+ install_element(RMAP_NODE, &match_ip_next_hop_type_cmd);
+ install_element(RMAP_NODE, &no_match_ip_next_hop_type_cmd);
+
+ install_element(RMAP_NODE, &match_ipv6_address_cmd);
+ install_element(RMAP_NODE, &no_match_ipv6_address_cmd);
+
+ install_element(RMAP_NODE, &match_ipv6_address_prefix_list_cmd);
+ install_element(RMAP_NODE, &no_match_ipv6_address_prefix_list_cmd);
+
+ install_element(RMAP_NODE, &match_ipv6_next_hop_type_cmd);
+ install_element(RMAP_NODE, &no_match_ipv6_next_hop_type_cmd);
+
+ install_element(RMAP_NODE, &match_metric_cmd);
+ install_element(RMAP_NODE, &no_match_metric_cmd);
+
+ install_element(RMAP_NODE, &match_tag_cmd);
+ install_element(RMAP_NODE, &no_match_tag_cmd);
+
+ /* Install 'set' commands. */
+ install_element(RMAP_NODE, &set_ip_nexthop_cmd);
+ install_element(RMAP_NODE, &no_set_ip_nexthop_cmd);
+
+ install_element(RMAP_NODE, &set_ipv6_nexthop_local_cmd);
+ install_element(RMAP_NODE, &no_set_ipv6_nexthop_local_cmd);
+
+ install_element(RMAP_NODE, &set_metric_cmd);
+ install_element(RMAP_NODE, &no_set_metric_cmd);
+
+ install_element(RMAP_NODE, &set_tag_cmd);
+ install_element(RMAP_NODE, &no_set_tag_cmd);
+
+ install_element(RMAP_NODE, &set_srte_color_cmd);
+ install_element(RMAP_NODE, &no_set_srte_color_cmd);
+}
diff --git a/lib/routemap_northbound.c b/lib/routemap_northbound.c
new file mode 100644
index 0000000..0ccfe98
--- /dev/null
+++ b/lib/routemap_northbound.c
@@ -0,0 +1,1439 @@
+/*
+ * Route map northbound implementation.
+ *
+ * Copyright (C) 2019 Network Device Education Foundation, Inc. ("NetDEF")
+ * Rafael Zalamena
+ *
+ * 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; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301 USA.
+ */
+
+#include <zebra.h>
+
+#include "lib/command.h"
+#include "lib/log.h"
+#include "lib/northbound.h"
+#include "lib/routemap.h"
+
+/*
+ * Auxiliary functions to avoid code duplication:
+ *
+ * lib_route_map_entry_set_destroy: unset `set` commands.
+ * lib_route_map_entry_match_destroy: unset `match` commands.
+ */
+int lib_route_map_entry_match_destroy(struct nb_cb_destroy_args *args)
+{
+ struct routemap_hook_context *rhc;
+ int rv;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ rhc = nb_running_get_entry(args->dnode, NULL, true);
+ if (rhc->rhc_mhook == NULL)
+ return NB_OK;
+
+ rv = rhc->rhc_mhook(rhc->rhc_rmi, rhc->rhc_rule, NULL,
+ rhc->rhc_event,
+ args->errmsg, args->errmsg_len);
+ if (rv != CMD_SUCCESS)
+ return NB_ERR_INCONSISTENCY;
+
+ return NB_OK;
+}
+
+int lib_route_map_entry_set_destroy(struct nb_cb_destroy_args *args)
+{
+ struct routemap_hook_context *rhc;
+ int rv;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ rhc = nb_running_get_entry(args->dnode, NULL, true);
+ if (rhc->rhc_shook == NULL)
+ return NB_OK;
+
+ rv = rhc->rhc_shook(rhc->rhc_rmi, rhc->rhc_rule, NULL,
+ args->errmsg, args->errmsg_len);
+ if (rv != CMD_SUCCESS)
+ return NB_ERR_INCONSISTENCY;
+
+ return NB_OK;
+}
+
+/*
+ * Auxiliary hook context list manipulation functions.
+ */
+struct routemap_hook_context *
+routemap_hook_context_insert(struct route_map_index *rmi)
+{
+ struct routemap_hook_context *rhc;
+
+ rhc = XCALLOC(MTYPE_TMP, sizeof(*rhc));
+ rhc->rhc_rmi = rmi;
+ TAILQ_INSERT_TAIL(&rmi->rhclist, rhc, rhc_entry);
+
+ return rhc;
+}
+
+void routemap_hook_context_free(struct routemap_hook_context *rhc)
+{
+ struct route_map_index *rmi = rhc->rhc_rmi;
+
+ TAILQ_REMOVE(&rmi->rhclist, rhc, rhc_entry);
+ XFREE(MTYPE_TMP, rhc);
+}
+
+/*
+ * XPath: /frr-route-map:lib/route-map
+ */
+static int lib_route_map_create(struct nb_cb_create_args *args)
+{
+ struct route_map *rm;
+ const char *rm_name;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ /* NOTHING */
+ break;
+ case NB_EV_APPLY:
+ rm_name = yang_dnode_get_string(args->dnode, "./name");
+ rm = route_map_get(rm_name);
+ nb_running_set_entry(args->dnode, rm);
+ break;
+ }
+
+ return NB_OK;
+}
+
+static int lib_route_map_destroy(struct nb_cb_destroy_args *args)
+{
+ struct route_map *rm;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ /* NOTHING */
+ break;
+ case NB_EV_APPLY:
+ rm = nb_running_unset_entry(args->dnode);
+ route_map_delete(rm);
+ break;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-route-map:lib/route-map/optimization-disabled
+ */
+static int
+lib_route_map_optimization_disabled_modify(struct nb_cb_modify_args *args)
+{
+ struct route_map *rm;
+ bool disabled = yang_dnode_get_bool(args->dnode, NULL);
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ /* NOTHING */
+ break;
+ case NB_EV_APPLY:
+ rm = nb_running_get_entry(args->dnode, NULL, true);
+ rm->optimization_disabled = disabled;
+ break;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-route-map:lib/route-map/entry
+ */
+static int lib_route_map_entry_create(struct nb_cb_create_args *args)
+{
+ struct route_map_index *rmi;
+ struct route_map *rm;
+ uint16_t sequence;
+ int action;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ /* NOTHING */
+ break;
+ case NB_EV_APPLY:
+ sequence = yang_dnode_get_uint16(args->dnode, "./sequence");
+ action = yang_dnode_get_enum(args->dnode, "./action") == 0
+ ? RMAP_PERMIT
+ : RMAP_DENY;
+ rm = nb_running_get_entry(args->dnode, NULL, true);
+ rmi = route_map_index_get(rm, action, sequence);
+ nb_running_set_entry(args->dnode, rmi);
+ break;
+ }
+
+ return NB_OK;
+}
+
+static int lib_route_map_entry_destroy(struct nb_cb_destroy_args *args)
+{
+ struct route_map_index *rmi;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ /* NOTHING */
+ break;
+ case NB_EV_APPLY:
+ rmi = nb_running_unset_entry(args->dnode);
+ route_map_index_delete(rmi, 1);
+ break;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-route-map:lib/route-map/entry/description
+ */
+static int
+lib_route_map_entry_description_modify(struct nb_cb_modify_args *args)
+{
+ struct route_map_index *rmi;
+ const char *description;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ /* NOTHING */
+ break;
+ case NB_EV_PREPARE:
+ description = yang_dnode_get_string(args->dnode, NULL);
+ args->resource->ptr = XSTRDUP(MTYPE_TMP, description);
+ if (args->resource->ptr == NULL)
+ return NB_ERR_RESOURCE;
+ break;
+ case NB_EV_ABORT:
+ XFREE(MTYPE_TMP, args->resource->ptr);
+ break;
+ case NB_EV_APPLY:
+ rmi = nb_running_get_entry(args->dnode, NULL, true);
+ XFREE(MTYPE_TMP, rmi->description);
+ rmi->description = args->resource->ptr;
+ break;
+ }
+
+ return NB_OK;
+}
+
+static int
+lib_route_map_entry_description_destroy(struct nb_cb_destroy_args *args)
+{
+ struct route_map_index *rmi;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ /* NOTHING */
+ break;
+ case NB_EV_APPLY:
+ rmi = nb_running_get_entry(args->dnode, NULL, true);
+ XFREE(MTYPE_TMP, rmi->description);
+ break;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-route-map:lib/route-map/entry/action
+ */
+static int lib_route_map_entry_action_modify(struct nb_cb_modify_args *args)
+{
+ struct route_map_index *rmi;
+ struct route_map *map;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ /* NOTHING */
+ break;
+ case NB_EV_APPLY:
+ rmi = nb_running_get_entry(args->dnode, NULL, true);
+ rmi->type = yang_dnode_get_enum(args->dnode, NULL);
+ map = rmi->map;
+
+ /* Execute event hook. */
+ if (route_map_master.event_hook) {
+ (*route_map_master.event_hook)(map->name);
+ route_map_notify_dependencies(map->name,
+ RMAP_EVENT_CALL_ADDED);
+ }
+
+ break;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-route-map:lib/route-map/entry/call
+ */
+static int lib_route_map_entry_call_modify(struct nb_cb_modify_args *args)
+{
+ struct route_map_index *rmi;
+ const char *rm_name, *rmn_name;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ rm_name = yang_dnode_get_string(args->dnode, "../../name");
+ rmn_name = yang_dnode_get_string(args->dnode, NULL);
+ /* Don't allow to jump to the same route map instance. */
+ if (strcmp(rm_name, rmn_name) == 0)
+ return NB_ERR_VALIDATION;
+
+ /* TODO: detect circular route map sequences. */
+ break;
+ case NB_EV_PREPARE:
+ rmn_name = yang_dnode_get_string(args->dnode, NULL);
+ args->resource->ptr = XSTRDUP(MTYPE_ROUTE_MAP_NAME, rmn_name);
+ break;
+ case NB_EV_ABORT:
+ XFREE(MTYPE_ROUTE_MAP_NAME, args->resource->ptr);
+ break;
+ case NB_EV_APPLY:
+ rmi = nb_running_get_entry(args->dnode, NULL, true);
+ if (rmi->nextrm) {
+ route_map_upd8_dependency(RMAP_EVENT_CALL_DELETED,
+ rmi->nextrm, rmi->map->name);
+ XFREE(MTYPE_ROUTE_MAP_NAME, rmi->nextrm);
+ }
+ rmi->nextrm = args->resource->ptr;
+ route_map_upd8_dependency(RMAP_EVENT_CALL_ADDED, rmi->nextrm,
+ rmi->map->name);
+ break;
+ }
+
+ return NB_OK;
+}
+
+static int lib_route_map_entry_call_destroy(struct nb_cb_destroy_args *args)
+{
+ struct route_map_index *rmi;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ /* NOTHING */
+ break;
+ case NB_EV_APPLY:
+ rmi = nb_running_get_entry(args->dnode, NULL, true);
+ route_map_upd8_dependency(RMAP_EVENT_CALL_DELETED, rmi->nextrm,
+ rmi->map->name);
+ XFREE(MTYPE_ROUTE_MAP_NAME, rmi->nextrm);
+ rmi->nextrm = NULL;
+ break;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-route-map:lib/route-map/entry/exit-policy
+ */
+static int
+lib_route_map_entry_exit_policy_modify(struct nb_cb_modify_args *args)
+{
+ struct route_map_index *rmi;
+ int rm_action;
+ int policy;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ policy = yang_dnode_get_enum(args->dnode, NULL);
+ switch (policy) {
+ case 0: /* permit-or-deny */
+ break;
+ case 1: /* next */
+ /* FALLTHROUGH */
+ case 2: /* goto */
+ rm_action =
+ yang_dnode_get_enum(args->dnode, "../action");
+ if (rm_action == 1 /* deny */) {
+ /*
+ * On deny it is not possible to 'goto'
+ * anywhere.
+ */
+ return NB_ERR_VALIDATION;
+ }
+ break;
+ }
+ break;
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ break;
+ case NB_EV_APPLY:
+ rmi = nb_running_get_entry(args->dnode, NULL, true);
+ policy = yang_dnode_get_enum(args->dnode, NULL);
+
+ switch (policy) {
+ case 0: /* permit-or-deny */
+ rmi->exitpolicy = RMAP_EXIT;
+ break;
+ case 1: /* next */
+ rmi->exitpolicy = RMAP_NEXT;
+ break;
+ case 2: /* goto */
+ rmi->exitpolicy = RMAP_GOTO;
+ break;
+ }
+ break;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-route-map:lib/route-map/entry/goto-value
+ */
+static int lib_route_map_entry_goto_value_modify(struct nb_cb_modify_args *args)
+{
+ struct route_map_index *rmi;
+ uint16_t rmi_index;
+ uint16_t rmi_next;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ rmi_index = yang_dnode_get_uint16(args->dnode, "../sequence");
+ rmi_next = yang_dnode_get_uint16(args->dnode, NULL);
+ if (rmi_next <= rmi_index) {
+ /* Can't jump backwards on a route map. */
+ return NB_ERR_VALIDATION;
+ }
+ break;
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ /* NOTHING */
+ break;
+ case NB_EV_APPLY:
+ rmi = nb_running_get_entry(args->dnode, NULL, true);
+ rmi->nextpref = yang_dnode_get_uint16(args->dnode, NULL);
+ break;
+ }
+
+ return NB_OK;
+}
+
+static int
+lib_route_map_entry_goto_value_destroy(struct nb_cb_destroy_args *args)
+{
+ struct route_map_index *rmi;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ /* NOTHING */
+ break;
+ case NB_EV_APPLY:
+ rmi = nb_running_get_entry(args->dnode, NULL, true);
+ rmi->nextpref = 0;
+ break;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-route-map:lib/route-map/entry/match-condition
+ */
+static int
+lib_route_map_entry_match_condition_create(struct nb_cb_create_args *args)
+{
+ struct routemap_hook_context *rhc;
+ struct route_map_index *rmi;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ /* NOTHING */
+ break;
+ case NB_EV_APPLY:
+ rmi = nb_running_get_entry(args->dnode, NULL, true);
+ rhc = routemap_hook_context_insert(rmi);
+ nb_running_set_entry(args->dnode, rhc);
+ break;
+ }
+
+ return NB_OK;
+}
+
+static int
+lib_route_map_entry_match_condition_destroy(struct nb_cb_destroy_args *args)
+{
+ struct routemap_hook_context *rhc;
+ int rv;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ rv = lib_route_map_entry_match_destroy(args);
+ rhc = nb_running_unset_entry(args->dnode);
+ routemap_hook_context_free(rhc);
+
+ return rv;
+}
+
+/*
+ * XPath: /frr-route-map:lib/route-map/entry/match-condition/interface
+ */
+static int lib_route_map_entry_match_condition_interface_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct routemap_hook_context *rhc;
+ const char *ifname;
+ int rv;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ /* Check for hook function. */
+ if (rmap_match_set_hook.match_interface == NULL)
+ return NB_OK;
+
+ /* Add configuration. */
+ rhc = nb_running_get_entry(args->dnode, NULL, true);
+ ifname = yang_dnode_get_string(args->dnode, NULL);
+
+ /* Set destroy information. */
+ rhc->rhc_mhook = rmap_match_set_hook.no_match_interface;
+ rhc->rhc_rule = "interface";
+ rhc->rhc_event = RMAP_EVENT_MATCH_DELETED;
+
+ rv = rmap_match_set_hook.match_interface(rhc->rhc_rmi,
+ "interface", ifname,
+ RMAP_EVENT_MATCH_ADDED,
+ args->errmsg, args->errmsg_len);
+ if (rv != CMD_SUCCESS) {
+ rhc->rhc_mhook = NULL;
+ return NB_ERR_INCONSISTENCY;
+ }
+
+ return NB_OK;
+}
+
+static int lib_route_map_entry_match_condition_interface_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ return lib_route_map_entry_match_destroy(args);
+}
+
+/*
+ * XPath: /frr-route-map:lib/route-map/entry/match-condition/list-name
+ */
+static int lib_route_map_entry_match_condition_list_name_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct routemap_hook_context *rhc;
+ const char *acl;
+ const char *condition;
+ int rv;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ /* Check for hook installation, otherwise we can just stop. */
+ acl = yang_dnode_get_string(args->dnode, NULL);
+ rhc = nb_running_get_entry(args->dnode, NULL, true);
+ condition = yang_dnode_get_string(args->dnode, "../../condition");
+
+ if (IS_MATCH_IPv4_ADDRESS_LIST(condition)) {
+ if (rmap_match_set_hook.match_ip_address == NULL)
+ return NB_OK;
+ rhc->rhc_mhook = rmap_match_set_hook.no_match_ip_address;
+ rhc->rhc_rule = "ip address";
+ rhc->rhc_event = RMAP_EVENT_FILTER_DELETED;
+ rv = rmap_match_set_hook.match_ip_address(
+ rhc->rhc_rmi, "ip address", acl,
+ RMAP_EVENT_FILTER_ADDED,
+ args->errmsg, args->errmsg_len);
+ } else if (IS_MATCH_IPv4_PREFIX_LIST(condition)) {
+ if (rmap_match_set_hook.match_ip_address_prefix_list == NULL)
+ return NB_OK;
+ rhc->rhc_mhook =
+ rmap_match_set_hook.no_match_ip_address_prefix_list;
+ rhc->rhc_rule = "ip address prefix-list";
+ rhc->rhc_event = RMAP_EVENT_PLIST_DELETED;
+ rv = rmap_match_set_hook.match_ip_address_prefix_list(
+ rhc->rhc_rmi, "ip address prefix-list", acl,
+ RMAP_EVENT_PLIST_ADDED,
+ args->errmsg, args->errmsg_len);
+ } else if (IS_MATCH_IPv4_NEXTHOP_LIST(condition)) {
+ if (rmap_match_set_hook.match_ip_next_hop == NULL)
+ return NB_OK;
+ rhc->rhc_mhook = rmap_match_set_hook.no_match_ip_next_hop;
+ rhc->rhc_rule = "ip next-hop";
+ rhc->rhc_event = RMAP_EVENT_FILTER_DELETED;
+ rv = rmap_match_set_hook.match_ip_next_hop(
+ rhc->rhc_rmi, "ip next-hop", acl,
+ RMAP_EVENT_FILTER_ADDED,
+ args->errmsg, args->errmsg_len);
+ } else if (IS_MATCH_IPv6_NEXTHOP_LIST(condition)) {
+ if (rmap_match_set_hook.match_ipv6_next_hop == NULL)
+ return NB_OK;
+ rhc->rhc_mhook = rmap_match_set_hook.no_match_ipv6_next_hop;
+ rhc->rhc_rule = "ipv6 next-hop";
+ rhc->rhc_event = RMAP_EVENT_FILTER_DELETED;
+ rv = rmap_match_set_hook.match_ipv6_next_hop(
+ rhc->rhc_rmi, "ipv6 next-hop", acl,
+ RMAP_EVENT_FILTER_ADDED, args->errmsg,
+ args->errmsg_len);
+ } else if (IS_MATCH_IPv4_NEXTHOP_PREFIX_LIST(condition)) {
+ if (rmap_match_set_hook.match_ip_next_hop_prefix_list == NULL)
+ return NB_OK;
+ rhc->rhc_mhook =
+ rmap_match_set_hook.no_match_ip_next_hop_prefix_list;
+ rhc->rhc_rule = "ip next-hop prefix-list";
+ rhc->rhc_event = RMAP_EVENT_PLIST_DELETED;
+ rv = rmap_match_set_hook.match_ip_next_hop_prefix_list(
+ rhc->rhc_rmi, "ip next-hop prefix-list", acl,
+ RMAP_EVENT_PLIST_ADDED,
+ args->errmsg, args->errmsg_len);
+ } else if (IS_MATCH_IPv6_NEXTHOP_PREFIX_LIST(condition)) {
+ if (rmap_match_set_hook.match_ipv6_next_hop_prefix_list == NULL)
+ return NB_OK;
+ rhc->rhc_mhook =
+ rmap_match_set_hook.no_match_ipv6_next_hop_prefix_list;
+ rhc->rhc_rule = "ipv6 next-hop prefix-list";
+ rhc->rhc_event = RMAP_EVENT_PLIST_DELETED;
+ rv = rmap_match_set_hook.match_ipv6_next_hop_prefix_list(
+ rhc->rhc_rmi, "ipv6 next-hop prefix-list", acl,
+ RMAP_EVENT_PLIST_ADDED, args->errmsg, args->errmsg_len);
+ } else if (IS_MATCH_IPv6_ADDRESS_LIST(condition)) {
+ if (rmap_match_set_hook.match_ipv6_address == NULL)
+ return NB_OK;
+ rhc->rhc_mhook = rmap_match_set_hook.no_match_ipv6_address;
+ rhc->rhc_rule = "ipv6 address";
+ rhc->rhc_event = RMAP_EVENT_FILTER_DELETED;
+ rv = rmap_match_set_hook.match_ipv6_address(
+ rhc->rhc_rmi, "ipv6 address", acl,
+ RMAP_EVENT_FILTER_ADDED,
+ args->errmsg, args->errmsg_len);
+ } else if (IS_MATCH_IPv6_PREFIX_LIST(condition)) {
+ if (rmap_match_set_hook.match_ipv6_address_prefix_list == NULL)
+ return NB_OK;
+ rhc->rhc_mhook =
+ rmap_match_set_hook.no_match_ipv6_address_prefix_list;
+ rhc->rhc_rule = "ipv6 address prefix-list";
+ rhc->rhc_event = RMAP_EVENT_PLIST_DELETED;
+ rv = rmap_match_set_hook.match_ipv6_address_prefix_list(
+ rhc->rhc_rmi, "ipv6 address prefix-list", acl,
+ RMAP_EVENT_PLIST_ADDED,
+ args->errmsg, args->errmsg_len);
+ } else
+ rv = CMD_ERR_NO_MATCH;
+
+ if (rv != CMD_SUCCESS) {
+ rhc->rhc_mhook = NULL;
+ return NB_ERR_INCONSISTENCY;
+ }
+
+ return NB_OK;
+}
+
+static int lib_route_map_entry_match_condition_list_name_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ return lib_route_map_entry_match_destroy(args);
+}
+
+/*
+ * XPath: /frr-route-map:lib/route-map/entry/match-condition/ipv4-next-hop-type
+ */
+static int lib_route_map_entry_match_condition_ipv4_next_hop_type_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct routemap_hook_context *rhc;
+ const char *type;
+ int rv;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ /* Check for hook function. */
+ if (rmap_match_set_hook.match_ip_next_hop_type == NULL)
+ return NB_OK;
+
+ /* Add configuration. */
+ rhc = nb_running_get_entry(args->dnode, NULL, true);
+ type = yang_dnode_get_string(args->dnode, NULL);
+
+ /* Set destroy information. */
+ rhc->rhc_mhook = rmap_match_set_hook.no_match_ip_next_hop_type;
+ rhc->rhc_rule = "ip next-hop type";
+ rhc->rhc_event = RMAP_EVENT_MATCH_DELETED;
+
+ rv = rmap_match_set_hook.match_ip_next_hop_type(
+ rhc->rhc_rmi, "ip next-hop type", type,
+ RMAP_EVENT_MATCH_ADDED,
+ args->errmsg, args->errmsg_len);
+ if (rv != CMD_SUCCESS) {
+ rhc->rhc_mhook = NULL;
+ return NB_ERR_INCONSISTENCY;
+ }
+
+ return NB_OK;
+}
+
+static int lib_route_map_entry_match_condition_ipv4_next_hop_type_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ return lib_route_map_entry_match_destroy(args);
+}
+
+/*
+ * XPath: /frr-route-map:lib/route-map/entry/match-condition/ipv6-next-hop-type
+ */
+static int lib_route_map_entry_match_condition_ipv6_next_hop_type_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct routemap_hook_context *rhc;
+ const char *type;
+ int rv;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ /* Check for hook function. */
+ if (rmap_match_set_hook.match_ipv6_next_hop_type == NULL)
+ return NB_OK;
+
+ /* Add configuration. */
+ rhc = nb_running_get_entry(args->dnode, NULL, true);
+ type = yang_dnode_get_string(args->dnode, NULL);
+
+ /* Set destroy information. */
+ rhc->rhc_mhook = rmap_match_set_hook.no_match_ipv6_next_hop_type;
+ rhc->rhc_rule = "ipv6 next-hop type";
+ rhc->rhc_event = RMAP_EVENT_MATCH_DELETED;
+
+ rv = rmap_match_set_hook.match_ipv6_next_hop_type(
+ rhc->rhc_rmi, "ipv6 next-hop type", type,
+ RMAP_EVENT_MATCH_ADDED,
+ args->errmsg, args->errmsg_len);
+ if (rv != CMD_SUCCESS) {
+ rhc->rhc_mhook = NULL;
+ return NB_ERR_INCONSISTENCY;
+ }
+
+ return NB_OK;
+}
+
+static int lib_route_map_entry_match_condition_ipv6_next_hop_type_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ return lib_route_map_entry_match_destroy(args);
+}
+
+/*
+ * XPath: /frr-route-map:lib/route-map/entry/match-condition/metric
+ */
+static int lib_route_map_entry_match_condition_metric_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct routemap_hook_context *rhc;
+ const char *type;
+ int rv;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ /* Check for hook function. */
+ if (rmap_match_set_hook.match_metric == NULL)
+ return NB_OK;
+
+ /* Add configuration. */
+ rhc = nb_running_get_entry(args->dnode, NULL, true);
+ type = yang_dnode_get_string(args->dnode, NULL);
+
+ /* Set destroy information. */
+ rhc->rhc_mhook = rmap_match_set_hook.no_match_metric;
+ rhc->rhc_rule = "metric";
+ rhc->rhc_event = RMAP_EVENT_MATCH_DELETED;
+
+ rv = rmap_match_set_hook.match_metric(rhc->rhc_rmi, "metric",
+ type, RMAP_EVENT_MATCH_ADDED,
+ args->errmsg, args->errmsg_len);
+ if (rv != CMD_SUCCESS) {
+ rhc->rhc_mhook = NULL;
+ return NB_ERR_INCONSISTENCY;
+ }
+
+ return NB_OK;
+}
+
+static int lib_route_map_entry_match_condition_metric_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ return lib_route_map_entry_match_destroy(args);
+}
+
+/*
+ * XPath: /frr-route-map:lib/route-map/entry/match-condition/tag
+ */
+static int
+lib_route_map_entry_match_condition_tag_modify(struct nb_cb_modify_args *args)
+{
+ struct routemap_hook_context *rhc;
+ const char *tag;
+ int rv;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ /* Check for hook function. */
+ if (rmap_match_set_hook.match_tag == NULL)
+ return NB_OK;
+
+ /* Add configuration. */
+ rhc = nb_running_get_entry(args->dnode, NULL, true);
+ tag = yang_dnode_get_string(args->dnode, NULL);
+
+ /* Set destroy information. */
+ rhc->rhc_mhook = rmap_match_set_hook.no_match_tag;
+ rhc->rhc_rule = "tag";
+ rhc->rhc_event = RMAP_EVENT_MATCH_DELETED;
+
+ rv = rmap_match_set_hook.match_tag(rhc->rhc_rmi, "tag", tag,
+ RMAP_EVENT_MATCH_ADDED,
+ args->errmsg, args->errmsg_len);
+ if (rv != CMD_SUCCESS) {
+ rhc->rhc_mhook = NULL;
+ return NB_ERR_INCONSISTENCY;
+ }
+
+ return NB_OK;
+}
+
+static int
+lib_route_map_entry_match_condition_tag_destroy(struct nb_cb_destroy_args *args)
+{
+ return lib_route_map_entry_match_destroy(args);
+}
+
+/*
+ * XPath: /frr-route-map:lib/route-map/entry/set-action
+ */
+static int lib_route_map_entry_set_action_create(struct nb_cb_create_args *args)
+{
+ return lib_route_map_entry_match_condition_create(args);
+}
+
+static int
+lib_route_map_entry_set_action_destroy(struct nb_cb_destroy_args *args)
+{
+ struct routemap_hook_context *rhc;
+ int rv;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ rv = lib_route_map_entry_set_destroy(args);
+ rhc = nb_running_unset_entry(args->dnode);
+ routemap_hook_context_free(rhc);
+
+ return rv;
+}
+
+/*
+ * XPath: /frr-route-map:lib/route-map/entry/set-action/ipv4-address
+ */
+static int lib_route_map_entry_set_action_ipv4_address_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct routemap_hook_context *rhc;
+ const char *address;
+ struct in_addr ia;
+ int rv;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ /*
+ * NOTE: validate if 'action' is 'ipv4-next-hop',
+ * currently it is not necessary because this is the
+ * only implemented action.
+ */
+ yang_dnode_get_ipv4(&ia, args->dnode, NULL);
+ if (ia.s_addr == INADDR_ANY || !ipv4_unicast_valid(&ia))
+ return NB_ERR_VALIDATION;
+ /* FALLTHROUGH */
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ return NB_OK;
+ case NB_EV_APPLY:
+ break;
+ }
+
+ /* Check for hook function. */
+ if (rmap_match_set_hook.set_ip_nexthop == NULL)
+ return NB_OK;
+
+ /* Add configuration. */
+ rhc = nb_running_get_entry(args->dnode, NULL, true);
+ address = yang_dnode_get_string(args->dnode, NULL);
+
+ /* Set destroy information. */
+ rhc->rhc_shook = rmap_match_set_hook.no_set_ip_nexthop;
+ rhc->rhc_rule = "ip next-hop";
+
+ rv = rmap_match_set_hook.set_ip_nexthop(rhc->rhc_rmi, "ip next-hop",
+ address,
+ args->errmsg, args->errmsg_len);
+ if (rv != CMD_SUCCESS) {
+ rhc->rhc_shook = NULL;
+ return NB_ERR_INCONSISTENCY;
+ }
+
+ return NB_OK;
+}
+
+static int lib_route_map_entry_set_action_ipv4_address_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ return lib_route_map_entry_set_destroy(args);
+}
+
+/*
+ * XPath: /frr-route-map:lib/route-map/entry/set-action/ipv6-address
+ */
+static int lib_route_map_entry_set_action_ipv6_address_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct routemap_hook_context *rhc;
+ const char *address;
+ struct in6_addr i6a;
+ int rv;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ /*
+ * NOTE: validate if 'action' is 'ipv6-next-hop',
+ * currently it is not necessary because this is the
+ * only implemented action. Other actions might have
+ * different validations.
+ */
+ yang_dnode_get_ipv6(&i6a, args->dnode, NULL);
+ if (!IN6_IS_ADDR_LINKLOCAL(&i6a))
+ return NB_ERR_VALIDATION;
+ /* FALLTHROUGH */
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ return NB_OK;
+ case NB_EV_APPLY:
+ break;
+ }
+
+ /* Check for hook function. */
+ if (rmap_match_set_hook.set_ipv6_nexthop_local == NULL)
+ return NB_OK;
+
+ /* Add configuration. */
+ rhc = nb_running_get_entry(args->dnode, NULL, true);
+ address = yang_dnode_get_string(args->dnode, NULL);
+
+ /* Set destroy information. */
+ rhc->rhc_shook = rmap_match_set_hook.no_set_ipv6_nexthop_local;
+ rhc->rhc_rule = "ipv6 next-hop local";
+
+ rv = rmap_match_set_hook.set_ipv6_nexthop_local(
+ rhc->rhc_rmi, "ipv6 next-hop local", address,
+ args->errmsg, args->errmsg_len);
+ if (rv != CMD_SUCCESS) {
+ rhc->rhc_shook = NULL;
+ return NB_ERR_INCONSISTENCY;
+ }
+
+ return NB_OK;
+}
+
+static int lib_route_map_entry_set_action_ipv6_address_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ return lib_route_map_entry_set_destroy(args);
+}
+
+/*
+ * XPath: /frr-route-map:lib/route-map/entry/set-action/value
+ */
+static int set_action_modify(enum nb_event event, const struct lyd_node *dnode,
+ union nb_resource *resource, const char *value,
+ char *errmsg, size_t errmsg_len)
+{
+ struct routemap_hook_context *rhc;
+ int rv;
+
+ /*
+ * NOTE: validate if 'action' is 'metric', currently it is not
+ * necessary because this is the only implemented action. Other
+ * actions might have different validations.
+ */
+ if (event != NB_EV_APPLY)
+ return NB_OK;
+
+ /* Check for hook function. */
+ if (rmap_match_set_hook.set_metric == NULL)
+ return NB_OK;
+
+ /* Add configuration. */
+ rhc = nb_running_get_entry(dnode, NULL, true);
+
+ /* Set destroy information. */
+ rhc->rhc_shook = rmap_match_set_hook.no_set_metric;
+ rhc->rhc_rule = "metric";
+
+ rv = rmap_match_set_hook.set_metric(rhc->rhc_rmi, "metric",
+ value,
+ errmsg, errmsg_len
+ );
+ if (rv != CMD_SUCCESS) {
+ rhc->rhc_shook = NULL;
+ return NB_ERR_INCONSISTENCY;
+ }
+
+ return NB_OK;
+}
+
+static int
+lib_route_map_entry_set_action_value_modify(struct nb_cb_modify_args *args)
+{
+ const char *metric = yang_dnode_get_string(args->dnode, NULL);
+
+ return set_action_modify(args->event, args->dnode, args->resource,
+ metric, args->errmsg, args->errmsg_len);
+}
+
+static int
+lib_route_map_entry_set_action_value_destroy(struct nb_cb_destroy_args *args)
+{
+ return lib_route_map_entry_set_destroy(args);
+}
+
+/*
+ * XPath: /frr-route-map:lib/route-map/entry/set-action/add-metric
+ */
+static int
+lib_route_map_entry_set_action_add_metric_modify(struct nb_cb_modify_args *args)
+{
+ char metric_str[16];
+
+ if (args->event == NB_EV_VALIDATE
+ && yang_dnode_get_uint32(args->dnode, NULL) == 0) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "Can't add zero to metric");
+ return NB_ERR_VALIDATION;
+ }
+
+ snprintf(metric_str, sizeof(metric_str), "+%s",
+ yang_dnode_get_string(args->dnode, NULL));
+ return set_action_modify(args->event, args->dnode, args->resource,
+ metric_str,
+ args->errmsg, args->errmsg_len);
+}
+
+static int lib_route_map_entry_set_action_add_metric_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ return lib_route_map_entry_set_action_value_destroy(args);
+}
+
+/*
+ * XPath: /frr-route-map:lib/route-map/entry/set-action/subtract-metric
+ */
+static int lib_route_map_entry_set_action_subtract_metric_modify(
+ struct nb_cb_modify_args *args)
+{
+ char metric_str[16];
+
+ if (args->event == NB_EV_VALIDATE
+ && yang_dnode_get_uint32(args->dnode, NULL) == 0) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "Can't subtract zero from metric");
+ return NB_ERR_VALIDATION;
+ }
+
+ snprintf(metric_str, sizeof(metric_str), "-%s",
+ yang_dnode_get_string(args->dnode, NULL));
+ return set_action_modify(args->event, args->dnode, args->resource,
+ metric_str,
+ args->errmsg, args->errmsg_len);
+}
+
+static int lib_route_map_entry_set_action_subtract_metric_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ return lib_route_map_entry_set_action_value_destroy(args);
+}
+
+/*
+ * XPath: /frr-route-map:lib/route-map/entry/set-action/use-round-trip-time
+ */
+static int lib_route_map_entry_set_action_use_round_trip_time_modify(
+ struct nb_cb_modify_args *args)
+{
+ return set_action_modify(args->event, args->dnode, args->resource,
+ "rtt",
+ args->errmsg, args->errmsg_len);
+}
+
+static int lib_route_map_entry_set_action_use_round_trip_time_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ return lib_route_map_entry_set_action_value_destroy(args);
+}
+
+/*
+ * XPath: /frr-route-map:lib/route-map/entry/set-action/add-round-trip-time
+ */
+static int lib_route_map_entry_set_action_add_round_trip_time_modify(
+ struct nb_cb_modify_args *args)
+{
+ return set_action_modify(args->event, args->dnode, args->resource,
+ "+rtt",
+ args->errmsg, args->errmsg_len);
+}
+
+static int lib_route_map_entry_set_action_add_round_trip_time_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ return lib_route_map_entry_set_action_value_destroy(args);
+}
+
+/*
+ * XPath: /frr-route-map:lib/route-map/entry/set-action/subtract-round-trip-time
+ */
+static int lib_route_map_entry_set_action_subtract_round_trip_time_modify(
+ struct nb_cb_modify_args *args)
+{
+ return set_action_modify(args->event, args->dnode, args->resource,
+ "-rtt", args->errmsg, args->errmsg_len);
+}
+
+static int lib_route_map_entry_set_action_subtract_round_trip_time_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ return lib_route_map_entry_set_action_value_destroy(args);
+}
+
+/*
+ * XPath: /frr-route-map:lib/route-map/entry/set-action/tag
+ */
+static int
+lib_route_map_entry_set_action_tag_modify(struct nb_cb_modify_args *args)
+{
+ struct routemap_hook_context *rhc;
+ const char *tag;
+ int rv;
+
+ /*
+ * NOTE: validate if 'action' is 'tag', currently it is not
+ * necessary because this is the only implemented action. Other
+ * actions might have different validations.
+ */
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ /* Check for hook function. */
+ if (rmap_match_set_hook.set_tag == NULL)
+ return NB_OK;
+
+ /* Add configuration. */
+ rhc = nb_running_get_entry(args->dnode, NULL, true);
+ tag = yang_dnode_get_string(args->dnode, NULL);
+
+ /* Set destroy information. */
+ rhc->rhc_shook = rmap_match_set_hook.no_set_tag;
+ rhc->rhc_rule = "tag";
+
+ rv = rmap_match_set_hook.set_tag(rhc->rhc_rmi, "tag", tag,
+ args->errmsg, args->errmsg_len);
+ if (rv != CMD_SUCCESS) {
+ rhc->rhc_shook = NULL;
+ return NB_ERR_INCONSISTENCY;
+ }
+
+ return NB_OK;
+}
+
+static int
+lib_route_map_entry_set_action_tag_destroy(struct nb_cb_destroy_args *args)
+{
+ return lib_route_map_entry_set_destroy(args);
+}
+
+/*
+ * XPath: /frr-route-map:lib/route-map/entry/set-action/policy
+ */
+static int
+lib_route_map_entry_set_action_policy_modify(struct nb_cb_modify_args *args)
+{
+ struct routemap_hook_context *rhc;
+ const char *policy;
+ int rv;
+
+ /*
+ * NOTE: validate if 'action' is 'tag', currently it is not
+ * necessary because this is the only implemented action. Other
+ * actions might have different validations.
+ */
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ /* Check for hook function. */
+ if (rmap_match_set_hook.set_srte_color == NULL)
+ return NB_OK;
+
+ /* Add configuration. */
+ rhc = nb_running_get_entry(args->dnode, NULL, true);
+ policy = yang_dnode_get_string(args->dnode, NULL);
+
+ /* Set destroy information. */
+ rhc->rhc_shook = rmap_match_set_hook.no_set_tag;
+ rhc->rhc_rule = "sr-te color";
+
+ rv = rmap_match_set_hook.set_tag(rhc->rhc_rmi, "sr-te color", policy,
+ args->errmsg, args->errmsg_len);
+ if (rv != CMD_SUCCESS) {
+ rhc->rhc_shook = NULL;
+ return NB_ERR_INCONSISTENCY;
+ }
+
+ return NB_OK;
+}
+
+static int
+lib_route_map_entry_set_action_policy_destroy(struct nb_cb_destroy_args *args)
+{
+ return lib_route_map_entry_set_destroy(args);
+}
+
+/* clang-format off */
+const struct frr_yang_module_info frr_route_map_info = {
+ .name = "frr-route-map",
+ .nodes = {
+ {
+ .xpath = "/frr-route-map:lib/route-map",
+ .cbs = {
+ .create = lib_route_map_create,
+ .destroy = lib_route_map_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-route-map:lib/route-map/optimization-disabled",
+ .cbs = {
+ .modify = lib_route_map_optimization_disabled_modify,
+ .cli_show = route_map_optimization_disabled_show,
+ }
+ },
+ {
+ .xpath = "/frr-route-map:lib/route-map/entry",
+ .cbs = {
+ .create = lib_route_map_entry_create,
+ .destroy = lib_route_map_entry_destroy,
+ .cli_cmp = route_map_instance_cmp,
+ .cli_show = route_map_instance_show,
+ .cli_show_end = route_map_instance_show_end,
+ }
+ },
+ {
+ .xpath = "/frr-route-map:lib/route-map/entry/description",
+ .cbs = {
+ .modify = lib_route_map_entry_description_modify,
+ .destroy = lib_route_map_entry_description_destroy,
+ .cli_show = route_map_description_show,
+ }
+ },
+ {
+ .xpath = "/frr-route-map:lib/route-map/entry/action",
+ .cbs = {
+ .modify = lib_route_map_entry_action_modify,
+ }
+ },
+ {
+ .xpath = "/frr-route-map:lib/route-map/entry/call",
+ .cbs = {
+ .modify = lib_route_map_entry_call_modify,
+ .destroy = lib_route_map_entry_call_destroy,
+ .cli_show = route_map_call_show,
+ }
+ },
+ {
+ .xpath = "/frr-route-map:lib/route-map/entry/exit-policy",
+ .cbs = {
+ .modify = lib_route_map_entry_exit_policy_modify,
+ .cli_show = route_map_exit_policy_show,
+ }
+ },
+ {
+ .xpath = "/frr-route-map:lib/route-map/entry/goto-value",
+ .cbs = {
+ .modify = lib_route_map_entry_goto_value_modify,
+ .destroy = lib_route_map_entry_goto_value_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-route-map:lib/route-map/entry/match-condition",
+ .cbs = {
+ .create = lib_route_map_entry_match_condition_create,
+ .destroy = lib_route_map_entry_match_condition_destroy,
+ .cli_show = route_map_condition_show,
+ }
+ },
+ {
+ .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/interface",
+ .cbs = {
+ .modify = lib_route_map_entry_match_condition_interface_modify,
+ .destroy = lib_route_map_entry_match_condition_interface_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/list-name",
+ .cbs = {
+ .modify = lib_route_map_entry_match_condition_list_name_modify,
+ .destroy = lib_route_map_entry_match_condition_list_name_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/ipv4-next-hop-type",
+ .cbs = {
+ .modify = lib_route_map_entry_match_condition_ipv4_next_hop_type_modify,
+ .destroy = lib_route_map_entry_match_condition_ipv4_next_hop_type_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/ipv6-next-hop-type",
+ .cbs = {
+ .modify = lib_route_map_entry_match_condition_ipv6_next_hop_type_modify,
+ .destroy = lib_route_map_entry_match_condition_ipv6_next_hop_type_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/metric",
+ .cbs = {
+ .modify = lib_route_map_entry_match_condition_metric_modify,
+ .destroy = lib_route_map_entry_match_condition_metric_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/tag",
+ .cbs = {
+ .modify = lib_route_map_entry_match_condition_tag_modify,
+ .destroy = lib_route_map_entry_match_condition_tag_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-route-map:lib/route-map/entry/set-action",
+ .cbs = {
+ .create = lib_route_map_entry_set_action_create,
+ .destroy = lib_route_map_entry_set_action_destroy,
+ .cli_show = route_map_action_show,
+ }
+ },
+ {
+ .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/ipv4-address",
+ .cbs = {
+ .modify = lib_route_map_entry_set_action_ipv4_address_modify,
+ .destroy = lib_route_map_entry_set_action_ipv4_address_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/ipv6-address",
+ .cbs = {
+ .modify = lib_route_map_entry_set_action_ipv6_address_modify,
+ .destroy = lib_route_map_entry_set_action_ipv6_address_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/value",
+ .cbs = {
+ .modify = lib_route_map_entry_set_action_value_modify,
+ .destroy = lib_route_map_entry_set_action_value_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/add-metric",
+ .cbs = {
+ .modify = lib_route_map_entry_set_action_add_metric_modify,
+ .destroy = lib_route_map_entry_set_action_add_metric_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/subtract-metric",
+ .cbs = {
+ .modify = lib_route_map_entry_set_action_subtract_metric_modify,
+ .destroy = lib_route_map_entry_set_action_subtract_metric_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/use-round-trip-time",
+ .cbs = {
+ .modify = lib_route_map_entry_set_action_use_round_trip_time_modify,
+ .destroy = lib_route_map_entry_set_action_use_round_trip_time_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/add-round-trip-time",
+ .cbs = {
+ .modify = lib_route_map_entry_set_action_add_round_trip_time_modify,
+ .destroy = lib_route_map_entry_set_action_add_round_trip_time_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/subtract-round-trip-time",
+ .cbs = {
+ .modify = lib_route_map_entry_set_action_subtract_round_trip_time_modify,
+ .destroy = lib_route_map_entry_set_action_subtract_round_trip_time_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/tag",
+ .cbs = {
+ .modify = lib_route_map_entry_set_action_tag_modify,
+ .destroy = lib_route_map_entry_set_action_tag_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/policy",
+ .cbs = {
+ .modify = lib_route_map_entry_set_action_policy_modify,
+ .destroy = lib_route_map_entry_set_action_policy_destroy,
+ }
+ },
+
+ {
+ .xpath = NULL,
+ },
+ }
+};
diff --git a/lib/routing_nb.c b/lib/routing_nb.c
new file mode 100644
index 0000000..6238fb0
--- /dev/null
+++ b/lib/routing_nb.c
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 Vmware
+ * Vishal Dhingra
+ *
+ * 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 "northbound.h"
+#include "libfrr.h"
+#include "routing_nb.h"
+
+
+
+/* clang-format off */
+const struct frr_yang_module_info frr_routing_info = {
+ .name = "frr-routing",
+ .nodes = {
+ {
+ .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol",
+ .cbs = {
+ .create = routing_control_plane_protocols_control_plane_protocol_create,
+ .destroy = routing_control_plane_protocols_control_plane_protocol_destroy,
+ }
+ },
+ {
+ .xpath = NULL,
+ },
+ }
+};
diff --git a/lib/routing_nb.h b/lib/routing_nb.h
new file mode 100644
index 0000000..c185091
--- /dev/null
+++ b/lib/routing_nb.h
@@ -0,0 +1,39 @@
+#ifndef _FRR_ROUTING_NB_H_
+#define _FRR_ROUTING_NB_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern const struct frr_yang_module_info frr_routing_info;
+
+/* Mandatory callbacks. */
+int routing_control_plane_protocols_control_plane_protocol_create(
+ struct nb_cb_create_args *args);
+int routing_control_plane_protocols_control_plane_protocol_destroy(
+ struct nb_cb_destroy_args *args);
+
+#define FRR_ROUTING_XPATH \
+ "/frr-routing:routing/control-plane-protocols/control-plane-protocol"
+
+#define FRR_ROUTING_KEY_XPATH \
+ "/frr-routing:routing/control-plane-protocols/" \
+ "control-plane-protocol[type='%s'][name='%s'][vrf='%s']"
+
+#define FRR_ROUTING_KEY_XPATH_VRF \
+ "/frr-routing:routing/control-plane-protocols/" \
+ "control-plane-protocol[vrf='%s']"
+
+/*
+ * callbacks for routing to handle configuration events
+ * based on the control plane protocol
+ */
+DECLARE_HOOK(routing_conf_event, (struct nb_cb_create_args *args), (args));
+
+void routing_control_plane_protocols_register_vrf_dependency(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRR_ROUTING_NB_H_ */
diff --git a/lib/routing_nb_config.c b/lib/routing_nb_config.c
new file mode 100644
index 0000000..594ad6c
--- /dev/null
+++ b/lib/routing_nb_config.c
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2018 Vmware
+ * Vishal Dhingra
+ *
+ * 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 "northbound.h"
+#include "libfrr.h"
+#include "vrf.h"
+#include "lib_errors.h"
+#include "routing_nb.h"
+
+
+DEFINE_HOOK(routing_conf_event, (struct nb_cb_create_args *args), (args));
+
+/*
+ * XPath: /frr-routing:routing/control-plane-protocols/control-plane-protocol
+ */
+
+int routing_control_plane_protocols_control_plane_protocol_create(
+ struct nb_cb_create_args *args)
+{
+ struct vrf *vrf;
+ const char *vrfname;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ if (hook_call(routing_conf_event, args))
+ return NB_ERR_VALIDATION;
+ break;
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ break;
+ case NB_EV_APPLY:
+ /*
+ * If the daemon relies on the VRF pointer stored in this
+ * dnode, then it should register the dependency between this
+ * module and the VRF module using
+ * routing_control_plane_protocols_register_vrf_dependency.
+ * If such dependency is not registered, then nothing is
+ * stored in the dnode. If the dependency is registered,
+ * find the vrf and store the pointer.
+ */
+ if (nb_node_has_dependency(args->dnode->schema->priv)) {
+ vrfname = yang_dnode_get_string(args->dnode, "./vrf");
+ vrf = vrf_lookup_by_name(vrfname);
+ assert(vrf);
+ nb_running_set_entry(args->dnode, vrf);
+ }
+ break;
+ };
+
+ return NB_OK;
+}
+
+int routing_control_plane_protocols_control_plane_protocol_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ /*
+ * If dependency on VRF module is registered, then VRF
+ * pointer was stored and must be cleared.
+ */
+ if (nb_node_has_dependency(args->dnode->schema->priv))
+ nb_running_unset_entry(args->dnode);
+
+ return NB_OK;
+}
+
+static void vrf_to_control_plane_protocol(const struct lyd_node *dnode,
+ char *xpath)
+{
+ const char *vrf;
+
+ vrf = yang_dnode_get_string(dnode, "./name");
+
+ snprintf(xpath, XPATH_MAXLEN, FRR_ROUTING_KEY_XPATH_VRF, vrf);
+}
+
+static void control_plane_protocol_to_vrf(const struct lyd_node *dnode,
+ char *xpath)
+{
+ const char *vrf;
+
+ vrf = yang_dnode_get_string(dnode, "./vrf");
+
+ snprintf(xpath, XPATH_MAXLEN, FRR_VRF_KEY_XPATH, vrf);
+}
+
+void routing_control_plane_protocols_register_vrf_dependency(void)
+{
+ struct nb_dependency_callbacks cbs;
+
+ cbs.get_dependant_xpath = vrf_to_control_plane_protocol;
+ cbs.get_dependency_xpath = control_plane_protocol_to_vrf;
+
+ nb_node_set_dependency_cbs(FRR_VRF_XPATH, FRR_ROUTING_XPATH, &cbs);
+}
diff --git a/lib/sbuf.c b/lib/sbuf.c
new file mode 100644
index 0000000..c04af15
--- /dev/null
+++ b/lib/sbuf.c
@@ -0,0 +1,107 @@
+/*
+ * Simple string buffer
+ *
+ * Copyright (C) 2017 Christian Franke
+ *
+ * This file is part of FRR.
+ *
+ * FRR 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, or (at your option) any
+ * later version.
+ *
+ * FRR 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 FRR; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+#include <zebra.h>
+
+#include "printfrr.h"
+#include "sbuf.h"
+#include "memory.h"
+
+void sbuf_init(struct sbuf *dest, char *buf, size_t size)
+{
+ dest->fixed = (size > 0);
+ if (dest->fixed) {
+ dest->buf = buf;
+ dest->size = size;
+ } else {
+ dest->buf = XMALLOC(MTYPE_TMP, 4096);
+ dest->size = 4096;
+ }
+
+ dest->pos = 0;
+ dest->buf[0] = '\0';
+}
+
+void sbuf_reset(struct sbuf *dest)
+{
+ dest->pos = 0;
+ dest->buf[0] = '\0';
+}
+
+const char *sbuf_buf(struct sbuf *buf)
+{
+ return buf->buf;
+}
+
+void sbuf_free(struct sbuf *buf)
+{
+ if (!buf->fixed)
+ XFREE(MTYPE_TMP, buf->buf);
+}
+
+void sbuf_push(struct sbuf *buf, int indent, const char *format, ...)
+{
+ va_list args;
+ int written;
+
+ if (!buf->fixed) {
+ int written1, written2;
+ size_t new_size;
+
+ written1 = indent;
+ va_start(args, format);
+ written2 = vsnprintfrr(NULL, 0, format, args);
+ va_end(args);
+
+ new_size = buf->size;
+ if (written1 >= 0 && written2 >= 0) {
+ while (buf->pos + written1 + written2 >= new_size)
+ new_size *= 2;
+ if (new_size > buf->size) {
+ buf->buf =
+ XREALLOC(MTYPE_TMP, buf->buf, new_size);
+ buf->size = new_size;
+ }
+ }
+ }
+
+ written = snprintf(buf->buf + buf->pos, buf->size - buf->pos, "%*s",
+ indent, "");
+
+ if (written >= 0)
+ buf->pos += written;
+ if (buf->pos > buf->size)
+ buf->pos = buf->size;
+
+ va_start(args, format);
+ written = vsnprintfrr(buf->buf + buf->pos, buf->size - buf->pos,
+ format, args);
+ va_end(args);
+
+ if (written >= 0)
+ buf->pos += written;
+ if (buf->pos > buf->size)
+ buf->pos = buf->size;
+
+ if (buf->pos == buf->size)
+ assert(!"Buffer filled up!");
+}
diff --git a/lib/sbuf.h b/lib/sbuf.h
new file mode 100644
index 0000000..aaa2db0
--- /dev/null
+++ b/lib/sbuf.h
@@ -0,0 +1,87 @@
+/*
+ * Simple string buffer
+ *
+ * Copyright (C) 2017 Christian Franke
+ *
+ * This file is part of FRR.
+ *
+ * FRR 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, or (at your option) any
+ * later version.
+ *
+ * FRR 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 FRR; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+#ifndef SBUF_H
+#define SBUF_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * sbuf provides a simple string buffer. One application where this comes
+ * in handy is the parsing of binary data: If there is an error in the parsing
+ * process due to invalid input data, printing an error message explaining what
+ * went wrong is definitely useful. However, just printing the actual error,
+ * without any information about the previous parsing steps, is usually not very
+ * helpful.
+ * Using sbuf, the parser can log the whole parsing process into a buffer using
+ * a printf like API. When an error occurs, all the information about previous
+ * parsing steps is there in the log, without any need for backtracking, and can
+ * be used to give a detailed and useful error description.
+ * When parsing completes successfully without any error, the log can just be
+ * discarded unless debugging is turned on, to not spam the log.
+ *
+ * For the described usecase, the code would look something like this:
+ *
+ * int sbuf_example(..., char **parser_log)
+ * {
+ * struct sbuf logbuf;
+ *
+ * sbuf_init(&logbuf, NULL, 0);
+ * sbuf_push(&logbuf, 0, "Starting parser\n");
+ *
+ * int rv = do_parse(&logbuf, ...);
+ *
+ * *parser_log = sbuf_buf(&logbuf);
+ *
+ * return 1;
+ * }
+ *
+ * In this case, sbuf_example uses a string buffer with undefined size, which
+ * will
+ * be allocated on the heap by sbuf. The caller of sbuf_example is expected to
+ * free
+ * the string returned in parser_log.
+ */
+
+struct sbuf {
+ bool fixed;
+ char *buf;
+ size_t size;
+ size_t pos;
+ int indent;
+};
+
+void sbuf_init(struct sbuf *dest, char *buf, size_t size);
+void sbuf_reset(struct sbuf *buf);
+const char *sbuf_buf(struct sbuf *buf);
+void sbuf_free(struct sbuf *buf);
+#include "lib/log.h"
+void sbuf_push(struct sbuf *buf, int indent, const char *format, ...)
+ PRINTFRR(3, 4);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/seqlock.c b/lib/seqlock.c
new file mode 100644
index 0000000..7767314
--- /dev/null
+++ b/lib/seqlock.c
@@ -0,0 +1,311 @@
+/*
+ * "Sequence" lock primitive
+ *
+ * Copyright (C) 2015 David Lamparter <equinox@diac24.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#define _GNU_SOURCE
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <pthread.h>
+#include <assert.h>
+
+#include "seqlock.h"
+
+/****************************************
+ * OS specific synchronization wrappers *
+ ****************************************/
+
+/*
+ * Linux: sys_futex()
+ */
+#ifdef HAVE_SYNC_LINUX_FUTEX
+#include <sys/syscall.h>
+#include <linux/futex.h>
+
+static long sys_futex(void *addr1, int op, int val1,
+ const struct timespec *timeout, void *addr2, int val3)
+{
+ return syscall(SYS_futex, addr1, op, val1, timeout, addr2, val3);
+}
+
+#define wait_once(sqlo, val) \
+ sys_futex((int *)&sqlo->pos, FUTEX_WAIT, (int)val, NULL, NULL, 0)
+#define wait_time(sqlo, val, time, reltime) \
+ sys_futex((int *)&sqlo->pos, FUTEX_WAIT_BITSET, (int)val, time, \
+ NULL, ~0U)
+#define wait_poke(sqlo) \
+ sys_futex((int *)&sqlo->pos, FUTEX_WAKE, INT_MAX, NULL, NULL, 0)
+
+/*
+ * OpenBSD: sys_futex(), almost the same as on Linux
+ */
+#elif defined(HAVE_SYNC_OPENBSD_FUTEX)
+#include <sys/syscall.h>
+#include <sys/futex.h>
+
+#define TIME_RELATIVE 1
+
+#define wait_once(sqlo, val) \
+ futex((int *)&sqlo->pos, FUTEX_WAIT, (int)val, NULL, NULL, 0)
+#define wait_time(sqlo, val, time, reltime) \
+ futex((int *)&sqlo->pos, FUTEX_WAIT, (int)val, reltime, NULL, 0)
+#define wait_poke(sqlo) \
+ futex((int *)&sqlo->pos, FUTEX_WAKE, INT_MAX, NULL, NULL, 0)
+
+/*
+ * FreeBSD: _umtx_op()
+ */
+#elif defined(HAVE_SYNC_UMTX_OP)
+#include <sys/umtx.h>
+
+#define wait_once(sqlo, val) \
+ _umtx_op((void *)&sqlo->pos, UMTX_OP_WAIT_UINT, val, NULL, NULL)
+static int wait_time(struct seqlock *sqlo, uint32_t val,
+ const struct timespec *abstime,
+ const struct timespec *reltime)
+{
+ struct _umtx_time t;
+ t._flags = UMTX_ABSTIME;
+ t._clockid = CLOCK_MONOTONIC;
+ memcpy(&t._timeout, abstime, sizeof(t._timeout));
+ return _umtx_op((void *)&sqlo->pos, UMTX_OP_WAIT_UINT, val,
+ (void *)(uintptr_t) sizeof(t), &t);
+}
+#define wait_poke(sqlo) \
+ _umtx_op((void *)&sqlo->pos, UMTX_OP_WAKE, INT_MAX, NULL, NULL)
+
+/*
+ * generic version. used on NetBSD, Solaris and OSX. really shitty.
+ */
+#else
+
+#define TIME_ABS_REALTIME 1
+
+#define wait_init(sqlo) do { \
+ pthread_mutex_init(&sqlo->lock, NULL); \
+ pthread_cond_init(&sqlo->wake, NULL); \
+ } while (0)
+#define wait_prep(sqlo) pthread_mutex_lock(&sqlo->lock)
+#define wait_once(sqlo, val) pthread_cond_wait(&sqlo->wake, &sqlo->lock)
+#define wait_time(sqlo, val, time, reltime) \
+ pthread_cond_timedwait(&sqlo->wake, \
+ &sqlo->lock, time);
+#define wait_done(sqlo) pthread_mutex_unlock(&sqlo->lock)
+#define wait_poke(sqlo) do { \
+ pthread_mutex_lock(&sqlo->lock); \
+ pthread_cond_broadcast(&sqlo->wake); \
+ pthread_mutex_unlock(&sqlo->lock); \
+ } while (0)
+
+#endif
+
+#ifndef wait_init
+#define wait_init(sqlo) /**/
+#define wait_prep(sqlo) /**/
+#define wait_done(sqlo) /**/
+#endif /* wait_init */
+
+
+void seqlock_wait(struct seqlock *sqlo, seqlock_val_t val)
+{
+ seqlock_val_t cur, cal;
+
+ seqlock_assert_valid(val);
+
+ wait_prep(sqlo);
+ cur = atomic_load_explicit(&sqlo->pos, memory_order_relaxed);
+
+ while (cur & SEQLOCK_HELD) {
+ cal = SEQLOCK_VAL(cur) - val - 1;
+ assert(cal < 0x40000000 || cal > 0xc0000000);
+ if (cal < 0x80000000)
+ break;
+
+ if ((cur & SEQLOCK_WAITERS)
+ || atomic_compare_exchange_weak_explicit(
+ &sqlo->pos, &cur, cur | SEQLOCK_WAITERS,
+ memory_order_relaxed, memory_order_relaxed)) {
+ wait_once(sqlo, cur | SEQLOCK_WAITERS);
+ cur = atomic_load_explicit(&sqlo->pos,
+ memory_order_relaxed);
+ }
+ /* else: we failed to swap in cur because it just changed */
+ }
+ wait_done(sqlo);
+}
+
+bool seqlock_timedwait(struct seqlock *sqlo, seqlock_val_t val,
+ const struct timespec *abs_monotime_limit)
+{
+/*
+ * ABS_REALTIME - used on NetBSD, Solaris and OSX
+ */
+#ifdef TIME_ABS_REALTIME
+#define time_arg1 &abs_rt
+#define time_arg2 NULL
+#define time_prep
+ struct timespec curmono, abs_rt;
+
+ clock_gettime(CLOCK_MONOTONIC, &curmono);
+ clock_gettime(CLOCK_REALTIME, &abs_rt);
+
+ abs_rt.tv_nsec += abs_monotime_limit->tv_nsec - curmono.tv_nsec;
+ if (abs_rt.tv_nsec < 0) {
+ abs_rt.tv_sec--;
+ abs_rt.tv_nsec += 1000000000;
+ } else if (abs_rt.tv_nsec >= 1000000000) {
+ abs_rt.tv_sec++;
+ abs_rt.tv_nsec -= 1000000000;
+ }
+ abs_rt.tv_sec += abs_monotime_limit->tv_sec - curmono.tv_sec;
+
+/*
+ * RELATIVE - used on OpenBSD (might get a patch to get absolute monotime)
+ */
+#elif defined(TIME_RELATIVE)
+ struct timespec reltime;
+
+#define time_arg1 abs_monotime_limit
+#define time_arg2 &reltime
+#define time_prep \
+ clock_gettime(CLOCK_MONOTONIC, &reltime); \
+ reltime.tv_sec = abs_monotime_limit.tv_sec - reltime.tv_sec; \
+ reltime.tv_nsec = abs_monotime_limit.tv_nsec - reltime.tv_nsec; \
+ if (reltime.tv_nsec < 0) { \
+ reltime.tv_sec--; \
+ reltime.tv_nsec += 1000000000; \
+ }
+/*
+ * FreeBSD & Linux: absolute time re. CLOCK_MONOTONIC
+ */
+#else
+#define time_arg1 abs_monotime_limit
+#define time_arg2 NULL
+#define time_prep
+#endif
+
+ bool ret = true;
+ seqlock_val_t cur, cal;
+
+ seqlock_assert_valid(val);
+
+ wait_prep(sqlo);
+ cur = atomic_load_explicit(&sqlo->pos, memory_order_relaxed);
+
+ while (cur & SEQLOCK_HELD) {
+ cal = SEQLOCK_VAL(cur) - val - 1;
+ assert(cal < 0x40000000 || cal > 0xc0000000);
+ if (cal < 0x80000000)
+ break;
+
+ if ((cur & SEQLOCK_WAITERS)
+ || atomic_compare_exchange_weak_explicit(
+ &sqlo->pos, &cur, cur | SEQLOCK_WAITERS,
+ memory_order_relaxed, memory_order_relaxed)) {
+ int rv;
+
+ time_prep
+
+ rv = wait_time(sqlo, cur | SEQLOCK_WAITERS, time_arg1,
+ time_arg2);
+ if (rv) {
+ ret = false;
+ break;
+ }
+ cur = atomic_load_explicit(&sqlo->pos,
+ memory_order_relaxed);
+ }
+ }
+ wait_done(sqlo);
+
+ return ret;
+}
+
+bool seqlock_check(struct seqlock *sqlo, seqlock_val_t val)
+{
+ seqlock_val_t cur;
+
+ seqlock_assert_valid(val);
+
+ cur = atomic_load_explicit(&sqlo->pos, memory_order_relaxed);
+ if (!(cur & SEQLOCK_HELD))
+ return true;
+ cur = SEQLOCK_VAL(cur) - val - 1;
+ assert(cur < 0x40000000 || cur > 0xc0000000);
+ return cur < 0x80000000;
+}
+
+void seqlock_acquire_val(struct seqlock *sqlo, seqlock_val_t val)
+{
+ seqlock_val_t prev;
+
+ seqlock_assert_valid(val);
+
+ prev = atomic_exchange_explicit(&sqlo->pos, val, memory_order_relaxed);
+ if (prev & SEQLOCK_WAITERS)
+ wait_poke(sqlo);
+}
+
+void seqlock_release(struct seqlock *sqlo)
+{
+ seqlock_val_t prev;
+
+ prev = atomic_exchange_explicit(&sqlo->pos, 0, memory_order_relaxed);
+ if (prev & SEQLOCK_WAITERS)
+ wait_poke(sqlo);
+}
+
+void seqlock_init(struct seqlock *sqlo)
+{
+ sqlo->pos = 0;
+ wait_init(sqlo);
+}
+
+
+seqlock_val_t seqlock_cur(struct seqlock *sqlo)
+{
+ return SEQLOCK_VAL(atomic_load_explicit(&sqlo->pos,
+ memory_order_relaxed));
+}
+
+seqlock_val_t seqlock_bump(struct seqlock *sqlo)
+{
+ seqlock_val_t val, cur;
+
+ cur = atomic_load_explicit(&sqlo->pos, memory_order_relaxed);
+ seqlock_assert_valid(cur);
+
+ do {
+ val = SEQLOCK_VAL(cur) + SEQLOCK_INCR;
+ } while (!atomic_compare_exchange_weak_explicit(&sqlo->pos, &cur, val,
+ memory_order_relaxed, memory_order_relaxed));
+
+ if (cur & SEQLOCK_WAITERS)
+ wait_poke(sqlo);
+ return val;
+}
diff --git a/lib/seqlock.h b/lib/seqlock.h
new file mode 100644
index 0000000..bfbf978
--- /dev/null
+++ b/lib/seqlock.h
@@ -0,0 +1,146 @@
+/*
+ * "Sequence" lock primitive
+ *
+ * Copyright (C) 2015 David Lamparter <equinox@diac24.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#ifndef _SEQLOCK_H
+#define _SEQLOCK_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <pthread.h>
+#include "frratomic.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * this locking primitive is intended to use in a 1:N setup.
+ *
+ * - one "counter" seqlock issuing increasing numbers
+ * - multiple seqlock users hold references on these numbers
+ *
+ * this is intended for implementing RCU reference-holding. There is one
+ * global counter, with threads locking a seqlock whenever they take a
+ * reference. A seqlock can also be idle/unlocked.
+ *
+ * The "counter" seqlock will always stay locked; the RCU cleanup thread
+ * continuously counts it up, waiting for threads to release or progress to a
+ * sequence number further ahead. If all threads are > N, references dropped
+ * in N can be free'd.
+ *
+ * generally, the lock function is:
+ *
+ * Thread-A Thread-B
+ *
+ * seqlock_acquire(a)
+ * | running seqlock_wait(b) -- a <= b
+ * seqlock_release() | blocked
+ * OR: seqlock_acquire(a') | -- a' > b
+ * (resumes)
+ */
+
+/* use sequentially increasing "ticket numbers". lowest bit will always
+ * be 1 to have a 'cleared' indication (i.e., counts 1,5,9,13,etc. )
+ * 2nd lowest bit is used to indicate we have waiters.
+ */
+typedef _Atomic uint32_t seqlock_ctr_t;
+typedef uint32_t seqlock_val_t;
+#define seqlock_assert_valid(val) assert((val) & SEQLOCK_HELD)
+
+/* NB: SEQLOCK_WAITERS is only allowed if SEQLOCK_HELD is also set; can't
+ * have waiters on an unheld seqlock
+ */
+#define SEQLOCK_HELD (1U << 0)
+#define SEQLOCK_WAITERS (1U << 1)
+#define SEQLOCK_VAL(n) ((n) & ~SEQLOCK_WAITERS)
+#define SEQLOCK_STARTVAL 1U
+#define SEQLOCK_INCR 4U
+
+/* TODO: originally, this was using "atomic_fetch_add", which is the reason
+ * bit 0 is used to indicate held state. With SEQLOCK_WAITERS added, there's
+ * no fetch_add anymore (cmpxchg loop instead), so we don't need to use bit 0
+ * for this anymore & can just special-case the value 0 for it and skip it in
+ * counting.
+ */
+
+struct seqlock {
+/* always used */
+ seqlock_ctr_t pos;
+/* used when futexes not available: (i.e. non-linux) */
+ pthread_mutex_t lock;
+ pthread_cond_t wake;
+};
+
+
+/* sqlo = 0 - init state: not held */
+extern void seqlock_init(struct seqlock *sqlo);
+
+
+/* basically: "while (sqlo <= val) wait();"
+ * returns when sqlo > val || !seqlock_held(sqlo)
+ */
+extern void seqlock_wait(struct seqlock *sqlo, seqlock_val_t val);
+
+/* same, but time-limited (limit is an absolute CLOCK_MONOTONIC value) */
+extern bool seqlock_timedwait(struct seqlock *sqlo, seqlock_val_t val,
+ const struct timespec *abs_monotime_limit);
+
+/* one-shot test, returns true if seqlock_wait would return immediately */
+extern bool seqlock_check(struct seqlock *sqlo, seqlock_val_t val);
+
+static inline bool seqlock_held(struct seqlock *sqlo)
+{
+ return !!atomic_load_explicit(&sqlo->pos, memory_order_relaxed);
+}
+
+/* sqlo - get seqlock position -- for the "counter" seqlock */
+extern seqlock_val_t seqlock_cur(struct seqlock *sqlo);
+
+/* ++sqlo (but atomic & wakes waiters) - returns value that we bumped to.
+ *
+ * guarantees:
+ * - each seqlock_bump call bumps the position by exactly one SEQLOCK_INCR.
+ * There are no skipped/missed or multiple increments.
+ * - each return value is only returned from one seqlock_bump() call
+ */
+extern seqlock_val_t seqlock_bump(struct seqlock *sqlo);
+
+
+/* sqlo = val - can be used on held seqlock. */
+extern void seqlock_acquire_val(struct seqlock *sqlo, seqlock_val_t val);
+
+/* sqlo = ref - standard pattern: acquire relative to other seqlock */
+static inline void seqlock_acquire(struct seqlock *sqlo, struct seqlock *ref)
+{
+ seqlock_acquire_val(sqlo, seqlock_cur(ref));
+}
+
+/* sqlo = 0 - set seqlock position to 0, marking as non-held */
+extern void seqlock_release(struct seqlock *sqlo);
+/* release should normally be followed by a bump on the "counter", if
+ * anything other than reading RCU items was done
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SEQLOCK_H */
diff --git a/lib/sha256.c b/lib/sha256.c
new file mode 100644
index 0000000..f1727b6
--- /dev/null
+++ b/lib/sha256.c
@@ -0,0 +1,413 @@
+/*-
+ * Copyright 2005,2007,2009 Colin Percival
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <zebra.h>
+#include "sha256.h"
+
+#if !HAVE_DECL_BE32DEC
+static inline uint32_t be32dec(const void *pp)
+{
+ const uint8_t *p = (uint8_t const *)pp;
+
+ return ((uint32_t)(p[3]) + ((uint32_t)(p[2]) << 8)
+ + ((uint32_t)(p[1]) << 16) + ((uint32_t)(p[0]) << 24));
+}
+#endif
+
+#if !HAVE_DECL_BE32ENC
+static inline void be32enc(void *pp, uint32_t x)
+{
+ uint8_t *p = (uint8_t *)pp;
+
+ p[3] = x & 0xff;
+ p[2] = (x >> 8) & 0xff;
+ p[1] = (x >> 16) & 0xff;
+ p[0] = (x >> 24) & 0xff;
+}
+#endif
+
+/*
+ * Encode a length len/4 vector of (uint32_t) into a length len vector of
+ * (unsigned char) in big-endian form. Assumes len is a multiple of 4.
+ */
+static void be32enc_vect(unsigned char *dst, const uint32_t *src, size_t len)
+{
+ size_t i;
+
+ for (i = 0; i < len / 4; i++)
+ be32enc(dst + i * 4, src[i]);
+}
+
+/*
+ * Decode a big-endian length len vector of (unsigned char) into a length
+ * len/4 vector of (uint32_t). Assumes len is a multiple of 4.
+ */
+static void be32dec_vect(uint32_t *dst, const unsigned char *src, size_t len)
+{
+ size_t i;
+
+ for (i = 0; i < len / 4; i++)
+ dst[i] = be32dec(src + i * 4);
+}
+
+/* Elementary functions used by SHA256 */
+#define Ch(x, y, z) ((x & (y ^ z)) ^ z)
+#define Maj(x, y, z) ((x & (y | z)) | (y & z))
+#define SHR(x, n) (x >> n)
+#define ROTR(x, n) ((x >> n) | (x << (32 - n)))
+#define S0(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22))
+#define S1(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25))
+#define s0(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ SHR(x, 3))
+#define s1(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ SHR(x, 10))
+
+/* SHA256 round function */
+#define RND(a, b, c, d, e, f, g, h, k) \
+ t0 = h + S1(e) + Ch(e, f, g) + k; \
+ t1 = S0(a) + Maj(a, b, c); \
+ d += t0; \
+ h = t0 + t1;
+
+/* Adjusted round function for rotating state */
+#define RNDr(S, W, i, k) \
+ RND(S[(64 - i) % 8], S[(65 - i) % 8], S[(66 - i) % 8], \
+ S[(67 - i) % 8], S[(68 - i) % 8], S[(69 - i) % 8], \
+ S[(70 - i) % 8], S[(71 - i) % 8], W[i] + k)
+
+/*
+ * SHA256 block compression function. The 256-bit state is transformed via
+ * the 512-bit input block to produce a new state.
+ */
+static void SHA256_Transform(uint32_t *state, const unsigned char block[64])
+{
+ uint32_t W[64];
+ uint32_t S[8];
+ uint32_t t0, t1;
+ int i;
+
+ /* 1. Prepare message schedule W. */
+ be32dec_vect(W, block, 64);
+ for (i = 16; i < 64; i++)
+ W[i] = s1(W[i - 2]) + W[i - 7] + s0(W[i - 15]) + W[i - 16];
+
+ /* 2. Initialize working variables. */
+ memcpy(S, state, 32);
+
+ /* 3. Mix. */
+ RNDr(S, W, 0, 0x428a2f98);
+ RNDr(S, W, 1, 0x71374491);
+ RNDr(S, W, 2, 0xb5c0fbcf);
+ RNDr(S, W, 3, 0xe9b5dba5);
+ RNDr(S, W, 4, 0x3956c25b);
+ RNDr(S, W, 5, 0x59f111f1);
+ RNDr(S, W, 6, 0x923f82a4);
+ RNDr(S, W, 7, 0xab1c5ed5);
+ RNDr(S, W, 8, 0xd807aa98);
+ RNDr(S, W, 9, 0x12835b01);
+ RNDr(S, W, 10, 0x243185be);
+ RNDr(S, W, 11, 0x550c7dc3);
+ RNDr(S, W, 12, 0x72be5d74);
+ RNDr(S, W, 13, 0x80deb1fe);
+ RNDr(S, W, 14, 0x9bdc06a7);
+ RNDr(S, W, 15, 0xc19bf174);
+ RNDr(S, W, 16, 0xe49b69c1);
+ RNDr(S, W, 17, 0xefbe4786);
+ RNDr(S, W, 18, 0x0fc19dc6);
+ RNDr(S, W, 19, 0x240ca1cc);
+ RNDr(S, W, 20, 0x2de92c6f);
+ RNDr(S, W, 21, 0x4a7484aa);
+ RNDr(S, W, 22, 0x5cb0a9dc);
+ RNDr(S, W, 23, 0x76f988da);
+ RNDr(S, W, 24, 0x983e5152);
+ RNDr(S, W, 25, 0xa831c66d);
+ RNDr(S, W, 26, 0xb00327c8);
+ RNDr(S, W, 27, 0xbf597fc7);
+ RNDr(S, W, 28, 0xc6e00bf3);
+ RNDr(S, W, 29, 0xd5a79147);
+ RNDr(S, W, 30, 0x06ca6351);
+ RNDr(S, W, 31, 0x14292967);
+ RNDr(S, W, 32, 0x27b70a85);
+ RNDr(S, W, 33, 0x2e1b2138);
+ RNDr(S, W, 34, 0x4d2c6dfc);
+ RNDr(S, W, 35, 0x53380d13);
+ RNDr(S, W, 36, 0x650a7354);
+ RNDr(S, W, 37, 0x766a0abb);
+ RNDr(S, W, 38, 0x81c2c92e);
+ RNDr(S, W, 39, 0x92722c85);
+ RNDr(S, W, 40, 0xa2bfe8a1);
+ RNDr(S, W, 41, 0xa81a664b);
+ RNDr(S, W, 42, 0xc24b8b70);
+ RNDr(S, W, 43, 0xc76c51a3);
+ RNDr(S, W, 44, 0xd192e819);
+ RNDr(S, W, 45, 0xd6990624);
+ RNDr(S, W, 46, 0xf40e3585);
+ RNDr(S, W, 47, 0x106aa070);
+ RNDr(S, W, 48, 0x19a4c116);
+ RNDr(S, W, 49, 0x1e376c08);
+ RNDr(S, W, 50, 0x2748774c);
+ RNDr(S, W, 51, 0x34b0bcb5);
+ RNDr(S, W, 52, 0x391c0cb3);
+ RNDr(S, W, 53, 0x4ed8aa4a);
+ RNDr(S, W, 54, 0x5b9cca4f);
+ RNDr(S, W, 55, 0x682e6ff3);
+ RNDr(S, W, 56, 0x748f82ee);
+ RNDr(S, W, 57, 0x78a5636f);
+ RNDr(S, W, 58, 0x84c87814);
+ RNDr(S, W, 59, 0x8cc70208);
+ RNDr(S, W, 60, 0x90befffa);
+ RNDr(S, W, 61, 0xa4506ceb);
+ RNDr(S, W, 62, 0xbef9a3f7);
+ RNDr(S, W, 63, 0xc67178f2);
+
+ /* 4. Mix local working variables into global state */
+ for (i = 0; i < 8; i++)
+ state[i] += S[i];
+
+ /* Clean the stack. */
+ explicit_bzero(W, 256);
+ explicit_bzero(S, 32);
+ explicit_bzero(&t0, sizeof(t0));
+ explicit_bzero(&t1, sizeof(t0));
+}
+
+static unsigned char PAD[64] = {
+ 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+/* Add padding and terminating bit-count. */
+static void SHA256_Pad(SHA256_CTX *ctx)
+{
+ unsigned char len[8];
+ uint32_t r, plen;
+
+ /*
+ * Convert length to a vector of bytes -- we do this now rather
+ * than later because the length will change after we pad.
+ */
+ be32enc_vect(len, ctx->count, 8);
+
+ /* Add 1--64 bytes so that the resulting length is 56 mod 64 */
+ r = (ctx->count[1] >> 3) & 0x3f;
+ plen = (r < 56) ? (56 - r) : (120 - r);
+ SHA256_Update(ctx, PAD, (size_t)plen);
+
+ /* Add the terminating bit-count */
+ SHA256_Update(ctx, len, 8);
+}
+
+/* SHA-256 initialization. Begins a SHA-256 operation. */
+void SHA256_Init(SHA256_CTX *ctx)
+{
+
+ /* Zero bits processed so far */
+ ctx->count[0] = ctx->count[1] = 0;
+
+ /* Magic initialization constants */
+ ctx->state[0] = 0x6A09E667;
+ ctx->state[1] = 0xBB67AE85;
+ ctx->state[2] = 0x3C6EF372;
+ ctx->state[3] = 0xA54FF53A;
+ ctx->state[4] = 0x510E527F;
+ ctx->state[5] = 0x9B05688C;
+ ctx->state[6] = 0x1F83D9AB;
+ ctx->state[7] = 0x5BE0CD19;
+}
+
+/* Add bytes into the hash */
+void SHA256_Update(SHA256_CTX *ctx, const void *in, size_t len)
+{
+ uint32_t bitlen[2];
+ uint32_t r;
+ const unsigned char *src = in;
+
+ /* Number of bytes left in the buffer from previous updates */
+ r = (ctx->count[1] >> 3) & 0x3f;
+
+ /* Convert the length into a number of bits */
+ bitlen[1] = ((uint32_t)len) << 3;
+ bitlen[0] = (uint32_t)(len >> 29);
+
+ /* Update number of bits */
+ if ((ctx->count[1] += bitlen[1]) < bitlen[1])
+ ctx->count[0]++;
+ ctx->count[0] += bitlen[0];
+
+ /* Handle the case where we don't need to perform any transforms */
+ if (len < 64 - r) {
+ memcpy(&ctx->buf[r], src, len);
+ return;
+ }
+
+ /* Finish the current block */
+ memcpy(&ctx->buf[r], src, 64 - r);
+ SHA256_Transform(ctx->state, ctx->buf);
+ src += 64 - r;
+ len -= 64 - r;
+
+ /* Perform complete blocks */
+ while (len >= 64) {
+ SHA256_Transform(ctx->state, src);
+ src += 64;
+ len -= 64;
+ }
+
+ /* Copy left over data into buffer */
+ memcpy(ctx->buf, src, len);
+}
+
+/*
+ * SHA-256 finalization. Pads the input data, exports the hash value,
+ * and clears the context state.
+ */
+void SHA256_Final(unsigned char digest[32], SHA256_CTX *ctx)
+{
+
+ /* Add padding */
+ SHA256_Pad(ctx);
+
+ /* Write the hash */
+ be32enc_vect(digest, ctx->state, 32);
+
+ /* Clear the context state */
+ explicit_bzero((void *)ctx, sizeof(*ctx));
+}
+
+/* Initialize an HMAC-SHA256 operation with the given key. */
+void HMAC__SHA256_Init(HMAC_SHA256_CTX *ctx, const void *_K, size_t Klen)
+{
+ unsigned char pad[64];
+ unsigned char khash[32];
+ const unsigned char *K = _K;
+ size_t i;
+
+ /* If Klen > 64, the key is really SHA256(K). */
+ if (Klen > 64) {
+ SHA256_Init(&ctx->ictx);
+ SHA256_Update(&ctx->ictx, K, Klen);
+ SHA256_Final(khash, &ctx->ictx);
+ K = khash;
+ Klen = 32;
+ }
+
+ /* Inner SHA256 operation is SHA256(K xor [block of 0x36] || data). */
+ SHA256_Init(&ctx->ictx);
+ memset(pad, 0x36, 64);
+ for (i = 0; i < Klen; i++)
+ pad[i] ^= K[i];
+ SHA256_Update(&ctx->ictx, pad, 64);
+
+ /* Outer SHA256 operation is SHA256(K xor [block of 0x5c] || hash). */
+ SHA256_Init(&ctx->octx);
+ memset(pad, 0x5c, 64);
+ for (i = 0; i < Klen; i++)
+ pad[i] ^= K[i];
+ SHA256_Update(&ctx->octx, pad, 64);
+
+ /* Clean the stack. */
+ explicit_bzero(khash, 32);
+}
+
+/* Add bytes to the HMAC-SHA256 operation. */
+void HMAC__SHA256_Update(HMAC_SHA256_CTX *ctx, const void *in, size_t len)
+{
+
+ /* Feed data to the inner SHA256 operation. */
+ SHA256_Update(&ctx->ictx, in, len);
+}
+
+/* Finish an HMAC-SHA256 operation. */
+void HMAC__SHA256_Final(unsigned char digest[32], HMAC_SHA256_CTX *ctx)
+{
+ unsigned char ihash[32];
+
+ /* Finish the inner SHA256 operation. */
+ SHA256_Final(ihash, &ctx->ictx);
+
+ /* Feed the inner hash to the outer SHA256 operation. */
+ SHA256_Update(&ctx->octx, ihash, 32);
+
+ /* Finish the outer SHA256 operation. */
+ SHA256_Final(digest, &ctx->octx);
+
+ /* Clean the stack. */
+ explicit_bzero(ihash, 32);
+}
+
+/**
+ * PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, c, buf, dkLen):
+ * Compute PBKDF2(passwd, salt, c, dkLen) using HMAC-SHA256 as the PRF, and
+ * write the output to buf. The value dkLen must be at most 32 * (2^32 - 1).
+ */
+void PBKDF2_SHA256(const uint8_t *passwd, size_t passwdlen, const uint8_t *salt,
+ size_t saltlen, uint64_t c, uint8_t *buf, size_t dkLen)
+{
+ HMAC_SHA256_CTX PShctx, hctx;
+ size_t i;
+ uint8_t ivec[4];
+ uint8_t U[32];
+ uint8_t T[32];
+ uint64_t j;
+ int k;
+ size_t clen;
+
+ /* Compute HMAC state after processing P and S. */
+ HMAC__SHA256_Init(&PShctx, passwd, passwdlen);
+ HMAC__SHA256_Update(&PShctx, salt, saltlen);
+
+ /* Iterate through the blocks. */
+ for (i = 0; i * 32 < dkLen; i++) {
+ /* Generate INT(i + 1). */
+ be32enc(ivec, (uint32_t)(i + 1));
+
+ /* Compute U_1 = PRF(P, S || INT(i)). */
+ memcpy(&hctx, &PShctx, sizeof(HMAC_SHA256_CTX));
+ HMAC__SHA256_Update(&hctx, ivec, 4);
+ HMAC__SHA256_Final(U, &hctx);
+
+ /* T_i = U_1 ... */
+ memcpy(T, U, 32);
+
+ for (j = 2; j <= c; j++) {
+ /* Compute U_j. */
+ HMAC__SHA256_Init(&hctx, passwd, passwdlen);
+ HMAC__SHA256_Update(&hctx, U, 32);
+ HMAC__SHA256_Final(U, &hctx);
+
+ /* ... xor U_j ... */
+ for (k = 0; k < 32; k++)
+ T[k] ^= U[k];
+ }
+
+ /* Copy as many bytes as necessary into buf. */
+ clen = dkLen - i * 32;
+ if (clen > 32)
+ clen = 32;
+ memcpy(&buf[i * 32], T, clen);
+ }
+
+ /* Clean PShctx, since we never called _Final on it. */
+ explicit_bzero(&PShctx, sizeof(HMAC_SHA256_CTX));
+}
diff --git a/lib/sha256.h b/lib/sha256.h
new file mode 100644
index 0000000..c93d253
--- /dev/null
+++ b/lib/sha256.h
@@ -0,0 +1,66 @@
+/*-
+ * Copyright 2005,2007,2009 Colin Percival
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/lib/libmd/sha256.h,v 1.2 2006/01/17 15:35:56 phk Exp $
+ */
+
+#ifndef _SHA256_H_
+#define _SHA256_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct SHA256Context {
+ uint32_t state[8];
+ uint32_t count[2];
+ unsigned char buf[64];
+} SHA256_CTX;
+
+typedef struct HMAC_SHA256Context {
+ SHA256_CTX ictx;
+ SHA256_CTX octx;
+} HMAC_SHA256_CTX;
+
+void SHA256_Init(SHA256_CTX *);
+void SHA256_Update(SHA256_CTX *, const void *, size_t);
+void SHA256_Final(unsigned char[32], SHA256_CTX *);
+void HMAC__SHA256_Init(HMAC_SHA256_CTX *, const void *, size_t);
+void HMAC__SHA256_Update(HMAC_SHA256_CTX *, const void *, size_t);
+void HMAC__SHA256_Final(unsigned char[32], HMAC_SHA256_CTX *);
+
+/**
+ * PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, c, buf, dkLen):
+ * Compute PBKDF2(passwd, salt, c, dkLen) using HMAC-SHA256 as the PRF, and
+ * write the output to buf. The value dkLen must be at most 32 * (2^32 - 1).
+ */
+void PBKDF2_SHA256(const uint8_t *, size_t, const uint8_t *, size_t, uint64_t,
+ uint8_t *, size_t);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !_SHA256_H_ */
diff --git a/lib/sigevent.c b/lib/sigevent.c
new file mode 100644
index 0000000..985bede
--- /dev/null
+++ b/lib/sigevent.c
@@ -0,0 +1,375 @@
+/* Quagga signal handling functions.
+ * Copyright (C) 2004 Paul Jakma,
+ *
+ * This file is part of Quagga.
+ *
+ * Quagga 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, or (at your option) any
+ * later version.
+ *
+ * Quagga 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 <sigevent.h>
+#include <log.h>
+#include <memory.h>
+#include <lib_errors.h>
+
+#ifdef HAVE_UCONTEXT_H
+#ifdef GNU_LINUX
+/* get REG_EIP from ucontext.h */
+#ifndef __USE_GNU
+#define __USE_GNU
+#endif /* __USE_GNU */
+#endif /* GNU_LINUX */
+#include <ucontext.h>
+#endif /* HAVE_UCONTEXT_H */
+
+
+/* master signals descriptor struct */
+static struct frr_sigevent_master_t {
+ struct thread *t;
+
+ struct frr_signal_t *signals;
+ int sigc;
+
+ volatile sig_atomic_t caught;
+} sigmaster;
+
+/* Generic signal handler
+ * Schedules signal event thread
+ */
+static void frr_signal_handler(int signo)
+{
+ int i;
+ struct frr_signal_t *sig;
+
+ for (i = 0; i < sigmaster.sigc; i++) {
+ sig = &(sigmaster.signals[i]);
+
+ if (sig->signal == signo)
+ sig->caught = 1;
+ }
+
+ sigmaster.caught = 1;
+}
+
+/*
+ * Check whether any signals have been received and are pending. This is done
+ * with the application's key signals blocked. The complete set of signals
+ * is returned in 'setp', so the caller can restore them when appropriate.
+ * If there are pending signals, returns 'true', 'false' otherwise.
+ */
+bool frr_sigevent_check(sigset_t *setp)
+{
+ sigset_t blocked;
+ int i;
+ bool ret;
+
+ sigemptyset(setp);
+ sigemptyset(&blocked);
+
+ /* Set up mask of application's signals */
+ for (i = 0; i < sigmaster.sigc; i++)
+ sigaddset(&blocked, sigmaster.signals[i].signal);
+
+ pthread_sigmask(SIG_BLOCK, &blocked, setp);
+
+ /* Now that the application's signals are blocked, test. */
+ ret = (sigmaster.caught != 0);
+
+ return ret;
+}
+
+/* check if signals have been caught and run appropriate handlers */
+int frr_sigevent_process(void)
+{
+ struct frr_signal_t *sig;
+ int i;
+#ifdef SIGEVENT_BLOCK_SIGNALS
+ /* shouldn't need to block signals, but potentially may be needed */
+ sigset_t newmask, oldmask;
+
+ /*
+ * Block most signals, but be careful not to defer SIGTRAP because
+ * doing so breaks gdb, at least on NetBSD 2.0. Avoid asking to
+ * block SIGKILL, just because we shouldn't be able to do so.
+ */
+ sigfillset(&newmask);
+ sigdelset(&newmask, SIGTRAP);
+ sigdelset(&newmask, SIGKILL);
+
+ if ((sigprocmask(SIG_BLOCK, &newmask, &oldmask)) < 0) {
+ flog_err_sys(EC_LIB_SYSTEM_CALL,
+ "frr_signal_timer: couldnt block signals!");
+ return -1;
+ }
+#endif /* SIGEVENT_BLOCK_SIGNALS */
+
+ if (sigmaster.caught > 0) {
+ sigmaster.caught = 0;
+ /* must not read or set sigmaster.caught after here,
+ * race condition with per-sig caught flags if one does
+ */
+
+ for (i = 0; i < sigmaster.sigc; i++) {
+ sig = &(sigmaster.signals[i]);
+
+ if (sig->caught > 0) {
+ sig->caught = 0;
+ if (sig->handler)
+ sig->handler();
+ }
+ }
+ }
+
+#ifdef SIGEVENT_BLOCK_SIGNALS
+ if (sigprocmask(SIG_UNBLOCK, &oldmask, NULL) < 0)
+ return -1;
+#endif /* SIGEVENT_BLOCK_SIGNALS */
+
+ return 0;
+}
+
+#ifdef SIGEVENT_SCHEDULE_THREAD
+/* timer thread to check signals. shouldn't be needed */
+void frr_signal_timer(struct thread *t)
+{
+ struct frr_sigevent_master_t *sigm;
+
+ sigm = THREAD_ARG(t);
+ sigm->t = NULL;
+ thread_add_timer(sigm->t->master, frr_signal_timer, &sigmaster,
+ FRR_SIGNAL_TIMER_INTERVAL, &sigm->t);
+ frr_sigevent_process();
+}
+#endif /* SIGEVENT_SCHEDULE_THREAD */
+
+/* Initialization of signal handles. */
+/* Signal wrapper. */
+static int signal_set(int signo)
+{
+ int ret;
+ struct sigaction sig;
+ struct sigaction osig;
+
+ sig.sa_handler = &frr_signal_handler;
+ sigfillset(&sig.sa_mask);
+ sig.sa_flags = 0;
+ if (signo == SIGALRM) {
+#ifdef SA_INTERRUPT
+ sig.sa_flags |= SA_INTERRUPT; /* SunOS */
+#endif
+ } else {
+#ifdef SA_RESTART
+ sig.sa_flags |= SA_RESTART;
+#endif /* SA_RESTART */
+ }
+
+ ret = sigaction(signo, &sig, &osig);
+ if (ret < 0)
+ return ret;
+ else
+ return 0;
+}
+
+/* XXX This function should be enhanced to support more platforms
+ (it currently works only on Linux/x86). */
+static void *program_counter(void *context)
+{
+#ifdef HAVE_UCONTEXT_H
+#ifdef GNU_LINUX
+/* these are from GNU libc, rather than Linux, strictly speaking */
+#if defined(REG_EIP)
+# define REG_INDEX REG_EIP
+#elif defined(REG_RIP)
+# define REG_INDEX REG_RIP
+#elif defined(__powerpc__)
+# define REG_INDEX 32
+#endif
+#endif /* GNU_LINUX */
+
+#ifdef REG_INDEX
+#ifdef HAVE_UCONTEXT_T_UC_MCONTEXT_GREGS
+# define REGS gregs[REG_INDEX]
+#elif defined(HAVE_UCONTEXT_T_UC_MCONTEXT_UC_REGS)
+# define REGS uc_regs->gregs[REG_INDEX]
+#endif /* HAVE_UCONTEXT_T_UC_MCONTEXT_GREGS */
+#endif /* REG_INDEX */
+
+#ifdef REGS
+ if (context)
+ return (void *)(((ucontext_t *)context)->uc_mcontext.REGS);
+#elif defined(HAVE_UCONTEXT_T_UC_MCONTEXT_REGS__NIP)
+ /* older Linux / struct pt_regs ? */
+ if (context)
+ return (void *)(((ucontext_t *)context)->uc_mcontext.regs->nip);
+#endif /* REGS */
+
+#endif /* HAVE_UCONTEXT_H */
+ return NULL;
+}
+
+static void __attribute__((noreturn))
+exit_handler(int signo, siginfo_t *siginfo, void *context)
+{
+ void *pc = program_counter(context);
+
+ zlog_signal(signo, "exiting...", siginfo, pc);
+ _exit(128 + signo);
+}
+
+static void __attribute__((noreturn))
+core_handler(int signo, siginfo_t *siginfo, void *context)
+{
+ void *pc = program_counter(context);
+
+ /* make sure we don't hang in here. default for SIGALRM is terminate.
+ * - if we're in backtrace for more than a second, abort. */
+ struct sigaction sa_default = {.sa_handler = SIG_DFL};
+
+ sigaction(SIGALRM, &sa_default, NULL);
+ sigaction(signo, &sa_default, NULL);
+
+ sigset_t sigset;
+
+ sigemptyset(&sigset);
+ sigaddset(&sigset, SIGALRM);
+ sigprocmask(SIG_UNBLOCK, &sigset, NULL);
+
+ alarm(1);
+
+ zlog_signal(signo, "aborting...", siginfo, pc);
+
+ /* dump memory stats on core */
+ log_memstats(stderr, "core_handler");
+
+ zlog_tls_buffer_fini();
+
+ /* give the kernel a chance to generate a coredump */
+ sigaddset(&sigset, signo);
+ sigprocmask(SIG_UNBLOCK, &sigset, NULL);
+ raise(signo);
+
+ /* only chance to end up here is if the default action for signo is
+ * something other than kill or coredump the process
+ */
+ _exit(128 + signo);
+}
+
+static void trap_default_signals(void)
+{
+ static const int core_signals[] = {
+ SIGQUIT, SIGILL, SIGABRT,
+#ifdef SIGEMT
+ SIGEMT,
+#endif
+ SIGFPE, SIGBUS, SIGSEGV,
+#ifdef SIGSYS
+ SIGSYS,
+#endif
+#ifdef SIGXCPU
+ SIGXCPU,
+#endif
+#ifdef SIGXFSZ
+ SIGXFSZ,
+#endif
+ };
+ static const int exit_signals[] = {
+ SIGHUP, SIGINT, SIGALRM, SIGTERM, SIGUSR1, SIGUSR2,
+#ifdef SIGPOLL
+ SIGPOLL,
+#endif
+#ifdef SIGVTALRM
+ SIGVTALRM,
+#endif
+#ifdef SIGSTKFLT
+ SIGSTKFLT,
+#endif
+ };
+ static const int ignore_signals[] = {
+ SIGPIPE,
+ };
+ static const struct {
+ const int *sigs;
+ unsigned int nsigs;
+ void (*handler)(int signo, siginfo_t *info, void *context);
+ } sigmap[] = {
+ {core_signals, array_size(core_signals), core_handler},
+ {exit_signals, array_size(exit_signals), exit_handler},
+ {ignore_signals, array_size(ignore_signals), NULL},
+ };
+ unsigned int i;
+
+ for (i = 0; i < array_size(sigmap); i++) {
+ unsigned int j;
+
+ for (j = 0; j < sigmap[i].nsigs; j++) {
+ struct sigaction oact;
+ if ((sigaction(sigmap[i].sigs[j], NULL, &oact) == 0)
+ && (oact.sa_handler == SIG_DFL)) {
+ struct sigaction act;
+ sigfillset(&act.sa_mask);
+ if (sigmap[i].handler == NULL) {
+ act.sa_handler = SIG_IGN;
+ act.sa_flags = 0;
+ } else {
+ /* Request extra arguments to signal
+ * handler. */
+ act.sa_sigaction = sigmap[i].handler;
+ act.sa_flags = SA_SIGINFO;
+#ifdef SA_RESETHAND
+ /* don't try to print backtraces
+ * recursively */
+ if (sigmap[i].handler == core_handler)
+ act.sa_flags |= SA_RESETHAND;
+#endif
+ }
+ if (sigaction(sigmap[i].sigs[j], &act, NULL)
+ < 0)
+ flog_err(
+ EC_LIB_SYSTEM_CALL,
+ "Unable to set signal handler for signal %d: %s",
+ sigmap[i].sigs[j],
+ safe_strerror(errno));
+ }
+ }
+ }
+}
+
+void signal_init(struct thread_master *m, int sigc,
+ struct frr_signal_t signals[])
+{
+
+ int i = 0;
+ struct frr_signal_t *sig;
+
+ /* First establish some default handlers that can be overridden by
+ the application. */
+ trap_default_signals();
+
+ while (i < sigc) {
+ sig = &signals[i];
+ if (signal_set(sig->signal) < 0)
+ exit(-1);
+ i++;
+ }
+
+ sigmaster.sigc = sigc;
+ sigmaster.signals = signals;
+
+#ifdef SIGEVENT_SCHEDULE_THREAD
+ sigmaster.t = NULL;
+ thread_add_timer(m, frr_signal_timer, &sigmaster,
+ FRR_SIGNAL_TIMER_INTERVAL, &sigmaster.t);
+#endif /* SIGEVENT_SCHEDULE_THREAD */
+}
diff --git a/lib/sigevent.h b/lib/sigevent.h
new file mode 100644
index 0000000..dd1ee99
--- /dev/null
+++ b/lib/sigevent.h
@@ -0,0 +1,67 @@
+/*
+ * Quagga Signal handling header.
+ *
+ * Copyright (C) 2004 Paul Jakma.
+ *
+ * This file is part of Quagga.
+ *
+ * Quagga 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, or (at your option) any
+ * later version.
+ *
+ * Quagga 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_SIGNAL_H
+#define _FRR_SIGNAL_H
+
+#include <thread.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define FRR_SIGNAL_TIMER_INTERVAL 2L
+
+struct frr_signal_t {
+ int signal; /* signal number */
+ void (*handler)(void); /* handler to call */
+
+ volatile sig_atomic_t caught; /* private member */
+};
+
+/* initialise sigevent system
+ * takes:
+ * - pointer to valid struct thread_master
+ * - number of elements in passed in signals array
+ * - array of frr_signal_t's describing signals to handle
+ * and handlers to use for each signal
+ */
+extern void signal_init(struct thread_master *m, int sigc,
+ struct frr_signal_t *signals);
+
+
+/*
+ * Check whether any signals have been received and are pending. This is done
+ * with the application's key signals blocked. The complete set of signals
+ * is returned in 'setp', so the caller can restore them when appropriate.
+ * If there are pending signals, returns 'true', 'false' otherwise.
+ */
+bool frr_sigevent_check(sigset_t *setp);
+
+/* check whether there are signals to handle, process any found */
+extern int frr_sigevent_process(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRR_SIGNAL_H */
diff --git a/lib/skiplist.c b/lib/skiplist.c
new file mode 100644
index 0000000..8140782
--- /dev/null
+++ b/lib/skiplist.c
@@ -0,0 +1,671 @@
+/*
+ * Copyright 1990 William Pugh
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Permission to include in quagga provide on March 31, 2016
+ */
+
+/*
+ * Skip List implementation based on code from William Pugh.
+ * ftp://ftp.cs.umd.edu/pub/skipLists/
+ *
+ * Skip Lists are a probabilistic alternative to balanced trees, as
+ * described in the June 1990 issue of CACM and were invented by
+ * William Pugh in 1987.
+ *
+ * This file contains source code to implement a dictionary using
+ * skip lists and a test driver to test the routines.
+ *
+ * A couple of comments about this implementation:
+ * The routine randomLevel has been hard-coded to generate random
+ * levels using p=0.25. It can be easily changed.
+ *
+ * The insertion routine has been implemented so as to use the
+ * dirty hack described in the CACM paper: if a random level is
+ * generated that is more than the current maximum level, the
+ * current maximum level plus one is used instead.
+ *
+ * Levels start at zero and go up to MaxLevel (which is equal to
+ * (MaxNumberOfLevels-1).
+ *
+ * The run-time flag SKIPLIST_FLAG_ALLOW_DUPLICATES determines whether or
+ * not duplicates are allowed for a given list. If set, duplicates are
+ * allowed and act in a FIFO manner. If not set, an insertion of a value
+ * already in the list updates the previously existing binding.
+ *
+ * BitsInRandom is defined to be the number of bits returned by a call to
+ * random(). For most all machines with 32-bit integers, this is 31 bits
+ * as currently set.
+ */
+
+
+#include <zebra.h>
+
+#include "memory.h"
+#include "log.h"
+#include "vty.h"
+#include "skiplist.h"
+#include "lib_errors.h"
+#include "network.h"
+
+DEFINE_MTYPE_STATIC(LIB, SKIP_LIST, "Skip List");
+DEFINE_MTYPE_STATIC(LIB, SKIP_LIST_NODE, "Skip Node");
+DEFINE_MTYPE_STATIC(LIB, SKIP_LIST_STATS, "Skiplist Counters");
+
+#define BitsInRandom 31
+
+#define MaxNumberOfLevels 16
+#define MaxLevel (MaxNumberOfLevels-1)
+#define newNodeOfLevel(l) \
+ XCALLOC(MTYPE_SKIP_LIST_NODE, \
+ sizeof(struct skiplistnode) \
+ + (l) * sizeof(struct skiplistnode *))
+
+/* XXX must match type of (struct skiplist).level_stats */
+#define newStatsOfLevel(l) \
+ XCALLOC(MTYPE_SKIP_LIST_STATS, ((l) + 1) * sizeof(int))
+
+static int randomsLeft;
+static int randomBits;
+
+#ifdef SKIPLIST_DEBUG
+#define CHECKLAST(sl) \
+ do { \
+ if ((sl)->header->forward[0] && !(sl)->last) \
+ assert(0); \
+ if (!(sl)->header->forward[0] && (sl)->last) \
+ assert(0); \
+ } while (0)
+#else
+#define CHECKLAST(sl)
+#endif
+
+
+static int randomLevel(void)
+{
+ register int level = 0;
+ register int b;
+
+ do {
+ if (randomsLeft <= 0) {
+ randomBits = frr_weak_random();
+ randomsLeft = BitsInRandom / 2;
+ }
+ b = randomBits & 3;
+ randomBits >>= 2;
+ --randomsLeft;
+
+ if (!b) {
+ level++;
+ if (level >= MaxLevel)
+ return MaxLevel;
+ }
+ } while (!b);
+
+ return level;
+}
+
+static int default_cmp(const void *key1, const void *key2)
+{
+ if (key1 < key2)
+ return -1;
+ if (key1 > key2)
+ return 1;
+ return 0;
+}
+
+unsigned int skiplist_count(struct skiplist *l)
+{
+ return l->count;
+}
+
+struct skiplist *skiplist_new(int flags,
+ int (*cmp)(const void *key1, const void *key2),
+ void (*del)(void *val))
+{
+ struct skiplist *new;
+
+ new = XCALLOC(MTYPE_SKIP_LIST, sizeof(struct skiplist));
+ assert(new);
+
+ new->level = 0;
+ new->count = 0;
+ new->header = newNodeOfLevel(MaxNumberOfLevels);
+ new->level_stats = newStatsOfLevel(MaxNumberOfLevels);
+
+ new->flags = flags;
+ if (cmp)
+ new->cmp = cmp;
+ else
+ new->cmp = default_cmp;
+
+ if (del)
+ new->del = del;
+
+ return new;
+}
+
+void skiplist_free(struct skiplist *l)
+{
+ register struct skiplistnode *p, *q;
+
+ p = l->header;
+
+ do {
+ q = p->forward[0];
+ if (l->del && p != l->header)
+ (*l->del)(p->value);
+ XFREE(MTYPE_SKIP_LIST_NODE, p);
+ p = q;
+ } while (p);
+
+ XFREE(MTYPE_SKIP_LIST_STATS, l->level_stats);
+ XFREE(MTYPE_SKIP_LIST, l);
+}
+
+
+int skiplist_insert(register struct skiplist *l, register void *key,
+ register void *value)
+{
+ register int k;
+ struct skiplistnode *update[MaxNumberOfLevels];
+ register struct skiplistnode *p, *q;
+
+ CHECKLAST(l);
+
+#ifdef SKIPLIST_DEBUG
+ /* DEBUG */
+ if (!key) {
+ flog_err(EC_LIB_DEVELOPMENT, "%s: key is 0, value is %p",
+ __func__, value);
+ }
+#endif
+
+ p = l->header;
+ k = l->level;
+ do {
+ while (q = p->forward[k], q && (*l->cmp)(q->key, key) < 0)
+ p = q;
+ update[k] = p;
+ } while (--k >= 0);
+
+ if (!(l->flags & SKIPLIST_FLAG_ALLOW_DUPLICATES) && q
+ && ((*l->cmp)(q->key, key) == 0)) {
+
+ return -1;
+ }
+
+ k = randomLevel();
+ assert(k >= 0);
+ if (k > l->level) {
+ k = ++l->level;
+ update[k] = l->header;
+ }
+
+ q = newNodeOfLevel(k);
+ q->key = key;
+ q->value = value;
+#ifdef SKIPLIST_0TIMER_DEBUG
+ q->flags = SKIPLIST_NODE_FLAG_INSERTED; /* debug */
+#endif
+
+ ++(l->level_stats[k]);
+#ifdef SKIPLIST_DEBUG
+ zlog_debug("%s: incremented level_stats @%p:%d, now %d", __func__, l, k,
+ l->level_stats[k]);
+#endif
+
+ do {
+ p = update[k];
+ q->forward[k] = p->forward[k];
+ p->forward[k] = q;
+ } while (--k >= 0);
+
+ /*
+ * If this is the last item in the list, update the "last" pointer
+ */
+ if (!q->forward[0]) {
+ l->last = q;
+ }
+
+ ++(l->count);
+
+ CHECKLAST(l);
+
+ return 0;
+}
+
+int skiplist_delete(register struct skiplist *l, register void *key,
+ register void *value) /* used only if duplicates allowed */
+{
+ register int k, m;
+ struct skiplistnode *update[MaxNumberOfLevels];
+ register struct skiplistnode *p, *q;
+
+ CHECKLAST(l);
+
+ /* to make debugging easier */
+ for (k = 0; k < MaxNumberOfLevels; ++k)
+ update[k] = NULL;
+
+ p = l->header;
+ k = m = l->level;
+ do {
+ while (q = p->forward[k], q && (*l->cmp)(q->key, key) < 0)
+ p = q;
+ update[k] = p;
+ } while (--k >= 0);
+
+ if (l->flags & SKIPLIST_FLAG_ALLOW_DUPLICATES) {
+ while (q && ((*l->cmp)(q->key, key) == 0)
+ && (q->value != value)) {
+ int i;
+ for (i = 0; i <= l->level; ++i) {
+ if (update[i]->forward[i] == q)
+ update[i] = q;
+ }
+ q = q->forward[0];
+ }
+ }
+
+ if (q && (*l->cmp)(q->key, key) == 0) {
+ if (!(l->flags & SKIPLIST_FLAG_ALLOW_DUPLICATES)
+ || (q->value == value)) {
+
+/*
+ * found node to delete
+ */
+#ifdef SKIPLIST_0TIMER_DEBUG
+ q->flags &= ~SKIPLIST_NODE_FLAG_INSERTED;
+#endif
+ /*
+ * If we are deleting the last element of the list,
+ * update the list's "last" pointer.
+ */
+ if (l->last == q) {
+ if (update[0] == l->header)
+ l->last = NULL;
+ else
+ l->last = update[0];
+ }
+
+ for (k = 0; k <= m && (p = update[k])->forward[k] == q;
+ k++) {
+ p->forward[k] = q->forward[k];
+ }
+ --(l->level_stats[k - 1]);
+#ifdef SKIPLIST_DEBUG
+ zlog_debug("%s: decremented level_stats @%p:%d, now %d",
+ __func__, l, k - 1, l->level_stats[k - 1]);
+#endif
+ if (l->del)
+ (*l->del)(q->value);
+ XFREE(MTYPE_SKIP_LIST_NODE, q);
+ while (l->header->forward[m] == NULL && m > 0)
+ m--;
+ l->level = m;
+ CHECKLAST(l);
+ --(l->count);
+ return 0;
+ }
+ }
+
+ CHECKLAST(l);
+ return -1;
+}
+
+/*
+ * Obtain first value matching "key". Unless SKIPLIST_FLAG_ALLOW_DUPLICATES
+ * is set, this will also be the only value matching "key".
+ *
+ * Also set a cursor for use with skiplist_next_value.
+ */
+int skiplist_first_value(register struct skiplist *l, /* in */
+ register const void *key, /* in */
+ void **valuePointer, /* out */
+ void **cursor) /* out */
+{
+ register int k;
+ register struct skiplistnode *p, *q;
+
+ p = l->header;
+ k = l->level;
+
+ do {
+ while (q = p->forward[k], q && (*l->cmp)(q->key, key) < 0)
+ p = q;
+
+ } while (--k >= 0);
+
+ if (!q || (*l->cmp)(q->key, key))
+ return -1;
+
+ if (valuePointer)
+ *valuePointer = q->value;
+
+ if (cursor)
+ *cursor = q;
+
+ return 0;
+}
+
+int skiplist_search(register struct skiplist *l, register void *key,
+ void **valuePointer)
+{
+ return skiplist_first_value(l, key, valuePointer, NULL);
+}
+
+
+/*
+ * Caller supplies key and value of an existing item in the list.
+ * Function returns the value of the next list item that has the
+ * same key (useful when SKIPLIST_FLAG_ALLOW_DUPLICATES is set).
+ *
+ * Returns 0 on success. If the caller-supplied key and value
+ * do not correspond to a list element, or if they specify the
+ * last element with the given key, -1 is returned.
+ */
+int skiplist_next_value(register struct skiplist *l, /* in */
+ register const void *key, /* in */
+ void **valuePointer, /* in/out */
+ void **cursor) /* in/out */
+{
+ register int k;
+ register struct skiplistnode *p, *q;
+
+ CHECKLAST(l);
+
+ if (!(l->flags & SKIPLIST_FLAG_ALLOW_DUPLICATES)) {
+ return -1;
+ }
+
+ if (!cursor || !*cursor) {
+ p = l->header;
+ k = l->level;
+
+ /*
+ * Find matching key
+ */
+ do {
+ while (q = p->forward[k],
+ q && (*l->cmp)(q->key, key) < 0)
+ p = q;
+ } while (--k >= 0);
+
+ /*
+ * Find matching value
+ */
+ while (q && ((*l->cmp)(q->key, key) == 0)
+ && (q->value != *valuePointer)) {
+ q = q->forward[0];
+ }
+
+ if (!q || ((*l->cmp)(q->key, key) != 0)
+ || (q->value != *valuePointer)) {
+ /*
+ * No matching value
+ */
+ CHECKLAST(l);
+ return -1;
+ }
+ } else {
+ q = (struct skiplistnode *)*cursor;
+ }
+
+ /*
+ * Advance cursor
+ */
+ q = q->forward[0];
+
+ /*
+ * If we reached end-of-list or if the key is no longer the same,
+ * then return error
+ */
+ if (!q || ((*l->cmp)(q->key, key) != 0))
+ return -1;
+
+ *valuePointer = q->value;
+ if (cursor)
+ *cursor = q;
+ CHECKLAST(l);
+ return 0;
+}
+
+int skiplist_first(register struct skiplist *l, void **keyPointer,
+ void **valuePointer)
+{
+ register struct skiplistnode *p;
+
+ CHECKLAST(l);
+ p = l->header->forward[0];
+ if (!p)
+ return -1;
+
+ if (keyPointer)
+ *keyPointer = p->key;
+
+ if (valuePointer)
+ *valuePointer = p->value;
+
+ CHECKLAST(l);
+
+ return 0;
+}
+
+int skiplist_last(register struct skiplist *l, void **keyPointer,
+ void **valuePointer)
+{
+ CHECKLAST(l);
+ if (l->last) {
+ if (keyPointer)
+ *keyPointer = l->last->key;
+ if (valuePointer)
+ *valuePointer = l->last->value;
+ return 0;
+ }
+ return -1;
+}
+
+/*
+ * true = empty
+ */
+int skiplist_empty(register struct skiplist *l)
+{
+ CHECKLAST(l);
+ if (l->last)
+ return 0;
+ return 1;
+}
+
+/*
+ * Use this to walk the list. Caller sets *cursor to NULL to obtain
+ * first element. Return value of 0 indicates valid cursor/element
+ * returned, otherwise NULL cursor arg or EOL.
+ */
+int skiplist_next(register struct skiplist *l, /* in */
+ void **keyPointer, /* out */
+ void **valuePointer, /* out */
+ void **cursor) /* in/out */
+{
+ struct skiplistnode *p;
+
+ if (!cursor)
+ return -1;
+
+ CHECKLAST(l);
+
+ if (!*cursor) {
+ p = l->header->forward[0];
+ } else {
+ p = *cursor;
+ p = p->forward[0];
+ }
+ *cursor = p;
+
+ if (!p)
+ return -1;
+
+ if (keyPointer)
+ *keyPointer = p->key;
+
+ if (valuePointer)
+ *valuePointer = p->value;
+
+ CHECKLAST(l);
+
+ return 0;
+}
+
+int skiplist_delete_first(register struct skiplist *l)
+{
+ register int k;
+ register struct skiplistnode *p, *q;
+ int nodelevel = 0;
+
+ CHECKLAST(l);
+
+ p = l->header;
+ q = l->header->forward[0];
+
+ if (!q)
+ return -1;
+
+ for (k = l->level; k >= 0; --k) {
+ if (p->forward[k] == q) {
+ p->forward[k] = q->forward[k];
+ if ((k == l->level) && (p->forward[k] == NULL)
+ && (l->level > 0))
+ --(l->level);
+ if (!nodelevel)
+ nodelevel = k;
+ }
+ }
+
+#ifdef SKIPLIST_0TIMER_DEBUG
+ q->flags &= ~SKIPLIST_NODE_FLAG_INSERTED;
+#endif
+ /*
+ * If we are deleting the last element of the list,
+ * update the list's "last" pointer.
+ */
+ if (l->last == q) {
+ l->last = NULL;
+ }
+
+ --(l->level_stats[nodelevel]);
+#ifdef SKIPLIST_DEBUG
+ zlog_debug("%s: decremented level_stats @%p:%d, now %d", __func__, l,
+ nodelevel, l->level_stats[nodelevel]);
+#endif
+
+ if (l->del)
+ (*l->del)(q->value);
+
+ XFREE(MTYPE_SKIP_LIST_NODE, q);
+
+ CHECKLAST(l);
+
+ --(l->count);
+
+ return 0;
+}
+
+void skiplist_debug(struct vty *vty, struct skiplist *l)
+{
+ int i;
+
+ if (!l)
+ return;
+
+ vty_out(vty, "Skiplist %p has max level %d\n", l, l->level);
+ for (i = l->level; i >= 0; --i)
+ vty_out(vty, " @%d: %d\n", i, l->level_stats[i]);
+}
+
+static void *scramble(int i)
+{
+ uintptr_t result;
+
+ result = (unsigned)(i & 0xff) << 24;
+ result |= (unsigned)i >> 8;
+
+ return (void *)result;
+}
+
+#define sampleSize 65536
+void skiplist_test(struct vty *vty)
+{
+ struct skiplist *l;
+ register int i, k;
+ void *keys[sampleSize];
+ void *v = NULL;
+
+ zlog_debug("%s: entry", __func__);
+
+ l = skiplist_new(SKIPLIST_FLAG_ALLOW_DUPLICATES, NULL, NULL);
+
+ zlog_debug("%s: skiplist_new returned %p", __func__, l);
+
+ for (i = 0; i < 4; i++) {
+
+ for (k = 0; k < sampleSize; k++) {
+ if (!(k % 1000)) {
+ zlog_debug("%s: (%d:%d)", __func__, i, k);
+ }
+ // keys[k] = (void *)random();
+ keys[k] = scramble(k);
+ if (skiplist_insert(l, keys[k], keys[k]))
+ zlog_debug("error in insert #%d,#%d", i, k);
+ }
+
+ zlog_debug("%s: inserts done", __func__);
+
+ for (k = 0; k < sampleSize; k++) {
+
+ if (!(k % 1000))
+ zlog_debug("[%d:%d]", i, k);
+ if (skiplist_search(l, keys[k], &v))
+ zlog_debug("error in search #%d,#%d", i, k);
+
+ if (v != keys[k])
+ zlog_debug("search returned wrong value");
+ }
+
+
+ for (k = 0; k < sampleSize; k++) {
+
+ if (!(k % 1000))
+ zlog_debug("<%d:%d>", i, k);
+ if (skiplist_delete(l, keys[k], keys[k]))
+ zlog_debug("error in delete");
+ keys[k] = scramble(k ^ 0xf0f0f0f0);
+ if (skiplist_insert(l, keys[k], keys[k]))
+ zlog_debug("error in insert #%d,#%d", i, k);
+ }
+
+ for (k = 0; k < sampleSize; k++) {
+
+ if (!(k % 1000))
+ zlog_debug("{%d:%d}", i, k);
+ if (skiplist_delete_first(l))
+ zlog_debug("error in delete_first");
+ }
+ }
+
+ skiplist_free(l);
+}
diff --git a/lib/skiplist.h b/lib/skiplist.h
new file mode 100644
index 0000000..1656078
--- /dev/null
+++ b/lib/skiplist.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright 1990 William Pugh
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Permission to include in quagga provide on March 31, 2016
+ */
+
+/*
+ * Skip List implementation based on code from William Pugh.
+ * ftp://ftp.cs.umd.edu/pub/skipLists/
+ */
+
+/* skiplist.h */
+
+
+#ifndef _ZEBRA_SKIPLIST_H
+#define _ZEBRA_SKIPLIST_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define SKIPLIST_0TIMER_DEBUG 1
+
+/*
+ * skiplistnodes must always contain data to be valid. Adding an
+ * empty node to a list is invalid
+ */
+struct skiplistnode {
+ void *key;
+ void *value;
+#if SKIPLIST_0TIMER_DEBUG
+ int flags;
+#define SKIPLIST_NODE_FLAG_INSERTED 0x00000001
+#endif
+
+ struct skiplistnode *forward[1]; /* variable sized */
+};
+
+struct skiplist {
+ int flags;
+
+#define SKIPLIST_FLAG_ALLOW_DUPLICATES 0x00000001
+
+ int level; /* max lvl (1 + current # of levels in list) */
+ unsigned int count;
+ struct skiplistnode *header;
+ int *level_stats;
+ struct skiplistnode
+ *last; /* last real list item (NULL if empty list) */
+
+ /*
+ * Returns -1 if val1 < val2, 0 if equal?, 1 if val1 > val2.
+ * Used as definition of sorted for listnode_add_sort
+ */
+ int (*cmp)(const void *val1, const void *val2);
+
+ /* callback to free user-owned data when listnode is deleted. supplying
+ * this callback is very much encouraged!
+ */
+ void (*del)(void *val);
+};
+
+
+/* Prototypes. */
+extern struct skiplist *
+skiplist_new(/* encouraged: set list.del callback on new lists */
+ int flags,
+ int (*cmp)(const void *key1,
+ const void *key2), /* NULL => default cmp */
+ void (*del)(void *val)); /* NULL => no auto val free */
+
+extern void skiplist_free(struct skiplist *);
+
+extern int skiplist_insert(register struct skiplist *l, register void *key,
+ register void *value);
+
+extern int skiplist_delete(register struct skiplist *l, register void *key,
+ register void *value);
+
+extern int skiplist_search(register struct skiplist *l, register void *key,
+ void **valuePointer);
+
+extern int skiplist_first_value(register struct skiplist *l, /* in */
+ register const void *key, /* in */
+ void **valuePointer, /* in/out */
+ void **cursor); /* out */
+
+extern int skiplist_next_value(register struct skiplist *l, /* in */
+ register const void *key, /* in */
+ void **valuePointer, /* in/out */
+ void **cursor); /* in/out */
+
+extern int skiplist_first(register struct skiplist *l, void **keyPointer,
+ void **valuePointer);
+
+extern int skiplist_last(register struct skiplist *l, void **keyPointer,
+ void **valuePointer);
+
+extern int skiplist_delete_first(register struct skiplist *l);
+
+extern int skiplist_next(register struct skiplist *l, /* in */
+ void **keyPointer, /* out */
+ void **valuePointer, /* out */
+ void **cursor); /* in/out */
+
+extern int skiplist_empty(register struct skiplist *l); /* in */
+
+extern unsigned int skiplist_count(register struct skiplist *l); /* in */
+
+struct vty;
+extern void skiplist_debug(struct vty *vty, struct skiplist *l);
+
+extern void skiplist_test(struct vty *vty);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ZEBRA_SKIPLIST_H */
diff --git a/lib/smux.h b/lib/smux.h
new file mode 100644
index 0000000..7444734
--- /dev/null
+++ b/lib/smux.h
@@ -0,0 +1,167 @@
+/* SNMP support
+ * Copyright (C) 1999 Kunihiro Ishiguro <kunihiro@zebra.org>
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 _ZEBRA_SNMP_H
+#define _ZEBRA_SNMP_H
+
+#include <net-snmp/agent/net-snmp-agent-includes.h>
+#include <net-snmp/agent/snmp_vars.h>
+
+#include "thread.h"
+#include "hook.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Structures here are mostly compatible with UCD SNMP 4.1.1 */
+#define MATCH_FAILED (-1)
+#define MATCH_SUCCEEDED 0
+
+/* SYNTAX TruthValue from SNMPv2-TC. */
+#define SNMP_TRUE 1
+#define SNMP_FALSE 2
+
+/* SYNTAX RowStatus from SNMPv2-TC. */
+#define SNMP_VALID 1
+#define SNMP_INVALID 2
+
+#define IN_ADDR_SIZE sizeof(struct in_addr)
+
+/* IANAipRouteProtocol */
+#define IANAIPROUTEPROTOCOLOTHER 1
+#define IANAIPROUTEPROTOCOLLOCAL 2
+#define IANAIPROUTEPROTOCOLNETMGMT 3
+#define IANAIPROUTEPROTOCOLICMP 4
+#define IANAIPROUTEPROTOCOLEGP 5
+#define IANAIPROUTEPROTOCOLGGP 6
+#define IANAIPROUTEPROTOCOLHELLO 7
+#define IANAIPROUTEPROTOCOLRIP 8
+#define IANAIPROUTEPROTOCOLISIS 9
+#define IANAIPROUTEPROTOCOLESIS 10
+#define IANAIPROUTEPROTOCOLCISCOIGRP 11
+#define IANAIPROUTEPROTOCOLBBNSPFIGP 12
+#define IANAIPROUTEPROTOCOLOSPF 13
+#define IANAIPROUTEPROTOCOLBGP 14
+#define IANAIPROUTEPROTOCOLIDPR 15
+#define IANAIPROUTEPROTOCOLCISCOEIGRP 16
+#define IANAIPROUTEPROTOCOLDVMRP 17
+
+#define INETADDRESSTYPEUNKNOWN 0
+#define INETADDRESSTYPEIPV4 1
+#define INETADDRESSTYPEIPV6 2
+
+#undef REGISTER_MIB
+#define REGISTER_MIB(descr, var, vartype, theoid) \
+ smux_register_mib(descr, (struct variable *)var, \
+ sizeof(struct vartype), \
+ sizeof(var) / sizeof(struct vartype), theoid, \
+ sizeof(theoid) / sizeof(oid))
+
+struct trap_object {
+ int namelen; /* Negative if the object is not indexed */
+ oid name[MAX_OID_LEN];
+};
+
+struct index_oid {
+ int indexlen;
+ oid indexname[MAX_OID_LEN];
+};
+/* Declare SMUX return value. */
+#define SNMP_LOCAL_VARIABLES \
+ static long snmp_int_val __attribute__((unused)); \
+ static struct in_addr snmp_in_addr_val __attribute__((unused));
+ static uint8_t snmp_octet_val __attribute__((unused));
+#define SNMP_INTEGER(V) \
+ (*var_len = sizeof(snmp_int_val), snmp_int_val = V, \
+ (uint8_t *)&snmp_int_val)
+
+#define SNMP_OCTET(V) \
+ (*var_len = sizeof(snmp_octet_val), snmp_octet_val = V, \
+ (uint8_t *)&snmp_octet_val)
+
+#define SNMP_IPADDRESS(V) \
+ (*var_len = sizeof(struct in_addr), snmp_in_addr_val = V, \
+ (uint8_t *)&snmp_in_addr_val)
+
+#define SNMP_IP6ADDRESS(V) (*var_len = sizeof(struct in6_addr), (uint8_t *)&V)
+
+extern int smux_enabled(void);
+
+extern void smux_init(struct thread_master *tm);
+extern void smux_agentx_enable(void);
+extern void smux_register_mib(const char *, struct variable *, size_t, int,
+ oid[], size_t);
+extern int smux_header_generic(struct variable *, oid[], size_t *, int,
+ size_t *, WriteMethod **);
+extern int smux_header_table(struct variable *, oid *, size_t *, int, size_t *,
+ WriteMethod **);
+
+/* For traps, three OID are provided:
+
+ 1. The enterprise OID to use (the last argument will be appended to
+ it to form the SNMP trap OID)
+
+ 2. The base OID for objects to be sent in traps.
+
+ 3. The index OID for objects to be sent in traps. This index is used
+ to designate a particular instance of a column.
+
+ The provided trap object contains the bindings to be sent with the
+ trap. The base OID will be prefixed to the provided OID and, if the
+ length is positive, the requested OID is assumed to be a columnar
+ object and the index OID will be appended.
+
+ The two first arguments are the MIB registry used to locate the trap
+ objects.
+
+ The use of the arguments may differ depending on the implementation
+ used.
+*/
+extern void smux_trap(struct variable *, size_t, const oid *, size_t,
+ const oid *, size_t, const oid *, size_t,
+ const struct trap_object *, size_t, uint8_t);
+
+extern int smux_trap_multi_index(struct variable *vp, size_t vp_len,
+ const oid *ename, size_t enamelen,
+ const oid *name, size_t namelen,
+ struct index_oid *iname, size_t index_len,
+ const struct trap_object *trapobj,
+ size_t trapobjlen, uint8_t sptrap);
+
+extern void smux_events_update(void);
+extern int oid_compare(const oid *, int, const oid *, int);
+extern void oid2in_addr(oid[], int, struct in_addr *);
+extern void oid2in6_addr(oid oid[], struct in6_addr *addr);
+extern void oid2int(oid oid[], int *dest);
+extern void *oid_copy(void *, const void *, size_t);
+extern void oid_copy_in_addr(oid[], const struct in_addr *);
+extern void oid_copy_in6_addr(oid[], const struct in6_addr *);
+extern void oid_copy_int(oid oid[], int *val);
+extern void oid2string(oid oid[], int len, char *string);
+extern void oid_copy_str(oid oid[], const char *string, int len);
+
+DECLARE_HOOK(agentx_enabled, (), ());
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ZEBRA_SNMP_H */
diff --git a/lib/snmp.c b/lib/snmp.c
new file mode 100644
index 0000000..8d8b3c9
--- /dev/null
+++ b/lib/snmp.c
@@ -0,0 +1,196 @@
+/* SNMP support
+ * Copyright (C) 1999 Kunihiro Ishiguro <kunihiro@zebra.org>
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 <net-snmp/net-snmp-config.h>
+#include <net-snmp/net-snmp-includes.h>
+
+#include "smux.h"
+
+int oid_compare(const oid *o1, int o1_len, const oid *o2, int o2_len)
+{
+ int i;
+
+ for (i = 0; i < MIN(o1_len, o2_len); i++) {
+ if (o1[i] < o2[i])
+ return -1;
+ else if (o1[i] > o2[i])
+ return 1;
+ }
+ if (o1_len < o2_len)
+ return -1;
+ if (o1_len > o2_len)
+ return 1;
+
+ return 0;
+}
+
+void *oid_copy(void *dest, const void *src, size_t size)
+{
+ return memcpy(dest, src, size * sizeof(oid));
+}
+
+void oid2in_addr(oid oid[], int len, struct in_addr *addr)
+{
+ int i;
+ uint8_t *pnt;
+
+ if (len == 0)
+ return;
+
+ pnt = (uint8_t *)addr;
+
+ for (i = 0; i < len; i++)
+ *pnt++ = oid[i];
+}
+
+void oid2in6_addr(oid oid[], struct in6_addr *addr)
+{
+ unsigned int i;
+ uint8_t *pnt;
+
+ pnt = (uint8_t *)addr;
+
+ for (i = 0; i < sizeof(struct in6_addr); i++)
+ *pnt++ = oid[i];
+}
+
+void oid2int(oid oid[], int *dest)
+{
+ uint8_t i;
+ uint8_t *pnt;
+ int network_dest;
+
+ pnt = (uint8_t *)&network_dest;
+
+ for (i = 0; i < sizeof(int); i++)
+ *pnt++ = oid[i];
+ *dest = ntohl(network_dest);
+}
+
+void oid_copy_in_addr(oid oid[], const struct in_addr *addr)
+{
+ int i;
+ const uint8_t *pnt;
+ int len = sizeof(struct in_addr);
+
+ pnt = (uint8_t *)addr;
+
+ for (i = 0; i < len; i++)
+ oid[i] = *pnt++;
+}
+
+
+void oid_copy_in6_addr(oid oid[], const struct in6_addr *addr)
+{
+ int i;
+ const uint8_t *pnt;
+ int len = sizeof(struct in6_addr);
+
+ pnt = (uint8_t *)addr;
+
+ for (i = 0; i < len; i++)
+ oid[i] = *pnt++;
+}
+
+void oid_copy_int(oid oid[], int *val)
+{
+ uint8_t i;
+ const uint8_t *pnt;
+ int network_val;
+
+ network_val = htonl(*val);
+ pnt = (uint8_t *)&network_val;
+
+ for (i = 0; i < sizeof(int); i++)
+ oid[i] = *pnt++;
+}
+
+void oid2string(oid oid[], int len, char *string)
+{
+ int i;
+ uint8_t *pnt;
+
+ if (len == 0)
+ return;
+
+ pnt = (uint8_t *)string;
+
+ for (i = 0; i < len; i++)
+ *pnt++ = (uint8_t)oid[i];
+}
+
+void oid_copy_str(oid oid[], const char *string, int len)
+{
+ int i;
+ const uint8_t *pnt;
+
+ if (len == 0)
+ return;
+
+ pnt = (uint8_t *)string;
+
+ for (i = 0; i < len; i++)
+ oid[i] = *pnt++;
+}
+
+int smux_header_generic(struct variable *v, oid *name, size_t *length,
+ int exact, size_t *var_len, WriteMethod **write_method)
+{
+ oid fulloid[MAX_OID_LEN];
+ int ret;
+
+ oid_copy(fulloid, v->name, v->namelen);
+ fulloid[v->namelen] = 0;
+ /* Check against full instance. */
+ ret = oid_compare(name, *length, fulloid, v->namelen + 1);
+
+ /* Check single instance. */
+ if ((exact && (ret != 0)) || (!exact && (ret >= 0)))
+ return MATCH_FAILED;
+
+ /* In case of getnext, fill in full instance. */
+ memcpy(name, fulloid, (v->namelen + 1) * sizeof(oid));
+ *length = v->namelen + 1;
+
+ *write_method = 0;
+ *var_len = sizeof(long); /* default to 'long' results */
+
+ return MATCH_SUCCEEDED;
+}
+
+int smux_header_table(struct variable *v, oid *name, size_t *length, int exact,
+ size_t *var_len, WriteMethod **write_method)
+{
+ /* If the requested OID name is less than OID prefix we
+ handle, adjust it to our prefix. */
+ if ((oid_compare(name, *length, v->name, v->namelen)) < 0) {
+ if (exact)
+ return MATCH_FAILED;
+ oid_copy(name, v->name, v->namelen);
+ *length = v->namelen;
+ }
+
+ *write_method = 0;
+ *var_len = sizeof(long);
+
+ return MATCH_SUCCEEDED;
+}
diff --git a/lib/sockopt.c b/lib/sockopt.c
new file mode 100644
index 0000000..7a2b8a1
--- /dev/null
+++ b/lib/sockopt.c
@@ -0,0 +1,695 @@
+/* setsockopt functions
+ * Copyright (C) 1999 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 "sockopt.h"
+#include "sockunion.h"
+#include "lib_errors.h"
+
+#if (defined(__FreeBSD__) \
+ && ((__FreeBSD_version >= 500022 && __FreeBSD_version < 700000) \
+ || (__FreeBSD_version < 500000 && __FreeBSD_version >= 440000))) \
+ || (defined(__NetBSD__) && defined(__NetBSD_Version__) \
+ && __NetBSD_Version__ >= 106010000) \
+ || defined(__OpenBSD__) || defined(__APPLE__) \
+ || defined(__DragonFly__) || defined(__sun)
+#define HAVE_BSD_STRUCT_IP_MREQ_HACK
+#endif
+
+void setsockopt_so_recvbuf(int sock, int size)
+{
+ int orig_req = size;
+
+ while (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size))
+ == -1)
+ size /= 2;
+
+ if (size != orig_req)
+ flog_err(EC_LIB_SOCKET,
+ "%s: fd %d: SO_RCVBUF set to %d (requested %d)",
+ __func__, sock, size, orig_req);
+}
+
+void setsockopt_so_sendbuf(const int sock, int size)
+{
+ int orig_req = size;
+
+ while (setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size))
+ == -1)
+ size /= 2;
+
+ if (size != orig_req)
+ flog_err(EC_LIB_SOCKET,
+ "%s: fd %d: SO_SNDBUF set to %d (requested %d)",
+ __func__, sock, size, orig_req);
+}
+
+int getsockopt_so_sendbuf(const int sock)
+{
+ uint32_t optval;
+ socklen_t optlen = sizeof(optval);
+ int ret = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)&optval,
+ &optlen);
+ if (ret < 0) {
+ flog_err_sys(EC_LIB_SYSTEM_CALL,
+ "fd %d: can't getsockopt SO_SNDBUF: %d (%s)", sock,
+ errno, safe_strerror(errno));
+ return ret;
+ }
+ return optval;
+}
+
+int getsockopt_so_recvbuf(const int sock)
+{
+ uint32_t optval;
+ socklen_t optlen = sizeof(optval);
+ int ret = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)&optval,
+ &optlen);
+ if (ret < 0) {
+ flog_err_sys(EC_LIB_SYSTEM_CALL,
+ "fd %d: can't getsockopt SO_RCVBUF: %d (%s)", sock,
+ errno, safe_strerror(errno));
+ return ret;
+ }
+ return optval;
+}
+
+static void *getsockopt_cmsg_data(struct msghdr *msgh, int level, int type)
+{
+ struct cmsghdr *cmsg;
+
+ for (cmsg = CMSG_FIRSTHDR(msgh); cmsg != NULL;
+ cmsg = CMSG_NXTHDR(msgh, cmsg))
+ if (cmsg->cmsg_level == level && cmsg->cmsg_type == type)
+ return CMSG_DATA(cmsg);
+
+ return NULL;
+}
+
+/* Set IPv6 packet info to the socket. */
+int setsockopt_ipv6_pktinfo(int sock, int val)
+{
+ int ret;
+
+#ifdef IPV6_RECVPKTINFO /*2292bis-01*/
+ ret = setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val,
+ sizeof(val));
+ if (ret < 0)
+ flog_err(EC_LIB_SOCKET,
+ "can't setsockopt IPV6_RECVPKTINFO : %s",
+ safe_strerror(errno));
+#else /*RFC2292*/
+ ret = setsockopt(sock, IPPROTO_IPV6, IPV6_PKTINFO, &val, sizeof(val));
+ if (ret < 0)
+ flog_err(EC_LIB_SOCKET, "can't setsockopt IPV6_PKTINFO : %s",
+ safe_strerror(errno));
+#endif /* IANA_IPV6 */
+ return ret;
+}
+
+/* Set multicast hops val to the socket. */
+int setsockopt_ipv6_multicast_hops(int sock, int val)
+{
+ int ret;
+
+ ret = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val,
+ sizeof(val));
+ if (ret < 0)
+ flog_err(EC_LIB_SOCKET, "can't setsockopt IPV6_MULTICAST_HOPS");
+ return ret;
+}
+
+/* Set multicast hops val to the socket. */
+int setsockopt_ipv6_unicast_hops(int sock, int val)
+{
+ int ret;
+
+ ret = setsockopt(sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val,
+ sizeof(val));
+ if (ret < 0)
+ flog_err(EC_LIB_SOCKET, "can't setsockopt IPV6_UNICAST_HOPS");
+ return ret;
+}
+
+int setsockopt_ipv6_hoplimit(int sock, int val)
+{
+ int ret;
+
+#ifdef IPV6_RECVHOPLIMIT /*2292bis-01*/
+ ret = setsockopt(sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &val,
+ sizeof(val));
+ if (ret < 0)
+ flog_err(EC_LIB_SOCKET, "can't setsockopt IPV6_RECVHOPLIMIT");
+#else /*RFC2292*/
+ ret = setsockopt(sock, IPPROTO_IPV6, IPV6_HOPLIMIT, &val, sizeof(val));
+ if (ret < 0)
+ flog_err(EC_LIB_SOCKET, "can't setsockopt IPV6_HOPLIMIT");
+#endif
+ return ret;
+}
+
+/* Set multicast loop zero to the socket. */
+int setsockopt_ipv6_multicast_loop(int sock, int val)
+{
+ int ret;
+
+ ret = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val,
+ sizeof(val));
+ if (ret < 0)
+ flog_err(EC_LIB_SOCKET, "can't setsockopt IPV6_MULTICAST_LOOP");
+ return ret;
+}
+
+static int getsockopt_ipv6_ifindex(struct msghdr *msgh)
+{
+ struct in6_pktinfo *pktinfo;
+
+ pktinfo = getsockopt_cmsg_data(msgh, IPPROTO_IPV6, IPV6_PKTINFO);
+
+ return pktinfo->ipi6_ifindex;
+}
+
+int setsockopt_ipv6_tclass(int sock, int tclass)
+{
+ int ret = 0;
+
+#ifdef IPV6_TCLASS /* RFC3542 */
+ ret = setsockopt(sock, IPPROTO_IPV6, IPV6_TCLASS, &tclass,
+ sizeof(tclass));
+ if (ret < 0)
+ flog_err(EC_LIB_SOCKET,
+ "Can't set IPV6_TCLASS option for fd %d to %#x: %s",
+ sock, tclass, safe_strerror(errno));
+#endif
+ return ret;
+}
+
+/*
+ * Process multicast socket options for IPv4 in an OS-dependent manner.
+ * Supported options are IP_{ADD,DROP}_MEMBERSHIP.
+ *
+ * Many operating systems have a limit on the number of groups that
+ * can be joined per socket (where each group and local address
+ * counts). This impacts OSPF, which joins groups on each interface
+ * using a single socket. The limit is typically 20, derived from the
+ * original BSD multicast implementation. Some systems have
+ * mechanisms for increasing this limit.
+ *
+ * In many 4.4BSD-derived systems, multicast group operations are not
+ * allowed on interfaces that are not UP. Thus, a previous attempt to
+ * leave the group may have failed, leaving it still joined, and we
+ * drop/join quietly to recover. This may not be necessary, but aims to
+ * defend against unknown behavior in that we will still return an error
+ * if the second join fails. It is not clear how other systems
+ * (e.g. Linux, Solaris) behave when leaving groups on down interfaces,
+ * but this behavior should not be harmful if they behave the same way,
+ * allow leaves, or implicitly leave all groups joined to down interfaces.
+ */
+int setsockopt_ipv4_multicast(int sock, int optname, struct in_addr if_addr,
+ unsigned int mcast_addr, ifindex_t ifindex)
+{
+#ifdef HAVE_RFC3678
+ struct group_req gr;
+ struct sockaddr_in *si;
+ int ret;
+ memset(&gr, 0, sizeof(gr));
+ si = (struct sockaddr_in *)&gr.gr_group;
+ gr.gr_interface = ifindex;
+ si->sin_family = AF_INET;
+#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN
+ si->sin_len = sizeof(struct sockaddr_in);
+#endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */
+ si->sin_addr.s_addr = mcast_addr;
+ ret = setsockopt(sock, IPPROTO_IP,
+ (optname == IP_ADD_MEMBERSHIP) ? MCAST_JOIN_GROUP
+ : MCAST_LEAVE_GROUP,
+ (void *)&gr, sizeof(gr));
+ if ((ret < 0) && (optname == IP_ADD_MEMBERSHIP)
+ && (errno == EADDRINUSE)) {
+ setsockopt(sock, IPPROTO_IP, MCAST_LEAVE_GROUP, (void *)&gr,
+ sizeof(gr));
+ ret = setsockopt(sock, IPPROTO_IP, MCAST_JOIN_GROUP,
+ (void *)&gr, sizeof(gr));
+ }
+ return ret;
+
+#elif defined(HAVE_STRUCT_IP_MREQN_IMR_IFINDEX) && !defined(__FreeBSD__)
+ struct ip_mreqn mreqn;
+ int ret;
+
+ assert(optname == IP_ADD_MEMBERSHIP || optname == IP_DROP_MEMBERSHIP);
+ memset(&mreqn, 0, sizeof(mreqn));
+
+ mreqn.imr_multiaddr.s_addr = mcast_addr;
+ mreqn.imr_ifindex = ifindex;
+
+ ret = setsockopt(sock, IPPROTO_IP, optname, (void *)&mreqn,
+ sizeof(mreqn));
+ if ((ret < 0) && (optname == IP_ADD_MEMBERSHIP)
+ && (errno == EADDRINUSE)) {
+ /* see above: handle possible problem when interface comes back
+ * up */
+ zlog_info(
+ "setsockopt_ipv4_multicast attempting to drop and re-add (fd %d, mcast %pI4, ifindex %u)",
+ sock, &mreqn.imr_multiaddr, ifindex);
+ setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, (void *)&mreqn,
+ sizeof(mreqn));
+ ret = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP,
+ (void *)&mreqn, sizeof(mreqn));
+ }
+ return ret;
+
+/* Example defines for another OS, boilerplate off other code in this
+ function, AND handle optname as per other sections for consistency !! */
+/* #elif defined(BOGON_NIX) && EXAMPLE_VERSION_CODE > -100000 */
+/* Add your favourite OS here! */
+
+#elif defined(HAVE_BSD_STRUCT_IP_MREQ_HACK) /* #if OS_TYPE */
+ /* standard BSD API */
+
+ struct ip_mreq mreq;
+ int ret;
+
+ assert(optname == IP_ADD_MEMBERSHIP || optname == IP_DROP_MEMBERSHIP);
+
+
+ memset(&mreq, 0, sizeof(mreq));
+ mreq.imr_multiaddr.s_addr = mcast_addr;
+#if !defined __OpenBSD__
+ mreq.imr_interface.s_addr = htonl(ifindex);
+#else
+ mreq.imr_interface.s_addr = if_addr.s_addr;
+#endif
+
+ ret = setsockopt(sock, IPPROTO_IP, optname, (void *)&mreq,
+ sizeof(mreq));
+ if ((ret < 0) && (optname == IP_ADD_MEMBERSHIP)
+ && (errno == EADDRINUSE)) {
+ /* see above: handle possible problem when interface comes back
+ * up */
+ zlog_info(
+ "setsockopt_ipv4_multicast attempting to drop and re-add (fd %d, mcast %pI4, ifindex %u)",
+ sock, &mreq.imr_multiaddr, ifindex);
+ setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, (void *)&mreq,
+ sizeof(mreq));
+ ret = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP,
+ (void *)&mreq, sizeof(mreq));
+ }
+ return ret;
+
+#else
+#error "Unsupported multicast API"
+#endif /* #if OS_TYPE */
+}
+
+/*
+ * Set IP_MULTICAST_IF socket option in an OS-dependent manner.
+ */
+int setsockopt_ipv4_multicast_if(int sock, struct in_addr if_addr,
+ ifindex_t ifindex)
+{
+
+#ifdef HAVE_STRUCT_IP_MREQN_IMR_IFINDEX
+ struct ip_mreqn mreqn;
+ memset(&mreqn, 0, sizeof(mreqn));
+
+ mreqn.imr_ifindex = ifindex;
+ return setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, (void *)&mreqn,
+ sizeof(mreqn));
+
+/* Example defines for another OS, boilerplate off other code in this
+ function */
+/* #elif defined(BOGON_NIX) && EXAMPLE_VERSION_CODE > -100000 */
+/* Add your favourite OS here! */
+#elif defined(HAVE_BSD_STRUCT_IP_MREQ_HACK)
+ struct in_addr m;
+
+#if !defined __OpenBSD__
+ m.s_addr = htonl(ifindex);
+#else
+ m.s_addr = if_addr.s_addr;
+#endif
+
+ return setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, (void *)&m,
+ sizeof(m));
+#else
+#error "Unsupported multicast API"
+#endif
+}
+
+int setsockopt_ipv4_multicast_loop(int sock, uint8_t val)
+{
+ int ret;
+
+ ret = setsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, (void *)&val,
+ sizeof(val));
+ if (ret < 0)
+ flog_err(EC_LIB_SOCKET, "can't setsockopt IP_MULTICAST_LOOP");
+
+ return ret;
+}
+
+static int setsockopt_ipv4_ifindex(int sock, ifindex_t val)
+{
+ int ret;
+
+#if defined(IP_PKTINFO)
+ ret = setsockopt(sock, IPPROTO_IP, IP_PKTINFO, &val, sizeof(val));
+ if (ret < 0)
+ flog_err(EC_LIB_SOCKET,
+ "Can't set IP_PKTINFO option for fd %d to %d: %s",
+ sock, val, safe_strerror(errno));
+#elif defined(IP_RECVIF)
+ ret = setsockopt(sock, IPPROTO_IP, IP_RECVIF, &val, sizeof(val));
+ if (ret < 0)
+ flog_err(EC_LIB_SOCKET,
+ "Can't set IP_RECVIF option for fd %d to %d: %s", sock,
+ val, safe_strerror(errno));
+#else
+#warning "Neither IP_PKTINFO nor IP_RECVIF is available."
+#warning "Will not be able to receive link info."
+#warning "Things might be seriously broken.."
+ /* XXX Does this ever happen? Should there be a zlog_warn message here?
+ */
+ ret = -1;
+#endif
+ return ret;
+}
+
+int setsockopt_ipv4_tos(int sock, int tos)
+{
+ int ret;
+
+ ret = setsockopt(sock, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
+ if (ret < 0)
+ flog_err(EC_LIB_SOCKET,
+ "Can't set IP_TOS option for fd %d to %#x: %s", sock,
+ tos, safe_strerror(errno));
+ return ret;
+}
+
+
+int setsockopt_ifindex(int af, int sock, ifindex_t val)
+{
+ int ret = -1;
+
+ switch (af) {
+ case AF_INET:
+ ret = setsockopt_ipv4_ifindex(sock, val);
+ break;
+ case AF_INET6:
+ ret = setsockopt_ipv6_pktinfo(sock, val);
+ break;
+ default:
+ flog_err(EC_LIB_DEVELOPMENT,
+ "setsockopt_ifindex: unknown address family %d", af);
+ }
+ return ret;
+}
+
+/*
+ * Requires: msgh is not NULL and points to a valid struct msghdr, which
+ * may or may not have control data about the incoming interface.
+ *
+ * Returns the interface index (small integer >= 1) if it can be
+ * determined, or else 0.
+ */
+static ifindex_t getsockopt_ipv4_ifindex(struct msghdr *msgh)
+{
+ ifindex_t ifindex;
+
+#if defined(IP_PKTINFO)
+ /* Linux pktinfo based ifindex retrieval */
+ struct in_pktinfo *pktinfo;
+
+ pktinfo = (struct in_pktinfo *)getsockopt_cmsg_data(msgh, IPPROTO_IP,
+ IP_PKTINFO);
+
+ /* getsockopt_ifindex() will forward this, being 0 "not found" */
+ if (pktinfo == NULL)
+ return 0;
+
+ ifindex = pktinfo->ipi_ifindex;
+
+#elif defined(IP_RECVIF)
+
+/* retrieval based on IP_RECVIF */
+
+ /* BSD systems use a sockaddr_dl as the control message payload. */
+ struct sockaddr_dl *sdl;
+
+ /* BSD */
+ sdl = (struct sockaddr_dl *)getsockopt_cmsg_data(msgh, IPPROTO_IP,
+ IP_RECVIF);
+ if (sdl != NULL)
+ ifindex = sdl->sdl_index;
+ else
+ ifindex = 0;
+
+#else
+/*
+ * Neither IP_PKTINFO nor IP_RECVIF defined - warn at compile time.
+ * XXX Decide if this is a core service, or if daemons have to cope.
+ * Since Solaris 8 and OpenBSD seem not to provide it, it seems that
+ * daemons have to cope.
+ */
+#warning "getsockopt_ipv4_ifindex: Neither IP_PKTINFO nor IP_RECVIF defined."
+#warning "Some daemons may fail to operate correctly!"
+ ifindex = 0;
+
+#endif /* IP_PKTINFO */
+
+ return ifindex;
+}
+
+/* return ifindex, 0 if none found */
+ifindex_t getsockopt_ifindex(int af, struct msghdr *msgh)
+{
+ switch (af) {
+ case AF_INET:
+ return (getsockopt_ipv4_ifindex(msgh));
+ case AF_INET6:
+ return (getsockopt_ipv6_ifindex(msgh));
+ default:
+ flog_err(EC_LIB_DEVELOPMENT,
+ "getsockopt_ifindex: unknown address family %d", af);
+ return 0;
+ }
+}
+
+/* swab iph between order system uses for IP_HDRINCL and host order */
+void sockopt_iphdrincl_swab_htosys(struct ip *iph)
+{
+/* BSD and derived take iph in network order, except for
+ * ip_len and ip_off
+ */
+#ifndef HAVE_IP_HDRINCL_BSD_ORDER
+ iph->ip_len = htons(iph->ip_len);
+ iph->ip_off = htons(iph->ip_off);
+#endif /* HAVE_IP_HDRINCL_BSD_ORDER */
+
+ iph->ip_id = htons(iph->ip_id);
+}
+
+void sockopt_iphdrincl_swab_systoh(struct ip *iph)
+{
+#ifndef HAVE_IP_HDRINCL_BSD_ORDER
+ iph->ip_len = ntohs(iph->ip_len);
+ iph->ip_off = ntohs(iph->ip_off);
+#endif /* HAVE_IP_HDRINCL_BSD_ORDER */
+
+ iph->ip_id = ntohs(iph->ip_id);
+}
+
+int sockopt_tcp_rtt(int sock)
+{
+#ifdef TCP_INFO
+ struct tcp_info ti;
+ socklen_t len = sizeof(ti);
+
+ if (getsockopt(sock, IPPROTO_TCP, TCP_INFO, &ti, &len) != 0)
+ return 0;
+
+ return ti.tcpi_rtt / 1000;
+#else
+ return 0;
+#endif
+}
+
+int sockopt_tcp_signature_ext(int sock, union sockunion *su, uint16_t prefixlen,
+ const char *password)
+{
+#ifndef HAVE_DECL_TCP_MD5SIG
+ /*
+ * We have been asked to enable MD5 auth for an address, but our
+ * platform doesn't support that
+ */
+ return -2;
+#endif
+
+#ifndef TCP_MD5SIG_EXT
+ /*
+ * We have been asked to enable MD5 auth for a prefix, but our platform
+ * doesn't support that
+ */
+ if (prefixlen > 0)
+ return -2;
+#endif
+
+#if HAVE_DECL_TCP_MD5SIG
+ int ret;
+
+ int optname = TCP_MD5SIG;
+#ifndef GNU_LINUX
+ /*
+ * XXX Need to do PF_KEY operation here to add/remove an SA entry,
+ * and add/remove an SP entry for this peer's packet flows also.
+ */
+ int md5sig = password && *password ? 1 : 0;
+#else
+ int keylen = password ? strlen(password) : 0;
+ struct tcp_md5sig md5sig;
+ union sockunion *su2, *susock;
+
+ /* Figure out whether the socket and the sockunion are the same family..
+ * adding AF_INET to AF_INET6 needs to be v4 mapped, you'd think..
+ */
+ if (!(susock = sockunion_getsockname(sock)))
+ return -1;
+
+ if (susock->sa.sa_family == su->sa.sa_family)
+ su2 = su;
+ else {
+ /* oops.. */
+ su2 = susock;
+
+ if (su2->sa.sa_family == AF_INET) {
+ sockunion_free(susock);
+ return 0;
+ }
+
+ /* If this does not work, then all users of this sockopt will
+ * need to
+ * differentiate between IPv4 and IPv6, and keep separate
+ * sockets for
+ * each.
+ *
+ * Sadly, it doesn't seem to work at present. It's unknown
+ * whether
+ * this is a bug or not.
+ */
+ if (su2->sa.sa_family == AF_INET6
+ && su->sa.sa_family == AF_INET) {
+ su2->sin6.sin6_family = AF_INET6;
+ /* V4Map the address */
+ memset(&su2->sin6.sin6_addr, 0,
+ sizeof(struct in6_addr));
+ su2->sin6.sin6_addr.s6_addr32[2] = htonl(0xffff);
+ memcpy(&su2->sin6.sin6_addr.s6_addr32[3],
+ &su->sin.sin_addr, 4);
+ }
+ }
+
+ memset(&md5sig, 0, sizeof(md5sig));
+ memcpy(&md5sig.tcpm_addr, su2, sizeof(*su2));
+
+ md5sig.tcpm_keylen = keylen;
+ if (keylen)
+ memcpy(md5sig.tcpm_key, password, keylen);
+ sockunion_free(susock);
+
+ /*
+ * Handle support for MD5 signatures on prefixes, if available and
+ * requested. Technically the #ifdef check below is not needed because
+ * if prefixlen > 0 and we don't have support for this feature we would
+ * have already returned by now, but leaving it there to be explicit.
+ */
+#ifdef TCP_MD5SIG_EXT
+ if (prefixlen > 0) {
+ md5sig.tcpm_prefixlen = prefixlen;
+ md5sig.tcpm_flags = TCP_MD5SIG_FLAG_PREFIX;
+ optname = TCP_MD5SIG_EXT;
+ }
+#endif /* TCP_MD5SIG_EXT */
+
+#endif /* GNU_LINUX */
+
+ ret = setsockopt(sock, IPPROTO_TCP, optname, &md5sig, sizeof(md5sig));
+ if (ret < 0) {
+ if (ENOENT == errno)
+ ret = 0;
+ else
+ flog_err_sys(
+ EC_LIB_SYSTEM_CALL,
+ "sockopt_tcp_signature: setsockopt(%d): %s",
+ sock, safe_strerror(errno));
+ }
+ return ret;
+#endif /* HAVE_TCP_MD5SIG */
+
+ /*
+ * Making compiler happy. If we get to this point we probably
+ * have done something really really wrong.
+ */
+ return -2;
+}
+
+int sockopt_tcp_signature(int sock, union sockunion *su, const char *password)
+{
+ return sockopt_tcp_signature_ext(sock, su, 0, password);
+}
+
+/* set TCP mss value to socket */
+int sockopt_tcp_mss_set(int sock, int tcp_maxseg)
+{
+ int ret = 0;
+ socklen_t tcp_maxseg_len = sizeof(tcp_maxseg);
+
+ ret = setsockopt(sock, IPPROTO_TCP, TCP_MAXSEG, &tcp_maxseg,
+ tcp_maxseg_len);
+ if (ret != 0) {
+ flog_err_sys(EC_LIB_SYSTEM_CALL,
+ "%s failed: setsockopt(%d): %s", __func__, sock,
+ safe_strerror(errno));
+ }
+
+ return ret;
+}
+
+/* get TCP mss value synced by socket */
+int sockopt_tcp_mss_get(int sock)
+{
+ int ret = 0;
+ int tcp_maxseg = 0;
+ socklen_t tcp_maxseg_len = sizeof(tcp_maxseg);
+
+ ret = getsockopt(sock, IPPROTO_TCP, TCP_MAXSEG, &tcp_maxseg,
+ &tcp_maxseg_len);
+ if (ret != 0) {
+ flog_err_sys(EC_LIB_SYSTEM_CALL,
+ "%s failed: getsockopt(%d): %s", __func__, sock,
+ safe_strerror(errno));
+ return 0;
+ }
+
+ return tcp_maxseg;
+}
diff --git a/lib/sockopt.h b/lib/sockopt.h
new file mode 100644
index 0000000..6c80841
--- /dev/null
+++ b/lib/sockopt.h
@@ -0,0 +1,160 @@
+/* Router advertisement
+ * Copyright (C) 1999 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 _ZEBRA_SOCKOPT_H
+#define _ZEBRA_SOCKOPT_H
+
+#include "sockunion.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern void setsockopt_so_recvbuf(int sock, int size);
+extern void setsockopt_so_sendbuf(const int sock, int size);
+extern int getsockopt_so_sendbuf(const int sock);
+extern int getsockopt_so_recvbuf(const int sock);
+
+extern int setsockopt_ipv6_pktinfo(int, int);
+extern int setsockopt_ipv6_multicast_hops(int, int);
+extern int setsockopt_ipv6_unicast_hops(int, int);
+extern int setsockopt_ipv6_hoplimit(int, int);
+extern int setsockopt_ipv6_multicast_loop(int, int);
+extern int setsockopt_ipv6_tclass(int, int);
+
+#define SOPT_SIZE_CMSG_PKTINFO_IPV6() (sizeof(struct in6_pktinfo));
+
+/*
+ * Size defines for control messages used to get ifindex. We define
+ * values for each method, and define a macro that can be used by code
+ * that is unaware of which method is in use.
+ * These values are without any alignment needed (see CMSG_SPACE in RFC3542).
+ */
+#if defined(IP_PKTINFO)
+/* Linux in_pktinfo. */
+#define SOPT_SIZE_CMSG_PKTINFO_IPV4() (CMSG_SPACE(sizeof(struct in_pktinfo)))
+/* XXX This should perhaps be defined even if IP_PKTINFO is not. */
+#define SOPT_SIZE_CMSG_PKTINFO(af) \
+ ((af == AF_INET) ? SOPT_SIZE_CMSG_PKTINFO_IPV4() \
+ : SOPT_SIZE_CMSG_PKTINFO_IPV6()
+#endif /* IP_PKTINFO */
+
+#if defined(IP_RECVIF)
+/* BSD/Solaris */
+
+#define SOPT_SIZE_CMSG_RECVIF_IPV4() (sizeof(struct sockaddr_dl))
+#endif /* IP_RECVIF */
+
+/* SOPT_SIZE_CMSG_IFINDEX_IPV4 - portable type */
+#if defined(SOPT_SIZE_CMSG_PKTINFO)
+#define SOPT_SIZE_CMSG_IFINDEX_IPV4() SOPT_SIZE_CMSG_PKTINFO_IPV4()
+#elif defined(SOPT_SIZE_CMSG_RECVIF_IPV4)
+#define SOPT_SIZE_CMSG_IFINDEX_IPV4() SOPT_SIZE_CMSG_RECVIF_IPV4()
+#else /* Nothing available */
+#define SOPT_SIZE_CMSG_IFINDEX_IPV4() (sizeof(char *))
+#endif /* SOPT_SIZE_CMSG_IFINDEX_IPV4 */
+
+#define SOPT_SIZE_CMSG_IFINDEX(af) \
+ (((af) == AF_INET) : SOPT_SIZE_CMSG_IFINDEX_IPV4() \
+ ? SOPT_SIZE_CMSG_PKTINFO_IPV6())
+
+extern int setsockopt_ipv4_multicast_if(int sock, struct in_addr if_addr,
+ ifindex_t ifindex);
+extern int setsockopt_ipv4_multicast(int sock, int optname,
+ struct in_addr if_addr,
+ unsigned int mcast_addr,
+ ifindex_t ifindex);
+extern int setsockopt_ipv4_multicast_loop(int sock, uint8_t val);
+
+extern int setsockopt_ipv4_tos(int sock, int tos);
+
+/* Ask for, and get, ifindex, by whatever method is supported. */
+extern int setsockopt_ifindex(int, int, ifindex_t);
+extern ifindex_t getsockopt_ifindex(int, struct msghdr *);
+
+/* swab the fields in iph between the host order and system order expected
+ * for IP_HDRINCL.
+ */
+extern void sockopt_iphdrincl_swab_htosys(struct ip *iph);
+extern void sockopt_iphdrincl_swab_systoh(struct ip *iph);
+
+extern int sockopt_tcp_rtt(int);
+
+/*
+ * TCP MD5 signature option. This option allows TCP MD5 to be enabled on
+ * addresses.
+ *
+ * sock
+ * Socket to enable option on.
+ *
+ * su
+ * Sockunion specifying address to enable option on.
+ *
+ * password
+ * MD5 auth password
+ */
+extern int sockopt_tcp_signature(int sock, union sockunion *su,
+ const char *password);
+
+/*
+ * Extended TCP MD5 signature option. This option allows TCP MD5 to be enabled
+ * on prefixes.
+ *
+ * sock
+ * Socket to enable option on.
+ *
+ * su
+ * Sockunion specifying address (or prefix) to enable option on.
+ *
+ * prefixlen
+ * 0 - su is an address; fall back to non-extended mode
+ * Else - su is a prefix; prefixlen is the mask length
+ *
+ * password
+ * MD5 auth password
+ */
+extern int sockopt_tcp_signature_ext(int sock, union sockunion *su,
+ uint16_t prefixlen, const char *password);
+
+/*
+ * set TCP max segment size. This option allows user to configure
+ * max segment size for TCP session
+ *
+ * sock
+ * Socket to enable option on.
+ *
+ * tcp_maxseg
+ * value used for TCP segment size negotiation during SYN
+ */
+extern int sockopt_tcp_mss_set(int sock, int tcp_maxseg);
+
+/*
+ * get TCP max segment size. This option allows user to get
+ * the segment size for TCP session
+ *
+ * sock
+ * Socket to get max segement size.
+ */
+extern int sockopt_tcp_mss_get(int sock);
+#ifdef __cplusplus
+}
+#endif
+
+#endif /*_ZEBRA_SOCKOPT_H */
diff --git a/lib/sockunion.c b/lib/sockunion.c
new file mode 100644
index 0000000..36ae21f
--- /dev/null
+++ b/lib/sockunion.c
@@ -0,0 +1,796 @@
+/* Socket union related function.
+ * Copyright (c) 1997, 98 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 "vty.h"
+#include "sockunion.h"
+#include "memory.h"
+#include "log.h"
+#include "jhash.h"
+#include "lib_errors.h"
+#include "printfrr.h"
+
+DEFINE_MTYPE_STATIC(LIB, SOCKUNION, "Socket union");
+
+const char *inet_sutop(const union sockunion *su, char *str)
+{
+ switch (su->sa.sa_family) {
+ case AF_INET:
+ inet_ntop(AF_INET, &su->sin.sin_addr, str, INET_ADDRSTRLEN);
+ break;
+ case AF_INET6:
+ inet_ntop(AF_INET6, &su->sin6.sin6_addr, str, INET6_ADDRSTRLEN);
+ break;
+ }
+ return str;
+}
+
+int str2sockunion(const char *str, union sockunion *su)
+{
+ int ret;
+
+ if (str == NULL)
+ return -1;
+
+ memset(su, 0, sizeof(union sockunion));
+
+ ret = inet_pton(AF_INET, str, &su->sin.sin_addr);
+ if (ret > 0) /* Valid IPv4 address format. */
+ {
+ su->sin.sin_family = AF_INET;
+#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN
+ su->sin.sin_len = sizeof(struct sockaddr_in);
+#endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */
+ return 0;
+ }
+ ret = inet_pton(AF_INET6, str, &su->sin6.sin6_addr);
+ if (ret > 0) /* Valid IPv6 address format. */
+ {
+ su->sin6.sin6_family = AF_INET6;
+#ifdef SIN6_LEN
+ su->sin6.sin6_len = sizeof(struct sockaddr_in6);
+#endif /* SIN6_LEN */
+ return 0;
+ }
+ return -1;
+}
+
+const char *sockunion2str(const union sockunion *su, char *buf, size_t len)
+{
+ switch (sockunion_family(su)) {
+ case AF_UNSPEC:
+ snprintf(buf, len, "(unspec)");
+ return buf;
+ case AF_INET:
+ return inet_ntop(AF_INET, &su->sin.sin_addr, buf, len);
+ case AF_INET6:
+ return inet_ntop(AF_INET6, &su->sin6.sin6_addr, buf, len);
+ }
+ snprintf(buf, len, "(af %d)", sockunion_family(su));
+ return buf;
+}
+
+union sockunion *sockunion_str2su(const char *str)
+{
+ union sockunion *su = XCALLOC(MTYPE_SOCKUNION, sizeof(union sockunion));
+
+ if (!str2sockunion(str, su))
+ return su;
+
+ XFREE(MTYPE_SOCKUNION, su);
+ return NULL;
+}
+
+/* Convert IPv4 compatible IPv6 address to IPv4 address. */
+static void sockunion_normalise_mapped(union sockunion *su)
+{
+ struct sockaddr_in sin;
+
+ if (su->sa.sa_family == AF_INET6
+ && IN6_IS_ADDR_V4MAPPED(&su->sin6.sin6_addr)) {
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_port = su->sin6.sin6_port;
+ memcpy(&sin.sin_addr, ((char *)&su->sin6.sin6_addr) + 12, 4);
+ memcpy(su, &sin, sizeof(struct sockaddr_in));
+ }
+}
+
+/* return sockunion structure : this function should be revised. */
+static const char *sockunion_log(const union sockunion *su, char *buf,
+ size_t len)
+{
+ switch (su->sa.sa_family) {
+ case AF_INET:
+ return inet_ntop(AF_INET, &su->sin.sin_addr, buf, len);
+
+ case AF_INET6:
+ return inet_ntop(AF_INET6, &(su->sin6.sin6_addr), buf, len);
+
+ default:
+ snprintf(buf, len, "af_unknown %d ", su->sa.sa_family);
+ return buf;
+ }
+}
+
+/* Return socket of sockunion. */
+int sockunion_socket(const union sockunion *su)
+{
+ int sock;
+
+ sock = socket(su->sa.sa_family, SOCK_STREAM, 0);
+ if (sock < 0) {
+ char buf[SU_ADDRSTRLEN];
+ flog_err(EC_LIB_SOCKET, "Can't make socket for %s : %s",
+ sockunion_log(su, buf, SU_ADDRSTRLEN),
+ safe_strerror(errno));
+ return -1;
+ }
+
+ return sock;
+}
+
+/* Return accepted new socket file descriptor. */
+int sockunion_accept(int sock, union sockunion *su)
+{
+ socklen_t len;
+ int client_sock;
+
+ len = sizeof(union sockunion);
+ client_sock = accept(sock, (struct sockaddr *)su, &len);
+
+ sockunion_normalise_mapped(su);
+ return client_sock;
+}
+
+/* Return sizeof union sockunion. */
+int sockunion_sizeof(const union sockunion *su)
+{
+ int ret;
+
+ ret = 0;
+ switch (su->sa.sa_family) {
+ case AF_INET:
+ ret = sizeof(struct sockaddr_in);
+ break;
+ case AF_INET6:
+ ret = sizeof(struct sockaddr_in6);
+ break;
+ }
+ return ret;
+}
+
+/* Performs a non-blocking connect(). */
+enum connect_result sockunion_connect(int fd, const union sockunion *peersu,
+ unsigned short port, ifindex_t ifindex)
+{
+ int ret;
+ union sockunion su;
+
+ memcpy(&su, peersu, sizeof(union sockunion));
+
+ switch (su.sa.sa_family) {
+ case AF_INET:
+ su.sin.sin_port = port;
+ break;
+ case AF_INET6:
+ su.sin6.sin6_port = port;
+#ifdef KAME
+ if (IN6_IS_ADDR_LINKLOCAL(&su.sin6.sin6_addr) && ifindex) {
+ su.sin6.sin6_scope_id = ifindex;
+ SET_IN6_LINKLOCAL_IFINDEX(su.sin6.sin6_addr, ifindex);
+ }
+#endif /* KAME */
+ break;
+ }
+
+ /* Call connect function. */
+ ret = connect(fd, (struct sockaddr *)&su, sockunion_sizeof(&su));
+
+ /* Immediate success */
+ if (ret == 0)
+ return connect_success;
+
+ /* If connect is in progress then return 1 else it's real error. */
+ if (ret < 0) {
+ if (errno != EINPROGRESS) {
+ char str[SU_ADDRSTRLEN];
+ zlog_info("can't connect to %s fd %d : %s",
+ sockunion_log(&su, str, sizeof(str)), fd,
+ safe_strerror(errno));
+ return connect_error;
+ }
+ }
+
+ return connect_in_progress;
+}
+
+/* Make socket from sockunion union. */
+int sockunion_stream_socket(union sockunion *su)
+{
+ int sock;
+
+ if (su->sa.sa_family == 0)
+ su->sa.sa_family = AF_INET_UNION;
+
+ sock = socket(su->sa.sa_family, SOCK_STREAM, 0);
+
+ if (sock < 0)
+ flog_err(EC_LIB_SOCKET,
+ "can't make socket sockunion_stream_socket");
+
+ return sock;
+}
+
+/* Bind socket to specified address. */
+int sockunion_bind(int sock, union sockunion *su, unsigned short port,
+ union sockunion *su_addr)
+{
+ int size = 0;
+ int ret;
+
+ if (su->sa.sa_family == AF_INET) {
+ size = sizeof(struct sockaddr_in);
+ su->sin.sin_port = htons(port);
+#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN
+ su->sin.sin_len = size;
+#endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */
+ if (su_addr == NULL)
+ sockunion2ip(su) = htonl(INADDR_ANY);
+ } else if (su->sa.sa_family == AF_INET6) {
+ size = sizeof(struct sockaddr_in6);
+ su->sin6.sin6_port = htons(port);
+#ifdef SIN6_LEN
+ su->sin6.sin6_len = size;
+#endif /* SIN6_LEN */
+ if (su_addr == NULL) {
+#ifdef LINUX_IPV6
+ memset(&su->sin6.sin6_addr, 0, sizeof(struct in6_addr));
+#else
+ su->sin6.sin6_addr = in6addr_any;
+#endif /* LINUX_IPV6 */
+ }
+ }
+
+ ret = bind(sock, (struct sockaddr *)su, size);
+ if (ret < 0) {
+ char buf[SU_ADDRSTRLEN];
+ flog_err(EC_LIB_SOCKET, "can't bind socket for %s : %s",
+ sockunion_log(su, buf, SU_ADDRSTRLEN),
+ safe_strerror(errno));
+ }
+
+ return ret;
+}
+
+int sockopt_reuseaddr(int sock)
+{
+ int ret;
+ int on = 1;
+
+ ret = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&on,
+ sizeof(on));
+ if (ret < 0) {
+ flog_err(
+ EC_LIB_SOCKET,
+ "can't set sockopt SO_REUSEADDR to socket %d errno=%d: %s",
+ sock, errno, safe_strerror(errno));
+ return -1;
+ }
+ return 0;
+}
+
+#ifdef SO_REUSEPORT
+int sockopt_reuseport(int sock)
+{
+ int ret;
+ int on = 1;
+
+ ret = setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, (void *)&on,
+ sizeof(on));
+ if (ret < 0) {
+ flog_err(EC_LIB_SOCKET,
+ "can't set sockopt SO_REUSEPORT to socket %d", sock);
+ return -1;
+ }
+ return 0;
+}
+#else
+int sockopt_reuseport(int sock)
+{
+ return 0;
+}
+#endif /* 0 */
+
+int sockopt_ttl(int family, int sock, int ttl)
+{
+ int ret;
+
+#ifdef IP_TTL
+ if (family == AF_INET) {
+ ret = setsockopt(sock, IPPROTO_IP, IP_TTL, (void *)&ttl,
+ sizeof(int));
+ if (ret < 0) {
+ flog_err(EC_LIB_SOCKET,
+ "can't set sockopt IP_TTL %d to socket %d",
+ ttl, sock);
+ return -1;
+ }
+ return 0;
+ }
+#endif /* IP_TTL */
+ if (family == AF_INET6) {
+ ret = setsockopt(sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS,
+ (void *)&ttl, sizeof(int));
+ if (ret < 0) {
+ flog_err(
+ EC_LIB_SOCKET,
+ "can't set sockopt IPV6_UNICAST_HOPS %d to socket %d",
+ ttl, sock);
+ return -1;
+ }
+ return 0;
+ }
+ return 0;
+}
+
+int sockopt_minttl(int family, int sock, int minttl)
+{
+#ifdef IP_MINTTL
+ if (family == AF_INET) {
+ int ret = setsockopt(sock, IPPROTO_IP, IP_MINTTL, &minttl,
+ sizeof(minttl));
+ if (ret < 0)
+ flog_err(
+ EC_LIB_SOCKET,
+ "can't set sockopt IP_MINTTL to %d on socket %d: %s",
+ minttl, sock, safe_strerror(errno));
+ return ret;
+ }
+#endif /* IP_MINTTL */
+#ifdef IPV6_MINHOPCOUNT
+ if (family == AF_INET6) {
+ int ret = setsockopt(sock, IPPROTO_IPV6, IPV6_MINHOPCOUNT,
+ &minttl, sizeof(minttl));
+ if (ret < 0)
+ flog_err(
+ EC_LIB_SOCKET,
+ "can't set sockopt IPV6_MINHOPCOUNT to %d on socket %d: %s",
+ minttl, sock, safe_strerror(errno));
+ return ret;
+ }
+#endif
+
+ errno = EOPNOTSUPP;
+ return -1;
+}
+
+int sockopt_v6only(int family, int sock)
+{
+ int ret, on = 1;
+
+#ifdef IPV6_V6ONLY
+ if (family == AF_INET6) {
+ ret = setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&on,
+ sizeof(int));
+ if (ret < 0) {
+ flog_err(EC_LIB_SOCKET,
+ "can't set sockopt IPV6_V6ONLY to socket %d",
+ sock);
+ return -1;
+ }
+ return 0;
+ }
+#endif /* IPV6_V6ONLY */
+ return 0;
+}
+
+/* If same family and same prefix return 1. */
+int sockunion_same(const union sockunion *su1, const union sockunion *su2)
+{
+ int ret = 0;
+
+ if (su1->sa.sa_family != su2->sa.sa_family)
+ return 0;
+
+ switch (su1->sa.sa_family) {
+ case AF_INET:
+ ret = memcmp(&su1->sin.sin_addr, &su2->sin.sin_addr,
+ sizeof(struct in_addr));
+ break;
+ case AF_INET6:
+ ret = memcmp(&su1->sin6.sin6_addr, &su2->sin6.sin6_addr,
+ sizeof(struct in6_addr));
+ if ((ret == 0) && IN6_IS_ADDR_LINKLOCAL(&su1->sin6.sin6_addr)) {
+ /* compare interface indices */
+ if (su1->sin6.sin6_scope_id && su2->sin6.sin6_scope_id)
+ ret = (su1->sin6.sin6_scope_id
+ == su2->sin6.sin6_scope_id)
+ ? 0
+ : 1;
+ }
+ break;
+ }
+ if (ret == 0)
+ return 1;
+ else
+ return 0;
+}
+
+unsigned int sockunion_hash(const union sockunion *su)
+{
+ switch (sockunion_family(su)) {
+ case AF_INET:
+ return jhash_1word(su->sin.sin_addr.s_addr, 0);
+ case AF_INET6:
+ return jhash2(su->sin6.sin6_addr.s6_addr32,
+ array_size(su->sin6.sin6_addr.s6_addr32), 0);
+ }
+ return 0;
+}
+
+size_t family2addrsize(int family)
+{
+ switch (family) {
+ case AF_INET:
+ return sizeof(struct in_addr);
+ case AF_INET6:
+ return sizeof(struct in6_addr);
+ }
+ return 0;
+}
+
+size_t sockunion_get_addrlen(const union sockunion *su)
+{
+ return family2addrsize(sockunion_family(su));
+}
+
+const uint8_t *sockunion_get_addr(const union sockunion *su)
+{
+ switch (sockunion_family(su)) {
+ case AF_INET:
+ return (const uint8_t *)&su->sin.sin_addr.s_addr;
+ case AF_INET6:
+ return (const uint8_t *)&su->sin6.sin6_addr;
+ }
+ return NULL;
+}
+
+void sockunion_set(union sockunion *su, int family, const uint8_t *addr,
+ size_t bytes)
+{
+ if (family2addrsize(family) != bytes)
+ return;
+
+ sockunion_family(su) = family;
+ switch (family) {
+ case AF_INET:
+ memcpy(&su->sin.sin_addr.s_addr, addr, bytes);
+ break;
+ case AF_INET6:
+ memcpy(&su->sin6.sin6_addr, addr, bytes);
+ break;
+ }
+}
+
+/* After TCP connection is established. Get local address and port. */
+union sockunion *sockunion_getsockname(int fd)
+{
+ int ret;
+ socklen_t len;
+ union {
+ struct sockaddr sa;
+ struct sockaddr_in sin;
+ struct sockaddr_in6 sin6;
+ char tmp_buffer[128];
+ } name;
+ union sockunion *su;
+
+ memset(&name, 0, sizeof(name));
+ len = sizeof(name);
+
+ ret = getsockname(fd, (struct sockaddr *)&name, &len);
+ if (ret < 0) {
+ flog_err(EC_LIB_SOCKET,
+ "Can't get local address and port by getsockname: %s",
+ safe_strerror(errno));
+ return NULL;
+ }
+
+ if (name.sa.sa_family == AF_INET) {
+ su = XCALLOC(MTYPE_SOCKUNION, sizeof(union sockunion));
+ memcpy(su, &name, sizeof(struct sockaddr_in));
+ return su;
+ }
+ if (name.sa.sa_family == AF_INET6) {
+ su = XCALLOC(MTYPE_SOCKUNION, sizeof(union sockunion));
+ memcpy(su, &name, sizeof(struct sockaddr_in6));
+ sockunion_normalise_mapped(su);
+ return su;
+ }
+
+ flog_err(
+ EC_LIB_SOCKET,
+ "Unexpected AFI received(%d) for sockunion_getsockname call for fd: %d",
+ name.sa.sa_family, fd);
+ return NULL;
+}
+
+/* After TCP connection is established. Get remote address and port. */
+union sockunion *sockunion_getpeername(int fd)
+{
+ int ret;
+ socklen_t len;
+ union {
+ struct sockaddr sa;
+ struct sockaddr_in sin;
+ struct sockaddr_in6 sin6;
+ char tmp_buffer[128];
+ } name;
+ union sockunion *su;
+
+ memset(&name, 0, sizeof(name));
+ len = sizeof(name);
+ ret = getpeername(fd, (struct sockaddr *)&name, &len);
+ if (ret < 0) {
+ flog_err(EC_LIB_SOCKET, "Can't get remote address and port: %s",
+ safe_strerror(errno));
+ return NULL;
+ }
+
+ if (name.sa.sa_family == AF_INET) {
+ su = XCALLOC(MTYPE_SOCKUNION, sizeof(union sockunion));
+ memcpy(su, &name, sizeof(struct sockaddr_in));
+ return su;
+ }
+ if (name.sa.sa_family == AF_INET6) {
+ su = XCALLOC(MTYPE_SOCKUNION, sizeof(union sockunion));
+ memcpy(su, &name, sizeof(struct sockaddr_in6));
+ sockunion_normalise_mapped(su);
+ return su;
+ }
+
+ flog_err(
+ EC_LIB_SOCKET,
+ "Unexpected AFI received(%d) for sockunion_getpeername call for fd: %d",
+ name.sa.sa_family, fd);
+ return NULL;
+}
+
+/* Print sockunion structure */
+static void __attribute__((unused)) sockunion_print(const union sockunion *su)
+{
+ if (su == NULL)
+ return;
+
+ switch (su->sa.sa_family) {
+ case AF_INET:
+ printf("%pI4\n", &su->sin.sin_addr);
+ break;
+ case AF_INET6:
+ printf("%pI6\n", &su->sin6.sin6_addr);
+ break;
+#ifdef AF_LINK
+ case AF_LINK: {
+ struct sockaddr_dl *sdl;
+
+ sdl = (struct sockaddr_dl *)&(su->sa);
+ printf("link#%d\n", sdl->sdl_index);
+ } break;
+#endif /* AF_LINK */
+ default:
+ printf("af_unknown %d\n", su->sa.sa_family);
+ break;
+ }
+}
+
+int in6addr_cmp(const struct in6_addr *addr1, const struct in6_addr *addr2)
+{
+ unsigned int i;
+ const uint8_t *p1, *p2;
+
+ p1 = (const uint8_t *)addr1;
+ p2 = (const uint8_t *)addr2;
+
+ for (i = 0; i < sizeof(struct in6_addr); i++) {
+ if (p1[i] > p2[i])
+ return 1;
+ else if (p1[i] < p2[i])
+ return -1;
+ }
+ return 0;
+}
+
+int sockunion_cmp(const union sockunion *su1, const union sockunion *su2)
+{
+ if (su1->sa.sa_family > su2->sa.sa_family)
+ return 1;
+ if (su1->sa.sa_family < su2->sa.sa_family)
+ return -1;
+
+ if (su1->sa.sa_family == AF_INET) {
+ if (ntohl(sockunion2ip(su1)) == ntohl(sockunion2ip(su2)))
+ return 0;
+ if (ntohl(sockunion2ip(su1)) > ntohl(sockunion2ip(su2)))
+ return 1;
+ else
+ return -1;
+ }
+ if (su1->sa.sa_family == AF_INET6)
+ return in6addr_cmp(&su1->sin6.sin6_addr, &su2->sin6.sin6_addr);
+ return 0;
+}
+
+/* Duplicate sockunion. */
+union sockunion *sockunion_dup(const union sockunion *su)
+{
+ union sockunion *dup =
+ XCALLOC(MTYPE_SOCKUNION, sizeof(union sockunion));
+ memcpy(dup, su, sizeof(union sockunion));
+ return dup;
+}
+
+void sockunion_free(union sockunion *su)
+{
+ XFREE(MTYPE_SOCKUNION, su);
+}
+
+void sockunion_init(union sockunion *su)
+{
+ memset(su, 0, sizeof(union sockunion));
+}
+
+printfrr_ext_autoreg_p("SU", printfrr_psu);
+static ssize_t printfrr_psu(struct fbuf *buf, struct printfrr_eargs *ea,
+ const void *ptr)
+{
+ const union sockunion *su = ptr;
+ bool include_port = false, include_scope = false;
+ bool endflags = false;
+ ssize_t ret = 0;
+ char cbuf[INET6_ADDRSTRLEN];
+
+ if (!su)
+ return bputs(buf, "(null)");
+
+ while (!endflags) {
+ switch (*ea->fmt) {
+ case 'p':
+ ea->fmt++;
+ include_port = true;
+ break;
+ case 's':
+ ea->fmt++;
+ include_scope = true;
+ break;
+ default:
+ endflags = true;
+ break;
+ }
+ }
+
+ switch (sockunion_family(su)) {
+ case AF_UNSPEC:
+ ret += bputs(buf, "(unspec)");
+ break;
+ case AF_INET:
+ inet_ntop(AF_INET, &su->sin.sin_addr, cbuf, sizeof(cbuf));
+ ret += bputs(buf, cbuf);
+ if (include_port)
+ ret += bprintfrr(buf, ":%d", ntohs(su->sin.sin_port));
+ break;
+ case AF_INET6:
+ if (include_port)
+ ret += bputch(buf, '[');
+ inet_ntop(AF_INET6, &su->sin6.sin6_addr, cbuf, sizeof(cbuf));
+ ret += bputs(buf, cbuf);
+ if (include_scope && su->sin6.sin6_scope_id)
+ ret += bprintfrr(buf, "%%%u",
+ (unsigned int)su->sin6.sin6_scope_id);
+ if (include_port)
+ ret += bprintfrr(buf, "]:%d",
+ ntohs(su->sin6.sin6_port));
+ break;
+ case AF_UNIX: {
+ int len;
+#ifdef __linux__
+ if (su->sun.sun_path[0] == '\0' && su->sun.sun_path[1]) {
+ len = strnlen(su->sun.sun_path + 1,
+ sizeof(su->sun.sun_path) - 1);
+ ret += bprintfrr(buf, "@%*pSE", len,
+ su->sun.sun_path + 1);
+ break;
+ }
+#endif
+ len = strnlen(su->sun.sun_path, sizeof(su->sun.sun_path));
+ ret += bprintfrr(buf, "%*pSE", len, su->sun.sun_path);
+ break;
+ }
+ default:
+ ret += bprintfrr(buf, "(af %d)", sockunion_family(su));
+ }
+
+ return ret;
+}
+
+int sockunion_is_null(const union sockunion *su)
+{
+ unsigned char null_s6_addr[16] = {0};
+
+ switch (sockunion_family(su)) {
+ case AF_UNSPEC:
+ return 1;
+ case AF_INET:
+ return (su->sin.sin_addr.s_addr == 0);
+ case AF_INET6:
+ return !memcmp(su->sin6.sin6_addr.s6_addr, null_s6_addr,
+ sizeof(null_s6_addr));
+ default:
+ return 0;
+ }
+}
+
+printfrr_ext_autoreg_i("PF", printfrr_pf);
+static ssize_t printfrr_pf(struct fbuf *buf, struct printfrr_eargs *ea,
+ uintmax_t val)
+{
+ switch (val) {
+ case AF_INET:
+ return bputs(buf, "AF_INET");
+ case AF_INET6:
+ return bputs(buf, "AF_INET6");
+ case AF_UNIX:
+ return bputs(buf, "AF_UNIX");
+#ifdef AF_PACKET
+ case AF_PACKET:
+ return bputs(buf, "AF_PACKET");
+#endif
+#ifdef AF_NETLINK
+ case AF_NETLINK:
+ return bputs(buf, "AF_NETLINK");
+#endif
+ }
+ return bprintfrr(buf, "AF_(%ju)", val);
+}
+
+printfrr_ext_autoreg_i("SO", printfrr_so);
+static ssize_t printfrr_so(struct fbuf *buf, struct printfrr_eargs *ea,
+ uintmax_t val)
+{
+ switch (val) {
+ case SOCK_STREAM:
+ return bputs(buf, "SOCK_STREAM");
+ case SOCK_DGRAM:
+ return bputs(buf, "SOCK_DGRAM");
+ case SOCK_SEQPACKET:
+ return bputs(buf, "SOCK_SEQPACKET");
+#ifdef SOCK_RAW
+ case SOCK_RAW:
+ return bputs(buf, "SOCK_RAW");
+#endif
+#ifdef SOCK_PACKET
+ case SOCK_PACKET:
+ return bputs(buf, "SOCK_PACKET");
+#endif
+ }
+ return bprintfrr(buf, "SOCK_(%ju)", val);
+}
diff --git a/lib/sockunion.h b/lib/sockunion.h
new file mode 100644
index 0000000..8ace3e4
--- /dev/null
+++ b/lib/sockunion.h
@@ -0,0 +1,127 @@
+/*
+ * Socket union header.
+ * Copyright (c) 1997 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 _ZEBRA_SOCKUNION_H
+#define _ZEBRA_SOCKUNION_H
+
+#include "privs.h"
+#include "if.h"
+#include <sys/un.h>
+#ifdef __OpenBSD__
+#include <netmpls/mpls.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+union sockunion {
+ struct sockaddr sa;
+ struct sockaddr_in sin;
+ struct sockaddr_in6 sin6;
+ struct sockaddr_un sun;
+#ifdef __OpenBSD__
+ struct sockaddr_mpls smpls;
+ struct sockaddr_rtlabel rtlabel;
+#endif
+};
+
+enum connect_result { connect_error, connect_success, connect_in_progress };
+
+/* Default address family. */
+#define AF_INET_UNION AF_INET6
+
+/* Sockunion address string length. Same as INET6_ADDRSTRLEN. */
+#define SU_ADDRSTRLEN 46
+
+/* Macro to set link local index to the IPv6 address. For KAME IPv6
+ stack. */
+#ifdef KAME
+#define IN6_LINKLOCAL_IFINDEX(a) ((a).s6_addr[2] << 8 | (a).s6_addr[3])
+#define SET_IN6_LINKLOCAL_IFINDEX(a, i) \
+ do { \
+ (a).s6_addr[2] = ((i) >> 8) & 0xff; \
+ (a).s6_addr[3] = (i)&0xff; \
+ } while (0)
+#else
+#define IN6_LINKLOCAL_IFINDEX(a)
+#define SET_IN6_LINKLOCAL_IFINDEX(a, i)
+#endif /* KAME */
+
+#define sockunion_family(X) (X)->sa.sa_family
+
+#define sockunion2ip(X) (X)->sin.sin_addr.s_addr
+
+/* Prototypes. */
+extern int str2sockunion(const char *, union sockunion *);
+extern const char *sockunion2str(const union sockunion *, char *, size_t);
+int in6addr_cmp(const struct in6_addr *addr1, const struct in6_addr *addr2);
+extern int sockunion_cmp(const union sockunion *, const union sockunion *);
+extern int sockunion_same(const union sockunion *, const union sockunion *);
+extern unsigned int sockunion_hash(const union sockunion *);
+
+extern size_t family2addrsize(int family);
+extern size_t sockunion_get_addrlen(const union sockunion *);
+extern const uint8_t *sockunion_get_addr(const union sockunion *);
+extern void sockunion_set(union sockunion *, int family, const uint8_t *addr,
+ size_t bytes);
+
+extern union sockunion *sockunion_str2su(const char *str);
+extern int sockunion_accept(int sock, union sockunion *);
+extern int sockunion_sizeof(const union sockunion *su);
+extern int sockunion_stream_socket(union sockunion *);
+extern int sockopt_reuseaddr(int);
+extern int sockopt_reuseport(int);
+extern int sockopt_v6only(int family, int sock);
+extern int sockunion_bind(int sock, union sockunion *, unsigned short,
+ union sockunion *);
+extern int sockopt_ttl(int family, int sock, int ttl);
+extern int sockopt_minttl(int family, int sock, int minttl);
+extern int sockunion_socket(const union sockunion *su);
+extern const char *inet_sutop(const union sockunion *su, char *str);
+extern enum connect_result sockunion_connect(int fd, const union sockunion *su,
+ unsigned short port, ifindex_t);
+extern union sockunion *sockunion_getsockname(int);
+extern union sockunion *sockunion_getpeername(int);
+extern union sockunion *sockunion_dup(const union sockunion *);
+extern void sockunion_free(union sockunion *);
+extern void sockunion_init(union sockunion *);
+extern int sockunion_is_null(const union sockunion *su);
+
+#ifdef _FRR_ATTRIBUTE_PRINTFRR
+#pragma FRR printfrr_ext "%pSU" (union sockunion *)
+#pragma FRR printfrr_ext "%pSU" (struct sockaddr *)
+#pragma FRR printfrr_ext "%pSU" (struct sockaddr_storage *)
+#pragma FRR printfrr_ext "%pSU" (struct sockaddr_in *)
+#pragma FRR printfrr_ext "%pSU" (struct sockaddr_in6 *)
+#pragma FRR printfrr_ext "%pSU" (struct sockaddr_un *)
+
+/* AF_INET/PF_INET & co., using "PF" to avoid confusion with AFI/SAFI */
+#pragma FRR printfrr_ext "%dPF" (int)
+/* SOCK_STREAM & co. */
+#pragma FRR printfrr_ext "%dSO" (int)
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ZEBRA_SOCKUNION_H */
diff --git a/lib/spf_backoff.c b/lib/spf_backoff.c
new file mode 100644
index 0000000..117b7d3
--- /dev/null
+++ b/lib/spf_backoff.c
@@ -0,0 +1,313 @@
+/*
+ * This is an implementation of the IETF SPF delay algorithm
+ * as explained in draft-ietf-rtgwg-backoff-algo-04
+ *
+ * Created: 25-01-2017 by S. Litkowski
+ *
+ * Copyright (C) 2017 Orange Labs http://www.orange.com/
+ * Copyright (C) 2017 by Christian Franke, Open Source Routing / NetDEF Inc.
+ *
+ * This file is part of FRRouting (FRR)
+ *
+ * FRR 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, or (at your option) any
+ * later version.
+ *
+ * FRR 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 "spf_backoff.h"
+
+#include "command.h"
+#include "memory.h"
+#include "thread.h"
+#include "vty.h"
+
+DEFINE_MTYPE_STATIC(LIB, SPF_BACKOFF, "SPF backoff");
+DEFINE_MTYPE_STATIC(LIB, SPF_BACKOFF_NAME, "SPF backoff name");
+
+static bool debug_spf_backoff = false;
+#define backoff_debug(...) \
+ do { \
+ if (debug_spf_backoff) \
+ zlog_debug(__VA_ARGS__); \
+ } while (0)
+
+enum spf_backoff_state {
+ SPF_BACKOFF_QUIET,
+ SPF_BACKOFF_SHORT_WAIT,
+ SPF_BACKOFF_LONG_WAIT
+};
+
+struct spf_backoff {
+ struct thread_master *m;
+
+ /* Timers as per draft */
+ long init_delay;
+ long short_delay;
+ long long_delay;
+ long holddown;
+ long timetolearn;
+
+ /* State machine */
+ enum spf_backoff_state state;
+ struct thread *t_holddown;
+ struct thread *t_timetolearn;
+
+ /* For debugging */
+ char *name;
+ struct timeval first_event_time;
+ struct timeval last_event_time;
+};
+
+static const char *spf_backoff_state2str(enum spf_backoff_state state)
+{
+ switch (state) {
+ case SPF_BACKOFF_QUIET:
+ return "QUIET";
+ case SPF_BACKOFF_SHORT_WAIT:
+ return "SHORT_WAIT";
+ case SPF_BACKOFF_LONG_WAIT:
+ return "LONG_WAIT";
+ }
+ return "???";
+}
+
+struct spf_backoff *spf_backoff_new(struct thread_master *m, const char *name,
+ long init_delay, long short_delay,
+ long long_delay, long holddown,
+ long timetolearn)
+{
+ struct spf_backoff *rv;
+
+ rv = XCALLOC(MTYPE_SPF_BACKOFF, sizeof(*rv));
+ rv->m = m;
+
+ rv->init_delay = init_delay;
+ rv->short_delay = short_delay;
+ rv->long_delay = long_delay;
+ rv->holddown = holddown;
+ rv->timetolearn = timetolearn;
+
+ rv->state = SPF_BACKOFF_QUIET;
+
+ rv->name = XSTRDUP(MTYPE_SPF_BACKOFF_NAME, name);
+ return rv;
+}
+
+void spf_backoff_free(struct spf_backoff *backoff)
+{
+ if (!backoff)
+ return;
+
+ thread_cancel(&backoff->t_holddown);
+ thread_cancel(&backoff->t_timetolearn);
+ XFREE(MTYPE_SPF_BACKOFF_NAME, backoff->name);
+
+ XFREE(MTYPE_SPF_BACKOFF, backoff);
+}
+
+static void spf_backoff_timetolearn_elapsed(struct thread *thread)
+{
+ struct spf_backoff *backoff = THREAD_ARG(thread);
+
+ backoff->state = SPF_BACKOFF_LONG_WAIT;
+ backoff_debug("SPF Back-off(%s) TIMETOLEARN elapsed, move to state %s",
+ backoff->name, spf_backoff_state2str(backoff->state));
+}
+
+static void spf_backoff_holddown_elapsed(struct thread *thread)
+{
+ struct spf_backoff *backoff = THREAD_ARG(thread);
+
+ THREAD_OFF(backoff->t_timetolearn);
+ timerclear(&backoff->first_event_time);
+ backoff->state = SPF_BACKOFF_QUIET;
+ backoff_debug("SPF Back-off(%s) HOLDDOWN elapsed, move to state %s",
+ backoff->name, spf_backoff_state2str(backoff->state));
+}
+
+long spf_backoff_schedule(struct spf_backoff *backoff)
+{
+ long rv = 0;
+ struct timeval now;
+
+ gettimeofday(&now, NULL);
+
+ backoff_debug("SPF Back-off(%s) schedule called in state %s",
+ backoff->name, spf_backoff_state2str(backoff->state));
+
+ backoff->last_event_time = now;
+
+ switch (backoff->state) {
+ case SPF_BACKOFF_QUIET:
+ backoff->state = SPF_BACKOFF_SHORT_WAIT;
+ thread_add_timer_msec(
+ backoff->m, spf_backoff_timetolearn_elapsed, backoff,
+ backoff->timetolearn, &backoff->t_timetolearn);
+ thread_add_timer_msec(backoff->m, spf_backoff_holddown_elapsed,
+ backoff, backoff->holddown,
+ &backoff->t_holddown);
+ backoff->first_event_time = now;
+ rv = backoff->init_delay;
+ break;
+ case SPF_BACKOFF_SHORT_WAIT:
+ case SPF_BACKOFF_LONG_WAIT:
+ thread_cancel(&backoff->t_holddown);
+ thread_add_timer_msec(backoff->m, spf_backoff_holddown_elapsed,
+ backoff, backoff->holddown,
+ &backoff->t_holddown);
+ if (backoff->state == SPF_BACKOFF_SHORT_WAIT)
+ rv = backoff->short_delay;
+ else
+ rv = backoff->long_delay;
+ break;
+ }
+
+ backoff_debug(
+ "SPF Back-off(%s) changed state to %s and returned %ld delay",
+ backoff->name, spf_backoff_state2str(backoff->state), rv);
+ return rv;
+}
+
+static const char *timeval_format(struct timeval *tv)
+{
+ struct tm tm_store;
+ struct tm *tm;
+ static char timebuf[256];
+
+ if (!tv->tv_sec && !tv->tv_usec)
+ return "(never)";
+
+ tm = localtime_r(&tv->tv_sec, &tm_store);
+ if (!tm
+ || strftime(timebuf, sizeof(timebuf), "%Z %a %Y-%m-%d %H:%M:%S", tm)
+ == 0) {
+ return "???";
+ }
+
+ size_t offset = strlen(timebuf);
+ snprintf(timebuf + offset, sizeof(timebuf) - offset, ".%ld",
+ (long int)tv->tv_usec);
+
+ return timebuf;
+}
+
+void spf_backoff_show(struct spf_backoff *backoff, struct vty *vty,
+ const char *prefix)
+{
+ vty_out(vty, "%sCurrent state: %s\n", prefix,
+ spf_backoff_state2str(backoff->state));
+ vty_out(vty, "%sInit timer: %ld msec\n", prefix,
+ backoff->init_delay);
+ vty_out(vty, "%sShort timer: %ld msec\n", prefix,
+ backoff->short_delay);
+ vty_out(vty, "%sLong timer: %ld msec\n", prefix,
+ backoff->long_delay);
+ vty_out(vty, "%sHolddown timer: %ld msec\n", prefix,
+ backoff->holddown);
+ if (backoff->t_holddown) {
+ struct timeval remain =
+ thread_timer_remain(backoff->t_holddown);
+ vty_out(vty, "%s Still runs for %lld msec\n",
+ prefix,
+ (long long)remain.tv_sec * 1000
+ + remain.tv_usec / 1000);
+ } else {
+ vty_out(vty, "%s Inactive\n", prefix);
+ }
+
+ vty_out(vty, "%sTimeToLearn timer: %ld msec\n", prefix,
+ backoff->timetolearn);
+ if (backoff->t_timetolearn) {
+ struct timeval remain =
+ thread_timer_remain(backoff->t_timetolearn);
+ vty_out(vty, "%s Still runs for %lld msec\n",
+ prefix,
+ (long long)remain.tv_sec * 1000
+ + remain.tv_usec / 1000);
+ } else {
+ vty_out(vty, "%s Inactive\n", prefix);
+ }
+
+ vty_out(vty, "%sFirst event: %s\n", prefix,
+ timeval_format(&backoff->first_event_time));
+ vty_out(vty, "%sLast event: %s\n", prefix,
+ timeval_format(&backoff->last_event_time));
+}
+
+DEFUN(spf_backoff_debug,
+ spf_backoff_debug_cmd,
+ "debug spf-delay-ietf",
+ DEBUG_STR
+ "SPF Back-off Debugging\n")
+{
+ debug_spf_backoff = true;
+ return CMD_SUCCESS;
+}
+
+DEFUN(no_spf_backoff_debug,
+ no_spf_backoff_debug_cmd,
+ "no debug spf-delay-ietf",
+ NO_STR
+ DEBUG_STR
+ "SPF Back-off Debugging\n")
+{
+ debug_spf_backoff = false;
+ return CMD_SUCCESS;
+}
+
+int spf_backoff_write_config(struct vty *vty)
+{
+ int written = 0;
+
+ if (debug_spf_backoff) {
+ vty_out(vty, "debug spf-delay-ietf\n");
+ written++;
+ }
+
+ return written;
+}
+
+void spf_backoff_cmd_init(void)
+{
+ install_element(ENABLE_NODE, &spf_backoff_debug_cmd);
+ install_element(CONFIG_NODE, &spf_backoff_debug_cmd);
+ install_element(ENABLE_NODE, &no_spf_backoff_debug_cmd);
+ install_element(CONFIG_NODE, &no_spf_backoff_debug_cmd);
+}
+
+long spf_backoff_init_delay(struct spf_backoff *backoff)
+{
+ return backoff->init_delay;
+}
+
+long spf_backoff_short_delay(struct spf_backoff *backoff)
+{
+ return backoff->short_delay;
+}
+
+long spf_backoff_long_delay(struct spf_backoff *backoff)
+{
+ return backoff->long_delay;
+}
+
+long spf_backoff_holddown(struct spf_backoff *backoff)
+{
+ return backoff->holddown;
+}
+
+long spf_backoff_timetolearn(struct spf_backoff *backoff)
+{
+ return backoff->timetolearn;
+}
diff --git a/lib/spf_backoff.h b/lib/spf_backoff.h
new file mode 100644
index 0000000..2617195
--- /dev/null
+++ b/lib/spf_backoff.h
@@ -0,0 +1,69 @@
+/*
+ * This is an implementation of the IETF SPF delay algorithm
+ * as explained in draft-ietf-rtgwg-backoff-algo-04
+ *
+ * Created: 25-01-2017 by S. Litkowski
+ *
+ * Copyright (C) 2017 Orange Labs http://www.orange.com/
+ * Copyright (C) 2017 by Christian Franke, Open Source Routing / NetDEF Inc.
+ *
+ * This file is part of FRRouting (FRR)
+ *
+ * FRR 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, or (at your option) any
+ * later version.
+ *
+ * FRR 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 _ZEBRA_SPF_BACKOFF_H
+#define _ZEBRA_SPF_BACKOFF_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct spf_backoff;
+struct thread_master;
+struct vty;
+
+struct spf_backoff *spf_backoff_new(struct thread_master *m, const char *name,
+ long init_delay, long short_delay,
+ long long_delay, long holddown,
+ long timetolearn);
+
+void spf_backoff_free(struct spf_backoff *backoff);
+
+/* Called whenever an IGP event is received, returns how many
+ * milliseconds routing table computation should be delayed */
+long spf_backoff_schedule(struct spf_backoff *backoff);
+
+/* Shows status of SPF backoff instance */
+void spf_backoff_show(struct spf_backoff *backoff, struct vty *vty,
+ const char *prefix);
+
+/* Writes out global SPF backoff debug config */
+int spf_backoff_write_config(struct vty *vty);
+
+/* Registers global SPF backoff debug commands */
+void spf_backoff_cmd_init(void);
+
+/* Accessor functions for SPF backoff parameters */
+long spf_backoff_init_delay(struct spf_backoff *backoff);
+long spf_backoff_short_delay(struct spf_backoff *backoff);
+long spf_backoff_long_delay(struct spf_backoff *backoff);
+long spf_backoff_holddown(struct spf_backoff *backoff);
+long spf_backoff_timetolearn(struct spf_backoff *backoff);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/srcdest_table.c b/lib/srcdest_table.c
new file mode 100644
index 0000000..828cf2c
--- /dev/null
+++ b/lib/srcdest_table.c
@@ -0,0 +1,334 @@
+/*
+ * SRC-DEST Routing Table
+ *
+ * Copyright (C) 2017 by David Lamparter & Christian Franke,
+ * Open Source Routing / NetDEF Inc.
+ *
+ * This file is part of FRRouting (FRR)
+ *
+ * FRR 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, or (at your option) any
+ * later version.
+ *
+ * FRR 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 "srcdest_table.h"
+
+#include "memory.h"
+#include "prefix.h"
+#include "table.h"
+#include "printfrr.h"
+
+DEFINE_MTYPE_STATIC(LIB, ROUTE_SRC_NODE, "Route source node");
+
+/* ----- functions to manage rnodes _with_ srcdest table ----- */
+struct srcdest_rnode {
+ /* must be first in structure for casting to/from route_node */
+ ROUTE_NODE_FIELDS;
+
+ struct route_table *src_table;
+};
+
+static struct srcdest_rnode *srcdest_rnode_from_rnode(struct route_node *rn)
+{
+ assert(rnode_is_dstnode(rn));
+ return (struct srcdest_rnode *)rn;
+}
+
+static struct route_node *srcdest_rnode_to_rnode(struct srcdest_rnode *srn)
+{
+ return (struct route_node *)srn;
+}
+
+static struct route_node *srcdest_rnode_create(route_table_delegate_t *delegate,
+ struct route_table *table)
+{
+ struct srcdest_rnode *srn;
+ srn = XCALLOC(MTYPE_ROUTE_NODE, sizeof(struct srcdest_rnode));
+ return srcdest_rnode_to_rnode(srn);
+}
+
+static void srcdest_rnode_destroy(route_table_delegate_t *delegate,
+ struct route_table *table,
+ struct route_node *rn)
+{
+ struct srcdest_rnode *srn = srcdest_rnode_from_rnode(rn);
+ struct route_table *src_table;
+
+ /* Clear route node's src_table here already, otherwise the
+ * deletion of the last node in the src_table will trigger
+ * another call to route_table_finish for the src_table.
+ *
+ * (Compare with srcdest_srcnode_destroy)
+ */
+ src_table = srn->src_table;
+ srn->src_table = NULL;
+ route_table_finish(src_table);
+ XFREE(MTYPE_ROUTE_NODE, rn);
+}
+
+route_table_delegate_t _srcdest_dstnode_delegate = {
+ .create_node = srcdest_rnode_create,
+ .destroy_node = srcdest_rnode_destroy};
+
+/* ----- functions to manage rnodes _in_ srcdest table ----- */
+
+/* node creation / deletion for srcdest source prefix nodes.
+ * the route_node isn't actually different from the normal route_node,
+ * but the cleanup is special to free the table (and possibly the
+ * destination prefix's route_node) */
+
+static struct route_node *
+srcdest_srcnode_create(route_table_delegate_t *delegate,
+ struct route_table *table)
+{
+ return XCALLOC(MTYPE_ROUTE_SRC_NODE, sizeof(struct route_node));
+}
+
+static void srcdest_srcnode_destroy(route_table_delegate_t *delegate,
+ struct route_table *table,
+ struct route_node *rn)
+{
+ struct srcdest_rnode *srn;
+
+ XFREE(MTYPE_ROUTE_SRC_NODE, rn);
+
+ srn = route_table_get_info(table);
+ if (srn->src_table && route_table_count(srn->src_table) == 0) {
+ /* deleting the route_table from inside destroy_node is ONLY
+ * permitted IF table->count is 0! see lib/table.c
+ * route_node_delete()
+ * for details */
+ route_table_finish(srn->src_table);
+ srn->src_table = NULL;
+
+ /* drop the ref we're holding in srcdest_node_get(). there
+ * might be
+ * non-srcdest routes, so the route_node may still exist.
+ * hence, it's
+ * important to clear src_table above. */
+ route_unlock_node(srcdest_rnode_to_rnode(srn));
+ }
+}
+
+route_table_delegate_t _srcdest_srcnode_delegate = {
+ .create_node = srcdest_srcnode_create,
+ .destroy_node = srcdest_srcnode_destroy};
+
+/* NB: read comments in code for refcounting before using! */
+static struct route_node *srcdest_srcnode_get(struct route_node *rn,
+ const struct prefix_ipv6 *src_p)
+{
+ struct srcdest_rnode *srn;
+
+ if (!src_p || src_p->prefixlen == 0)
+ return rn;
+
+ srn = srcdest_rnode_from_rnode(rn);
+ if (!srn->src_table) {
+ /* this won't use srcdest_rnode, we're already on the source
+ * here */
+ srn->src_table = route_table_init_with_delegate(
+ &_srcdest_srcnode_delegate);
+ route_table_set_info(srn->src_table, srn);
+
+ /* there is no route_unlock_node on the original rn here.
+ * The reference is kept for the src_table. */
+ } else {
+ /* only keep 1 reference for the src_table, makes the
+ * refcounting
+ * more similar to the non-srcdest case. Either way after
+ * return from
+ * function, the only reference held is the one on the return
+ * value.
+ *
+ * We can safely drop our reference here because src_table is
+ * holding
+ * another reference, so this won't free rn */
+ route_unlock_node(rn);
+ }
+
+ return route_node_get(srn->src_table, (const struct prefix *)src_p);
+}
+
+static struct route_node *srcdest_srcnode_lookup(
+ struct route_node *rn,
+ const struct prefix_ipv6 *src_p)
+{
+ struct srcdest_rnode *srn;
+
+ if (!rn || !src_p || src_p->prefixlen == 0)
+ return rn;
+
+ /* We got this rn from a lookup, so its refcnt was incremented. As we
+ * won't
+ * return return rn from any point beyond here, we should decrement its
+ * refcnt.
+ */
+ route_unlock_node(rn);
+
+ srn = srcdest_rnode_from_rnode(rn);
+ if (!srn->src_table)
+ return NULL;
+
+ return route_node_lookup(srn->src_table, (const struct prefix *)src_p);
+}
+
+/* ----- exported functions ----- */
+
+struct route_table *srcdest_table_init(void)
+{
+ return route_table_init_with_delegate(&_srcdest_dstnode_delegate);
+}
+
+struct route_node *srcdest_route_next(struct route_node *rn)
+{
+ struct route_node *next, *parent;
+
+ /* For a non src-dest node, just return route_next */
+ if (!(rnode_is_dstnode(rn) || rnode_is_srcnode(rn)))
+ return route_next(rn);
+
+ if (rnode_is_dstnode(rn)) {
+ /* This means the route_node is part of the top hierarchy
+ * and refers to a destination prefix. */
+ struct srcdest_rnode *srn = srcdest_rnode_from_rnode(rn);
+
+ if (srn->src_table)
+ next = route_top(srn->src_table);
+ else
+ next = NULL;
+
+ if (next) {
+ /* There is a source prefix. Return the node for it */
+ route_unlock_node(rn);
+ return next;
+ } else {
+ /* There is no source prefix, just continue as usual */
+ return route_next(rn);
+ }
+ }
+
+ /* This part handles the case of iterating source nodes. */
+ parent = route_lock_node(route_table_get_info(rn->table));
+ next = route_next(rn);
+
+ if (next) {
+ /* There is another source node, continue in the source table */
+ route_unlock_node(parent);
+ return next;
+ } else {
+ /* The source table is complete, continue in the parent table */
+ return route_next(parent);
+ }
+}
+
+struct route_node *srcdest_rnode_get(struct route_table *table,
+ union prefixconstptr dst_pu,
+ const struct prefix_ipv6 *src_p)
+{
+ const struct prefix_ipv6 *dst_p = dst_pu.p6;
+ struct route_node *rn;
+
+ rn = route_node_get(table, (const struct prefix *)dst_p);
+ return srcdest_srcnode_get(rn, src_p);
+}
+
+struct route_node *srcdest_rnode_lookup(struct route_table *table,
+ union prefixconstptr dst_pu,
+ const struct prefix_ipv6 *src_p)
+{
+ const struct prefix_ipv6 *dst_p = dst_pu.p6;
+ struct route_node *rn;
+ struct route_node *srn;
+
+ rn = route_node_lookup_maynull(table, (const struct prefix *)dst_p);
+ srn = srcdest_srcnode_lookup(rn, src_p);
+
+ if (rn != NULL && rn == srn && !rn->info) {
+ /* Match the behavior of route_node_lookup and don't return an
+ * empty route-node for a dest-route */
+ route_unlock_node(rn);
+ return NULL;
+ }
+ return srn;
+}
+
+void srcdest_rnode_prefixes(const struct route_node *rn,
+ const struct prefix **p,
+ const struct prefix **src_p)
+{
+ if (rnode_is_srcnode(rn)) {
+ struct route_node *dst_rn = route_table_get_info(rn->table);
+ if (p)
+ *p = &dst_rn->p;
+ if (src_p)
+ *src_p = &rn->p;
+ } else {
+ if (p)
+ *p = &rn->p;
+ if (src_p)
+ *src_p = NULL;
+ }
+}
+
+const char *srcdest2str(const struct prefix *dst_p,
+ const struct prefix_ipv6 *src_p,
+ char *str, int size)
+{
+ char dst_buf[PREFIX_STRLEN], src_buf[PREFIX_STRLEN];
+
+ snprintf(str, size, "%s%s%s",
+ prefix2str(dst_p, dst_buf, sizeof(dst_buf)),
+ (src_p && src_p->prefixlen) ? " from " : "",
+ (src_p && src_p->prefixlen)
+ ? prefix2str(src_p, src_buf, sizeof(src_buf))
+ : "");
+ return str;
+}
+
+const char *srcdest_rnode2str(const struct route_node *rn, char *str, int size)
+{
+ const struct prefix *dst_p, *src_p;
+
+ srcdest_rnode_prefixes(rn, &dst_p, &src_p);
+ return srcdest2str(dst_p, (const struct prefix_ipv6 *)src_p, str, size);
+}
+
+printfrr_ext_autoreg_p("RN", printfrr_rn);
+static ssize_t printfrr_rn(struct fbuf *buf, struct printfrr_eargs *ea,
+ const void *ptr)
+{
+ const struct route_node *rn = ptr;
+ const struct prefix *dst_p, *src_p;
+ char cbuf[PREFIX_STRLEN * 2 + 6];
+
+ if (!rn)
+ return bputs(buf, "(null)");
+
+ srcdest_rnode_prefixes(rn, &dst_p, &src_p);
+ srcdest2str(dst_p, (const struct prefix_ipv6 *)src_p,
+ cbuf, sizeof(cbuf));
+ return bputs(buf, cbuf);
+}
+
+struct route_table *srcdest_srcnode_table(struct route_node *rn)
+{
+ if (rnode_is_dstnode(rn)) {
+ struct srcdest_rnode *srn = srcdest_rnode_from_rnode(rn);
+
+ return srn->src_table;
+ }
+ return NULL;
+}
diff --git a/lib/srcdest_table.h b/lib/srcdest_table.h
new file mode 100644
index 0000000..79afef9
--- /dev/null
+++ b/lib/srcdest_table.h
@@ -0,0 +1,109 @@
+/*
+ * SRC-DEST Routing Table
+ *
+ * Copyright (C) 2017 by David Lamparter & Christian Franke,
+ * Open Source Routing / NetDEF Inc.
+ *
+ * This file is part of FRRouting (FRR)
+ *
+ * FRR 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, or (at your option) any
+ * later version.
+ *
+ * FRR 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 _ZEBRA_SRC_DEST_TABLE_H
+#define _ZEBRA_SRC_DEST_TABLE_H
+
+/* old/IPv4/non-srcdest:
+ * table -> route_node .info -> [obj]
+ *
+ * new/IPv6/srcdest:
+ * table -...-> srcdest_rnode [prefix = dest] .info -> [obj]
+ * .src_table ->
+ * srcdest table -...-> route_node [prefix = src] .info -> [obj]
+ *
+ * non-srcdest routes (src = ::/0) are treated just like before, their
+ * information being directly there in the info pointer.
+ *
+ * srcdest routes are found by looking up destination first, then looking
+ * up the source in the "src_table". src_table contains normal route_nodes,
+ * whose prefix is the _source_ prefix.
+ *
+ * NB: info can be NULL on the destination rnode, if there are only srcdest
+ * routes for a particular destination prefix.
+ */
+
+#include "prefix.h"
+#include "table.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define SRCDEST2STR_BUFFER (2*PREFIX2STR_BUFFER + sizeof(" from "))
+
+/* extended route node for IPv6 srcdest routing */
+struct srcdest_rnode;
+
+extern route_table_delegate_t _srcdest_dstnode_delegate;
+extern route_table_delegate_t _srcdest_srcnode_delegate;
+
+extern struct route_table *srcdest_table_init(void);
+extern struct route_node *srcdest_rnode_get(struct route_table *table,
+ union prefixconstptr dst_pu,
+ const struct prefix_ipv6 *src_p);
+extern struct route_node *srcdest_rnode_lookup(struct route_table *table,
+ union prefixconstptr dst_pu,
+ const struct prefix_ipv6 *src_p);
+extern void srcdest_rnode_prefixes(const struct route_node *rn,
+ const struct prefix **p,
+ const struct prefix **src_p);
+extern const char *srcdest2str(const struct prefix *dst_p,
+ const struct prefix_ipv6 *src_p,
+ char *str, int size);
+extern const char *srcdest_rnode2str(const struct route_node *rn, char *str,
+ int size);
+extern struct route_node *srcdest_route_next(struct route_node *rn);
+
+static inline int rnode_is_dstnode(const struct route_node *rn)
+{
+ return rn->table->delegate == &_srcdest_dstnode_delegate;
+}
+
+static inline int rnode_is_srcnode(const struct route_node *rn)
+{
+ return rn->table->delegate == &_srcdest_srcnode_delegate;
+}
+
+static inline struct route_table *srcdest_rnode_table(struct route_node *rn)
+{
+ if (rnode_is_srcnode(rn)) {
+ struct route_node *dst_rn =
+ (struct route_node *)route_table_get_info(rn->table);
+ return dst_rn->table;
+ } else {
+ return rn->table;
+ }
+}
+static inline void *srcdest_rnode_table_info(struct route_node *rn)
+{
+ return route_table_get_info(srcdest_rnode_table(rn));
+}
+
+extern struct route_table *srcdest_srcnode_table(struct route_node *rn);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ZEBRA_SRC_DEST_TABLE_H */
diff --git a/lib/srte.h b/lib/srte.h
new file mode 100644
index 0000000..d468c1c
--- /dev/null
+++ b/lib/srte.h
@@ -0,0 +1,56 @@
+/*
+ * SR-TE definitions
+ * Copyright 2020 NetDef Inc.
+ * Sascha Kattelmann
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra 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_SRTE_H
+#define _FRR_SRTE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define SRTE_POLICY_NAME_MAX_LENGTH 64
+
+enum zebra_sr_policy_status {
+ ZEBRA_SR_POLICY_UP = 0,
+ ZEBRA_SR_POLICY_DOWN,
+};
+
+static inline int sr_policy_compare(const struct ipaddr *a_endpoint,
+ const struct ipaddr *b_endpoint,
+ uint32_t a_color, uint32_t b_color)
+{
+ int ret;
+
+ ret = ipaddr_cmp(a_endpoint, b_endpoint);
+ if (ret < 0)
+ return -1;
+ if (ret > 0)
+ return 1;
+
+ return a_color - b_color;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRR_SRTE_H */
diff --git a/lib/srv6.c b/lib/srv6.c
new file mode 100644
index 0000000..6a65844
--- /dev/null
+++ b/lib/srv6.c
@@ -0,0 +1,295 @@
+/*
+ * SRv6 definitions
+ * Copyright (C) 2020 Hiroki Shirokura, LINE Corporation
+ *
+ * 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 "srv6.h"
+#include "log.h"
+
+DEFINE_QOBJ_TYPE(srv6_locator);
+DEFINE_MTYPE_STATIC(LIB, SRV6_LOCATOR, "SRV6 locator");
+DEFINE_MTYPE_STATIC(LIB, SRV6_LOCATOR_CHUNK, "SRV6 locator chunk");
+
+const char *seg6local_action2str(uint32_t action)
+{
+ switch (action) {
+ case ZEBRA_SEG6_LOCAL_ACTION_END:
+ return "End";
+ case ZEBRA_SEG6_LOCAL_ACTION_END_X:
+ return "End.X";
+ case ZEBRA_SEG6_LOCAL_ACTION_END_T:
+ return "End.T";
+ case ZEBRA_SEG6_LOCAL_ACTION_END_DX2:
+ return "End.DX2";
+ case ZEBRA_SEG6_LOCAL_ACTION_END_DX6:
+ return "End.DX6";
+ case ZEBRA_SEG6_LOCAL_ACTION_END_DX4:
+ return "End.DX4";
+ case ZEBRA_SEG6_LOCAL_ACTION_END_DT6:
+ return "End.DT6";
+ case ZEBRA_SEG6_LOCAL_ACTION_END_DT4:
+ return "End.DT4";
+ case ZEBRA_SEG6_LOCAL_ACTION_END_B6:
+ return "End.B6";
+ case ZEBRA_SEG6_LOCAL_ACTION_END_B6_ENCAP:
+ return "End.B6.Encap";
+ case ZEBRA_SEG6_LOCAL_ACTION_END_BM:
+ return "End.BM";
+ case ZEBRA_SEG6_LOCAL_ACTION_END_S:
+ return "End.S";
+ case ZEBRA_SEG6_LOCAL_ACTION_END_AS:
+ return "End.AS";
+ case ZEBRA_SEG6_LOCAL_ACTION_END_AM:
+ return "End.AM";
+ case ZEBRA_SEG6_LOCAL_ACTION_UNSPEC:
+ return "unspec";
+ default:
+ return "unknown";
+ }
+}
+
+int snprintf_seg6_segs(char *str,
+ size_t size, const struct seg6_segs *segs)
+{
+ str[0] = '\0';
+ for (size_t i = 0; i < segs->num_segs; i++) {
+ char addr[INET6_ADDRSTRLEN];
+ bool not_last = (i + 1) < segs->num_segs;
+
+ inet_ntop(AF_INET6, &segs->segs[i], addr, sizeof(addr));
+ strlcat(str, addr, size);
+ strlcat(str, not_last ? "," : "", size);
+ }
+ return strlen(str);
+}
+
+const char *seg6local_context2str(char *str, size_t size,
+ const struct seg6local_context *ctx,
+ uint32_t action)
+{
+ char b0[128];
+
+ switch (action) {
+
+ case ZEBRA_SEG6_LOCAL_ACTION_END:
+ snprintf(str, size, "USP");
+ return str;
+
+ case ZEBRA_SEG6_LOCAL_ACTION_END_X:
+ case ZEBRA_SEG6_LOCAL_ACTION_END_DX6:
+ inet_ntop(AF_INET6, &ctx->nh6, b0, 128);
+ snprintf(str, size, "nh6 %s", b0);
+ return str;
+
+ case ZEBRA_SEG6_LOCAL_ACTION_END_DX4:
+ inet_ntop(AF_INET, &ctx->nh4, b0, 128);
+ snprintf(str, size, "nh4 %s", b0);
+ return str;
+
+ case ZEBRA_SEG6_LOCAL_ACTION_END_T:
+ case ZEBRA_SEG6_LOCAL_ACTION_END_DT6:
+ case ZEBRA_SEG6_LOCAL_ACTION_END_DT4:
+ snprintf(str, size, "table %u", ctx->table);
+ return str;
+
+ case ZEBRA_SEG6_LOCAL_ACTION_END_DX2:
+ case ZEBRA_SEG6_LOCAL_ACTION_END_B6:
+ case ZEBRA_SEG6_LOCAL_ACTION_END_B6_ENCAP:
+ case ZEBRA_SEG6_LOCAL_ACTION_END_BM:
+ case ZEBRA_SEG6_LOCAL_ACTION_END_S:
+ case ZEBRA_SEG6_LOCAL_ACTION_END_AS:
+ case ZEBRA_SEG6_LOCAL_ACTION_END_AM:
+ case ZEBRA_SEG6_LOCAL_ACTION_UNSPEC:
+ default:
+ snprintf(str, size, "unknown(%s)", __func__);
+ return str;
+ }
+}
+
+struct srv6_locator *srv6_locator_alloc(const char *name)
+{
+ struct srv6_locator *locator = NULL;
+
+ locator = XCALLOC(MTYPE_SRV6_LOCATOR, sizeof(struct srv6_locator));
+ strlcpy(locator->name, name, sizeof(locator->name));
+ locator->chunks = list_new();
+ locator->chunks->del = (void (*)(void *))srv6_locator_chunk_free;
+
+ QOBJ_REG(locator, srv6_locator);
+ return locator;
+}
+
+struct srv6_locator_chunk *srv6_locator_chunk_alloc(void)
+{
+ struct srv6_locator_chunk *chunk = NULL;
+
+ chunk = XCALLOC(MTYPE_SRV6_LOCATOR_CHUNK,
+ sizeof(struct srv6_locator_chunk));
+ return chunk;
+}
+
+void srv6_locator_free(struct srv6_locator *locator)
+{
+ if (locator) {
+ QOBJ_UNREG(locator);
+ list_delete(&locator->chunks);
+
+ XFREE(MTYPE_SRV6_LOCATOR, locator);
+ }
+}
+
+void srv6_locator_chunk_free(struct srv6_locator_chunk *chunk)
+{
+ XFREE(MTYPE_SRV6_LOCATOR_CHUNK, chunk);
+}
+
+json_object *srv6_locator_chunk_json(const struct srv6_locator_chunk *chunk)
+{
+ json_object *jo_root = NULL;
+
+ jo_root = json_object_new_object();
+ json_object_string_addf(jo_root, "prefix", "%pFX", &chunk->prefix);
+ json_object_string_add(jo_root, "proto",
+ zebra_route_string(chunk->proto));
+
+ return jo_root;
+}
+
+json_object *
+srv6_locator_chunk_detailed_json(const struct srv6_locator_chunk *chunk)
+{
+ json_object *jo_root = NULL;
+
+ jo_root = json_object_new_object();
+
+ /* set prefix */
+ json_object_string_addf(jo_root, "prefix", "%pFX", &chunk->prefix);
+
+ /* set block_bits_length */
+ json_object_int_add(jo_root, "blockBitsLength",
+ chunk->block_bits_length);
+
+ /* set node_bits_length */
+ json_object_int_add(jo_root, "nodeBitsLength", chunk->node_bits_length);
+
+ /* set function_bits_length */
+ json_object_int_add(jo_root, "functionBitsLength",
+ chunk->function_bits_length);
+
+ /* set argument_bits_length */
+ json_object_int_add(jo_root, "argumentBitsLength",
+ chunk->argument_bits_length);
+
+ /* set keep */
+ json_object_int_add(jo_root, "keep", chunk->keep);
+
+ /* set proto */
+ json_object_string_add(jo_root, "proto",
+ zebra_route_string(chunk->proto));
+
+ /* set instance */
+ json_object_int_add(jo_root, "instance", chunk->instance);
+
+ /* set session_id */
+ json_object_int_add(jo_root, "sessionId", chunk->session_id);
+
+ return jo_root;
+}
+
+json_object *srv6_locator_json(const struct srv6_locator *loc)
+{
+ struct listnode *node;
+ struct srv6_locator_chunk *chunk;
+ json_object *jo_root = NULL;
+ json_object *jo_chunk = NULL;
+ json_object *jo_chunks = NULL;
+
+ jo_root = json_object_new_object();
+
+ /* set name */
+ json_object_string_add(jo_root, "name", loc->name);
+
+ /* set prefix */
+ json_object_string_addf(jo_root, "prefix", "%pFX", &loc->prefix);
+
+ /* set function_bits_length */
+ json_object_int_add(jo_root, "functionBitsLength",
+ loc->function_bits_length);
+
+ /* set status_up */
+ json_object_boolean_add(jo_root, "statusUp",
+ loc->status_up);
+
+ /* set chunks */
+ jo_chunks = json_object_new_array();
+ json_object_object_add(jo_root, "chunks", jo_chunks);
+ for (ALL_LIST_ELEMENTS_RO((struct list *)loc->chunks, node, chunk)) {
+ jo_chunk = srv6_locator_chunk_json(chunk);
+ json_object_array_add(jo_chunks, jo_chunk);
+ }
+
+ return jo_root;
+}
+
+json_object *srv6_locator_detailed_json(const struct srv6_locator *loc)
+{
+ struct listnode *node;
+ struct srv6_locator_chunk *chunk;
+ json_object *jo_root = NULL;
+ json_object *jo_chunk = NULL;
+ json_object *jo_chunks = NULL;
+
+ jo_root = json_object_new_object();
+
+ /* set name */
+ json_object_string_add(jo_root, "name", loc->name);
+
+ /* set prefix */
+ json_object_string_addf(jo_root, "prefix", "%pFX", &loc->prefix);
+
+ /* set block_bits_length */
+ json_object_int_add(jo_root, "blockBitsLength", loc->block_bits_length);
+
+ /* set node_bits_length */
+ json_object_int_add(jo_root, "nodeBitsLength", loc->node_bits_length);
+
+ /* set function_bits_length */
+ json_object_int_add(jo_root, "functionBitsLength",
+ loc->function_bits_length);
+
+ /* set argument_bits_length */
+ json_object_int_add(jo_root, "argumentBitsLength",
+ loc->argument_bits_length);
+
+ /* set algonum */
+ json_object_int_add(jo_root, "algoNum", loc->algonum);
+
+ /* set status_up */
+ json_object_boolean_add(jo_root, "statusUp", loc->status_up);
+
+ /* set chunks */
+ jo_chunks = json_object_new_array();
+ json_object_object_add(jo_root, "chunks", jo_chunks);
+ for (ALL_LIST_ELEMENTS_RO((struct list *)loc->chunks, node, chunk)) {
+ jo_chunk = srv6_locator_chunk_detailed_json(chunk);
+ json_object_array_add(jo_chunks, jo_chunk);
+ }
+
+ return jo_root;
+}
diff --git a/lib/srv6.h b/lib/srv6.h
new file mode 100644
index 0000000..e0db30c
--- /dev/null
+++ b/lib/srv6.h
@@ -0,0 +1,200 @@
+/*
+ * SRv6 definitions
+ * Copyright (C) 2020 Hiroki Shirokura, LINE Corporation
+ *
+ * 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_SRV6_H
+#define _FRR_SRV6_H
+
+#include <zebra.h>
+#include "prefix.h"
+#include "json.h"
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#define SRV6_MAX_SIDS 16
+#define SRV6_LOCNAME_SIZE 256
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define sid2str(sid, str, size) \
+ inet_ntop(AF_INET6, sid, str, size)
+
+enum seg6_mode_t {
+ INLINE,
+ ENCAP,
+ L2ENCAP,
+};
+
+enum seg6local_action_t {
+ ZEBRA_SEG6_LOCAL_ACTION_UNSPEC = 0,
+ ZEBRA_SEG6_LOCAL_ACTION_END = 1,
+ ZEBRA_SEG6_LOCAL_ACTION_END_X = 2,
+ ZEBRA_SEG6_LOCAL_ACTION_END_T = 3,
+ ZEBRA_SEG6_LOCAL_ACTION_END_DX2 = 4,
+ ZEBRA_SEG6_LOCAL_ACTION_END_DX6 = 5,
+ ZEBRA_SEG6_LOCAL_ACTION_END_DX4 = 6,
+ ZEBRA_SEG6_LOCAL_ACTION_END_DT6 = 7,
+ ZEBRA_SEG6_LOCAL_ACTION_END_DT4 = 8,
+ ZEBRA_SEG6_LOCAL_ACTION_END_B6 = 9,
+ ZEBRA_SEG6_LOCAL_ACTION_END_B6_ENCAP = 10,
+ ZEBRA_SEG6_LOCAL_ACTION_END_BM = 11,
+ ZEBRA_SEG6_LOCAL_ACTION_END_S = 12,
+ ZEBRA_SEG6_LOCAL_ACTION_END_AS = 13,
+ ZEBRA_SEG6_LOCAL_ACTION_END_AM = 14,
+ ZEBRA_SEG6_LOCAL_ACTION_END_BPF = 15,
+};
+
+struct seg6_segs {
+ size_t num_segs;
+ struct in6_addr segs[256];
+};
+
+struct seg6local_context {
+ struct in_addr nh4;
+ struct in6_addr nh6;
+ uint32_t table;
+};
+
+struct srv6_locator {
+ char name[SRV6_LOCNAME_SIZE];
+ struct prefix_ipv6 prefix;
+
+ /*
+ * Bit length of SRv6 locator described in
+ * draft-ietf-bess-srv6-services-05#section-3.2.1
+ */
+ uint8_t block_bits_length;
+ uint8_t node_bits_length;
+ uint8_t function_bits_length;
+ uint8_t argument_bits_length;
+
+ int algonum;
+ uint64_t current;
+ bool status_up;
+ struct list *chunks;
+
+ QOBJ_FIELDS;
+};
+DECLARE_QOBJ_TYPE(srv6_locator);
+
+struct srv6_locator_chunk {
+ char locator_name[SRV6_LOCNAME_SIZE];
+ struct prefix_ipv6 prefix;
+
+ /*
+ * Bit length of SRv6 locator described in
+ * draft-ietf-bess-srv6-services-05#section-3.2.1
+ */
+ uint8_t block_bits_length;
+ uint8_t node_bits_length;
+ uint8_t function_bits_length;
+ uint8_t argument_bits_length;
+
+ /*
+ * For Zclient communication values
+ */
+ uint8_t keep;
+ uint8_t proto;
+ uint16_t instance;
+ uint32_t session_id;
+};
+
+struct nexthop_srv6 {
+ /* SRv6 localsid info for Endpoint-behaviour */
+ enum seg6local_action_t seg6local_action;
+ struct seg6local_context seg6local_ctx;
+
+ /* SRv6 Headend-behaviour */
+ struct in6_addr seg6_segs;
+};
+
+static inline const char *seg6_mode2str(enum seg6_mode_t mode)
+{
+ switch (mode) {
+ case INLINE:
+ return "INLINE";
+ case ENCAP:
+ return "ENCAP";
+ case L2ENCAP:
+ return "L2ENCAP";
+ default:
+ return "unknown";
+ }
+}
+
+static inline bool sid_same(
+ const struct in6_addr *a,
+ const struct in6_addr *b)
+{
+ if (!a && !b)
+ return true;
+ else if (!(a && b))
+ return false;
+ else
+ return memcmp(a, b, sizeof(struct in6_addr)) == 0;
+}
+
+static inline bool sid_diff(
+ const struct in6_addr *a,
+ const struct in6_addr *b)
+{
+ return !sid_same(a, b);
+}
+
+static inline bool sid_zero(
+ const struct in6_addr *a)
+{
+ struct in6_addr zero = {};
+
+ return sid_same(a, &zero);
+}
+
+static inline void *sid_copy(struct in6_addr *dst,
+ const struct in6_addr *src)
+{
+ return memcpy(dst, src, sizeof(struct in6_addr));
+}
+
+const char *
+seg6local_action2str(uint32_t action);
+
+const char *seg6local_context2str(char *str, size_t size,
+ const struct seg6local_context *ctx,
+ uint32_t action);
+
+int snprintf_seg6_segs(char *str,
+ size_t size, const struct seg6_segs *segs);
+
+extern struct srv6_locator *srv6_locator_alloc(const char *name);
+extern struct srv6_locator_chunk *srv6_locator_chunk_alloc(void);
+extern void srv6_locator_free(struct srv6_locator *locator);
+extern void srv6_locator_chunk_free(struct srv6_locator_chunk *chunk);
+json_object *srv6_locator_chunk_json(const struct srv6_locator_chunk *chunk);
+json_object *srv6_locator_json(const struct srv6_locator *loc);
+json_object *srv6_locator_detailed_json(const struct srv6_locator *loc);
+json_object *
+srv6_locator_chunk_detailed_json(const struct srv6_locator_chunk *chunk);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/stream.c b/lib/stream.c
new file mode 100644
index 0000000..2de3abd
--- /dev/null
+++ b/lib/stream.c
@@ -0,0 +1,1389 @@
+/*
+ * Packet interface
+ * Copyright (C) 1999 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 <stddef.h>
+#include <pthread.h>
+
+#include "stream.h"
+#include "memory.h"
+#include "network.h"
+#include "prefix.h"
+#include "log.h"
+#include "frr_pthread.h"
+#include "lib_errors.h"
+
+DEFINE_MTYPE_STATIC(LIB, STREAM, "Stream");
+DEFINE_MTYPE_STATIC(LIB, STREAM_FIFO, "Stream FIFO");
+
+/* Tests whether a position is valid */
+#define GETP_VALID(S, G) ((G) <= (S)->endp)
+#define PUT_AT_VALID(S,G) GETP_VALID(S,G)
+#define ENDP_VALID(S, E) ((E) <= (S)->size)
+
+/* asserting sanity checks. Following must be true before
+ * stream functions are called:
+ *
+ * Following must always be true of stream elements
+ * before and after calls to stream functions:
+ *
+ * getp <= endp <= size
+ *
+ * Note that after a stream function is called following may be true:
+ * if (getp == endp) then stream is no longer readable
+ * if (endp == size) then stream is no longer writeable
+ *
+ * It is valid to put to anywhere within the size of the stream, but only
+ * using stream_put..._at() functions.
+ */
+#define STREAM_WARN_OFFSETS(S) \
+ do { \
+ flog_warn(EC_LIB_STREAM, \
+ "&(struct stream): %p, size: %lu, getp: %lu, endp: %lu", \
+ (void *)(S), (unsigned long)(S)->size, \
+ (unsigned long)(S)->getp, (unsigned long)(S)->endp); \
+ zlog_backtrace(LOG_WARNING); \
+ } while (0)
+
+#define STREAM_VERIFY_SANE(S) \
+ do { \
+ if (!(GETP_VALID(S, (S)->getp) && ENDP_VALID(S, (S)->endp))) { \
+ STREAM_WARN_OFFSETS(S); \
+ } \
+ assert(GETP_VALID(S, (S)->getp)); \
+ assert(ENDP_VALID(S, (S)->endp)); \
+ } while (0)
+
+#define STREAM_BOUND_WARN(S, WHAT) \
+ do { \
+ flog_warn(EC_LIB_STREAM, "%s: Attempt to %s out of bounds", \
+ __func__, (WHAT)); \
+ STREAM_WARN_OFFSETS(S); \
+ assert(0); \
+ } while (0)
+
+#define STREAM_BOUND_WARN2(S, WHAT) \
+ do { \
+ flog_warn(EC_LIB_STREAM, "%s: Attempt to %s out of bounds", \
+ __func__, (WHAT)); \
+ STREAM_WARN_OFFSETS(S); \
+ } while (0)
+
+/* XXX: Deprecated macro: do not use */
+#define CHECK_SIZE(S, Z) \
+ do { \
+ if (((S)->endp + (Z)) > (S)->size) { \
+ flog_warn( \
+ EC_LIB_STREAM, \
+ "CHECK_SIZE: truncating requested size %lu", \
+ (unsigned long)(Z)); \
+ STREAM_WARN_OFFSETS(S); \
+ (Z) = (S)->size - (S)->endp; \
+ } \
+ } while (0);
+
+/* Make stream buffer. */
+struct stream *stream_new(size_t size)
+{
+ struct stream *s;
+
+ assert(size > 0);
+
+ s = XMALLOC(MTYPE_STREAM, sizeof(struct stream) + size);
+
+ s->getp = s->endp = 0;
+ s->next = NULL;
+ s->size = size;
+ return s;
+}
+
+/* Free it now. */
+void stream_free(struct stream *s)
+{
+ if (!s)
+ return;
+
+ XFREE(MTYPE_STREAM, s);
+}
+
+struct stream *stream_copy(struct stream *dest, const struct stream *src)
+{
+ STREAM_VERIFY_SANE(src);
+
+ assert(dest != NULL);
+ assert(STREAM_SIZE(dest) >= src->endp);
+
+ dest->endp = src->endp;
+ dest->getp = src->getp;
+
+ memcpy(dest->data, src->data, src->endp);
+
+ return dest;
+}
+
+struct stream *stream_dup(const struct stream *s)
+{
+ struct stream *snew;
+
+ STREAM_VERIFY_SANE(s);
+
+ snew = stream_new(s->endp);
+
+ return (stream_copy(snew, s));
+}
+
+struct stream *stream_dupcat(const struct stream *s1, const struct stream *s2,
+ size_t offset)
+{
+ struct stream *new;
+
+ STREAM_VERIFY_SANE(s1);
+ STREAM_VERIFY_SANE(s2);
+
+ if ((new = stream_new(s1->endp + s2->endp)) == NULL)
+ return NULL;
+
+ memcpy(new->data, s1->data, offset);
+ memcpy(new->data + offset, s2->data, s2->endp);
+ memcpy(new->data + offset + s2->endp, s1->data + offset,
+ (s1->endp - offset));
+ new->endp = s1->endp + s2->endp;
+ return new;
+}
+
+size_t stream_resize_inplace(struct stream **sptr, size_t newsize)
+{
+ struct stream *orig = *sptr;
+
+ STREAM_VERIFY_SANE(orig);
+
+ orig = XREALLOC(MTYPE_STREAM, orig, sizeof(struct stream) + newsize);
+
+ orig->size = newsize;
+
+ if (orig->endp > orig->size)
+ orig->endp = orig->size;
+ if (orig->getp > orig->endp)
+ orig->getp = orig->endp;
+
+ STREAM_VERIFY_SANE(orig);
+
+ *sptr = orig;
+ return orig->size;
+}
+
+size_t stream_get_getp(const struct stream *s)
+{
+ STREAM_VERIFY_SANE(s);
+ return s->getp;
+}
+
+size_t stream_get_endp(const struct stream *s)
+{
+ STREAM_VERIFY_SANE(s);
+ return s->endp;
+}
+
+size_t stream_get_size(const struct stream *s)
+{
+ STREAM_VERIFY_SANE(s);
+ return s->size;
+}
+
+/* Stream structre' stream pointer related functions. */
+void stream_set_getp(struct stream *s, size_t pos)
+{
+ STREAM_VERIFY_SANE(s);
+
+ if (!GETP_VALID(s, pos)) {
+ STREAM_BOUND_WARN(s, "set getp");
+ pos = s->endp;
+ }
+
+ s->getp = pos;
+}
+
+void stream_set_endp(struct stream *s, size_t pos)
+{
+ STREAM_VERIFY_SANE(s);
+
+ if (!ENDP_VALID(s, pos)) {
+ STREAM_BOUND_WARN(s, "set endp");
+ return;
+ }
+
+ /*
+ * Make sure the current read pointer is not beyond the new endp.
+ */
+ if (s->getp > pos) {
+ STREAM_BOUND_WARN(s, "set endp");
+ return;
+ }
+
+ s->endp = pos;
+ STREAM_VERIFY_SANE(s);
+}
+
+/* Forward pointer. */
+void stream_forward_getp(struct stream *s, size_t size)
+{
+ STREAM_VERIFY_SANE(s);
+
+ if (!GETP_VALID(s, s->getp + size)) {
+ STREAM_BOUND_WARN(s, "seek getp");
+ return;
+ }
+
+ s->getp += size;
+}
+
+bool stream_forward_getp2(struct stream *s, size_t size)
+{
+ STREAM_VERIFY_SANE(s);
+
+ if (!GETP_VALID(s, s->getp + size))
+ return false;
+
+ s->getp += size;
+
+ return true;
+}
+
+void stream_rewind_getp(struct stream *s, size_t size)
+{
+ STREAM_VERIFY_SANE(s);
+
+ if (size > s->getp || !GETP_VALID(s, s->getp - size)) {
+ STREAM_BOUND_WARN(s, "rewind getp");
+ return;
+ }
+
+ s->getp -= size;
+}
+
+bool stream_rewind_getp2(struct stream *s, size_t size)
+{
+ STREAM_VERIFY_SANE(s);
+
+ if (size > s->getp || !GETP_VALID(s, s->getp - size))
+ return false;
+
+ s->getp -= size;
+
+ return true;
+}
+
+void stream_forward_endp(struct stream *s, size_t size)
+{
+ STREAM_VERIFY_SANE(s);
+
+ if (!ENDP_VALID(s, s->endp + size)) {
+ STREAM_BOUND_WARN(s, "seek endp");
+ return;
+ }
+
+ s->endp += size;
+}
+
+bool stream_forward_endp2(struct stream *s, size_t size)
+{
+ STREAM_VERIFY_SANE(s);
+
+ if (!ENDP_VALID(s, s->endp + size))
+ return false;
+
+ s->endp += size;
+
+ return true;
+}
+
+/* Copy from stream to destination. */
+bool stream_get2(void *dst, struct stream *s, size_t size)
+{
+ STREAM_VERIFY_SANE(s);
+
+ if (STREAM_READABLE(s) < size) {
+ STREAM_BOUND_WARN2(s, "get");
+ return false;
+ }
+
+ memcpy(dst, s->data + s->getp, size);
+ s->getp += size;
+
+ return true;
+}
+
+void stream_get(void *dst, struct stream *s, size_t size)
+{
+ STREAM_VERIFY_SANE(s);
+
+ if (STREAM_READABLE(s) < size) {
+ STREAM_BOUND_WARN(s, "get");
+ return;
+ }
+
+ memcpy(dst, s->data + s->getp, size);
+ s->getp += size;
+}
+
+/* Get next character from the stream. */
+bool stream_getc2(struct stream *s, uint8_t *byte)
+{
+ STREAM_VERIFY_SANE(s);
+
+ if (STREAM_READABLE(s) < sizeof(uint8_t)) {
+ STREAM_BOUND_WARN2(s, "get char");
+ return false;
+ }
+ *byte = s->data[s->getp++];
+
+ return true;
+}
+
+uint8_t stream_getc(struct stream *s)
+{
+ uint8_t c;
+
+ STREAM_VERIFY_SANE(s);
+
+ if (STREAM_READABLE(s) < sizeof(uint8_t)) {
+ STREAM_BOUND_WARN(s, "get char");
+ return 0;
+ }
+ c = s->data[s->getp++];
+
+ return c;
+}
+
+/* Get next character from the stream. */
+uint8_t stream_getc_from(struct stream *s, size_t from)
+{
+ uint8_t c;
+
+ STREAM_VERIFY_SANE(s);
+
+ if (!GETP_VALID(s, from + sizeof(uint8_t))) {
+ STREAM_BOUND_WARN(s, "get char");
+ return 0;
+ }
+
+ c = s->data[from];
+
+ return c;
+}
+
+bool stream_getw2(struct stream *s, uint16_t *word)
+{
+ STREAM_VERIFY_SANE(s);
+
+ if (STREAM_READABLE(s) < sizeof(uint16_t)) {
+ STREAM_BOUND_WARN2(s, "get ");
+ return false;
+ }
+
+ *word = s->data[s->getp++] << 8;
+ *word |= s->data[s->getp++];
+
+ return true;
+}
+
+/* Get next word from the stream. */
+uint16_t stream_getw(struct stream *s)
+{
+ uint16_t w;
+
+ STREAM_VERIFY_SANE(s);
+
+ if (STREAM_READABLE(s) < sizeof(uint16_t)) {
+ STREAM_BOUND_WARN(s, "get ");
+ return 0;
+ }
+
+ w = s->data[s->getp++] << 8;
+ w |= s->data[s->getp++];
+
+ return w;
+}
+
+/* Get next word from the stream. */
+uint16_t stream_getw_from(struct stream *s, size_t from)
+{
+ uint16_t w;
+
+ STREAM_VERIFY_SANE(s);
+
+ if (!GETP_VALID(s, from + sizeof(uint16_t))) {
+ STREAM_BOUND_WARN(s, "get ");
+ return 0;
+ }
+
+ w = s->data[from++] << 8;
+ w |= s->data[from];
+
+ return w;
+}
+
+/* Get next 3-byte from the stream. */
+uint32_t stream_get3_from(struct stream *s, size_t from)
+{
+ uint32_t l;
+
+ STREAM_VERIFY_SANE(s);
+
+ if (!GETP_VALID(s, from + 3)) {
+ STREAM_BOUND_WARN(s, "get 3byte");
+ return 0;
+ }
+
+ l = s->data[from++] << 16;
+ l |= s->data[from++] << 8;
+ l |= s->data[from];
+
+ return l;
+}
+
+uint32_t stream_get3(struct stream *s)
+{
+ uint32_t l;
+
+ STREAM_VERIFY_SANE(s);
+
+ if (STREAM_READABLE(s) < 3) {
+ STREAM_BOUND_WARN(s, "get 3byte");
+ return 0;
+ }
+
+ l = s->data[s->getp++] << 16;
+ l |= s->data[s->getp++] << 8;
+ l |= s->data[s->getp++];
+
+ return l;
+}
+
+/* Get next long word from the stream. */
+uint32_t stream_getl_from(struct stream *s, size_t from)
+{
+ uint32_t l;
+
+ STREAM_VERIFY_SANE(s);
+
+ if (!GETP_VALID(s, from + sizeof(uint32_t))) {
+ STREAM_BOUND_WARN(s, "get long");
+ return 0;
+ }
+
+ l = (unsigned)(s->data[from++]) << 24;
+ l |= s->data[from++] << 16;
+ l |= s->data[from++] << 8;
+ l |= s->data[from];
+
+ return l;
+}
+
+/* Copy from stream at specific location to destination. */
+void stream_get_from(void *dst, struct stream *s, size_t from, size_t size)
+{
+ STREAM_VERIFY_SANE(s);
+
+ if (!GETP_VALID(s, from + size)) {
+ STREAM_BOUND_WARN(s, "get from");
+ return;
+ }
+
+ memcpy(dst, s->data + from, size);
+}
+
+bool stream_getl2(struct stream *s, uint32_t *l)
+{
+ STREAM_VERIFY_SANE(s);
+
+ if (STREAM_READABLE(s) < sizeof(uint32_t)) {
+ STREAM_BOUND_WARN2(s, "get long");
+ return false;
+ }
+
+ *l = (unsigned int)(s->data[s->getp++]) << 24;
+ *l |= s->data[s->getp++] << 16;
+ *l |= s->data[s->getp++] << 8;
+ *l |= s->data[s->getp++];
+
+ return true;
+}
+
+uint32_t stream_getl(struct stream *s)
+{
+ uint32_t l;
+
+ STREAM_VERIFY_SANE(s);
+
+ if (STREAM_READABLE(s) < sizeof(uint32_t)) {
+ STREAM_BOUND_WARN(s, "get long");
+ return 0;
+ }
+
+ l = (unsigned)(s->data[s->getp++]) << 24;
+ l |= s->data[s->getp++] << 16;
+ l |= s->data[s->getp++] << 8;
+ l |= s->data[s->getp++];
+
+ return l;
+}
+
+/* Get next quad word from the stream. */
+uint64_t stream_getq_from(struct stream *s, size_t from)
+{
+ uint64_t q;
+
+ STREAM_VERIFY_SANE(s);
+
+ if (!GETP_VALID(s, from + sizeof(uint64_t))) {
+ STREAM_BOUND_WARN(s, "get quad");
+ return 0;
+ }
+
+ q = ((uint64_t)s->data[from++]) << 56;
+ q |= ((uint64_t)s->data[from++]) << 48;
+ q |= ((uint64_t)s->data[from++]) << 40;
+ q |= ((uint64_t)s->data[from++]) << 32;
+ q |= ((uint64_t)s->data[from++]) << 24;
+ q |= ((uint64_t)s->data[from++]) << 16;
+ q |= ((uint64_t)s->data[from++]) << 8;
+ q |= ((uint64_t)s->data[from++]);
+
+ return q;
+}
+
+uint64_t stream_getq(struct stream *s)
+{
+ uint64_t q;
+
+ STREAM_VERIFY_SANE(s);
+
+ if (STREAM_READABLE(s) < sizeof(uint64_t)) {
+ STREAM_BOUND_WARN(s, "get quad");
+ return 0;
+ }
+
+ q = ((uint64_t)s->data[s->getp++]) << 56;
+ q |= ((uint64_t)s->data[s->getp++]) << 48;
+ q |= ((uint64_t)s->data[s->getp++]) << 40;
+ q |= ((uint64_t)s->data[s->getp++]) << 32;
+ q |= ((uint64_t)s->data[s->getp++]) << 24;
+ q |= ((uint64_t)s->data[s->getp++]) << 16;
+ q |= ((uint64_t)s->data[s->getp++]) << 8;
+ q |= ((uint64_t)s->data[s->getp++]);
+
+ return q;
+}
+
+bool stream_getq2(struct stream *s, uint64_t *q)
+{
+ STREAM_VERIFY_SANE(s);
+
+ if (STREAM_READABLE(s) < sizeof(uint64_t)) {
+ STREAM_BOUND_WARN2(s, "get uint64");
+ return false;
+ }
+
+ *q = ((uint64_t)s->data[s->getp++]) << 56;
+ *q |= ((uint64_t)s->data[s->getp++]) << 48;
+ *q |= ((uint64_t)s->data[s->getp++]) << 40;
+ *q |= ((uint64_t)s->data[s->getp++]) << 32;
+ *q |= ((uint64_t)s->data[s->getp++]) << 24;
+ *q |= ((uint64_t)s->data[s->getp++]) << 16;
+ *q |= ((uint64_t)s->data[s->getp++]) << 8;
+ *q |= ((uint64_t)s->data[s->getp++]);
+
+ return true;
+}
+
+/* Get next long word from the stream. */
+uint32_t stream_get_ipv4(struct stream *s)
+{
+ uint32_t l;
+
+ STREAM_VERIFY_SANE(s);
+
+ if (STREAM_READABLE(s) < sizeof(uint32_t)) {
+ STREAM_BOUND_WARN(s, "get ipv4");
+ return 0;
+ }
+
+ memcpy(&l, s->data + s->getp, sizeof(uint32_t));
+ s->getp += sizeof(uint32_t);
+
+ return l;
+}
+
+bool stream_get_ipaddr(struct stream *s, struct ipaddr *ip)
+{
+ uint16_t ipa_len;
+
+ STREAM_VERIFY_SANE(s);
+
+ /* Get address type. */
+ if (STREAM_READABLE(s) < sizeof(uint16_t)) {
+ STREAM_BOUND_WARN2(s, "get ipaddr");
+ return false;
+ }
+ ip->ipa_type = stream_getw(s);
+
+ /* Get address value. */
+ switch (ip->ipa_type) {
+ case IPADDR_V4:
+ ipa_len = IPV4_MAX_BYTELEN;
+ break;
+ case IPADDR_V6:
+ ipa_len = IPV6_MAX_BYTELEN;
+ break;
+ default:
+ flog_err(EC_LIB_DEVELOPMENT,
+ "%s: unknown ip address-family: %u", __func__,
+ ip->ipa_type);
+ return false;
+ }
+ if (STREAM_READABLE(s) < ipa_len) {
+ STREAM_BOUND_WARN2(s, "get ipaddr");
+ return false;
+ }
+ memcpy(&ip->ip, s->data + s->getp, ipa_len);
+ s->getp += ipa_len;
+
+ return true;
+}
+
+float stream_getf(struct stream *s)
+{
+ union {
+ float r;
+ uint32_t d;
+ } u;
+ u.d = stream_getl(s);
+ return u.r;
+}
+
+double stream_getd(struct stream *s)
+{
+ union {
+ double r;
+ uint64_t d;
+ } u;
+ u.d = stream_getq(s);
+ return u.r;
+}
+
+/* Copy from source to stream.
+ *
+ * XXX: This uses CHECK_SIZE and hence has funny semantics -> Size will wrap
+ * around. This should be fixed once the stream updates are working.
+ *
+ * stream_write() is saner
+ */
+void stream_put(struct stream *s, const void *src, size_t size)
+{
+
+ /* XXX: CHECK_SIZE has strange semantics. It should be deprecated */
+ CHECK_SIZE(s, size);
+
+ STREAM_VERIFY_SANE(s);
+
+ if (STREAM_WRITEABLE(s) < size) {
+ STREAM_BOUND_WARN(s, "put");
+ return;
+ }
+
+ if (src)
+ memcpy(s->data + s->endp, src, size);
+ else
+ memset(s->data + s->endp, 0, size);
+
+ s->endp += size;
+}
+
+/* Put character to the stream. */
+int stream_putc(struct stream *s, uint8_t c)
+{
+ STREAM_VERIFY_SANE(s);
+
+ if (STREAM_WRITEABLE(s) < sizeof(uint8_t)) {
+ STREAM_BOUND_WARN(s, "put");
+ return 0;
+ }
+
+ s->data[s->endp++] = c;
+ return sizeof(uint8_t);
+}
+
+/* Put word to the stream. */
+int stream_putw(struct stream *s, uint16_t w)
+{
+ STREAM_VERIFY_SANE(s);
+
+ if (STREAM_WRITEABLE(s) < sizeof(uint16_t)) {
+ STREAM_BOUND_WARN(s, "put");
+ return 0;
+ }
+
+ s->data[s->endp++] = (uint8_t)(w >> 8);
+ s->data[s->endp++] = (uint8_t)w;
+
+ return 2;
+}
+
+/* Put long word to the stream. */
+int stream_put3(struct stream *s, uint32_t l)
+{
+ STREAM_VERIFY_SANE(s);
+
+ if (STREAM_WRITEABLE(s) < 3) {
+ STREAM_BOUND_WARN(s, "put");
+ return 0;
+ }
+
+ s->data[s->endp++] = (uint8_t)(l >> 16);
+ s->data[s->endp++] = (uint8_t)(l >> 8);
+ s->data[s->endp++] = (uint8_t)l;
+
+ return 3;
+}
+
+/* Put long word to the stream. */
+int stream_putl(struct stream *s, uint32_t l)
+{
+ STREAM_VERIFY_SANE(s);
+
+ if (STREAM_WRITEABLE(s) < sizeof(uint32_t)) {
+ STREAM_BOUND_WARN(s, "put");
+ return 0;
+ }
+
+ s->data[s->endp++] = (uint8_t)(l >> 24);
+ s->data[s->endp++] = (uint8_t)(l >> 16);
+ s->data[s->endp++] = (uint8_t)(l >> 8);
+ s->data[s->endp++] = (uint8_t)l;
+
+ return 4;
+}
+
+/* Put quad word to the stream. */
+int stream_putq(struct stream *s, uint64_t q)
+{
+ STREAM_VERIFY_SANE(s);
+
+ if (STREAM_WRITEABLE(s) < sizeof(uint64_t)) {
+ STREAM_BOUND_WARN(s, "put quad");
+ return 0;
+ }
+
+ s->data[s->endp++] = (uint8_t)(q >> 56);
+ s->data[s->endp++] = (uint8_t)(q >> 48);
+ s->data[s->endp++] = (uint8_t)(q >> 40);
+ s->data[s->endp++] = (uint8_t)(q >> 32);
+ s->data[s->endp++] = (uint8_t)(q >> 24);
+ s->data[s->endp++] = (uint8_t)(q >> 16);
+ s->data[s->endp++] = (uint8_t)(q >> 8);
+ s->data[s->endp++] = (uint8_t)q;
+
+ return 8;
+}
+
+int stream_putf(struct stream *s, float f)
+{
+ union {
+ float i;
+ uint32_t o;
+ } u;
+ u.i = f;
+ return stream_putl(s, u.o);
+}
+
+int stream_putd(struct stream *s, double d)
+{
+ union {
+ double i;
+ uint64_t o;
+ } u;
+ u.i = d;
+ return stream_putq(s, u.o);
+}
+
+int stream_putc_at(struct stream *s, size_t putp, uint8_t c)
+{
+ STREAM_VERIFY_SANE(s);
+
+ if (!PUT_AT_VALID(s, putp + sizeof(uint8_t))) {
+ STREAM_BOUND_WARN(s, "put");
+ return 0;
+ }
+
+ s->data[putp] = c;
+
+ return 1;
+}
+
+int stream_putw_at(struct stream *s, size_t putp, uint16_t w)
+{
+ STREAM_VERIFY_SANE(s);
+
+ if (!PUT_AT_VALID(s, putp + sizeof(uint16_t))) {
+ STREAM_BOUND_WARN(s, "put");
+ return 0;
+ }
+
+ s->data[putp] = (uint8_t)(w >> 8);
+ s->data[putp + 1] = (uint8_t)w;
+
+ return 2;
+}
+
+int stream_put3_at(struct stream *s, size_t putp, uint32_t l)
+{
+ STREAM_VERIFY_SANE(s);
+
+ if (!PUT_AT_VALID(s, putp + 3)) {
+ STREAM_BOUND_WARN(s, "put");
+ return 0;
+ }
+ s->data[putp] = (uint8_t)(l >> 16);
+ s->data[putp + 1] = (uint8_t)(l >> 8);
+ s->data[putp + 2] = (uint8_t)l;
+
+ return 3;
+}
+
+int stream_putl_at(struct stream *s, size_t putp, uint32_t l)
+{
+ STREAM_VERIFY_SANE(s);
+
+ if (!PUT_AT_VALID(s, putp + sizeof(uint32_t))) {
+ STREAM_BOUND_WARN(s, "put");
+ return 0;
+ }
+ s->data[putp] = (uint8_t)(l >> 24);
+ s->data[putp + 1] = (uint8_t)(l >> 16);
+ s->data[putp + 2] = (uint8_t)(l >> 8);
+ s->data[putp + 3] = (uint8_t)l;
+
+ return 4;
+}
+
+int stream_putq_at(struct stream *s, size_t putp, uint64_t q)
+{
+ STREAM_VERIFY_SANE(s);
+
+ if (!PUT_AT_VALID(s, putp + sizeof(uint64_t))) {
+ STREAM_BOUND_WARN(s, "put");
+ return 0;
+ }
+ s->data[putp] = (uint8_t)(q >> 56);
+ s->data[putp + 1] = (uint8_t)(q >> 48);
+ s->data[putp + 2] = (uint8_t)(q >> 40);
+ s->data[putp + 3] = (uint8_t)(q >> 32);
+ s->data[putp + 4] = (uint8_t)(q >> 24);
+ s->data[putp + 5] = (uint8_t)(q >> 16);
+ s->data[putp + 6] = (uint8_t)(q >> 8);
+ s->data[putp + 7] = (uint8_t)q;
+
+ return 8;
+}
+
+/* Put long word to the stream. */
+int stream_put_ipv4(struct stream *s, uint32_t l)
+{
+ STREAM_VERIFY_SANE(s);
+
+ if (STREAM_WRITEABLE(s) < sizeof(uint32_t)) {
+ STREAM_BOUND_WARN(s, "put");
+ return 0;
+ }
+ memcpy(s->data + s->endp, &l, sizeof(uint32_t));
+ s->endp += sizeof(uint32_t);
+
+ return sizeof(uint32_t);
+}
+
+/* Put long word to the stream. */
+int stream_put_in_addr(struct stream *s, const struct in_addr *addr)
+{
+ STREAM_VERIFY_SANE(s);
+
+ if (STREAM_WRITEABLE(s) < sizeof(uint32_t)) {
+ STREAM_BOUND_WARN(s, "put");
+ return 0;
+ }
+
+ memcpy(s->data + s->endp, addr, sizeof(uint32_t));
+ s->endp += sizeof(uint32_t);
+
+ return sizeof(uint32_t);
+}
+
+bool stream_put_ipaddr(struct stream *s, struct ipaddr *ip)
+{
+ stream_putw(s, ip->ipa_type);
+
+ switch (ip->ipa_type) {
+ case IPADDR_V4:
+ stream_put_in_addr(s, &ip->ipaddr_v4);
+ break;
+ case IPADDR_V6:
+ stream_write(s, (uint8_t *)&ip->ipaddr_v6, 16);
+ break;
+ default:
+ flog_err(EC_LIB_DEVELOPMENT,
+ "%s: unknown ip address-family: %u", __func__,
+ ip->ipa_type);
+ return false;
+ }
+
+ return true;
+}
+
+/* Put in_addr at location in the stream. */
+int stream_put_in_addr_at(struct stream *s, size_t putp,
+ const struct in_addr *addr)
+{
+ STREAM_VERIFY_SANE(s);
+
+ if (!PUT_AT_VALID(s, putp + 4)) {
+ STREAM_BOUND_WARN(s, "put");
+ return 0;
+ }
+
+ memcpy(&s->data[putp], addr, 4);
+ return 4;
+}
+
+/* Put in6_addr at location in the stream. */
+int stream_put_in6_addr_at(struct stream *s, size_t putp,
+ const struct in6_addr *addr)
+{
+ STREAM_VERIFY_SANE(s);
+
+ if (!PUT_AT_VALID(s, putp + 16)) {
+ STREAM_BOUND_WARN(s, "put");
+ return 0;
+ }
+
+ memcpy(&s->data[putp], addr, 16);
+ return 16;
+}
+
+/* Put prefix by nlri type format. */
+int stream_put_prefix_addpath(struct stream *s, const struct prefix *p,
+ bool addpath_capable, uint32_t addpath_tx_id)
+{
+ size_t psize;
+ size_t psize_with_addpath;
+
+ STREAM_VERIFY_SANE(s);
+
+ psize = PSIZE(p->prefixlen);
+
+ if (addpath_capable)
+ psize_with_addpath = psize + 4;
+ else
+ psize_with_addpath = psize;
+
+ if (STREAM_WRITEABLE(s) < (psize_with_addpath + sizeof(uint8_t))) {
+ STREAM_BOUND_WARN(s, "put");
+ return 0;
+ }
+
+ if (addpath_capable) {
+ s->data[s->endp++] = (uint8_t)(addpath_tx_id >> 24);
+ s->data[s->endp++] = (uint8_t)(addpath_tx_id >> 16);
+ s->data[s->endp++] = (uint8_t)(addpath_tx_id >> 8);
+ s->data[s->endp++] = (uint8_t)addpath_tx_id;
+ }
+
+ s->data[s->endp++] = p->prefixlen;
+ memcpy(s->data + s->endp, &p->u.prefix, psize);
+ s->endp += psize;
+
+ return psize;
+}
+
+int stream_put_prefix(struct stream *s, const struct prefix *p)
+{
+ return stream_put_prefix_addpath(s, p, 0, 0);
+}
+
+/* Put NLRI with label */
+int stream_put_labeled_prefix(struct stream *s, const struct prefix *p,
+ mpls_label_t *label, bool addpath_capable,
+ uint32_t addpath_tx_id)
+{
+ size_t psize;
+ size_t psize_with_addpath;
+ uint8_t *label_pnt = (uint8_t *)label;
+
+ STREAM_VERIFY_SANE(s);
+
+ psize = PSIZE(p->prefixlen);
+ psize_with_addpath = psize + (addpath_capable ? 4 : 0);
+
+ if (STREAM_WRITEABLE(s) < (psize_with_addpath + 3)) {
+ STREAM_BOUND_WARN(s, "put");
+ return 0;
+ }
+
+ if (addpath_capable) {
+ s->data[s->endp++] = (uint8_t)(addpath_tx_id >> 24);
+ s->data[s->endp++] = (uint8_t)(addpath_tx_id >> 16);
+ s->data[s->endp++] = (uint8_t)(addpath_tx_id >> 8);
+ s->data[s->endp++] = (uint8_t)addpath_tx_id;
+ }
+
+ stream_putc(s, (p->prefixlen + 24));
+ stream_putc(s, label_pnt[0]);
+ stream_putc(s, label_pnt[1]);
+ stream_putc(s, label_pnt[2]);
+ memcpy(s->data + s->endp, &p->u.prefix, psize);
+ s->endp += psize;
+
+ return (psize + 3);
+}
+
+/* Read size from fd. */
+int stream_read(struct stream *s, int fd, size_t size)
+{
+ int nbytes;
+
+ STREAM_VERIFY_SANE(s);
+
+ if (STREAM_WRITEABLE(s) < size) {
+ STREAM_BOUND_WARN(s, "put");
+ return 0;
+ }
+
+ nbytes = readn(fd, s->data + s->endp, size);
+
+ if (nbytes > 0)
+ s->endp += nbytes;
+
+ return nbytes;
+}
+
+ssize_t stream_read_try(struct stream *s, int fd, size_t size)
+{
+ ssize_t nbytes;
+
+ STREAM_VERIFY_SANE(s);
+
+ if (STREAM_WRITEABLE(s) < size) {
+ STREAM_BOUND_WARN(s, "put");
+ /* Fatal (not transient) error, since retrying will not help
+ (stream is too small to contain the desired data). */
+ return -1;
+ }
+
+ nbytes = read(fd, s->data + s->endp, size);
+ if (nbytes >= 0) {
+ s->endp += nbytes;
+ return nbytes;
+ }
+ /* Error: was it transient (return -2) or fatal (return -1)? */
+ if (ERRNO_IO_RETRY(errno))
+ return -2;
+ flog_err(EC_LIB_SOCKET, "%s: read failed on fd %d: %s", __func__, fd,
+ safe_strerror(errno));
+ return -1;
+}
+
+/* Read up to size bytes into the stream from the fd, using recvmsgfrom
+ * whose arguments match the remaining arguments to this function
+ */
+ssize_t stream_recvfrom(struct stream *s, int fd, size_t size, int flags,
+ struct sockaddr *from, socklen_t *fromlen)
+{
+ ssize_t nbytes;
+
+ STREAM_VERIFY_SANE(s);
+
+ if (STREAM_WRITEABLE(s) < size) {
+ STREAM_BOUND_WARN(s, "put");
+ /* Fatal (not transient) error, since retrying will not help
+ (stream is too small to contain the desired data). */
+ return -1;
+ }
+
+ nbytes = recvfrom(fd, s->data + s->endp, size, flags, from, fromlen);
+ if (nbytes >= 0) {
+ s->endp += nbytes;
+ return nbytes;
+ }
+ /* Error: was it transient (return -2) or fatal (return -1)? */
+ if (ERRNO_IO_RETRY(errno))
+ return -2;
+ flog_err(EC_LIB_SOCKET, "%s: read failed on fd %d: %s", __func__, fd,
+ safe_strerror(errno));
+ return -1;
+}
+
+/* Read up to smaller of size or SIZE_REMAIN() bytes to the stream, starting
+ * from endp.
+ * First iovec will be used to receive the data.
+ * Stream need not be empty.
+ */
+ssize_t stream_recvmsg(struct stream *s, int fd, struct msghdr *msgh, int flags,
+ size_t size)
+{
+ int nbytes;
+ struct iovec *iov;
+
+ STREAM_VERIFY_SANE(s);
+ assert(msgh->msg_iovlen > 0);
+
+ if (STREAM_WRITEABLE(s) < size) {
+ STREAM_BOUND_WARN(s, "put");
+ /* This is a logic error in the calling code: the stream is too
+ small
+ to hold the desired data! */
+ return -1;
+ }
+
+ iov = &(msgh->msg_iov[0]);
+ iov->iov_base = (s->data + s->endp);
+ iov->iov_len = size;
+
+ nbytes = recvmsg(fd, msgh, flags);
+
+ if (nbytes > 0)
+ s->endp += nbytes;
+
+ return nbytes;
+}
+
+/* Write data to buffer. */
+size_t stream_write(struct stream *s, const void *ptr, size_t size)
+{
+
+ CHECK_SIZE(s, size);
+
+ STREAM_VERIFY_SANE(s);
+
+ if (STREAM_WRITEABLE(s) < size) {
+ STREAM_BOUND_WARN(s, "put");
+ return 0;
+ }
+
+ memcpy(s->data + s->endp, ptr, size);
+ s->endp += size;
+
+ return size;
+}
+
+/* Return current read pointer.
+ * DEPRECATED!
+ * Use stream_get_pnt_to if you must, but decoding streams properly
+ * is preferred
+ */
+uint8_t *stream_pnt(struct stream *s)
+{
+ STREAM_VERIFY_SANE(s);
+ return s->data + s->getp;
+}
+
+/* Check does this stream empty? */
+int stream_empty(struct stream *s)
+{
+ STREAM_VERIFY_SANE(s);
+
+ return (s->endp == 0);
+}
+
+/* Reset stream. */
+void stream_reset(struct stream *s)
+{
+ STREAM_VERIFY_SANE(s);
+
+ s->getp = s->endp = 0;
+}
+
+/* Write stream contens to the file discriptor. */
+int stream_flush(struct stream *s, int fd)
+{
+ int nbytes;
+
+ STREAM_VERIFY_SANE(s);
+
+ nbytes = write(fd, s->data + s->getp, s->endp - s->getp);
+
+ return nbytes;
+}
+
+void stream_hexdump(const struct stream *s)
+{
+ zlog_hexdump(s->data, s->endp);
+}
+
+/* Stream first in first out queue. */
+
+struct stream_fifo *stream_fifo_new(void)
+{
+ struct stream_fifo *new;
+
+ new = XMALLOC(MTYPE_STREAM_FIFO, sizeof(struct stream_fifo));
+ stream_fifo_init(new);
+ return new;
+}
+
+void stream_fifo_init(struct stream_fifo *fifo)
+{
+ memset(fifo, 0, sizeof(struct stream_fifo));
+ pthread_mutex_init(&fifo->mtx, NULL);
+}
+
+/* Add new stream to fifo. */
+void stream_fifo_push(struct stream_fifo *fifo, struct stream *s)
+{
+#if defined DEV_BUILD
+ size_t max, curmax;
+#endif
+
+ if (fifo->tail)
+ fifo->tail->next = s;
+ else
+ fifo->head = s;
+
+ fifo->tail = s;
+ fifo->tail->next = NULL;
+#if !defined DEV_BUILD
+ atomic_fetch_add_explicit(&fifo->count, 1, memory_order_release);
+#else
+ max = atomic_fetch_add_explicit(&fifo->count, 1, memory_order_release);
+ curmax = atomic_load_explicit(&fifo->max_count, memory_order_relaxed);
+ if (max > curmax)
+ atomic_store_explicit(&fifo->max_count, max,
+ memory_order_relaxed);
+#endif
+}
+
+void stream_fifo_push_safe(struct stream_fifo *fifo, struct stream *s)
+{
+ frr_with_mutex (&fifo->mtx) {
+ stream_fifo_push(fifo, s);
+ }
+}
+
+/* Delete first stream from fifo. */
+struct stream *stream_fifo_pop(struct stream_fifo *fifo)
+{
+ struct stream *s;
+
+ s = fifo->head;
+
+ if (s) {
+ fifo->head = s->next;
+
+ if (fifo->head == NULL)
+ fifo->tail = NULL;
+
+ atomic_fetch_sub_explicit(&fifo->count, 1,
+ memory_order_release);
+
+ /* ensure stream is scrubbed of references to this fifo */
+ s->next = NULL;
+ }
+
+ return s;
+}
+
+struct stream *stream_fifo_pop_safe(struct stream_fifo *fifo)
+{
+ struct stream *ret;
+
+ frr_with_mutex (&fifo->mtx) {
+ ret = stream_fifo_pop(fifo);
+ }
+
+ return ret;
+}
+
+struct stream *stream_fifo_head(struct stream_fifo *fifo)
+{
+ return fifo->head;
+}
+
+struct stream *stream_fifo_head_safe(struct stream_fifo *fifo)
+{
+ struct stream *ret;
+
+ frr_with_mutex (&fifo->mtx) {
+ ret = stream_fifo_head(fifo);
+ }
+
+ return ret;
+}
+
+void stream_fifo_clean(struct stream_fifo *fifo)
+{
+ struct stream *s;
+ struct stream *next;
+
+ for (s = fifo->head; s; s = next) {
+ next = s->next;
+ stream_free(s);
+ }
+ fifo->head = fifo->tail = NULL;
+ atomic_store_explicit(&fifo->count, 0, memory_order_release);
+}
+
+void stream_fifo_clean_safe(struct stream_fifo *fifo)
+{
+ frr_with_mutex (&fifo->mtx) {
+ stream_fifo_clean(fifo);
+ }
+}
+
+size_t stream_fifo_count_safe(struct stream_fifo *fifo)
+{
+ return atomic_load_explicit(&fifo->count, memory_order_acquire);
+}
+
+void stream_fifo_deinit(struct stream_fifo *fifo)
+{
+ stream_fifo_clean(fifo);
+ pthread_mutex_destroy(&fifo->mtx);
+}
+
+void stream_fifo_free(struct stream_fifo *fifo)
+{
+ stream_fifo_deinit(fifo);
+ XFREE(MTYPE_STREAM_FIFO, fifo);
+}
+
+void stream_pulldown(struct stream *s)
+{
+ size_t rlen = STREAM_READABLE(s);
+
+ /* No more data, so just move the pointers. */
+ if (rlen == 0) {
+ stream_reset(s);
+ return;
+ }
+
+ /* Move the available data to the beginning. */
+ memmove(s->data, &s->data[s->getp], rlen);
+ s->getp = 0;
+ s->endp = rlen;
+}
diff --git a/lib/stream.h b/lib/stream.h
new file mode 100644
index 0000000..35733e7
--- /dev/null
+++ b/lib/stream.h
@@ -0,0 +1,500 @@
+/*
+ * Packet interface
+ * Copyright (C) 1999 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 _ZEBRA_STREAM_H
+#define _ZEBRA_STREAM_H
+
+#include <pthread.h>
+
+#include "frratomic.h"
+#include "mpls.h"
+#include "prefix.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * A stream is an arbitrary buffer, whose contents generally are assumed to
+ * be in network order.
+ *
+ * A stream has the following attributes associated with it:
+ *
+ * - size: the allocated, invariant size of the buffer.
+ *
+ * - getp: the get position marker, denoting the offset in the stream where
+ * the next read (or 'get') will be from. This getp marker is
+ * automatically adjusted when data is read from the stream, the
+ * user may also manipulate this offset as they wish, within limits
+ * (see below)
+ *
+ * - endp: the end position marker, denoting the offset in the stream where
+ * valid data ends, and if the user attempted to write (or
+ * 'put') data where that data would be written (or 'put') to.
+ *
+ * These attributes are all size_t values.
+ *
+ * Constraints:
+ *
+ * 1. getp can never exceed endp
+ *
+ * - hence if getp is equal to endp, there is no more valid data that can be
+ * gotten from the stream (though, the user may reposition getp to earlier in
+ * the stream, if they wish).
+ *
+ * 2. endp can never exceed size
+ *
+ * - hence, if endp is equal to size, then the stream is full, and no more
+ * data can be written to the stream.
+ *
+ * In other words the following must always be true, and the stream
+ * abstraction is allowed internally to assert that the following property
+ * holds true for a stream, as and when it wishes:
+ *
+ * getp <= endp <= size
+ *
+ * It is the users responsibility to ensure this property is never violated.
+ *
+ * A stream therefore can be thought of like this:
+ *
+ * ---------------------------------------------------
+ * |XXXXXXXXXXXXXXXXXXXXXXXX |
+ * ---------------------------------------------------
+ * ^ ^ ^
+ * getp endp size
+ *
+ * This shows a stream containing data (shown as 'X') up to the endp offset.
+ * The stream is empty from endp to size. Without adjusting getp, there are
+ * still endp-getp bytes of valid data to be read from the stream.
+ *
+ * Methods are provided to get and put to/from the stream, as well as
+ * retrieve the values of the 3 markers and manipulate the getp marker.
+ *
+ * Note:
+ * At the moment, newly allocated streams are zero filled. Hence, one can
+ * use stream_forward_endp() to effectively create arbitrary zero-fill
+ * padding. However, note that stream_reset() does *not* zero-out the
+ * stream. This property should **not** be relied upon.
+ *
+ * Best practice is to use stream_put (<stream *>, NULL, <size>) to zero out
+ * any part of a stream which isn't otherwise written to.
+ */
+
+/* Stream buffer. */
+struct stream {
+ struct stream *next;
+
+ /*
+ * Remainder is ***private*** to stream
+ * direct access is frowned upon!
+ * Use the appropriate functions/macros
+ */
+ size_t getp; /* next get position */
+ size_t endp; /* last valid data position */
+ size_t size; /* size of data segment */
+ unsigned char data[]; /* data pointer */
+};
+
+/* First in first out queue structure. */
+struct stream_fifo {
+ /* lock for mt-safe operations */
+ pthread_mutex_t mtx;
+
+ /* number of streams in this fifo */
+ atomic_size_t count;
+#if defined DEV_BUILD
+ atomic_size_t max_count;
+#endif
+
+ struct stream *head;
+ struct stream *tail;
+};
+
+/* Utility macros. */
+#define STREAM_SIZE(S) ((S)->size)
+/* number of bytes which can still be written */
+#define STREAM_WRITEABLE(S) ((S)->size - (S)->endp)
+/* number of bytes still to be read */
+#define STREAM_READABLE(S) ((S)->endp - (S)->getp)
+
+#define STREAM_CONCAT_REMAIN(S1, S2, size) ((size) - (S1)->endp - (S2)->endp)
+
+/* this macro is deprecated, but not slated for removal anytime soon */
+#define STREAM_DATA(S) ((S)->data)
+
+/* Stream prototypes.
+ * For stream_{put,get}S, the S suffix mean:
+ *
+ * c: character (unsigned byte)
+ * w: word (two bytes)
+ * l: long (two words)
+ * q: quad (four words)
+ */
+extern struct stream *stream_new(size_t);
+extern void stream_free(struct stream *);
+/* Copy 'src' into 'dest', returns 'dest' */
+extern struct stream *stream_copy(struct stream *dest,
+ const struct stream *src);
+extern struct stream *stream_dup(const struct stream *s);
+
+extern size_t stream_resize_inplace(struct stream **sptr, size_t newsize);
+
+extern size_t stream_get_getp(const struct stream *s);
+extern size_t stream_get_endp(const struct stream *s);
+extern size_t stream_get_size(const struct stream *s);
+
+/**
+ * Create a new stream structure; copy offset bytes from s1 to the new
+ * stream; copy s2 data to the new stream; copy rest of s1 data to the
+ * new stream.
+ */
+extern struct stream *stream_dupcat(const struct stream *s1,
+ const struct stream *s2, size_t offset);
+
+extern void stream_set_getp(struct stream *, size_t);
+extern void stream_set_endp(struct stream *, size_t);
+extern void stream_forward_getp(struct stream *, size_t);
+extern bool stream_forward_getp2(struct stream *, size_t);
+extern void stream_rewind_getp(struct stream *s, size_t size);
+extern bool stream_rewind_getp2(struct stream *s, size_t size);
+extern void stream_forward_endp(struct stream *, size_t);
+extern bool stream_forward_endp2(struct stream *, size_t);
+
+/* steam_put: NULL source zeroes out size_t bytes of stream */
+extern void stream_put(struct stream *, const void *, size_t);
+extern int stream_putc(struct stream *, uint8_t);
+extern int stream_putc_at(struct stream *, size_t, uint8_t);
+extern int stream_putw(struct stream *, uint16_t);
+extern int stream_putw_at(struct stream *, size_t, uint16_t);
+extern int stream_put3(struct stream *, uint32_t);
+extern int stream_put3_at(struct stream *, size_t, uint32_t);
+extern int stream_putl(struct stream *, uint32_t);
+extern int stream_putl_at(struct stream *, size_t, uint32_t);
+extern int stream_putq(struct stream *, uint64_t);
+extern int stream_putq_at(struct stream *, size_t, uint64_t);
+extern int stream_put_ipv4(struct stream *, uint32_t);
+extern int stream_put_in_addr(struct stream *s, const struct in_addr *addr);
+extern bool stream_put_ipaddr(struct stream *s, struct ipaddr *ip);
+extern int stream_put_in_addr_at(struct stream *s, size_t putp,
+ const struct in_addr *addr);
+extern int stream_put_in6_addr_at(struct stream *s, size_t putp,
+ const struct in6_addr *addr);
+extern int stream_put_prefix_addpath(struct stream *s, const struct prefix *p,
+ bool addpath_capable,
+ uint32_t addpath_tx_id);
+extern int stream_put_prefix(struct stream *s, const struct prefix *p);
+extern int stream_put_labeled_prefix(struct stream *, const struct prefix *,
+ mpls_label_t *, bool addpath_capable,
+ uint32_t addpath_tx_id);
+extern void stream_get(void *, struct stream *, size_t);
+extern bool stream_get2(void *data, struct stream *s, size_t size);
+extern void stream_get_from(void *, struct stream *, size_t, size_t);
+extern uint8_t stream_getc(struct stream *);
+extern bool stream_getc2(struct stream *s, uint8_t *byte);
+extern uint8_t stream_getc_from(struct stream *, size_t);
+extern uint16_t stream_getw(struct stream *);
+extern bool stream_getw2(struct stream *s, uint16_t *word);
+extern uint16_t stream_getw_from(struct stream *, size_t);
+extern uint32_t stream_get3(struct stream *);
+extern uint32_t stream_get3_from(struct stream *, size_t);
+extern uint32_t stream_getl(struct stream *);
+extern bool stream_getl2(struct stream *s, uint32_t *l);
+extern uint32_t stream_getl_from(struct stream *, size_t);
+extern uint64_t stream_getq(struct stream *);
+extern uint64_t stream_getq_from(struct stream *, size_t);
+bool stream_getq2(struct stream *s, uint64_t *q);
+extern uint32_t stream_get_ipv4(struct stream *);
+extern bool stream_get_ipaddr(struct stream *s, struct ipaddr *ip);
+
+/* IEEE-754 floats */
+extern float stream_getf(struct stream *);
+extern double stream_getd(struct stream *);
+extern int stream_putf(struct stream *, float);
+extern int stream_putd(struct stream *, double);
+
+#undef stream_read
+#undef stream_write
+
+/* Deprecated: assumes blocking I/O. Will be removed.
+ Use stream_read_try instead. */
+extern int stream_read(struct stream *, int, size_t);
+
+/* Read up to size bytes into the stream.
+ Return code:
+ >0: number of bytes read
+ 0: end-of-file
+ -1: fatal error
+ -2: transient error, should retry later (i.e. EAGAIN or EINTR)
+ This is suitable for use with non-blocking file descriptors.
+ */
+extern ssize_t stream_read_try(struct stream *s, int fd, size_t size);
+
+extern ssize_t stream_recvmsg(struct stream *s, int fd, struct msghdr *,
+ int flags, size_t size);
+extern ssize_t stream_recvfrom(struct stream *s, int fd, size_t len, int flags,
+ struct sockaddr *from, socklen_t *fromlen);
+extern size_t stream_write(struct stream *, const void *, size_t);
+
+/* reset the stream. See Note above */
+extern void stream_reset(struct stream *);
+extern int stream_flush(struct stream *, int);
+extern int stream_empty(struct stream *); /* is the stream empty? */
+
+/* debugging */
+extern void stream_hexdump(const struct stream *s);
+
+/**
+ * Reorganize the buffer data so it can fit more. This function is normally
+ * called right after stream data is consumed so we can read more data
+ * (the functions that consume data start with `stream_get*()` and macros
+ * `STREAM_GET*()`).
+ *
+ * \param s stream pointer.
+ */
+extern void stream_pulldown(struct stream *s);
+
+/* deprecated */
+extern uint8_t *stream_pnt(struct stream *);
+
+/*
+ * Operations on struct stream_fifo.
+ *
+ * Each function has a safe variant, which ensures that the operation performed
+ * is atomic with respect to the operations performed by all other safe
+ * variants. In other words, the safe variants lock the stream_fifo's mutex
+ * before performing their action. These are provided for convenience when
+ * using stream_fifo in a multithreaded context, to alleviate the need for the
+ * caller to implement their own synchronization around the stream_fifo.
+ *
+ * The following functions do not have safe variants. The caller must ensure
+ * that these operations are performed safely in a multithreaded context:
+ * - stream_fifo_new
+ * - stream_fifo_free
+ */
+
+/*
+ * Create a new stream_fifo.
+ *
+ * Returns:
+ * newly created stream_fifo
+ */
+extern struct stream_fifo *stream_fifo_new(void);
+
+/*
+ * Init or re-init an on-stack fifo. This allows use of a fifo struct without
+ * requiring a malloc/free cycle.
+ * Note well that the fifo must be de-inited with the 'fifo_deinit' api.
+ */
+void stream_fifo_init(struct stream_fifo *fifo);
+
+/*
+ * Deinit an on-stack fifo.
+ */
+void stream_fifo_deinit(struct stream_fifo *fifo);
+
+/*
+ * Push a stream onto a stream_fifo.
+ *
+ * fifo
+ * the stream_fifo to push onto
+ *
+ * s
+ * the stream to push onto the stream_fifo
+ */
+extern void stream_fifo_push(struct stream_fifo *fifo, struct stream *s);
+extern void stream_fifo_push_safe(struct stream_fifo *fifo, struct stream *s);
+
+/*
+ * Pop a stream off a stream_fifo.
+ *
+ * fifo
+ * the stream_fifo to pop from
+ *
+ * Returns:
+ * the next stream in the stream_fifo
+ */
+extern struct stream *stream_fifo_pop(struct stream_fifo *fifo);
+extern struct stream *stream_fifo_pop_safe(struct stream_fifo *fifo);
+
+/*
+ * Retrieve the next stream from a stream_fifo without popping it.
+ *
+ * fifo
+ * the stream_fifo to operate on
+ *
+ * Returns:
+ * the next stream that would be returned from stream_fifo_pop
+ */
+extern struct stream *stream_fifo_head(struct stream_fifo *fifo);
+extern struct stream *stream_fifo_head_safe(struct stream_fifo *fifo);
+
+/*
+ * Remove all streams from a stream_fifo.
+ *
+ * fifo
+ * the stream_fifo to clean
+ */
+extern void stream_fifo_clean(struct stream_fifo *fifo);
+extern void stream_fifo_clean_safe(struct stream_fifo *fifo);
+
+/*
+ * Retrieve number of streams on a stream_fifo.
+ *
+ * fifo
+ * the stream_fifo to retrieve the count for
+ *
+ * Returns:
+ * the number of streams on the stream_fifo
+ */
+extern size_t stream_fifo_count_safe(struct stream_fifo *fifo);
+
+/*
+ * Free a stream_fifo.
+ *
+ * Calls stream_fifo_clean, then deinitializes the stream_fifo and frees it.
+ *
+ * fifo
+ * the stream_fifo to free
+ */
+extern void stream_fifo_free(struct stream_fifo *fifo);
+
+/* This is here because "<< 24" is particularly problematic in C.
+ * This is because the left operand of << is integer-promoted, which means
+ * an uint8_t gets converted into a *signed* int. Shifting into the sign
+ * bit of a signed int is theoretically undefined behaviour, so - the left
+ * operand needs to be cast to unsigned.
+ *
+ * This is not a problem for 16- or 8-bit values (they don't reach the sign
+ * bit), for 64-bit values (you need to cast them anyway), and neither for
+ * encoding (because it's downcasted.)
+ */
+static inline const uint8_t *ptr_get_be32(const uint8_t *ptr, uint32_t *out)
+{
+ uint32_t tmp;
+
+ memcpy(&tmp, ptr, sizeof(tmp));
+ *out = ntohl(tmp);
+ return ptr + 4;
+}
+
+static inline uint8_t *ptr_get_be16(uint8_t *ptr, uint16_t *out)
+{
+ uint16_t tmp;
+
+ memcpy(&tmp, ptr, sizeof(tmp));
+ *out = ntohs(tmp);
+
+ return ptr + 2;
+}
+
+/*
+ * so Normal stream_getX functions assert. Which is anathema
+ * to keeping a daemon up and running when something goes south
+ * Provide a stream_getX2 functions that do not assert.
+ * In addition provide these macro's that upon failure
+ * goto stream_failure. This is modeled upon some NL_XX
+ * macros in the linux kernel.
+ *
+ * This change allows for proper memory freeing
+ * after we've detected an error.
+ *
+ * In the future we will be removing the assert in
+ * the stream functions but we need a transition
+ * plan.
+ */
+#define STREAM_GETC(S, P) \
+ do { \
+ uint8_t _pval; \
+ if (!stream_getc2((S), &_pval)) \
+ goto stream_failure; \
+ (P) = _pval; \
+ } while (0)
+
+#define STREAM_GETW(S, P) \
+ do { \
+ uint16_t _pval; \
+ if (!stream_getw2((S), &_pval)) \
+ goto stream_failure; \
+ (P) = _pval; \
+ } while (0)
+
+#define STREAM_GETL(S, P) \
+ do { \
+ uint32_t _pval; \
+ if (!stream_getl2((S), &_pval)) \
+ goto stream_failure; \
+ (P) = _pval; \
+ } while (0)
+
+#define STREAM_GETF(S, P) \
+ do { \
+ union { \
+ float r; \
+ uint32_t d; \
+ } _pval; \
+ if (!stream_getl2((S), &_pval.d)) \
+ goto stream_failure; \
+ (P) = _pval.r; \
+ } while (0)
+
+#define STREAM_GETQ(S, P) \
+ do { \
+ uint64_t _pval; \
+ if (!stream_getq2((S), &_pval)) \
+ goto stream_failure; \
+ (P) = _pval; \
+ } while (0)
+
+#define STREAM_GET_IPADDR(S, P) \
+ do { \
+ if (!stream_get_ipaddr((S), (P))) \
+ goto stream_failure; \
+ } while (0)
+
+#define STREAM_GET(P, STR, SIZE) \
+ do { \
+ if (!stream_get2((P), (STR), (SIZE))) \
+ goto stream_failure; \
+ } while (0)
+
+#define STREAM_FORWARD_GETP(STR, SIZE) \
+ do { \
+ if (!stream_forward_getp2((STR), (SIZE))) \
+ goto stream_failure; \
+ } while (0)
+
+#define STREAM_REWIND_GETP(STR, SIZE) \
+ do { \
+ if (!stream_rewind_getp2((STR), (SIZE))) \
+ goto stream_failure; \
+ } while (0)
+
+#define STREAM_FORWARD_ENDP(STR, SIZE) \
+ do { \
+ if (!stream_forward_endp2((STR), (SIZE))) \
+ goto stream_failure; \
+ } while (0)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ZEBRA_STREAM_H */
diff --git a/lib/strformat.c b/lib/strformat.c
new file mode 100644
index 0000000..d941a7f
--- /dev/null
+++ b/lib/strformat.c
@@ -0,0 +1,599 @@
+/*
+ * Copyright (c) 2019 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "compiler.h"
+
+#include <string.h>
+#include <ctype.h>
+#include <time.h>
+
+#include "printfrr.h"
+#include "monotime.h"
+
+printfrr_ext_autoreg_p("HX", printfrr_hexdump);
+static ssize_t printfrr_hexdump(struct fbuf *buf, struct printfrr_eargs *ea,
+ const void *ptr)
+{
+ ssize_t ret = 0;
+ ssize_t input_len = printfrr_ext_len(ea);
+ char sep = ' ';
+ const uint8_t *pos, *end;
+
+ if (ea->fmt[0] == 'c') {
+ ea->fmt++;
+ sep = ':';
+ } else if (ea->fmt[0] == 'n') {
+ ea->fmt++;
+ sep = '\0';
+ }
+
+ if (input_len < 0)
+ return 0;
+
+ for (pos = ptr, end = pos + input_len; pos < end; pos++) {
+ if (sep && pos != ptr)
+ ret += bputch(buf, sep);
+ ret += bputhex(buf, *pos);
+ }
+
+ return ret;
+}
+
+/* string analog for hexdumps / the "this." in ("74 68 69 73 0a |this.|") */
+
+printfrr_ext_autoreg_p("HS", printfrr_hexdstr);
+static ssize_t printfrr_hexdstr(struct fbuf *buf, struct printfrr_eargs *ea,
+ const void *ptr)
+{
+ ssize_t ret = 0;
+ ssize_t input_len = printfrr_ext_len(ea);
+ const uint8_t *pos, *end;
+
+ if (input_len < 0)
+ return 0;
+
+ for (pos = ptr, end = pos + input_len; pos < end; pos++) {
+ if (*pos >= 0x20 && *pos < 0x7f)
+ ret += bputch(buf, *pos);
+ else
+ ret += bputch(buf, '.');
+ }
+
+ return ret;
+}
+
+enum escape_flags {
+ ESC_N_R_T = (1 << 0), /* use \n \r \t instead of \x0a ...*/
+ ESC_SPACE = (1 << 1), /* \ */
+ ESC_BACKSLASH = (1 << 2), /* \\ */
+ ESC_DBLQUOTE = (1 << 3), /* \" */
+ ESC_SGLQUOTE = (1 << 4), /* \' */
+ ESC_BACKTICK = (1 << 5), /* \` */
+ ESC_DOLLAR = (1 << 6), /* \$ */
+ ESC_CLBRACKET = (1 << 7), /* \] for RFC5424 syslog */
+ ESC_OTHER = (1 << 8), /* remaining non-alpha */
+
+ ESC_ALL = ESC_N_R_T | ESC_SPACE | ESC_BACKSLASH | ESC_DBLQUOTE
+ | ESC_SGLQUOTE | ESC_DOLLAR | ESC_OTHER,
+ ESC_QUOTSTRING = ESC_N_R_T | ESC_BACKSLASH | ESC_DBLQUOTE,
+ /* if needed: ESC_SHELL = ... */
+};
+
+static ssize_t bquote(struct fbuf *buf, const uint8_t *pos, size_t len,
+ unsigned int flags)
+{
+ ssize_t ret = 0;
+ const uint8_t *end = pos + len;
+
+ for (; pos < end; pos++) {
+ /* here's to hoping this might be a bit faster... */
+ if (__builtin_expect(!!isalnum(*pos), 1)) {
+ ret += bputch(buf, *pos);
+ continue;
+ }
+
+ switch (*pos) {
+ case '%':
+ case '+':
+ case ',':
+ case '-':
+ case '.':
+ case '/':
+ case ':':
+ case '@':
+ case '_':
+ ret += bputch(buf, *pos);
+ continue;
+
+ case '\r':
+ if (!(flags & ESC_N_R_T))
+ break;
+ ret += bputch(buf, '\\');
+ ret += bputch(buf, 'r');
+ continue;
+ case '\n':
+ if (!(flags & ESC_N_R_T))
+ break;
+ ret += bputch(buf, '\\');
+ ret += bputch(buf, 'n');
+ continue;
+ case '\t':
+ if (!(flags & ESC_N_R_T))
+ break;
+ ret += bputch(buf, '\\');
+ ret += bputch(buf, 't');
+ continue;
+
+ case ' ':
+ if (flags & ESC_SPACE)
+ ret += bputch(buf, '\\');
+ ret += bputch(buf, *pos);
+ continue;
+
+ case '\\':
+ if (flags & ESC_BACKSLASH)
+ ret += bputch(buf, '\\');
+ ret += bputch(buf, *pos);
+ continue;
+
+ case '"':
+ if (flags & ESC_DBLQUOTE)
+ ret += bputch(buf, '\\');
+ ret += bputch(buf, *pos);
+ continue;
+
+ case '\'':
+ if (flags & ESC_SGLQUOTE)
+ ret += bputch(buf, '\\');
+ ret += bputch(buf, *pos);
+ continue;
+
+ case '`':
+ if (flags & ESC_BACKTICK)
+ ret += bputch(buf, '\\');
+ ret += bputch(buf, *pos);
+ continue;
+
+ case '$':
+ if (flags & ESC_DOLLAR)
+ ret += bputch(buf, '\\');
+ ret += bputch(buf, *pos);
+ continue;
+
+ case ']':
+ if (flags & ESC_CLBRACKET)
+ ret += bputch(buf, '\\');
+ ret += bputch(buf, *pos);
+ continue;
+
+ /* remaining: !#&'()*;<=>?[^{|}~ */
+
+ default:
+ if (*pos >= 0x20 && *pos < 0x7f) {
+ if (flags & ESC_OTHER)
+ ret += bputch(buf, '\\');
+ ret += bputch(buf, *pos);
+ continue;
+ }
+ }
+ ret += bputch(buf, '\\');
+ ret += bputch(buf, 'x');
+ ret += bputhex(buf, *pos);
+ }
+
+ return ret;
+}
+
+printfrr_ext_autoreg_p("SE", printfrr_escape);
+static ssize_t printfrr_escape(struct fbuf *buf, struct printfrr_eargs *ea,
+ const void *vptr)
+{
+ ssize_t len = printfrr_ext_len(ea);
+ const uint8_t *ptr = vptr;
+ bool null_is_empty = false;
+
+ if (ea->fmt[0] == 'n') {
+ null_is_empty = true;
+ ea->fmt++;
+ }
+
+ if (!ptr) {
+ if (null_is_empty)
+ return 0;
+ return bputs(buf, "(null)");
+ }
+
+ if (len < 0)
+ len = strlen((const char *)ptr);
+
+ return bquote(buf, ptr, len, ESC_ALL);
+}
+
+printfrr_ext_autoreg_p("SQ", printfrr_quote);
+static ssize_t printfrr_quote(struct fbuf *buf, struct printfrr_eargs *ea,
+ const void *vptr)
+{
+ ssize_t len = printfrr_ext_len(ea);
+ const uint8_t *ptr = vptr;
+ ssize_t ret = 0;
+ bool null_is_empty = false;
+ bool do_quotes = false;
+ unsigned int flags = ESC_QUOTSTRING;
+
+ while (ea->fmt[0]) {
+ switch (ea->fmt[0]) {
+ case 'n':
+ null_is_empty = true;
+ ea->fmt++;
+ continue;
+ case 'q':
+ do_quotes = true;
+ ea->fmt++;
+ continue;
+ case 's':
+ flags |= ESC_CLBRACKET;
+ flags &= ~ESC_N_R_T;
+ ea->fmt++;
+ continue;
+ }
+ break;
+ }
+
+ if (!ptr) {
+ if (null_is_empty)
+ return bputs(buf, do_quotes ? "\"\"" : "");
+ return bputs(buf, "(null)");
+ }
+
+ if (len < 0)
+ len = strlen((const char *)ptr);
+
+ if (do_quotes)
+ ret += bputch(buf, '"');
+ ret += bquote(buf, ptr, len, flags);
+ if (do_quotes)
+ ret += bputch(buf, '"');
+ return ret;
+}
+
+static ssize_t printfrr_abstime(struct fbuf *buf, struct printfrr_eargs *ea,
+ const struct timespec *ts, unsigned int flags);
+static ssize_t printfrr_reltime(struct fbuf *buf, struct printfrr_eargs *ea,
+ const struct timespec *ts, unsigned int flags);
+
+ssize_t printfrr_time(struct fbuf *buf, struct printfrr_eargs *ea,
+ const struct timespec *ts, unsigned int flags)
+{
+ bool have_abs, have_anchor;
+
+ if (!(flags & TIMEFMT_PRESELECT)) {
+ switch (ea->fmt[0]) {
+ case 'I':
+ /* no bit set */
+ break;
+ case 'M':
+ flags |= TIMEFMT_MONOTONIC;
+ break;
+ case 'R':
+ flags |= TIMEFMT_REALTIME;
+ break;
+ default:
+ return bputs(buf,
+ "{invalid time format input specifier}");
+ }
+ ea->fmt++;
+
+ if (ea->fmt[0] == 's') {
+ flags |= TIMEFMT_SINCE;
+ ea->fmt++;
+ } else if (ea->fmt[0] == 'u') {
+ flags |= TIMEFMT_UNTIL;
+ ea->fmt++;
+ }
+ }
+
+ have_abs = !!(flags & TIMEFMT_ABSOLUTE);
+ have_anchor = !!(flags & TIMEFMT_ANCHORS);
+
+ if (have_abs ^ have_anchor)
+ return printfrr_abstime(buf, ea, ts, flags);
+ else
+ return printfrr_reltime(buf, ea, ts, flags);
+}
+
+static ssize_t do_subsec(struct fbuf *buf, const struct timespec *ts,
+ int precision, unsigned int flags)
+{
+ unsigned long long frac;
+
+ if (precision <= 0 || (flags & TIMEFMT_SECONDS))
+ return 0;
+
+ frac = ts->tv_nsec;
+ if (precision > 9)
+ precision = 9;
+ for (int i = precision; i < 9; i++)
+ frac /= 10;
+ return bprintfrr(buf, ".%0*llu", precision, frac);
+}
+
+static ssize_t printfrr_abstime(struct fbuf *buf, struct printfrr_eargs *ea,
+ const struct timespec *ts, unsigned int flags)
+{
+ struct timespec real_ts[1];
+ struct tm tm;
+ char cbuf[32] = ""; /* manpage says 26 for ctime_r */
+ ssize_t ret = 0;
+ int precision = ea->precision;
+
+ while (ea->fmt[0]) {
+ char ch = *ea->fmt++;
+
+ switch (ch) {
+ case 'p':
+ flags |= TIMEFMT_SPACE;
+ continue;
+ case 'i':
+ flags |= TIMEFMT_ISO8601;
+ continue;
+ }
+
+ ea->fmt--;
+ break;
+ }
+
+ if (flags & TIMEFMT_SKIP)
+ return 0;
+ if (!ts)
+ return bputch(buf, '-');
+
+ if (flags & TIMEFMT_REALTIME)
+ *real_ts = *ts;
+ else if (flags & TIMEFMT_MONOTONIC) {
+ struct timespec mono_now[1];
+
+ clock_gettime(CLOCK_REALTIME, real_ts);
+ clock_gettime(CLOCK_MONOTONIC, mono_now);
+
+ timespecsub(real_ts, mono_now, real_ts);
+ timespecadd(real_ts, ts, real_ts);
+ } else {
+ clock_gettime(CLOCK_REALTIME, real_ts);
+
+ if (flags & TIMEFMT_SINCE)
+ timespecsub(real_ts, ts, real_ts);
+ else /* flags & TIMEFMT_UNTIL */
+ timespecadd(real_ts, ts, real_ts);
+ }
+
+ localtime_r(&real_ts->tv_sec, &tm);
+
+ if (flags & TIMEFMT_ISO8601) {
+ if (flags & TIMEFMT_SPACE)
+ strftime(cbuf, sizeof(cbuf), "%Y-%m-%d %H:%M:%S", &tm);
+ else
+ strftime(cbuf, sizeof(cbuf), "%Y-%m-%dT%H:%M:%S", &tm);
+ ret += bputs(buf, cbuf);
+
+ if (precision == -1)
+ precision = 3;
+ ret += do_subsec(buf, real_ts, precision, flags);
+ } else {
+ size_t len;
+
+ asctime_r(&tm, cbuf);
+
+ len = strlen(cbuf);
+ if (!len)
+ /* WTF. */
+ return 0;
+ if (cbuf[len - 1] == '\n')
+ cbuf[len - 1] = '\0';
+
+ ret += bputs(buf, cbuf);
+ }
+ return ret;
+}
+
+static ssize_t printfrr_reltime(struct fbuf *buf, struct printfrr_eargs *ea,
+ const struct timespec *ts, unsigned int flags)
+{
+ struct timespec real_ts[1];
+ ssize_t ret = 0;
+ const char *space = "";
+ const char *dashes = "-";
+ int precision = ea->precision;
+
+ while (ea->fmt[0]) {
+ char ch = *ea->fmt++;
+
+ switch (ch) {
+ case 'p':
+ flags |= TIMEFMT_SPACE;
+ space = " ";
+ continue;
+ case 't':
+ flags |= TIMEFMT_BASIC;
+ continue;
+ case 'd':
+ flags |= TIMEFMT_DECIMAL;
+ continue;
+ case 'm':
+ flags |= TIMEFMT_MMSS;
+ dashes = "--:--";
+ continue;
+ case 'h':
+ flags |= TIMEFMT_HHMMSS;
+ dashes = "--:--:--";
+ continue;
+ case 'x':
+ flags |= TIMEFMT_DASHES;
+ continue;
+ }
+
+ ea->fmt--;
+ break;
+ }
+
+ if (flags & TIMEFMT_SKIP)
+ return 0;
+ if (!ts)
+ return bputch(buf, '-');
+
+ if (flags & TIMEFMT_ABSOLUTE) {
+ struct timespec anchor[1];
+
+ if (flags & TIMEFMT_REALTIME)
+ clock_gettime(CLOCK_REALTIME, anchor);
+ else
+ clock_gettime(CLOCK_MONOTONIC, anchor);
+ if (flags & TIMEFMT_UNTIL)
+ timespecsub(ts, anchor, real_ts);
+ else /* flags & TIMEFMT_SINCE */
+ timespecsub(anchor, ts, real_ts);
+ } else
+ *real_ts = *ts;
+
+ if (real_ts->tv_sec == 0 && real_ts->tv_nsec == 0 &&
+ (flags & TIMEFMT_DASHES))
+ return bputs(buf, dashes);
+
+ if (real_ts->tv_sec < 0) {
+ if (flags & TIMEFMT_DASHES)
+ return bputs(buf, dashes);
+
+ /* -0.3s is { -1s + 700ms } */
+ real_ts->tv_sec = -real_ts->tv_sec - 1;
+ real_ts->tv_nsec = 1000000000L - real_ts->tv_nsec;
+ if (real_ts->tv_nsec >= 1000000000L) {
+ real_ts->tv_sec++;
+ real_ts->tv_nsec -= 1000000000L;
+ }
+
+ /* all formats have a - make sense in front */
+ ret += bputch(buf, '-');
+ }
+
+ if (flags & TIMEFMT_DECIMAL) {
+ ret += bprintfrr(buf, "%lld", (long long)real_ts->tv_sec);
+ if (precision == -1)
+ precision = 3;
+ ret += do_subsec(buf, real_ts, precision, flags);
+ return ret;
+ }
+
+ /* these divisions may be slow on embedded boxes, hence only do the
+ * ones we need, plus the ?: zero check to hopefully skip zeros fast
+ */
+ lldiv_t min_sec = lldiv(real_ts->tv_sec, 60);
+
+ if (flags & TIMEFMT_MMSS) {
+ ret += bprintfrr(buf, "%02lld:%02lld", min_sec.quot,
+ min_sec.rem);
+ ret += do_subsec(buf, real_ts, precision, flags);
+ return ret;
+ }
+
+ lldiv_t hour_min = min_sec.quot ? lldiv(min_sec.quot, 60) : (lldiv_t){};
+
+ if (flags & TIMEFMT_HHMMSS) {
+ ret += bprintfrr(buf, "%02lld:%02lld:%02lld", hour_min.quot,
+ hour_min.rem, min_sec.rem);
+ ret += do_subsec(buf, real_ts, precision, flags);
+ return ret;
+ }
+
+ lldiv_t day_hour =
+ hour_min.quot ? lldiv(hour_min.quot, 24) : (lldiv_t){};
+ lldiv_t week_day =
+ day_hour.quot ? lldiv(day_hour.quot, 7) : (lldiv_t){};
+
+ /* if sub-second precision is not supported, return */
+ if (flags & TIMEFMT_BASIC) {
+ /* match frrtime_to_interval (without space flag) */
+ if (week_day.quot)
+ ret += bprintfrr(buf, "%lldw%s%lldd%s%02lldh",
+ week_day.quot, space, week_day.rem,
+ space, day_hour.rem);
+ else if (day_hour.quot)
+ ret += bprintfrr(buf, "%lldd%s%02lldh%s%02lldm",
+ day_hour.quot, space, day_hour.rem,
+ space, hour_min.rem);
+ else
+ ret += bprintfrr(buf, "%02lld:%02lld:%02lld",
+ hour_min.quot, hour_min.rem,
+ min_sec.rem);
+ /* no sub-seconds here */
+ return ret;
+ }
+
+ /* default format */
+ if (week_day.quot)
+ ret += bprintfrr(buf, "%lldw%s", week_day.quot, space);
+ if (week_day.rem || week_day.quot)
+ ret += bprintfrr(buf, "%lldd%s", week_day.rem, space);
+
+ ret += bprintfrr(buf, "%02lld:%02lld:%02lld", day_hour.rem,
+ hour_min.rem, min_sec.rem);
+
+ if (precision == -1)
+ precision = 3;
+ ret += do_subsec(buf, real_ts, precision, flags);
+ return ret;
+}
+
+printfrr_ext_autoreg_p("TS", printfrr_ts);
+static ssize_t printfrr_ts(struct fbuf *buf, struct printfrr_eargs *ea,
+ const void *vptr)
+{
+ const struct timespec *ts = vptr;
+
+ return printfrr_time(buf, ea, ts, 0);
+}
+
+printfrr_ext_autoreg_p("TV", printfrr_tv);
+static ssize_t printfrr_tv(struct fbuf *buf, struct printfrr_eargs *ea,
+ const void *vptr)
+{
+ const struct timeval *tv = vptr;
+ struct timespec ts;
+
+ if (!tv)
+ return printfrr_time(buf, ea, NULL, 0);
+
+ ts.tv_sec = tv->tv_sec;
+ ts.tv_nsec = tv->tv_usec * 1000;
+ return printfrr_time(buf, ea, &ts, 0);
+}
+
+printfrr_ext_autoreg_p("TT", printfrr_tt);
+static ssize_t printfrr_tt(struct fbuf *buf, struct printfrr_eargs *ea,
+ const void *vptr)
+{
+ const time_t *tt = vptr;
+ struct timespec ts;
+
+ if (!tt)
+ return printfrr_time(buf, ea, NULL, TIMEFMT_SECONDS);
+
+ ts.tv_sec = *tt;
+ ts.tv_nsec = 0;
+ return printfrr_time(buf, ea, &ts, TIMEFMT_SECONDS);
+}
diff --git a/lib/strlcat.c b/lib/strlcat.c
new file mode 100644
index 0000000..a046822
--- /dev/null
+++ b/lib/strlcat.c
@@ -0,0 +1,71 @@
+/* Append a null-terminated string to another string, with length checking.
+ * Copyright (C) 2016 Free Software Foundation, Inc.
+ * This file is part of the GNU C Library.
+ *
+ * The GNU C Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * The GNU C Library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the GNU C Library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+/* adapted for Quagga from glibc patch submission originally from
+ * Florian Weimer <fweimer@redhat.com>, 2016-05-18 */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdint.h>
+#include <string.h>
+
+#ifndef HAVE_STRLCAT
+#undef strlcat
+
+size_t strlcat(char *__restrict dest,
+ const char *__restrict src, size_t destsize);
+
+size_t strlcat(char *__restrict dest,
+ const char *__restrict src, size_t destsize)
+{
+ size_t src_length = strlen(src);
+
+ /* Our implementation strlcat supports dest == NULL if size == 0
+ (for consistency with snprintf and strlcpy), but strnlen does
+ not, so we have to cover this case explicitly. */
+ if (destsize == 0)
+ return src_length;
+
+ size_t dest_length = strnlen(dest, destsize);
+ if (dest_length != destsize) {
+ /* Copy at most the remaining number of characters in the
+ destination buffer. Leave for the NUL terminator. */
+ size_t to_copy = destsize - dest_length - 1;
+ /* But not more than what is available in the source string. */
+ if (to_copy > src_length)
+ to_copy = src_length;
+
+ char *target = dest + dest_length;
+ memcpy(target, src, to_copy);
+ target[to_copy] = '\0';
+ }
+
+/* If the sum wraps around, we have more than SIZE_MAX + 2 bytes in
+ the two input strings (including both null terminators). If each
+ byte in the address space can be assigned a unique size_t value
+ (which the static_assert checks), then by the pigeonhole
+ principle, the two input strings must overlap, which is
+ undefined. */
+ _Static_assert(sizeof(uintptr_t) == sizeof(size_t),
+ "theoretical maximum object size covers address space");
+ return dest_length + src_length;
+}
+#endif /* HAVE_STRLCAT */
diff --git a/lib/strlcpy.c b/lib/strlcpy.c
new file mode 100644
index 0000000..71ee9f1
--- /dev/null
+++ b/lib/strlcpy.c
@@ -0,0 +1,58 @@
+/* Copy a null-terminated string to a fixed-size buffer, with length checking.
+ * Copyright (C) 2016 Free Software Foundation, Inc.
+ * This file is part of the GNU C Library.
+ *
+ * The GNU C Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * The GNU C Library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the GNU C Library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+/* adapted for Quagga from glibc patch submission originally from
+ * Florian Weimer <fweimer@redhat.com>, 2016-05-18 */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+
+#ifndef HAVE_STRLCPY
+#undef strlcpy
+
+size_t strlcpy(char *__restrict dest,
+ const char *__restrict src, size_t destsize);
+
+size_t strlcpy(char *__restrict dest,
+ const char *__restrict src, size_t destsize)
+{
+ size_t src_length = strlen(src);
+
+ if (__builtin_expect(src_length >= destsize, 0)) {
+ if (destsize > 0) {
+ /*
+ * Copy the leading portion of the string. The last
+ * character is subsequently overwritten with the NUL
+ * terminator, but the destination destsize is usually
+ * a multiple of a small power of two, so writing it
+ * twice should be more efficient than copying an odd
+ * number of bytes.
+ */
+ memcpy(dest, src, destsize);
+ dest[destsize - 1] = '\0';
+ }
+ } else
+ /* Copy the string and its terminating NUL character. */
+ memcpy(dest, src, src_length + 1);
+ return src_length;
+}
+#endif /* HAVE_STRLCPY */
diff --git a/lib/subdir.am b/lib/subdir.am
new file mode 100644
index 0000000..d6defd7
--- /dev/null
+++ b/lib/subdir.am
@@ -0,0 +1,608 @@
+#
+# libfrr
+#
+
+lib_LTLIBRARIES += lib/libfrr.la
+lib_libfrr_la_LDFLAGS = $(LIB_LDFLAGS) -version-info 0:0:0 -Xlinker -e_libfrr_version
+lib_libfrr_la_LIBADD = $(LIBCAP) $(UNWIND_LIBS) $(LIBYANG_LIBS) $(LUA_LIB) $(UST_LIBS) $(LIBCRYPT) $(LIBDL) $(LIBM)
+
+lib_libfrr_la_SOURCES = \
+ lib/agg_table.c \
+ lib/atomlist.c \
+ lib/base64.c \
+ lib/bfd.c \
+ lib/buffer.c \
+ lib/checksum.c \
+ lib/command.c \
+ lib/command_graph.c \
+ lib/command_lex.l \
+ lib/command_match.c \
+ lib/command_parse.y \
+ lib/cspf.c \
+ lib/csv.c \
+ lib/debug.c \
+ lib/defaults.c \
+ lib/distribute.c \
+ lib/explicit_bzero.c \
+ lib/ferr.c \
+ lib/filter.c \
+ lib/filter_cli.c \
+ lib/filter_nb.c \
+ lib/frrcu.c \
+ lib/frrlua.c \
+ lib/frrscript.c \
+ lib/frr_pthread.c \
+ lib/frrstr.c \
+ lib/getopt.c \
+ lib/getopt1.c \
+ lib/grammar_sandbox.c \
+ lib/graph.c \
+ lib/hash.c \
+ lib/hook.c \
+ lib/id_alloc.c \
+ lib/if.c \
+ lib/if_rmap.c \
+ lib/imsg-buffer.c \
+ lib/imsg.c \
+ lib/jhash.c \
+ lib/json.c \
+ lib/keychain.c \
+ lib/ldp_sync.c \
+ lib/lib_errors.c \
+ lib/lib_vty.c \
+ lib/libfrr.c \
+ lib/libfrr_trace.c \
+ lib/linklist.c \
+ lib/link_state.c \
+ lib/log.c \
+ lib/log_filter.c \
+ lib/log_vty.c \
+ lib/md5.c \
+ lib/memory.c \
+ lib/mlag.c \
+ lib/module.c \
+ lib/mpls.c \
+ lib/srv6.c \
+ lib/network.c \
+ lib/nexthop.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/ntop.c \
+ lib/openbsd-tree.c \
+ lib/pid_output.c \
+ lib/plist.c \
+ lib/prefix.c \
+ lib/privs.c \
+ lib/ptm_lib.c \
+ lib/pullwr.c \
+ lib/qobj.c \
+ lib/ringbuf.c \
+ lib/routemap.c \
+ lib/routemap_cli.c \
+ lib/routemap_northbound.c \
+ lib/sbuf.c \
+ lib/seqlock.c \
+ lib/sha256.c \
+ lib/sigevent.c \
+ lib/skiplist.c \
+ lib/sockopt.c \
+ lib/sockunion.c \
+ lib/spf_backoff.c \
+ lib/srcdest_table.c \
+ lib/stream.c \
+ lib/strformat.c \
+ lib/strlcat.c \
+ lib/strlcpy.c \
+ lib/systemd.c \
+ lib/table.c \
+ lib/termtable.c \
+ lib/thread.c \
+ lib/typerb.c \
+ lib/typesafe.c \
+ lib/vector.c \
+ lib/vrf.c \
+ lib/vty.c \
+ lib/wheel.c \
+ lib/workqueue.c \
+ lib/xref.c \
+ lib/yang.c \
+ lib/yang_translator.c \
+ lib/yang_wrappers.c \
+ lib/zclient.c \
+ lib/zlog.c \
+ lib/zlog_5424.c \
+ lib/zlog_5424_cli.c \
+ lib/zlog_live.c \
+ lib/zlog_targets.c \
+ lib/printf/printf-pos.c \
+ lib/printf/vfprintf.c \
+ lib/printf/glue.c \
+ lib/routing_nb.c \
+ lib/routing_nb_config.c \
+ # end
+
+nodist_lib_libfrr_la_SOURCES = \
+ yang/frr-filter.yang.c \
+ yang/frr-interface.yang.c \
+ yang/frr-route-map.yang.c \
+ yang/frr-route-types.yang.c \
+ yang/frr-vrf.yang.c \
+ yang/frr-routing.yang.c \
+ yang/frr-nexthop.yang.c \
+ yang/ietf/ietf-routing-types.yang.c \
+ yang/ietf/ietf-interfaces.yang.c \
+ yang/ietf/ietf-bgp-types.yang.c \
+ yang/frr-module-translator.yang.c \
+ # end
+
+vtysh_scan += \
+ lib/distribute.c \
+ lib/filter.c \
+ lib/filter_cli.c \
+ lib/if.c \
+ lib/if_rmap.c \
+ lib/keychain.c \
+ lib/lib_vty.c \
+ lib/log_vty.c \
+ lib/nexthop_group.c \
+ lib/plist.c \
+ lib/routemap.c \
+ lib/routemap_cli.c \
+ lib/spf_backoff.c \
+ lib/thread.c \
+ lib/vrf.c \
+ lib/vty.c \
+ # end
+# can be loaded as DSO - always include for vtysh
+vtysh_scan += lib/agentx.c
+
+if SQLITE3
+lib_libfrr_la_LIBADD += $(SQLITE3_LIBS)
+lib_libfrr_la_SOURCES += lib/db.c
+endif
+
+clippy_scan += \
+ lib/if.c \
+ lib/filter_cli.c \
+ lib/log_vty.c \
+ lib/nexthop_group.c \
+ lib/northbound_cli.c \
+ lib/plist.c \
+ lib/routemap_cli.c \
+ lib/thread.c \
+ lib/vty.c \
+ lib/zlog_5424_cli.c \
+ # end
+
+pkginclude_HEADERS += \
+ lib/agg_table.h \
+ lib/atomlist.h \
+ lib/base64.h \
+ lib/bfd.h \
+ lib/bitfield.h \
+ lib/buffer.h \
+ lib/checksum.h \
+ lib/mlag.h \
+ lib/command.h \
+ lib/command_graph.h \
+ lib/command_match.h \
+ lib/compiler.h \
+ lib/cspf.h \
+ lib/csv.h \
+ lib/db.h \
+ lib/debug.h \
+ lib/defaults.h \
+ lib/distribute.h \
+ lib/ferr.h \
+ lib/filter.h \
+ lib/freebsd-queue.h \
+ lib/frrlua.h \
+ lib/frrscript.h \
+ lib/frr_pthread.h \
+ lib/frratomic.h \
+ lib/frrcu.h \
+ lib/frrstr.h \
+ lib/getopt.h \
+ lib/graph.h \
+ lib/hash.h \
+ lib/hook.h \
+ lib/iana_afi.h \
+ lib/id_alloc.h \
+ lib/if.h \
+ lib/if_rmap.h \
+ lib/imsg.h \
+ lib/ipaddr.h \
+ lib/jhash.h \
+ lib/json.h \
+ lib/keychain.h \
+ lib/ldp_sync.h \
+ lib/lib_errors.h \
+ lib/lib_vty.h \
+ lib/libfrr.h \
+ lib/libfrr_trace.h \
+ lib/libospf.h \
+ lib/linklist.h \
+ lib/link_state.h \
+ lib/log.h \
+ lib/log_vty.h \
+ lib/md5.h \
+ lib/memory.h \
+ lib/module.h \
+ lib/monotime.h \
+ lib/mpls.h \
+ lib/srv6.h \
+ lib/network.h \
+ lib/nexthop.h \
+ lib/nexthop_group.h \
+ lib/nexthop_group_private.h \
+ lib/northbound.h \
+ lib/northbound_cli.h \
+ lib/northbound_db.h \
+ lib/ns.h \
+ lib/openbsd-queue.h \
+ lib/openbsd-tree.h \
+ lib/plist.h \
+ lib/prefix.h \
+ lib/printfrr.h \
+ lib/privs.h \
+ lib/ptm_lib.h \
+ lib/pullwr.h \
+ lib/pw.h \
+ lib/qobj.h \
+ lib/queue.h \
+ lib/ringbuf.h \
+ lib/routemap.h \
+ lib/route_opaque.h \
+ lib/sbuf.h \
+ lib/seqlock.h \
+ lib/sha256.h \
+ lib/sigevent.h \
+ lib/skiplist.h \
+ lib/smux.h \
+ lib/sockopt.h \
+ lib/sockunion.h \
+ lib/spf_backoff.h \
+ lib/srcdest_table.h \
+ lib/srte.h \
+ lib/stream.h \
+ lib/systemd.h \
+ lib/table.h \
+ lib/termtable.h \
+ lib/thread.h \
+ lib/trace.h \
+ lib/typerb.h \
+ lib/typesafe.h \
+ lib/vector.h \
+ lib/vlan.h \
+ lib/vrf.h \
+ lib/vrf_int.h \
+ lib/vty.h \
+ lib/vxlan.h \
+ lib/wheel.h \
+ lib/workqueue.h \
+ lib/xref.h \
+ lib/yang.h \
+ lib/yang_translator.h \
+ lib/yang_wrappers.h \
+ lib/zclient.h \
+ lib/zebra.h \
+ lib/zlog.h \
+ lib/zlog_5424.h \
+ lib/zlog_live.h \
+ lib/zlog_targets.h \
+ lib/pbr.h \
+ lib/routing_nb.h \
+ \
+ lib/assert/assert.h \
+ # end
+
+
+nodist_pkginclude_HEADERS += \
+ lib/route_types.h \
+ lib/version.h \
+ # end
+
+noinst_HEADERS += \
+ lib/clippy.h \
+ lib/plist_int.h \
+ lib/printf/printfcommon.h \
+ lib/printf/printflocal.h \
+ #end
+
+# General note about module and module helper library (libfrrsnmp, libfrrzmq)
+# linking: If we're linking libfrr statically into daemons, we *must* remove
+# libfrr from modules because modules will always link it in dynamically and
+# thus 2 copies of libfrr will be loaded... hilarity ensues.
+#
+# Not linking libfrr into modules should generally work fine because the
+# executable refers to libfrr either way and the dynamic linker should make
+# libfrr available to modules. If some OS platform has a dynamic linker that
+# doesn't do that, libfrr needs to be readded to modules, but _only_ _if_
+# it's not linked into daemons statically.
+
+#
+# SNMP support
+#
+if SNMP
+lib_LTLIBRARIES += lib/libfrrsnmp.la
+endif
+
+lib_libfrrsnmp_la_CFLAGS = $(AM_CFLAGS) $(SNMP_CFLAGS) -std=gnu11
+lib_libfrrsnmp_la_LDFLAGS = $(LIB_LDFLAGS) -version-info 0:0:0
+lib_libfrrsnmp_la_LIBADD = $(SNMP_LIBS)
+lib_libfrrsnmp_la_SOURCES = \
+ lib/agentx.c \
+ lib/snmp.c \
+ # end
+
+
+#
+# c-ares support
+#
+if CARES
+lib_LTLIBRARIES += lib/libfrrcares.la
+pkginclude_HEADERS += lib/resolver.h
+vtysh_scan += lib/resolver.c
+endif
+
+lib_libfrrcares_la_CFLAGS = $(AM_CFLAGS) $(CARES_CFLAGS)
+lib_libfrrcares_la_LDFLAGS = $(LIB_LDFLAGS) -version-info 0:0:0
+lib_libfrrcares_la_LIBADD = $(CARES_LIBS)
+lib_libfrrcares_la_SOURCES = \
+ lib/resolver.c \
+ #end
+
+#
+# ZeroMQ support
+#
+if ZEROMQ
+lib_LTLIBRARIES += lib/libfrrzmq.la
+pkginclude_HEADERS += lib/frr_zmq.h
+endif
+
+lib_libfrrzmq_la_CFLAGS = $(AM_CFLAGS) $(ZEROMQ_CFLAGS)
+lib_libfrrzmq_la_LDFLAGS = $(LIB_LDFLAGS) -version-info 0:0:0
+lib_libfrrzmq_la_LIBADD = $(ZEROMQ_LIBS)
+lib_libfrrzmq_la_SOURCES = \
+ lib/frr_zmq.c \
+ #end
+
+#
+# Tail-f's ConfD support
+#
+if CONFD
+module_LTLIBRARIES += lib/confd.la
+endif
+
+lib_confd_la_CFLAGS = $(AM_CFLAGS) $(CONFD_CFLAGS)
+lib_confd_la_LDFLAGS = $(MODULE_LDFLAGS)
+lib_confd_la_LIBADD = lib/libfrr.la $(CONFD_LIBS)
+lib_confd_la_SOURCES = lib/northbound_confd.c
+
+#
+# Sysrepo support
+#
+if SYSREPO
+module_LTLIBRARIES += lib/sysrepo.la
+endif
+
+lib_sysrepo_la_CFLAGS = $(AM_CFLAGS) $(SYSREPO_CFLAGS)
+lib_sysrepo_la_LDFLAGS = $(MODULE_LDFLAGS)
+lib_sysrepo_la_LIBADD = lib/libfrr.la $(SYSREPO_LIBS)
+lib_sysrepo_la_SOURCES = lib/northbound_sysrepo.c
+
+#
+# gRPC northbound plugin
+#
+if GRPC
+module_LTLIBRARIES += lib/grpc.la
+endif
+
+lib_grpc_la_CXXFLAGS = $(WERROR) $(GRPC_CFLAGS)
+lib_grpc_la_LDFLAGS = $(MODULE_LDFLAGS)
+lib_grpc_la_LIBADD = lib/libfrr.la grpc/libfrrgrpc_pb.la $(GRPC_LIBS)
+lib_grpc_la_SOURCES = lib/northbound_grpc.cpp
+
+#
+# CLI utilities
+#
+noinst_PROGRAMS += \
+ lib/grammar_sandbox \
+ # end
+
+if BUILD_CLIPPY
+noinst_PROGRAMS += lib/clippy
+else
+if HOSTTOOLS_CLIPPY
+$(CLIPPY):
+ @$(MAKE) -C $(top_builddir)/hosttools lib/route_types.h lib/clippy
+endif
+endif
+
+lib_grammar_sandbox_SOURCES = \
+ lib/grammar_sandbox_main.c
+lib_grammar_sandbox_LDADD = \
+ lib/libfrr.la
+
+lib_clippy_CPPFLAGS = $(CPPFLAGS_BASE) -D_GNU_SOURCE -DBUILDING_CLIPPY
+lib_clippy_CFLAGS = $(AC_CFLAGS) $(PYTHON_CFLAGS)
+lib_clippy_LDADD = $(PYTHON_LIBS) $(UST_LIBS) -lelf
+# no $(SAN_FLAGS) here
+lib_clippy_LDFLAGS = -export-dynamic $(AC_LDFLAGS) $(AC_LDFLAGS_EXEC)
+lib_clippy_SOURCES = \
+ lib/jhash.c \
+ lib/clippy.c \
+ lib/command_graph.c \
+ lib/command_lex.l \
+ lib/command_parse.y \
+ lib/command_py.c \
+ lib/defun_lex.l \
+ lib/elf_py.c \
+ lib/graph.c \
+ lib/libfrr_trace.c \
+ lib/memory.c \
+ lib/typesafe.c \
+ lib/vector.c \
+ # end
+
+# (global) clippy rules for all directories
+
+AM_V_CLIPPY = $(am__v_CLIPPY_$(V))
+am__v_CLIPPY_ = $(am__v_CLIPPY_$(AM_DEFAULT_VERBOSITY))
+am__v_CLIPPY_0 = @echo " CLIPPY " $@;
+am__v_CLIPPY_1 =
+
+CLIPPY_DEPS = $(CLIPPY) $(top_srcdir)/python/clidef.py
+
+SUFFIXES += _clippy.c
+.c_clippy.c:
+ $(AM_V_CLIPPY) $(CLIPPY) $(top_srcdir)/python/clidef.py -o $@ $<
+
+# xrelfo, the ELF xref extractor
+
+AM_V_XRELFO = $(am__v_XRELFO_$(V))
+am__v_XRELFO_ = $(am__v_XRELFO_$(AM_DEFAULT_VERBOSITY))
+am__v_XRELFO_0 = @echo " XRELFO " $@;
+am__v_XRELFO_1 =
+
+XRELFO_FLAGS = -Wlog-format -Wlog-args
+
+SUFFIXES += .xref
+%.xref: % $(CLIPPY)
+ $(AM_V_XRELFO) $(CLIPPY) $(top_srcdir)/python/xrelfo.py $(WERROR) $(XRELFO_FLAGS) -o $@ $<
+
+# dependencies added in python/makefile.py
+frr.xref:
+ $(AM_V_XRELFO) $(CLIPPY) $(top_srcdir)/python/xrelfo.py -o $@ $^
+all-am: frr.xref
+
+clean-xref:
+ -rm -rf $(xrefs) frr.xref
+clean-local: clean-xref
+
+## automake's "ylwrap" is a great piece of GNU software... not.
+.l.c:
+ $(AM_V_LEX)$(am__skiplex) $(LEXCOMPILE) $<
+.y.c:
+ $(AM_V_YACC)$(am__skipyacc) $(YACCCOMPILE) $<
+
+#
+# generated sources & extra foo
+#
+EXTRA_DIST += \
+ lib/command_lex.h \
+ lib/command_parse.h \
+ lib/gitversion.pl \
+ lib/route_types.pl \
+ lib/route_types.txt \
+ # end
+
+BUILT_SOURCES += \
+ lib/gitversion.h \
+ lib/route_types.h \
+ # end
+
+## force route_types.h
+$(lib_clippy_OBJECTS): lib/route_types.h
+$(lib_libfrr_la_OBJECTS): lib/route_types.h
+
+AM_YFLAGS = -d -Dapi.prefix=@BISON_OPENBRACE@cmd_yy@BISON_CLOSEBRACE@ @BISON_VERBOSE@
+
+lib/command_lex.h: lib/command_lex.c
+ @if test ! -f $@; then rm -f "lib/command_lex.c"; else :; fi
+ @if test ! -f $@; then $(MAKE) $(AM_MAKEFLAGS) "lib/command_lex.c"; else :; fi
+lib/command_lex.lo: lib/command_parse.h
+lib/command_parse.lo: lib/command_lex.h
+lib/clippy-command_lex.$(OBJEXT): lib/command_parse.h
+lib/clippy-command_parse.$(OBJEXT): lib/command_lex.h
+lib/lib_clippy-command_lex.$(OBJEXT): lib/command_parse.h
+lib/lib_clippy-command_parse.$(OBJEXT): lib/command_lex.h
+
+DISTCLEANFILES += lib/command_lex.h \
+ lib/command_lex.c \
+ lib/command_parse.h \
+ lib/command_parse.c
+
+rt_enabled =
+
+if BABELD
+rt_enabled += --enabled babeld
+endif
+if BFDD
+rt_enabled += --enabled bfdd
+endif
+if BGPD
+rt_enabled += --enabled bgpd
+if ENABLE_BGP_VNC
+rt_enabled += --enabled bgpd-vnc
+endif
+endif
+if EIGRPD
+rt_enabled += --enabled eigrpd
+endif
+if ISISD
+rt_enabled += --enabled isisd
+endif
+if FABRICD
+rt_enabled += --enabled fabricd
+endif
+if LDPD
+rt_enabled += --enabled ldpd
+endif
+if NHRPD
+rt_enabled += --enabled nhrpd
+endif
+if OSPFD
+rt_enabled += --enabled ospfd
+endif
+if OSPF6D
+rt_enabled += --enabled ospf6d
+endif
+if PBRD
+rt_enabled += --enabled pbrd
+endif
+if PIMD
+rt_enabled += --enabled pimd
+endif
+if RIPD
+rt_enabled += --enabled ripd
+endif
+if RIPNGD
+rt_enabled += --enabled ripngd
+endif
+if SHARPD
+rt_enabled += --enabled sharpd
+endif
+if ZEBRA
+rt_enabled += --enabled zebra
+endif
+
+lib/route_types.h: $(top_srcdir)/lib/route_types.txt $(top_srcdir)/lib/route_types.pl
+ $(PERL) $(top_srcdir)/lib/route_types.pl $(rt_enabled) < $(top_srcdir)/lib/route_types.txt > $@
+DISTCLEANFILES += lib/route_types.h
+
+if GIT_VERSION
+# bit of a trick here to always have up-to-date git stamps without triggering
+# unnecessary rebuilds. .PHONY causes the .tmp file to be rebuilt always,
+# but if we use that on gitversion.h it'll ripple through the .c file deps.
+# (even if gitversion.h's file timestamp doesn't change, make will think it
+# did, because of .PHONY...)
+
+PHONY_GITVERSION=lib/gitversion.h.tmp
+.SILENT: lib/gitversion.h lib/gitversion.h.tmp
+GITH=lib/gitversion.h
+lib/gitversion.h.tmp: $(top_srcdir)/.git
+ $(PERL) $(top_srcdir)/lib/gitversion.pl $(top_srcdir) > ${GITH}.tmp
+lib/gitversion.h: lib/gitversion.h.tmp
+ { test -f ${GITH} && diff -s -q ${GITH}.tmp ${GITH}; } || cp ${GITH}.tmp ${GITH}
+
+else
+PHONY_GITVERSION=lib/gitversion.h
+lib/gitversion.h:
+ true
+endif
+.PHONY: $(PHONY_GITVERSION)
diff --git a/lib/systemd.c b/lib/systemd.c
new file mode 100644
index 0000000..1c9a6f1
--- /dev/null
+++ b/lib/systemd.c
@@ -0,0 +1,171 @@
+/* lib/systemd Code
+ * Copyright (C) 2016 Cumulus Networks, Inc.
+ * Donald Sharp
+ *
+ * This file is part of Quagga.
+ *
+ * Quagga 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, or (at your option) any
+ * later version.
+ *
+ * Quagga 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 <sys/un.h>
+
+#include "thread.h"
+#include "systemd.h"
+#include "lib_errors.h"
+
+/* these are cleared from env so they don't "leak" into things we fork(),
+ * particularly for watchfrr starting individual daemons
+ *
+ * watchdog_pid is currently not used since watchfrr starts forking.
+ * (TODO: handle that better, somehow?)
+ */
+static pid_t watchdog_pid = -1;
+static intmax_t watchdog_msec;
+
+/* not used yet, but can trigger auto-switch to journald logging */
+bool sd_stdout_is_journal;
+bool sd_stderr_is_journal;
+
+static char *notify_socket;
+
+/* talk to whatever entity claims to be systemd ;)
+ *
+ * refer to sd_notify docs for messages systemd accepts over this socket.
+ * This function should be functionally equivalent to sd_notify().
+ */
+static void systemd_send_information(const char *info)
+{
+ int sock;
+ struct sockaddr_un sun;
+
+ if (!notify_socket)
+ return;
+
+ sock = socket(AF_UNIX, SOCK_DGRAM, 0);
+ if (sock < 0)
+ return;
+
+ sun.sun_family = AF_UNIX;
+ strlcpy(sun.sun_path, notify_socket, sizeof(sun.sun_path));
+
+ /* linux abstract unix socket namespace */
+ if (sun.sun_path[0] == '@')
+ sun.sun_path[0] = '\0';
+
+ /* nothing we can do if this errors out... */
+ (void)sendto(sock, info, strlen(info), 0, (struct sockaddr *)&sun,
+ sizeof(sun));
+
+ close(sock);
+}
+
+void systemd_send_stopping(void)
+{
+ systemd_send_information("STATUS=");
+ systemd_send_information("STOPPING=1");
+}
+
+static struct thread_master *systemd_master = NULL;
+
+static void systemd_send_watchdog(struct thread *t)
+{
+ systemd_send_information("WATCHDOG=1");
+
+ assert(watchdog_msec > 0);
+ thread_add_timer_msec(systemd_master, systemd_send_watchdog, NULL,
+ watchdog_msec, NULL);
+}
+
+void systemd_send_started(struct thread_master *m)
+{
+ assert(m != NULL);
+
+ systemd_master = m;
+
+ systemd_send_information("READY=1");
+ if (watchdog_msec > 0)
+ systemd_send_watchdog(NULL);
+}
+
+void systemd_send_status(const char *status)
+{
+ char buffer[1024];
+
+ snprintf(buffer, sizeof(buffer), "STATUS=%s", status);
+ systemd_send_information(buffer);
+}
+
+static intmax_t getenv_int(const char *varname, intmax_t dflt)
+{
+ char *val, *err;
+ intmax_t intval;
+
+ val = getenv(varname);
+ if (!val)
+ return dflt;
+
+ intval = strtoimax(val, &err, 0);
+ if (*err || !*val)
+ return dflt;
+ return intval;
+}
+
+void systemd_init_env(void)
+{
+ char *tmp;
+ uintmax_t dev, ino;
+ int len;
+ struct stat st;
+
+ notify_socket = getenv("NOTIFY_SOCKET");
+
+ /* no point in setting up watchdog w/o notify socket */
+ if (notify_socket) {
+ intmax_t watchdog_usec;
+
+ watchdog_pid = getenv_int("WATCHDOG_PID", -1);
+ if (watchdog_pid <= 0)
+ watchdog_pid = -1;
+
+ /* note this is the deadline, hence the divide by 3 */
+ watchdog_usec = getenv_int("WATCHDOG_USEC", 0);
+ if (watchdog_usec >= 3000)
+ watchdog_msec = watchdog_usec / 3000;
+ else {
+ if (watchdog_usec != 0)
+ flog_err(
+ EC_LIB_UNAVAILABLE,
+ "systemd expects a %jd microsecond watchdog timer, but FRR only supports millisecond resolution!",
+ watchdog_usec);
+ watchdog_msec = 0;
+ }
+ }
+
+ tmp = getenv("JOURNAL_STREAM");
+ if (tmp && sscanf(tmp, "%ju:%ju%n", &dev, &ino, &len) == 2
+ && (size_t)len == strlen(tmp)) {
+ if (fstat(1, &st) == 0 && st.st_dev == (dev_t)dev
+ && st.st_ino == (ino_t)ino)
+ sd_stdout_is_journal = true;
+ if (fstat(2, &st) == 0 && st.st_dev == (dev_t)dev
+ && st.st_ino == (ino_t)ino)
+ sd_stderr_is_journal = true;
+ }
+
+ /* these should *not* be passed to any other process we start */
+ unsetenv("WATCHDOG_PID");
+ unsetenv("WATCHDOG_USEC");
+}
diff --git a/lib/systemd.h b/lib/systemd.h
new file mode 100644
index 0000000..b929093
--- /dev/null
+++ b/lib/systemd.h
@@ -0,0 +1,57 @@
+/* lib/systemd Code
+ * Copyright (C) 2016 Cumulus Networks, Inc.
+ * Donald Sharp
+ *
+ * This file is part of Quagga.
+ *
+ * Quagga 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, or (at your option) any
+ * later version.
+ *
+ * Quagga 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
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* fd 1/2 connected to journald? */
+extern bool sd_stdout_is_journal;
+extern bool sd_stderr_is_journal;
+
+/*
+ * Wrapper functions to systemd calls.
+ *
+ * Design point is that if systemd is not being used on this system
+ * then these functions becomes a no-op.
+ */
+void systemd_send_stopping(void);
+
+/*
+ * master - The struct thread_master * to use to schedule ourself
+ * the_process - Should we send watchdog if we are not the requested
+ * process?
+ */
+void systemd_send_started(struct thread_master *master);
+
+/*
+ * status - A status string to send to systemd
+ */
+void systemd_send_status(const char *status);
+
+/*
+ * grab startup state from env vars
+ */
+void systemd_init_env(void);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/lib/table.c b/lib/table.c
new file mode 100644
index 0000000..6c4c6c7
--- /dev/null
+++ b/lib/table.c
@@ -0,0 +1,796 @@
+/*
+ * Routing Table functions.
+ * Copyright (C) 1998 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 FRR_COMPILING_TABLE_C
+
+#include <zebra.h>
+
+#include "prefix.h"
+#include "table.h"
+#include "memory.h"
+#include "sockunion.h"
+#include "libfrr_trace.h"
+
+DEFINE_MTYPE_STATIC(LIB, ROUTE_TABLE, "Route table");
+DEFINE_MTYPE(LIB, ROUTE_NODE, "Route node");
+
+static void route_table_free(struct route_table *);
+
+static int route_table_hash_cmp(const struct route_node *a,
+ const struct route_node *b)
+{
+ return prefix_cmp(&a->p, &b->p);
+}
+
+DECLARE_HASH(rn_hash_node, struct route_node, nodehash, route_table_hash_cmp,
+ prefix_hash_key);
+/*
+ * route_table_init_with_delegate
+ */
+struct route_table *
+route_table_init_with_delegate(route_table_delegate_t *delegate)
+{
+ struct route_table *rt;
+
+ rt = XCALLOC(MTYPE_ROUTE_TABLE, sizeof(struct route_table));
+ rt->delegate = delegate;
+ rn_hash_node_init(&rt->hash);
+ return rt;
+}
+
+void route_table_finish(struct route_table *rt)
+{
+ route_table_free(rt);
+}
+
+/* Allocate new route node. */
+static struct route_node *route_node_new(struct route_table *table)
+{
+ return table->delegate->create_node(table->delegate, table);
+}
+
+/* Allocate new route node with prefix set. */
+static struct route_node *route_node_set(struct route_table *table,
+ const struct prefix *prefix)
+{
+ struct route_node *node;
+
+ node = route_node_new(table);
+
+ prefix_copy(&node->p, prefix);
+ node->table = table;
+
+ rn_hash_node_add(&node->table->hash, node);
+
+ return node;
+}
+
+/* Free route node. */
+static void route_node_free(struct route_table *table, struct route_node *node)
+{
+ if (table->cleanup)
+ table->cleanup(table, node);
+ table->delegate->destroy_node(table->delegate, table, node);
+}
+
+/* Free route table. */
+static void route_table_free(struct route_table *rt)
+{
+ struct route_node *tmp_node;
+ struct route_node *node;
+
+ if (rt == NULL)
+ return;
+
+ node = rt->top;
+
+ /* Bulk deletion of nodes remaining in this table. This function is not
+ called until workers have completed their dependency on this table.
+ A final route_unlock_node() will not be called for these nodes. */
+ while (node) {
+ if (node->l_left) {
+ node = node->l_left;
+ continue;
+ }
+
+ if (node->l_right) {
+ node = node->l_right;
+ continue;
+ }
+
+ tmp_node = node;
+ node = node->parent;
+
+ tmp_node->table->count--;
+ tmp_node->lock =
+ 0; /* to cause assert if unlocked after this */
+ rn_hash_node_del(&rt->hash, tmp_node);
+ route_node_free(rt, tmp_node);
+
+ if (node != NULL) {
+ if (node->l_left == tmp_node)
+ node->l_left = NULL;
+ else
+ node->l_right = NULL;
+ } else {
+ break;
+ }
+ }
+
+ assert(rt->count == 0);
+
+ rn_hash_node_fini(&rt->hash);
+ XFREE(MTYPE_ROUTE_TABLE, rt);
+ return;
+}
+
+/* Utility mask array. */
+static const uint8_t maskbit[] = {0x00, 0x80, 0xc0, 0xe0, 0xf0,
+ 0xf8, 0xfc, 0xfe, 0xff};
+
+/* Common prefix route genaration. */
+static void route_common(const struct prefix *n, const struct prefix *p,
+ struct prefix *new)
+{
+ int i;
+ uint8_t diff;
+ uint8_t mask;
+ const uint8_t *np;
+ const uint8_t *pp;
+ uint8_t *newp;
+
+ if (n->family == AF_FLOWSPEC)
+ return prefix_copy(new, p);
+ np = (const uint8_t *)&n->u.prefix;
+ pp = (const uint8_t *)&p->u.prefix;
+
+ newp = &new->u.prefix;
+
+ for (i = 0; i < p->prefixlen / 8; i++) {
+ if (np[i] == pp[i])
+ newp[i] = np[i];
+ else
+ break;
+ }
+
+ new->prefixlen = i * 8;
+
+ if (new->prefixlen != p->prefixlen) {
+ diff = np[i] ^ pp[i];
+ mask = 0x80;
+ while (new->prefixlen < p->prefixlen && !(mask & diff)) {
+ mask >>= 1;
+ new->prefixlen++;
+ }
+ newp[i] = np[i] & maskbit[new->prefixlen % 8];
+ }
+}
+
+static void set_link(struct route_node *node, struct route_node *new)
+{
+ unsigned int bit = prefix_bit(&new->p.u.prefix, node->p.prefixlen);
+
+ node->link[bit] = new;
+ new->parent = node;
+}
+
+/* Find matched prefix. */
+struct route_node *route_node_match(struct route_table *table,
+ union prefixconstptr pu)
+{
+ const struct prefix *p = pu.p;
+ struct route_node *node;
+ struct route_node *matched;
+
+ matched = NULL;
+ node = table->top;
+
+ /* Walk down tree. If there is matched route then store it to
+ matched. */
+ while (node && node->p.prefixlen <= p->prefixlen
+ && prefix_match(&node->p, p)) {
+ if (node->info)
+ matched = node;
+
+ if (node->p.prefixlen == p->prefixlen)
+ break;
+
+ node = node->link[prefix_bit(&p->u.prefix, node->p.prefixlen)];
+ }
+
+ /* If matched route found, return it. */
+ if (matched)
+ return route_lock_node(matched);
+
+ return NULL;
+}
+
+struct route_node *route_node_match_ipv4(struct route_table *table,
+ const struct in_addr *addr)
+{
+ struct prefix_ipv4 p;
+
+ memset(&p, 0, sizeof(p));
+ p.family = AF_INET;
+ p.prefixlen = IPV4_MAX_BITLEN;
+ p.prefix = *addr;
+
+ return route_node_match(table, (struct prefix *)&p);
+}
+
+struct route_node *route_node_match_ipv6(struct route_table *table,
+ const struct in6_addr *addr)
+{
+ struct prefix_ipv6 p;
+
+ memset(&p, 0, sizeof(p));
+ p.family = AF_INET6;
+ p.prefixlen = IPV6_MAX_BITLEN;
+ p.prefix = *addr;
+
+ return route_node_match(table, &p);
+}
+
+/* Lookup same prefix node. Return NULL when we can't find route. */
+struct route_node *route_node_lookup(struct route_table *table,
+ union prefixconstptr pu)
+{
+ struct route_node rn, *node;
+ prefix_copy(&rn.p, pu.p);
+ apply_mask(&rn.p);
+
+ node = rn_hash_node_find(&table->hash, &rn);
+ return (node && node->info) ? route_lock_node(node) : NULL;
+}
+
+/* Lookup same prefix node. Return NULL when we can't find route. */
+struct route_node *route_node_lookup_maynull(struct route_table *table,
+ union prefixconstptr pu)
+{
+ struct route_node rn, *node;
+ prefix_copy(&rn.p, pu.p);
+ apply_mask(&rn.p);
+
+ node = rn_hash_node_find(&table->hash, &rn);
+ return node ? route_lock_node(node) : NULL;
+}
+
+/* Add node to routing table. */
+struct route_node *route_node_get(struct route_table *table,
+ union prefixconstptr pu)
+{
+ if (frrtrace_enabled(frr_libfrr, route_node_get)) {
+ char buf[PREFIX2STR_BUFFER];
+ prefix2str(pu, buf, sizeof(buf));
+ frrtrace(2, frr_libfrr, route_node_get, table, buf);
+ }
+
+ struct route_node search;
+ struct prefix *p = &search.p;
+
+ prefix_copy(p, pu.p);
+ apply_mask(p);
+
+ struct route_node *new;
+ struct route_node *node;
+ struct route_node *match;
+ uint16_t prefixlen = p->prefixlen;
+ const uint8_t *prefix = &p->u.prefix;
+
+ node = rn_hash_node_find(&table->hash, &search);
+ if (node && node->info)
+ return route_lock_node(node);
+
+ match = NULL;
+ node = table->top;
+ while (node && node->p.prefixlen <= prefixlen
+ && prefix_match(&node->p, p)) {
+ if (node->p.prefixlen == prefixlen)
+ return route_lock_node(node);
+
+ match = node;
+ node = node->link[prefix_bit(prefix, node->p.prefixlen)];
+ }
+
+ if (node == NULL) {
+ new = route_node_set(table, p);
+ if (match)
+ set_link(match, new);
+ else
+ table->top = new;
+ } else {
+ new = route_node_new(table);
+ route_common(&node->p, p, &new->p);
+ new->p.family = p->family;
+ new->table = table;
+ set_link(new, node);
+ rn_hash_node_add(&table->hash, new);
+
+ if (match)
+ set_link(match, new);
+ else
+ table->top = new;
+
+ if (new->p.prefixlen != p->prefixlen) {
+ match = new;
+ new = route_node_set(table, p);
+ set_link(match, new);
+ table->count++;
+ }
+ }
+ table->count++;
+ route_lock_node(new);
+
+ return new;
+}
+
+/* Delete node from the routing table. */
+void route_node_delete(struct route_node *node)
+{
+ struct route_node *child;
+ struct route_node *parent;
+
+ assert(node->lock == 0);
+ assert(node->info == NULL);
+
+ if (node->l_left && node->l_right)
+ return;
+
+ if (node->l_left)
+ child = node->l_left;
+ else
+ child = node->l_right;
+
+ parent = node->parent;
+
+ if (child)
+ child->parent = parent;
+
+ if (parent) {
+ if (parent->l_left == node)
+ parent->l_left = child;
+ else
+ parent->l_right = child;
+ } else
+ node->table->top = child;
+
+ node->table->count--;
+
+ rn_hash_node_del(&node->table->hash, node);
+
+ /* WARNING: FRAGILE CODE!
+ * route_node_free may have the side effect of free'ing the entire
+ * table.
+ * this is permitted only if table->count got decremented to zero above,
+ * because in that case parent will also be NULL, so that we won't try
+ * to
+ * delete a now-stale parent below.
+ *
+ * cf. srcdest_srcnode_destroy() in zebra/zebra_rib.c */
+
+ route_node_free(node->table, node);
+
+ /* If parent node is stub then delete it also. */
+ if (parent && parent->lock == 0)
+ route_node_delete(parent);
+}
+
+/* Get first node and lock it. This function is useful when one wants
+ to lookup all the node exist in the routing table. */
+struct route_node *route_top(struct route_table *table)
+{
+ /* If there is no node in the routing table return NULL. */
+ if (table->top == NULL)
+ return NULL;
+
+ /* Lock the top node and return it. */
+ route_lock_node(table->top);
+ return table->top;
+}
+
+/* Unlock current node and lock next node then return it. */
+struct route_node *route_next(struct route_node *node)
+{
+ struct route_node *next;
+ struct route_node *start;
+
+ /* Node may be deleted from route_unlock_node so we have to preserve
+ next node's pointer. */
+
+ if (node->l_left) {
+ next = node->l_left;
+ route_lock_node(next);
+ route_unlock_node(node);
+ return next;
+ }
+ if (node->l_right) {
+ next = node->l_right;
+ route_lock_node(next);
+ route_unlock_node(node);
+ return next;
+ }
+
+ start = node;
+ while (node->parent) {
+ if (node->parent->l_left == node && node->parent->l_right) {
+ next = node->parent->l_right;
+ route_lock_node(next);
+ route_unlock_node(start);
+ return next;
+ }
+ node = node->parent;
+ }
+ route_unlock_node(start);
+ return NULL;
+}
+
+/* Unlock current node and lock next node until limit. */
+struct route_node *route_next_until(struct route_node *node,
+ const struct route_node *limit)
+{
+ struct route_node *next;
+ struct route_node *start;
+
+ /* Node may be deleted from route_unlock_node so we have to preserve
+ next node's pointer. */
+
+ if (node->l_left) {
+ next = node->l_left;
+ route_lock_node(next);
+ route_unlock_node(node);
+ return next;
+ }
+ if (node->l_right) {
+ next = node->l_right;
+ route_lock_node(next);
+ route_unlock_node(node);
+ return next;
+ }
+
+ start = node;
+ while (node->parent && node != limit) {
+ if (node->parent->l_left == node && node->parent->l_right) {
+ next = node->parent->l_right;
+ route_lock_node(next);
+ route_unlock_node(start);
+ return next;
+ }
+ node = node->parent;
+ }
+ route_unlock_node(start);
+ return NULL;
+}
+
+unsigned long route_table_count(struct route_table *table)
+{
+ return table->count;
+}
+
+/**
+ * route_node_create
+ *
+ * Default function for creating a route node.
+ */
+struct route_node *route_node_create(route_table_delegate_t *delegate,
+ struct route_table *table)
+{
+ struct route_node *node;
+ node = XCALLOC(MTYPE_ROUTE_NODE, sizeof(struct route_node));
+ return node;
+}
+
+/**
+ * route_node_destroy
+ *
+ * Default function for destroying a route node.
+ */
+void route_node_destroy(route_table_delegate_t *delegate,
+ struct route_table *table, struct route_node *node)
+{
+ XFREE(MTYPE_ROUTE_NODE, node);
+}
+
+/*
+ * Default delegate.
+ */
+static route_table_delegate_t default_delegate = {
+ .create_node = route_node_create,
+ .destroy_node = route_node_destroy};
+
+route_table_delegate_t *route_table_get_default_delegate(void)
+{
+ return &default_delegate;
+}
+
+/*
+ * route_table_init
+ */
+struct route_table *route_table_init(void)
+{
+ return route_table_init_with_delegate(&default_delegate);
+}
+
+/**
+ * route_table_prefix_iter_cmp
+ *
+ * Compare two prefixes according to the order in which they appear in
+ * an iteration over a tree.
+ *
+ * @return -1 if p1 occurs before p2 (p1 < p2)
+ * 0 if the prefixes are identical (p1 == p2)
+ * +1 if p1 occurs after p2 (p1 > p2)
+ */
+int route_table_prefix_iter_cmp(const struct prefix *p1,
+ const struct prefix *p2)
+{
+ struct prefix common_space;
+ struct prefix *common = &common_space;
+
+ if (p1->prefixlen <= p2->prefixlen) {
+ if (prefix_match(p1, p2)) {
+
+ /*
+ * p1 contains p2, or is equal to it.
+ */
+ return (p1->prefixlen == p2->prefixlen) ? 0 : -1;
+ }
+ } else {
+
+ /*
+ * Check if p2 contains p1.
+ */
+ if (prefix_match(p2, p1))
+ return 1;
+ }
+
+ route_common(p1, p2, common);
+ assert(common->prefixlen < p1->prefixlen);
+ assert(common->prefixlen < p2->prefixlen);
+
+ /*
+ * Both prefixes are longer than the common prefix.
+ *
+ * We need to check the bit after the common prefixlen to determine
+ * which one comes later.
+ */
+ if (prefix_bit(&p1->u.prefix, common->prefixlen)) {
+
+ /*
+ * We branch to the right to get to p1 from the common prefix.
+ */
+ assert(!prefix_bit(&p2->u.prefix, common->prefixlen));
+ return 1;
+ }
+
+ /*
+ * We branch to the right to get to p2 from the common prefix.
+ */
+ assert(prefix_bit(&p2->u.prefix, common->prefixlen));
+ return -1;
+}
+
+/*
+ * route_get_subtree_next
+ *
+ * Helper function that returns the first node that follows the nodes
+ * in the sub-tree under 'node' in iteration order.
+ */
+static struct route_node *route_get_subtree_next(struct route_node *node)
+{
+ while (node->parent) {
+ if (node->parent->l_left == node && node->parent->l_right)
+ return node->parent->l_right;
+
+ node = node->parent;
+ }
+
+ return NULL;
+}
+
+/**
+ * route_table_get_next_internal
+ *
+ * Helper function to find the node that occurs after the given prefix in
+ * order of iteration.
+ *
+ * @see route_table_get_next
+ */
+static struct route_node *
+route_table_get_next_internal(struct route_table *table,
+ const struct prefix *p)
+{
+ struct route_node *node, *tmp_node;
+ int cmp;
+
+ node = table->top;
+
+ while (node) {
+ int match;
+
+ if (node->p.prefixlen < p->prefixlen)
+ match = prefix_match(&node->p, p);
+ else
+ match = prefix_match(p, &node->p);
+
+ if (match) {
+ if (node->p.prefixlen == p->prefixlen) {
+
+ /*
+ * The prefix p exists in the tree, just return
+ * the next
+ * node.
+ */
+ route_lock_node(node);
+ node = route_next(node);
+ if (node)
+ route_unlock_node(node);
+
+ return (node);
+ }
+
+ if (node->p.prefixlen > p->prefixlen) {
+
+ /*
+ * Node is in the subtree of p, and hence
+ * greater than p.
+ */
+ return node;
+ }
+
+ /*
+ * p is in the sub-tree under node.
+ */
+ tmp_node = node->link[prefix_bit(&p->u.prefix,
+ node->p.prefixlen)];
+
+ if (tmp_node) {
+ node = tmp_node;
+ continue;
+ }
+
+ /*
+ * There are no nodes in the direction where p should
+ * be. If
+ * node has a right child, then it must be greater than
+ * p.
+ */
+ if (node->l_right)
+ return node->l_right;
+
+ /*
+ * No more children to follow, go upwards looking for
+ * the next
+ * node.
+ */
+ return route_get_subtree_next(node);
+ }
+
+ /*
+ * Neither node prefix nor 'p' contains the other.
+ */
+ cmp = route_table_prefix_iter_cmp(&node->p, p);
+ if (cmp > 0) {
+
+ /*
+ * Node follows p in iteration order. Return it.
+ */
+ return node;
+ }
+
+ assert(cmp < 0);
+
+ /*
+ * Node and the subtree under it come before prefix p in
+ * iteration order. Prefix p and its sub-tree are not present in
+ * the tree. Go upwards and find the first node that follows the
+ * subtree. That node will also succeed p.
+ */
+ return route_get_subtree_next(node);
+ }
+
+ return NULL;
+}
+
+/**
+ * route_table_get_next
+ *
+ * Find the node that occurs after the given prefix in order of
+ * iteration.
+ */
+struct route_node *route_table_get_next(struct route_table *table,
+ union prefixconstptr pu)
+{
+ const struct prefix *p = pu.p;
+ struct route_node *node;
+
+ node = route_table_get_next_internal(table, p);
+ if (node) {
+ assert(route_table_prefix_iter_cmp(&node->p, p) > 0);
+ route_lock_node(node);
+ }
+ return node;
+}
+
+/*
+ * route_table_iter_init
+ */
+void route_table_iter_init(route_table_iter_t *iter, struct route_table *table)
+{
+ memset(iter, 0, sizeof(*iter));
+ iter->state = RT_ITER_STATE_INIT;
+ iter->table = table;
+}
+
+/*
+ * route_table_iter_pause
+ *
+ * Pause an iteration over the table. This allows the iteration to be
+ * resumed point after arbitrary additions/deletions from the table.
+ * An iteration can be resumed by just calling route_table_iter_next()
+ * on the iterator.
+ */
+void route_table_iter_pause(route_table_iter_t *iter)
+{
+ switch (iter->state) {
+
+ case RT_ITER_STATE_INIT:
+ case RT_ITER_STATE_PAUSED:
+ case RT_ITER_STATE_DONE:
+ return;
+
+ case RT_ITER_STATE_ITERATING:
+
+ /*
+ * Save the prefix that we are currently at. The next call to
+ * route_table_iter_next() will return the node after this
+ * prefix
+ * in the tree.
+ */
+ prefix_copy(&iter->pause_prefix, &iter->current->p);
+ route_unlock_node(iter->current);
+ iter->current = NULL;
+ iter->state = RT_ITER_STATE_PAUSED;
+ return;
+
+ default:
+ assert(0);
+ }
+}
+
+/*
+ * route_table_iter_cleanup
+ *
+ * Release any resources held by the iterator.
+ */
+void route_table_iter_cleanup(route_table_iter_t *iter)
+{
+ if (iter->state == RT_ITER_STATE_ITERATING) {
+ route_unlock_node(iter->current);
+ iter->current = NULL;
+ }
+ assert(!iter->current);
+
+ /*
+ * Set the state to RT_ITER_STATE_DONE to make any
+ * route_table_iter_next() calls on this iterator return NULL.
+ */
+ iter->state = RT_ITER_STATE_DONE;
+}
diff --git a/lib/table.h b/lib/table.h
new file mode 100644
index 0000000..5dec69e
--- /dev/null
+++ b/lib/table.h
@@ -0,0 +1,343 @@
+/*
+ * Routing Table
+ * Copyright (C) 1998 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 _ZEBRA_TABLE_H
+#define _ZEBRA_TABLE_H
+
+#include "memory.h"
+#include "hash.h"
+#include "prefix.h"
+#include "typesafe.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+DECLARE_MTYPE(ROUTE_NODE);
+
+/*
+ * Forward declarations.
+ */
+struct route_node;
+struct route_table;
+
+/*
+ * route_table_delegate_t
+ *
+ * Function vector that can be used by a client to customize the
+ * behavior of one or more route tables.
+ */
+typedef const struct route_table_delegate_t_ route_table_delegate_t;
+
+typedef struct route_node *(*route_table_create_node_func_t)(
+ route_table_delegate_t *, struct route_table *);
+
+typedef void (*route_table_destroy_node_func_t)(route_table_delegate_t *,
+ struct route_table *,
+ struct route_node *);
+
+struct route_table_delegate_t_ {
+ route_table_create_node_func_t create_node;
+ route_table_destroy_node_func_t destroy_node;
+};
+
+PREDECL_HASH(rn_hash_node);
+
+/* Routing table top structure. */
+struct route_table {
+ struct route_node *top;
+ struct rn_hash_node_head hash;
+
+ /*
+ * Delegate that performs certain functions for this table.
+ */
+ route_table_delegate_t *delegate;
+ void (*cleanup)(struct route_table *, struct route_node *);
+
+ unsigned long count;
+
+ /*
+ * User data.
+ */
+ void *info;
+};
+
+/*
+ * node->link is really internal to the table code and should not be
+ * accessed by outside code. We don't have any writers (yay), though some
+ * readers are left to be fixed.
+ *
+ * rationale: we need to add a hash table in parallel, to speed up
+ * exact-match lookups.
+ *
+ * same really applies for node->parent, though that's less of an issue.
+ * table->link should be - and is - NEVER written by outside code
+ */
+#ifdef FRR_COMPILING_TABLE_C
+#define table_rdonly(x) x
+#define table_internal(x) x
+#else
+#define table_rdonly(x) const x
+#define table_internal(x) \
+ const x __attribute__( \
+ (deprecated("this should only be accessed by lib/table.c")))
+/* table_internal is for node->link and node->lock, once we have done
+ * something about remaining accesses */
+#endif
+
+/* so... the problem with this is that "const" doesn't mean "readonly".
+ * It in fact may allow the compiler to optimize based on the assumption
+ * that the value doesn't change. Hence, since the only purpose of this
+ * is to aid in development, don't put the "const" in release builds.
+ *
+ * (I haven't seen this actually break, but GCC and LLVM are getting ever
+ * more aggressive in optimizing...)
+ */
+#ifndef DEV_BUILD
+#undef table_rdonly
+#define table_rdonly(x) x
+#endif
+
+/*
+ * Macro that defines all fields in a route node.
+ */
+#define ROUTE_NODE_FIELDS \
+ /* Actual prefix of this radix. */ \
+ struct prefix p; \
+ \
+ /* Tree link. */ \
+ struct route_table *table_rdonly(table); \
+ struct route_node *table_rdonly(parent); \
+ struct route_node *table_rdonly(link[2]); \
+ \
+ /* Lock of this radix */ \
+ unsigned int table_rdonly(lock); \
+ \
+ struct rn_hash_node_item nodehash; \
+ /* Each node of route. */ \
+ void *info; \
+
+
+/* Each routing entry. */
+struct route_node {
+ ROUTE_NODE_FIELDS
+
+#define l_left link[0]
+#define l_right link[1]
+};
+
+typedef struct route_table_iter_t_ route_table_iter_t;
+
+typedef enum {
+ RT_ITER_STATE_INIT,
+ RT_ITER_STATE_ITERATING,
+ RT_ITER_STATE_PAUSED,
+ RT_ITER_STATE_DONE
+} route_table_iter_state_t;
+
+/*
+ * route_table_iter_t
+ *
+ * Structure that holds state for iterating over a route table.
+ */
+struct route_table_iter_t_ {
+
+ route_table_iter_state_t state;
+
+ /*
+ * Routing table that we are iterating over. The caller must ensure
+ * that that table outlives the iterator.
+ */
+ struct route_table *table;
+
+ /*
+ * The node that the iterator is currently on.
+ */
+ struct route_node *current;
+
+ /*
+ * The last prefix that the iterator processed before it was paused.
+ */
+ struct prefix pause_prefix;
+};
+
+/* Prototypes. */
+extern struct route_table *route_table_init(void);
+
+extern struct route_table *
+route_table_init_with_delegate(route_table_delegate_t *delegate);
+
+extern route_table_delegate_t *route_table_get_default_delegate(void);
+
+static inline void *route_table_get_info(struct route_table *table)
+{
+ return table->info;
+}
+
+static inline void route_table_set_info(struct route_table *table, void *d)
+{
+ table->info = d;
+}
+
+extern void route_table_finish(struct route_table *table);
+extern struct route_node *route_top(struct route_table *table);
+extern struct route_node *route_next(struct route_node *node);
+extern struct route_node *route_next_until(struct route_node *node,
+ const struct route_node *limit);
+extern struct route_node *route_node_get(struct route_table *table,
+ union prefixconstptr pu);
+extern struct route_node *route_node_lookup(struct route_table *table,
+ union prefixconstptr pu);
+extern struct route_node *route_node_lookup_maynull(struct route_table *table,
+ union prefixconstptr pu);
+extern struct route_node *route_node_match(struct route_table *table,
+ union prefixconstptr pu);
+extern struct route_node *route_node_match_ipv4(struct route_table *table,
+ const struct in_addr *addr);
+extern struct route_node *route_node_match_ipv6(struct route_table *table,
+ const struct in6_addr *addr);
+
+extern unsigned long route_table_count(struct route_table *table);
+
+extern struct route_node *route_node_create(route_table_delegate_t *delegate,
+ struct route_table *table);
+extern void route_node_delete(struct route_node *node);
+extern void route_node_destroy(route_table_delegate_t *delegate,
+ struct route_table *table,
+ struct route_node *node);
+
+extern struct route_node *route_table_get_next(struct route_table *table,
+ union prefixconstptr pu);
+extern int route_table_prefix_iter_cmp(const struct prefix *p1,
+ const struct prefix *p2);
+
+/*
+ * Iterator functions.
+ */
+extern void route_table_iter_init(route_table_iter_t *iter,
+ struct route_table *table);
+extern void route_table_iter_pause(route_table_iter_t *iter);
+extern void route_table_iter_cleanup(route_table_iter_t *iter);
+
+/*
+ * Inline functions.
+ */
+
+/* Lock node. */
+static inline struct route_node *route_lock_node(struct route_node *node)
+{
+ (*(unsigned *)&node->lock)++;
+ return node;
+}
+
+/* Unlock node. */
+static inline void route_unlock_node(struct route_node *node)
+{
+ assert(node->lock > 0);
+ (*(unsigned *)&node->lock)--;
+
+ if (node->lock == 0)
+ route_node_delete(node);
+}
+
+static inline unsigned int route_node_get_lock_count(struct route_node *node)
+{
+ return node->lock;
+}
+
+/*
+ * route_table_iter_next
+ *
+ * Get the next node in the tree.
+ */
+static inline struct route_node *route_table_iter_next(route_table_iter_t *iter)
+{
+ struct route_node *node;
+
+ switch (iter->state) {
+
+ case RT_ITER_STATE_INIT:
+
+ /*
+ * We're just starting the iteration.
+ */
+ node = route_top(iter->table);
+ break;
+
+ case RT_ITER_STATE_ITERATING:
+ node = route_next(iter->current);
+ break;
+
+ case RT_ITER_STATE_PAUSED:
+
+ /*
+ * Start with the node following pause_prefix.
+ */
+ node = route_table_get_next(iter->table, &iter->pause_prefix);
+ break;
+
+ case RT_ITER_STATE_DONE:
+ return NULL;
+
+ default:
+ /* Suppress uninitialized variable warning */
+ node = NULL;
+ assert(0);
+ }
+
+ iter->current = node;
+ if (node)
+ iter->state = RT_ITER_STATE_ITERATING;
+ else
+ iter->state = RT_ITER_STATE_DONE;
+
+ return node;
+}
+
+/*
+ * route_table_iter_is_done
+ *
+ * Returns true if the iteration is complete.
+ */
+static inline int route_table_iter_is_done(route_table_iter_t *iter)
+{
+ return iter->state == RT_ITER_STATE_DONE;
+}
+
+/*
+ * route_table_iter_started
+ *
+ * Returns true if this iterator has started iterating over the tree.
+ */
+static inline int route_table_iter_started(route_table_iter_t *iter)
+{
+ return iter->state != RT_ITER_STATE_INIT;
+}
+
+#ifdef _FRR_ATTRIBUTE_PRINTFRR
+#pragma FRR printfrr_ext "%pRN" (struct route_node *)
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ZEBRA_TABLE_H */
diff --git a/lib/termtable.c b/lib/termtable.c
new file mode 100644
index 0000000..ddf8822
--- /dev/null
+++ b/lib/termtable.c
@@ -0,0 +1,500 @@
+/*
+ * ASCII table generator.
+ * Copyright (C) 2017 Cumulus Networks
+ * Quentin Young
+ *
+ * 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 <stdio.h>
+
+#include "printfrr.h"
+#include "memory.h"
+#include "termtable.h"
+
+DEFINE_MTYPE_STATIC(LIB, TTABLE, "ASCII table");
+
+/* clang-format off */
+const struct ttable_style ttable_styles[] = {
+ { // default ascii
+ .corner = '+',
+ .rownums_on = false,
+ .indent = 1,
+ .border = {
+ .top = '-',
+ .bottom = '-',
+ .left = '|',
+ .right = '|',
+ .top_on = true,
+ .bottom_on = true,
+ .left_on = true,
+ .right_on = true,
+ },
+ .cell = {
+ .lpad = 1,
+ .rpad = 1,
+ .align = LEFT,
+ .border = {
+ .bottom = '-',
+ .bottom_on = true,
+ .top = '-',
+ .top_on = false,
+ .right = '|',
+ .right_on = true,
+ .left = '|',
+ .left_on = false,
+ },
+ },
+ }, { // blank, suitable for plaintext alignment
+ .corner = ' ',
+ .rownums_on = false,
+ .indent = 1,
+ .border = {
+ .top = ' ',
+ .bottom = ' ',
+ .left = ' ',
+ .right = ' ',
+ .top_on = false,
+ .bottom_on = false,
+ .left_on = false,
+ .right_on = false,
+ },
+ .cell = {
+ .lpad = 0,
+ .rpad = 3,
+ .align = LEFT,
+ .border = {
+ .bottom = ' ',
+ .bottom_on = false,
+ .top = ' ',
+ .top_on = false,
+ .right = ' ',
+ .right_on = false,
+ .left = ' ',
+ .left_on = false,
+ },
+ }
+ }
+};
+/* clang-format on */
+
+void ttable_del(struct ttable *tt)
+{
+ for (int i = tt->nrows - 1; i >= 0; i--)
+ ttable_del_row(tt, i);
+
+ XFREE(MTYPE_TTABLE, tt->table);
+ XFREE(MTYPE_TTABLE, tt);
+}
+
+struct ttable *ttable_new(const struct ttable_style *style)
+{
+ struct ttable *tt;
+
+ tt = XCALLOC(MTYPE_TTABLE, sizeof(struct ttable));
+ tt->style = *style;
+ tt->nrows = 0;
+ tt->ncols = 0;
+ tt->size = 0;
+ tt->table = NULL;
+
+ return tt;
+}
+
+/**
+ * Inserts or appends a new row at the specified index.
+ *
+ * If the index is -1, the row is added to the end of the table. Otherwise the
+ * index must be a valid index into tt->table.
+ *
+ * If the table already has at least one row (and therefore a determinate
+ * number of columns), a format string specifying a number of columns not equal
+ * to tt->ncols will result in a no-op and a return value of NULL.
+ *
+ * @param tt table to insert into
+ * @param i insertion index; inserted row will be (i + 1)'th row
+ * @param format printf format string as in ttable_[add|insert]_row()
+ * @param ap pre-initialized variadic list of arguments for format string
+ *
+ * @return pointer to the first cell of allocated row
+ */
+static struct ttable_cell *ttable_insert_row_va(struct ttable *tt, int i,
+ const char *format, va_list ap)
+{
+ assert(i >= -1 && i < tt->nrows);
+
+ char shortbuf[256];
+ char *res, *orig, *section;
+ struct ttable_cell *row;
+ int col = 0;
+ int ncols = 0;
+
+ /* count how many columns we have */
+ for (int j = 0; format[j]; j++)
+ ncols += !!(format[j] == '|');
+ ncols++;
+
+ if (tt->ncols == 0)
+ tt->ncols = ncols;
+ else if (ncols != tt->ncols)
+ return NULL;
+
+ /* reallocate chunk if necessary */
+ while (tt->size < (tt->nrows + 1) * sizeof(struct ttable_cell *)) {
+ tt->size = MAX(2 * tt->size, 2 * sizeof(struct ttable_cell *));
+ tt->table = XREALLOC(MTYPE_TTABLE, tt->table, tt->size);
+ }
+
+ /* CALLOC a block of cells */
+ row = XCALLOC(MTYPE_TTABLE, tt->ncols * sizeof(struct ttable_cell));
+
+ res = vasnprintfrr(MTYPE_TMP, shortbuf, sizeof(shortbuf), format, ap);
+ orig = res;
+
+ while (res && col < tt->ncols) {
+ section = strsep(&res, "|");
+ row[col].text = XSTRDUP(MTYPE_TTABLE, section);
+ row[col].style = tt->style.cell;
+ col++;
+ }
+
+ if (orig != shortbuf)
+ XFREE(MTYPE_TMP, orig);
+
+ /* insert row */
+ if (i == -1 || i == tt->nrows)
+ tt->table[tt->nrows] = row;
+ else {
+ memmove(&tt->table[i + 1], &tt->table[i],
+ (tt->nrows - i) * sizeof(struct ttable_cell *));
+ tt->table[i] = row;
+ }
+
+ tt->nrows++;
+
+ return row;
+}
+
+struct ttable_cell *ttable_insert_row(struct ttable *tt, unsigned int i,
+ const char *format, ...)
+{
+ struct ttable_cell *ret;
+ va_list ap;
+
+ va_start(ap, format);
+ ret = ttable_insert_row_va(tt, i, format, ap);
+ va_end(ap);
+
+ return ret;
+}
+
+struct ttable_cell *ttable_add_row(struct ttable *tt, const char *format, ...)
+{
+ struct ttable_cell *ret;
+ va_list ap;
+
+ va_start(ap, format);
+ ret = ttable_insert_row_va(tt, -1, format, ap);
+ va_end(ap);
+
+ return ret;
+}
+
+void ttable_del_row(struct ttable *tt, unsigned int i)
+{
+ assert((int)i < tt->nrows);
+
+ for (int j = 0; j < tt->ncols; j++)
+ XFREE(MTYPE_TTABLE, tt->table[i][j].text);
+
+ XFREE(MTYPE_TTABLE, tt->table[i]);
+
+ memmove(&tt->table[i], &tt->table[i + 1],
+ (tt->nrows - i - 1) * sizeof(struct ttable_cell *));
+
+ tt->nrows--;
+
+ if (tt->nrows == 0)
+ tt->ncols = 0;
+}
+
+void ttable_align(struct ttable *tt, unsigned int row, unsigned int col,
+ unsigned int nrow, unsigned int ncol, enum ttable_align align)
+{
+ assert((int)row < tt->nrows);
+ assert((int)col < tt->ncols);
+ assert((int)row + (int)nrow <= tt->nrows);
+ assert((int)col + (int)ncol <= tt->ncols);
+
+ for (unsigned int i = row; i < row + nrow; i++)
+ for (unsigned int j = col; j < col + ncol; j++)
+ tt->table[i][j].style.align = align;
+}
+
+static void ttable_cell_pad(struct ttable_cell *cell, enum ttable_align align,
+ short pad)
+{
+ if (align == LEFT)
+ cell->style.lpad = pad;
+ else
+ cell->style.rpad = pad;
+}
+
+void ttable_pad(struct ttable *tt, unsigned int row, unsigned int col,
+ unsigned int nrow, unsigned int ncol, enum ttable_align align,
+ short pad)
+{
+ assert((int)row < tt->nrows);
+ assert((int)col < tt->ncols);
+ assert((int)row + (int)nrow <= tt->nrows);
+ assert((int)col + (int)ncol <= tt->ncols);
+
+ for (unsigned int i = row; i < row + nrow; i++)
+ for (unsigned int j = col; j < col + ncol; j++)
+ ttable_cell_pad(&tt->table[i][j], align, pad);
+}
+
+void ttable_restyle(struct ttable *tt)
+{
+ for (int i = 0; i < tt->nrows; i++)
+ for (int j = 0; j < tt->ncols; j++)
+ tt->table[i][j].style = tt->style.cell;
+}
+
+void ttable_colseps(struct ttable *tt, unsigned int col,
+ enum ttable_align align, bool on, char sep)
+{
+ for (int i = 0; i < tt->nrows; i++) {
+ if (align == RIGHT) {
+ tt->table[i][col].style.border.right_on = on;
+ tt->table[i][col].style.border.right = sep;
+ } else {
+ tt->table[i][col].style.border.left_on = on;
+ tt->table[i][col].style.border.left = sep;
+ }
+ }
+}
+
+void ttable_rowseps(struct ttable *tt, unsigned int row,
+ enum ttable_align align, bool on, char sep)
+{
+ for (int i = 0; i < tt->ncols; i++) {
+ if (align == TOP) {
+ tt->table[row][i].style.border.top_on = on;
+ tt->table[row][i].style.border.top = sep;
+ } else {
+ tt->table[row][i].style.border.bottom_on = on;
+ tt->table[row][i].style.border.bottom = sep;
+ }
+ }
+}
+
+char *ttable_dump(struct ttable *tt, const char *newline)
+{
+ /* clang-format off */
+ char *buf; // print buffer
+ size_t pos; // position in buffer
+ size_t nl_len; // strlen(newline)
+ int cw[tt->ncols]; // calculated column widths
+ int nlines; // total number of newlines / table lines
+ size_t width; // length of one line, with newline
+ int abspad; // calculated whitespace for sprintf
+ char *left; // left part of line
+ size_t lsize; // size of above
+ char *right; // right part of line
+ size_t rsize; // size of above
+ struct ttable_cell *cell, *row; // iteration pointers
+ /* clang-format on */
+
+ nl_len = strlen(newline);
+
+ /* calculate width of each column */
+ memset(cw, 0x00, sizeof(int) * tt->ncols);
+
+ for (int j = 0; j < tt->ncols; j++)
+ for (int i = 0, cellw = 0; i < tt->nrows; i++) {
+ cell = &tt->table[i][j];
+ cellw = 0;
+ cellw += (int)strlen(cell->text);
+ cellw += cell->style.lpad;
+ cellw += cell->style.rpad;
+ if (j != 0)
+ cellw += cell->style.border.left_on ? 1 : 0;
+ if (j != tt->ncols - 1)
+ cellw += cell->style.border.right_on ? 1 : 0;
+ cw[j] = MAX(cw[j], cellw);
+ }
+
+ /* calculate overall line width, including newline */
+ width = 0;
+ width += tt->style.indent;
+ width += tt->style.border.left_on ? 1 : 0;
+ width += tt->style.border.right_on ? 1 : 0;
+ width += strlen(newline);
+ for (int i = 0; i < tt->ncols; i++)
+ width += cw[i];
+
+ /* calculate number of lines en total */
+ nlines = tt->nrows;
+ nlines += tt->style.border.top_on ? 1 : 0;
+ nlines += 1; // tt->style.border.bottom_on ? 1 : 1; makes life easier
+ for (int i = 0; i < tt->nrows; i++) {
+ /* if leftmost cell has top / bottom border, whole row does */
+ nlines += tt->table[i][0].style.border.top_on ? 1 : 0;
+ nlines += tt->table[i][0].style.border.bottom_on ? 1 : 0;
+ }
+
+ /* initialize left & right */
+ lsize = tt->style.indent + (tt->style.border.left_on ? 1 : 0);
+ left = XCALLOC(MTYPE_TTABLE, lsize);
+ rsize = nl_len + (tt->style.border.right_on ? 1 : 0);
+ right = XCALLOC(MTYPE_TTABLE, rsize);
+
+ memset(left, ' ', lsize);
+
+ if (tt->style.border.left_on)
+ left[lsize - 1] = tt->style.border.left;
+
+ if (tt->style.border.right_on) {
+ right[0] = tt->style.border.right;
+ memcpy(&right[1], newline, nl_len);
+ } else
+ memcpy(&right[0], newline, nl_len);
+
+ /* allocate print buffer */
+ buf = XCALLOC(MTYPE_TMP, width * (nlines + 1) + 1);
+ pos = 0;
+
+ if (tt->style.border.top_on) {
+ memcpy(&buf[pos], left, lsize);
+ pos += lsize;
+
+ for (size_t i = 0; i < width - lsize - rsize; i++)
+ buf[pos++] = tt->style.border.top;
+
+ memcpy(&buf[pos], right, rsize);
+ pos += rsize;
+ }
+
+ for (int i = 0; i < tt->nrows; i++) {
+ row = tt->table[i];
+
+ /* if top border and not first row, print top row border */
+ if (row[0].style.border.top_on && i != 0) {
+ memcpy(&buf[pos], left, lsize);
+ pos += lsize;
+
+ for (size_t l = 0; l < width - lsize - rsize; l++)
+ buf[pos++] = row[0].style.border.top;
+
+ pos -= width - lsize - rsize;
+ for (int k = 0; k < tt->ncols; k++) {
+ if (k != 0 && row[k].style.border.left_on)
+ buf[pos] = tt->style.corner;
+ pos += cw[k];
+ if (row[k].style.border.right_on
+ && k != tt->ncols - 1)
+ buf[pos - 1] = tt->style.corner;
+ }
+
+ memcpy(&buf[pos], right, rsize);
+ pos += rsize;
+ }
+
+ memcpy(&buf[pos], left, lsize);
+ pos += lsize;
+
+ for (int j = 0; j < tt->ncols; j++) {
+ /* if left border && not first col print left border */
+ if (row[j].style.border.left_on && j != 0)
+ buf[pos++] = row[j].style.border.left;
+
+ /* print left padding */
+ for (int k = 0; k < row[j].style.lpad; k++)
+ buf[pos++] = ' ';
+
+ /* calculate padding for sprintf */
+ abspad = cw[j];
+ abspad -= row[j].style.rpad;
+ abspad -= row[j].style.lpad;
+ if (j != 0)
+ abspad -= row[j].style.border.left_on ? 1 : 0;
+ if (j != tt->ncols - 1)
+ abspad -= row[j].style.border.right_on ? 1 : 0;
+
+ /* print text */
+ const char *fmt;
+ if (row[j].style.align == LEFT)
+ fmt = "%-*s";
+ else
+ fmt = "%*s";
+
+ pos += sprintf(&buf[pos], fmt, abspad, row[j].text);
+
+ /* print right padding */
+ for (int k = 0; k < row[j].style.rpad; k++)
+ buf[pos++] = ' ';
+
+ /* if right border && not last col print right border */
+ if (row[j].style.border.right_on && j != tt->ncols - 1)
+ buf[pos++] = row[j].style.border.right;
+ }
+
+ memcpy(&buf[pos], right, rsize);
+ pos += rsize;
+
+ /* if bottom border and not last row, print bottom border */
+ if (row[0].style.border.bottom_on && i != tt->nrows - 1) {
+ memcpy(&buf[pos], left, lsize);
+ pos += lsize;
+
+ for (size_t l = 0; l < width - lsize - rsize; l++)
+ buf[pos++] = row[0].style.border.bottom;
+
+ pos -= width - lsize - rsize;
+ for (int k = 0; k < tt->ncols; k++) {
+ if (k != 0 && row[k].style.border.left_on)
+ buf[pos] = tt->style.corner;
+ pos += cw[k];
+ if (row[k].style.border.right_on
+ && k != tt->ncols - 1)
+ buf[pos - 1] = tt->style.corner;
+ }
+
+ memcpy(&buf[pos], right, rsize);
+ pos += rsize;
+ }
+
+ assert(!buf[pos]); /* pos == & of first \0 in buf */
+ }
+
+ if (tt->style.border.bottom_on) {
+ memcpy(&buf[pos], left, lsize);
+ pos += lsize;
+
+ for (size_t l = 0; l < width - lsize - rsize; l++)
+ buf[pos++] = tt->style.border.bottom;
+
+ memcpy(&buf[pos], right, rsize);
+ pos += rsize;
+ }
+
+ buf[pos] = '\0';
+
+ XFREE(MTYPE_TTABLE, left);
+ XFREE(MTYPE_TTABLE, right);
+
+ return buf;
+}
diff --git a/lib/termtable.h b/lib/termtable.h
new file mode 100644
index 0000000..e61c7fa
--- /dev/null
+++ b/lib/termtable.h
@@ -0,0 +1,297 @@
+/*
+ * ASCII table generator.
+ * Copyright (C) 2017 Cumulus Networks
+ * Quentin Young
+ *
+ * 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 _TERMTABLE_H_
+#define _TERMTABLE_H_
+
+#include <zebra.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum ttable_align {
+ LEFT,
+ RIGHT,
+ TOP,
+ BOTTOM,
+};
+
+struct ttable_border {
+ char top;
+ char bottom;
+ char left;
+ char right;
+
+ bool top_on;
+ bool bottom_on;
+ bool left_on;
+ bool right_on;
+};
+
+/* cell style and cell */
+struct ttable_cellstyle {
+ short lpad;
+ short rpad;
+ enum ttable_align align;
+ struct ttable_border border;
+};
+
+struct ttable_cell {
+ char *text;
+ struct ttable_cellstyle style;
+};
+
+/* table style and table */
+struct ttable_style {
+ char corner; /* intersection */
+ int indent; /* left table indent */
+ bool rownums_on; /* show row numbers; unimplemented */
+
+ struct ttable_border border;
+ struct ttable_cellstyle cell;
+};
+
+struct ttable {
+ int nrows; /* number of rows */
+ int ncols; /* number of cols */
+ struct ttable_cell **table; /* table, row x col */
+ size_t size; /* size */
+ struct ttable_style style; /* style */
+};
+
+/* some predefined styles */
+#define TTSTYLE_ASCII 0
+#define TTSTYLE_BLANK 1
+
+extern const struct ttable_style ttable_styles[2];
+
+/**
+ * Creates a new table with the default style, which looks like this:
+ *
+ * +----------+----------+
+ * | column 1 | column 2 |
+ * +----------+----------+
+ * | data... | data!! |
+ * +----------+----------+
+ * | datums | 12345 |
+ * +----------+----------+
+ *
+ * @return the created table
+ */
+struct ttable *ttable_new(const struct ttable_style *tts);
+
+/**
+ * Deletes a table and releases all associated resources.
+ *
+ * @param tt the table to destroy
+ */
+void ttable_del(struct ttable *tt);
+
+/**
+ * Inserts a new row at the given index.
+ *
+ * The row contents are determined by a format string. The format string has
+ * the same form as a regular printf format string, except that columns are
+ * delimited by '|'. For example, to make the first column of the table above,
+ * the call is:
+ *
+ * ttable_insert_row(<tt>, <n>, "%s|%s", "column 1", "column 2");
+ *
+ * All features of printf format strings are permissible here.
+ *
+ * Caveats:
+ * - At present you cannot insert '|' into a cell's contents.
+ * - If there are N columns, '|' must appear n-1 times or the row will not be
+ * created
+ *
+ * @param tt table to insert row into
+ * @param row the row number (begins at 0)
+ * @param format column-separated format string
+ * @param ... arguments to format string
+ *
+ * @return pointer to the first cell in the created row, or NULL if not enough
+ * columns were specified
+ */
+struct ttable_cell *ttable_insert_row(struct ttable *tt, unsigned int row,
+ const char *format, ...) PRINTFRR(3, 4);
+/**
+ * Inserts a new row at the end of the table.
+ *
+ * The row contents are determined by a format string. The format string has
+ * the same form as a regular printf format string, except that columns are
+ * delimited by '|'. For example, to make the first column of the table above,
+ * the call is:
+ *
+ * ttable_add_row(<tt>, "%s|%s", "column 1", "column 2");
+ *
+ * All features of printf format strings are permissible here.
+ *
+ * Caveats:
+ * - At present you cannot insert '|' into a cell's contents.
+ * - If there are N columns, '|' must appear n-1 times or the row will not be
+ * created
+ *
+ * @param tt table to insert row into
+ * @param format column-separated format string
+ * @param ... arguments to format string
+ *
+ * @return pointer to the first cell in the created row, or NULL if not enough
+ * columns were specified
+ */
+struct ttable_cell *ttable_add_row(struct ttable *tt, const char *format, ...)
+ PRINTFRR(2, 3);
+
+/**
+ * Removes a row from the table.
+ *
+ * @param tt table to delete row from
+ * @param row the row number (begins at 0)
+ */
+void ttable_del_row(struct ttable *tt, unsigned int row);
+
+/**
+ * Sets alignment for a range of cells.
+ *
+ * Available alignments are LEFT and RIGHT. Cell contents will be aligned
+ * accordingly, while respecting padding (if any). Suppose a cell has:
+ *
+ * lpad = 1
+ * rpad = 1
+ * align = RIGHT
+ * text = 'datums'
+ *
+ * The cell would look like:
+ *
+ * +-------------------+
+ * | datums |
+ * +-------------------+
+ *
+ * On the other hand:
+ *
+ * lpad = 1
+ * rpad = 10
+ * align = RIGHT
+ * text = 'datums'
+ *
+ * +-------------------+
+ * | datums |
+ * +-------------------+
+ *
+ * The default alignment is LEFT.
+ *
+ * @param tt the table to set alignment on
+ * @param srow starting row index
+ * @param scol starting column index
+ * @param nrow # rows to align
+ * @param ncol # cols to align
+ * @param align the alignment to set
+ */
+void ttable_align(struct ttable *tt, unsigned int srow, unsigned int scol,
+ unsigned int erow, unsigned int ecol,
+ enum ttable_align align);
+
+/**
+ * Sets padding for a range of cells.
+ *
+ * Available padding options are LEFT and RIGHT (the alignment enum is reused).
+ * Both options may be set. Padding is treated as though it is stuck to the
+ * walls of the cell. Suppose a cell has:
+ *
+ * lpad = 4
+ * rpad = 2
+ * align = RIGHT
+ * text = 'datums'
+ *
+ * The cell padding, marked by '.', would look like:
+ *
+ * +--------------+
+ * | .datums. |
+ * +--------------+
+ *
+ * If the column is wider than the cell, the cell contents are aligned in an
+ * additional padded field according to the cell alignment.
+ *
+ * +--------------------+
+ * | Data!!!11!~~~~~:-) |
+ * +--------------------+
+ * | . datums. |
+ * +--------------------+
+ *
+ * @param tt the table to set padding on
+ * @param srow starting row index
+ * @param scol starting column index
+ * @param nrow # rows to pad
+ * @param ncol # cols to pad
+ * @param align LEFT or RIGHT
+ * @param pad # spaces to pad with
+ */
+void ttable_pad(struct ttable *tt, unsigned int srow, unsigned int scol,
+ unsigned int nrow, unsigned int ncol, enum ttable_align align,
+ short pad);
+
+/**
+ * Restyle all cells according to table.cell.style.
+ *
+ * @param tt table to restyle
+ */
+void ttable_restyle(struct ttable *tt);
+
+/**
+ * Turn left/right column separators on or off for specified column.
+ *
+ * @param tt table
+ * @param col column index
+ * @param align left or right separators
+ * @param on true/false for on/off
+ * @param sep character to use
+ */
+void ttable_colseps(struct ttable *tt, unsigned int col,
+ enum ttable_align align, bool on, char sep);
+
+/**
+ * Turn bottom row separators on or off for specified row.
+ *
+ * @param tt table
+ * @param row row index
+ * @param align left or right separators
+ * @param on true/false for on/off
+ * @param sep character to use
+ */
+void ttable_rowseps(struct ttable *tt, unsigned int row,
+ enum ttable_align align, bool on, char sep);
+
+/**
+ * Dumps a table to a heap-allocated string.
+ *
+ * Caller must free this string after use with
+ *
+ * XFREE (MTYPE_TMP, str);
+ *
+ * @param tt the table to dump
+ * @param newline the desired newline sequence to use, null terminated.
+ * @return table in text form
+ */
+char *ttable_dump(struct ttable *tt, const char *newline);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _TERMTABLE_H */
diff --git a/lib/thread.c b/lib/thread.c
new file mode 100644
index 0000000..9eac9b4
--- /dev/null
+++ b/lib/thread.c
@@ -0,0 +1,2217 @@
+/* Thread management routine
+ * Copyright (C) 1998, 2000 Kunihiro Ishiguro <kunihiro@zebra.org>
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 DEBUG */
+
+#include <zebra.h>
+#include <sys/resource.h>
+
+#include "thread.h"
+#include "memory.h"
+#include "frrcu.h"
+#include "log.h"
+#include "hash.h"
+#include "command.h"
+#include "sigevent.h"
+#include "network.h"
+#include "jhash.h"
+#include "frratomic.h"
+#include "frr_pthread.h"
+#include "lib_errors.h"
+#include "libfrr_trace.h"
+#include "libfrr.h"
+
+DEFINE_MTYPE_STATIC(LIB, THREAD, "Thread");
+DEFINE_MTYPE_STATIC(LIB, THREAD_MASTER, "Thread master");
+DEFINE_MTYPE_STATIC(LIB, THREAD_POLL, "Thread Poll Info");
+DEFINE_MTYPE_STATIC(LIB, THREAD_STATS, "Thread stats");
+
+DECLARE_LIST(thread_list, struct thread, threaditem);
+
+struct cancel_req {
+ int flags;
+ struct thread *thread;
+ void *eventobj;
+ struct thread **threadref;
+};
+
+/* Flags for task cancellation */
+#define THREAD_CANCEL_FLAG_READY 0x01
+
+static int thread_timer_cmp(const struct thread *a, const struct thread *b)
+{
+ if (a->u.sands.tv_sec < b->u.sands.tv_sec)
+ return -1;
+ if (a->u.sands.tv_sec > b->u.sands.tv_sec)
+ return 1;
+ if (a->u.sands.tv_usec < b->u.sands.tv_usec)
+ return -1;
+ if (a->u.sands.tv_usec > b->u.sands.tv_usec)
+ return 1;
+ return 0;
+}
+
+DECLARE_HEAP(thread_timer_list, struct thread, timeritem, thread_timer_cmp);
+
+#if defined(__APPLE__)
+#include <mach/mach.h>
+#include <mach/mach_time.h>
+#endif
+
+#define AWAKEN(m) \
+ do { \
+ const unsigned char wakebyte = 0x01; \
+ write(m->io_pipe[1], &wakebyte, 1); \
+ } while (0);
+
+/* control variable for initializer */
+static pthread_once_t init_once = PTHREAD_ONCE_INIT;
+pthread_key_t thread_current;
+
+static pthread_mutex_t masters_mtx = PTHREAD_MUTEX_INITIALIZER;
+static struct list *masters;
+
+static void thread_free(struct thread_master *master, struct thread *thread);
+
+#ifndef EXCLUDE_CPU_TIME
+#define EXCLUDE_CPU_TIME 0
+#endif
+#ifndef CONSUMED_TIME_CHECK
+#define CONSUMED_TIME_CHECK 0
+#endif
+
+bool cputime_enabled = !EXCLUDE_CPU_TIME;
+unsigned long cputime_threshold = CONSUMED_TIME_CHECK;
+unsigned long walltime_threshold = CONSUMED_TIME_CHECK;
+
+/* CLI start ---------------------------------------------------------------- */
+#ifndef VTYSH_EXTRACT_PL
+#include "lib/thread_clippy.c"
+#endif
+
+static unsigned int cpu_record_hash_key(const struct cpu_thread_history *a)
+{
+ int size = sizeof(a->func);
+
+ return jhash(&a->func, size, 0);
+}
+
+static bool cpu_record_hash_cmp(const struct cpu_thread_history *a,
+ const struct cpu_thread_history *b)
+{
+ return a->func == b->func;
+}
+
+static void *cpu_record_hash_alloc(struct cpu_thread_history *a)
+{
+ struct cpu_thread_history *new;
+ new = XCALLOC(MTYPE_THREAD_STATS, sizeof(struct cpu_thread_history));
+ new->func = a->func;
+ new->funcname = a->funcname;
+ return new;
+}
+
+static void cpu_record_hash_free(void *a)
+{
+ struct cpu_thread_history *hist = a;
+
+ XFREE(MTYPE_THREAD_STATS, hist);
+}
+
+static void vty_out_cpu_thread_history(struct vty *vty,
+ struct cpu_thread_history *a)
+{
+ vty_out(vty,
+ "%5zu %10zu.%03zu %9zu %8zu %9zu %8zu %9zu %9zu %9zu %10zu",
+ a->total_active, a->cpu.total / 1000, a->cpu.total % 1000,
+ a->total_calls, (a->cpu.total / a->total_calls), a->cpu.max,
+ (a->real.total / a->total_calls), a->real.max,
+ a->total_cpu_warn, a->total_wall_warn, a->total_starv_warn);
+ vty_out(vty, " %c%c%c%c%c %s\n",
+ a->types & (1 << THREAD_READ) ? 'R' : ' ',
+ a->types & (1 << THREAD_WRITE) ? 'W' : ' ',
+ a->types & (1 << THREAD_TIMER) ? 'T' : ' ',
+ a->types & (1 << THREAD_EVENT) ? 'E' : ' ',
+ a->types & (1 << THREAD_EXECUTE) ? 'X' : ' ', a->funcname);
+}
+
+static void cpu_record_hash_print(struct hash_bucket *bucket, void *args[])
+{
+ struct cpu_thread_history *totals = args[0];
+ struct cpu_thread_history copy;
+ struct vty *vty = args[1];
+ uint8_t *filter = args[2];
+
+ struct cpu_thread_history *a = bucket->data;
+
+ copy.total_active =
+ atomic_load_explicit(&a->total_active, memory_order_seq_cst);
+ copy.total_calls =
+ atomic_load_explicit(&a->total_calls, memory_order_seq_cst);
+ copy.total_cpu_warn =
+ atomic_load_explicit(&a->total_cpu_warn, memory_order_seq_cst);
+ copy.total_wall_warn =
+ atomic_load_explicit(&a->total_wall_warn, memory_order_seq_cst);
+ copy.total_starv_warn = atomic_load_explicit(&a->total_starv_warn,
+ memory_order_seq_cst);
+ copy.cpu.total =
+ atomic_load_explicit(&a->cpu.total, memory_order_seq_cst);
+ copy.cpu.max = atomic_load_explicit(&a->cpu.max, memory_order_seq_cst);
+ copy.real.total =
+ atomic_load_explicit(&a->real.total, memory_order_seq_cst);
+ copy.real.max =
+ atomic_load_explicit(&a->real.max, memory_order_seq_cst);
+ copy.types = atomic_load_explicit(&a->types, memory_order_seq_cst);
+ copy.funcname = a->funcname;
+
+ if (!(copy.types & *filter))
+ return;
+
+ vty_out_cpu_thread_history(vty, &copy);
+ totals->total_active += copy.total_active;
+ totals->total_calls += copy.total_calls;
+ totals->total_cpu_warn += copy.total_cpu_warn;
+ totals->total_wall_warn += copy.total_wall_warn;
+ totals->total_starv_warn += copy.total_starv_warn;
+ totals->real.total += copy.real.total;
+ if (totals->real.max < copy.real.max)
+ totals->real.max = copy.real.max;
+ totals->cpu.total += copy.cpu.total;
+ if (totals->cpu.max < copy.cpu.max)
+ totals->cpu.max = copy.cpu.max;
+}
+
+static void cpu_record_print(struct vty *vty, uint8_t filter)
+{
+ struct cpu_thread_history tmp;
+ void *args[3] = {&tmp, vty, &filter};
+ struct thread_master *m;
+ struct listnode *ln;
+
+ if (!cputime_enabled)
+ vty_out(vty,
+ "\n"
+ "Collecting CPU time statistics is currently disabled. Following statistics\n"
+ "will be zero or may display data from when collection was enabled. Use the\n"
+ " \"service cputime-stats\" command to start collecting data.\n"
+ "\nCounters and wallclock times are always maintained and should be accurate.\n");
+
+ memset(&tmp, 0, sizeof(tmp));
+ tmp.funcname = "TOTAL";
+ tmp.types = filter;
+
+ frr_with_mutex (&masters_mtx) {
+ for (ALL_LIST_ELEMENTS_RO(masters, ln, m)) {
+ const char *name = m->name ? m->name : "main";
+
+ char underline[strlen(name) + 1];
+ memset(underline, '-', sizeof(underline));
+ underline[sizeof(underline) - 1] = '\0';
+
+ vty_out(vty, "\n");
+ vty_out(vty, "Showing statistics for pthread %s\n",
+ name);
+ vty_out(vty, "-------------------------------%s\n",
+ underline);
+ vty_out(vty, "%30s %18s %18s\n", "",
+ "CPU (user+system):", "Real (wall-clock):");
+ vty_out(vty,
+ "Active Runtime(ms) Invoked Avg uSec Max uSecs");
+ vty_out(vty, " Avg uSec Max uSecs");
+ vty_out(vty,
+ " CPU_Warn Wall_Warn Starv_Warn Type Thread\n");
+
+ if (m->cpu_record->count)
+ hash_iterate(
+ m->cpu_record,
+ (void (*)(struct hash_bucket *,
+ void *))cpu_record_hash_print,
+ args);
+ else
+ vty_out(vty, "No data to display yet.\n");
+
+ vty_out(vty, "\n");
+ }
+ }
+
+ vty_out(vty, "\n");
+ vty_out(vty, "Total thread statistics\n");
+ vty_out(vty, "-------------------------\n");
+ vty_out(vty, "%30s %18s %18s\n", "",
+ "CPU (user+system):", "Real (wall-clock):");
+ vty_out(vty, "Active Runtime(ms) Invoked Avg uSec Max uSecs");
+ vty_out(vty, " Avg uSec Max uSecs CPU_Warn Wall_Warn");
+ vty_out(vty, " Type Thread\n");
+
+ if (tmp.total_calls > 0)
+ vty_out_cpu_thread_history(vty, &tmp);
+}
+
+static void cpu_record_hash_clear(struct hash_bucket *bucket, void *args[])
+{
+ uint8_t *filter = args[0];
+ struct hash *cpu_record = args[1];
+
+ struct cpu_thread_history *a = bucket->data;
+
+ if (!(a->types & *filter))
+ return;
+
+ hash_release(cpu_record, bucket->data);
+}
+
+static void cpu_record_clear(uint8_t filter)
+{
+ uint8_t *tmp = &filter;
+ struct thread_master *m;
+ struct listnode *ln;
+
+ frr_with_mutex (&masters_mtx) {
+ for (ALL_LIST_ELEMENTS_RO(masters, ln, m)) {
+ frr_with_mutex (&m->mtx) {
+ void *args[2] = {tmp, m->cpu_record};
+ hash_iterate(
+ m->cpu_record,
+ (void (*)(struct hash_bucket *,
+ void *))cpu_record_hash_clear,
+ args);
+ }
+ }
+ }
+}
+
+static uint8_t parse_filter(const char *filterstr)
+{
+ int i = 0;
+ int filter = 0;
+
+ while (filterstr[i] != '\0') {
+ switch (filterstr[i]) {
+ case 'r':
+ case 'R':
+ filter |= (1 << THREAD_READ);
+ break;
+ case 'w':
+ case 'W':
+ filter |= (1 << THREAD_WRITE);
+ break;
+ case 't':
+ case 'T':
+ filter |= (1 << THREAD_TIMER);
+ break;
+ case 'e':
+ case 'E':
+ filter |= (1 << THREAD_EVENT);
+ break;
+ case 'x':
+ case 'X':
+ filter |= (1 << THREAD_EXECUTE);
+ break;
+ default:
+ break;
+ }
+ ++i;
+ }
+ return filter;
+}
+
+DEFUN_NOSH (show_thread_cpu,
+ show_thread_cpu_cmd,
+ "show thread cpu [FILTER]",
+ SHOW_STR
+ "Thread information\n"
+ "Thread CPU usage\n"
+ "Display filter (rwtex)\n")
+{
+ uint8_t filter = (uint8_t)-1U;
+ int idx = 0;
+
+ if (argv_find(argv, argc, "FILTER", &idx)) {
+ filter = parse_filter(argv[idx]->arg);
+ if (!filter) {
+ vty_out(vty,
+ "Invalid filter \"%s\" specified; must contain at leastone of 'RWTEXB'\n",
+ argv[idx]->arg);
+ return CMD_WARNING;
+ }
+ }
+
+ cpu_record_print(vty, filter);
+ return CMD_SUCCESS;
+}
+
+DEFPY (service_cputime_stats,
+ service_cputime_stats_cmd,
+ "[no] service cputime-stats",
+ NO_STR
+ "Set up miscellaneous service\n"
+ "Collect CPU usage statistics\n")
+{
+ cputime_enabled = !no;
+ return CMD_SUCCESS;
+}
+
+DEFPY (service_cputime_warning,
+ service_cputime_warning_cmd,
+ "[no] service cputime-warning (1-4294967295)",
+ NO_STR
+ "Set up miscellaneous service\n"
+ "Warn for tasks exceeding CPU usage threshold\n"
+ "Warning threshold in milliseconds\n")
+{
+ if (no)
+ cputime_threshold = 0;
+ else
+ cputime_threshold = cputime_warning * 1000;
+ return CMD_SUCCESS;
+}
+
+ALIAS (service_cputime_warning,
+ no_service_cputime_warning_cmd,
+ "no service cputime-warning",
+ NO_STR
+ "Set up miscellaneous service\n"
+ "Warn for tasks exceeding CPU usage threshold\n")
+
+DEFPY (service_walltime_warning,
+ service_walltime_warning_cmd,
+ "[no] service walltime-warning (1-4294967295)",
+ NO_STR
+ "Set up miscellaneous service\n"
+ "Warn for tasks exceeding total wallclock threshold\n"
+ "Warning threshold in milliseconds\n")
+{
+ if (no)
+ walltime_threshold = 0;
+ else
+ walltime_threshold = walltime_warning * 1000;
+ return CMD_SUCCESS;
+}
+
+ALIAS (service_walltime_warning,
+ no_service_walltime_warning_cmd,
+ "no service walltime-warning",
+ NO_STR
+ "Set up miscellaneous service\n"
+ "Warn for tasks exceeding total wallclock threshold\n")
+
+static void show_thread_poll_helper(struct vty *vty, struct thread_master *m)
+{
+ const char *name = m->name ? m->name : "main";
+ char underline[strlen(name) + 1];
+ struct thread *thread;
+ uint32_t i;
+
+ memset(underline, '-', sizeof(underline));
+ underline[sizeof(underline) - 1] = '\0';
+
+ vty_out(vty, "\nShowing poll FD's for %s\n", name);
+ vty_out(vty, "----------------------%s\n", underline);
+ vty_out(vty, "Count: %u/%d\n", (uint32_t)m->handler.pfdcount,
+ m->fd_limit);
+ for (i = 0; i < m->handler.pfdcount; i++) {
+ vty_out(vty, "\t%6d fd:%6d events:%2d revents:%2d\t\t", i,
+ m->handler.pfds[i].fd, m->handler.pfds[i].events,
+ m->handler.pfds[i].revents);
+
+ if (m->handler.pfds[i].events & POLLIN) {
+ thread = m->read[m->handler.pfds[i].fd];
+
+ if (!thread)
+ vty_out(vty, "ERROR ");
+ else
+ vty_out(vty, "%s ", thread->xref->funcname);
+ } else
+ vty_out(vty, " ");
+
+ if (m->handler.pfds[i].events & POLLOUT) {
+ thread = m->write[m->handler.pfds[i].fd];
+
+ if (!thread)
+ vty_out(vty, "ERROR\n");
+ else
+ vty_out(vty, "%s\n", thread->xref->funcname);
+ } else
+ vty_out(vty, "\n");
+ }
+}
+
+DEFUN_NOSH (show_thread_poll,
+ show_thread_poll_cmd,
+ "show thread poll",
+ SHOW_STR
+ "Thread information\n"
+ "Show poll FD's and information\n")
+{
+ struct listnode *node;
+ struct thread_master *m;
+
+ frr_with_mutex (&masters_mtx) {
+ for (ALL_LIST_ELEMENTS_RO(masters, node, m)) {
+ show_thread_poll_helper(vty, m);
+ }
+ }
+
+ return CMD_SUCCESS;
+}
+
+
+DEFUN (clear_thread_cpu,
+ clear_thread_cpu_cmd,
+ "clear thread cpu [FILTER]",
+ "Clear stored data in all pthreads\n"
+ "Thread information\n"
+ "Thread CPU usage\n"
+ "Display filter (rwtexb)\n")
+{
+ uint8_t filter = (uint8_t)-1U;
+ int idx = 0;
+
+ if (argv_find(argv, argc, "FILTER", &idx)) {
+ filter = parse_filter(argv[idx]->arg);
+ if (!filter) {
+ vty_out(vty,
+ "Invalid filter \"%s\" specified; must contain at leastone of 'RWTEXB'\n",
+ argv[idx]->arg);
+ return CMD_WARNING;
+ }
+ }
+
+ cpu_record_clear(filter);
+ return CMD_SUCCESS;
+}
+
+static void show_thread_timers_helper(struct vty *vty, struct thread_master *m)
+{
+ const char *name = m->name ? m->name : "main";
+ char underline[strlen(name) + 1];
+ struct thread *thread;
+
+ memset(underline, '-', sizeof(underline));
+ underline[sizeof(underline) - 1] = '\0';
+
+ vty_out(vty, "\nShowing timers for %s\n", name);
+ vty_out(vty, "-------------------%s\n", underline);
+
+ frr_each (thread_timer_list, &m->timer, thread) {
+ vty_out(vty, " %-50s%pTH\n", thread->hist->funcname, thread);
+ }
+}
+
+DEFPY_NOSH (show_thread_timers,
+ show_thread_timers_cmd,
+ "show thread timers",
+ SHOW_STR
+ "Thread information\n"
+ "Show all timers and how long they have in the system\n")
+{
+ struct listnode *node;
+ struct thread_master *m;
+
+ frr_with_mutex (&masters_mtx) {
+ for (ALL_LIST_ELEMENTS_RO(masters, node, m))
+ show_thread_timers_helper(vty, m);
+ }
+
+ return CMD_SUCCESS;
+}
+
+void thread_cmd_init(void)
+{
+ install_element(VIEW_NODE, &show_thread_cpu_cmd);
+ install_element(VIEW_NODE, &show_thread_poll_cmd);
+ install_element(ENABLE_NODE, &clear_thread_cpu_cmd);
+
+ install_element(CONFIG_NODE, &service_cputime_stats_cmd);
+ install_element(CONFIG_NODE, &service_cputime_warning_cmd);
+ install_element(CONFIG_NODE, &no_service_cputime_warning_cmd);
+ install_element(CONFIG_NODE, &service_walltime_warning_cmd);
+ install_element(CONFIG_NODE, &no_service_walltime_warning_cmd);
+
+ install_element(VIEW_NODE, &show_thread_timers_cmd);
+}
+/* CLI end ------------------------------------------------------------------ */
+
+
+static void cancelreq_del(void *cr)
+{
+ XFREE(MTYPE_TMP, cr);
+}
+
+/* initializer, only ever called once */
+static void initializer(void)
+{
+ pthread_key_create(&thread_current, NULL);
+}
+
+struct thread_master *thread_master_create(const char *name)
+{
+ struct thread_master *rv;
+ struct rlimit limit;
+
+ pthread_once(&init_once, &initializer);
+
+ rv = XCALLOC(MTYPE_THREAD_MASTER, sizeof(struct thread_master));
+
+ /* Initialize master mutex */
+ pthread_mutex_init(&rv->mtx, NULL);
+ pthread_cond_init(&rv->cancel_cond, NULL);
+
+ /* Set name */
+ name = name ? name : "default";
+ rv->name = XSTRDUP(MTYPE_THREAD_MASTER, name);
+
+ /* Initialize I/O task data structures */
+
+ /* Use configured limit if present, ulimit otherwise. */
+ rv->fd_limit = frr_get_fd_limit();
+ if (rv->fd_limit == 0) {
+ getrlimit(RLIMIT_NOFILE, &limit);
+ rv->fd_limit = (int)limit.rlim_cur;
+ }
+
+ rv->read = XCALLOC(MTYPE_THREAD_POLL,
+ sizeof(struct thread *) * rv->fd_limit);
+
+ rv->write = XCALLOC(MTYPE_THREAD_POLL,
+ sizeof(struct thread *) * rv->fd_limit);
+
+ char tmhashname[strlen(name) + 32];
+ snprintf(tmhashname, sizeof(tmhashname), "%s - threadmaster event hash",
+ name);
+ rv->cpu_record = hash_create_size(
+ 8, (unsigned int (*)(const void *))cpu_record_hash_key,
+ (bool (*)(const void *, const void *))cpu_record_hash_cmp,
+ tmhashname);
+
+ thread_list_init(&rv->event);
+ thread_list_init(&rv->ready);
+ thread_list_init(&rv->unuse);
+ thread_timer_list_init(&rv->timer);
+
+ /* Initialize thread_fetch() settings */
+ rv->spin = true;
+ rv->handle_signals = true;
+
+ /* Set pthread owner, should be updated by actual owner */
+ rv->owner = pthread_self();
+ rv->cancel_req = list_new();
+ rv->cancel_req->del = cancelreq_del;
+ rv->canceled = true;
+
+ /* Initialize pipe poker */
+ pipe(rv->io_pipe);
+ set_nonblocking(rv->io_pipe[0]);
+ set_nonblocking(rv->io_pipe[1]);
+
+ /* Initialize data structures for poll() */
+ rv->handler.pfdsize = rv->fd_limit;
+ rv->handler.pfdcount = 0;
+ rv->handler.pfds = XCALLOC(MTYPE_THREAD_MASTER,
+ sizeof(struct pollfd) * rv->handler.pfdsize);
+ rv->handler.copy = XCALLOC(MTYPE_THREAD_MASTER,
+ sizeof(struct pollfd) * rv->handler.pfdsize);
+
+ /* add to list of threadmasters */
+ frr_with_mutex (&masters_mtx) {
+ if (!masters)
+ masters = list_new();
+
+ listnode_add(masters, rv);
+ }
+
+ return rv;
+}
+
+void thread_master_set_name(struct thread_master *master, const char *name)
+{
+ frr_with_mutex (&master->mtx) {
+ XFREE(MTYPE_THREAD_MASTER, master->name);
+ master->name = XSTRDUP(MTYPE_THREAD_MASTER, name);
+ }
+}
+
+#define THREAD_UNUSED_DEPTH 10
+
+/* Move thread to unuse list. */
+static void thread_add_unuse(struct thread_master *m, struct thread *thread)
+{
+ pthread_mutex_t mtxc = thread->mtx;
+
+ assert(m != NULL && thread != NULL);
+
+ thread->hist->total_active--;
+ memset(thread, 0, sizeof(struct thread));
+ thread->type = THREAD_UNUSED;
+
+ /* Restore the thread mutex context. */
+ thread->mtx = mtxc;
+
+ if (thread_list_count(&m->unuse) < THREAD_UNUSED_DEPTH) {
+ thread_list_add_tail(&m->unuse, thread);
+ return;
+ }
+
+ thread_free(m, thread);
+}
+
+/* Free all unused thread. */
+static void thread_list_free(struct thread_master *m,
+ struct thread_list_head *list)
+{
+ struct thread *t;
+
+ while ((t = thread_list_pop(list)))
+ thread_free(m, t);
+}
+
+static void thread_array_free(struct thread_master *m,
+ struct thread **thread_array)
+{
+ struct thread *t;
+ int index;
+
+ for (index = 0; index < m->fd_limit; ++index) {
+ t = thread_array[index];
+ if (t) {
+ thread_array[index] = NULL;
+ thread_free(m, t);
+ }
+ }
+ XFREE(MTYPE_THREAD_POLL, thread_array);
+}
+
+/*
+ * thread_master_free_unused
+ *
+ * As threads are finished with they are put on the
+ * unuse list for later reuse.
+ * If we are shutting down, Free up unused threads
+ * So we can see if we forget to shut anything off
+ */
+void thread_master_free_unused(struct thread_master *m)
+{
+ frr_with_mutex (&m->mtx) {
+ struct thread *t;
+ while ((t = thread_list_pop(&m->unuse)))
+ thread_free(m, t);
+ }
+}
+
+/* Stop thread scheduler. */
+void thread_master_free(struct thread_master *m)
+{
+ struct thread *t;
+
+ frr_with_mutex (&masters_mtx) {
+ listnode_delete(masters, m);
+ if (masters->count == 0) {
+ list_delete(&masters);
+ }
+ }
+
+ thread_array_free(m, m->read);
+ thread_array_free(m, m->write);
+ while ((t = thread_timer_list_pop(&m->timer)))
+ thread_free(m, t);
+ thread_list_free(m, &m->event);
+ thread_list_free(m, &m->ready);
+ thread_list_free(m, &m->unuse);
+ pthread_mutex_destroy(&m->mtx);
+ pthread_cond_destroy(&m->cancel_cond);
+ close(m->io_pipe[0]);
+ close(m->io_pipe[1]);
+ list_delete(&m->cancel_req);
+ m->cancel_req = NULL;
+
+ hash_clean(m->cpu_record, cpu_record_hash_free);
+ hash_free(m->cpu_record);
+ m->cpu_record = NULL;
+
+ XFREE(MTYPE_THREAD_MASTER, m->name);
+ XFREE(MTYPE_THREAD_MASTER, m->handler.pfds);
+ XFREE(MTYPE_THREAD_MASTER, m->handler.copy);
+ XFREE(MTYPE_THREAD_MASTER, m);
+}
+
+/* Return remain time in milliseconds. */
+unsigned long thread_timer_remain_msec(struct thread *thread)
+{
+ int64_t remain;
+
+ if (!thread_is_scheduled(thread))
+ return 0;
+
+ frr_with_mutex (&thread->mtx) {
+ remain = monotime_until(&thread->u.sands, NULL) / 1000LL;
+ }
+
+ return remain < 0 ? 0 : remain;
+}
+
+/* Return remain time in seconds. */
+unsigned long thread_timer_remain_second(struct thread *thread)
+{
+ return thread_timer_remain_msec(thread) / 1000LL;
+}
+
+struct timeval thread_timer_remain(struct thread *thread)
+{
+ struct timeval remain;
+ frr_with_mutex (&thread->mtx) {
+ monotime_until(&thread->u.sands, &remain);
+ }
+ return remain;
+}
+
+static int time_hhmmss(char *buf, int buf_size, long sec)
+{
+ long hh;
+ long mm;
+ int wr;
+
+ assert(buf_size >= 8);
+
+ hh = sec / 3600;
+ sec %= 3600;
+ mm = sec / 60;
+ sec %= 60;
+
+ wr = snprintf(buf, buf_size, "%02ld:%02ld:%02ld", hh, mm, sec);
+
+ return wr != 8;
+}
+
+char *thread_timer_to_hhmmss(char *buf, int buf_size,
+ struct thread *t_timer)
+{
+ if (t_timer) {
+ time_hhmmss(buf, buf_size,
+ thread_timer_remain_second(t_timer));
+ } else {
+ snprintf(buf, buf_size, "--:--:--");
+ }
+ return buf;
+}
+
+/* Get new thread. */
+static struct thread *thread_get(struct thread_master *m, uint8_t type,
+ void (*func)(struct thread *), void *arg,
+ const struct xref_threadsched *xref)
+{
+ struct thread *thread = thread_list_pop(&m->unuse);
+ struct cpu_thread_history tmp;
+
+ if (!thread) {
+ thread = XCALLOC(MTYPE_THREAD, sizeof(struct thread));
+ /* mutex only needs to be initialized at struct creation. */
+ pthread_mutex_init(&thread->mtx, NULL);
+ m->alloc++;
+ }
+
+ thread->type = type;
+ thread->add_type = type;
+ thread->master = m;
+ thread->arg = arg;
+ thread->yield = THREAD_YIELD_TIME_SLOT; /* default */
+ thread->ref = NULL;
+ thread->ignore_timer_late = false;
+
+ /*
+ * So if the passed in funcname is not what we have
+ * stored that means the thread->hist needs to be
+ * updated. We keep the last one around in unused
+ * under the assumption that we are probably
+ * going to immediately allocate the same
+ * type of thread.
+ * This hopefully saves us some serious
+ * hash_get lookups.
+ */
+ if ((thread->xref && thread->xref->funcname != xref->funcname)
+ || thread->func != func) {
+ tmp.func = func;
+ tmp.funcname = xref->funcname;
+ thread->hist =
+ hash_get(m->cpu_record, &tmp,
+ (void *(*)(void *))cpu_record_hash_alloc);
+ }
+ thread->hist->total_active++;
+ thread->func = func;
+ thread->xref = xref;
+
+ return thread;
+}
+
+static void thread_free(struct thread_master *master, struct thread *thread)
+{
+ /* Update statistics. */
+ assert(master->alloc > 0);
+ master->alloc--;
+
+ /* Free allocated resources. */
+ pthread_mutex_destroy(&thread->mtx);
+ XFREE(MTYPE_THREAD, thread);
+}
+
+static int fd_poll(struct thread_master *m, const struct timeval *timer_wait,
+ bool *eintr_p)
+{
+ sigset_t origsigs;
+ unsigned char trash[64];
+ nfds_t count = m->handler.copycount;
+
+ /*
+ * If timer_wait is null here, that means poll() should block
+ * indefinitely, unless the thread_master has overridden it by setting
+ * ->selectpoll_timeout.
+ *
+ * If the value is positive, it specifies the maximum number of
+ * milliseconds to wait. If the timeout is -1, it specifies that
+ * we should never wait and always return immediately even if no
+ * event is detected. If the value is zero, the behavior is default.
+ */
+ int timeout = -1;
+
+ /* number of file descriptors with events */
+ int num;
+
+ if (timer_wait != NULL
+ && m->selectpoll_timeout == 0) // use the default value
+ timeout = (timer_wait->tv_sec * 1000)
+ + (timer_wait->tv_usec / 1000);
+ else if (m->selectpoll_timeout > 0) // use the user's timeout
+ timeout = m->selectpoll_timeout;
+ else if (m->selectpoll_timeout
+ < 0) // effect a poll (return immediately)
+ timeout = 0;
+
+ zlog_tls_buffer_flush();
+ rcu_read_unlock();
+ rcu_assert_read_unlocked();
+
+ /* add poll pipe poker */
+ assert(count + 1 < m->handler.pfdsize);
+ m->handler.copy[count].fd = m->io_pipe[0];
+ m->handler.copy[count].events = POLLIN;
+ m->handler.copy[count].revents = 0x00;
+
+ /* We need to deal with a signal-handling race here: we
+ * don't want to miss a crucial signal, such as SIGTERM or SIGINT,
+ * that may arrive just before we enter poll(). We will block the
+ * key signals, then check whether any have arrived - if so, we return
+ * before calling poll(). If not, we'll re-enable the signals
+ * in the ppoll() call.
+ */
+
+ sigemptyset(&origsigs);
+ if (m->handle_signals) {
+ /* Main pthread that handles the app signals */
+ if (frr_sigevent_check(&origsigs)) {
+ /* Signal to process - restore signal mask and return */
+ pthread_sigmask(SIG_SETMASK, &origsigs, NULL);
+ num = -1;
+ *eintr_p = true;
+ goto done;
+ }
+ } else {
+ /* Don't make any changes for the non-main pthreads */
+ pthread_sigmask(SIG_SETMASK, NULL, &origsigs);
+ }
+
+#if defined(HAVE_PPOLL)
+ struct timespec ts, *tsp;
+
+ if (timeout >= 0) {
+ ts.tv_sec = timeout / 1000;
+ ts.tv_nsec = (timeout % 1000) * 1000000;
+ tsp = &ts;
+ } else
+ tsp = NULL;
+
+ num = ppoll(m->handler.copy, count + 1, tsp, &origsigs);
+ pthread_sigmask(SIG_SETMASK, &origsigs, NULL);
+#else
+ /* Not ideal - there is a race after we restore the signal mask */
+ pthread_sigmask(SIG_SETMASK, &origsigs, NULL);
+ num = poll(m->handler.copy, count + 1, timeout);
+#endif
+
+done:
+
+ if (num < 0 && errno == EINTR)
+ *eintr_p = true;
+
+ if (num > 0 && m->handler.copy[count].revents != 0 && num--)
+ while (read(m->io_pipe[0], &trash, sizeof(trash)) > 0)
+ ;
+
+ rcu_read_lock();
+
+ return num;
+}
+
+/* Add new read thread. */
+void _thread_add_read_write(const struct xref_threadsched *xref,
+ struct thread_master *m,
+ void (*func)(struct thread *), void *arg, int fd,
+ struct thread **t_ptr)
+{
+ int dir = xref->thread_type;
+ struct thread *thread = NULL;
+ struct thread **thread_array;
+
+ if (dir == THREAD_READ)
+ frrtrace(9, frr_libfrr, schedule_read, m,
+ xref->funcname, xref->xref.file, xref->xref.line,
+ t_ptr, fd, 0, arg, 0);
+ else
+ frrtrace(9, frr_libfrr, schedule_write, m,
+ xref->funcname, xref->xref.file, xref->xref.line,
+ t_ptr, fd, 0, arg, 0);
+
+ assert(fd >= 0);
+ if (fd >= m->fd_limit)
+ assert(!"Number of FD's open is greater than FRR currently configured to handle, aborting");
+
+ frr_with_mutex (&m->mtx) {
+ if (t_ptr && *t_ptr)
+ // thread is already scheduled; don't reschedule
+ break;
+
+ /* default to a new pollfd */
+ nfds_t queuepos = m->handler.pfdcount;
+
+ if (dir == THREAD_READ)
+ thread_array = m->read;
+ else
+ thread_array = m->write;
+
+ /* if we already have a pollfd for our file descriptor, find and
+ * use it */
+ for (nfds_t i = 0; i < m->handler.pfdcount; i++)
+ if (m->handler.pfds[i].fd == fd) {
+ queuepos = i;
+
+#ifdef DEV_BUILD
+ /*
+ * What happens if we have a thread already
+ * created for this event?
+ */
+ if (thread_array[fd])
+ assert(!"Thread already scheduled for file descriptor");
+#endif
+ break;
+ }
+
+ /* make sure we have room for this fd + pipe poker fd */
+ assert(queuepos + 1 < m->handler.pfdsize);
+
+ thread = thread_get(m, dir, func, arg, xref);
+
+ m->handler.pfds[queuepos].fd = fd;
+ m->handler.pfds[queuepos].events |=
+ (dir == THREAD_READ ? POLLIN : POLLOUT);
+
+ if (queuepos == m->handler.pfdcount)
+ m->handler.pfdcount++;
+
+ if (thread) {
+ frr_with_mutex (&thread->mtx) {
+ thread->u.fd = fd;
+ thread_array[thread->u.fd] = thread;
+ }
+
+ if (t_ptr) {
+ *t_ptr = thread;
+ thread->ref = t_ptr;
+ }
+ }
+
+ AWAKEN(m);
+ }
+}
+
+static void _thread_add_timer_timeval(const struct xref_threadsched *xref,
+ struct thread_master *m,
+ void (*func)(struct thread *), void *arg,
+ struct timeval *time_relative,
+ struct thread **t_ptr)
+{
+ struct thread *thread;
+ struct timeval t;
+
+ assert(m != NULL);
+
+ assert(time_relative);
+
+ frrtrace(9, frr_libfrr, schedule_timer, m,
+ xref->funcname, xref->xref.file, xref->xref.line,
+ t_ptr, 0, 0, arg, (long)time_relative->tv_sec);
+
+ /* Compute expiration/deadline time. */
+ monotime(&t);
+ timeradd(&t, time_relative, &t);
+
+ frr_with_mutex (&m->mtx) {
+ if (t_ptr && *t_ptr)
+ /* thread is already scheduled; don't reschedule */
+ return;
+
+ thread = thread_get(m, THREAD_TIMER, func, arg, xref);
+
+ frr_with_mutex (&thread->mtx) {
+ thread->u.sands = t;
+ thread_timer_list_add(&m->timer, thread);
+ if (t_ptr) {
+ *t_ptr = thread;
+ thread->ref = t_ptr;
+ }
+ }
+
+ /* The timer list is sorted - if this new timer
+ * might change the time we'll wait for, give the pthread
+ * a chance to re-compute.
+ */
+ if (thread_timer_list_first(&m->timer) == thread)
+ AWAKEN(m);
+ }
+#define ONEYEAR2SEC (60 * 60 * 24 * 365)
+ if (time_relative->tv_sec > ONEYEAR2SEC)
+ flog_err(
+ EC_LIB_TIMER_TOO_LONG,
+ "Timer: %pTHD is created with an expiration that is greater than 1 year",
+ thread);
+}
+
+
+/* Add timer event thread. */
+void _thread_add_timer(const struct xref_threadsched *xref,
+ struct thread_master *m, void (*func)(struct thread *),
+ void *arg, long timer, struct thread **t_ptr)
+{
+ struct timeval trel;
+
+ assert(m != NULL);
+
+ trel.tv_sec = timer;
+ trel.tv_usec = 0;
+
+ _thread_add_timer_timeval(xref, m, func, arg, &trel, t_ptr);
+}
+
+/* Add timer event thread with "millisecond" resolution */
+void _thread_add_timer_msec(const struct xref_threadsched *xref,
+ struct thread_master *m,
+ void (*func)(struct thread *), void *arg,
+ long timer, struct thread **t_ptr)
+{
+ struct timeval trel;
+
+ assert(m != NULL);
+
+ trel.tv_sec = timer / 1000;
+ trel.tv_usec = 1000 * (timer % 1000);
+
+ _thread_add_timer_timeval(xref, m, func, arg, &trel, t_ptr);
+}
+
+/* Add timer event thread with "timeval" resolution */
+void _thread_add_timer_tv(const struct xref_threadsched *xref,
+ struct thread_master *m,
+ void (*func)(struct thread *), void *arg,
+ struct timeval *tv, struct thread **t_ptr)
+{
+ _thread_add_timer_timeval(xref, m, func, arg, tv, t_ptr);
+}
+
+/* Add simple event thread. */
+void _thread_add_event(const struct xref_threadsched *xref,
+ struct thread_master *m, void (*func)(struct thread *),
+ void *arg, int val, struct thread **t_ptr)
+{
+ struct thread *thread = NULL;
+
+ frrtrace(9, frr_libfrr, schedule_event, m,
+ xref->funcname, xref->xref.file, xref->xref.line,
+ t_ptr, 0, val, arg, 0);
+
+ assert(m != NULL);
+
+ frr_with_mutex (&m->mtx) {
+ if (t_ptr && *t_ptr)
+ /* thread is already scheduled; don't reschedule */
+ break;
+
+ thread = thread_get(m, THREAD_EVENT, func, arg, xref);
+ frr_with_mutex (&thread->mtx) {
+ thread->u.val = val;
+ thread_list_add_tail(&m->event, thread);
+ }
+
+ if (t_ptr) {
+ *t_ptr = thread;
+ thread->ref = t_ptr;
+ }
+
+ AWAKEN(m);
+ }
+}
+
+/* Thread cancellation ------------------------------------------------------ */
+
+/**
+ * NOT's out the .events field of pollfd corresponding to the given file
+ * descriptor. The event to be NOT'd is passed in the 'state' parameter.
+ *
+ * This needs to happen for both copies of pollfd's. See 'thread_fetch'
+ * implementation for details.
+ *
+ * @param master
+ * @param fd
+ * @param state the event to cancel. One or more (OR'd together) of the
+ * following:
+ * - POLLIN
+ * - POLLOUT
+ */
+static void thread_cancel_rw(struct thread_master *master, int fd, short state,
+ int idx_hint)
+{
+ bool found = false;
+
+ /* find the index of corresponding pollfd */
+ nfds_t i;
+
+ /* Cancel POLLHUP too just in case some bozo set it */
+ state |= POLLHUP;
+
+ /* Some callers know the index of the pfd already */
+ if (idx_hint >= 0) {
+ i = idx_hint;
+ found = true;
+ } else {
+ /* Have to look for the fd in the pfd array */
+ for (i = 0; i < master->handler.pfdcount; i++)
+ if (master->handler.pfds[i].fd == fd) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ zlog_debug(
+ "[!] Received cancellation request for nonexistent rw job");
+ zlog_debug("[!] threadmaster: %s | fd: %d",
+ master->name ? master->name : "", fd);
+ return;
+ }
+
+ /* NOT out event. */
+ master->handler.pfds[i].events &= ~(state);
+
+ /* If all events are canceled, delete / resize the pollfd array. */
+ if (master->handler.pfds[i].events == 0) {
+ memmove(master->handler.pfds + i, master->handler.pfds + i + 1,
+ (master->handler.pfdcount - i - 1)
+ * sizeof(struct pollfd));
+ master->handler.pfdcount--;
+ master->handler.pfds[master->handler.pfdcount].fd = 0;
+ master->handler.pfds[master->handler.pfdcount].events = 0;
+ }
+
+ /* If we have the same pollfd in the copy, perform the same operations,
+ * otherwise return. */
+ if (i >= master->handler.copycount)
+ return;
+
+ master->handler.copy[i].events &= ~(state);
+
+ if (master->handler.copy[i].events == 0) {
+ memmove(master->handler.copy + i, master->handler.copy + i + 1,
+ (master->handler.copycount - i - 1)
+ * sizeof(struct pollfd));
+ master->handler.copycount--;
+ master->handler.copy[master->handler.copycount].fd = 0;
+ master->handler.copy[master->handler.copycount].events = 0;
+ }
+}
+
+/*
+ * Process task cancellation given a task argument: iterate through the
+ * various lists of tasks, looking for any that match the argument.
+ */
+static void cancel_arg_helper(struct thread_master *master,
+ const struct cancel_req *cr)
+{
+ struct thread *t;
+ nfds_t i;
+ int fd;
+ struct pollfd *pfd;
+
+ /* We're only processing arg-based cancellations here. */
+ if (cr->eventobj == NULL)
+ return;
+
+ /* First process the ready lists. */
+ frr_each_safe(thread_list, &master->event, t) {
+ if (t->arg != cr->eventobj)
+ continue;
+ thread_list_del(&master->event, t);
+ if (t->ref)
+ *t->ref = NULL;
+ thread_add_unuse(master, t);
+ }
+
+ frr_each_safe(thread_list, &master->ready, t) {
+ if (t->arg != cr->eventobj)
+ continue;
+ thread_list_del(&master->ready, t);
+ if (t->ref)
+ *t->ref = NULL;
+ thread_add_unuse(master, t);
+ }
+
+ /* If requested, stop here and ignore io and timers */
+ if (CHECK_FLAG(cr->flags, THREAD_CANCEL_FLAG_READY))
+ return;
+
+ /* Check the io tasks */
+ for (i = 0; i < master->handler.pfdcount;) {
+ pfd = master->handler.pfds + i;
+
+ if (pfd->events & POLLIN)
+ t = master->read[pfd->fd];
+ else
+ t = master->write[pfd->fd];
+
+ if (t && t->arg == cr->eventobj) {
+ fd = pfd->fd;
+
+ /* Found a match to cancel: clean up fd arrays */
+ thread_cancel_rw(master, pfd->fd, pfd->events, i);
+
+ /* Clean up thread arrays */
+ master->read[fd] = NULL;
+ master->write[fd] = NULL;
+
+ /* Clear caller's ref */
+ if (t->ref)
+ *t->ref = NULL;
+
+ thread_add_unuse(master, t);
+
+ /* Don't increment 'i' since the cancellation will have
+ * removed the entry from the pfd array
+ */
+ } else
+ i++;
+ }
+
+ /* Check the timer tasks */
+ t = thread_timer_list_first(&master->timer);
+ while (t) {
+ struct thread *t_next;
+
+ t_next = thread_timer_list_next(&master->timer, t);
+
+ if (t->arg == cr->eventobj) {
+ thread_timer_list_del(&master->timer, t);
+ if (t->ref)
+ *t->ref = NULL;
+ thread_add_unuse(master, t);
+ }
+
+ t = t_next;
+ }
+}
+
+/**
+ * Process cancellation requests.
+ *
+ * This may only be run from the pthread which owns the thread_master.
+ *
+ * @param master the thread master to process
+ * @REQUIRE master->mtx
+ */
+static void do_thread_cancel(struct thread_master *master)
+{
+ struct thread_list_head *list = NULL;
+ struct thread **thread_array = NULL;
+ struct thread *thread;
+ struct cancel_req *cr;
+ struct listnode *ln;
+
+ for (ALL_LIST_ELEMENTS_RO(master->cancel_req, ln, cr)) {
+ /*
+ * If this is an event object cancellation, search
+ * through task lists deleting any tasks which have the
+ * specified argument - use this handy helper function.
+ */
+ if (cr->eventobj) {
+ cancel_arg_helper(master, cr);
+ continue;
+ }
+
+ /*
+ * The pointer varies depending on whether the cancellation
+ * request was made asynchronously or not. If it was, we
+ * need to check whether the thread even exists anymore
+ * before cancelling it.
+ */
+ thread = (cr->thread) ? cr->thread : *cr->threadref;
+
+ if (!thread)
+ continue;
+
+ list = NULL;
+ thread_array = NULL;
+
+ /* Determine the appropriate queue to cancel the thread from */
+ switch (thread->type) {
+ case THREAD_READ:
+ thread_cancel_rw(master, thread->u.fd, POLLIN, -1);
+ thread_array = master->read;
+ break;
+ case THREAD_WRITE:
+ thread_cancel_rw(master, thread->u.fd, POLLOUT, -1);
+ thread_array = master->write;
+ break;
+ case THREAD_TIMER:
+ thread_timer_list_del(&master->timer, thread);
+ break;
+ case THREAD_EVENT:
+ list = &master->event;
+ break;
+ case THREAD_READY:
+ list = &master->ready;
+ break;
+ default:
+ continue;
+ break;
+ }
+
+ if (list) {
+ thread_list_del(list, thread);
+ } else if (thread_array) {
+ thread_array[thread->u.fd] = NULL;
+ }
+
+ if (thread->ref)
+ *thread->ref = NULL;
+
+ thread_add_unuse(thread->master, thread);
+ }
+
+ /* Delete and free all cancellation requests */
+ if (master->cancel_req)
+ list_delete_all_node(master->cancel_req);
+
+ /* Wake up any threads which may be blocked in thread_cancel_async() */
+ master->canceled = true;
+ pthread_cond_broadcast(&master->cancel_cond);
+}
+
+/*
+ * Helper function used for multiple flavors of arg-based cancellation.
+ */
+static void cancel_event_helper(struct thread_master *m, void *arg, int flags)
+{
+ struct cancel_req *cr;
+
+ assert(m->owner == pthread_self());
+
+ /* Only worth anything if caller supplies an arg. */
+ if (arg == NULL)
+ return;
+
+ cr = XCALLOC(MTYPE_TMP, sizeof(struct cancel_req));
+
+ cr->flags = flags;
+
+ frr_with_mutex (&m->mtx) {
+ cr->eventobj = arg;
+ listnode_add(m->cancel_req, cr);
+ do_thread_cancel(m);
+ }
+}
+
+/**
+ * Cancel any events which have the specified argument.
+ *
+ * MT-Unsafe
+ *
+ * @param m the thread_master to cancel from
+ * @param arg the argument passed when creating the event
+ */
+void thread_cancel_event(struct thread_master *master, void *arg)
+{
+ cancel_event_helper(master, arg, 0);
+}
+
+/*
+ * Cancel ready tasks with an arg matching 'arg'
+ *
+ * MT-Unsafe
+ *
+ * @param m the thread_master to cancel from
+ * @param arg the argument passed when creating the event
+ */
+void thread_cancel_event_ready(struct thread_master *m, void *arg)
+{
+
+ /* Only cancel ready/event tasks */
+ cancel_event_helper(m, arg, THREAD_CANCEL_FLAG_READY);
+}
+
+/**
+ * Cancel a specific task.
+ *
+ * MT-Unsafe
+ *
+ * @param thread task to cancel
+ */
+void thread_cancel(struct thread **thread)
+{
+ struct thread_master *master;
+
+ if (thread == NULL || *thread == NULL)
+ return;
+
+ master = (*thread)->master;
+
+ frrtrace(9, frr_libfrr, thread_cancel, master,
+ (*thread)->xref->funcname, (*thread)->xref->xref.file,
+ (*thread)->xref->xref.line, NULL, (*thread)->u.fd,
+ (*thread)->u.val, (*thread)->arg, (*thread)->u.sands.tv_sec);
+
+ assert(master->owner == pthread_self());
+
+ frr_with_mutex (&master->mtx) {
+ struct cancel_req *cr =
+ XCALLOC(MTYPE_TMP, sizeof(struct cancel_req));
+ cr->thread = *thread;
+ listnode_add(master->cancel_req, cr);
+ do_thread_cancel(master);
+ }
+
+ *thread = NULL;
+}
+
+/**
+ * Asynchronous cancellation.
+ *
+ * Called with either a struct thread ** or void * to an event argument,
+ * this function posts the correct cancellation request and blocks until it is
+ * serviced.
+ *
+ * If the thread is currently running, execution blocks until it completes.
+ *
+ * The last two parameters are mutually exclusive, i.e. if you pass one the
+ * other must be NULL.
+ *
+ * When the cancellation procedure executes on the target thread_master, the
+ * thread * provided is checked for nullity. If it is null, the thread is
+ * assumed to no longer exist and the cancellation request is a no-op. Thus
+ * users of this API must pass a back-reference when scheduling the original
+ * task.
+ *
+ * MT-Safe
+ *
+ * @param master the thread master with the relevant event / task
+ * @param thread pointer to thread to cancel
+ * @param eventobj the event
+ */
+void thread_cancel_async(struct thread_master *master, struct thread **thread,
+ void *eventobj)
+{
+ assert(!(thread && eventobj) && (thread || eventobj));
+
+ if (thread && *thread)
+ frrtrace(9, frr_libfrr, thread_cancel_async, master,
+ (*thread)->xref->funcname, (*thread)->xref->xref.file,
+ (*thread)->xref->xref.line, NULL, (*thread)->u.fd,
+ (*thread)->u.val, (*thread)->arg,
+ (*thread)->u.sands.tv_sec);
+ else
+ frrtrace(9, frr_libfrr, thread_cancel_async, master, NULL, NULL,
+ 0, NULL, 0, 0, eventobj, 0);
+
+ assert(master->owner != pthread_self());
+
+ frr_with_mutex (&master->mtx) {
+ master->canceled = false;
+
+ if (thread) {
+ struct cancel_req *cr =
+ XCALLOC(MTYPE_TMP, sizeof(struct cancel_req));
+ cr->threadref = thread;
+ listnode_add(master->cancel_req, cr);
+ } else if (eventobj) {
+ struct cancel_req *cr =
+ XCALLOC(MTYPE_TMP, sizeof(struct cancel_req));
+ cr->eventobj = eventobj;
+ listnode_add(master->cancel_req, cr);
+ }
+ AWAKEN(master);
+
+ while (!master->canceled)
+ pthread_cond_wait(&master->cancel_cond, &master->mtx);
+ }
+
+ if (thread)
+ *thread = NULL;
+}
+/* ------------------------------------------------------------------------- */
+
+static struct timeval *thread_timer_wait(struct thread_timer_list_head *timers,
+ struct timeval *timer_val)
+{
+ if (!thread_timer_list_count(timers))
+ return NULL;
+
+ struct thread *next_timer = thread_timer_list_first(timers);
+ monotime_until(&next_timer->u.sands, timer_val);
+ return timer_val;
+}
+
+static struct thread *thread_run(struct thread_master *m, struct thread *thread,
+ struct thread *fetch)
+{
+ *fetch = *thread;
+ thread_add_unuse(m, thread);
+ return fetch;
+}
+
+static int thread_process_io_helper(struct thread_master *m,
+ struct thread *thread, short state,
+ short actual_state, int pos)
+{
+ struct thread **thread_array;
+
+ /*
+ * poll() clears the .events field, but the pollfd array we
+ * pass to poll() is a copy of the one used to schedule threads.
+ * We need to synchronize state between the two here by applying
+ * the same changes poll() made on the copy of the "real" pollfd
+ * array.
+ *
+ * This cleans up a possible infinite loop where we refuse
+ * to respond to a poll event but poll is insistent that
+ * we should.
+ */
+ m->handler.pfds[pos].events &= ~(state);
+
+ if (!thread) {
+ if ((actual_state & (POLLHUP|POLLIN)) != POLLHUP)
+ flog_err(EC_LIB_NO_THREAD,
+ "Attempting to process an I/O event but for fd: %d(%d) no thread to handle this!",
+ m->handler.pfds[pos].fd, actual_state);
+ return 0;
+ }
+
+ if (thread->type == THREAD_READ)
+ thread_array = m->read;
+ else
+ thread_array = m->write;
+
+ thread_array[thread->u.fd] = NULL;
+ thread_list_add_tail(&m->ready, thread);
+ thread->type = THREAD_READY;
+
+ return 1;
+}
+
+/**
+ * Process I/O events.
+ *
+ * Walks through file descriptor array looking for those pollfds whose .revents
+ * field has something interesting. Deletes any invalid file descriptors.
+ *
+ * @param m the thread master
+ * @param num the number of active file descriptors (return value of poll())
+ */
+static void thread_process_io(struct thread_master *m, unsigned int num)
+{
+ unsigned int ready = 0;
+ struct pollfd *pfds = m->handler.copy;
+
+ for (nfds_t i = 0; i < m->handler.copycount && ready < num; ++i) {
+ /* no event for current fd? immediately continue */
+ if (pfds[i].revents == 0)
+ continue;
+
+ ready++;
+
+ /*
+ * Unless someone has called thread_cancel from another
+ * pthread, the only thing that could have changed in
+ * m->handler.pfds while we were asleep is the .events
+ * field in a given pollfd. Barring thread_cancel() that
+ * value should be a superset of the values we have in our
+ * copy, so there's no need to update it. Similarily,
+ * barring deletion, the fd should still be a valid index
+ * into the master's pfds.
+ *
+ * We are including POLLERR here to do a READ event
+ * this is because the read should fail and the
+ * read function should handle it appropriately
+ */
+ if (pfds[i].revents & (POLLIN | POLLHUP | POLLERR)) {
+ thread_process_io_helper(m, m->read[pfds[i].fd], POLLIN,
+ pfds[i].revents, i);
+ }
+ if (pfds[i].revents & POLLOUT)
+ thread_process_io_helper(m, m->write[pfds[i].fd],
+ POLLOUT, pfds[i].revents, i);
+
+ /* if one of our file descriptors is garbage, remove the same
+ * from
+ * both pfds + update sizes and index */
+ if (pfds[i].revents & POLLNVAL) {
+ memmove(m->handler.pfds + i, m->handler.pfds + i + 1,
+ (m->handler.pfdcount - i - 1)
+ * sizeof(struct pollfd));
+ m->handler.pfdcount--;
+ m->handler.pfds[m->handler.pfdcount].fd = 0;
+ m->handler.pfds[m->handler.pfdcount].events = 0;
+
+ memmove(pfds + i, pfds + i + 1,
+ (m->handler.copycount - i - 1)
+ * sizeof(struct pollfd));
+ m->handler.copycount--;
+ m->handler.copy[m->handler.copycount].fd = 0;
+ m->handler.copy[m->handler.copycount].events = 0;
+
+ i--;
+ }
+ }
+}
+
+/* Add all timers that have popped to the ready list. */
+static unsigned int thread_process_timers(struct thread_master *m,
+ struct timeval *timenow)
+{
+ struct timeval prev = *timenow;
+ bool displayed = false;
+ struct thread *thread;
+ unsigned int ready = 0;
+
+ while ((thread = thread_timer_list_first(&m->timer))) {
+ if (timercmp(timenow, &thread->u.sands, <))
+ break;
+ prev = thread->u.sands;
+ prev.tv_sec += 4;
+ /*
+ * If the timer would have popped 4 seconds in the
+ * past then we are in a situation where we are
+ * really getting behind on handling of events.
+ * Let's log it and do the right thing with it.
+ */
+ if (timercmp(timenow, &prev, >)) {
+ atomic_fetch_add_explicit(
+ &thread->hist->total_starv_warn, 1,
+ memory_order_seq_cst);
+ if (!displayed && !thread->ignore_timer_late) {
+ flog_warn(
+ EC_LIB_STARVE_THREAD,
+ "Thread Starvation: %pTHD was scheduled to pop greater than 4s ago",
+ thread);
+ displayed = true;
+ }
+ }
+
+ thread_timer_list_pop(&m->timer);
+ thread->type = THREAD_READY;
+ thread_list_add_tail(&m->ready, thread);
+ ready++;
+ }
+
+ return ready;
+}
+
+/* process a list en masse, e.g. for event thread lists */
+static unsigned int thread_process(struct thread_list_head *list)
+{
+ struct thread *thread;
+ unsigned int ready = 0;
+
+ while ((thread = thread_list_pop(list))) {
+ thread->type = THREAD_READY;
+ thread_list_add_tail(&thread->master->ready, thread);
+ ready++;
+ }
+ return ready;
+}
+
+
+/* Fetch next ready thread. */
+struct thread *thread_fetch(struct thread_master *m, struct thread *fetch)
+{
+ struct thread *thread = NULL;
+ struct timeval now;
+ struct timeval zerotime = {0, 0};
+ struct timeval tv;
+ struct timeval *tw = NULL;
+ bool eintr_p = false;
+ int num = 0;
+
+ do {
+ /* Handle signals if any */
+ if (m->handle_signals)
+ frr_sigevent_process();
+
+ pthread_mutex_lock(&m->mtx);
+
+ /* Process any pending cancellation requests */
+ do_thread_cancel(m);
+
+ /*
+ * Attempt to flush ready queue before going into poll().
+ * This is performance-critical. Think twice before modifying.
+ */
+ if ((thread = thread_list_pop(&m->ready))) {
+ fetch = thread_run(m, thread, fetch);
+ if (fetch->ref)
+ *fetch->ref = NULL;
+ pthread_mutex_unlock(&m->mtx);
+ if (!m->ready_run_loop)
+ GETRUSAGE(&m->last_getrusage);
+ m->ready_run_loop = true;
+ break;
+ }
+
+ m->ready_run_loop = false;
+ /* otherwise, tick through scheduling sequence */
+
+ /*
+ * Post events to ready queue. This must come before the
+ * following block since events should occur immediately
+ */
+ thread_process(&m->event);
+
+ /*
+ * If there are no tasks on the ready queue, we will poll()
+ * until a timer expires or we receive I/O, whichever comes
+ * first. The strategy for doing this is:
+ *
+ * - If there are events pending, set the poll() timeout to zero
+ * - If there are no events pending, but there are timers
+ * pending, set the timeout to the smallest remaining time on
+ * any timer.
+ * - If there are neither timers nor events pending, but there
+ * are file descriptors pending, block indefinitely in poll()
+ * - If nothing is pending, it's time for the application to die
+ *
+ * In every case except the last, we need to hit poll() at least
+ * once per loop to avoid starvation by events
+ */
+ if (!thread_list_count(&m->ready))
+ tw = thread_timer_wait(&m->timer, &tv);
+
+ if (thread_list_count(&m->ready) ||
+ (tw && !timercmp(tw, &zerotime, >)))
+ tw = &zerotime;
+
+ if (!tw && m->handler.pfdcount == 0) { /* die */
+ pthread_mutex_unlock(&m->mtx);
+ fetch = NULL;
+ break;
+ }
+
+ /*
+ * Copy pollfd array + # active pollfds in it. Not necessary to
+ * copy the array size as this is fixed.
+ */
+ m->handler.copycount = m->handler.pfdcount;
+ memcpy(m->handler.copy, m->handler.pfds,
+ m->handler.copycount * sizeof(struct pollfd));
+
+ pthread_mutex_unlock(&m->mtx);
+ {
+ eintr_p = false;
+ num = fd_poll(m, tw, &eintr_p);
+ }
+ pthread_mutex_lock(&m->mtx);
+
+ /* Handle any errors received in poll() */
+ if (num < 0) {
+ if (eintr_p) {
+ pthread_mutex_unlock(&m->mtx);
+ /* loop around to signal handler */
+ continue;
+ }
+
+ /* else die */
+ flog_err(EC_LIB_SYSTEM_CALL, "poll() error: %s",
+ safe_strerror(errno));
+ pthread_mutex_unlock(&m->mtx);
+ fetch = NULL;
+ break;
+ }
+
+ /* Post timers to ready queue. */
+ monotime(&now);
+ thread_process_timers(m, &now);
+
+ /* Post I/O to ready queue. */
+ if (num > 0)
+ thread_process_io(m, num);
+
+ pthread_mutex_unlock(&m->mtx);
+
+ } while (!thread && m->spin);
+
+ return fetch;
+}
+
+static unsigned long timeval_elapsed(struct timeval a, struct timeval b)
+{
+ return (((a.tv_sec - b.tv_sec) * TIMER_SECOND_MICRO)
+ + (a.tv_usec - b.tv_usec));
+}
+
+unsigned long thread_consumed_time(RUSAGE_T *now, RUSAGE_T *start,
+ unsigned long *cputime)
+{
+#ifdef HAVE_CLOCK_THREAD_CPUTIME_ID
+
+#ifdef __FreeBSD__
+ /*
+ * FreeBSD appears to have an issue when calling clock_gettime
+ * with CLOCK_THREAD_CPUTIME_ID really close to each other
+ * occassionally the now time will be before the start time.
+ * This is not good and FRR is ending up with CPU HOG's
+ * when the subtraction wraps to very large numbers
+ *
+ * What we are going to do here is cheat a little bit
+ * and notice that this is a problem and just correct
+ * it so that it is impossible to happen
+ */
+ if (start->cpu.tv_sec == now->cpu.tv_sec &&
+ start->cpu.tv_nsec > now->cpu.tv_nsec)
+ now->cpu.tv_nsec = start->cpu.tv_nsec + 1;
+ else if (start->cpu.tv_sec > now->cpu.tv_sec) {
+ now->cpu.tv_sec = start->cpu.tv_sec;
+ now->cpu.tv_nsec = start->cpu.tv_nsec + 1;
+ }
+#endif
+ *cputime = (now->cpu.tv_sec - start->cpu.tv_sec) * TIMER_SECOND_MICRO
+ + (now->cpu.tv_nsec - start->cpu.tv_nsec) / 1000;
+#else
+ /* This is 'user + sys' time. */
+ *cputime = timeval_elapsed(now->cpu.ru_utime, start->cpu.ru_utime)
+ + timeval_elapsed(now->cpu.ru_stime, start->cpu.ru_stime);
+#endif
+ return timeval_elapsed(now->real, start->real);
+}
+
+/* We should aim to yield after yield milliseconds, which defaults
+ to THREAD_YIELD_TIME_SLOT .
+ Note: we are using real (wall clock) time for this calculation.
+ It could be argued that CPU time may make more sense in certain
+ contexts. The things to consider are whether the thread may have
+ blocked (in which case wall time increases, but CPU time does not),
+ or whether the system is heavily loaded with other processes competing
+ for CPU time. On balance, wall clock time seems to make sense.
+ Plus it has the added benefit that gettimeofday should be faster
+ than calling getrusage. */
+int thread_should_yield(struct thread *thread)
+{
+ int result;
+ frr_with_mutex (&thread->mtx) {
+ result = monotime_since(&thread->real, NULL)
+ > (int64_t)thread->yield;
+ }
+ return result;
+}
+
+void thread_set_yield_time(struct thread *thread, unsigned long yield_time)
+{
+ frr_with_mutex (&thread->mtx) {
+ thread->yield = yield_time;
+ }
+}
+
+void thread_getrusage(RUSAGE_T *r)
+{
+ monotime(&r->real);
+ if (!cputime_enabled) {
+ memset(&r->cpu, 0, sizeof(r->cpu));
+ return;
+ }
+
+#ifdef HAVE_CLOCK_THREAD_CPUTIME_ID
+ /* not currently implemented in Linux's vDSO, but maybe at some point
+ * in the future?
+ */
+ clock_gettime(CLOCK_THREAD_CPUTIME_ID, &r->cpu);
+#else /* !HAVE_CLOCK_THREAD_CPUTIME_ID */
+#if defined RUSAGE_THREAD
+#define FRR_RUSAGE RUSAGE_THREAD
+#else
+#define FRR_RUSAGE RUSAGE_SELF
+#endif
+ getrusage(FRR_RUSAGE, &(r->cpu));
+#endif
+}
+
+/*
+ * Call a thread.
+ *
+ * This function will atomically update the thread's usage history. At present
+ * this is the only spot where usage history is written. Nevertheless the code
+ * has been written such that the introduction of writers in the future should
+ * not need to update it provided the writers atomically perform only the
+ * operations done here, i.e. updating the total and maximum times. In
+ * particular, the maximum real and cpu times must be monotonically increasing
+ * or this code is not correct.
+ */
+void thread_call(struct thread *thread)
+{
+ RUSAGE_T before, after;
+
+ /* if the thread being called is the CLI, it may change cputime_enabled
+ * ("service cputime-stats" command), which can result in nonsensical
+ * and very confusing warnings
+ */
+ bool cputime_enabled_here = cputime_enabled;
+
+ if (thread->master->ready_run_loop)
+ before = thread->master->last_getrusage;
+ else
+ GETRUSAGE(&before);
+
+ thread->real = before.real;
+
+ frrtrace(9, frr_libfrr, thread_call, thread->master,
+ thread->xref->funcname, thread->xref->xref.file,
+ thread->xref->xref.line, NULL, thread->u.fd,
+ thread->u.val, thread->arg, thread->u.sands.tv_sec);
+
+ pthread_setspecific(thread_current, thread);
+ (*thread->func)(thread);
+ pthread_setspecific(thread_current, NULL);
+
+ GETRUSAGE(&after);
+ thread->master->last_getrusage = after;
+
+ unsigned long walltime, cputime;
+ unsigned long exp;
+
+ walltime = thread_consumed_time(&after, &before, &cputime);
+
+ /* update walltime */
+ atomic_fetch_add_explicit(&thread->hist->real.total, walltime,
+ memory_order_seq_cst);
+ exp = atomic_load_explicit(&thread->hist->real.max,
+ memory_order_seq_cst);
+ while (exp < walltime
+ && !atomic_compare_exchange_weak_explicit(
+ &thread->hist->real.max, &exp, walltime,
+ memory_order_seq_cst, memory_order_seq_cst))
+ ;
+
+ if (cputime_enabled_here && cputime_enabled) {
+ /* update cputime */
+ atomic_fetch_add_explicit(&thread->hist->cpu.total, cputime,
+ memory_order_seq_cst);
+ exp = atomic_load_explicit(&thread->hist->cpu.max,
+ memory_order_seq_cst);
+ while (exp < cputime
+ && !atomic_compare_exchange_weak_explicit(
+ &thread->hist->cpu.max, &exp, cputime,
+ memory_order_seq_cst, memory_order_seq_cst))
+ ;
+ }
+
+ atomic_fetch_add_explicit(&thread->hist->total_calls, 1,
+ memory_order_seq_cst);
+ atomic_fetch_or_explicit(&thread->hist->types, 1 << thread->add_type,
+ memory_order_seq_cst);
+
+ if (cputime_enabled_here && cputime_enabled && cputime_threshold
+ && cputime > cputime_threshold) {
+ /*
+ * We have a CPU Hog on our hands. The time FRR has spent
+ * doing actual work (not sleeping) is greater than 5 seconds.
+ * Whinge about it now, so we're aware this is yet another task
+ * to fix.
+ */
+ atomic_fetch_add_explicit(&thread->hist->total_cpu_warn,
+ 1, memory_order_seq_cst);
+ flog_warn(
+ EC_LIB_SLOW_THREAD_CPU,
+ "CPU HOG: task %s (%lx) ran for %lums (cpu time %lums)",
+ thread->xref->funcname, (unsigned long)thread->func,
+ walltime / 1000, cputime / 1000);
+
+ } else if (walltime_threshold && walltime > walltime_threshold) {
+ /*
+ * The runtime for a task is greater than 5 seconds, but the
+ * cpu time is under 5 seconds. Let's whine about this because
+ * this could imply some sort of scheduling issue.
+ */
+ atomic_fetch_add_explicit(&thread->hist->total_wall_warn,
+ 1, memory_order_seq_cst);
+ flog_warn(
+ EC_LIB_SLOW_THREAD_WALL,
+ "STARVATION: task %s (%lx) ran for %lums (cpu time %lums)",
+ thread->xref->funcname, (unsigned long)thread->func,
+ walltime / 1000, cputime / 1000);
+ }
+}
+
+/* Execute thread */
+void _thread_execute(const struct xref_threadsched *xref,
+ struct thread_master *m, void (*func)(struct thread *),
+ void *arg, int val)
+{
+ struct thread *thread;
+
+ /* Get or allocate new thread to execute. */
+ frr_with_mutex (&m->mtx) {
+ thread = thread_get(m, THREAD_EVENT, func, arg, xref);
+
+ /* Set its event value. */
+ frr_with_mutex (&thread->mtx) {
+ thread->add_type = THREAD_EXECUTE;
+ thread->u.val = val;
+ thread->ref = &thread;
+ }
+ }
+
+ /* Execute thread doing all accounting. */
+ thread_call(thread);
+
+ /* Give back or free thread. */
+ thread_add_unuse(m, thread);
+}
+
+/* Debug signal mask - if 'sigs' is NULL, use current effective mask. */
+void debug_signals(const sigset_t *sigs)
+{
+ int i, found;
+ sigset_t tmpsigs;
+ char buf[300];
+
+ /*
+ * We're only looking at the non-realtime signals here, so we need
+ * some limit value. Platform differences mean at some point we just
+ * need to pick a reasonable value.
+ */
+#if defined SIGRTMIN
+# define LAST_SIGNAL SIGRTMIN
+#else
+# define LAST_SIGNAL 32
+#endif
+
+
+ if (sigs == NULL) {
+ sigemptyset(&tmpsigs);
+ pthread_sigmask(SIG_BLOCK, NULL, &tmpsigs);
+ sigs = &tmpsigs;
+ }
+
+ found = 0;
+ buf[0] = '\0';
+
+ for (i = 0; i < LAST_SIGNAL; i++) {
+ char tmp[20];
+
+ if (sigismember(sigs, i) > 0) {
+ if (found > 0)
+ strlcat(buf, ",", sizeof(buf));
+ snprintf(tmp, sizeof(tmp), "%d", i);
+ strlcat(buf, tmp, sizeof(buf));
+ found++;
+ }
+ }
+
+ if (found == 0)
+ snprintf(buf, sizeof(buf), "<none>");
+
+ zlog_debug("%s: %s", __func__, buf);
+}
+
+static ssize_t printfrr_thread_dbg(struct fbuf *buf, struct printfrr_eargs *ea,
+ const struct thread *thread)
+{
+ static const char * const types[] = {
+ [THREAD_READ] = "read",
+ [THREAD_WRITE] = "write",
+ [THREAD_TIMER] = "timer",
+ [THREAD_EVENT] = "event",
+ [THREAD_READY] = "ready",
+ [THREAD_UNUSED] = "unused",
+ [THREAD_EXECUTE] = "exec",
+ };
+ ssize_t rv = 0;
+ char info[16] = "";
+
+ if (!thread)
+ return bputs(buf, "{(thread *)NULL}");
+
+ rv += bprintfrr(buf, "{(thread *)%p arg=%p", thread, thread->arg);
+
+ if (thread->type < array_size(types) && types[thread->type])
+ rv += bprintfrr(buf, " %-6s", types[thread->type]);
+ else
+ rv += bprintfrr(buf, " INVALID(%u)", thread->type);
+
+ switch (thread->type) {
+ case THREAD_READ:
+ case THREAD_WRITE:
+ snprintfrr(info, sizeof(info), "fd=%d", thread->u.fd);
+ break;
+
+ case THREAD_TIMER:
+ snprintfrr(info, sizeof(info), "r=%pTVMud", &thread->u.sands);
+ break;
+ }
+
+ rv += bprintfrr(buf, " %-12s %s() %s from %s:%d}", info,
+ thread->xref->funcname, thread->xref->dest,
+ thread->xref->xref.file, thread->xref->xref.line);
+ return rv;
+}
+
+printfrr_ext_autoreg_p("TH", printfrr_thread);
+static ssize_t printfrr_thread(struct fbuf *buf, struct printfrr_eargs *ea,
+ const void *ptr)
+{
+ const struct thread *thread = ptr;
+ struct timespec remain = {};
+
+ if (ea->fmt[0] == 'D') {
+ ea->fmt++;
+ return printfrr_thread_dbg(buf, ea, thread);
+ }
+
+ if (!thread) {
+ /* need to jump over time formatting flag characters in the
+ * input format string, i.e. adjust ea->fmt!
+ */
+ printfrr_time(buf, ea, &remain,
+ TIMEFMT_TIMER_DEADLINE | TIMEFMT_SKIP);
+ return bputch(buf, '-');
+ }
+
+ TIMEVAL_TO_TIMESPEC(&thread->u.sands, &remain);
+ return printfrr_time(buf, ea, &remain, TIMEFMT_TIMER_DEADLINE);
+}
diff --git a/lib/thread.h b/lib/thread.h
new file mode 100644
index 0000000..acdcec5
--- /dev/null
+++ b/lib/thread.h
@@ -0,0 +1,306 @@
+/* Thread management routine header.
+ * Copyright (C) 1998 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 _ZEBRA_THREAD_H
+#define _ZEBRA_THREAD_H
+
+#include <zebra.h>
+#include <pthread.h>
+#include <poll.h>
+#include "monotime.h"
+#include "frratomic.h"
+#include "typesafe.h"
+#include "xref.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern bool cputime_enabled;
+extern unsigned long cputime_threshold;
+/* capturing wallclock time is always enabled since it is fast (reading
+ * hardware TSC w/o syscalls)
+ */
+extern unsigned long walltime_threshold;
+
+struct rusage_t {
+#ifdef HAVE_CLOCK_THREAD_CPUTIME_ID
+ struct timespec cpu;
+#else
+ struct rusage cpu;
+#endif
+ struct timeval real;
+};
+#define RUSAGE_T struct rusage_t
+
+#define GETRUSAGE(X) thread_getrusage(X)
+
+PREDECL_LIST(thread_list);
+PREDECL_HEAP(thread_timer_list);
+
+struct fd_handler {
+ /* number of pfd that fit in the allocated space of pfds. This is a
+ * constant and is the same for both pfds and copy.
+ */
+ nfds_t pfdsize;
+
+ /* file descriptors to monitor for i/o */
+ struct pollfd *pfds;
+ /* number of pollfds stored in pfds */
+ nfds_t pfdcount;
+
+ /* chunk used for temp copy of pollfds */
+ struct pollfd *copy;
+ /* number of pollfds stored in copy */
+ nfds_t copycount;
+};
+
+struct xref_threadsched {
+ struct xref xref;
+
+ const char *funcname;
+ const char *dest;
+ uint32_t thread_type;
+};
+
+/* Master of the theads. */
+struct thread_master {
+ char *name;
+
+ struct thread **read;
+ struct thread **write;
+ struct thread_timer_list_head timer;
+ struct thread_list_head event, ready, unuse;
+ struct list *cancel_req;
+ bool canceled;
+ pthread_cond_t cancel_cond;
+ struct hash *cpu_record;
+ int io_pipe[2];
+ int fd_limit;
+ struct fd_handler handler;
+ unsigned long alloc;
+ long selectpoll_timeout;
+ bool spin;
+ bool handle_signals;
+ pthread_mutex_t mtx;
+ pthread_t owner;
+
+ bool ready_run_loop;
+ RUSAGE_T last_getrusage;
+};
+
+/* Thread itself. */
+struct thread {
+ uint8_t type; /* thread type */
+ uint8_t add_type; /* thread type */
+ struct thread_list_item threaditem;
+ struct thread_timer_list_item timeritem;
+ struct thread **ref; /* external reference (if given) */
+ struct thread_master *master; /* pointer to the struct thread_master */
+ void (*func)(struct thread *); /* event function */
+ void *arg; /* event argument */
+ union {
+ int val; /* second argument of the event. */
+ int fd; /* file descriptor in case of r/w */
+ struct timeval sands; /* rest of time sands value. */
+ } u;
+ struct timeval real;
+ struct cpu_thread_history *hist; /* cache pointer to cpu_history */
+ unsigned long yield; /* yield time in microseconds */
+ const struct xref_threadsched *xref; /* origin location */
+ pthread_mutex_t mtx; /* mutex for thread.c functions */
+ bool ignore_timer_late;
+};
+
+#ifdef _FRR_ATTRIBUTE_PRINTFRR
+#pragma FRR printfrr_ext "%pTH" (struct thread *)
+#endif
+
+struct cpu_thread_history {
+ void (*func)(struct thread *);
+ atomic_size_t total_cpu_warn;
+ atomic_size_t total_wall_warn;
+ atomic_size_t total_starv_warn;
+ atomic_size_t total_calls;
+ atomic_size_t total_active;
+ struct time_stats {
+ atomic_size_t total, max;
+ } real;
+ struct time_stats cpu;
+ atomic_uint_fast32_t types;
+ const char *funcname;
+};
+
+/* Struct timeval's tv_usec one second value. */
+#define TIMER_SECOND_MICRO 1000000L
+
+/* Thread types. */
+#define THREAD_READ 0
+#define THREAD_WRITE 1
+#define THREAD_TIMER 2
+#define THREAD_EVENT 3
+#define THREAD_READY 4
+#define THREAD_UNUSED 5
+#define THREAD_EXECUTE 6
+
+/* Thread yield time. */
+#define THREAD_YIELD_TIME_SLOT 10 * 1000L /* 10ms */
+
+#define THREAD_TIMER_STRLEN 12
+
+/* Macros. */
+#define THREAD_ARG(X) ((X)->arg)
+#define THREAD_FD(X) ((X)->u.fd)
+#define THREAD_VAL(X) ((X)->u.val)
+
+/*
+ * Please consider this macro deprecated, and do not use it in new code.
+ */
+#define THREAD_OFF(thread) \
+ do { \
+ if ((thread)) \
+ thread_cancel(&(thread)); \
+ } while (0)
+
+/*
+ * Macro wrappers to generate xrefs for all thread add calls. Includes
+ * file/line/function info for debugging/tracing.
+ */
+#include "lib/xref.h"
+
+#define _xref_t_a(addfn, type, m, f, a, v, t) \
+ ({ \
+ static const struct xref_threadsched _xref \
+ __attribute__((used)) = { \
+ .xref = XREF_INIT(XREFT_THREADSCHED, NULL, __func__), \
+ .funcname = #f, \
+ .dest = #t, \
+ .thread_type = THREAD_ ## type, \
+ }; \
+ XREF_LINK(_xref.xref); \
+ _thread_add_ ## addfn(&_xref, m, f, a, v, t); \
+ }) \
+ /* end */
+
+#define thread_add_read(m,f,a,v,t) _xref_t_a(read_write, READ, m,f,a,v,t)
+#define thread_add_write(m,f,a,v,t) _xref_t_a(read_write, WRITE, m,f,a,v,t)
+#define thread_add_timer(m,f,a,v,t) _xref_t_a(timer, TIMER, m,f,a,v,t)
+#define thread_add_timer_msec(m,f,a,v,t) _xref_t_a(timer_msec, TIMER, m,f,a,v,t)
+#define thread_add_timer_tv(m,f,a,v,t) _xref_t_a(timer_tv, TIMER, m,f,a,v,t)
+#define thread_add_event(m,f,a,v,t) _xref_t_a(event, EVENT, m,f,a,v,t)
+
+#define thread_execute(m,f,a,v) \
+ ({ \
+ static const struct xref_threadsched _xref \
+ __attribute__((used)) = { \
+ .xref = XREF_INIT(XREFT_THREADSCHED, NULL, __func__), \
+ .funcname = #f, \
+ .dest = NULL, \
+ .thread_type = THREAD_EXECUTE, \
+ }; \
+ XREF_LINK(_xref.xref); \
+ _thread_execute(&_xref, m, f, a, v); \
+ }) /* end */
+
+/* Prototypes. */
+extern struct thread_master *thread_master_create(const char *);
+void thread_master_set_name(struct thread_master *master, const char *name);
+extern void thread_master_free(struct thread_master *);
+extern void thread_master_free_unused(struct thread_master *);
+
+extern void _thread_add_read_write(const struct xref_threadsched *xref,
+ struct thread_master *master,
+ void (*fn)(struct thread *), void *arg,
+ int fd, struct thread **tref);
+
+extern void _thread_add_timer(const struct xref_threadsched *xref,
+ struct thread_master *master,
+ void (*fn)(struct thread *), void *arg, long t,
+ struct thread **tref);
+
+extern void _thread_add_timer_msec(const struct xref_threadsched *xref,
+ struct thread_master *master,
+ void (*fn)(struct thread *), void *arg,
+ long t, struct thread **tref);
+
+extern void _thread_add_timer_tv(const struct xref_threadsched *xref,
+ struct thread_master *master,
+ void (*fn)(struct thread *), void *arg,
+ struct timeval *tv, struct thread **tref);
+
+extern void _thread_add_event(const struct xref_threadsched *xref,
+ struct thread_master *master,
+ void (*fn)(struct thread *), void *arg, int val,
+ struct thread **tref);
+
+extern void _thread_execute(const struct xref_threadsched *xref,
+ struct thread_master *master,
+ void (*fn)(struct thread *), void *arg, int val);
+
+extern void thread_cancel(struct thread **event);
+extern void thread_cancel_async(struct thread_master *, struct thread **,
+ void *);
+/* Cancel ready tasks with an arg matching 'arg' */
+extern void thread_cancel_event_ready(struct thread_master *m, void *arg);
+/* Cancel all tasks with an arg matching 'arg', including timers and io */
+extern void thread_cancel_event(struct thread_master *m, void *arg);
+extern struct thread *thread_fetch(struct thread_master *, struct thread *);
+extern void thread_call(struct thread *);
+extern unsigned long thread_timer_remain_second(struct thread *);
+extern struct timeval thread_timer_remain(struct thread *);
+extern unsigned long thread_timer_remain_msec(struct thread *);
+extern int thread_should_yield(struct thread *);
+/* set yield time for thread */
+extern void thread_set_yield_time(struct thread *, unsigned long);
+
+/* Internal libfrr exports */
+extern void thread_getrusage(RUSAGE_T *);
+extern void thread_cmd_init(void);
+
+/* Returns elapsed real (wall clock) time. */
+extern unsigned long thread_consumed_time(RUSAGE_T *after, RUSAGE_T *before,
+ unsigned long *cpu_time_elapsed);
+
+/* only for use in logging functions! */
+extern pthread_key_t thread_current;
+extern char *thread_timer_to_hhmmss(char *buf, int buf_size,
+ struct thread *t_timer);
+
+static inline bool thread_is_scheduled(struct thread *thread)
+{
+ if (thread)
+ return true;
+
+ return false;
+}
+
+/* Debug signal mask */
+void debug_signals(const sigset_t *sigs);
+
+static inline void thread_ignore_late_timer(struct thread *thread)
+{
+ thread->ignore_timer_late = true;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ZEBRA_THREAD_H */
diff --git a/lib/trace.h b/lib/trace.h
new file mode 100644
index 0000000..73fc10a
--- /dev/null
+++ b/lib/trace.h
@@ -0,0 +1,80 @@
+/* Tracing macros
+ *
+ * Wraps tracepoint macros for different tracing systems to allow switching
+ * between them at compile time.
+ *
+ * This should not be included directly by source files wishing to provide
+ * tracepoints. Instead, write a header that defines LTTng tracepoints and
+ * which includes this header, and include your new header in your source. USDT
+ * probes do not need tracepoint definitions, but are less capable than LTTng
+ * tracepoints.
+ *
+ * Copyright (C) 2020 NVIDIA Corporation
+ * Quentin Young
+ *
+ * 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 _TRACE_H_
+#define _TRACE_H_
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+/*
+ * Provided here:
+ * - frrtrace(n, provider, name, ...args...)
+ * - frrtrace_enabled(provider, name)
+ * - frrtracelog(level, msg, ...)
+ *
+ * Use frrtrace() to define tracepoints. n is the number of arguments; this is
+ * needed because USDT probe definitions use DTRACE_PROBEn macros, so the
+ * number of args must be passed in order to expand the correct macro.
+ *
+ * frrtrace_enabled() maps to tracepoint_enabled() under LTTng and is always
+ * true when using USDT. In the future it could be mapped to USDT semaphores
+ * but this is not implemented at present.
+ *
+ * frrtracelog() maps to tracelog() under LTTng and should only be used in zlog
+ * core code, to propagate zlog messages to LTTng. It expands to nothing
+ * otherwise.
+ */
+
+#if defined(HAVE_LTTNG)
+
+#define frrtrace(nargs, provider, name, ...) \
+ tracepoint(provider, name, ## __VA_ARGS__)
+#define frrtrace_enabled(...) tracepoint_enabled(__VA_ARGS__)
+#define frrtracelog(...) tracelog(__VA_ARGS__)
+
+#elif defined(HAVE_USDT)
+
+#include "sys/sdt.h"
+
+#define frrtrace(nargs, provider, name, ...) \
+ DTRACE_PROBE##nargs(provider, name, ## __VA_ARGS__)
+#define frrtrace_enabled(...) true
+#define frrtracelog(...)
+
+#else
+
+#define frrtrace(nargs, provider, name, ...) (void)0
+#define frrtrace_enabled(...) false
+#define frrtracelog(...) (void)0
+
+#endif
+
+#endif /* _TRACE_H_ */
diff --git a/lib/typerb.c b/lib/typerb.c
new file mode 100644
index 0000000..fe142ff
--- /dev/null
+++ b/lib/typerb.c
@@ -0,0 +1,525 @@
+/* RB-tree */
+
+/*
+ * Copyright 2002 Niels Provos <provos@citi.umich.edu>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 2016 David Gwynne <dlg@openbsd.org>
+ *
+ * Permission to use, copy, modify, and 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+#include "typerb.h"
+
+#define RB_BLACK 0
+#define RB_RED 1
+
+#define rb_entry typed_rb_entry
+#define rbt_tree typed_rb_root
+
+#define RBE_LEFT(_rbe) (_rbe)->rbt_left
+#define RBE_RIGHT(_rbe) (_rbe)->rbt_right
+#define RBE_PARENT(_rbe) (_rbe)->rbt_parent
+#define RBE_COLOR(_rbe) (_rbe)->rbt_color
+
+#define RBH_ROOT(_rbt) (_rbt)->rbt_root
+
+static inline void rbe_set(struct rb_entry *rbe, struct rb_entry *parent)
+{
+ RBE_PARENT(rbe) = parent;
+ RBE_LEFT(rbe) = RBE_RIGHT(rbe) = NULL;
+ RBE_COLOR(rbe) = RB_RED;
+}
+
+static inline void rbe_set_blackred(struct rb_entry *black,
+ struct rb_entry *red)
+{
+ RBE_COLOR(black) = RB_BLACK;
+ RBE_COLOR(red) = RB_RED;
+}
+
+static inline void rbe_rotate_left(struct rbt_tree *rbt, struct rb_entry *rbe)
+{
+ struct rb_entry *parent;
+ struct rb_entry *tmp;
+
+ tmp = RBE_RIGHT(rbe);
+ RBE_RIGHT(rbe) = RBE_LEFT(tmp);
+ if (RBE_RIGHT(rbe) != NULL)
+ RBE_PARENT(RBE_LEFT(tmp)) = rbe;
+
+ parent = RBE_PARENT(rbe);
+ RBE_PARENT(tmp) = parent;
+ if (parent != NULL) {
+ if (rbe == RBE_LEFT(parent))
+ RBE_LEFT(parent) = tmp;
+ else
+ RBE_RIGHT(parent) = tmp;
+ } else
+ RBH_ROOT(rbt) = tmp;
+
+ RBE_LEFT(tmp) = rbe;
+ RBE_PARENT(rbe) = tmp;
+}
+
+static inline void rbe_rotate_right(struct rbt_tree *rbt, struct rb_entry *rbe)
+{
+ struct rb_entry *parent;
+ struct rb_entry *tmp;
+
+ tmp = RBE_LEFT(rbe);
+ RBE_LEFT(rbe) = RBE_RIGHT(tmp);
+ if (RBE_LEFT(rbe) != NULL)
+ RBE_PARENT(RBE_RIGHT(tmp)) = rbe;
+
+ parent = RBE_PARENT(rbe);
+ RBE_PARENT(tmp) = parent;
+ if (parent != NULL) {
+ if (rbe == RBE_LEFT(parent))
+ RBE_LEFT(parent) = tmp;
+ else
+ RBE_RIGHT(parent) = tmp;
+ } else
+ RBH_ROOT(rbt) = tmp;
+
+ RBE_RIGHT(tmp) = rbe;
+ RBE_PARENT(rbe) = tmp;
+}
+
+static inline void rbe_insert_color(struct rbt_tree *rbt, struct rb_entry *rbe)
+{
+ struct rb_entry *parent, *gparent, *tmp;
+
+ rbt->count++;
+
+ while ((parent = RBE_PARENT(rbe)) != NULL
+ && RBE_COLOR(parent) == RB_RED) {
+ gparent = RBE_PARENT(parent);
+
+ if (parent == RBE_LEFT(gparent)) {
+ tmp = RBE_RIGHT(gparent);
+ if (tmp != NULL && RBE_COLOR(tmp) == RB_RED) {
+ RBE_COLOR(tmp) = RB_BLACK;
+ rbe_set_blackred(parent, gparent);
+ rbe = gparent;
+ continue;
+ }
+
+ if (RBE_RIGHT(parent) == rbe) {
+ rbe_rotate_left(rbt, parent);
+ tmp = parent;
+ parent = rbe;
+ rbe = tmp;
+ }
+
+ rbe_set_blackred(parent, gparent);
+ rbe_rotate_right(rbt, gparent);
+ } else {
+ tmp = RBE_LEFT(gparent);
+ if (tmp != NULL && RBE_COLOR(tmp) == RB_RED) {
+ RBE_COLOR(tmp) = RB_BLACK;
+ rbe_set_blackred(parent, gparent);
+ rbe = gparent;
+ continue;
+ }
+
+ if (RBE_LEFT(parent) == rbe) {
+ rbe_rotate_right(rbt, parent);
+ tmp = parent;
+ parent = rbe;
+ rbe = tmp;
+ }
+
+ rbe_set_blackred(parent, gparent);
+ rbe_rotate_left(rbt, gparent);
+ }
+ }
+
+ RBE_COLOR(RBH_ROOT(rbt)) = RB_BLACK;
+}
+
+static inline void rbe_remove_color(struct rbt_tree *rbt,
+ struct rb_entry *parent,
+ struct rb_entry *rbe)
+{
+ struct rb_entry *tmp;
+
+ while ((rbe == NULL || RBE_COLOR(rbe) == RB_BLACK)
+ && rbe != RBH_ROOT(rbt) && parent) {
+ if (RBE_LEFT(parent) == rbe) {
+ tmp = RBE_RIGHT(parent);
+ if (RBE_COLOR(tmp) == RB_RED) {
+ rbe_set_blackred(tmp, parent);
+ rbe_rotate_left(rbt, parent);
+ tmp = RBE_RIGHT(parent);
+ }
+ if ((RBE_LEFT(tmp) == NULL
+ || RBE_COLOR(RBE_LEFT(tmp)) == RB_BLACK)
+ && (RBE_RIGHT(tmp) == NULL
+ || RBE_COLOR(RBE_RIGHT(tmp)) == RB_BLACK)) {
+ RBE_COLOR(tmp) = RB_RED;
+ rbe = parent;
+ parent = RBE_PARENT(rbe);
+ } else {
+ if (RBE_RIGHT(tmp) == NULL
+ || RBE_COLOR(RBE_RIGHT(tmp)) == RB_BLACK) {
+ struct rb_entry *oleft;
+
+ oleft = RBE_LEFT(tmp);
+ if (oleft != NULL)
+ RBE_COLOR(oleft) = RB_BLACK;
+
+ RBE_COLOR(tmp) = RB_RED;
+ rbe_rotate_right(rbt, tmp);
+ tmp = RBE_RIGHT(parent);
+ }
+
+ RBE_COLOR(tmp) = RBE_COLOR(parent);
+ RBE_COLOR(parent) = RB_BLACK;
+ if (RBE_RIGHT(tmp))
+ RBE_COLOR(RBE_RIGHT(tmp)) = RB_BLACK;
+
+ rbe_rotate_left(rbt, parent);
+ rbe = RBH_ROOT(rbt);
+ break;
+ }
+ } else {
+ tmp = RBE_LEFT(parent);
+ if (RBE_COLOR(tmp) == RB_RED) {
+ rbe_set_blackred(tmp, parent);
+ rbe_rotate_right(rbt, parent);
+ tmp = RBE_LEFT(parent);
+ }
+
+ if ((RBE_LEFT(tmp) == NULL
+ || RBE_COLOR(RBE_LEFT(tmp)) == RB_BLACK)
+ && (RBE_RIGHT(tmp) == NULL
+ || RBE_COLOR(RBE_RIGHT(tmp)) == RB_BLACK)) {
+ RBE_COLOR(tmp) = RB_RED;
+ rbe = parent;
+ parent = RBE_PARENT(rbe);
+ } else {
+ if (RBE_LEFT(tmp) == NULL
+ || RBE_COLOR(RBE_LEFT(tmp)) == RB_BLACK) {
+ struct rb_entry *oright;
+
+ oright = RBE_RIGHT(tmp);
+ if (oright != NULL)
+ RBE_COLOR(oright) = RB_BLACK;
+
+ RBE_COLOR(tmp) = RB_RED;
+ rbe_rotate_left(rbt, tmp);
+ tmp = RBE_LEFT(parent);
+ }
+
+ RBE_COLOR(tmp) = RBE_COLOR(parent);
+ RBE_COLOR(parent) = RB_BLACK;
+ if (RBE_LEFT(tmp) != NULL)
+ RBE_COLOR(RBE_LEFT(tmp)) = RB_BLACK;
+
+ rbe_rotate_right(rbt, parent);
+ rbe = RBH_ROOT(rbt);
+ break;
+ }
+ }
+ }
+
+ if (rbe != NULL)
+ RBE_COLOR(rbe) = RB_BLACK;
+}
+
+static inline struct rb_entry *
+rbe_remove(struct rbt_tree *rbt, struct rb_entry *rbe)
+{
+ struct rb_entry *child, *parent, *old = rbe;
+ unsigned int color;
+
+ if (RBE_LEFT(rbe) == NULL)
+ child = RBE_RIGHT(rbe);
+ else if (RBE_RIGHT(rbe) == NULL)
+ child = RBE_LEFT(rbe);
+ else {
+ struct rb_entry *tmp;
+
+ rbe = RBE_RIGHT(rbe);
+ while ((tmp = RBE_LEFT(rbe)) != NULL)
+ rbe = tmp;
+
+ child = RBE_RIGHT(rbe);
+ parent = RBE_PARENT(rbe);
+ color = RBE_COLOR(rbe);
+ if (child != NULL)
+ RBE_PARENT(child) = parent;
+ if (parent != NULL) {
+ if (RBE_LEFT(parent) == rbe)
+ RBE_LEFT(parent) = child;
+ else
+ RBE_RIGHT(parent) = child;
+ } else
+ RBH_ROOT(rbt) = child;
+ if (RBE_PARENT(rbe) == old)
+ parent = rbe;
+ *rbe = *old;
+
+ tmp = RBE_PARENT(old);
+ if (tmp != NULL) {
+ if (RBE_LEFT(tmp) == old)
+ RBE_LEFT(tmp) = rbe;
+ else
+ RBE_RIGHT(tmp) = rbe;
+ } else
+ RBH_ROOT(rbt) = rbe;
+
+ RBE_PARENT(RBE_LEFT(old)) = rbe;
+ if (RBE_RIGHT(old))
+ RBE_PARENT(RBE_RIGHT(old)) = rbe;
+
+ goto color;
+ }
+
+ parent = RBE_PARENT(rbe);
+ color = RBE_COLOR(rbe);
+
+ if (child != NULL)
+ RBE_PARENT(child) = parent;
+ if (parent != NULL) {
+ if (RBE_LEFT(parent) == rbe)
+ RBE_LEFT(parent) = child;
+ else
+ RBE_RIGHT(parent) = child;
+ } else
+ RBH_ROOT(rbt) = child;
+color:
+ if (color == RB_BLACK)
+ rbe_remove_color(rbt, parent, child);
+
+ rbt->count--;
+ memset(old, 0, sizeof(*old));
+ return (old);
+}
+
+struct typed_rb_entry *typed_rb_remove(struct rbt_tree *rbt,
+ struct rb_entry *rbe)
+{
+ return rbe_remove(rbt, rbe);
+}
+
+struct typed_rb_entry *typed_rb_insert(struct rbt_tree *rbt,
+ struct rb_entry *rbe, int (*cmpfn)(
+ const struct typed_rb_entry *a,
+ const struct typed_rb_entry *b))
+{
+ struct rb_entry *tmp;
+ struct rb_entry *parent = NULL;
+ int comp = 0;
+
+ tmp = RBH_ROOT(rbt);
+ while (tmp != NULL) {
+ parent = tmp;
+
+ comp = cmpfn(rbe, tmp);
+ if (comp < 0)
+ tmp = RBE_LEFT(tmp);
+ else if (comp > 0)
+ tmp = RBE_RIGHT(tmp);
+ else
+ return tmp;
+ }
+
+ rbe_set(rbe, parent);
+
+ if (parent != NULL) {
+ if (comp < 0)
+ RBE_LEFT(parent) = rbe;
+ else
+ RBE_RIGHT(parent) = rbe;
+ } else
+ RBH_ROOT(rbt) = rbe;
+
+ rbe_insert_color(rbt, rbe);
+
+ return NULL;
+}
+
+/* Finds the node with the same key as elm */
+const struct rb_entry *typed_rb_find(const struct rbt_tree *rbt,
+ const struct rb_entry *key,
+ int (*cmpfn)(
+ const struct typed_rb_entry *a,
+ const struct typed_rb_entry *b))
+{
+ const struct rb_entry *tmp = RBH_ROOT(rbt);
+ int comp;
+
+ while (tmp != NULL) {
+ comp = cmpfn(key, tmp);
+ if (comp < 0)
+ tmp = RBE_LEFT(tmp);
+ else if (comp > 0)
+ tmp = RBE_RIGHT(tmp);
+ else
+ return tmp;
+ }
+
+ return NULL;
+}
+
+const struct rb_entry *typed_rb_find_gteq(const struct rbt_tree *rbt,
+ const struct rb_entry *key,
+ int (*cmpfn)(
+ const struct typed_rb_entry *a,
+ const struct typed_rb_entry *b))
+{
+ const struct rb_entry *tmp = RBH_ROOT(rbt), *best = NULL;
+ int comp;
+
+ while (tmp != NULL) {
+ comp = cmpfn(key, tmp);
+ if (comp < 0) {
+ best = tmp;
+ tmp = RBE_LEFT(tmp);
+ } else if (comp > 0)
+ tmp = RBE_RIGHT(tmp);
+ else
+ return tmp;
+ }
+
+ return best;
+}
+
+const struct rb_entry *typed_rb_find_lt(const struct rbt_tree *rbt,
+ const struct rb_entry *key,
+ int (*cmpfn)(
+ const struct typed_rb_entry *a,
+ const struct typed_rb_entry *b))
+{
+ const struct rb_entry *tmp = RBH_ROOT(rbt), *best = NULL;
+ int comp;
+
+ while (tmp != NULL) {
+ comp = cmpfn(key, tmp);
+ if (comp <= 0)
+ tmp = RBE_LEFT(tmp);
+ else {
+ best = tmp;
+ tmp = RBE_RIGHT(tmp);
+ }
+ }
+
+ return best;
+}
+
+struct rb_entry *typed_rb_next(const struct rb_entry *rbe_const)
+{
+ struct rb_entry *rbe = (struct rb_entry *)rbe_const;
+
+ if (RBE_RIGHT(rbe) != NULL) {
+ rbe = RBE_RIGHT(rbe);
+ while (RBE_LEFT(rbe) != NULL)
+ rbe = RBE_LEFT(rbe);
+ } else {
+ if (RBE_PARENT(rbe) && (rbe == RBE_LEFT(RBE_PARENT(rbe))))
+ rbe = RBE_PARENT(rbe);
+ else {
+ while (RBE_PARENT(rbe)
+ && (rbe == RBE_RIGHT(RBE_PARENT(rbe))))
+ rbe = RBE_PARENT(rbe);
+ rbe = RBE_PARENT(rbe);
+ }
+ }
+
+ return rbe;
+}
+
+struct rb_entry *typed_rb_prev(const struct rb_entry *rbe_const)
+{
+ struct rb_entry *rbe = (struct rb_entry *)rbe_const;
+
+ if (RBE_LEFT(rbe)) {
+ rbe = RBE_LEFT(rbe);
+ while (RBE_RIGHT(rbe))
+ rbe = RBE_RIGHT(rbe);
+ } else {
+ if (RBE_PARENT(rbe) && (rbe == RBE_RIGHT(RBE_PARENT(rbe))))
+ rbe = RBE_PARENT(rbe);
+ else {
+ while (RBE_PARENT(rbe)
+ && (rbe == RBE_LEFT(RBE_PARENT(rbe))))
+ rbe = RBE_PARENT(rbe);
+ rbe = RBE_PARENT(rbe);
+ }
+ }
+
+ return rbe;
+}
+
+struct rb_entry *typed_rb_min(const struct rbt_tree *rbt)
+{
+ struct rb_entry *rbe = RBH_ROOT(rbt);
+ struct rb_entry *parent = NULL;
+
+ while (rbe != NULL) {
+ parent = rbe;
+ rbe = RBE_LEFT(rbe);
+ }
+
+ return parent;
+}
+
+struct rb_entry *typed_rb_max(const struct rbt_tree *rbt)
+{
+ struct rb_entry *rbe = RBH_ROOT(rbt);
+ struct rb_entry *parent = NULL;
+
+ while (rbe != NULL) {
+ parent = rbe;
+ rbe = RBE_RIGHT(rbe);
+ }
+
+ return parent;
+}
+
+bool typed_rb_member(const struct typed_rb_root *rbt,
+ const struct typed_rb_entry *rbe)
+{
+ while (rbe->rbt_parent)
+ rbe = rbe->rbt_parent;
+ return rbe == rbt->rbt_root;
+}
diff --git a/lib/typerb.h b/lib/typerb.h
new file mode 100644
index 0000000..8ac1821
--- /dev/null
+++ b/lib/typerb.h
@@ -0,0 +1,230 @@
+/*
+ * The following Red-Black tree implementation is based off code with
+ * original copyright:
+ *
+ * Copyright (c) 2016 David Gwynne <dlg@openbsd.org>
+ *
+ * Permission to use, copy, modify, and 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_TYPERB_H
+#define _FRR_TYPERB_H
+
+#include <string.h>
+#include "typesafe.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct typed_rb_entry {
+ struct typed_rb_entry *rbt_parent;
+ struct typed_rb_entry *rbt_left;
+ struct typed_rb_entry *rbt_right;
+ unsigned int rbt_color;
+};
+
+struct typed_rb_root {
+ struct typed_rb_entry *rbt_root;
+ size_t count;
+};
+
+struct typed_rb_entry *typed_rb_insert(struct typed_rb_root *rbt,
+ struct typed_rb_entry *rbe,
+ int (*cmpfn)(
+ const struct typed_rb_entry *a,
+ const struct typed_rb_entry *b));
+struct typed_rb_entry *typed_rb_remove(struct typed_rb_root *rbt,
+ struct typed_rb_entry *rbe);
+const struct typed_rb_entry *typed_rb_find(const struct typed_rb_root *rbt,
+ const struct typed_rb_entry *rbe,
+ int (*cmpfn)(
+ const struct typed_rb_entry *a,
+ const struct typed_rb_entry *b));
+const struct typed_rb_entry *typed_rb_find_gteq(const struct typed_rb_root *rbt,
+ const struct typed_rb_entry *rbe,
+ int (*cmpfn)(
+ const struct typed_rb_entry *a,
+ const struct typed_rb_entry *b));
+const struct typed_rb_entry *typed_rb_find_lt(const struct typed_rb_root *rbt,
+ const struct typed_rb_entry *rbe,
+ int (*cmpfn)(
+ const struct typed_rb_entry *a,
+ const struct typed_rb_entry *b));
+struct typed_rb_entry *typed_rb_min(const struct typed_rb_root *rbt);
+struct typed_rb_entry *typed_rb_max(const struct typed_rb_root *rbt);
+struct typed_rb_entry *typed_rb_prev(const struct typed_rb_entry *rbe);
+struct typed_rb_entry *typed_rb_next(const struct typed_rb_entry *rbe);
+bool typed_rb_member(const struct typed_rb_root *rbt,
+ const struct typed_rb_entry *rbe);
+
+#define _PREDECL_RBTREE(prefix) \
+struct prefix ## _head { struct typed_rb_root rr; }; \
+struct prefix ## _item { struct typed_rb_entry re; }; \
+MACRO_REQUIRE_SEMICOLON() /* end */
+
+#define INIT_RBTREE_UNIQ(var) { }
+#define INIT_RBTREE_NONUNIQ(var) { }
+
+#define _DECLARE_RBTREE(prefix, type, field, cmpfn_nuq, cmpfn_uq) \
+ \
+macro_inline void prefix ## _init(struct prefix##_head *h) \
+{ \
+ memset(h, 0, sizeof(*h)); \
+} \
+macro_inline void prefix ## _fini(struct prefix##_head *h) \
+{ \
+ memset(h, 0, sizeof(*h)); \
+} \
+macro_inline type *prefix ## _add(struct prefix##_head *h, type *item) \
+{ \
+ struct typed_rb_entry *re; \
+ re = typed_rb_insert(&h->rr, &item->field.re, cmpfn_uq); \
+ return container_of_null(re, type, field.re); \
+} \
+macro_inline const type *prefix ## _const_find_gteq( \
+ const struct prefix##_head *h, const type *item) \
+{ \
+ const struct typed_rb_entry *re; \
+ re = typed_rb_find_gteq(&h->rr, &item->field.re, cmpfn_nuq); \
+ return container_of_null(re, type, field.re); \
+} \
+macro_inline const type *prefix ## _const_find_lt( \
+ const struct prefix##_head *h, const type *item) \
+{ \
+ const struct typed_rb_entry *re; \
+ re = typed_rb_find_lt(&h->rr, &item->field.re, cmpfn_nuq); \
+ return container_of_null(re, type, field.re); \
+} \
+TYPESAFE_FIND_CMP(prefix, type) \
+macro_inline type *prefix ## _del(struct prefix##_head *h, type *item) \
+{ \
+ struct typed_rb_entry *re; \
+ re = typed_rb_remove(&h->rr, &item->field.re); \
+ return container_of_null(re, type, field.re); \
+} \
+macro_inline type *prefix ## _pop(struct prefix##_head *h) \
+{ \
+ struct typed_rb_entry *re; \
+ re = typed_rb_min(&h->rr); \
+ if (!re) \
+ return NULL; \
+ typed_rb_remove(&h->rr, re); \
+ return container_of(re, type, field.re); \
+} \
+TYPESAFE_SWAP_ALL_SIMPLE(prefix) \
+macro_pure const type *prefix ## _const_first(const struct prefix##_head *h) \
+{ \
+ const struct typed_rb_entry *re; \
+ re = typed_rb_min(&h->rr); \
+ return container_of_null(re, type, field.re); \
+} \
+macro_pure const type *prefix ## _const_next(const struct prefix##_head *h, \
+ const type *item) \
+{ \
+ const struct typed_rb_entry *re; \
+ re = typed_rb_next(&item->field.re); \
+ return container_of_null(re, type, field.re); \
+} \
+TYPESAFE_FIRST_NEXT(prefix, type) \
+macro_pure const type *prefix ## _const_last(const struct prefix##_head *h) \
+{ \
+ const struct typed_rb_entry *re; \
+ re = typed_rb_max(&h->rr); \
+ return container_of_null(re, type, field.re); \
+} \
+macro_pure const type *prefix ## _const_prev(const struct prefix##_head *h, \
+ const type *item) \
+{ \
+ const struct typed_rb_entry *re; \
+ re = typed_rb_prev(&item->field.re); \
+ return container_of_null(re, type, field.re); \
+} \
+TYPESAFE_LAST_PREV(prefix, type) \
+macro_pure type *prefix ## _next_safe(struct prefix##_head *h, type *item) \
+{ \
+ struct typed_rb_entry *re; \
+ re = item ? typed_rb_next(&item->field.re) : NULL; \
+ return container_of_null(re, type, field.re); \
+} \
+macro_pure type *prefix ## _prev_safe(struct prefix##_head *h, type *item) \
+{ \
+ struct typed_rb_entry *re; \
+ re = item ? typed_rb_prev(&item->field.re) : NULL; \
+ return container_of_null(re, type, field.re); \
+} \
+macro_pure size_t prefix ## _count(const struct prefix##_head *h) \
+{ \
+ return h->rr.count; \
+} \
+macro_pure bool prefix ## _member(const struct prefix##_head *h, \
+ const type *item) \
+{ \
+ return typed_rb_member(&h->rr, &item->field.re); \
+} \
+MACRO_REQUIRE_SEMICOLON() /* end */
+
+#define PREDECL_RBTREE_UNIQ(prefix) \
+ _PREDECL_RBTREE(prefix)
+#define DECLARE_RBTREE_UNIQ(prefix, type, field, cmpfn) \
+ \
+macro_inline int prefix ## __cmp(const struct typed_rb_entry *a, \
+ const struct typed_rb_entry *b) \
+{ \
+ return cmpfn(container_of(a, type, field.re), \
+ container_of(b, type, field.re)); \
+} \
+macro_inline const type *prefix ## _const_find(const struct prefix##_head *h, \
+ const type *item) \
+{ \
+ const struct typed_rb_entry *re; \
+ re = typed_rb_find(&h->rr, &item->field.re, &prefix ## __cmp); \
+ return container_of_null(re, type, field.re); \
+} \
+TYPESAFE_FIND(prefix, type) \
+ \
+_DECLARE_RBTREE(prefix, type, field, prefix ## __cmp, prefix ## __cmp); \
+MACRO_REQUIRE_SEMICOLON() /* end */
+
+#define PREDECL_RBTREE_NONUNIQ(prefix) \
+ _PREDECL_RBTREE(prefix)
+#define DECLARE_RBTREE_NONUNIQ(prefix, type, field, cmpfn) \
+ \
+macro_inline int prefix ## __cmp(const struct typed_rb_entry *a, \
+ const struct typed_rb_entry *b) \
+{ \
+ return cmpfn(container_of(a, type, field.re), \
+ container_of(b, type, field.re)); \
+} \
+macro_inline int prefix ## __cmp_uq(const struct typed_rb_entry *a, \
+ const struct typed_rb_entry *b) \
+{ \
+ int cmpval = cmpfn(container_of(a, type, field.re), \
+ container_of(b, type, field.re)); \
+ if (cmpval) \
+ return cmpval; \
+ if (a < b) \
+ return -1; \
+ if (a > b) \
+ return 1; \
+ return 0; \
+} \
+ \
+_DECLARE_RBTREE(prefix, type, field, prefix ## __cmp, prefix ## __cmp_uq); \
+MACRO_REQUIRE_SEMICOLON() /* end */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRR_TYPERB_H */
diff --git a/lib/typesafe.c b/lib/typesafe.c
new file mode 100644
index 0000000..3b65a2d
--- /dev/null
+++ b/lib/typesafe.c
@@ -0,0 +1,611 @@
+/*
+ * Copyright (c) 2019 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "typesafe.h"
+#include "memory.h"
+#include "network.h"
+
+DEFINE_MTYPE_STATIC(LIB, TYPEDHASH_BUCKET, "Typed-hash bucket");
+DEFINE_MTYPE_STATIC(LIB, SKIPLIST_OFLOW, "Skiplist overflow");
+DEFINE_MTYPE_STATIC(LIB, HEAP_ARRAY, "Typed-heap array");
+
+struct slist_item typesafe_slist_sentinel = { NULL };
+
+bool typesafe_list_member(const struct slist_head *head,
+ const struct slist_item *item)
+{
+ struct slist_item *fromhead = head->first;
+ struct slist_item **fromnext = (struct slist_item **)&item->next;
+
+ while (fromhead != _SLIST_LAST) {
+ if (fromhead == item || fromnext == head->last_next)
+ return true;
+
+ fromhead = fromhead->next;
+ if (!*fromnext || *fromnext == _SLIST_LAST)
+ break;
+ fromnext = &(*fromnext)->next;
+ }
+
+ return false;
+}
+
+bool typesafe_dlist_member(const struct dlist_head *head,
+ const struct dlist_item *item)
+{
+ const struct dlist_item *fromhead = head->hitem.next;
+ const struct dlist_item *fromitem = item->next;
+
+ if (!item->prev || !item->next)
+ return false;
+
+ while (fromhead != &head->hitem && fromitem != item) {
+ if (fromitem == &head->hitem || fromhead == item)
+ return true;
+ fromhead = fromhead->next;
+ fromitem = fromitem->next;
+ }
+
+ return false;
+}
+
+#if 0
+static void hash_consistency_check(struct thash_head *head)
+{
+ uint32_t i;
+ struct thash_item *item, *prev;
+
+ for (i = 0; i < HASH_SIZE(*head); i++) {
+ item = head->entries[i];
+ prev = NULL;
+ while (item) {
+ assert(HASH_KEY(*head, item->hashval) == i);
+ assert(!prev || item->hashval >= prev->hashval);
+ prev = item;
+ item = item->next;
+ }
+ }
+}
+#else
+#define hash_consistency_check(x)
+#endif
+
+void typesafe_hash_grow(struct thash_head *head)
+{
+ uint32_t newsize = head->count, i, j;
+ uint8_t newshift, delta;
+
+ hash_consistency_check(head);
+
+ newsize |= newsize >> 1;
+ newsize |= newsize >> 2;
+ newsize |= newsize >> 4;
+ newsize |= newsize >> 8;
+ newsize |= newsize >> 16;
+ newsize++;
+ newshift = __builtin_ctz(newsize) + 1;
+
+ if (head->maxshift && newshift > head->maxshift)
+ newshift = head->maxshift;
+ if (newshift == head->tabshift)
+ return;
+ newsize = _HASH_SIZE(newshift);
+
+ head->entries = XREALLOC(MTYPE_TYPEDHASH_BUCKET, head->entries,
+ sizeof(head->entries[0]) * newsize);
+ memset(head->entries + HASH_SIZE(*head), 0,
+ sizeof(head->entries[0]) *
+ (newsize - HASH_SIZE(*head)));
+
+ delta = newshift - head->tabshift;
+
+ i = HASH_SIZE(*head);
+ if (i == 0)
+ goto out;
+ do {
+ struct thash_item **apos, *item;
+
+ i--;
+ apos = &head->entries[i];
+
+ for (j = 0; j < (1U << delta); j++) {
+ item = *apos;
+ *apos = NULL;
+
+ head->entries[(i << delta) + j] = item;
+ apos = &head->entries[(i << delta) + j];
+
+ while ((item = *apos)) {
+ uint32_t midbits;
+ midbits = _HASH_KEY(newshift, item->hashval);
+ midbits &= (1 << delta) - 1;
+ if (midbits > j)
+ break;
+ apos = &item->next;
+ }
+ }
+ } while (i > 0);
+
+out:
+ head->tabshift = newshift;
+ hash_consistency_check(head);
+}
+
+void typesafe_hash_shrink(struct thash_head *head)
+{
+ uint32_t newsize = head->count, i, j;
+ uint8_t newshift, delta;
+
+ hash_consistency_check(head);
+
+ if (!head->count) {
+ XFREE(MTYPE_TYPEDHASH_BUCKET, head->entries);
+ head->tabshift = 0;
+ return;
+ }
+
+ newsize |= newsize >> 1;
+ newsize |= newsize >> 2;
+ newsize |= newsize >> 4;
+ newsize |= newsize >> 8;
+ newsize |= newsize >> 16;
+ newsize++;
+ newshift = __builtin_ctz(newsize) + 1;
+
+ if (head->minshift && newshift < head->minshift)
+ newshift = head->minshift;
+ if (newshift == head->tabshift)
+ return;
+ newsize = _HASH_SIZE(newshift);
+
+ delta = head->tabshift - newshift;
+
+ for (i = 0; i < newsize; i++) {
+ struct thash_item **apos = &head->entries[i];
+
+ for (j = 0; j < (1U << delta); j++) {
+ *apos = head->entries[(i << delta) + j];
+ while (*apos)
+ apos = &(*apos)->next;
+ }
+ }
+ head->entries = XREALLOC(MTYPE_TYPEDHASH_BUCKET, head->entries,
+ sizeof(head->entries[0]) * newsize);
+ head->tabshift = newshift;
+
+ hash_consistency_check(head);
+}
+
+/* skiplist */
+
+static inline struct sskip_item *sl_level_get(const struct sskip_item *item,
+ size_t level)
+{
+ if (level < SKIPLIST_OVERFLOW)
+ return item->next[level];
+ if (level == SKIPLIST_OVERFLOW && !((uintptr_t)item->next[level] & 1))
+ return item->next[level];
+
+ uintptr_t ptrval = (uintptr_t)item->next[SKIPLIST_OVERFLOW];
+ ptrval &= UINTPTR_MAX - 3;
+ struct sskip_overflow *oflow = (struct sskip_overflow *)ptrval;
+ return oflow->next[level - SKIPLIST_OVERFLOW];
+}
+
+static inline void sl_level_set(struct sskip_item *item, size_t level,
+ struct sskip_item *value)
+{
+ if (level < SKIPLIST_OVERFLOW)
+ item->next[level] = value;
+ else if (level == SKIPLIST_OVERFLOW && !((uintptr_t)item->next[level] & 1))
+ item->next[level] = value;
+ else {
+ uintptr_t ptrval = (uintptr_t)item->next[SKIPLIST_OVERFLOW];
+ ptrval &= UINTPTR_MAX - 3;
+ struct sskip_overflow *oflow = (struct sskip_overflow *)ptrval;
+ oflow->next[level - SKIPLIST_OVERFLOW] = value;
+ }
+}
+
+struct sskip_item *typesafe_skiplist_add(struct sskip_head *head,
+ struct sskip_item *item,
+ int (*cmpfn)(const struct sskip_item *a,
+ const struct sskip_item *b))
+{
+ size_t level = SKIPLIST_MAXDEPTH, newlevel, auxlevel;
+ struct sskip_item *prev = &head->hitem, *next, *auxprev, *auxnext;
+ int cmpval;
+
+ /* level / newlevel are 1-counted here */
+ newlevel = __builtin_ctz(frr_weak_random()) + 1;
+ if (newlevel > SKIPLIST_MAXDEPTH)
+ newlevel = SKIPLIST_MAXDEPTH;
+
+ next = NULL;
+ while (level >= newlevel) {
+ next = sl_level_get(prev, level - 1);
+ if (!next) {
+ level--;
+ continue;
+ }
+ cmpval = cmpfn(next, item);
+ if (cmpval < 0) {
+ prev = next;
+ continue;
+ } else if (cmpval == 0) {
+ return next;
+ }
+ level--;
+ }
+
+ /* check for duplicate item - could be removed if code doesn't rely
+ * on it, but not really work the complication. */
+ auxlevel = level;
+ auxprev = prev;
+ while (auxlevel) {
+ auxlevel--;
+ auxnext = sl_level_get(auxprev, auxlevel);
+ cmpval = 1;
+ while (auxnext && (cmpval = cmpfn(auxnext, item)) < 0) {
+ auxprev = auxnext;
+ auxnext = sl_level_get(auxprev, auxlevel);
+ }
+ if (cmpval == 0)
+ return auxnext;
+ };
+
+ head->count++;
+ memset(item, 0, sizeof(*item));
+ if (newlevel > SKIPLIST_EMBED) {
+ struct sskip_overflow *oflow;
+ oflow = XMALLOC(MTYPE_SKIPLIST_OFLOW, sizeof(void *)
+ * (newlevel - SKIPLIST_OVERFLOW));
+ item->next[SKIPLIST_OVERFLOW] = (struct sskip_item *)
+ ((uintptr_t)oflow | 1);
+ }
+
+ sl_level_set(item, level, next);
+ sl_level_set(prev, level, item);
+ /* level is now 0-counted and < newlevel*/
+ while (level) {
+ level--;
+ next = sl_level_get(prev, level);
+ while (next && cmpfn(next, item) < 0) {
+ prev = next;
+ next = sl_level_get(prev, level);
+ }
+
+ sl_level_set(item, level, next);
+ sl_level_set(prev, level, item);
+ };
+ return NULL;
+}
+
+/* NOTE: level counting below is 1-based since that makes the code simpler! */
+
+const struct sskip_item *typesafe_skiplist_find(
+ const struct sskip_head *head,
+ const struct sskip_item *item, int (*cmpfn)(
+ const struct sskip_item *a,
+ const struct sskip_item *b))
+{
+ size_t level = SKIPLIST_MAXDEPTH;
+ const struct sskip_item *prev = &head->hitem, *next;
+ int cmpval;
+
+ while (level) {
+ next = sl_level_get(prev, level - 1);
+ if (!next) {
+ level--;
+ continue;
+ }
+ cmpval = cmpfn(next, item);
+ if (cmpval < 0) {
+ prev = next;
+ continue;
+ }
+ if (cmpval == 0)
+ return next;
+ level--;
+ }
+ return NULL;
+}
+
+const struct sskip_item *typesafe_skiplist_find_gteq(
+ const struct sskip_head *head,
+ const struct sskip_item *item, int (*cmpfn)(
+ const struct sskip_item *a,
+ const struct sskip_item *b))
+{
+ size_t level = SKIPLIST_MAXDEPTH;
+ const struct sskip_item *prev = &head->hitem, *next;
+ int cmpval;
+
+ while (level) {
+ next = sl_level_get(prev, level - 1);
+ if (!next) {
+ level--;
+ continue;
+ }
+ cmpval = cmpfn(next, item);
+ if (cmpval < 0) {
+ prev = next;
+ continue;
+ }
+ if (cmpval == 0)
+ return next;
+ level--;
+ }
+ return next;
+}
+
+const struct sskip_item *typesafe_skiplist_find_lt(
+ const struct sskip_head *head,
+ const struct sskip_item *item, int (*cmpfn)(
+ const struct sskip_item *a,
+ const struct sskip_item *b))
+{
+ size_t level = SKIPLIST_MAXDEPTH;
+ const struct sskip_item *prev = &head->hitem, *next, *best = NULL;
+ int cmpval;
+
+ while (level) {
+ next = sl_level_get(prev, level - 1);
+ if (!next) {
+ level--;
+ continue;
+ }
+ cmpval = cmpfn(next, item);
+ if (cmpval < 0) {
+ best = prev = next;
+ continue;
+ }
+ level--;
+ }
+ return best;
+}
+
+struct sskip_item *typesafe_skiplist_del(
+ struct sskip_head *head, struct sskip_item *item,
+ int (*cmpfn)(const struct sskip_item *a, const struct sskip_item *b))
+{
+ size_t level = SKIPLIST_MAXDEPTH;
+ struct sskip_item *prev = &head->hitem, *next;
+ int cmpval;
+ bool found = false;
+
+ while (level) {
+ next = sl_level_get(prev, level - 1);
+ if (!next) {
+ level--;
+ continue;
+ }
+ if (next == item) {
+ sl_level_set(prev, level - 1,
+ sl_level_get(item, level - 1));
+ level--;
+ found = true;
+ continue;
+ }
+ cmpval = cmpfn(next, item);
+ if (cmpval < 0) {
+ prev = next;
+ continue;
+ }
+ level--;
+ }
+
+ if (!found)
+ return NULL;
+
+ /* TBD: assert when trying to remove non-existing item? */
+ head->count--;
+
+ if ((uintptr_t)item->next[SKIPLIST_OVERFLOW] & 1) {
+ uintptr_t ptrval = (uintptr_t)item->next[SKIPLIST_OVERFLOW];
+ ptrval &= UINTPTR_MAX - 3;
+ struct sskip_overflow *oflow = (struct sskip_overflow *)ptrval;
+ XFREE(MTYPE_SKIPLIST_OFLOW, oflow);
+ }
+ memset(item, 0, sizeof(*item));
+
+ return item;
+}
+
+struct sskip_item *typesafe_skiplist_pop(struct sskip_head *head)
+{
+ size_t level = SKIPLIST_MAXDEPTH;
+ struct sskip_item *prev = &head->hitem, *next, *item;
+
+ item = sl_level_get(prev, 0);
+ if (!item)
+ return NULL;
+
+ do {
+ level--;
+
+ next = sl_level_get(prev, level);
+ if (next != item)
+ continue;
+
+ sl_level_set(prev, level, sl_level_get(item, level));
+ } while (level);
+
+ head->count--;
+
+ if ((uintptr_t)item->next[SKIPLIST_OVERFLOW] & 1) {
+ uintptr_t ptrval = (uintptr_t)item->next[SKIPLIST_OVERFLOW];
+ ptrval &= UINTPTR_MAX - 3;
+ struct sskip_overflow *oflow = (struct sskip_overflow *)ptrval;
+ XFREE(MTYPE_SKIPLIST_OFLOW, oflow);
+ }
+ memset(item, 0, sizeof(*item));
+
+ return item;
+}
+
+/* heap */
+
+#if 0
+static void heap_consistency_check(struct heap_head *head,
+ int (*cmpfn)(const struct heap_item *a,
+ const struct heap_item *b),
+ uint32_t pos)
+{
+ uint32_t rghtpos = pos + 1;
+ uint32_t downpos = HEAP_NARY * (pos + 1);
+
+ if (pos + 1 > ~0U / HEAP_NARY)
+ downpos = ~0U;
+
+ if ((pos & (HEAP_NARY - 1)) != HEAP_NARY - 1 && rghtpos < head->count) {
+ assert(cmpfn(head->array[rghtpos], head->array[pos]) >= 0);
+ heap_consistency_check(head, cmpfn, rghtpos);
+ }
+ if (downpos < head->count) {
+ assert(cmpfn(head->array[downpos], head->array[pos]) >= 0);
+ heap_consistency_check(head, cmpfn, downpos);
+ }
+}
+#else
+#define heap_consistency_check(head, cmpfn, pos)
+#endif
+
+void typesafe_heap_resize(struct heap_head *head, bool grow)
+{
+ uint32_t newsize;
+
+ if (grow) {
+ newsize = head->arraysz;
+ if (newsize <= 36)
+ newsize = 72;
+ else if (newsize < 262144)
+ newsize += newsize / 2;
+ else if (newsize < 0xaaaa0000)
+ newsize += newsize / 3;
+ else
+ assert(!newsize);
+ } else if (head->count > 0) {
+ newsize = head->count;
+ } else {
+ XFREE(MTYPE_HEAP_ARRAY, head->array);
+ head->arraysz = 0;
+ return;
+ }
+
+ newsize += HEAP_NARY - 1;
+ newsize &= ~(HEAP_NARY - 1);
+ if (newsize == head->arraysz)
+ return;
+
+ head->array = XREALLOC(MTYPE_HEAP_ARRAY, head->array,
+ newsize * sizeof(struct heap_item *));
+ head->arraysz = newsize;
+}
+
+void typesafe_heap_pushdown(struct heap_head *head, uint32_t pos,
+ struct heap_item *item,
+ int (*cmpfn)(const struct heap_item *a,
+ const struct heap_item *b))
+{
+ uint32_t rghtpos, downpos, moveto;
+
+ while (1) {
+ /* rghtpos: neighbor to the "right", inside block of NARY.
+ * may be invalid if last in block, check nary_last()
+ * downpos: first neighbor in the "downwards" block further
+ * away from the root
+ */
+ rghtpos = pos + 1;
+
+ /* make sure we can use the full 4G items */
+ downpos = HEAP_NARY * (pos + 1);
+ if (pos + 1 > ~0U / HEAP_NARY)
+ /* multiplication overflowed. ~0U is guaranteed
+ * to be an invalid index; size limit is enforced in
+ * resize()
+ */
+ downpos = ~0U;
+
+ /* only used on break */
+ moveto = pos;
+
+#define nary_last(x) (((x) & (HEAP_NARY - 1)) == HEAP_NARY - 1)
+ if (downpos >= head->count
+ || cmpfn(head->array[downpos], item) >= 0) {
+ /* not moving down; either at end or down is >= item */
+ if (nary_last(pos) || rghtpos >= head->count
+ || cmpfn(head->array[rghtpos], item) >= 0)
+ /* not moving right either - got our spot */
+ break;
+
+ moveto = rghtpos;
+
+ /* else: downpos is valid and < item. choose between down
+ * or right (if the latter is an option) */
+ } else if (nary_last(pos) || cmpfn(head->array[rghtpos],
+ head->array[downpos]) >= 0)
+ moveto = downpos;
+ else
+ moveto = rghtpos;
+#undef nary_last
+
+ head->array[pos] = head->array[moveto];
+ head->array[pos]->index = pos;
+ pos = moveto;
+ }
+
+ head->array[moveto] = item;
+ item->index = moveto;
+
+ heap_consistency_check(head, cmpfn, 0);
+}
+
+void typesafe_heap_pullup(struct heap_head *head, uint32_t pos,
+ struct heap_item *item,
+ int (*cmpfn)(const struct heap_item *a,
+ const struct heap_item *b))
+{
+ uint32_t moveto;
+
+ while (pos != 0) {
+ if ((pos & (HEAP_NARY - 1)) == 0)
+ moveto = pos / HEAP_NARY - 1;
+ else
+ moveto = pos - 1;
+
+ if (cmpfn(head->array[moveto], item) <= 0)
+ break;
+
+ head->array[pos] = head->array[moveto];
+ head->array[pos]->index = pos;
+
+ pos = moveto;
+ }
+
+ head->array[pos] = item;
+ item->index = pos;
+
+ heap_consistency_check(head, cmpfn, 0);
+}
diff --git a/lib/typesafe.h b/lib/typesafe.h
new file mode 100644
index 0000000..50c410a
--- /dev/null
+++ b/lib/typesafe.h
@@ -0,0 +1,1165 @@
+/*
+ * Copyright (c) 2016-2019 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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_TYPESAFE_H
+#define _FRR_TYPESAFE_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include "compiler.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* generic macros for all list-like types */
+
+#define frr_each(prefix, head, item) \
+ for (item = prefix##_first(head); item; \
+ item = prefix##_next(head, item))
+#define frr_each_safe(prefix, head, item) \
+ for (typeof(prefix##_next_safe(head, NULL)) prefix##_safe = \
+ prefix##_next_safe(head, \
+ (item = prefix##_first(head))); \
+ item; \
+ item = prefix##_safe, \
+ prefix##_safe = prefix##_next_safe(head, prefix##_safe))
+#define frr_each_from(prefix, head, item, from) \
+ for (item = from, from = prefix##_next_safe(head, item); \
+ item; \
+ item = from, from = prefix##_next_safe(head, from))
+
+/* reverse direction, only supported by a few containers */
+
+#define frr_rev_each(prefix, head, item) \
+ for (item = prefix##_last(head); item; \
+ item = prefix##_prev(head, item))
+#define frr_rev_each_safe(prefix, head, item) \
+ for (typeof(prefix##_prev_safe(head, NULL)) prefix##_safe = \
+ prefix##_prev_safe(head, \
+ (item = prefix##_last(head))); \
+ item; \
+ item = prefix##_safe, \
+ prefix##_safe = prefix##_prev_safe(head, prefix##_safe))
+#define frr_rev_each_from(prefix, head, item, from) \
+ for (item = from, from = prefix##_prev_safe(head, item); \
+ item; \
+ item = from, from = prefix##_prev_safe(head, from))
+
+/* non-const variants. these wrappers are the same for all the types, so
+ * bundle them together here.
+ */
+#define TYPESAFE_FIRST_NEXT(prefix, type) \
+macro_pure type *prefix ## _first(struct prefix##_head *h) \
+{ \
+ return (type *)prefix ## _const_first(h); \
+} \
+macro_pure type *prefix ## _next(struct prefix##_head *h, type *item) \
+{ \
+ return (type *)prefix ## _const_next(h, item); \
+} \
+/* ... */
+#define TYPESAFE_LAST_PREV(prefix, type) \
+macro_pure type *prefix ## _last(struct prefix##_head *h) \
+{ \
+ return (type *)prefix ## _const_last(h); \
+} \
+macro_pure type *prefix ## _prev(struct prefix##_head *h, type *item) \
+{ \
+ return (type *)prefix ## _const_prev(h, item); \
+} \
+/* ... */
+#define TYPESAFE_FIND(prefix, type) \
+macro_inline type *prefix ## _find(struct prefix##_head *h, \
+ const type *item) \
+{ \
+ return (type *)prefix ## _const_find(h, item); \
+} \
+/* ... */
+#define TYPESAFE_FIND_CMP(prefix, type) \
+macro_inline type *prefix ## _find_lt(struct prefix##_head *h, \
+ const type *item) \
+{ \
+ return (type *)prefix ## _const_find_lt(h, item); \
+} \
+macro_inline type *prefix ## _find_gteq(struct prefix##_head *h, \
+ const type *item) \
+{ \
+ return (type *)prefix ## _const_find_gteq(h, item); \
+} \
+/* ... */
+
+/* *_member via find - when there is no better membership check than find() */
+#define TYPESAFE_MEMBER_VIA_FIND(prefix, type) \
+macro_inline bool prefix ## _member(struct prefix##_head *h, \
+ const type *item) \
+{ \
+ return item == prefix ## _const_find(h, item); \
+} \
+/* ... */
+
+/* *_member via find_gteq - same for non-unique containers */
+#define TYPESAFE_MEMBER_VIA_FIND_GTEQ(prefix, type, cmpfn) \
+macro_inline bool prefix ## _member(struct prefix##_head *h, \
+ const type *item) \
+{ \
+ const type *iter; \
+ for (iter = prefix ## _const_find_gteq(h, item); iter; \
+ iter = prefix ## _const_next(h, iter)) { \
+ if (iter == item) \
+ return true; \
+ if (cmpfn(iter, item) > 0) \
+ break; \
+ } \
+ return false; \
+} \
+/* ... */
+
+/* SWAP_ALL_SIMPLE = for containers where the items don't point back to the
+ * head *AND* the head doesn't point to itself (= everything except LIST,
+ * DLIST and SKIPLIST), just switch out the entire head
+ */
+#define TYPESAFE_SWAP_ALL_SIMPLE(prefix) \
+macro_inline void prefix ## _swap_all(struct prefix##_head *a, \
+ struct prefix##_head *b) \
+{ \
+ struct prefix##_head tmp = *a; \
+ *a = *b; \
+ *b = tmp; \
+} \
+/* ... */
+
+/* single-linked list, unsorted/arbitrary.
+ * can be used as queue with add_tail / pop
+ */
+
+/* don't use these structs directly */
+struct slist_item {
+ struct slist_item *next;
+};
+
+struct slist_head {
+ struct slist_item *first, **last_next;
+ size_t count;
+};
+
+/* this replaces NULL as the value for ->next on the last item. */
+extern struct slist_item typesafe_slist_sentinel;
+#define _SLIST_LAST &typesafe_slist_sentinel
+
+static inline void typesafe_list_add(struct slist_head *head,
+ struct slist_item **pos, struct slist_item *item)
+{
+ item->next = *pos;
+ *pos = item;
+ if (pos == head->last_next)
+ head->last_next = &item->next;
+ head->count++;
+}
+
+extern bool typesafe_list_member(const struct slist_head *head,
+ const struct slist_item *item);
+
+/* use as:
+ *
+ * PREDECL_LIST(namelist);
+ * struct name {
+ * struct namelist_item nlitem;
+ * }
+ * DECLARE_LIST(namelist, struct name, nlitem);
+ */
+#define PREDECL_LIST(prefix) \
+struct prefix ## _head { struct slist_head sh; }; \
+struct prefix ## _item { struct slist_item si; }; \
+MACRO_REQUIRE_SEMICOLON() /* end */
+
+#define INIT_LIST(var) { .sh = { .last_next = &var.sh.first, }, }
+
+#define DECLARE_LIST(prefix, type, field) \
+ \
+macro_inline void prefix ## _init(struct prefix##_head *h) \
+{ \
+ memset(h, 0, sizeof(*h)); \
+ h->sh.first = _SLIST_LAST; \
+ h->sh.last_next = &h->sh.first; \
+} \
+macro_inline void prefix ## _fini(struct prefix##_head *h) \
+{ \
+ memset(h, 0, sizeof(*h)); \
+} \
+macro_inline void prefix ## _add_head(struct prefix##_head *h, type *item) \
+{ \
+ typesafe_list_add(&h->sh, &h->sh.first, &item->field.si); \
+} \
+macro_inline void prefix ## _add_tail(struct prefix##_head *h, type *item) \
+{ \
+ typesafe_list_add(&h->sh, h->sh.last_next, &item->field.si); \
+} \
+macro_inline void prefix ## _add_after(struct prefix##_head *h, \
+ type *after, type *item) \
+{ \
+ struct slist_item **nextp; \
+ nextp = after ? &after->field.si.next : &h->sh.first; \
+ typesafe_list_add(&h->sh, nextp, &item->field.si); \
+} \
+/* TODO: del_hint */ \
+macro_inline type *prefix ## _del(struct prefix##_head *h, type *item) \
+{ \
+ struct slist_item **iter = &h->sh.first; \
+ while (*iter != _SLIST_LAST && *iter != &item->field.si) \
+ iter = &(*iter)->next; \
+ if (*iter == _SLIST_LAST) \
+ return NULL; \
+ h->sh.count--; \
+ *iter = item->field.si.next; \
+ if (item->field.si.next == _SLIST_LAST) \
+ h->sh.last_next = iter; \
+ item->field.si.next = NULL; \
+ return item; \
+} \
+macro_inline type *prefix ## _pop(struct prefix##_head *h) \
+{ \
+ struct slist_item *sitem = h->sh.first; \
+ if (sitem == _SLIST_LAST) \
+ return NULL; \
+ h->sh.count--; \
+ h->sh.first = sitem->next; \
+ if (h->sh.first == _SLIST_LAST) \
+ h->sh.last_next = &h->sh.first; \
+ sitem->next = NULL; \
+ return container_of(sitem, type, field.si); \
+} \
+macro_inline void prefix ## _swap_all(struct prefix##_head *a, \
+ struct prefix##_head *b) \
+{ \
+ struct prefix##_head tmp = *a; \
+ *a = *b; \
+ *b = tmp; \
+ if (a->sh.last_next == &b->sh.first) \
+ a->sh.last_next = &a->sh.first; \
+ if (b->sh.last_next == &a->sh.first) \
+ b->sh.last_next = &b->sh.first; \
+} \
+macro_pure const type *prefix ## _const_first(const struct prefix##_head *h) \
+{ \
+ if (h->sh.first != _SLIST_LAST) \
+ return container_of(h->sh.first, type, field.si); \
+ return NULL; \
+} \
+macro_pure const type *prefix ## _const_next(const struct prefix##_head *h, \
+ const type *item) \
+{ \
+ const struct slist_item *sitem = &item->field.si; \
+ if (sitem->next != _SLIST_LAST) \
+ return container_of(sitem->next, type, field.si); \
+ return NULL; \
+} \
+TYPESAFE_FIRST_NEXT(prefix, type) \
+macro_pure type *prefix ## _next_safe(struct prefix##_head *h, type *item) \
+{ \
+ struct slist_item *sitem; \
+ if (!item) \
+ return NULL; \
+ sitem = &item->field.si; \
+ if (sitem->next != _SLIST_LAST) \
+ return container_of(sitem->next, type, field.si); \
+ return NULL; \
+} \
+macro_pure size_t prefix ## _count(const struct prefix##_head *h) \
+{ \
+ return h->sh.count; \
+} \
+macro_pure bool prefix ## _anywhere(const type *item) \
+{ \
+ return item->field.si.next != NULL; \
+} \
+macro_pure bool prefix ## _member(const struct prefix##_head *h, \
+ const type *item) \
+{ \
+ return typesafe_list_member(&h->sh, &item->field.si); \
+} \
+MACRO_REQUIRE_SEMICOLON() /* end */
+
+/* don't use these structs directly */
+struct dlist_item {
+ struct dlist_item *next;
+ struct dlist_item *prev;
+};
+
+struct dlist_head {
+ struct dlist_item hitem;
+ size_t count;
+};
+
+static inline void typesafe_dlist_add(struct dlist_head *head,
+ struct dlist_item *prev, struct dlist_item *item)
+{
+ /* SA on clang-11 thinks this can happen, but in reality -assuming no
+ * memory corruption- it can't. DLIST uses a "closed" ring, i.e. the
+ * termination at the end of the list is not NULL but rather a pointer
+ * back to the head. (This eliminates special-casing the first or last
+ * item.)
+ *
+ * Sadly, can't use assert() here since the libfrr assert / xref code
+ * uses typesafe lists itself... that said, if an assert tripped here
+ * we'd already be way past some memory corruption, so we might as
+ * well just take the SEGV. (In the presence of corruption, we'd see
+ * random SEGVs from places that make no sense at all anyway, an
+ * assert might actually be a red herring.)
+ *
+ * ("assume()" tells the compiler to produce code as if the condition
+ * will always hold; it doesn't have any actual effect here, it'll
+ * just SEGV out on "item->next->prev = item".)
+ */
+ assume(prev->next != NULL);
+
+ item->next = prev->next;
+ item->next->prev = item;
+ item->prev = prev;
+ prev->next = item;
+ head->count++;
+}
+
+static inline void typesafe_dlist_swap_all(struct dlist_head *a,
+ struct dlist_head *b)
+{
+ struct dlist_head tmp = *a;
+
+ a->count = b->count;
+ if (a->count) {
+ a->hitem.next = b->hitem.next;
+ a->hitem.prev = b->hitem.prev;
+ a->hitem.next->prev = &a->hitem;
+ a->hitem.prev->next = &a->hitem;
+ } else {
+ a->hitem.next = &a->hitem;
+ a->hitem.prev = &a->hitem;
+ }
+
+ b->count = tmp.count;
+ if (b->count) {
+ b->hitem.next = tmp.hitem.next;
+ b->hitem.prev = tmp.hitem.prev;
+ b->hitem.next->prev = &b->hitem;
+ b->hitem.prev->next = &b->hitem;
+ } else {
+ b->hitem.next = &b->hitem;
+ b->hitem.prev = &b->hitem;
+ }
+}
+
+extern bool typesafe_dlist_member(const struct dlist_head *head,
+ const struct dlist_item *item);
+
+/* double-linked list, for fast item deletion
+ */
+#define PREDECL_DLIST(prefix) \
+struct prefix ## _head { struct dlist_head dh; }; \
+struct prefix ## _item { struct dlist_item di; }; \
+MACRO_REQUIRE_SEMICOLON() /* end */
+
+#define INIT_DLIST(var) { .dh = { \
+ .hitem = { &var.dh.hitem, &var.dh.hitem }, }, }
+
+#define DECLARE_DLIST(prefix, type, field) \
+ \
+macro_inline void prefix ## _init(struct prefix##_head *h) \
+{ \
+ memset(h, 0, sizeof(*h)); \
+ h->dh.hitem.prev = &h->dh.hitem; \
+ h->dh.hitem.next = &h->dh.hitem; \
+} \
+macro_inline void prefix ## _fini(struct prefix##_head *h) \
+{ \
+ memset(h, 0, sizeof(*h)); \
+} \
+macro_inline void prefix ## _add_head(struct prefix##_head *h, type *item) \
+{ \
+ typesafe_dlist_add(&h->dh, &h->dh.hitem, &item->field.di); \
+} \
+macro_inline void prefix ## _add_tail(struct prefix##_head *h, type *item) \
+{ \
+ typesafe_dlist_add(&h->dh, h->dh.hitem.prev, &item->field.di); \
+} \
+macro_inline void prefix ## _add_after(struct prefix##_head *h, \
+ type *after, type *item) \
+{ \
+ struct dlist_item *prev; \
+ prev = after ? &after->field.di : &h->dh.hitem; \
+ typesafe_dlist_add(&h->dh, prev, &item->field.di); \
+} \
+macro_inline type *prefix ## _del(struct prefix##_head *h, type *item) \
+{ \
+ struct dlist_item *ditem = &item->field.di; \
+ ditem->prev->next = ditem->next; \
+ ditem->next->prev = ditem->prev; \
+ h->dh.count--; \
+ ditem->prev = ditem->next = NULL; \
+ return item; \
+} \
+macro_inline type *prefix ## _pop(struct prefix##_head *h) \
+{ \
+ struct dlist_item *ditem = h->dh.hitem.next; \
+ if (ditem == &h->dh.hitem) \
+ return NULL; \
+ ditem->prev->next = ditem->next; \
+ ditem->next->prev = ditem->prev; \
+ h->dh.count--; \
+ ditem->prev = ditem->next = NULL; \
+ return container_of(ditem, type, field.di); \
+} \
+macro_inline void prefix ## _swap_all(struct prefix##_head *a, \
+ struct prefix##_head *b) \
+{ \
+ typesafe_dlist_swap_all(&a->dh, &b->dh); \
+} \
+macro_pure const type *prefix ## _const_first(const struct prefix##_head *h) \
+{ \
+ const struct dlist_item *ditem = h->dh.hitem.next; \
+ if (ditem == &h->dh.hitem) \
+ return NULL; \
+ return container_of(ditem, type, field.di); \
+} \
+macro_pure const type *prefix ## _const_next(const struct prefix##_head *h, \
+ const type *item) \
+{ \
+ const struct dlist_item *ditem = &item->field.di; \
+ if (ditem->next == &h->dh.hitem) \
+ return NULL; \
+ return container_of(ditem->next, type, field.di); \
+} \
+TYPESAFE_FIRST_NEXT(prefix, type) \
+macro_pure const type *prefix ## _const_last(const struct prefix##_head *h) \
+{ \
+ const struct dlist_item *ditem = h->dh.hitem.prev; \
+ if (ditem == &h->dh.hitem) \
+ return NULL; \
+ return container_of(ditem, type, field.di); \
+} \
+macro_pure const type *prefix ## _const_prev(const struct prefix##_head *h, \
+ const type *item) \
+{ \
+ const struct dlist_item *ditem = &item->field.di; \
+ if (ditem->prev == &h->dh.hitem) \
+ return NULL; \
+ return container_of(ditem->prev, type, field.di); \
+} \
+TYPESAFE_LAST_PREV(prefix, type) \
+macro_pure type *prefix ## _next_safe(struct prefix##_head *h, type *item) \
+{ \
+ if (!item) \
+ return NULL; \
+ return prefix ## _next(h, item); \
+} \
+macro_pure type *prefix ## _prev_safe(struct prefix##_head *h, type *item) \
+{ \
+ if (!item) \
+ return NULL; \
+ return prefix ## _prev(h, item); \
+} \
+macro_pure size_t prefix ## _count(const struct prefix##_head *h) \
+{ \
+ return h->dh.count; \
+} \
+macro_pure bool prefix ## _anywhere(const type *item) \
+{ \
+ const struct dlist_item *ditem = &item->field.di; \
+ return ditem->next && ditem->prev; \
+} \
+macro_pure bool prefix ## _member(const struct prefix##_head *h, \
+ const type *item) \
+{ \
+ return typesafe_dlist_member(&h->dh, &item->field.di); \
+} \
+MACRO_REQUIRE_SEMICOLON() /* end */
+
+/* note: heap currently caps out at 4G items */
+
+#define HEAP_NARY 8U
+typedef uint32_t heap_index_i;
+
+struct heap_item {
+ uint32_t index;
+};
+
+struct heap_head {
+ struct heap_item **array;
+ uint32_t arraysz, count;
+};
+
+#define HEAP_RESIZE_TRESH_UP(h) \
+ (h->hh.count + 1 >= h->hh.arraysz)
+#define HEAP_RESIZE_TRESH_DN(h) \
+ (h->hh.count == 0 || \
+ h->hh.arraysz - h->hh.count > (h->hh.count + 1024) / 2)
+
+#define PREDECL_HEAP(prefix) \
+struct prefix ## _head { struct heap_head hh; }; \
+struct prefix ## _item { struct heap_item hi; }; \
+MACRO_REQUIRE_SEMICOLON() /* end */
+
+#define INIT_HEAP(var) { }
+
+#define DECLARE_HEAP(prefix, type, field, cmpfn) \
+ \
+macro_inline void prefix ## _init(struct prefix##_head *h) \
+{ \
+ memset(h, 0, sizeof(*h)); \
+} \
+macro_inline void prefix ## _fini(struct prefix##_head *h) \
+{ \
+ assert(h->hh.count == 0); \
+ memset(h, 0, sizeof(*h)); \
+} \
+macro_inline int prefix ## __cmp(const struct heap_item *a, \
+ const struct heap_item *b) \
+{ \
+ return cmpfn(container_of(a, type, field.hi), \
+ container_of(b, type, field.hi)); \
+} \
+macro_inline type *prefix ## _add(struct prefix##_head *h, type *item) \
+{ \
+ if (HEAP_RESIZE_TRESH_UP(h)) \
+ typesafe_heap_resize(&h->hh, true); \
+ typesafe_heap_pullup(&h->hh, h->hh.count, &item->field.hi, \
+ prefix ## __cmp); \
+ h->hh.count++; \
+ return NULL; \
+} \
+macro_inline type *prefix ## _del(struct prefix##_head *h, type *item) \
+{ \
+ struct heap_item *other; \
+ uint32_t index = item->field.hi.index; \
+ assert(h->hh.array[index] == &item->field.hi); \
+ h->hh.count--; \
+ other = h->hh.array[h->hh.count]; \
+ if (cmpfn(container_of(other, type, field.hi), item) < 0) \
+ typesafe_heap_pullup(&h->hh, index, other, prefix ## __cmp); \
+ else \
+ typesafe_heap_pushdown(&h->hh, index, other, prefix ## __cmp); \
+ if (HEAP_RESIZE_TRESH_DN(h)) \
+ typesafe_heap_resize(&h->hh, false); \
+ return item; \
+} \
+macro_inline type *prefix ## _pop(struct prefix##_head *h) \
+{ \
+ struct heap_item *hitem, *other; \
+ if (h->hh.count == 0) \
+ return NULL; \
+ hitem = h->hh.array[0]; \
+ h->hh.count--; \
+ other = h->hh.array[h->hh.count]; \
+ typesafe_heap_pushdown(&h->hh, 0, other, prefix ## __cmp); \
+ if (HEAP_RESIZE_TRESH_DN(h)) \
+ typesafe_heap_resize(&h->hh, false); \
+ return container_of(hitem, type, field.hi); \
+} \
+TYPESAFE_SWAP_ALL_SIMPLE(prefix) \
+macro_pure const type *prefix ## _const_first(const struct prefix##_head *h) \
+{ \
+ if (h->hh.count == 0) \
+ return NULL; \
+ return container_of(h->hh.array[0], type, field.hi); \
+} \
+macro_pure const type *prefix ## _const_next(const struct prefix##_head *h, \
+ const type *item) \
+{ \
+ uint32_t idx = item->field.hi.index + 1; \
+ if (idx >= h->hh.count) \
+ return NULL; \
+ return container_of(h->hh.array[idx], type, field.hi); \
+} \
+TYPESAFE_FIRST_NEXT(prefix, type) \
+macro_pure type *prefix ## _next_safe(struct prefix##_head *h, type *item) \
+{ \
+ if (!item) \
+ return NULL; \
+ return prefix ## _next(h, item); \
+} \
+macro_pure size_t prefix ## _count(const struct prefix##_head *h) \
+{ \
+ return h->hh.count; \
+} \
+macro_pure bool prefix ## _member(const struct prefix##_head *h, \
+ const type *item) \
+{ \
+ uint32_t idx = item->field.hi.index; \
+ if (idx >= h->hh.count) \
+ return false; \
+ return h->hh.array[idx] == &item->field.hi; \
+} \
+MACRO_REQUIRE_SEMICOLON() /* end */
+
+extern void typesafe_heap_resize(struct heap_head *head, bool grow);
+extern void typesafe_heap_pushdown(struct heap_head *head, uint32_t index,
+ struct heap_item *item,
+ int (*cmpfn)(const struct heap_item *a,
+ const struct heap_item *b));
+extern void typesafe_heap_pullup(struct heap_head *head, uint32_t index,
+ struct heap_item *item,
+ int (*cmpfn)(const struct heap_item *a,
+ const struct heap_item *b));
+
+/* single-linked list, sorted.
+ * can be used as priority queue with add / pop
+ */
+
+/* don't use these structs directly */
+struct ssort_item {
+ struct ssort_item *next;
+};
+
+struct ssort_head {
+ struct ssort_item *first;
+ size_t count;
+};
+
+/* use as:
+ *
+ * PREDECL_SORTLIST(namelist)
+ * struct name {
+ * struct namelist_item nlitem;
+ * }
+ * DECLARE_SORTLIST(namelist, struct name, nlitem)
+ */
+#define _PREDECL_SORTLIST(prefix) \
+struct prefix ## _head { struct ssort_head sh; }; \
+struct prefix ## _item { struct ssort_item si; }; \
+MACRO_REQUIRE_SEMICOLON() /* end */
+
+#define INIT_SORTLIST_UNIQ(var) { }
+#define INIT_SORTLIST_NONUNIQ(var) { }
+
+#define PREDECL_SORTLIST_UNIQ(prefix) \
+ _PREDECL_SORTLIST(prefix)
+#define PREDECL_SORTLIST_NONUNIQ(prefix) \
+ _PREDECL_SORTLIST(prefix)
+
+#define _DECLARE_SORTLIST(prefix, type, field, cmpfn_nuq, cmpfn_uq) \
+ \
+macro_inline void prefix ## _init(struct prefix##_head *h) \
+{ \
+ memset(h, 0, sizeof(*h)); \
+} \
+macro_inline void prefix ## _fini(struct prefix##_head *h) \
+{ \
+ memset(h, 0, sizeof(*h)); \
+} \
+macro_inline type *prefix ## _add(struct prefix##_head *h, type *item) \
+{ \
+ struct ssort_item **np = &h->sh.first; \
+ int c = 1; \
+ while (*np && (c = cmpfn_uq( \
+ container_of(*np, type, field.si), item)) < 0) \
+ np = &(*np)->next; \
+ if (c == 0) \
+ return container_of(*np, type, field.si); \
+ item->field.si.next = *np; \
+ *np = &item->field.si; \
+ h->sh.count++; \
+ return NULL; \
+} \
+macro_inline const type *prefix ## _const_find_gteq( \
+ const struct prefix##_head *h, const type *item) \
+{ \
+ const struct ssort_item *sitem = h->sh.first; \
+ int cmpval = 0; \
+ while (sitem && (cmpval = cmpfn_nuq( \
+ container_of(sitem, type, field.si), item)) < 0) \
+ sitem = sitem->next; \
+ return container_of_null(sitem, type, field.si); \
+} \
+macro_inline const type *prefix ## _const_find_lt( \
+ const struct prefix##_head *h, const type *item) \
+{ \
+ const struct ssort_item *prev = NULL, *sitem = h->sh.first; \
+ int cmpval = 0; \
+ while (sitem && (cmpval = cmpfn_nuq( \
+ container_of(sitem, type, field.si), item)) < 0) \
+ sitem = (prev = sitem)->next; \
+ return container_of_null(prev, type, field.si); \
+} \
+TYPESAFE_FIND_CMP(prefix, type) \
+/* TODO: del_hint */ \
+macro_inline type *prefix ## _del(struct prefix##_head *h, type *item) \
+{ \
+ struct ssort_item **iter = &h->sh.first; \
+ while (*iter && *iter != &item->field.si) \
+ iter = &(*iter)->next; \
+ if (!*iter) \
+ return NULL; \
+ h->sh.count--; \
+ *iter = item->field.si.next; \
+ item->field.si.next = NULL; \
+ return item; \
+} \
+macro_inline type *prefix ## _pop(struct prefix##_head *h) \
+{ \
+ struct ssort_item *sitem = h->sh.first; \
+ if (!sitem) \
+ return NULL; \
+ h->sh.count--; \
+ h->sh.first = sitem->next; \
+ return container_of(sitem, type, field.si); \
+} \
+TYPESAFE_SWAP_ALL_SIMPLE(prefix) \
+macro_pure const type *prefix ## _const_first(const struct prefix##_head *h) \
+{ \
+ return container_of_null(h->sh.first, type, field.si); \
+} \
+macro_pure const type *prefix ## _const_next(const struct prefix##_head *h, \
+ const type *item) \
+{ \
+ const struct ssort_item *sitem = &item->field.si; \
+ return container_of_null(sitem->next, type, field.si); \
+} \
+TYPESAFE_FIRST_NEXT(prefix, type) \
+macro_pure type *prefix ## _next_safe(struct prefix##_head *h, type *item) \
+{ \
+ struct ssort_item *sitem; \
+ if (!item) \
+ return NULL; \
+ sitem = &item->field.si; \
+ return container_of_null(sitem->next, type, field.si); \
+} \
+macro_pure size_t prefix ## _count(const struct prefix##_head *h) \
+{ \
+ return h->sh.count; \
+} \
+MACRO_REQUIRE_SEMICOLON() /* end */
+
+#define DECLARE_SORTLIST_UNIQ(prefix, type, field, cmpfn) \
+ _DECLARE_SORTLIST(prefix, type, field, cmpfn, cmpfn); \
+ \
+macro_inline const type *prefix ## _const_find(const struct prefix##_head *h, \
+ const type *item) \
+{ \
+ const struct ssort_item *sitem = h->sh.first; \
+ int cmpval = 0; \
+ while (sitem && (cmpval = cmpfn( \
+ container_of(sitem, type, field.si), item)) < 0) \
+ sitem = sitem->next; \
+ if (!sitem || cmpval > 0) \
+ return NULL; \
+ return container_of(sitem, type, field.si); \
+} \
+TYPESAFE_FIND(prefix, type) \
+TYPESAFE_MEMBER_VIA_FIND(prefix, type) \
+MACRO_REQUIRE_SEMICOLON() /* end */
+
+#define DECLARE_SORTLIST_NONUNIQ(prefix, type, field, cmpfn) \
+macro_inline int _ ## prefix ## _cmp(const type *a, const type *b) \
+{ \
+ int cmpval = cmpfn(a, b); \
+ if (cmpval) \
+ return cmpval; \
+ if (a < b) \
+ return -1; \
+ if (a > b) \
+ return 1; \
+ return 0; \
+} \
+ _DECLARE_SORTLIST(prefix, type, field, cmpfn, _ ## prefix ## _cmp); \
+TYPESAFE_MEMBER_VIA_FIND_GTEQ(prefix, type, cmpfn) \
+MACRO_REQUIRE_SEMICOLON() /* end */
+
+
+/* hash, "sorted" by hash value
+ */
+
+/* don't use these structs directly */
+struct thash_item {
+ struct thash_item *next;
+ uint32_t hashval;
+};
+
+struct thash_head {
+ struct thash_item **entries;
+ uint32_t count;
+
+ uint8_t tabshift;
+ uint8_t minshift, maxshift;
+};
+
+#define _HASH_SIZE(tabshift) \
+ ((1U << (tabshift)) >> 1)
+#define HASH_SIZE(head) \
+ _HASH_SIZE((head).tabshift)
+#define _HASH_KEY(tabshift, val) \
+ ((val) >> (33 - (tabshift)))
+#define HASH_KEY(head, val) \
+ _HASH_KEY((head).tabshift, val)
+#define HASH_GROW_THRESHOLD(head) \
+ ((head).count >= HASH_SIZE(head))
+#define HASH_SHRINK_THRESHOLD(head) \
+ ((head).count <= (HASH_SIZE(head) - 1) / 2)
+
+extern void typesafe_hash_grow(struct thash_head *head);
+extern void typesafe_hash_shrink(struct thash_head *head);
+
+/* use as:
+ *
+ * PREDECL_HASH(namelist)
+ * struct name {
+ * struct namelist_item nlitem;
+ * }
+ * DECLARE_HASH(namelist, struct name, nlitem, cmpfunc, hashfunc)
+ */
+#define PREDECL_HASH(prefix) \
+struct prefix ## _head { struct thash_head hh; }; \
+struct prefix ## _item { struct thash_item hi; }; \
+MACRO_REQUIRE_SEMICOLON() /* end */
+
+#define INIT_HASH(var) { }
+
+#define DECLARE_HASH(prefix, type, field, cmpfn, hashfn) \
+ \
+macro_inline void prefix ## _init(struct prefix##_head *h) \
+{ \
+ memset(h, 0, sizeof(*h)); \
+} \
+macro_inline void prefix ## _fini(struct prefix##_head *h) \
+{ \
+ assert(h->hh.count == 0); \
+ h->hh.minshift = 0; \
+ typesafe_hash_shrink(&h->hh); \
+ memset(h, 0, sizeof(*h)); \
+} \
+macro_inline type *prefix ## _add(struct prefix##_head *h, type *item) \
+{ \
+ h->hh.count++; \
+ if (!h->hh.tabshift || HASH_GROW_THRESHOLD(h->hh)) \
+ typesafe_hash_grow(&h->hh); \
+ \
+ uint32_t hval = hashfn(item), hbits = HASH_KEY(h->hh, hval); \
+ item->field.hi.hashval = hval; \
+ struct thash_item **np = &h->hh.entries[hbits]; \
+ while (*np && (*np)->hashval < hval) \
+ np = &(*np)->next; \
+ if (*np && cmpfn(container_of(*np, type, field.hi), item) == 0) { \
+ h->hh.count--; \
+ return container_of(*np, type, field.hi); \
+ } \
+ item->field.hi.next = *np; \
+ *np = &item->field.hi; \
+ return NULL; \
+} \
+macro_inline const type *prefix ## _const_find(const struct prefix##_head *h, \
+ const type *item) \
+{ \
+ if (!h->hh.tabshift) \
+ return NULL; \
+ uint32_t hval = hashfn(item), hbits = HASH_KEY(h->hh, hval); \
+ const struct thash_item *hitem = h->hh.entries[hbits]; \
+ while (hitem && hitem->hashval < hval) \
+ hitem = hitem->next; \
+ while (hitem && hitem->hashval == hval) { \
+ if (!cmpfn(container_of(hitem, type, field.hi), item)) \
+ return container_of(hitem, type, field.hi); \
+ hitem = hitem->next; \
+ } \
+ return NULL; \
+} \
+TYPESAFE_FIND(prefix, type) \
+macro_inline type *prefix ## _del(struct prefix##_head *h, type *item) \
+{ \
+ if (!h->hh.tabshift) \
+ return NULL; \
+ uint32_t hval = item->field.hi.hashval, hbits = HASH_KEY(h->hh, hval); \
+ struct thash_item **np = &h->hh.entries[hbits]; \
+ while (*np && (*np)->hashval < hval) \
+ np = &(*np)->next; \
+ while (*np && *np != &item->field.hi && (*np)->hashval == hval) \
+ np = &(*np)->next; \
+ if (*np != &item->field.hi) \
+ return NULL; \
+ *np = item->field.hi.next; \
+ item->field.hi.next = NULL; \
+ h->hh.count--; \
+ if (HASH_SHRINK_THRESHOLD(h->hh)) \
+ typesafe_hash_shrink(&h->hh); \
+ return item; \
+} \
+macro_inline type *prefix ## _pop(struct prefix##_head *h) \
+{ \
+ uint32_t i; \
+ for (i = 0; i < HASH_SIZE(h->hh); i++) \
+ if (h->hh.entries[i]) { \
+ struct thash_item *hitem = h->hh.entries[i]; \
+ h->hh.entries[i] = hitem->next; \
+ h->hh.count--; \
+ hitem->next = NULL; \
+ if (HASH_SHRINK_THRESHOLD(h->hh)) \
+ typesafe_hash_shrink(&h->hh); \
+ return container_of(hitem, type, field.hi); \
+ } \
+ return NULL; \
+} \
+TYPESAFE_SWAP_ALL_SIMPLE(prefix) \
+macro_pure const type *prefix ## _const_first(const struct prefix##_head *h) \
+{ \
+ uint32_t i; \
+ for (i = 0; i < HASH_SIZE(h->hh); i++) \
+ if (h->hh.entries[i]) \
+ return container_of(h->hh.entries[i], type, field.hi); \
+ return NULL; \
+} \
+macro_pure const type *prefix ## _const_next(const struct prefix##_head *h, \
+ const type *item) \
+{ \
+ const struct thash_item *hitem = &item->field.hi; \
+ if (hitem->next) \
+ return container_of(hitem->next, type, field.hi); \
+ uint32_t i = HASH_KEY(h->hh, hitem->hashval) + 1; \
+ for (; i < HASH_SIZE(h->hh); i++) \
+ if (h->hh.entries[i]) \
+ return container_of(h->hh.entries[i], type, field.hi); \
+ return NULL; \
+} \
+TYPESAFE_FIRST_NEXT(prefix, type) \
+macro_pure type *prefix ## _next_safe(struct prefix##_head *h, type *item) \
+{ \
+ if (!item) \
+ return NULL; \
+ return prefix ## _next(h, item); \
+} \
+macro_pure size_t prefix ## _count(const struct prefix##_head *h) \
+{ \
+ return h->hh.count; \
+} \
+macro_pure bool prefix ## _member(const struct prefix##_head *h, \
+ const type *item) \
+{ \
+ uint32_t hval = item->field.hi.hashval, hbits = HASH_KEY(h->hh, hval); \
+ const struct thash_item *hitem = h->hh.entries[hbits]; \
+ while (hitem && hitem->hashval < hval) \
+ hitem = hitem->next; \
+ for (hitem = h->hh.entries[hbits]; hitem && hitem->hashval <= hval; \
+ hitem = hitem->next) \
+ if (hitem == &item->field.hi) \
+ return true; \
+ return false; \
+} \
+MACRO_REQUIRE_SEMICOLON() /* end */
+
+/* skiplist, sorted.
+ * can be used as priority queue with add / pop
+ */
+
+/* don't use these structs directly */
+#define SKIPLIST_MAXDEPTH 16
+#define SKIPLIST_EMBED 4
+#define SKIPLIST_OVERFLOW (SKIPLIST_EMBED - 1)
+
+struct sskip_item {
+ struct sskip_item *next[SKIPLIST_EMBED];
+};
+
+struct sskip_overflow {
+ struct sskip_item *next[SKIPLIST_MAXDEPTH - SKIPLIST_OVERFLOW];
+};
+
+struct sskip_head {
+ struct sskip_item hitem;
+ struct sskip_item *overflow[SKIPLIST_MAXDEPTH - SKIPLIST_OVERFLOW];
+ size_t count;
+};
+
+/* use as:
+ *
+ * PREDECL_SKIPLIST(namelist)
+ * struct name {
+ * struct namelist_item nlitem;
+ * }
+ * DECLARE_SKIPLIST(namelist, struct name, nlitem, cmpfunc)
+ */
+#define _PREDECL_SKIPLIST(prefix) \
+struct prefix ## _head { struct sskip_head sh; }; \
+struct prefix ## _item { struct sskip_item si; }; \
+MACRO_REQUIRE_SEMICOLON() /* end */
+
+#define INIT_SKIPLIST_UNIQ(var) { }
+#define INIT_SKIPLIST_NONUNIQ(var) { }
+
+#define _DECLARE_SKIPLIST(prefix, type, field, cmpfn_nuq, cmpfn_uq) \
+ \
+macro_inline void prefix ## _init(struct prefix##_head *h) \
+{ \
+ memset(h, 0, sizeof(*h)); \
+ h->sh.hitem.next[SKIPLIST_OVERFLOW] = (struct sskip_item *) \
+ ((uintptr_t)h->sh.overflow | 1); \
+} \
+macro_inline void prefix ## _fini(struct prefix##_head *h) \
+{ \
+ memset(h, 0, sizeof(*h)); \
+} \
+macro_inline type *prefix ## _add(struct prefix##_head *h, type *item) \
+{ \
+ struct sskip_item *si; \
+ si = typesafe_skiplist_add(&h->sh, &item->field.si, cmpfn_uq); \
+ return container_of_null(si, type, field.si); \
+} \
+macro_inline const type *prefix ## _const_find_gteq( \
+ const struct prefix##_head *h, const type *item) \
+{ \
+ const struct sskip_item *sitem = typesafe_skiplist_find_gteq(&h->sh, \
+ &item->field.si, cmpfn_nuq); \
+ return container_of_null(sitem, type, field.si); \
+} \
+macro_inline const type *prefix ## _const_find_lt( \
+ const struct prefix##_head *h, const type *item) \
+{ \
+ const struct sskip_item *sitem = typesafe_skiplist_find_lt(&h->sh, \
+ &item->field.si, cmpfn_nuq); \
+ return container_of_null(sitem, type, field.si); \
+} \
+TYPESAFE_FIND_CMP(prefix, type) \
+macro_inline type *prefix ## _del(struct prefix##_head *h, type *item) \
+{ \
+ struct sskip_item *sitem = typesafe_skiplist_del(&h->sh, \
+ &item->field.si, cmpfn_uq); \
+ return container_of_null(sitem, type, field.si); \
+} \
+macro_inline type *prefix ## _pop(struct prefix##_head *h) \
+{ \
+ struct sskip_item *sitem = typesafe_skiplist_pop(&h->sh); \
+ return container_of_null(sitem, type, field.si); \
+} \
+macro_inline void prefix ## _swap_all(struct prefix##_head *a, \
+ struct prefix##_head *b) \
+{ \
+ struct prefix##_head tmp = *a; \
+ *a = *b; \
+ *b = tmp; \
+ a->sh.hitem.next[SKIPLIST_OVERFLOW] = (struct sskip_item *) \
+ ((uintptr_t)a->sh.overflow | 1); \
+ b->sh.hitem.next[SKIPLIST_OVERFLOW] = (struct sskip_item *) \
+ ((uintptr_t)b->sh.overflow | 1); \
+} \
+macro_pure const type *prefix ## _const_first(const struct prefix##_head *h) \
+{ \
+ const struct sskip_item *first = h->sh.hitem.next[0]; \
+ return container_of_null(first, type, field.si); \
+} \
+macro_pure const type *prefix ## _const_next(const struct prefix##_head *h, \
+ const type *item) \
+{ \
+ const struct sskip_item *next = item->field.si.next[0]; \
+ return container_of_null(next, type, field.si); \
+} \
+TYPESAFE_FIRST_NEXT(prefix, type) \
+macro_pure type *prefix ## _next_safe(struct prefix##_head *h, type *item) \
+{ \
+ struct sskip_item *next; \
+ next = item ? item->field.si.next[0] : NULL; \
+ return container_of_null(next, type, field.si); \
+} \
+macro_pure size_t prefix ## _count(const struct prefix##_head *h) \
+{ \
+ return h->sh.count; \
+} \
+MACRO_REQUIRE_SEMICOLON() /* end */
+
+#define PREDECL_SKIPLIST_UNIQ(prefix) \
+ _PREDECL_SKIPLIST(prefix)
+#define DECLARE_SKIPLIST_UNIQ(prefix, type, field, cmpfn) \
+ \
+macro_inline int prefix ## __cmp(const struct sskip_item *a, \
+ const struct sskip_item *b) \
+{ \
+ return cmpfn(container_of(a, type, field.si), \
+ container_of(b, type, field.si)); \
+} \
+macro_inline const type *prefix ## _const_find(const struct prefix##_head *h, \
+ const type *item) \
+{ \
+ const struct sskip_item *sitem = typesafe_skiplist_find(&h->sh, \
+ &item->field.si, &prefix ## __cmp); \
+ return container_of_null(sitem, type, field.si); \
+} \
+TYPESAFE_FIND(prefix, type) \
+TYPESAFE_MEMBER_VIA_FIND(prefix, type) \
+ \
+_DECLARE_SKIPLIST(prefix, type, field, \
+ prefix ## __cmp, prefix ## __cmp); \
+MACRO_REQUIRE_SEMICOLON() /* end */
+
+#define PREDECL_SKIPLIST_NONUNIQ(prefix) \
+ _PREDECL_SKIPLIST(prefix)
+#define DECLARE_SKIPLIST_NONUNIQ(prefix, type, field, cmpfn) \
+ \
+macro_inline int prefix ## __cmp(const struct sskip_item *a, \
+ const struct sskip_item *b) \
+{ \
+ return cmpfn(container_of(a, type, field.si), \
+ container_of(b, type, field.si)); \
+} \
+macro_inline int prefix ## __cmp_uq(const struct sskip_item *a, \
+ const struct sskip_item *b) \
+{ \
+ int cmpval = cmpfn(container_of(a, type, field.si), \
+ container_of(b, type, field.si)); \
+ if (cmpval) \
+ return cmpval; \
+ if (a < b) \
+ return -1; \
+ if (a > b) \
+ return 1; \
+ return 0; \
+} \
+ \
+_DECLARE_SKIPLIST(prefix, type, field, \
+ prefix ## __cmp, prefix ## __cmp_uq); \
+TYPESAFE_MEMBER_VIA_FIND_GTEQ(prefix, type, cmpfn) \
+MACRO_REQUIRE_SEMICOLON() /* end */
+
+
+extern struct sskip_item *typesafe_skiplist_add(struct sskip_head *head,
+ struct sskip_item *item, int (*cmpfn)(
+ const struct sskip_item *a,
+ const struct sskip_item *b));
+extern const struct sskip_item *typesafe_skiplist_find(
+ const struct sskip_head *head,
+ const struct sskip_item *item, int (*cmpfn)(
+ const struct sskip_item *a,
+ const struct sskip_item *b));
+extern const struct sskip_item *typesafe_skiplist_find_gteq(
+ const struct sskip_head *head,
+ const struct sskip_item *item, int (*cmpfn)(
+ const struct sskip_item *a,
+ const struct sskip_item *b));
+extern const struct sskip_item *typesafe_skiplist_find_lt(
+ const struct sskip_head *head,
+ const struct sskip_item *item, int (*cmpfn)(
+ const struct sskip_item *a,
+ const struct sskip_item *b));
+extern struct sskip_item *typesafe_skiplist_del(
+ struct sskip_head *head, struct sskip_item *item, int (*cmpfn)(
+ const struct sskip_item *a,
+ const struct sskip_item *b));
+extern struct sskip_item *typesafe_skiplist_pop(struct sskip_head *head);
+
+#ifdef __cplusplus
+}
+#endif
+
+/* this needs to stay at the end because both files include each other.
+ * the resolved order is typesafe.h before typerb.h
+ */
+#include "typerb.h"
+
+#endif /* _FRR_TYPESAFE_H */
diff --git a/lib/vector.c b/lib/vector.c
new file mode 100644
index 0000000..5497c24
--- /dev/null
+++ b/lib/vector.c
@@ -0,0 +1,227 @@
+/* Generic vector interface routine
+ * Copyright (C) 1997 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 "vector.h"
+#include "memory.h"
+
+DEFINE_MTYPE_STATIC(LIB, VECTOR, "Vector");
+DEFINE_MTYPE_STATIC(LIB, VECTOR_INDEX, "Vector index");
+
+/* Initialize vector : allocate memory and return vector. */
+vector vector_init(unsigned int size)
+{
+ vector v = XCALLOC(MTYPE_VECTOR, sizeof(struct _vector));
+
+ /* allocate at least one slot */
+ if (size == 0)
+ size = 1;
+
+ v->alloced = size;
+ v->active = 0;
+ v->count = 0;
+ v->index = XCALLOC(MTYPE_VECTOR_INDEX, sizeof(void *) * size);
+ return v;
+}
+
+void vector_free(vector v)
+{
+ XFREE(MTYPE_VECTOR_INDEX, v->index);
+ XFREE(MTYPE_VECTOR, v);
+}
+
+vector vector_copy(vector v)
+{
+ unsigned int size;
+ vector new = XCALLOC(MTYPE_VECTOR, sizeof(struct _vector));
+
+ new->active = v->active;
+ new->alloced = v->alloced;
+ new->count = v->count;
+
+ size = sizeof(void *) * (v->alloced);
+ new->index = XCALLOC(MTYPE_VECTOR_INDEX, size);
+ memcpy(new->index, v->index, size);
+
+ return new;
+}
+
+/* Check assigned index, and if it runs short double index pointer */
+void vector_ensure(vector v, unsigned int num)
+{
+ if (v->alloced > num)
+ return;
+
+ v->index = XREALLOC(MTYPE_VECTOR_INDEX, v->index,
+ sizeof(void *) * (v->alloced * 2));
+ memset(&v->index[v->alloced], 0, sizeof(void *) * v->alloced);
+ v->alloced *= 2;
+
+ if (v->alloced <= num)
+ vector_ensure(v, num);
+}
+
+/* This function only returns next empty slot index. It dose not mean
+ the slot's index memory is assigned, please call vector_ensure()
+ after calling this function. */
+int vector_empty_slot(vector v)
+{
+ unsigned int i;
+
+ if (v->active == v->count)
+ return v->active;
+
+ if (v->active == 0)
+ return 0;
+
+ for (i = 0; i < v->active; i++)
+ if (v->index[i] == 0)
+ return i;
+
+ return i;
+}
+
+/* Set value to the smallest empty slot. */
+int vector_set(vector v, void *val)
+{
+ unsigned int i;
+
+ i = vector_empty_slot(v);
+ vector_ensure(v, i);
+
+ if (v->index[i])
+ v->count--;
+ if (val)
+ v->count++;
+ v->index[i] = val;
+
+ if (v->active <= i)
+ v->active = i + 1;
+
+ return i;
+}
+
+/* Set value to specified index slot. */
+int vector_set_index(vector v, unsigned int i, void *val)
+{
+ vector_ensure(v, i);
+
+ if (v->index[i])
+ v->count--;
+ if (val)
+ v->count++;
+ v->index[i] = val;
+
+ if (v->active <= i)
+ v->active = i + 1;
+
+ return i;
+}
+
+/* Look up vector. */
+void *vector_lookup(vector v, unsigned int i)
+{
+ if (i >= v->active)
+ return NULL;
+ return v->index[i];
+}
+
+/* Lookup vector, ensure it. */
+void *vector_lookup_ensure(vector v, unsigned int i)
+{
+ vector_ensure(v, i);
+ return v->index[i];
+}
+
+/* Unset value at specified index slot. */
+void vector_unset(vector v, unsigned int i)
+{
+ if (i >= v->alloced)
+ return;
+
+ if (v->index[i])
+ v->count--;
+
+ v->index[i] = NULL;
+
+ if (i + 1 == v->active) {
+ v->active--;
+ while (i && v->index[--i] == NULL && v->active--)
+ ; /* Is this ugly ? */
+ }
+}
+
+void vector_remove(vector v, unsigned int ix)
+{
+ if (ix >= v->active)
+ return;
+
+ if (v->index[ix])
+ v->count--;
+
+ int n = (--v->active) - ix;
+
+ memmove(&v->index[ix], &v->index[ix + 1], n * sizeof(void *));
+ v->index[v->active] = NULL;
+}
+
+void vector_compact(vector v)
+{
+ for (unsigned int i = 0; i < vector_active(v); ++i) {
+ if (vector_slot(v, i) == NULL) {
+ vector_remove(v, i);
+ --i;
+ }
+ }
+}
+
+void vector_unset_value(vector v, void *val)
+{
+ size_t i;
+
+ for (i = 0; i < v->active; i++)
+ if (v->index[i] == val) {
+ v->index[i] = NULL;
+ v->count--;
+ break;
+ }
+
+ if (i + 1 == v->active)
+ do
+ v->active--;
+ while (i && v->index[--i] == NULL);
+}
+
+void vector_to_array(vector v, void ***dest, int *argc)
+{
+ *dest = XCALLOC(MTYPE_TMP, sizeof(void *) * v->active);
+ memcpy(*dest, v->index, sizeof(void *) * v->active);
+ *argc = v->active;
+}
+
+vector array_to_vector(void **src, int argc)
+{
+ vector v = vector_init(VECTOR_MIN_SIZE);
+
+ for (int i = 0; i < argc; i++)
+ vector_set_index(v, i, src[i]);
+ return v;
+}
diff --git a/lib/vector.h b/lib/vector.h
new file mode 100644
index 0000000..71c497a
--- /dev/null
+++ b/lib/vector.h
@@ -0,0 +1,80 @@
+/*
+ * Generic vector interface header.
+ * Copyright (C) 1997, 98 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 _ZEBRA_VECTOR_H
+#define _ZEBRA_VECTOR_H
+
+#include "memory.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* struct for vector */
+struct _vector {
+ unsigned int active; /* number of active slots */
+ unsigned int alloced; /* number of allocated slot */
+ unsigned int count;
+ void **index; /* index to data */
+};
+typedef struct _vector *vector;
+
+#define VECTOR_MIN_SIZE 1
+
+/* (Sometimes) usefull macros. This macro convert index expression to
+ array expression. */
+/* Reference slot at given index, caller must ensure slot is active */
+#define vector_slot(V,I) ((V)->index[(I)])
+/* Number of active slots.
+ * Note that this differs from vector_count() as it the count returned
+ * will include any empty slots
+ */
+#define vector_active(V) ((V)->active)
+
+/* Prototypes. */
+extern vector vector_init(unsigned int size);
+extern void vector_ensure(vector v, unsigned int num);
+extern int vector_empty_slot(vector v);
+extern int vector_set(vector v, void *val);
+extern int vector_set_index(vector v, unsigned int i, void *val);
+extern void vector_unset(vector v, unsigned int i);
+extern void vector_unset_value(vector v, void *val);
+extern void vector_remove(vector v, unsigned int ix);
+extern void vector_compact(vector v);
+
+static inline unsigned int vector_count(vector v)
+{
+ return v->count;
+}
+
+extern void vector_free(vector v);
+extern vector vector_copy(vector v);
+
+extern void *vector_lookup(vector, unsigned int);
+extern void *vector_lookup_ensure(vector, unsigned int);
+extern void vector_to_array(vector v, void ***dest, int *argc);
+extern vector array_to_vector(void **src, int argc);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ZEBRA_VECTOR_H */
diff --git a/lib/version.h.in b/lib/version.h.in
new file mode 100644
index 0000000..5078f3a
--- /dev/null
+++ b/lib/version.h.in
@@ -0,0 +1,65 @@
+/* @configure_input@
+ *
+ * Quagga version
+ * Copyright (C) 1997, 1999 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 GNU Zebra; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef _ZEBRA_VERSION_H
+#define _ZEBRA_VERSION_H
+
+#ifdef GIT_VERSION
+#include "lib/gitversion.h"
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef GIT_SUFFIX
+#define GIT_SUFFIX ""
+#endif
+#ifndef GIT_INFO
+#define GIT_INFO ""
+#endif
+
+#define FRR_PAM_NAME "@PACKAGE_NAME@"
+#define FRR_SMUX_NAME "@PACKAGE_NAME@"
+#define FRR_PTM_NAME "@PACKAGE_NAME@"
+
+#define FRR_FULL_NAME "FRRouting"
+#define FRR_VERSION "@PACKAGE_VERSION@" GIT_SUFFIX
+#define FRR_VER_SHORT "@PACKAGE_VERSION@"
+#define FRR_BUG_ADDRESS "@PACKAGE_BUGREPORT@"
+#define FRR_COPYRIGHT "Copyright 1996-2005 Kunihiro Ishiguro, et al."
+#define FRR_CONFIG_ARGS "@CONFIG_ARGS@"
+
+#define FRR_DEFAULT_MOTD \
+ "\n" \
+ "Hello, this is " FRR_FULL_NAME " (version " FRR_VERSION ").\n" \
+ FRR_COPYRIGHT "\n" \
+ GIT_INFO "\n"
+
+pid_t pid_output (const char *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ZEBRA_VERSION_H */
diff --git a/lib/vlan.h b/lib/vlan.h
new file mode 100644
index 0000000..eea2633
--- /dev/null
+++ b/lib/vlan.h
@@ -0,0 +1,37 @@
+/* VLAN (802.1q) common header.
+ * Copyright (C) 2016, 2017 Cumulus Networks, Inc.
+ *
+ * This file is part of FRR.
+ *
+ * FRR 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, or (at your option) any
+ * later version.
+ *
+ * FRR 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 FRR; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef __VLAN_H__
+#define __VLAN_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* VLAN Identifier */
+typedef uint16_t vlanid_t;
+#define VLANID_MAX 4095
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __VLAN_H__ */
diff --git a/lib/vrf.c b/lib/vrf.c
new file mode 100644
index 0000000..5878c17
--- /dev/null
+++ b/lib/vrf.c
@@ -0,0 +1,1041 @@
+/*
+ * VRF functions.
+ * Copyright (C) 2014 6WIND S.A.
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra 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 "if.h"
+#include "vrf.h"
+#include "vrf_int.h"
+#include "prefix.h"
+#include "table.h"
+#include "log.h"
+#include "memory.h"
+#include "command.h"
+#include "ns.h"
+#include "privs.h"
+#include "nexthop_group.h"
+#include "lib_errors.h"
+#include "northbound.h"
+#include "northbound_cli.h"
+
+/* default VRF name value used when VRF backend is not NETNS */
+#define VRF_DEFAULT_NAME_INTERNAL "default"
+
+DEFINE_MTYPE_STATIC(LIB, VRF, "VRF");
+DEFINE_MTYPE_STATIC(LIB, VRF_BITMAP, "VRF bit-map");
+
+DEFINE_QOBJ_TYPE(vrf);
+
+static __inline int vrf_id_compare(const struct vrf *, const struct vrf *);
+static __inline int vrf_name_compare(const struct vrf *, const struct vrf *);
+
+RB_GENERATE(vrf_id_head, vrf, id_entry, vrf_id_compare);
+RB_GENERATE(vrf_name_head, vrf, name_entry, vrf_name_compare);
+
+struct vrf_id_head vrfs_by_id = RB_INITIALIZER(&vrfs_by_id);
+struct vrf_name_head vrfs_by_name = RB_INITIALIZER(&vrfs_by_name);
+
+static int vrf_backend;
+static int vrf_backend_configured;
+static char vrf_default_name[VRF_NAMSIZ] = VRF_DEFAULT_NAME_INTERNAL;
+
+/*
+ * Turn on/off debug code
+ * for vrf.
+ */
+static int debug_vrf = 0;
+
+/* Holding VRF hooks */
+static struct vrf_master {
+ int (*vrf_new_hook)(struct vrf *);
+ int (*vrf_delete_hook)(struct vrf *);
+ int (*vrf_enable_hook)(struct vrf *);
+ int (*vrf_disable_hook)(struct vrf *);
+} vrf_master = {
+ 0,
+};
+
+static int vrf_is_enabled(struct vrf *vrf);
+
+/* VRF list existance check by name. */
+struct vrf *vrf_lookup_by_name(const char *name)
+{
+ struct vrf vrf;
+ strlcpy(vrf.name, name, sizeof(vrf.name));
+ return (RB_FIND(vrf_name_head, &vrfs_by_name, &vrf));
+}
+
+static __inline int vrf_id_compare(const struct vrf *a, const struct vrf *b)
+{
+ return (a->vrf_id - b->vrf_id);
+}
+
+static int vrf_name_compare(const struct vrf *a, const struct vrf *b)
+{
+ return strcmp(a->name, b->name);
+}
+
+int vrf_switch_to_netns(vrf_id_t vrf_id)
+{
+ char *name;
+ struct vrf *vrf = vrf_lookup_by_id(vrf_id);
+
+ /* VRF is default VRF. silently ignore */
+ if (!vrf || vrf->vrf_id == VRF_DEFAULT)
+ return 1; /* 1 = default */
+ /* VRF has no NETNS backend. silently ignore */
+ if (vrf->data.l.netns_name[0] == '\0')
+ return 2; /* 2 = no netns */
+ name = ns_netns_pathname(NULL, vrf->data.l.netns_name);
+ if (debug_vrf)
+ zlog_debug("VRF_SWITCH: %s(%u)", name, vrf->vrf_id);
+ return ns_switch_to_netns(name);
+}
+
+int vrf_switchback_to_initial(void)
+{
+ int ret = ns_switchback_to_initial();
+
+ if (ret == 0 && debug_vrf)
+ zlog_debug("VRF_SWITCHBACK");
+ return ret;
+}
+
+/* Get a VRF. If not found, create one.
+ * Arg:
+ * name - The name of the vrf. May be NULL if unknown.
+ * vrf_id - The vrf_id of the vrf. May be VRF_UNKNOWN if unknown
+ * Description: Please note that this routine can be called with just the name
+ * and 0 vrf-id
+ */
+struct vrf *vrf_get(vrf_id_t vrf_id, const char *name)
+{
+ struct vrf *vrf = NULL;
+ int new = 0;
+
+ /* Nothing to see, move along here */
+ if (!name && vrf_id == VRF_UNKNOWN)
+ return NULL;
+
+ /* attempt to find already available VRF
+ */
+ if (name)
+ vrf = vrf_lookup_by_name(name);
+ if (vrf && vrf_id != VRF_UNKNOWN
+ && vrf->vrf_id != VRF_UNKNOWN
+ && vrf->vrf_id != vrf_id) {
+ zlog_debug("VRF_GET: avoid %s creation(%u), same name exists (%u)",
+ name, vrf_id, vrf->vrf_id);
+ return NULL;
+ }
+ /* Try to find VRF both by ID and name */
+ if (!vrf && vrf_id != VRF_UNKNOWN)
+ vrf = vrf_lookup_by_id(vrf_id);
+
+ if (vrf == NULL) {
+ vrf = XCALLOC(MTYPE_VRF, sizeof(struct vrf));
+ vrf->vrf_id = VRF_UNKNOWN;
+ QOBJ_REG(vrf, vrf);
+ new = 1;
+
+ if (debug_vrf)
+ zlog_debug("VRF(%u) %s is created.", vrf_id,
+ (name) ? name : "(NULL)");
+ }
+
+ /* Set identifier */
+ if (vrf_id != VRF_UNKNOWN && vrf->vrf_id == VRF_UNKNOWN) {
+ vrf->vrf_id = vrf_id;
+ RB_INSERT(vrf_id_head, &vrfs_by_id, vrf);
+ }
+
+ /* Set name */
+ if (name && vrf->name[0] != '\0' && strcmp(name, vrf->name)) {
+ /* update the vrf name */
+ RB_REMOVE(vrf_name_head, &vrfs_by_name, vrf);
+ strlcpy(vrf->data.l.netns_name,
+ name, NS_NAMSIZ);
+ strlcpy(vrf->name, name, sizeof(vrf->name));
+ RB_INSERT(vrf_name_head, &vrfs_by_name, vrf);
+ } else if (name && vrf->name[0] == '\0') {
+ strlcpy(vrf->name, name, sizeof(vrf->name));
+ RB_INSERT(vrf_name_head, &vrfs_by_name, vrf);
+ }
+ if (new &&vrf_master.vrf_new_hook)
+ (*vrf_master.vrf_new_hook)(vrf);
+
+ return vrf;
+}
+
+/* Update a VRF. If not found, create one.
+ * Arg:
+ * name - The name of the vrf.
+ * vrf_id - The vrf_id of the vrf.
+ * Description: This function first finds the vrf using its name. If the vrf is
+ * found and the vrf-id of the existing vrf does not match the new vrf id, it
+ * will disable the existing vrf and update it with new vrf-id. If the vrf is
+ * not found, it will create the vrf with given name and the new vrf id.
+ */
+struct vrf *vrf_update(vrf_id_t new_vrf_id, const char *name)
+{
+ struct vrf *vrf = NULL;
+
+ /*Treat VRF add for existing vrf as update
+ * Update VRF ID and also update in VRF ID table
+ */
+ if (name)
+ vrf = vrf_lookup_by_name(name);
+ if (vrf && new_vrf_id != VRF_UNKNOWN && vrf->vrf_id != VRF_UNKNOWN
+ && vrf->vrf_id != new_vrf_id) {
+ if (debug_vrf) {
+ zlog_debug(
+ "Vrf Update event: %s old id: %u, new id: %u",
+ name, vrf->vrf_id, new_vrf_id);
+ }
+
+ /*Disable the vrf to simulate implicit delete
+ * so that all stale routes are deleted
+ * This vrf will be enabled down the line
+ */
+ vrf_disable(vrf);
+
+
+ RB_REMOVE(vrf_id_head, &vrfs_by_id, vrf);
+ vrf->vrf_id = new_vrf_id;
+ RB_INSERT(vrf_id_head, &vrfs_by_id, vrf);
+
+ } else {
+
+ /*
+ * vrf_get is implied creation if it does not exist
+ */
+ vrf = vrf_get(new_vrf_id, name);
+ }
+ return vrf;
+}
+
+/* Delete a VRF. This is called when the underlying VRF goes away, a
+ * pre-configured VRF is deleted or when shutting down (vrf_terminate()).
+ */
+void vrf_delete(struct vrf *vrf)
+{
+ if (debug_vrf)
+ zlog_debug("VRF %s(%u) is to be deleted.", vrf->name,
+ vrf->vrf_id);
+
+ if (vrf_is_enabled(vrf))
+ vrf_disable(vrf);
+
+ if (vrf->vrf_id != VRF_UNKNOWN) {
+ RB_REMOVE(vrf_id_head, &vrfs_by_id, vrf);
+ vrf->vrf_id = VRF_UNKNOWN;
+ }
+
+ /* If the VRF is user configured, it'll stick around, just remove
+ * the ID mapping. Interfaces assigned to this VRF should've been
+ * removed already as part of the VRF going down.
+ */
+ if (vrf_is_user_cfged(vrf))
+ return;
+
+ /* Do not delete the VRF if it has interfaces configured in it. */
+ if (!RB_EMPTY(if_name_head, &vrf->ifaces_by_name))
+ return;
+
+ if (vrf_master.vrf_delete_hook)
+ (*vrf_master.vrf_delete_hook)(vrf);
+
+ QOBJ_UNREG(vrf);
+
+ if (vrf->name[0] != '\0')
+ RB_REMOVE(vrf_name_head, &vrfs_by_name, vrf);
+
+ XFREE(MTYPE_VRF, vrf);
+}
+
+/* Look up a VRF by identifier. */
+struct vrf *vrf_lookup_by_id(vrf_id_t vrf_id)
+{
+ struct vrf vrf;
+ vrf.vrf_id = vrf_id;
+ return (RB_FIND(vrf_id_head, &vrfs_by_id, &vrf));
+}
+
+/*
+ * Enable a VRF - that is, let the VRF be ready to use.
+ * The VRF_ENABLE_HOOK callback will be called to inform
+ * that they can allocate resources in this VRF.
+ *
+ * RETURN: 1 - enabled successfully; otherwise, 0.
+ */
+int vrf_enable(struct vrf *vrf)
+{
+ if (vrf_is_enabled(vrf))
+ return 1;
+
+ if (debug_vrf)
+ zlog_debug("VRF %s(%u) is enabled.", vrf->name, vrf->vrf_id);
+
+ SET_FLAG(vrf->status, VRF_ACTIVE);
+
+ if (vrf_master.vrf_enable_hook)
+ (*vrf_master.vrf_enable_hook)(vrf);
+
+ /*
+ * If we have any nexthop group entries that
+ * are awaiting vrf initialization then
+ * let's let people know about it
+ */
+ nexthop_group_enable_vrf(vrf);
+
+ return 1;
+}
+
+/*
+ * Disable a VRF - that is, let the VRF be unusable.
+ * The VRF_DELETE_HOOK callback will be called to inform
+ * that they must release the resources in the VRF.
+ */
+void vrf_disable(struct vrf *vrf)
+{
+ if (!vrf_is_enabled(vrf))
+ return;
+
+ UNSET_FLAG(vrf->status, VRF_ACTIVE);
+
+ if (debug_vrf)
+ zlog_debug("VRF %s(%u) is to be disabled.", vrf->name,
+ vrf->vrf_id);
+
+ /* Till now, nothing to be done for the default VRF. */
+ // Pending: see why this statement.
+
+
+ /*
+ * When the vrf is disabled let's
+ * handle all nexthop-groups associated
+ * with this vrf
+ */
+ nexthop_group_disable_vrf(vrf);
+
+ if (vrf_master.vrf_disable_hook)
+ (*vrf_master.vrf_disable_hook)(vrf);
+}
+
+const char *vrf_id_to_name(vrf_id_t vrf_id)
+{
+ struct vrf *vrf;
+
+ if (vrf_id == VRF_DEFAULT)
+ return VRF_DEFAULT_NAME;
+
+ vrf = vrf_lookup_by_id(vrf_id);
+ return VRF_LOGNAME(vrf);
+}
+
+/* Look up the data pointer of the specified VRF. */
+void *vrf_info_lookup(vrf_id_t vrf_id)
+{
+ struct vrf *vrf = vrf_lookup_by_id(vrf_id);
+ return vrf ? vrf->info : NULL;
+}
+
+/*
+ * VRF hash for storing set or not.
+ */
+struct vrf_bit_set {
+ vrf_id_t vrf_id;
+ bool set;
+};
+
+static unsigned int vrf_hash_bitmap_key(const void *data)
+{
+ const struct vrf_bit_set *bit = data;
+
+ return bit->vrf_id;
+}
+
+static bool vrf_hash_bitmap_cmp(const void *a, const void *b)
+{
+ const struct vrf_bit_set *bit1 = a;
+ const struct vrf_bit_set *bit2 = b;
+
+ return bit1->vrf_id == bit2->vrf_id;
+}
+
+static void *vrf_hash_bitmap_alloc(void *data)
+{
+ struct vrf_bit_set *copy = data;
+ struct vrf_bit_set *bit;
+
+ bit = XMALLOC(MTYPE_VRF_BITMAP, sizeof(*bit));
+ bit->vrf_id = copy->vrf_id;
+
+ return bit;
+}
+
+static void vrf_hash_bitmap_free(void *data)
+{
+ struct vrf_bit_set *bit = data;
+
+ XFREE(MTYPE_VRF_BITMAP, bit);
+}
+
+vrf_bitmap_t vrf_bitmap_init(void)
+{
+ return hash_create_size(32, vrf_hash_bitmap_key, vrf_hash_bitmap_cmp,
+ "VRF BIT HASH");
+}
+
+void vrf_bitmap_free(vrf_bitmap_t bmap)
+{
+ struct hash *vrf_hash = bmap;
+
+ if (vrf_hash == NULL)
+ return;
+
+ hash_clean(vrf_hash, vrf_hash_bitmap_free);
+ hash_free(vrf_hash);
+}
+
+void vrf_bitmap_set(vrf_bitmap_t bmap, vrf_id_t vrf_id)
+{
+ struct vrf_bit_set lookup = { .vrf_id = vrf_id };
+ struct hash *vrf_hash = bmap;
+ struct vrf_bit_set *bit;
+
+ if (vrf_hash == NULL || vrf_id == VRF_UNKNOWN)
+ return;
+
+ bit = hash_get(vrf_hash, &lookup, vrf_hash_bitmap_alloc);
+ bit->set = true;
+}
+
+void vrf_bitmap_unset(vrf_bitmap_t bmap, vrf_id_t vrf_id)
+{
+ struct vrf_bit_set lookup = { .vrf_id = vrf_id };
+ struct hash *vrf_hash = bmap;
+ struct vrf_bit_set *bit;
+
+ if (vrf_hash == NULL || vrf_id == VRF_UNKNOWN)
+ return;
+
+ bit = hash_get(vrf_hash, &lookup, vrf_hash_bitmap_alloc);
+ bit->set = false;
+}
+
+int vrf_bitmap_check(vrf_bitmap_t bmap, vrf_id_t vrf_id)
+{
+ struct vrf_bit_set lookup = { .vrf_id = vrf_id };
+ struct hash *vrf_hash = bmap;
+ struct vrf_bit_set *bit;
+
+ if (vrf_hash == NULL || vrf_id == VRF_UNKNOWN)
+ return 0;
+
+ bit = hash_lookup(vrf_hash, &lookup);
+ if (bit)
+ return bit->set;
+
+ return 0;
+}
+
+static void vrf_autocomplete(vector comps, struct cmd_token *token)
+{
+ struct vrf *vrf = NULL;
+
+ RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name)
+ vector_set(comps, XSTRDUP(MTYPE_COMPLETION, vrf->name));
+}
+
+static const struct cmd_variable_handler vrf_var_handlers[] = {
+ {
+ .varname = "vrf",
+ .completions = vrf_autocomplete,
+ },
+ {
+ .varname = "vrf_name",
+ .completions = vrf_autocomplete,
+ },
+ {
+ .varname = "nexthop_vrf",
+ .completions = vrf_autocomplete,
+ },
+ {.completions = NULL},
+};
+
+/* Initialize VRF module. */
+void vrf_init(int (*create)(struct vrf *), int (*enable)(struct vrf *),
+ int (*disable)(struct vrf *), int (*destroy)(struct vrf *))
+{
+ struct vrf *default_vrf;
+
+ /* initialise NS, in case VRF backend if NETNS */
+ ns_init();
+ if (debug_vrf)
+ zlog_debug("%s: Initializing VRF subsystem", __func__);
+
+ vrf_master.vrf_new_hook = create;
+ vrf_master.vrf_enable_hook = enable;
+ vrf_master.vrf_disable_hook = disable;
+ vrf_master.vrf_delete_hook = destroy;
+
+ /* The default VRF always exists. */
+ default_vrf = vrf_get(VRF_DEFAULT, VRF_DEFAULT_NAME);
+ if (!default_vrf) {
+ flog_err(EC_LIB_VRF_START,
+ "vrf_init: failed to create the default VRF!");
+ exit(1);
+ }
+ if (vrf_is_backend_netns()) {
+ struct ns *ns;
+
+ strlcpy(default_vrf->data.l.netns_name,
+ VRF_DEFAULT_NAME, NS_NAMSIZ);
+ ns = ns_lookup(NS_DEFAULT);
+ ns->vrf_ctxt = default_vrf;
+ default_vrf->ns_ctxt = ns;
+ }
+
+ /* Enable the default VRF. */
+ if (!vrf_enable(default_vrf)) {
+ flog_err(EC_LIB_VRF_START,
+ "vrf_init: failed to enable the default VRF!");
+ exit(1);
+ }
+
+ cmd_variable_handler_register(vrf_var_handlers);
+}
+
+static void vrf_terminate_single(struct vrf *vrf)
+{
+ /* Clear configured flag and invoke delete. */
+ vrf_disable(vrf);
+ UNSET_FLAG(vrf->status, VRF_CONFIGURED);
+ if_terminate(vrf);
+ vrf_delete(vrf);
+}
+
+/* Terminate VRF module. */
+void vrf_terminate(void)
+{
+ struct vrf *vrf, *tmp;
+
+ if (debug_vrf)
+ zlog_debug("%s: Shutting down vrf subsystem", __func__);
+
+ RB_FOREACH_SAFE (vrf, vrf_id_head, &vrfs_by_id, tmp) {
+ if (vrf->vrf_id == VRF_DEFAULT)
+ continue;
+
+ vrf_terminate_single(vrf);
+ }
+
+ RB_FOREACH_SAFE (vrf, vrf_name_head, &vrfs_by_name, tmp) {
+ if (vrf->vrf_id == VRF_DEFAULT)
+ continue;
+
+ vrf_terminate_single(vrf);
+ }
+
+ /* Finally terminate default VRF */
+ vrf = vrf_lookup_by_id(VRF_DEFAULT);
+ if (vrf)
+ vrf_terminate_single(vrf);
+}
+
+int vrf_socket(int domain, int type, int protocol, vrf_id_t vrf_id,
+ const char *interfacename)
+{
+ int ret, save_errno, ret2;
+
+ ret = vrf_switch_to_netns(vrf_id);
+ if (ret < 0)
+ flog_err_sys(EC_LIB_SOCKET, "%s: Can't switch to VRF %u (%s)",
+ __func__, vrf_id, safe_strerror(errno));
+
+ ret = socket(domain, type, protocol);
+ save_errno = errno;
+ ret2 = vrf_switchback_to_initial();
+ if (ret2 < 0)
+ flog_err_sys(EC_LIB_SOCKET,
+ "%s: Can't switchback from VRF %u (%s)", __func__,
+ vrf_id, safe_strerror(errno));
+ errno = save_errno;
+ if (ret <= 0)
+ return ret;
+ ret2 = vrf_bind(vrf_id, ret, interfacename);
+ if (ret2 < 0) {
+ close(ret);
+ ret = ret2;
+ }
+ return ret;
+}
+
+int vrf_is_backend_netns(void)
+{
+ return (vrf_backend == VRF_BACKEND_NETNS);
+}
+
+int vrf_get_backend(void)
+{
+ if (!vrf_backend_configured)
+ return VRF_BACKEND_UNKNOWN;
+ return vrf_backend;
+}
+
+int vrf_configure_backend(enum vrf_backend_type backend)
+{
+ /* Work around issue in old gcc */
+ switch (backend) {
+ case VRF_BACKEND_UNKNOWN:
+ case VRF_BACKEND_NETNS:
+ case VRF_BACKEND_VRF_LITE:
+ break;
+ default:
+ return -1;
+ }
+
+ vrf_backend = backend;
+ vrf_backend_configured = 1;
+
+ return 0;
+}
+
+/* vrf CLI commands */
+DEFUN_NOSH(vrf_exit,
+ vrf_exit_cmd,
+ "exit-vrf",
+ "Exit current mode and down to previous mode\n")
+{
+ cmd_exit(vty);
+ return CMD_SUCCESS;
+}
+
+DEFUN_YANG_NOSH (vrf,
+ vrf_cmd,
+ "vrf NAME",
+ "Select a VRF to configure\n"
+ "VRF's name\n")
+{
+ int idx_name = 1;
+ const char *vrfname = argv[idx_name]->arg;
+ char xpath_list[XPATH_MAXLEN];
+ struct vrf *vrf;
+ int ret;
+
+ if (strlen(vrfname) > VRF_NAMSIZ) {
+ vty_out(vty,
+ "%% VRF name %s invalid: length exceeds %d bytes\n",
+ vrfname, VRF_NAMSIZ);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ snprintf(xpath_list, sizeof(xpath_list), FRR_VRF_KEY_XPATH, vrfname);
+
+ nb_cli_enqueue_change(vty, xpath_list, NB_OP_CREATE, NULL);
+ ret = nb_cli_apply_changes_clear_pending(vty, xpath_list);
+ if (ret == CMD_SUCCESS) {
+ VTY_PUSH_XPATH(VRF_NODE, xpath_list);
+ vrf = vrf_lookup_by_name(vrfname);
+ if (vrf)
+ VTY_PUSH_CONTEXT(VRF_NODE, vrf);
+ }
+
+ return ret;
+}
+
+DEFUN_YANG (no_vrf,
+ no_vrf_cmd,
+ "no vrf NAME",
+ NO_STR
+ "Delete a pseudo VRF's configuration\n"
+ "VRF's name\n")
+{
+ const char *vrfname = argv[2]->arg;
+ char xpath_list[XPATH_MAXLEN];
+
+ struct vrf *vrfp;
+
+ vrfp = vrf_lookup_by_name(vrfname);
+
+ if (vrfp == NULL)
+ return CMD_SUCCESS;
+
+ if (CHECK_FLAG(vrfp->status, VRF_ACTIVE)) {
+ vty_out(vty, "%% Only inactive VRFs can be deleted\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ if (vrf_get_backend() == VRF_BACKEND_VRF_LITE) {
+ /*
+ * Remove the VRF interface config when removing the VRF.
+ */
+ snprintf(xpath_list, sizeof(xpath_list),
+ "/frr-interface:lib/interface[name='%s']", vrfname);
+ nb_cli_enqueue_change(vty, xpath_list, NB_OP_DESTROY, NULL);
+ }
+
+ snprintf(xpath_list, sizeof(xpath_list), FRR_VRF_KEY_XPATH, vrfname);
+
+ nb_cli_enqueue_change(vty, xpath_list, NB_OP_DESTROY, NULL);
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+
+static struct cmd_node vrf_node = {
+ .name = "vrf",
+ .node = VRF_NODE,
+ .parent_node = CONFIG_NODE,
+ .prompt = "%s(config-vrf)# ",
+};
+
+/*
+ * Debug CLI for vrf's
+ */
+DEFUN (vrf_debug,
+ vrf_debug_cmd,
+ "debug vrf",
+ DEBUG_STR
+ "VRF Debugging\n")
+{
+ debug_vrf = 1;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_vrf_debug,
+ no_vrf_debug_cmd,
+ "no debug vrf",
+ NO_STR
+ DEBUG_STR
+ "VRF Debugging\n")
+{
+ debug_vrf = 0;
+
+ return CMD_SUCCESS;
+}
+
+static int vrf_write_host(struct vty *vty)
+{
+ if (debug_vrf)
+ vty_out(vty, "debug vrf\n");
+
+ return 1;
+}
+
+static int vrf_write_host(struct vty *vty);
+static struct cmd_node vrf_debug_node = {
+ .name = "vrf debug",
+ .node = VRF_DEBUG_NODE,
+ .prompt = "",
+ .config_write = vrf_write_host,
+};
+
+void vrf_install_commands(void)
+{
+ install_node(&vrf_debug_node);
+
+ install_element(CONFIG_NODE, &vrf_debug_cmd);
+ install_element(ENABLE_NODE, &vrf_debug_cmd);
+ install_element(CONFIG_NODE, &no_vrf_debug_cmd);
+ install_element(ENABLE_NODE, &no_vrf_debug_cmd);
+}
+
+void vrf_cmd_init(int (*writefunc)(struct vty *vty))
+{
+ install_element(CONFIG_NODE, &vrf_cmd);
+ install_element(CONFIG_NODE, &no_vrf_cmd);
+ vrf_node.config_write = writefunc;
+ install_node(&vrf_node);
+ install_default(VRF_NODE);
+ install_element(VRF_NODE, &vrf_exit_cmd);
+}
+
+void vrf_set_default_name(const char *default_name)
+{
+ snprintf(vrf_default_name, VRF_NAMSIZ, "%s", default_name);
+}
+
+const char *vrf_get_default_name(void)
+{
+ return vrf_default_name;
+}
+
+int vrf_bind(vrf_id_t vrf_id, int fd, const char *ifname)
+{
+ int ret = 0;
+ struct interface *ifp;
+ struct vrf *vrf;
+
+ if (fd < 0)
+ return -1;
+
+ if (vrf_id == VRF_UNKNOWN)
+ return -1;
+
+ /* can't bind to a VRF that doesn't exist */
+ vrf = vrf_lookup_by_id(vrf_id);
+ if (!vrf_is_enabled(vrf))
+ return -1;
+
+ if (ifname && strcmp(ifname, vrf->name)) {
+ /* binding to a regular interface */
+
+ /* can't bind to an interface that doesn't exist */
+ ifp = if_lookup_by_name(ifname, vrf_id);
+ if (!ifp)
+ return -1;
+ } else {
+ /* binding to a VRF device */
+
+ /* nothing to do for netns */
+ if (vrf_is_backend_netns())
+ return 0;
+
+ /* nothing to do for default vrf */
+ if (vrf_id == VRF_DEFAULT)
+ return 0;
+
+ ifname = vrf->name;
+ }
+
+#ifdef SO_BINDTODEVICE
+ ret = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, ifname,
+ strlen(ifname) + 1);
+ if (ret < 0)
+ zlog_err("bind to interface %s failed, errno=%d", ifname,
+ errno);
+#endif /* SO_BINDTODEVICE */
+ return ret;
+}
+int vrf_getaddrinfo(const char *node, const char *service,
+ const struct addrinfo *hints, struct addrinfo **res,
+ vrf_id_t vrf_id)
+{
+ int ret, ret2, save_errno;
+
+ ret = vrf_switch_to_netns(vrf_id);
+ if (ret < 0)
+ flog_err_sys(EC_LIB_SOCKET, "%s: Can't switch to VRF %u (%s)",
+ __func__, vrf_id, safe_strerror(errno));
+ ret = getaddrinfo(node, service, hints, res);
+ save_errno = errno;
+ ret2 = vrf_switchback_to_initial();
+ if (ret2 < 0)
+ flog_err_sys(EC_LIB_SOCKET,
+ "%s: Can't switchback from VRF %u (%s)", __func__,
+ vrf_id, safe_strerror(errno));
+ errno = save_errno;
+ return ret;
+}
+
+int vrf_ioctl(vrf_id_t vrf_id, int d, unsigned long request, char *params)
+{
+ int ret, saved_errno, rc;
+
+ ret = vrf_switch_to_netns(vrf_id);
+ if (ret < 0) {
+ flog_err_sys(EC_LIB_SOCKET, "%s: Can't switch to VRF %u (%s)",
+ __func__, vrf_id, safe_strerror(errno));
+ return 0;
+ }
+ rc = ioctl(d, request, params);
+ saved_errno = errno;
+ ret = vrf_switchback_to_initial();
+ if (ret < 0)
+ flog_err_sys(EC_LIB_SOCKET,
+ "%s: Can't switchback from VRF %u (%s)", __func__,
+ vrf_id, safe_strerror(errno));
+ errno = saved_errno;
+ return rc;
+}
+
+int vrf_sockunion_socket(const union sockunion *su, vrf_id_t vrf_id,
+ const char *interfacename)
+{
+ int ret, save_errno, ret2;
+
+ ret = vrf_switch_to_netns(vrf_id);
+ if (ret < 0)
+ flog_err_sys(EC_LIB_SOCKET, "%s: Can't switch to VRF %u (%s)",
+ __func__, vrf_id, safe_strerror(errno));
+ ret = sockunion_socket(su);
+ save_errno = errno;
+ ret2 = vrf_switchback_to_initial();
+ if (ret2 < 0)
+ flog_err_sys(EC_LIB_SOCKET,
+ "%s: Can't switchback from VRF %u (%s)", __func__,
+ vrf_id, safe_strerror(errno));
+ errno = save_errno;
+
+ if (ret <= 0)
+ return ret;
+ ret2 = vrf_bind(vrf_id, ret, interfacename);
+ if (ret2 < 0) {
+ close(ret);
+ ret = ret2;
+ }
+ return ret;
+}
+
+/* ------- Northbound callbacks ------- */
+
+/*
+ * XPath: /frr-vrf:lib/vrf
+ */
+static int lib_vrf_create(struct nb_cb_create_args *args)
+{
+ const char *vrfname;
+ struct vrf *vrfp;
+
+ vrfname = yang_dnode_get_string(args->dnode, "./name");
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ vrfp = vrf_get(VRF_UNKNOWN, vrfname);
+
+ SET_FLAG(vrfp->status, VRF_CONFIGURED);
+ nb_running_set_entry(args->dnode, vrfp);
+
+ return NB_OK;
+}
+
+static int lib_vrf_destroy(struct nb_cb_destroy_args *args)
+{
+ struct vrf *vrfp;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ vrfp = nb_running_get_entry(args->dnode, NULL, true);
+ if (CHECK_FLAG(vrfp->status, VRF_ACTIVE)) {
+ snprintf(args->errmsg, args->errmsg_len,
+ "Only inactive VRFs can be deleted");
+ return NB_ERR_VALIDATION;
+ }
+ break;
+ case NB_EV_PREPARE:
+ case NB_EV_ABORT:
+ break;
+ case NB_EV_APPLY:
+ vrfp = nb_running_unset_entry(args->dnode);
+
+ /* Clear configured flag and invoke delete. */
+ UNSET_FLAG(vrfp->status, VRF_CONFIGURED);
+ vrf_delete(vrfp);
+ break;
+ }
+
+ return NB_OK;
+}
+
+static const void *lib_vrf_get_next(struct nb_cb_get_next_args *args)
+{
+ struct vrf *vrfp = (struct vrf *)args->list_entry;
+
+ if (args->list_entry == NULL) {
+ vrfp = RB_MIN(vrf_name_head, &vrfs_by_name);
+ } else {
+ vrfp = RB_NEXT(vrf_name_head, vrfp);
+ }
+
+ return vrfp;
+}
+
+static int lib_vrf_get_keys(struct nb_cb_get_keys_args *args)
+{
+ struct vrf *vrfp = (struct vrf *)args->list_entry;
+
+ args->keys->num = 1;
+ strlcpy(args->keys->key[0], vrfp->name, sizeof(args->keys->key[0]));
+
+ return NB_OK;
+}
+
+static const void *lib_vrf_lookup_entry(struct nb_cb_lookup_entry_args *args)
+{
+ const char *vrfname = args->keys->key[0];
+
+ struct vrf *vrf = vrf_lookup_by_name(vrfname);
+
+ return vrf;
+}
+
+/*
+ * XPath: /frr-vrf:lib/vrf/id
+ */
+static struct yang_data *
+lib_vrf_state_id_get_elem(struct nb_cb_get_elem_args *args)
+{
+ struct vrf *vrfp = (struct vrf *)args->list_entry;
+
+ return yang_data_new_uint32(args->xpath, vrfp->vrf_id);
+}
+
+/*
+ * XPath: /frr-vrf:lib/vrf/active
+ */
+static struct yang_data *
+lib_vrf_state_active_get_elem(struct nb_cb_get_elem_args *args)
+{
+ struct vrf *vrfp = (struct vrf *)args->list_entry;
+
+ if (vrfp->status == VRF_ACTIVE)
+ return yang_data_new_bool(
+ args->xpath, vrfp->status == VRF_ACTIVE ? true : false);
+
+ return NULL;
+}
+
+/* clang-format off */
+const struct frr_yang_module_info frr_vrf_info = {
+ .name = "frr-vrf",
+ .nodes = {
+ {
+ .xpath = "/frr-vrf:lib/vrf",
+ .cbs = {
+ .create = lib_vrf_create,
+ .destroy = lib_vrf_destroy,
+ .get_next = lib_vrf_get_next,
+ .get_keys = lib_vrf_get_keys,
+ .lookup_entry = lib_vrf_lookup_entry,
+ },
+ .priority = NB_DFLT_PRIORITY - 2,
+ },
+ {
+ .xpath = "/frr-vrf:lib/vrf/state/id",
+ .cbs = {
+ .get_elem = lib_vrf_state_id_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-vrf:lib/vrf/state/active",
+ .cbs = {
+ .get_elem = lib_vrf_state_active_get_elem,
+ }
+ },
+ {
+ .xpath = NULL,
+ },
+ }
+};
+
diff --git a/lib/vrf.h b/lib/vrf.h
new file mode 100644
index 0000000..734176d
--- /dev/null
+++ b/lib/vrf.h
@@ -0,0 +1,318 @@
+/*
+ * VRF related header.
+ * Copyright (C) 2014 6WIND S.A.
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra 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 _ZEBRA_VRF_H
+#define _ZEBRA_VRF_H
+
+#include "openbsd-tree.h"
+#include "linklist.h"
+#include "qobj.h"
+#include "vty.h"
+#include "ns.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* The default VRF ID */
+#define VRF_UNKNOWN UINT32_MAX
+
+/* Pending: May need to refine this. */
+#ifndef IFLA_VRF_MAX
+enum { IFLA_VRF_UNSPEC, IFLA_VRF_TABLE, __IFLA_VRF_MAX };
+
+#define IFLA_VRF_MAX (__IFLA_VRF_MAX - 1)
+#endif
+
+#define VRF_NAMSIZ 36
+#define NS_NAMSIZ 36
+
+/*
+ * The command strings
+ */
+#define VRF_CMD_HELP_STR "Specify the VRF\nThe VRF name\n"
+#define VRF_ALL_CMD_HELP_STR "Specify the VRF\nAll VRFs\n"
+#define VRF_FULL_CMD_HELP_STR "Specify the VRF\nThe VRF name\nAll VRFs\n"
+
+#define FRR_VRF_XPATH "/frr-vrf:lib/vrf"
+#define FRR_VRF_KEY_XPATH "/frr-vrf:lib/vrf[name='%s']"
+
+/*
+ * Pass some OS specific data up through
+ * to the daemons
+ */
+struct vrf_data {
+ union {
+ struct {
+ uint32_t table_id;
+ char netns_name[NS_NAMSIZ];
+ } l;
+ };
+};
+
+struct vrf {
+ RB_ENTRY(vrf) id_entry, name_entry;
+
+ /* Identifier, same as the vector index */
+ vrf_id_t vrf_id;
+
+ /* Name */
+ char name[VRF_NAMSIZ + 1];
+
+ /* Zebra internal VRF status */
+ uint8_t status;
+#define VRF_ACTIVE (1 << 0) /* VRF is up in kernel */
+#define VRF_CONFIGURED (1 << 1) /* VRF has some FRR configuration */
+
+ /* Interfaces belonging to this VRF */
+ struct if_name_head ifaces_by_name;
+ struct if_index_head ifaces_by_index;
+
+ /* User data */
+ void *info;
+
+ /* The table_id from the kernel */
+ struct vrf_data data;
+
+ /* Back pointer to namespace context */
+ void *ns_ctxt;
+
+ QOBJ_FIELDS;
+};
+RB_HEAD(vrf_id_head, vrf);
+RB_PROTOTYPE(vrf_id_head, vrf, id_entry, vrf_id_compare)
+RB_HEAD(vrf_name_head, vrf);
+RB_PROTOTYPE(vrf_name_head, vrf, name_entry, vrf_name_compare)
+DECLARE_QOBJ_TYPE(vrf);
+
+/* Allow VRF with netns as backend */
+enum vrf_backend_type {
+ VRF_BACKEND_VRF_LITE,
+ VRF_BACKEND_NETNS,
+ VRF_BACKEND_UNKNOWN,
+ VRF_BACKEND_MAX,
+};
+
+extern struct vrf_id_head vrfs_by_id;
+extern struct vrf_name_head vrfs_by_name;
+
+extern struct vrf *vrf_lookup_by_id(vrf_id_t);
+extern struct vrf *vrf_lookup_by_name(const char *);
+extern struct vrf *vrf_get(vrf_id_t, const char *);
+extern struct vrf *vrf_update(vrf_id_t new_vrf_id, const char *name);
+extern const char *vrf_id_to_name(vrf_id_t vrf_id);
+
+#define VRF_LOGNAME(V) V ? V->name : "Unknown"
+
+#define VRF_GET_ID(V, NAME, USE_JSON) \
+ do { \
+ struct vrf *_vrf; \
+ if (!(_vrf = vrf_lookup_by_name(NAME))) { \
+ if (USE_JSON) { \
+ vty_out(vty, "{}\n"); \
+ } else { \
+ vty_out(vty, "%% VRF %s not found\n", NAME); \
+ } \
+ return CMD_WARNING; \
+ } \
+ if (_vrf->vrf_id == VRF_UNKNOWN) { \
+ if (USE_JSON) { \
+ vty_out(vty, "{}\n"); \
+ } else { \
+ vty_out(vty, "%% VRF %s not active\n", NAME); \
+ } \
+ return CMD_WARNING; \
+ } \
+ (V) = _vrf->vrf_id; \
+ } while (0)
+
+/*
+ * Check whether the VRF is enabled.
+ */
+static inline int vrf_is_enabled(struct vrf *vrf)
+{
+ return vrf && CHECK_FLAG(vrf->status, VRF_ACTIVE);
+}
+
+/* check if the vrf is user configured */
+static inline int vrf_is_user_cfged(struct vrf *vrf)
+{
+ return vrf && CHECK_FLAG(vrf->status, VRF_CONFIGURED);
+}
+
+static inline uint32_t vrf_interface_count(struct vrf *vrf)
+{
+ uint32_t count = 0;
+ struct interface *ifp;
+
+ RB_FOREACH (ifp, if_name_head, &vrf->ifaces_by_name) {
+ /* skip the l3mdev */
+ if (strncmp(ifp->name, vrf->name, VRF_NAMSIZ) == 0)
+ continue;
+ count++;
+ }
+ return count;
+}
+
+/*
+ * Utilities to obtain the user data
+ */
+
+/* Look up the data pointer of the specified VRF. */
+extern void *vrf_info_lookup(vrf_id_t);
+
+/*
+ * VRF bit-map: maintaining flags, one bit per VRF ID
+ */
+
+typedef void *vrf_bitmap_t;
+#define VRF_BITMAP_NULL NULL
+
+extern vrf_bitmap_t vrf_bitmap_init(void);
+extern void vrf_bitmap_free(vrf_bitmap_t);
+extern void vrf_bitmap_set(vrf_bitmap_t, vrf_id_t);
+extern void vrf_bitmap_unset(vrf_bitmap_t, vrf_id_t);
+extern int vrf_bitmap_check(vrf_bitmap_t, vrf_id_t);
+
+/*
+ * VRF initializer/destructor
+ *
+ * create -> Called back when a new VRF is created. This
+ * can be either through these 3 options:
+ * 1) CLI mentions a vrf before OS knows about it
+ * 2) OS calls zebra and we create the vrf from OS
+ * callback
+ * 3) zebra calls individual protocols to notify
+ * about the new vrf
+ *
+ * enable -> Called back when a VRF is actually usable from
+ * an OS perspective ( 2 and 3 above )
+ *
+ * disable -> Called back when a VRF is being deleted from
+ * the system ( 2 and 3 ) above
+ *
+ * delete -> Called back when a vrf is being deleted from
+ * the system ( 2 and 3 ) above.
+ */
+extern void vrf_init(int (*create)(struct vrf *vrf),
+ int (*enable)(struct vrf *vrf),
+ int (*disable)(struct vrf *vrf),
+ int (*destroy)(struct vrf *vrf));
+
+/*
+ * Call vrf_terminate when the protocol is being shutdown
+ */
+extern void vrf_terminate(void);
+
+/*
+ * Utilities to create networks objects,
+ * or call network operations
+ */
+
+/*
+ * Create a new socket associated with a VRF.
+ *
+ * This is a wrapper that ensures correct behavior when using namespace VRFs.
+ * In the namespace case, the socket is created within the namespace. In the
+ * non-namespace case, this is equivalent to socket().
+ *
+ * If name is provided, this is provided to vrf_bind() to bind the socket to
+ * the VRF. This is only relevant when using VRF-lite.
+ *
+ * Summary:
+ * - Namespace: pass vrf_id but not name
+ * - VRF-lite: pass vrf_id and name of VRF device to bind to
+ * - VRF-lite, no binding: pass vrf_id but not name, or just use socket()
+ */
+extern int vrf_socket(int domain, int type, int protocol, vrf_id_t vrf_id,
+ const char *name);
+
+extern int vrf_sockunion_socket(const union sockunion *su, vrf_id_t vrf_id,
+ const char *name);
+
+/*
+ * Binds a socket to an interface (ifname) in a VRF (vrf_id).
+ *
+ * If ifname is NULL or is equal to the VRF name then bind to a VRF device.
+ * Otherwise, bind to the specified interface in the specified VRF.
+ *
+ * Returns 0 on success and -1 on failure.
+ */
+extern int vrf_bind(vrf_id_t vrf_id, int fd, const char *ifname);
+
+/* VRF ioctl operations */
+extern int vrf_getaddrinfo(const char *node, const char *service,
+ const struct addrinfo *hints, struct addrinfo **res,
+ vrf_id_t vrf_id);
+
+extern int vrf_ioctl(vrf_id_t vrf_id, int d, unsigned long request, char *args);
+
+/* The default VRF ID */
+#define VRF_DEFAULT 0
+
+/* Must be called only during startup, before config is read */
+extern void vrf_set_default_name(const char *default_name);
+
+extern const char *vrf_get_default_name(void);
+#define VRF_DEFAULT_NAME vrf_get_default_name()
+
+/* VRF switch from NETNS */
+extern int vrf_switch_to_netns(vrf_id_t vrf_id);
+extern int vrf_switchback_to_initial(void);
+
+/*
+ * VRF backend routines
+ * should be called from zebra only
+ */
+
+/* VRF vty command initialisation
+ */
+extern void vrf_cmd_init(int (*writefunc)(struct vty *vty));
+
+/* VRF vty debugging
+ */
+extern void vrf_install_commands(void);
+
+/*
+ * VRF utilities
+ */
+
+/*
+ * API for configuring VRF backend
+ */
+extern int vrf_configure_backend(enum vrf_backend_type backend);
+extern int vrf_get_backend(void);
+extern int vrf_is_backend_netns(void);
+
+/* used internally to enable or disable VRF.
+ * Notify a change in the VRF ID of the VRF
+ */
+extern void vrf_disable(struct vrf *vrf);
+extern int vrf_enable(struct vrf *vrf);
+extern void vrf_delete(struct vrf *vrf);
+
+extern const struct frr_yang_module_info frr_vrf_info;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /*_ZEBRA_VRF_H*/
diff --git a/lib/vrf_int.h b/lib/vrf_int.h
new file mode 100644
index 0000000..8dc078e
--- /dev/null
+++ b/lib/vrf_int.h
@@ -0,0 +1,63 @@
+/*
+ * VRF Internal Header
+ * Copyright (C) 2017 Cumulus Networks, Inc.
+ * Donald Sharp
+ *
+ * This file is part of FRR.
+ *
+ * FRR 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, or (at your option) any
+ * later version.
+ *
+ * FRR 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 FRR; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+#ifndef __LIB_VRF_PRIVATE_H__
+#define __LIB_VRF_PRIVATE_H__
+
+#include "vrf.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * These functions should only be called by:
+ * zebra/if_netlink.c -> The interface from OS into Zebra
+ * lib/zclient.c -> The interface from Zebra to each daemon
+ *
+ * Why you ask? Well because these are the turn on/off
+ * functions and the only place we can really turn a
+ * vrf on properly is in the call up from the os -> zebra
+ * and the pass through of this informatoin from zebra -> protocols
+ */
+
+/*
+ * vrf_enable
+ *
+ * Given a newly running vrf enable it to be used
+ * by interested routing protocols
+ */
+extern int vrf_enable(struct vrf *);
+
+/*
+ * vrf_delete
+ *
+ * Given a vrf that is being deleted, delete it
+ * from interested parties
+ */
+extern void vrf_delete(struct vrf *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/vty.c b/lib/vty.c
new file mode 100644
index 0000000..169e780
--- /dev/null
+++ b/lib/vty.c
@@ -0,0 +1,3236 @@
+/*
+ * Virtual terminal [aka TeletYpe] interface routine.
+ * Copyright (C) 1997, 98 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 <lib/version.h>
+#include <sys/types.h>
+#include <sys/types.h>
+#ifdef HAVE_LIBPCREPOSIX
+#include <pcreposix.h>
+#else
+#include <regex.h>
+#endif /* HAVE_LIBPCREPOSIX */
+#include <stdio.h>
+
+#include "linklist.h"
+#include "thread.h"
+#include "buffer.h"
+#include "command.h"
+#include "sockunion.h"
+#include "memory.h"
+#include "log.h"
+#include "prefix.h"
+#include "filter.h"
+#include "vty.h"
+#include "privs.h"
+#include "network.h"
+#include "libfrr.h"
+#include "frrstr.h"
+#include "lib_errors.h"
+#include "northbound_cli.h"
+#include "printfrr.h"
+#include "json.h"
+
+#include <arpa/telnet.h>
+#include <termios.h>
+
+#ifndef VTYSH_EXTRACT_PL
+#include "lib/vty_clippy.c"
+#endif
+
+DEFINE_MTYPE_STATIC(LIB, VTY, "VTY");
+DEFINE_MTYPE_STATIC(LIB, VTY_SERV, "VTY server");
+DEFINE_MTYPE_STATIC(LIB, VTY_OUT_BUF, "VTY output buffer");
+DEFINE_MTYPE_STATIC(LIB, VTY_HIST, "VTY history");
+
+DECLARE_DLIST(vtys, struct vty, itm);
+
+/* Vty events */
+enum vty_event {
+ VTY_SERV,
+ VTY_READ,
+ VTY_WRITE,
+ VTY_TIMEOUT_RESET,
+#ifdef VTYSH
+ VTYSH_SERV,
+ VTYSH_READ,
+ VTYSH_WRITE
+#endif /* VTYSH */
+};
+
+PREDECL_DLIST(vtyservs);
+
+struct vty_serv {
+ struct vtyservs_item itm;
+
+ int sock;
+ bool vtysh;
+
+ struct thread *t_accept;
+};
+
+DECLARE_DLIST(vtyservs, struct vty_serv, itm);
+
+static void vty_event_serv(enum vty_event event, struct vty_serv *);
+static void vty_event(enum vty_event, struct vty *);
+
+/* Extern host structure from command.c */
+extern struct host host;
+
+/* active listeners */
+static struct vtyservs_head vty_servs[1] = {INIT_DLIST(vty_servs[0])};
+
+/* active connections */
+static struct vtys_head vty_sessions[1] = {INIT_DLIST(vty_sessions[0])};
+static struct vtys_head vtysh_sessions[1] = {INIT_DLIST(vtysh_sessions[0])};
+
+/* Vty timeout value. */
+static unsigned long vty_timeout_val = VTY_TIMEOUT_DEFAULT;
+
+/* Vty access-class command */
+static char *vty_accesslist_name = NULL;
+
+/* Vty access-calss for IPv6. */
+static char *vty_ipv6_accesslist_name = NULL;
+
+/* Current directory. */
+static char vty_cwd[MAXPATHLEN];
+
+/* Login password check. */
+static int no_password_check = 0;
+
+/* Integrated configuration file path */
+static char integrate_default[] = SYSCONFDIR INTEGRATE_DEFAULT_CONFIG;
+
+static bool do_log_commands;
+static bool do_log_commands_perm;
+
+void vty_frame(struct vty *vty, const char *format, ...)
+{
+ va_list args;
+
+ va_start(args, format);
+ vsnprintfrr(vty->frame + vty->frame_pos,
+ sizeof(vty->frame) - vty->frame_pos, format, args);
+ vty->frame_pos = strlen(vty->frame);
+ va_end(args);
+}
+
+void vty_endframe(struct vty *vty, const char *endtext)
+{
+ if (vty->frame_pos == 0 && endtext)
+ vty_out(vty, "%s", endtext);
+ vty->frame_pos = 0;
+}
+
+bool vty_set_include(struct vty *vty, const char *regexp)
+{
+ int errcode;
+ bool ret = true;
+ char errbuf[256];
+
+ if (!regexp) {
+ if (vty->filter) {
+ regfree(&vty->include);
+ vty->filter = false;
+ }
+ return true;
+ }
+
+ errcode = regcomp(&vty->include, regexp,
+ REG_EXTENDED | REG_NEWLINE | REG_NOSUB);
+ if (errcode) {
+ ret = false;
+ regerror(errcode, &vty->include, errbuf, sizeof(errbuf));
+ vty_out(vty, "%% Regex compilation error: %s\n", errbuf);
+ } else {
+ vty->filter = true;
+ }
+
+ return ret;
+}
+
+/* VTY standard output function. */
+int vty_out(struct vty *vty, const char *format, ...)
+{
+ va_list args;
+ ssize_t len;
+ char buf[1024];
+ char *p = NULL;
+ char *filtered;
+ /* format string may contain %m, keep errno intact for printfrr */
+ int saved_errno = errno;
+
+ if (vty->frame_pos) {
+ vty->frame_pos = 0;
+ vty_out(vty, "%s", vty->frame);
+ }
+
+ va_start(args, format);
+ errno = saved_errno;
+ p = vasnprintfrr(MTYPE_VTY_OUT_BUF, buf, sizeof(buf), format, args);
+ va_end(args);
+
+ len = strlen(p);
+
+ /* filter buffer */
+ if (vty->filter) {
+ vector lines = frrstr_split_vec(p, "\n");
+
+ /* Place first value in the cache */
+ char *firstline = vector_slot(lines, 0);
+ buffer_put(vty->lbuf, (uint8_t *) firstline, strlen(firstline));
+
+ /* If our split returned more than one entry, time to filter */
+ if (vector_active(lines) > 1) {
+ /*
+ * returned string is MTYPE_TMP so it matches the MTYPE
+ * of everything else in the vector
+ */
+ char *bstr = buffer_getstr(vty->lbuf);
+ buffer_reset(vty->lbuf);
+ XFREE(MTYPE_TMP, lines->index[0]);
+ vector_set_index(lines, 0, bstr);
+ frrstr_filter_vec(lines, &vty->include);
+ vector_compact(lines);
+ /*
+ * Consider the string "foo\n". If the regex is an empty string
+ * and the line ended with a newline, then the vector will look
+ * like:
+ *
+ * [0]: 'foo'
+ * [1]: ''
+ *
+ * If the regex isn't empty, the vector will look like:
+ *
+ * [0]: 'foo'
+ *
+ * In this case we'd like to preserve the newline, so we add
+ * the empty string [1] as in the first example.
+ */
+ if (p[strlen(p) - 1] == '\n' && vector_active(lines) > 0
+ && strlen(vector_slot(lines, vector_active(lines) - 1)))
+ vector_set(lines, XSTRDUP(MTYPE_TMP, ""));
+
+ filtered = frrstr_join_vec(lines, "\n");
+ }
+ else {
+ filtered = NULL;
+ }
+
+ frrstr_strvec_free(lines);
+
+ } else {
+ filtered = p;
+ }
+
+ if (!filtered)
+ goto done;
+
+ switch (vty->type) {
+ case VTY_TERM:
+ /* print with crlf replacement */
+ buffer_put_crlf(vty->obuf, (uint8_t *)filtered,
+ strlen(filtered));
+ break;
+ case VTY_SHELL:
+ if (vty->of) {
+ fprintf(vty->of, "%s", filtered);
+ fflush(vty->of);
+ } else if (vty->of_saved) {
+ fprintf(vty->of_saved, "%s", filtered);
+ fflush(vty->of_saved);
+ }
+ break;
+ case VTY_SHELL_SERV:
+ case VTY_FILE:
+ default:
+ /* print without crlf replacement */
+ buffer_put(vty->obuf, (uint8_t *)filtered, strlen(filtered));
+ break;
+ }
+
+done:
+
+ if (vty->filter && filtered)
+ XFREE(MTYPE_TMP, filtered);
+
+ /* If p is not different with buf, it is allocated buffer. */
+ if (p != buf)
+ XFREE(MTYPE_VTY_OUT_BUF, p);
+
+ return len;
+}
+
+static int vty_json_helper(struct vty *vty, struct json_object *json,
+ uint32_t options)
+{
+ const char *text;
+
+ if (!json)
+ return CMD_SUCCESS;
+
+ text = json_object_to_json_string_ext(
+ json, options);
+ vty_out(vty, "%s\n", text);
+ json_object_free(json);
+
+ return CMD_SUCCESS;
+}
+
+int vty_json(struct vty *vty, struct json_object *json)
+{
+ return vty_json_helper(vty, json,
+ JSON_C_TO_STRING_PRETTY |
+ JSON_C_TO_STRING_NOSLASHESCAPE);
+}
+
+int vty_json_no_pretty(struct vty *vty, struct json_object *json)
+{
+ return vty_json_helper(vty, json, JSON_C_TO_STRING_NOSLASHESCAPE);
+}
+
+/* Output current time to the vty. */
+void vty_time_print(struct vty *vty, int cr)
+{
+ char buf[FRR_TIMESTAMP_LEN];
+
+ if (frr_timestamp(0, buf, sizeof(buf)) == 0) {
+ zlog_info("frr_timestamp error");
+ return;
+ }
+ if (cr)
+ vty_out(vty, "%s\n", buf);
+ else
+ vty_out(vty, "%s ", buf);
+
+ return;
+}
+
+/* Say hello to vty interface. */
+void vty_hello(struct vty *vty)
+{
+ if (host.motdfile) {
+ FILE *f;
+ char buf[4096];
+
+ f = fopen(host.motdfile, "r");
+ if (f) {
+ while (fgets(buf, sizeof(buf), f)) {
+ char *s;
+ /* work backwards to ignore trailling isspace()
+ */
+ for (s = buf + strlen(buf);
+ (s > buf) && isspace((unsigned char)s[-1]);
+ s--)
+ ;
+ *s = '\0';
+ vty_out(vty, "%s\n", buf);
+ }
+ fclose(f);
+ } else
+ vty_out(vty, "MOTD file not found\n");
+ } else if (host.motd)
+ vty_out(vty, "%s", host.motd);
+}
+
+/* Put out prompt and wait input from user. */
+static void vty_prompt(struct vty *vty)
+{
+ if (vty->type == VTY_TERM) {
+ vty_out(vty, cmd_prompt(vty->node), cmd_hostname_get());
+ }
+}
+
+/* Send WILL TELOPT_ECHO to remote server. */
+static void vty_will_echo(struct vty *vty)
+{
+ unsigned char cmd[] = {IAC, WILL, TELOPT_ECHO, '\0'};
+ vty_out(vty, "%s", cmd);
+}
+
+/* Make suppress Go-Ahead telnet option. */
+static void vty_will_suppress_go_ahead(struct vty *vty)
+{
+ unsigned char cmd[] = {IAC, WILL, TELOPT_SGA, '\0'};
+ vty_out(vty, "%s", cmd);
+}
+
+/* Make don't use linemode over telnet. */
+static void vty_dont_linemode(struct vty *vty)
+{
+ unsigned char cmd[] = {IAC, DONT, TELOPT_LINEMODE, '\0'};
+ vty_out(vty, "%s", cmd);
+}
+
+/* Use window size. */
+static void vty_do_window_size(struct vty *vty)
+{
+ unsigned char cmd[] = {IAC, DO, TELOPT_NAWS, '\0'};
+ vty_out(vty, "%s", cmd);
+}
+
+/* Authentication of vty */
+static void vty_auth(struct vty *vty, char *buf)
+{
+ char *passwd = NULL;
+ enum node_type next_node = 0;
+ int fail;
+
+ switch (vty->node) {
+ case AUTH_NODE:
+ if (host.encrypt)
+ passwd = host.password_encrypt;
+ else
+ passwd = host.password;
+ if (host.advanced)
+ next_node = host.enable ? VIEW_NODE : ENABLE_NODE;
+ else
+ next_node = VIEW_NODE;
+ break;
+ case AUTH_ENABLE_NODE:
+ if (host.encrypt)
+ passwd = host.enable_encrypt;
+ else
+ passwd = host.enable;
+ next_node = ENABLE_NODE;
+ break;
+ }
+
+ if (passwd) {
+ if (host.encrypt)
+ fail = strcmp(crypt(buf, passwd), passwd);
+ else
+ fail = strcmp(buf, passwd);
+ } else
+ fail = 1;
+
+ if (!fail) {
+ vty->fail = 0;
+ vty->node = next_node; /* Success ! */
+ } else {
+ vty->fail++;
+ if (vty->fail >= 3) {
+ if (vty->node == AUTH_NODE) {
+ vty_out(vty,
+ "%% Bad passwords, too many failures!\n");
+ vty->status = VTY_CLOSE;
+ } else {
+ /* AUTH_ENABLE_NODE */
+ vty->fail = 0;
+ vty_out(vty,
+ "%% Bad enable passwords, too many failures!\n");
+ vty->status = VTY_CLOSE;
+ }
+ }
+ }
+}
+
+/* Command execution over the vty interface. */
+static int vty_command(struct vty *vty, char *buf)
+{
+ int ret;
+ const char *protocolname;
+ char *cp = NULL;
+
+ assert(vty);
+
+ /*
+ * Log non empty command lines
+ */
+ if (do_log_commands &&
+ strncmp(buf, "echo PING", strlen("echo PING")) != 0)
+ cp = buf;
+ if (cp != NULL) {
+ /* Skip white spaces. */
+ while (isspace((unsigned char)*cp) && *cp != '\0')
+ cp++;
+ }
+ if (cp != NULL && *cp != '\0') {
+ char vty_str[VTY_BUFSIZ];
+ char prompt_str[VTY_BUFSIZ];
+
+ /* format the base vty info */
+ snprintf(vty_str, sizeof(vty_str), "vty[%d]@%s", vty->fd,
+ vty->address);
+
+ /* format the prompt */
+ snprintf(prompt_str, sizeof(prompt_str), cmd_prompt(vty->node),
+ vty_str);
+
+ /* now log the command */
+ zlog_notice("%s%s", prompt_str, buf);
+ }
+
+ RUSAGE_T before;
+ RUSAGE_T after;
+ unsigned long walltime, cputime;
+
+ /* cmd_execute() may change cputime_enabled if we're executing the
+ * "service cputime-stats" command, which can result in nonsensical
+ * and very confusing warnings
+ */
+ bool cputime_enabled_here = cputime_enabled;
+
+ GETRUSAGE(&before);
+
+ ret = cmd_execute(vty, buf, NULL, 0);
+
+ GETRUSAGE(&after);
+
+ walltime = thread_consumed_time(&after, &before, &cputime);
+
+ if (cputime_enabled_here && cputime_enabled && cputime_threshold
+ && cputime > cputime_threshold)
+ /* Warn about CPU hog that must be fixed. */
+ flog_warn(EC_LIB_SLOW_THREAD_CPU,
+ "CPU HOG: command took %lums (cpu time %lums): %s",
+ walltime / 1000, cputime / 1000, buf);
+ else if (walltime_threshold && walltime > walltime_threshold)
+ flog_warn(EC_LIB_SLOW_THREAD_WALL,
+ "STARVATION: command took %lums (cpu time %lums): %s",
+ walltime / 1000, cputime / 1000, buf);
+
+ /* Get the name of the protocol if any */
+ protocolname = frr_protoname;
+
+ if (ret != CMD_SUCCESS)
+ switch (ret) {
+ case CMD_WARNING:
+ if (vty->type == VTY_FILE)
+ vty_out(vty, "Warning...\n");
+ break;
+ case CMD_ERR_AMBIGUOUS:
+ vty_out(vty, "%% Ambiguous command.\n");
+ break;
+ case CMD_ERR_NO_MATCH:
+ vty_out(vty, "%% [%s] Unknown command: %s\n",
+ protocolname, buf);
+ break;
+ case CMD_ERR_INCOMPLETE:
+ vty_out(vty, "%% Command incomplete.\n");
+ break;
+ }
+
+ return ret;
+}
+
+static const char telnet_backward_char = 0x08;
+static const char telnet_space_char = ' ';
+
+/* Basic function to write buffer to vty. */
+static void vty_write(struct vty *vty, const char *buf, size_t nbytes)
+{
+ if ((vty->node == AUTH_NODE) || (vty->node == AUTH_ENABLE_NODE))
+ return;
+
+ /* Should we do buffering here ? And make vty_flush (vty) ? */
+ buffer_put(vty->obuf, buf, nbytes);
+}
+
+/* Basic function to insert character into vty. */
+static void vty_self_insert(struct vty *vty, char c)
+{
+ int i;
+ int length;
+
+ if (vty->length + 1 >= VTY_BUFSIZ)
+ return;
+
+ length = vty->length - vty->cp;
+ memmove(&vty->buf[vty->cp + 1], &vty->buf[vty->cp], length);
+ vty->buf[vty->cp] = c;
+
+ vty_write(vty, &vty->buf[vty->cp], length + 1);
+ for (i = 0; i < length; i++)
+ vty_write(vty, &telnet_backward_char, 1);
+
+ vty->cp++;
+ vty->length++;
+
+ vty->buf[vty->length] = '\0';
+}
+
+/* Self insert character 'c' in overwrite mode. */
+static void vty_self_insert_overwrite(struct vty *vty, char c)
+{
+ if (vty->cp == vty->length) {
+ vty_self_insert(vty, c);
+ return;
+ }
+
+ vty->buf[vty->cp++] = c;
+ vty_write(vty, &c, 1);
+}
+
+/**
+ * Insert a string into vty->buf at the current cursor position.
+ *
+ * If the resultant string would be larger than VTY_BUFSIZ it is
+ * truncated to fit.
+ */
+static void vty_insert_word_overwrite(struct vty *vty, char *str)
+{
+ if (vty->cp == VTY_BUFSIZ)
+ return;
+
+ size_t nwrite = MIN((int)strlen(str), VTY_BUFSIZ - vty->cp - 1);
+ memcpy(&vty->buf[vty->cp], str, nwrite);
+ vty->cp += nwrite;
+ vty->length = MAX(vty->cp, vty->length);
+ vty->buf[vty->length] = '\0';
+ vty_write(vty, str, nwrite);
+}
+
+/* Forward character. */
+static void vty_forward_char(struct vty *vty)
+{
+ if (vty->cp < vty->length) {
+ vty_write(vty, &vty->buf[vty->cp], 1);
+ vty->cp++;
+ }
+}
+
+/* Backward character. */
+static void vty_backward_char(struct vty *vty)
+{
+ if (vty->cp > 0) {
+ vty->cp--;
+ vty_write(vty, &telnet_backward_char, 1);
+ }
+}
+
+/* Move to the beginning of the line. */
+static void vty_beginning_of_line(struct vty *vty)
+{
+ while (vty->cp)
+ vty_backward_char(vty);
+}
+
+/* Move to the end of the line. */
+static void vty_end_of_line(struct vty *vty)
+{
+ while (vty->cp < vty->length)
+ vty_forward_char(vty);
+}
+
+static void vty_kill_line_from_beginning(struct vty *);
+static void vty_redraw_line(struct vty *);
+
+/* Print command line history. This function is called from
+ vty_next_line and vty_previous_line. */
+static void vty_history_print(struct vty *vty)
+{
+ int length;
+
+ vty_kill_line_from_beginning(vty);
+
+ /* Get previous line from history buffer */
+ length = strlen(vty->hist[vty->hp]);
+ memcpy(vty->buf, vty->hist[vty->hp], length);
+ vty->cp = vty->length = length;
+ vty->buf[vty->length] = '\0';
+
+ /* Redraw current line */
+ vty_redraw_line(vty);
+}
+
+/* Show next command line history. */
+static void vty_next_line(struct vty *vty)
+{
+ int try_index;
+
+ if (vty->hp == vty->hindex)
+ return;
+
+ /* Try is there history exist or not. */
+ try_index = vty->hp;
+ if (try_index == (VTY_MAXHIST - 1))
+ try_index = 0;
+ else
+ try_index++;
+
+ /* If there is not history return. */
+ if (vty->hist[try_index] == NULL)
+ return;
+ else
+ vty->hp = try_index;
+
+ vty_history_print(vty);
+}
+
+/* Show previous command line history. */
+static void vty_previous_line(struct vty *vty)
+{
+ int try_index;
+
+ try_index = vty->hp;
+ if (try_index == 0)
+ try_index = VTY_MAXHIST - 1;
+ else
+ try_index--;
+
+ if (vty->hist[try_index] == NULL)
+ return;
+ else
+ vty->hp = try_index;
+
+ vty_history_print(vty);
+}
+
+/* This function redraw all of the command line character. */
+static void vty_redraw_line(struct vty *vty)
+{
+ vty_write(vty, vty->buf, vty->length);
+ vty->cp = vty->length;
+}
+
+/* Forward word. */
+static void vty_forward_word(struct vty *vty)
+{
+ while (vty->cp != vty->length && vty->buf[vty->cp] != ' ')
+ vty_forward_char(vty);
+
+ while (vty->cp != vty->length && vty->buf[vty->cp] == ' ')
+ vty_forward_char(vty);
+}
+
+/* Backward word without skipping training space. */
+static void vty_backward_pure_word(struct vty *vty)
+{
+ while (vty->cp > 0 && vty->buf[vty->cp - 1] != ' ')
+ vty_backward_char(vty);
+}
+
+/* Backward word. */
+static void vty_backward_word(struct vty *vty)
+{
+ while (vty->cp > 0 && vty->buf[vty->cp - 1] == ' ')
+ vty_backward_char(vty);
+
+ while (vty->cp > 0 && vty->buf[vty->cp - 1] != ' ')
+ vty_backward_char(vty);
+}
+
+/* When '^D' is typed at the beginning of the line we move to the down
+ level. */
+static void vty_down_level(struct vty *vty)
+{
+ vty_out(vty, "\n");
+ cmd_exit(vty);
+ vty_prompt(vty);
+ vty->cp = 0;
+}
+
+/* When '^Z' is received from vty, move down to the enable mode. */
+static void vty_end_config(struct vty *vty)
+{
+ vty_out(vty, "\n");
+
+ if (vty->config) {
+ vty_config_exit(vty);
+ vty->node = ENABLE_NODE;
+ }
+
+ vty_prompt(vty);
+ vty->cp = 0;
+}
+
+/* Delete a character at the current point. */
+static void vty_delete_char(struct vty *vty)
+{
+ int i;
+ int size;
+
+ if (vty->length == 0) {
+ vty_down_level(vty);
+ return;
+ }
+
+ if (vty->cp == vty->length)
+ return; /* completion need here? */
+
+ size = vty->length - vty->cp;
+
+ vty->length--;
+ memmove(&vty->buf[vty->cp], &vty->buf[vty->cp + 1], size - 1);
+ vty->buf[vty->length] = '\0';
+
+ if (vty->node == AUTH_NODE || vty->node == AUTH_ENABLE_NODE)
+ return;
+
+ vty_write(vty, &vty->buf[vty->cp], size - 1);
+ vty_write(vty, &telnet_space_char, 1);
+
+ for (i = 0; i < size; i++)
+ vty_write(vty, &telnet_backward_char, 1);
+}
+
+/* Delete a character before the point. */
+static void vty_delete_backward_char(struct vty *vty)
+{
+ if (vty->cp == 0)
+ return;
+
+ vty_backward_char(vty);
+ vty_delete_char(vty);
+}
+
+/* Kill rest of line from current point. */
+static void vty_kill_line(struct vty *vty)
+{
+ int i;
+ int size;
+
+ size = vty->length - vty->cp;
+
+ if (size == 0)
+ return;
+
+ for (i = 0; i < size; i++)
+ vty_write(vty, &telnet_space_char, 1);
+ for (i = 0; i < size; i++)
+ vty_write(vty, &telnet_backward_char, 1);
+
+ memset(&vty->buf[vty->cp], 0, size);
+ vty->length = vty->cp;
+}
+
+/* Kill line from the beginning. */
+static void vty_kill_line_from_beginning(struct vty *vty)
+{
+ vty_beginning_of_line(vty);
+ vty_kill_line(vty);
+}
+
+/* Delete a word before the point. */
+static void vty_forward_kill_word(struct vty *vty)
+{
+ while (vty->cp != vty->length && vty->buf[vty->cp] == ' ')
+ vty_delete_char(vty);
+ while (vty->cp != vty->length && vty->buf[vty->cp] != ' ')
+ vty_delete_char(vty);
+}
+
+/* Delete a word before the point. */
+static void vty_backward_kill_word(struct vty *vty)
+{
+ while (vty->cp > 0 && vty->buf[vty->cp - 1] == ' ')
+ vty_delete_backward_char(vty);
+ while (vty->cp > 0 && vty->buf[vty->cp - 1] != ' ')
+ vty_delete_backward_char(vty);
+}
+
+/* Transpose chars before or at the point. */
+static void vty_transpose_chars(struct vty *vty)
+{
+ char c1, c2;
+
+ /* If length is short or point is near by the beginning of line then
+ return. */
+ if (vty->length < 2 || vty->cp < 1)
+ return;
+
+ /* In case of point is located at the end of the line. */
+ if (vty->cp == vty->length) {
+ c1 = vty->buf[vty->cp - 1];
+ c2 = vty->buf[vty->cp - 2];
+
+ vty_backward_char(vty);
+ vty_backward_char(vty);
+ vty_self_insert_overwrite(vty, c1);
+ vty_self_insert_overwrite(vty, c2);
+ } else {
+ c1 = vty->buf[vty->cp];
+ c2 = vty->buf[vty->cp - 1];
+
+ vty_backward_char(vty);
+ vty_self_insert_overwrite(vty, c1);
+ vty_self_insert_overwrite(vty, c2);
+ }
+}
+
+/* Do completion at vty interface. */
+static void vty_complete_command(struct vty *vty)
+{
+ int i;
+ int ret;
+ char **matched = NULL;
+ vector vline;
+
+ if (vty->node == AUTH_NODE || vty->node == AUTH_ENABLE_NODE)
+ return;
+
+ vline = cmd_make_strvec(vty->buf);
+ if (vline == NULL)
+ return;
+
+ /* In case of 'help \t'. */
+ if (isspace((unsigned char)vty->buf[vty->length - 1]))
+ vector_set(vline, NULL);
+
+ matched = cmd_complete_command(vline, vty, &ret);
+
+ cmd_free_strvec(vline);
+
+ vty_out(vty, "\n");
+ switch (ret) {
+ case CMD_ERR_AMBIGUOUS:
+ vty_out(vty, "%% Ambiguous command.\n");
+ vty_prompt(vty);
+ vty_redraw_line(vty);
+ break;
+ case CMD_ERR_NO_MATCH:
+ /* vty_out (vty, "%% There is no matched command.\n"); */
+ vty_prompt(vty);
+ vty_redraw_line(vty);
+ break;
+ case CMD_COMPLETE_FULL_MATCH:
+ if (!matched[0]) {
+ /* 2016-11-28 equinox -- need to debug, SEGV here */
+ vty_out(vty, "%% CLI BUG: FULL_MATCH with NULL str\n");
+ vty_prompt(vty);
+ vty_redraw_line(vty);
+ break;
+ }
+ vty_prompt(vty);
+ vty_redraw_line(vty);
+ vty_backward_pure_word(vty);
+ vty_insert_word_overwrite(vty, matched[0]);
+ vty_self_insert(vty, ' ');
+ XFREE(MTYPE_COMPLETION, matched[0]);
+ break;
+ case CMD_COMPLETE_MATCH:
+ vty_prompt(vty);
+ vty_redraw_line(vty);
+ vty_backward_pure_word(vty);
+ vty_insert_word_overwrite(vty, matched[0]);
+ XFREE(MTYPE_COMPLETION, matched[0]);
+ break;
+ case CMD_COMPLETE_LIST_MATCH:
+ for (i = 0; matched[i] != NULL; i++) {
+ if (i != 0 && ((i % 6) == 0))
+ vty_out(vty, "\n");
+ vty_out(vty, "%-10s ", matched[i]);
+ XFREE(MTYPE_COMPLETION, matched[i]);
+ }
+ vty_out(vty, "\n");
+
+ vty_prompt(vty);
+ vty_redraw_line(vty);
+ break;
+ case CMD_ERR_NOTHING_TODO:
+ vty_prompt(vty);
+ vty_redraw_line(vty);
+ break;
+ default:
+ break;
+ }
+ XFREE(MTYPE_TMP, matched);
+}
+
+static void vty_describe_fold(struct vty *vty, int cmd_width,
+ unsigned int desc_width, struct cmd_token *token)
+{
+ char *buf;
+ const char *cmd, *p;
+ int pos;
+
+ cmd = token->text;
+
+ if (desc_width <= 0) {
+ vty_out(vty, " %-*s %s\n", cmd_width, cmd, token->desc);
+ return;
+ }
+
+ buf = XCALLOC(MTYPE_TMP, strlen(token->desc) + 1);
+
+ for (p = token->desc; strlen(p) > desc_width; p += pos + 1) {
+ for (pos = desc_width; pos > 0; pos--)
+ if (*(p + pos) == ' ')
+ break;
+
+ if (pos == 0)
+ break;
+
+ memcpy(buf, p, pos);
+ buf[pos] = '\0';
+ vty_out(vty, " %-*s %s\n", cmd_width, cmd, buf);
+
+ cmd = "";
+ }
+
+ vty_out(vty, " %-*s %s\n", cmd_width, cmd, p);
+
+ XFREE(MTYPE_TMP, buf);
+}
+
+/* Describe matched command function. */
+static void vty_describe_command(struct vty *vty)
+{
+ int ret;
+ vector vline;
+ vector describe;
+ unsigned int i, width, desc_width;
+ struct cmd_token *token, *token_cr = NULL;
+
+ vline = cmd_make_strvec(vty->buf);
+
+ /* In case of '> ?'. */
+ if (vline == NULL) {
+ vline = vector_init(1);
+ vector_set(vline, NULL);
+ } else if (isspace((unsigned char)vty->buf[vty->length - 1]))
+ vector_set(vline, NULL);
+
+ describe = cmd_describe_command(vline, vty, &ret);
+
+ vty_out(vty, "\n");
+
+ /* Ambiguous error. */
+ switch (ret) {
+ case CMD_ERR_AMBIGUOUS:
+ vty_out(vty, "%% Ambiguous command.\n");
+ goto out;
+ break;
+ case CMD_ERR_NO_MATCH:
+ vty_out(vty, "%% There is no matched command.\n");
+ goto out;
+ break;
+ }
+
+ /* Get width of command string. */
+ width = 0;
+ for (i = 0; i < vector_active(describe); i++)
+ if ((token = vector_slot(describe, i)) != NULL) {
+ unsigned int len;
+
+ if (token->text[0] == '\0')
+ continue;
+
+ len = strlen(token->text);
+
+ if (width < len)
+ width = len;
+ }
+
+ /* Get width of description string. */
+ desc_width = vty->width - (width + 6);
+
+ /* Print out description. */
+ for (i = 0; i < vector_active(describe); i++)
+ if ((token = vector_slot(describe, i)) != NULL) {
+ if (token->text[0] == '\0')
+ continue;
+
+ if (strcmp(token->text, CMD_CR_TEXT) == 0) {
+ token_cr = token;
+ continue;
+ }
+
+ if (!token->desc)
+ vty_out(vty, " %-s\n", token->text);
+ else if (desc_width >= strlen(token->desc))
+ vty_out(vty, " %-*s %s\n", width, token->text,
+ token->desc);
+ else
+ vty_describe_fold(vty, width, desc_width,
+ token);
+
+ if (IS_VARYING_TOKEN(token->type)) {
+ const char *ref = vector_slot(
+ vline, vector_active(vline) - 1);
+
+ vector varcomps = vector_init(VECTOR_MIN_SIZE);
+ cmd_variable_complete(token, ref, varcomps);
+
+ if (vector_active(varcomps) > 0) {
+ char *ac = cmd_variable_comp2str(
+ varcomps, vty->width);
+ vty_out(vty, "%s\n", ac);
+ XFREE(MTYPE_TMP, ac);
+ }
+
+ vector_free(varcomps);
+ }
+ }
+
+ if ((token = token_cr)) {
+ if (!token->desc)
+ vty_out(vty, " %-s\n", token->text);
+ else if (desc_width >= strlen(token->desc))
+ vty_out(vty, " %-*s %s\n", width, token->text,
+ token->desc);
+ else
+ vty_describe_fold(vty, width, desc_width, token);
+ }
+
+out:
+ cmd_free_strvec(vline);
+ if (describe)
+ vector_free(describe);
+
+ vty_prompt(vty);
+ vty_redraw_line(vty);
+}
+
+static void vty_clear_buf(struct vty *vty)
+{
+ memset(vty->buf, 0, vty->max);
+}
+
+/* ^C stop current input and do not add command line to the history. */
+static void vty_stop_input(struct vty *vty)
+{
+ vty->cp = vty->length = 0;
+ vty_clear_buf(vty);
+ vty_out(vty, "\n");
+
+ if (vty->config) {
+ vty_config_exit(vty);
+ vty->node = ENABLE_NODE;
+ }
+
+ vty_prompt(vty);
+
+ /* Set history pointer to the latest one. */
+ vty->hp = vty->hindex;
+}
+
+/* Add current command line to the history buffer. */
+static void vty_hist_add(struct vty *vty)
+{
+ int index;
+
+ if (vty->length == 0)
+ return;
+
+ index = vty->hindex ? vty->hindex - 1 : VTY_MAXHIST - 1;
+
+ /* Ignore the same string as previous one. */
+ if (vty->hist[index])
+ if (strcmp(vty->buf, vty->hist[index]) == 0) {
+ vty->hp = vty->hindex;
+ return;
+ }
+
+ /* Insert history entry. */
+ XFREE(MTYPE_VTY_HIST, vty->hist[vty->hindex]);
+ vty->hist[vty->hindex] = XSTRDUP(MTYPE_VTY_HIST, vty->buf);
+
+ /* History index rotation. */
+ vty->hindex++;
+ if (vty->hindex == VTY_MAXHIST)
+ vty->hindex = 0;
+
+ vty->hp = vty->hindex;
+}
+
+/* #define TELNET_OPTION_DEBUG */
+
+/* Get telnet window size. */
+static int vty_telnet_option(struct vty *vty, unsigned char *buf, int nbytes)
+{
+#ifdef TELNET_OPTION_DEBUG
+ int i;
+
+ for (i = 0; i < nbytes; i++) {
+ switch (buf[i]) {
+ case IAC:
+ vty_out(vty, "IAC ");
+ break;
+ case WILL:
+ vty_out(vty, "WILL ");
+ break;
+ case WONT:
+ vty_out(vty, "WONT ");
+ break;
+ case DO:
+ vty_out(vty, "DO ");
+ break;
+ case DONT:
+ vty_out(vty, "DONT ");
+ break;
+ case SB:
+ vty_out(vty, "SB ");
+ break;
+ case SE:
+ vty_out(vty, "SE ");
+ break;
+ case TELOPT_ECHO:
+ vty_out(vty, "TELOPT_ECHO \n");
+ break;
+ case TELOPT_SGA:
+ vty_out(vty, "TELOPT_SGA \n");
+ break;
+ case TELOPT_NAWS:
+ vty_out(vty, "TELOPT_NAWS \n");
+ break;
+ default:
+ vty_out(vty, "%x ", buf[i]);
+ break;
+ }
+ }
+ vty_out(vty, "\n");
+
+#endif /* TELNET_OPTION_DEBUG */
+
+ switch (buf[0]) {
+ case SB:
+ vty->sb_len = 0;
+ vty->iac_sb_in_progress = 1;
+ return 0;
+ case SE: {
+ if (!vty->iac_sb_in_progress)
+ return 0;
+
+ if ((vty->sb_len == 0) || (vty->sb_buf[0] == '\0')) {
+ vty->iac_sb_in_progress = 0;
+ return 0;
+ }
+ switch (vty->sb_buf[0]) {
+ case TELOPT_NAWS:
+ if (vty->sb_len != TELNET_NAWS_SB_LEN)
+ flog_err(
+ EC_LIB_SYSTEM_CALL,
+ "RFC 1073 violation detected: telnet NAWS option should send %d characters, but we received %lu",
+ TELNET_NAWS_SB_LEN,
+ (unsigned long)vty->sb_len);
+ else if (sizeof(vty->sb_buf) < TELNET_NAWS_SB_LEN)
+ flog_err(
+ EC_LIB_DEVELOPMENT,
+ "Bug detected: sizeof(vty->sb_buf) %lu < %d, too small to handle the telnet NAWS option",
+ (unsigned long)sizeof(vty->sb_buf),
+ TELNET_NAWS_SB_LEN);
+ else {
+ vty->width = ((vty->sb_buf[1] << 8)
+ | vty->sb_buf[2]);
+ vty->height = ((vty->sb_buf[3] << 8)
+ | vty->sb_buf[4]);
+#ifdef TELNET_OPTION_DEBUG
+ vty_out(vty,
+ "TELNET NAWS window size negotiation completed: width %d, height %d\n",
+ vty->width, vty->height);
+#endif
+ }
+ break;
+ }
+ vty->iac_sb_in_progress = 0;
+ return 0;
+ }
+ default:
+ break;
+ }
+ return 1;
+}
+
+/* Execute current command line. */
+static int vty_execute(struct vty *vty)
+{
+ int ret;
+
+ ret = CMD_SUCCESS;
+
+ switch (vty->node) {
+ case AUTH_NODE:
+ case AUTH_ENABLE_NODE:
+ vty_auth(vty, vty->buf);
+ break;
+ default:
+ ret = vty_command(vty, vty->buf);
+ if (vty->type == VTY_TERM)
+ vty_hist_add(vty);
+ break;
+ }
+
+ /* Clear command line buffer. */
+ vty->cp = vty->length = 0;
+ vty_clear_buf(vty);
+
+ if (vty->status != VTY_CLOSE)
+ vty_prompt(vty);
+
+ return ret;
+}
+
+#define CONTROL(X) ((X) - '@')
+#define VTY_NORMAL 0
+#define VTY_PRE_ESCAPE 1
+#define VTY_ESCAPE 2
+#define VTY_CR 3
+
+/* Escape character command map. */
+static void vty_escape_map(unsigned char c, struct vty *vty)
+{
+ switch (c) {
+ case ('A'):
+ vty_previous_line(vty);
+ break;
+ case ('B'):
+ vty_next_line(vty);
+ break;
+ case ('C'):
+ vty_forward_char(vty);
+ break;
+ case ('D'):
+ vty_backward_char(vty);
+ break;
+ default:
+ break;
+ }
+
+ /* Go back to normal mode. */
+ vty->escape = VTY_NORMAL;
+}
+
+/* Quit print out to the buffer. */
+static void vty_buffer_reset(struct vty *vty)
+{
+ buffer_reset(vty->obuf);
+ buffer_reset(vty->lbuf);
+ vty_prompt(vty);
+ vty_redraw_line(vty);
+}
+
+/* Read data via vty socket. */
+static void vty_read(struct thread *thread)
+{
+ int i;
+ int nbytes;
+ unsigned char buf[VTY_READ_BUFSIZ];
+
+ struct vty *vty = THREAD_ARG(thread);
+
+ /* Read raw data from socket */
+ if ((nbytes = read(vty->fd, buf, VTY_READ_BUFSIZ)) <= 0) {
+ if (nbytes < 0) {
+ if (ERRNO_IO_RETRY(errno)) {
+ vty_event(VTY_READ, vty);
+ return;
+ }
+ flog_err(
+ EC_LIB_SOCKET,
+ "%s: read error on vty client fd %d, closing: %s",
+ __func__, vty->fd, safe_strerror(errno));
+ buffer_reset(vty->obuf);
+ buffer_reset(vty->lbuf);
+ }
+ vty->status = VTY_CLOSE;
+ }
+
+ for (i = 0; i < nbytes; i++) {
+ if (buf[i] == IAC) {
+ if (!vty->iac) {
+ vty->iac = 1;
+ continue;
+ } else {
+ vty->iac = 0;
+ }
+ }
+
+ if (vty->iac_sb_in_progress && !vty->iac) {
+ if (vty->sb_len < sizeof(vty->sb_buf))
+ vty->sb_buf[vty->sb_len] = buf[i];
+ vty->sb_len++;
+ continue;
+ }
+
+ if (vty->iac) {
+ /* In case of telnet command */
+ int ret = 0;
+ ret = vty_telnet_option(vty, buf + i, nbytes - i);
+ vty->iac = 0;
+ i += ret;
+ continue;
+ }
+
+
+ if (vty->status == VTY_MORE) {
+ switch (buf[i]) {
+ case CONTROL('C'):
+ case 'q':
+ case 'Q':
+ vty_buffer_reset(vty);
+ break;
+ default:
+ break;
+ }
+ continue;
+ }
+
+ /* Escape character. */
+ if (vty->escape == VTY_ESCAPE) {
+ vty_escape_map(buf[i], vty);
+ continue;
+ }
+
+ /* Pre-escape status. */
+ if (vty->escape == VTY_PRE_ESCAPE) {
+ switch (buf[i]) {
+ case '[':
+ vty->escape = VTY_ESCAPE;
+ break;
+ case 'b':
+ vty_backward_word(vty);
+ vty->escape = VTY_NORMAL;
+ break;
+ case 'f':
+ vty_forward_word(vty);
+ vty->escape = VTY_NORMAL;
+ break;
+ case 'd':
+ vty_forward_kill_word(vty);
+ vty->escape = VTY_NORMAL;
+ break;
+ case CONTROL('H'):
+ case 0x7f:
+ vty_backward_kill_word(vty);
+ vty->escape = VTY_NORMAL;
+ break;
+ default:
+ vty->escape = VTY_NORMAL;
+ break;
+ }
+ continue;
+ }
+
+ if (vty->escape == VTY_CR) {
+ /* if we get CR+NL, the NL results in an extra empty
+ * prompt line being printed without this; just drop
+ * the NL if it immediately follows CR.
+ */
+ vty->escape = VTY_NORMAL;
+
+ if (buf[i] == '\n')
+ continue;
+ }
+
+ switch (buf[i]) {
+ case CONTROL('A'):
+ vty_beginning_of_line(vty);
+ break;
+ case CONTROL('B'):
+ vty_backward_char(vty);
+ break;
+ case CONTROL('C'):
+ vty_stop_input(vty);
+ break;
+ case CONTROL('D'):
+ vty_delete_char(vty);
+ break;
+ case CONTROL('E'):
+ vty_end_of_line(vty);
+ break;
+ case CONTROL('F'):
+ vty_forward_char(vty);
+ break;
+ case CONTROL('H'):
+ case 0x7f:
+ vty_delete_backward_char(vty);
+ break;
+ case CONTROL('K'):
+ vty_kill_line(vty);
+ break;
+ case CONTROL('N'):
+ vty_next_line(vty);
+ break;
+ case CONTROL('P'):
+ vty_previous_line(vty);
+ break;
+ case CONTROL('T'):
+ vty_transpose_chars(vty);
+ break;
+ case CONTROL('U'):
+ vty_kill_line_from_beginning(vty);
+ break;
+ case CONTROL('W'):
+ vty_backward_kill_word(vty);
+ break;
+ case CONTROL('Z'):
+ vty_end_config(vty);
+ break;
+ case '\r':
+ vty->escape = VTY_CR;
+ /* fallthru */
+ case '\n':
+ vty_out(vty, "\n");
+ buffer_flush_available(vty->obuf, vty->wfd);
+ vty_execute(vty);
+
+ if (vty->pass_fd != -1) {
+ close(vty->pass_fd);
+ vty->pass_fd = -1;
+ }
+ break;
+ case '\t':
+ vty_complete_command(vty);
+ break;
+ case '?':
+ if (vty->node == AUTH_NODE
+ || vty->node == AUTH_ENABLE_NODE)
+ vty_self_insert(vty, buf[i]);
+ else
+ vty_describe_command(vty);
+ break;
+ case '\033':
+ if (i + 1 < nbytes && buf[i + 1] == '[') {
+ vty->escape = VTY_ESCAPE;
+ i++;
+ } else
+ vty->escape = VTY_PRE_ESCAPE;
+ break;
+ default:
+ if (buf[i] > 31 && buf[i] < 127)
+ vty_self_insert(vty, buf[i]);
+ break;
+ }
+ }
+
+ /* Check status. */
+ if (vty->status == VTY_CLOSE)
+ vty_close(vty);
+ else {
+ vty_event(VTY_WRITE, vty);
+ vty_event(VTY_READ, vty);
+ }
+}
+
+/* Flush buffer to the vty. */
+static void vty_flush(struct thread *thread)
+{
+ int erase;
+ buffer_status_t flushrc;
+ struct vty *vty = THREAD_ARG(thread);
+
+ /* Tempolary disable read thread. */
+ if (vty->lines == 0)
+ THREAD_OFF(vty->t_read);
+
+ /* Function execution continue. */
+ erase = ((vty->status == VTY_MORE || vty->status == VTY_MORELINE));
+
+ /* N.B. if width is 0, that means we don't know the window size. */
+ if ((vty->lines == 0) || (vty->width == 0) || (vty->height == 0))
+ flushrc = buffer_flush_available(vty->obuf, vty->wfd);
+ else if (vty->status == VTY_MORELINE)
+ flushrc = buffer_flush_window(vty->obuf, vty->wfd, vty->width,
+ 1, erase, 0);
+ else
+ flushrc = buffer_flush_window(
+ vty->obuf, vty->wfd, vty->width,
+ vty->lines >= 0 ? vty->lines : vty->height, erase, 0);
+ switch (flushrc) {
+ case BUFFER_ERROR:
+ zlog_info("buffer_flush failed on vty client fd %d/%d, closing",
+ vty->fd, vty->wfd);
+ buffer_reset(vty->lbuf);
+ buffer_reset(vty->obuf);
+ vty_close(vty);
+ return;
+ case BUFFER_EMPTY:
+ if (vty->status == VTY_CLOSE)
+ vty_close(vty);
+ else {
+ vty->status = VTY_NORMAL;
+ if (vty->lines == 0)
+ vty_event(VTY_READ, vty);
+ }
+ break;
+ case BUFFER_PENDING:
+ /* There is more data waiting to be written. */
+ vty->status = VTY_MORE;
+ if (vty->lines == 0)
+ vty_event(VTY_WRITE, vty);
+ break;
+ }
+}
+
+/* Allocate new vty struct. */
+struct vty *vty_new(void)
+{
+ struct vty *new = XCALLOC(MTYPE_VTY, sizeof(struct vty));
+
+ new->fd = new->wfd = -1;
+ new->of = stdout;
+ new->lbuf = buffer_new(0);
+ new->obuf = buffer_new(0); /* Use default buffer size. */
+ new->buf = XCALLOC(MTYPE_VTY, VTY_BUFSIZ);
+ new->max = VTY_BUFSIZ;
+ new->pass_fd = -1;
+
+ return new;
+}
+
+
+/* allocate and initialise vty */
+static struct vty *vty_new_init(int vty_sock)
+{
+ struct vty *vty;
+
+ vty = vty_new();
+ vty->fd = vty_sock;
+ vty->wfd = vty_sock;
+ vty->type = VTY_TERM;
+ vty->node = AUTH_NODE;
+ vty->fail = 0;
+ vty->cp = 0;
+ vty_clear_buf(vty);
+ vty->length = 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;
+ vty->status = VTY_NORMAL;
+ vty->lines = -1;
+ vty->iac = 0;
+ vty->iac_sb_in_progress = 0;
+ vty->sb_len = 0;
+
+ vtys_add_tail(vty_sessions, vty);
+
+ return vty;
+}
+
+/* Create new vty structure. */
+static struct vty *vty_create(int vty_sock, union sockunion *su)
+{
+ char buf[SU_ADDRSTRLEN];
+ struct vty *vty;
+
+ sockunion2str(su, buf, SU_ADDRSTRLEN);
+
+ /* Allocate new vty structure and set up default values. */
+ vty = vty_new_init(vty_sock);
+
+ /* configurable parameters not part of basic init */
+ vty->v_timeout = vty_timeout_val;
+ strlcpy(vty->address, buf, sizeof(vty->address));
+ if (no_password_check) {
+ if (host.advanced)
+ vty->node = ENABLE_NODE;
+ else
+ vty->node = VIEW_NODE;
+ }
+ if (host.lines >= 0)
+ vty->lines = host.lines;
+
+ if (!no_password_check) {
+ /* Vty is not available if password isn't set. */
+ if (host.password == NULL && host.password_encrypt == NULL) {
+ vty_out(vty, "Vty password is not set.\n");
+ vty->status = VTY_CLOSE;
+ vty_close(vty);
+ return NULL;
+ }
+ }
+
+ /* Say hello to the world. */
+ vty_hello(vty);
+ if (!no_password_check)
+ vty_out(vty, "\nUser Access Verification\n\n");
+
+ /* Setting up terminal. */
+ vty_will_echo(vty);
+ vty_will_suppress_go_ahead(vty);
+
+ vty_dont_linemode(vty);
+ vty_do_window_size(vty);
+ /* vty_dont_lflow_ahead (vty); */
+
+ vty_prompt(vty);
+
+ /* Add read/write thread. */
+ vty_event(VTY_WRITE, vty);
+ vty_event(VTY_READ, vty);
+
+ return vty;
+}
+
+/* create vty for stdio */
+static struct termios stdio_orig_termios;
+static struct vty *stdio_vty = NULL;
+static bool stdio_termios = false;
+static void (*stdio_vty_atclose)(int isexit);
+
+static void vty_stdio_reset(int isexit)
+{
+ if (stdio_vty) {
+ if (stdio_termios)
+ tcsetattr(0, TCSANOW, &stdio_orig_termios);
+ stdio_termios = false;
+
+ stdio_vty = NULL;
+
+ if (stdio_vty_atclose)
+ stdio_vty_atclose(isexit);
+ stdio_vty_atclose = NULL;
+ }
+}
+
+static void vty_stdio_atexit(void)
+{
+ vty_stdio_reset(1);
+}
+
+void vty_stdio_suspend(void)
+{
+ if (!stdio_vty)
+ return;
+
+ THREAD_OFF(stdio_vty->t_write);
+ THREAD_OFF(stdio_vty->t_read);
+ THREAD_OFF(stdio_vty->t_timeout);
+
+ if (stdio_termios)
+ tcsetattr(0, TCSANOW, &stdio_orig_termios);
+ stdio_termios = false;
+}
+
+void vty_stdio_resume(void)
+{
+ if (!stdio_vty)
+ return;
+
+ if (!tcgetattr(0, &stdio_orig_termios)) {
+ struct termios termios;
+
+ termios = stdio_orig_termios;
+ termios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR
+ | IGNCR | ICRNL | IXON);
+ termios.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN);
+ termios.c_cflag &= ~(CSIZE | PARENB);
+ termios.c_cflag |= CS8;
+ tcsetattr(0, TCSANOW, &termios);
+ stdio_termios = true;
+ }
+
+ vty_prompt(stdio_vty);
+
+ /* Add read/write thread. */
+ vty_event(VTY_WRITE, stdio_vty);
+ vty_event(VTY_READ, stdio_vty);
+}
+
+void vty_stdio_close(void)
+{
+ if (!stdio_vty)
+ return;
+ vty_close(stdio_vty);
+}
+
+struct vty *vty_stdio(void (*atclose)(int isexit))
+{
+ struct vty *vty;
+
+ /* refuse creating two vtys on stdio */
+ if (stdio_vty)
+ return NULL;
+
+ vty = stdio_vty = vty_new_init(0);
+ stdio_vty_atclose = atclose;
+ vty->wfd = 1;
+
+ /* always have stdio vty in a known _unchangeable_ state, don't want
+ * config
+ * to have any effect here to make sure scripting this works as intended
+ */
+ vty->node = ENABLE_NODE;
+ vty->v_timeout = 0;
+ strlcpy(vty->address, "console", sizeof(vty->address));
+
+ vty_stdio_resume();
+ return vty;
+}
+
+/* Accept connection from the network. */
+static void vty_accept(struct thread *thread)
+{
+ struct vty_serv *vtyserv = THREAD_ARG(thread);
+ int vty_sock;
+ union sockunion su;
+ int ret;
+ unsigned int on;
+ int accept_sock = vtyserv->sock;
+ struct prefix p;
+ struct access_list *acl = NULL;
+
+ /* We continue hearing vty socket. */
+ vty_event_serv(VTY_SERV, vtyserv);
+
+ memset(&su, 0, sizeof(union sockunion));
+
+ /* We can handle IPv4 or IPv6 socket. */
+ vty_sock = sockunion_accept(accept_sock, &su);
+ if (vty_sock < 0) {
+ flog_err(EC_LIB_SOCKET, "can't accept vty socket : %s",
+ safe_strerror(errno));
+ return;
+ }
+ set_nonblocking(vty_sock);
+ set_cloexec(vty_sock);
+
+ if (!sockunion2hostprefix(&su, &p)) {
+ close(vty_sock);
+ zlog_info("Vty unable to convert prefix from sockunion %pSU",
+ &su);
+ return;
+ }
+
+ /* VTY's accesslist apply. */
+ if (p.family == AF_INET && vty_accesslist_name) {
+ if ((acl = access_list_lookup(AFI_IP, vty_accesslist_name))
+ && (access_list_apply(acl, &p) == FILTER_DENY)) {
+ zlog_info("Vty connection refused from %pSU", &su);
+ close(vty_sock);
+ return;
+ }
+ }
+
+ /* VTY's ipv6 accesslist apply. */
+ if (p.family == AF_INET6 && vty_ipv6_accesslist_name) {
+ if ((acl = access_list_lookup(AFI_IP6,
+ vty_ipv6_accesslist_name))
+ && (access_list_apply(acl, &p) == FILTER_DENY)) {
+ zlog_info("Vty connection refused from %pSU", &su);
+ close(vty_sock);
+ return;
+ }
+ }
+
+ on = 1;
+ ret = setsockopt(vty_sock, IPPROTO_TCP, TCP_NODELAY, (char *)&on,
+ sizeof(on));
+ if (ret < 0)
+ zlog_info("can't set sockopt to vty_sock : %s",
+ safe_strerror(errno));
+
+ zlog_info("Vty connection from %pSU", &su);
+
+ vty_create(vty_sock, &su);
+}
+
+static void vty_serv_sock_addrinfo(const char *hostname, unsigned short port)
+{
+ int ret;
+ struct addrinfo req;
+ struct addrinfo *ainfo;
+ struct addrinfo *ainfo_save;
+ int sock;
+ char port_str[BUFSIZ];
+
+ memset(&req, 0, sizeof(req));
+ req.ai_flags = AI_PASSIVE;
+ req.ai_family = AF_UNSPEC;
+ req.ai_socktype = SOCK_STREAM;
+ snprintf(port_str, sizeof(port_str), "%d", port);
+ port_str[sizeof(port_str) - 1] = '\0';
+
+ ret = getaddrinfo(hostname, port_str, &req, &ainfo);
+
+ if (ret != 0) {
+ flog_err_sys(EC_LIB_SYSTEM_CALL, "getaddrinfo failed: %s",
+ gai_strerror(ret));
+ exit(1);
+ }
+
+ ainfo_save = ainfo;
+
+ do {
+ struct vty_serv *vtyserv;
+
+ if (ainfo->ai_family != AF_INET && ainfo->ai_family != AF_INET6)
+ continue;
+
+ sock = socket(ainfo->ai_family, ainfo->ai_socktype,
+ ainfo->ai_protocol);
+ if (sock < 0)
+ continue;
+
+ sockopt_v6only(ainfo->ai_family, sock);
+ sockopt_reuseaddr(sock);
+ sockopt_reuseport(sock);
+ set_cloexec(sock);
+
+ ret = bind(sock, ainfo->ai_addr, ainfo->ai_addrlen);
+ if (ret < 0) {
+ close(sock); /* Avoid sd leak. */
+ continue;
+ }
+
+ ret = listen(sock, 3);
+ if (ret < 0) {
+ close(sock); /* Avoid sd leak. */
+ continue;
+ }
+
+ vtyserv = XCALLOC(MTYPE_VTY_SERV, sizeof(*vtyserv));
+ vtyserv->sock = sock;
+ vtyservs_add_tail(vty_servs, vtyserv);
+
+ vty_event_serv(VTY_SERV, vtyserv);
+ } while ((ainfo = ainfo->ai_next) != NULL);
+
+ freeaddrinfo(ainfo_save);
+}
+
+#ifdef VTYSH
+/* For sockaddr_un. */
+#include <sys/un.h>
+
+/* VTY shell UNIX domain socket. */
+static void vty_serv_un(const char *path)
+{
+ struct vty_serv *vtyserv;
+ int ret;
+ int sock, len;
+ struct sockaddr_un serv;
+ mode_t old_mask;
+ struct zprivs_ids_t ids;
+
+ /* First of all, unlink existing socket */
+ unlink(path);
+
+ /* Set umask */
+ old_mask = umask(0007);
+
+ /* Make UNIX domain socket. */
+ sock = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (sock < 0) {
+ flog_err_sys(EC_LIB_SOCKET,
+ "Cannot create unix stream socket: %s",
+ safe_strerror(errno));
+ return;
+ }
+
+ /* Make server socket. */
+ memset(&serv, 0, sizeof(serv));
+ serv.sun_family = AF_UNIX;
+ strlcpy(serv.sun_path, path, sizeof(serv.sun_path));
+#ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN
+ len = serv.sun_len = SUN_LEN(&serv);
+#else
+ len = sizeof(serv.sun_family) + strlen(serv.sun_path);
+#endif /* HAVE_STRUCT_SOCKADDR_UN_SUN_LEN */
+
+ set_cloexec(sock);
+
+ ret = bind(sock, (struct sockaddr *)&serv, len);
+ if (ret < 0) {
+ flog_err_sys(EC_LIB_SOCKET, "Cannot bind path %s: %s", path,
+ safe_strerror(errno));
+ close(sock); /* Avoid sd leak. */
+ return;
+ }
+
+ ret = listen(sock, 5);
+ if (ret < 0) {
+ flog_err_sys(EC_LIB_SOCKET, "listen(fd %d) failed: %s", sock,
+ safe_strerror(errno));
+ close(sock); /* Avoid sd leak. */
+ return;
+ }
+
+ umask(old_mask);
+
+ zprivs_get_ids(&ids);
+
+ /* Hack: ids.gid_vty is actually a uint, but we stored -1 in it
+ earlier for the case when we don't need to chown the file
+ type casting it here to make a compare */
+ if ((int)ids.gid_vty > 0) {
+ /* set group of socket */
+ if (chown(path, -1, ids.gid_vty)) {
+ flog_err_sys(EC_LIB_SYSTEM_CALL,
+ "vty_serv_un: could chown socket, %s",
+ safe_strerror(errno));
+ }
+ }
+
+ vtyserv = XCALLOC(MTYPE_VTY_SERV, sizeof(*vtyserv));
+ vtyserv->sock = sock;
+ vtyserv->vtysh = true;
+ vtyservs_add_tail(vty_servs, vtyserv);
+
+ vty_event_serv(VTYSH_SERV, vtyserv);
+}
+
+/* #define VTYSH_DEBUG 1 */
+
+static void vtysh_accept(struct thread *thread)
+{
+ struct vty_serv *vtyserv = THREAD_ARG(thread);
+ int accept_sock = vtyserv->sock;
+ int sock;
+ int client_len;
+ struct sockaddr_un client;
+ struct vty *vty;
+
+ vty_event_serv(VTYSH_SERV, vtyserv);
+
+ memset(&client, 0, sizeof(client));
+ client_len = sizeof(struct sockaddr_un);
+
+ sock = accept(accept_sock, (struct sockaddr *)&client,
+ (socklen_t *)&client_len);
+
+ if (sock < 0) {
+ flog_err(EC_LIB_SOCKET, "can't accept vty socket : %s",
+ safe_strerror(errno));
+ return;
+ }
+
+ if (set_nonblocking(sock) < 0) {
+ flog_err(
+ EC_LIB_SOCKET,
+ "vtysh_accept: could not set vty socket %d to non-blocking, %s, closing",
+ sock, safe_strerror(errno));
+ close(sock);
+ return;
+ }
+ set_cloexec(sock);
+
+#ifdef VTYSH_DEBUG
+ printf("VTY shell accept\n");
+#endif /* VTYSH_DEBUG */
+
+ vty = vty_new();
+ vty->fd = sock;
+ vty->wfd = sock;
+ vty->type = VTY_SHELL_SERV;
+ vty->node = VIEW_NODE;
+ vtys_add_tail(vtysh_sessions, vty);
+
+ vty_event(VTYSH_READ, vty);
+}
+
+static int vtysh_do_pass_fd(struct vty *vty)
+{
+ struct iovec iov[1] = {
+ {
+ .iov_base = vty->pass_fd_status,
+ .iov_len = sizeof(vty->pass_fd_status),
+ },
+ };
+ union {
+ uint8_t buf[CMSG_SPACE(sizeof(int))];
+ struct cmsghdr align;
+ } u;
+ struct msghdr mh = {
+ .msg_iov = iov,
+ .msg_iovlen = array_size(iov),
+ .msg_control = u.buf,
+ .msg_controllen = sizeof(u.buf),
+ };
+ struct cmsghdr *cmh = CMSG_FIRSTHDR(&mh);
+ ssize_t ret;
+
+ memset(&u.buf, 0, sizeof(u.buf));
+ cmh->cmsg_level = SOL_SOCKET;
+ cmh->cmsg_type = SCM_RIGHTS;
+ cmh->cmsg_len = CMSG_LEN(sizeof(int));
+ memcpy(CMSG_DATA(cmh), &vty->pass_fd, sizeof(int));
+
+ ret = sendmsg(vty->wfd, &mh, 0);
+ if (ret < 0 && ERRNO_IO_RETRY(errno))
+ return BUFFER_PENDING;
+
+ close(vty->pass_fd);
+ vty->pass_fd = -1;
+ vty->status = VTY_NORMAL;
+
+ if (ret <= 0)
+ return BUFFER_ERROR;
+
+ /* resume accepting commands (suspended in vtysh_read) */
+ vty_event(VTYSH_READ, vty);
+
+ if ((size_t)ret < sizeof(vty->pass_fd_status)) {
+ size_t remains = sizeof(vty->pass_fd_status) - ret;
+
+ buffer_put(vty->obuf, vty->pass_fd_status + ret, remains);
+ return BUFFER_PENDING;
+ }
+ return BUFFER_EMPTY;
+}
+
+static int vtysh_flush(struct vty *vty)
+{
+ int ret;
+
+ ret = buffer_flush_available(vty->obuf, vty->wfd);
+ if (ret == BUFFER_EMPTY && vty->status == VTY_PASSFD)
+ ret = vtysh_do_pass_fd(vty);
+
+ switch (ret) {
+ case BUFFER_PENDING:
+ vty_event(VTYSH_WRITE, vty);
+ break;
+ case BUFFER_ERROR:
+ flog_err(EC_LIB_SOCKET, "%s: write error to fd %d, closing",
+ __func__, vty->fd);
+ buffer_reset(vty->lbuf);
+ buffer_reset(vty->obuf);
+ vty_close(vty);
+ return -1;
+ case BUFFER_EMPTY:
+ break;
+ }
+ return 0;
+}
+
+void vty_pass_fd(struct vty *vty, int fd)
+{
+ if (vty->pass_fd != -1)
+ close(vty->pass_fd);
+
+ vty->pass_fd = fd;
+}
+
+static void vtysh_read(struct thread *thread)
+{
+ int ret;
+ int sock;
+ int nbytes;
+ struct vty *vty;
+ unsigned char buf[VTY_READ_BUFSIZ];
+ unsigned char *p;
+ uint8_t header[4] = {0, 0, 0, 0};
+
+ sock = THREAD_FD(thread);
+ vty = THREAD_ARG(thread);
+
+ if ((nbytes = read(sock, buf, VTY_READ_BUFSIZ)) <= 0) {
+ if (nbytes < 0) {
+ if (ERRNO_IO_RETRY(errno)) {
+ vty_event(VTYSH_READ, vty);
+ return;
+ }
+ flog_err(
+ EC_LIB_SOCKET,
+ "%s: read failed on vtysh client fd %d, closing: %s",
+ __func__, sock, safe_strerror(errno));
+ }
+ buffer_reset(vty->lbuf);
+ buffer_reset(vty->obuf);
+ vty_close(vty);
+#ifdef VTYSH_DEBUG
+ printf("close vtysh\n");
+#endif /* VTYSH_DEBUG */
+ return;
+ }
+
+#ifdef VTYSH_DEBUG
+ printf("line: %.*s\n", nbytes, buf);
+#endif /* VTYSH_DEBUG */
+
+ if (vty->length + nbytes >= VTY_BUFSIZ) {
+ /* Clear command line buffer. */
+ vty->cp = vty->length = 0;
+ vty_clear_buf(vty);
+ vty_out(vty, "%% Command is too long.\n");
+ } else {
+ for (p = buf; p < buf + nbytes; p++) {
+ vty->buf[vty->length++] = *p;
+ if (*p == '\0') {
+ /* Pass this line to parser. */
+ ret = vty_execute(vty);
+/* Note that vty_execute clears the command buffer and resets
+ vty->length to 0. */
+
+/* Return result. */
+#ifdef VTYSH_DEBUG
+ printf("result: %d\n", ret);
+ printf("vtysh node: %d\n", vty->node);
+#endif /* VTYSH_DEBUG */
+
+ if (vty->pass_fd != -1) {
+ memset(vty->pass_fd_status, 0, 4);
+ vty->pass_fd_status[3] = ret;
+ vty->status = VTY_PASSFD;
+
+ if (!vty->t_write)
+ vty_event(VTYSH_WRITE, vty);
+
+ /* this introduces a "sequence point"
+ * command output is written normally,
+ * read processing is suspended until
+ * buffer is empty
+ * then retcode + FD is written
+ * then normal processing resumes
+ *
+ * => skip vty_event(VTYSH_READ, vty)!
+ */
+ return;
+ }
+
+ /* hack for asynchronous "write integrated"
+ * - other commands in "buf" will be ditched
+ * - input during pending config-write is
+ * "unsupported" */
+ if (ret == CMD_SUSPEND)
+ break;
+
+ /* warning: watchfrr hardcodes this result write
+ */
+ header[3] = ret;
+ buffer_put(vty->obuf, header, 4);
+
+ if (!vty->t_write && (vtysh_flush(vty) < 0))
+ /* Try to flush results; exit if a write
+ * error occurs. */
+ return;
+ }
+ }
+ }
+
+ if (vty->status == VTY_CLOSE)
+ vty_close(vty);
+ else
+ vty_event(VTYSH_READ, vty);
+}
+
+static void vtysh_write(struct thread *thread)
+{
+ struct vty *vty = THREAD_ARG(thread);
+
+ vtysh_flush(vty);
+}
+
+#endif /* VTYSH */
+
+/* Determine address family to bind. */
+void vty_serv_sock(const char *addr, unsigned short port, const char *path)
+{
+ /* If port is set to 0, do not listen on TCP/IP at all! */
+ if (port)
+ vty_serv_sock_addrinfo(addr, port);
+
+#ifdef VTYSH
+ vty_serv_un(path);
+#endif /* VTYSH */
+}
+
+static void vty_error_delete(void *arg)
+{
+ struct vty_error *ve = arg;
+
+ XFREE(MTYPE_TMP, ve);
+}
+
+/* Close vty interface. Warning: call this only from functions that
+ will be careful not to access the vty afterwards (since it has
+ now been freed). This is safest from top-level functions (called
+ directly by the thread dispatcher). */
+void vty_close(struct vty *vty)
+{
+ int i;
+ bool was_stdio = false;
+
+ /* Drop out of configure / transaction if needed. */
+ vty_config_exit(vty);
+
+ /* Cancel threads.*/
+ THREAD_OFF(vty->t_read);
+ THREAD_OFF(vty->t_write);
+ THREAD_OFF(vty->t_timeout);
+
+ if (vty->pass_fd != -1) {
+ close(vty->pass_fd);
+ vty->pass_fd = -1;
+ }
+ zlog_live_close(&vty->live_log);
+
+ /* Flush buffer. */
+ buffer_flush_all(vty->obuf, vty->wfd);
+
+ /* Free input buffer. */
+ buffer_free(vty->obuf);
+ buffer_free(vty->lbuf);
+
+ /* Free command history. */
+ for (i = 0; i < VTY_MAXHIST; i++) {
+ XFREE(MTYPE_VTY_HIST, vty->hist[i]);
+ }
+
+ /* Unset vector. */
+ if (vty->fd != -1) {
+ if (vty->type == VTY_SHELL_SERV)
+ vtys_del(vtysh_sessions, vty);
+ else
+ vtys_del(vty_sessions, vty);
+ }
+
+ if (vty->wfd > 0 && vty->type == VTY_FILE)
+ fsync(vty->wfd);
+
+ /* Close socket.
+ * note check is for fd > STDERR_FILENO, not fd != -1.
+ * We never close stdin/stdout/stderr here, because we may be
+ * running in foreground mode with logging to stdout. Also,
+ * additionally, we'd need to replace these fds with /dev/null. */
+ if (vty->wfd > STDERR_FILENO && vty->wfd != vty->fd)
+ close(vty->wfd);
+ if (vty->fd > STDERR_FILENO)
+ close(vty->fd);
+ if (vty->fd == STDIN_FILENO)
+ was_stdio = true;
+
+ XFREE(MTYPE_VTY, vty->buf);
+
+ if (vty->error) {
+ vty->error->del = vty_error_delete;
+ list_delete(&vty->error);
+ }
+
+ /* OK free vty. */
+ XFREE(MTYPE_VTY, vty);
+
+ if (was_stdio)
+ vty_stdio_reset(0);
+}
+
+/* When time out occur output message then close connection. */
+static void vty_timeout(struct thread *thread)
+{
+ struct vty *vty;
+
+ vty = THREAD_ARG(thread);
+ vty->v_timeout = 0;
+
+ /* Clear buffer*/
+ buffer_reset(vty->lbuf);
+ buffer_reset(vty->obuf);
+ vty_out(vty, "\nVty connection is timed out.\n");
+
+ /* Close connection. */
+ vty->status = VTY_CLOSE;
+ vty_close(vty);
+}
+
+/* Read up configuration file from file_name. */
+static void vty_read_file(struct nb_config *config, FILE *confp)
+{
+ int ret;
+ struct vty *vty;
+ struct vty_error *ve;
+ struct listnode *node;
+ unsigned int line_num = 0;
+
+ vty = vty_new();
+ /* vty_close won't close stderr; if some config command prints
+ * something it'll end up there. (not ideal; it'd be better if output
+ * from a file-load went to logging instead. Also note that if this
+ * function is called after daemonizing, stderr will be /dev/null.)
+ *
+ * vty->fd will be -1 from vty_new()
+ */
+ vty->wfd = STDERR_FILENO;
+ vty->type = VTY_FILE;
+ vty->node = CONFIG_NODE;
+ vty->config = true;
+ 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);
+
+ /* Flush any previous errors before printing messages below */
+ buffer_flush_all(vty->obuf, vty->wfd);
+
+ if (!((ret == CMD_SUCCESS) || (ret == CMD_ERR_NOTHING_TODO))) {
+ const char *message = NULL;
+ char *nl;
+
+ switch (ret) {
+ case CMD_ERR_AMBIGUOUS:
+ message = "Ambiguous command";
+ break;
+ case CMD_ERR_NO_MATCH:
+ message = "No such command";
+ break;
+ case CMD_WARNING:
+ message = "Command returned Warning";
+ break;
+ case CMD_WARNING_CONFIG_FAILED:
+ message = "Command returned Warning Config Failed";
+ break;
+ case CMD_ERR_INCOMPLETE:
+ message = "Command returned Incomplete";
+ break;
+ case CMD_ERR_EXEED_ARGC_MAX:
+ message =
+ "Command exceeded maximum number of Arguments";
+ break;
+ default:
+ message = "Command returned unhandled error message";
+ break;
+ }
+
+ for (ALL_LIST_ELEMENTS_RO(vty->error, node, ve)) {
+ nl = strchr(ve->error_buf, '\n');
+ if (nl)
+ *nl = '\0';
+ flog_err(EC_LIB_VTY, "%s on config line %u: %s",
+ message, ve->line_num, ve->error_buf);
+ }
+ }
+
+ /*
+ * Automatically commit the candidate configuration after
+ * reading the configuration file.
+ */
+ if (config == NULL) {
+ struct nb_context context = {};
+ char errmsg[BUFSIZ] = {0};
+
+ context.client = NB_CLIENT_CLI;
+ context.user = vty;
+ ret = nb_candidate_commit(&context, vty->candidate_config, true,
+ "Read configuration file", NULL,
+ errmsg, sizeof(errmsg));
+ if (ret != NB_OK && ret != NB_ERR_NO_CHANGES)
+ zlog_err(
+ "%s: failed to read configuration file: %s (%s)",
+ __func__, nb_err_name(ret), errmsg);
+ }
+
+ vty_close(vty);
+}
+
+static FILE *vty_use_backup_config(const char *fullpath)
+{
+ char *fullpath_sav, *fullpath_tmp;
+ FILE *ret = NULL;
+ int tmp, sav;
+ int c;
+ char buffer[512];
+
+ size_t fullpath_sav_sz = strlen(fullpath) + strlen(CONF_BACKUP_EXT) + 1;
+ fullpath_sav = malloc(fullpath_sav_sz);
+ strlcpy(fullpath_sav, fullpath, fullpath_sav_sz);
+ strlcat(fullpath_sav, CONF_BACKUP_EXT, fullpath_sav_sz);
+
+ sav = open(fullpath_sav, O_RDONLY);
+ if (sav < 0) {
+ free(fullpath_sav);
+ return NULL;
+ }
+
+ fullpath_tmp = malloc(strlen(fullpath) + 8);
+ snprintf(fullpath_tmp, strlen(fullpath) + 8, "%s.XXXXXX", fullpath);
+
+ /* Open file to configuration write. */
+ tmp = mkstemp(fullpath_tmp);
+ if (tmp < 0)
+ goto out_close_sav;
+
+ if (fchmod(tmp, CONFIGFILE_MASK) != 0)
+ goto out_close;
+
+ while ((c = read(sav, buffer, 512)) > 0) {
+ if (write(tmp, buffer, c) <= 0)
+ goto out_close;
+ }
+ close(sav);
+ close(tmp);
+
+ if (rename(fullpath_tmp, fullpath) == 0)
+ ret = fopen(fullpath, "r");
+ else
+ unlink(fullpath_tmp);
+
+ if (0) {
+ out_close:
+ close(tmp);
+ unlink(fullpath_tmp);
+ out_close_sav:
+ close(sav);
+ }
+
+ free(fullpath_sav);
+ free(fullpath_tmp);
+ return ret;
+}
+
+/* Read up configuration file from file_name. */
+bool vty_read_config(struct nb_config *config, const char *config_file,
+ char *config_default_dir)
+{
+ char cwd[MAXPATHLEN];
+ FILE *confp = NULL;
+ const char *fullpath;
+ char *tmp = NULL;
+ bool read_success = false;
+
+ /* If -f flag specified. */
+ if (config_file != NULL) {
+ if (!IS_DIRECTORY_SEP(config_file[0])) {
+ if (getcwd(cwd, MAXPATHLEN) == NULL) {
+ flog_err_sys(
+ EC_LIB_SYSTEM_CALL,
+ "%s: failure to determine Current Working Directory %d!",
+ __func__, errno);
+ goto tmp_free_and_out;
+ }
+ size_t tmp_len = strlen(cwd) + strlen(config_file) + 2;
+ tmp = XMALLOC(MTYPE_TMP, tmp_len);
+ snprintf(tmp, tmp_len, "%s/%s", cwd, config_file);
+ fullpath = tmp;
+ } else
+ fullpath = config_file;
+
+ confp = fopen(fullpath, "r");
+
+ if (confp == NULL) {
+ flog_warn(
+ EC_LIB_BACKUP_CONFIG,
+ "%s: failed to open configuration file %s: %s, checking backup",
+ __func__, fullpath, safe_strerror(errno));
+
+ confp = vty_use_backup_config(fullpath);
+ if (confp)
+ flog_warn(EC_LIB_BACKUP_CONFIG,
+ "using backup configuration file!");
+ else {
+ flog_err(
+ EC_LIB_VTY,
+ "%s: can't open configuration file [%s]",
+ __func__, config_file);
+ goto tmp_free_and_out;
+ }
+ }
+ } else {
+
+ host_config_set(config_default_dir);
+
+#ifdef VTYSH
+ int ret;
+ struct stat conf_stat;
+
+ /* !!!!PLEASE LEAVE!!!!
+ * This is NEEDED for use with vtysh -b, or else you can get
+ * a real configuration food fight with a lot garbage in the
+ * merged configuration file it creates coming from the per
+ * daemon configuration files. This also allows the daemons
+ * to start if there default configuration file is not
+ * present or ignore them, as needed when using vtysh -b to
+ * configure the daemons at boot - MAG
+ */
+
+ /* Stat for vtysh Zebra.conf, if found startup and wait for
+ * boot configuration
+ */
+
+ if (strstr(config_default_dir, "vtysh") == NULL) {
+ ret = stat(integrate_default, &conf_stat);
+ if (ret >= 0) {
+ read_success = true;
+ goto tmp_free_and_out;
+ }
+ }
+#endif /* VTYSH */
+ confp = fopen(config_default_dir, "r");
+ if (confp == NULL) {
+ flog_err(
+ EC_LIB_SYSTEM_CALL,
+ "%s: failed to open configuration file %s: %s, checking backup",
+ __func__, config_default_dir,
+ safe_strerror(errno));
+
+ confp = vty_use_backup_config(config_default_dir);
+ if (confp) {
+ flog_warn(EC_LIB_BACKUP_CONFIG,
+ "using backup configuration file!");
+ fullpath = config_default_dir;
+ } else {
+ flog_err(EC_LIB_VTY,
+ "can't open configuration file [%s]",
+ config_default_dir);
+ goto tmp_free_and_out;
+ }
+ } else
+ fullpath = config_default_dir;
+ }
+
+ vty_read_file(config, confp);
+ read_success = true;
+
+ fclose(confp);
+
+ host_config_set(fullpath);
+
+tmp_free_and_out:
+ XFREE(MTYPE_TMP, tmp);
+
+ return read_success;
+}
+
+static void update_xpath(struct vty *vty, const char *oldpath,
+ const char *newpath)
+{
+ int i;
+
+ for (i = 0; i < vty->xpath_index; i++) {
+ if (!frrstr_startswith(vty->xpath[i], oldpath))
+ break;
+
+ char *tmp = frrstr_replace(vty->xpath[i], oldpath, newpath);
+ strlcpy(vty->xpath[i], tmp, sizeof(vty->xpath[0]));
+ XFREE(MTYPE_TMP, tmp);
+ }
+}
+
+void vty_update_xpath(const char *oldpath, const char *newpath)
+{
+ struct vty *vty;
+
+ frr_each (vtys, vtysh_sessions, vty)
+ update_xpath(vty, oldpath, newpath);
+ frr_each (vtys, vty_sessions, vty)
+ update_xpath(vty, oldpath, newpath);
+}
+
+int vty_config_enter(struct vty *vty, bool private_config, bool exclusive)
+{
+ if (exclusive && nb_running_lock(NB_CLIENT_CLI, vty)) {
+ vty_out(vty, "%% Configuration is locked by other client\n");
+ return CMD_WARNING;
+ }
+
+ vty->node = CONFIG_NODE;
+ vty->config = true;
+ vty->private_config = private_config;
+ vty->xpath_index = 0;
+
+ if (private_config) {
+ 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");
+ } else {
+ 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;
+}
+
+void vty_config_exit(struct vty *vty)
+{
+ enum node_type node = vty->node;
+ struct cmd_node *cnode;
+
+ /* unlock and jump up to ENABLE_NODE if -and only if- we're
+ * somewhere below CONFIG_NODE */
+ while (node && node != CONFIG_NODE) {
+ cnode = vector_lookup(cmdvec, node);
+ node = cnode->parent_node;
+ }
+ if (node != CONFIG_NODE)
+ /* called outside config, e.g. vty_close() in ENABLE_NODE */
+ return;
+
+ while (vty->node != ENABLE_NODE)
+ /* will call vty_config_node_exit() below */
+ cmd_exit(vty);
+}
+
+int vty_config_node_exit(struct vty *vty)
+{
+ vty->xpath_index = 0;
+
+ /* Perform any pending commits. */
+ (void)nb_cli_pending_commit_check(vty);
+
+ /* Check if there's a pending confirmed commit. */
+ if (vty->t_confirmed_commit_timeout) {
+ vty_out(vty,
+ "exiting with a pending confirmed commit. Rolling back to previous configuration.\n\n");
+ nb_cli_confirmed_commit_rollback(vty);
+ nb_cli_confirmed_commit_clean(vty);
+ }
+
+ (void)nb_running_unlock(NB_CLIENT_CLI, 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;
+ }
+
+ vty->config = false;
+ return 1;
+}
+
+/* Master of the threads. */
+static struct thread_master *vty_master;
+
+static void vty_event_serv(enum vty_event event, struct vty_serv *vty_serv)
+{
+ switch (event) {
+ case VTY_SERV:
+ thread_add_read(vty_master, vty_accept, vty_serv,
+ vty_serv->sock, &vty_serv->t_accept);
+ break;
+#ifdef VTYSH
+ case VTYSH_SERV:
+ thread_add_read(vty_master, vtysh_accept, vty_serv,
+ vty_serv->sock, &vty_serv->t_accept);
+ break;
+#endif /* VTYSH */
+ default:
+ assert(!"vty_event_serv() called incorrectly");
+ }
+}
+
+static void vty_event(enum vty_event event, struct vty *vty)
+{
+ switch (event) {
+#ifdef VTYSH
+ case VTYSH_READ:
+ thread_add_read(vty_master, vtysh_read, vty, vty->fd,
+ &vty->t_read);
+ break;
+ case VTYSH_WRITE:
+ thread_add_write(vty_master, vtysh_write, vty, vty->wfd,
+ &vty->t_write);
+ break;
+#endif /* VTYSH */
+ case VTY_READ:
+ thread_add_read(vty_master, vty_read, vty, vty->fd,
+ &vty->t_read);
+
+ /* Time out treatment. */
+ if (vty->v_timeout) {
+ THREAD_OFF(vty->t_timeout);
+ thread_add_timer(vty_master, vty_timeout, vty,
+ vty->v_timeout, &vty->t_timeout);
+ }
+ break;
+ case VTY_WRITE:
+ thread_add_write(vty_master, vty_flush, vty, vty->wfd,
+ &vty->t_write);
+ break;
+ case VTY_TIMEOUT_RESET:
+ THREAD_OFF(vty->t_timeout);
+ if (vty->v_timeout)
+ thread_add_timer(vty_master, vty_timeout, vty,
+ vty->v_timeout, &vty->t_timeout);
+ break;
+ default:
+ assert(!"vty_event() called incorrectly");
+ }
+}
+
+DEFUN_NOSH (config_who,
+ config_who_cmd,
+ "who",
+ "Display who is on vty\n")
+{
+ struct vty *v;
+
+ frr_each (vtys, vty_sessions, v)
+ vty_out(vty, "%svty[%d] connected from %s%s.\n",
+ v->config ? "*" : " ", v->fd, v->address,
+ zlog_live_is_null(&v->live_log) ? "" : ", live log");
+ return CMD_SUCCESS;
+}
+
+/* Move to vty configuration mode. */
+DEFUN_NOSH (line_vty,
+ line_vty_cmd,
+ "line vty",
+ "Configure a terminal line\n"
+ "Virtual terminal\n")
+{
+ vty->node = VTY_NODE;
+ return CMD_SUCCESS;
+}
+
+/* Set time out value. */
+static int exec_timeout(struct vty *vty, const char *min_str,
+ const char *sec_str)
+{
+ unsigned long timeout = 0;
+
+ /* min_str and sec_str are already checked by parser. So it must be
+ all digit string. */
+ if (min_str) {
+ timeout = strtol(min_str, NULL, 10);
+ timeout *= 60;
+ }
+ if (sec_str)
+ timeout += strtol(sec_str, NULL, 10);
+
+ vty_timeout_val = timeout;
+ vty->v_timeout = timeout;
+ vty_event(VTY_TIMEOUT_RESET, vty);
+
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (exec_timeout_min,
+ exec_timeout_min_cmd,
+ "exec-timeout (0-35791)",
+ "Set timeout value\n"
+ "Timeout value in minutes\n")
+{
+ int idx_number = 1;
+ return exec_timeout(vty, argv[idx_number]->arg, NULL);
+}
+
+DEFUN (exec_timeout_sec,
+ exec_timeout_sec_cmd,
+ "exec-timeout (0-35791) (0-2147483)",
+ "Set the EXEC timeout\n"
+ "Timeout in minutes\n"
+ "Timeout in seconds\n")
+{
+ int idx_number = 1;
+ int idx_number_2 = 2;
+ return exec_timeout(vty, argv[idx_number]->arg,
+ argv[idx_number_2]->arg);
+}
+
+DEFUN (no_exec_timeout,
+ no_exec_timeout_cmd,
+ "no exec-timeout",
+ NO_STR
+ "Set the EXEC timeout\n")
+{
+ return exec_timeout(vty, NULL, NULL);
+}
+
+/* Set vty access class. */
+DEFUN (vty_access_class,
+ vty_access_class_cmd,
+ "access-class WORD",
+ "Filter connections based on an IP access list\n"
+ "IP access list\n")
+{
+ int idx_word = 1;
+ if (vty_accesslist_name)
+ XFREE(MTYPE_VTY, vty_accesslist_name);
+
+ vty_accesslist_name = XSTRDUP(MTYPE_VTY, argv[idx_word]->arg);
+
+ return CMD_SUCCESS;
+}
+
+/* Clear vty access class. */
+DEFUN (no_vty_access_class,
+ no_vty_access_class_cmd,
+ "no access-class [WORD]",
+ NO_STR
+ "Filter connections based on an IP access list\n"
+ "IP access list\n")
+{
+ int idx_word = 2;
+ const char *accesslist = (argc == 3) ? argv[idx_word]->arg : NULL;
+ if (!vty_accesslist_name
+ || (argc == 3 && strcmp(vty_accesslist_name, accesslist))) {
+ vty_out(vty, "Access-class is not currently applied to vty\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ XFREE(MTYPE_VTY, vty_accesslist_name);
+
+ vty_accesslist_name = NULL;
+
+ return CMD_SUCCESS;
+}
+
+/* Set vty access class. */
+DEFUN (vty_ipv6_access_class,
+ vty_ipv6_access_class_cmd,
+ "ipv6 access-class WORD",
+ IPV6_STR
+ "Filter connections based on an IP access list\n"
+ "IPv6 access list\n")
+{
+ int idx_word = 2;
+ if (vty_ipv6_accesslist_name)
+ XFREE(MTYPE_VTY, vty_ipv6_accesslist_name);
+
+ vty_ipv6_accesslist_name = XSTRDUP(MTYPE_VTY, argv[idx_word]->arg);
+
+ return CMD_SUCCESS;
+}
+
+/* Clear vty access class. */
+DEFUN (no_vty_ipv6_access_class,
+ no_vty_ipv6_access_class_cmd,
+ "no ipv6 access-class [WORD]",
+ NO_STR
+ IPV6_STR
+ "Filter connections based on an IP access list\n"
+ "IPv6 access list\n")
+{
+ int idx_word = 3;
+ const char *accesslist = (argc == 4) ? argv[idx_word]->arg : NULL;
+
+ if (!vty_ipv6_accesslist_name
+ || (argc == 4 && strcmp(vty_ipv6_accesslist_name, accesslist))) {
+ vty_out(vty,
+ "IPv6 access-class is not currently applied to vty\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ XFREE(MTYPE_VTY, vty_ipv6_accesslist_name);
+
+ vty_ipv6_accesslist_name = NULL;
+
+ return CMD_SUCCESS;
+}
+
+/* vty login. */
+DEFUN (vty_login,
+ vty_login_cmd,
+ "login",
+ "Enable password checking\n")
+{
+ no_password_check = 0;
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_vty_login,
+ no_vty_login_cmd,
+ "no login",
+ NO_STR
+ "Enable password checking\n")
+{
+ no_password_check = 1;
+ return CMD_SUCCESS;
+}
+
+DEFUN (service_advanced_vty,
+ service_advanced_vty_cmd,
+ "service advanced-vty",
+ "Set up miscellaneous service\n"
+ "Enable advanced mode vty interface\n")
+{
+ host.advanced = 1;
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_service_advanced_vty,
+ no_service_advanced_vty_cmd,
+ "no service advanced-vty",
+ NO_STR
+ "Set up miscellaneous service\n"
+ "Enable advanced mode vty interface\n")
+{
+ host.advanced = 0;
+ return CMD_SUCCESS;
+}
+
+DEFUN_NOSH(terminal_monitor,
+ terminal_monitor_cmd,
+ "terminal monitor [detach]",
+ "Set terminal line parameters\n"
+ "Copy debug output to the current terminal line\n"
+ "Keep logging feed open independent of VTY session\n")
+{
+ int fd_ret = -1;
+
+ if (vty->type != VTY_SHELL_SERV) {
+ vty_out(vty, "%% not supported\n");
+ return CMD_WARNING;
+ }
+
+ if (argc == 3) {
+ struct zlog_live_cfg detach_log = {};
+
+ zlog_live_open(&detach_log, LOG_DEBUG, &fd_ret);
+ zlog_live_disown(&detach_log);
+ } else
+ zlog_live_open(&vty->live_log, LOG_DEBUG, &fd_ret);
+
+ if (fd_ret == -1) {
+ vty_out(vty, "%% error opening live log: %m\n");
+ return CMD_WARNING;
+ }
+
+ vty_pass_fd(vty, fd_ret);
+ return CMD_SUCCESS;
+}
+
+DEFUN_NOSH(no_terminal_monitor,
+ no_terminal_monitor_cmd,
+ "no terminal monitor",
+ NO_STR
+ "Set terminal line parameters\n"
+ "Copy debug output to the current terminal line\n")
+{
+ zlog_live_close(&vty->live_log);
+ return CMD_SUCCESS;
+}
+
+DEFUN_NOSH(terminal_no_monitor,
+ terminal_no_monitor_cmd,
+ "terminal no monitor",
+ "Set terminal line parameters\n"
+ NO_STR
+ "Copy debug output to the current terminal line\n")
+{
+ return no_terminal_monitor(self, vty, argc, argv);
+}
+
+
+DEFUN_NOSH (show_history,
+ show_history_cmd,
+ "show history",
+ SHOW_STR
+ "Display the session command history\n")
+{
+ int index;
+
+ for (index = vty->hindex + 1; index != vty->hindex;) {
+ if (index == VTY_MAXHIST) {
+ index = 0;
+ continue;
+ }
+
+ if (vty->hist[index] != NULL)
+ vty_out(vty, " %s\n", vty->hist[index]);
+
+ index++;
+ }
+
+ return CMD_SUCCESS;
+}
+
+/* vty login. */
+DEFPY (log_commands,
+ log_commands_cmd,
+ "[no] log commands",
+ NO_STR
+ "Logging control\n"
+ "Log all commands\n")
+{
+ if (no) {
+ if (do_log_commands_perm) {
+ vty_out(vty,
+ "Daemon started with permanent logging turned on for commands, ignoring\n");
+ return CMD_WARNING;
+ }
+
+ do_log_commands = false;
+ } else
+ do_log_commands = true;
+
+ return CMD_SUCCESS;
+}
+
+/* Display current configuration. */
+static int vty_config_write(struct vty *vty)
+{
+ vty_frame(vty, "line vty\n");
+
+ if (vty_accesslist_name)
+ vty_out(vty, " access-class %s\n", vty_accesslist_name);
+
+ if (vty_ipv6_accesslist_name)
+ vty_out(vty, " ipv6 access-class %s\n",
+ vty_ipv6_accesslist_name);
+
+ /* exec-timeout */
+ if (vty_timeout_val != VTY_TIMEOUT_DEFAULT)
+ vty_out(vty, " exec-timeout %ld %ld\n", vty_timeout_val / 60,
+ vty_timeout_val % 60);
+
+ /* login */
+ if (no_password_check)
+ vty_out(vty, " no login\n");
+
+ vty_endframe(vty, "exit\n");
+
+ if (do_log_commands)
+ vty_out(vty, "log commands\n");
+
+ vty_out(vty, "!\n");
+
+ return CMD_SUCCESS;
+}
+
+static int vty_config_write(struct vty *vty);
+struct cmd_node vty_node = {
+ .name = "vty",
+ .node = VTY_NODE,
+ .parent_node = CONFIG_NODE,
+ .prompt = "%s(config-line)# ",
+ .config_write = vty_config_write,
+};
+
+/* Reset all VTY status. */
+void vty_reset(void)
+{
+ struct vty *vty;
+
+ frr_each_safe (vtys, vty_sessions, vty) {
+ buffer_reset(vty->lbuf);
+ buffer_reset(vty->obuf);
+ vty->status = VTY_CLOSE;
+ vty_close(vty);
+ }
+
+ vty_timeout_val = VTY_TIMEOUT_DEFAULT;
+
+ XFREE(MTYPE_VTY, vty_accesslist_name);
+ XFREE(MTYPE_VTY, vty_ipv6_accesslist_name);
+}
+
+static void vty_save_cwd(void)
+{
+ char *c;
+
+ c = getcwd(vty_cwd, sizeof(vty_cwd));
+
+ if (!c) {
+ /*
+ * At this point if these go wrong, more than likely
+ * the whole world is coming down around us
+ * Hence not worrying about it too much.
+ */
+ if (chdir(SYSCONFDIR)) {
+ flog_err_sys(EC_LIB_SYSTEM_CALL,
+ "Failure to chdir to %s, errno: %d",
+ SYSCONFDIR, errno);
+ exit(-1);
+ }
+ if (getcwd(vty_cwd, sizeof(vty_cwd)) == NULL) {
+ flog_err_sys(EC_LIB_SYSTEM_CALL,
+ "Failure to getcwd, errno: %d", errno);
+ exit(-1);
+ }
+ }
+}
+
+char *vty_get_cwd(void)
+{
+ return vty_cwd;
+}
+
+int vty_shell(struct vty *vty)
+{
+ return vty->type == VTY_SHELL ? 1 : 0;
+}
+
+int vty_shell_serv(struct vty *vty)
+{
+ return vty->type == VTY_SHELL_SERV ? 1 : 0;
+}
+
+void vty_init_vtysh(void)
+{
+ /* currently nothing to do, but likely to have future use */
+}
+
+/* Install vty's own commands like `who' command. */
+void vty_init(struct thread_master *master_thread, bool do_command_logging)
+{
+ /* For further configuration read, preserve current directory. */
+ vty_save_cwd();
+
+ vty_master = master_thread;
+
+ atexit(vty_stdio_atexit);
+
+ /* Install bgp top node. */
+ install_node(&vty_node);
+
+ install_element(VIEW_NODE, &config_who_cmd);
+ install_element(VIEW_NODE, &show_history_cmd);
+ install_element(CONFIG_NODE, &line_vty_cmd);
+ install_element(CONFIG_NODE, &service_advanced_vty_cmd);
+ install_element(CONFIG_NODE, &no_service_advanced_vty_cmd);
+ install_element(CONFIG_NODE, &show_history_cmd);
+ install_element(CONFIG_NODE, &log_commands_cmd);
+
+ if (do_command_logging) {
+ do_log_commands = true;
+ do_log_commands_perm = true;
+ }
+
+ install_element(ENABLE_NODE, &terminal_monitor_cmd);
+ install_element(ENABLE_NODE, &terminal_no_monitor_cmd);
+ install_element(ENABLE_NODE, &no_terminal_monitor_cmd);
+
+ install_default(VTY_NODE);
+ install_element(VTY_NODE, &exec_timeout_min_cmd);
+ install_element(VTY_NODE, &exec_timeout_sec_cmd);
+ install_element(VTY_NODE, &no_exec_timeout_cmd);
+ install_element(VTY_NODE, &vty_access_class_cmd);
+ install_element(VTY_NODE, &no_vty_access_class_cmd);
+ install_element(VTY_NODE, &vty_login_cmd);
+ install_element(VTY_NODE, &no_vty_login_cmd);
+ install_element(VTY_NODE, &vty_ipv6_access_class_cmd);
+ install_element(VTY_NODE, &no_vty_ipv6_access_class_cmd);
+}
+
+void vty_terminate(void)
+{
+ struct vty *vty;
+ struct vty_serv *vtyserv;
+
+ memset(vty_cwd, 0x00, sizeof(vty_cwd));
+
+ vty_reset();
+
+ /* default state of vty_sessions is initialized & empty. */
+ vtys_fini(vty_sessions);
+ vtys_init(vty_sessions);
+
+ /* vty_reset() doesn't close vtysh sessions */
+ frr_each_safe (vtys, vtysh_sessions, vty) {
+ buffer_reset(vty->lbuf);
+ buffer_reset(vty->obuf);
+ vty->status = VTY_CLOSE;
+ vty_close(vty);
+ }
+
+ vtys_fini(vtysh_sessions);
+ vtys_init(vtysh_sessions);
+
+ while ((vtyserv = vtyservs_pop(vty_servs))) {
+ THREAD_OFF(vtyserv->t_accept);
+ close(vtyserv->sock);
+ XFREE(MTYPE_VTY_SERV, vtyserv);
+ }
+
+ vtyservs_fini(vty_servs);
+ vtyservs_init(vty_servs);
+}
diff --git a/lib/vty.h b/lib/vty.h
new file mode 100644
index 0000000..0031ab6
--- /dev/null
+++ b/lib/vty.h
@@ -0,0 +1,387 @@
+/* Virtual terminal [aka TeletYpe] interface routine
+ * Copyright (C) 1997 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 _ZEBRA_VTY_H
+#define _ZEBRA_VTY_H
+
+#include <sys/types.h>
+#ifdef HAVE_LIBPCREPOSIX
+#include <pcreposix.h>
+#else
+#include <regex.h>
+#endif /* HAVE_LIBPCREPOSIX */
+
+#include "thread.h"
+#include "log.h"
+#include "sockunion.h"
+#include "qobj.h"
+#include "compiler.h"
+#include "northbound.h"
+#include "zlog_live.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct json_object;
+
+#define VTY_BUFSIZ 4096
+#define VTY_MAXHIST 20
+#define VTY_MAXDEPTH 8
+
+#define VTY_MAXCFGCHANGES 16
+
+struct vty_error {
+ char error_buf[VTY_BUFSIZ];
+ uint32_t line_num;
+};
+
+struct vty_cfg_change {
+ char xpath[XPATH_MAXLEN];
+ enum nb_operation operation;
+ const char *value;
+};
+
+PREDECL_DLIST(vtys);
+
+/* VTY struct. */
+struct vty {
+ struct vtys_item itm;
+
+ /* File descripter of this vty. */
+ int fd;
+
+ /* output FD, to support stdin/stdout combination */
+ int wfd;
+
+ /* File output, used for VTYSH only */
+ FILE *of;
+ FILE *of_saved;
+
+ /* whether we are using pager or not */
+ bool is_paged;
+
+ /* Is this vty connect to file or not */
+ enum { VTY_TERM, VTY_FILE, VTY_SHELL, VTY_SHELL_SERV } type;
+
+ /* Node status of this vty */
+ int node;
+
+ /* Failure count */
+ int fail;
+
+ /* Output filer regex */
+ bool filter;
+ regex_t include;
+
+ /* Line buffer */
+ struct buffer *lbuf;
+
+ /* Output buffer. */
+ struct buffer *obuf;
+
+ /* Command input buffer */
+ char *buf;
+
+ /* Command input error buffer */
+ struct list *error;
+
+ /* Command cursor point */
+ int cp;
+
+ /* Command length */
+ int length;
+
+ /* Command max length. */
+ int max;
+
+ /* Histry of command */
+ char *hist[VTY_MAXHIST];
+
+ /* History lookup current point */
+ int hp;
+
+ /* History insert end point */
+ int hindex;
+
+ /* Changes enqueued to be applied in the candidate configuration. */
+ size_t num_cfg_changes;
+ struct vty_cfg_change cfg_changes[VTY_MAXCFGCHANGES];
+
+ /* XPath of the current node */
+ int xpath_index;
+ char xpath[VTY_MAXDEPTH][XPATH_MAXLEN];
+
+ /* In configure mode. */
+ bool config;
+
+ /* Private candidate configuration mode. */
+ bool private_config;
+
+ /* Candidate configuration. */
+ struct nb_config *candidate_config;
+
+ /* Base candidate configuration. */
+ struct nb_config *candidate_config_base;
+
+ /* Dynamic transaction information. */
+ bool pending_allowed;
+ bool pending_commit;
+ char *pending_cmds_buf;
+ size_t pending_cmds_buflen;
+ size_t pending_cmds_bufpos;
+
+ /* Confirmed-commit timeout and rollback configuration. */
+ struct thread *t_confirmed_commit_timeout;
+ struct nb_config *confirmed_commit_rollback;
+
+ /* qobj object ID (replacement for "index") */
+ uint64_t qobj_index;
+
+ /* qobj second-level object ID (replacement for "index_sub") */
+ uint64_t qobj_index_sub;
+
+ /* For escape character. */
+ unsigned char escape;
+
+ /* Current vty status. */
+ enum {
+ VTY_NORMAL,
+ VTY_CLOSE,
+ VTY_MORE,
+ VTY_MORELINE,
+ VTY_PASSFD,
+ } status;
+
+ /* vtysh socket/fd passing (for terminal monitor) */
+ int pass_fd;
+
+ /* CLI command return value (likely CMD_SUCCESS) when pass_fd != -1 */
+ uint8_t pass_fd_status[4];
+
+ /* live logging target / terminal monitor */
+ struct zlog_live_cfg live_log;
+
+ /* IAC handling: was the last character received the
+ IAC (interpret-as-command) escape character (and therefore the next
+ character will be the command code)? Refer to Telnet RFC 854. */
+ unsigned char iac;
+
+ /* IAC SB (option subnegotiation) handling */
+ unsigned char iac_sb_in_progress;
+/* At the moment, we care only about the NAWS (window size) negotiation,
+ and that requires just a 5-character buffer (RFC 1073):
+ <NAWS char> <16-bit width> <16-bit height> */
+#define TELNET_NAWS_SB_LEN 5
+ unsigned char sb_buf[TELNET_NAWS_SB_LEN];
+ /* How many subnegotiation characters have we received? We just drop
+ those that do not fit in the buffer. */
+ size_t sb_len;
+
+ /* Window width/height. */
+ int width;
+ int height;
+
+ /* Configure lines. */
+ int lines;
+
+ /* Read and write thread. */
+ struct thread *t_read;
+ struct thread *t_write;
+
+ /* Timeout seconds and thread. */
+ unsigned long v_timeout;
+ struct thread *t_timeout;
+
+ /* What address is this vty comming from. */
+ char address[SU_ADDRSTRLEN];
+
+ /* "frame" output. This is buffered and will be printed if some
+ * actual output follows, or will be discarded if the frame ends
+ * without any output. */
+ size_t frame_pos;
+ char frame[1024];
+};
+
+static inline void vty_push_context(struct vty *vty, int node, uint64_t id)
+{
+ vty->node = node;
+ vty->qobj_index = id;
+}
+
+/* note: VTY_PUSH_CONTEXT(..., NULL) doesn't work, since it will try to
+ * dereference "NULL->qobj_node.nid" */
+#define VTY_PUSH_CONTEXT(nodeval, ptr) \
+ vty_push_context(vty, nodeval, QOBJ_ID_0SAFE(ptr))
+#define VTY_PUSH_CONTEXT_NULL(nodeval) vty_push_context(vty, nodeval, 0ULL)
+#define VTY_PUSH_CONTEXT_SUB(nodeval, ptr) \
+ do { \
+ vty->node = nodeval; \
+ /* qobj_index stays untouched */ \
+ vty->qobj_index_sub = QOBJ_ID_0SAFE(ptr); \
+ } while (0)
+
+/* can return NULL if context is invalid! */
+#define VTY_GET_CONTEXT(structname) \
+ QOBJ_GET_TYPESAFE(vty->qobj_index, structname)
+#define VTY_GET_CONTEXT_SUB(structname) \
+ QOBJ_GET_TYPESAFE(vty->qobj_index_sub, structname)
+
+/* will return if ptr is NULL. */
+#define VTY_CHECK_CONTEXT(ptr) \
+ if (!ptr) { \
+ vty_out(vty, \
+ "Current configuration object was deleted " \
+ "by another process.\n"); \
+ return CMD_WARNING; \
+ }
+
+/* struct structname *ptr = <context>; ptr will never be NULL. */
+#define VTY_DECLVAR_CONTEXT(structname, ptr) \
+ struct structname *ptr = VTY_GET_CONTEXT(structname); \
+ VTY_CHECK_CONTEXT(ptr);
+#define VTY_DECLVAR_CONTEXT_SUB(structname, ptr) \
+ struct structname *ptr = VTY_GET_CONTEXT_SUB(structname); \
+ VTY_CHECK_CONTEXT(ptr);
+#define VTY_DECLVAR_INSTANCE_CONTEXT(structname, ptr) \
+ if (vty->qobj_index == 0) \
+ return CMD_NOT_MY_INSTANCE; \
+ struct structname *ptr = VTY_GET_CONTEXT(structname); \
+ VTY_CHECK_CONTEXT(ptr);
+
+#define VTY_DECLVAR_CONTEXT_VRF(vrfptr) \
+ struct vrf *vrfptr; \
+ if (vty->node == CONFIG_NODE) \
+ vrfptr = vrf_lookup_by_id(VRF_DEFAULT); \
+ else \
+ vrfptr = VTY_GET_CONTEXT(vrf); \
+ VTY_CHECK_CONTEXT(vrfptr); \
+ MACRO_REQUIRE_SEMICOLON() /* end */
+
+/* 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->type != VTY_FILE && !vty->private_config \
+ && 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;
+ const char **argv;
+ int argc;
+};
+
+/* Integrated configuration file. */
+#define INTEGRATE_DEFAULT_CONFIG "frr.conf"
+
+/* Default time out value */
+#define VTY_TIMEOUT_DEFAULT 600
+
+/* Vty read buffer size. */
+#define VTY_READ_BUFSIZ 512
+
+/* Directory separator. */
+#ifndef DIRECTORY_SEP
+#define DIRECTORY_SEP '/'
+#endif /* DIRECTORY_SEP */
+
+#ifndef IS_DIRECTORY_SEP
+#define IS_DIRECTORY_SEP(c) ((c) == DIRECTORY_SEP)
+#endif
+
+/* Prototypes. */
+extern void vty_init(struct thread_master *, bool do_command_logging);
+extern void vty_init_vtysh(void);
+extern void vty_terminate(void);
+extern void vty_reset(void);
+extern struct vty *vty_new(void);
+extern struct vty *vty_stdio(void (*atclose)(int isexit));
+
+/* - vty_frame() output goes to a buffer (for context-begin markers)
+ * - vty_out() will first print this buffer, and clear it
+ * - vty_endframe() clears the buffer without printing it, and prints an
+ * extra string if the buffer was empty before (for context-end markers)
+ */
+extern int vty_out(struct vty *, const char *, ...) PRINTFRR(2, 3);
+extern void vty_frame(struct vty *, const char *, ...) PRINTFRR(2, 3);
+extern void vty_endframe(struct vty *, const char *);
+extern bool vty_set_include(struct vty *vty, const char *regexp);
+/* returns CMD_SUCCESS so you can do a one-line "return vty_json(...)"
+ * NULL check and json_object_free() is included.
+ *
+ * _no_pretty means do not add a bunch of newlines and dump the output
+ * as densely as possible.
+ */
+extern int vty_json(struct vty *vty, struct json_object *json);
+extern int vty_json_no_pretty(struct vty *vty, struct json_object *json);
+
+/* post fd to be passed to the vtysh client
+ * fd is owned by the VTY code after this and will be closed when done
+ */
+extern void vty_pass_fd(struct vty *vty, int fd);
+
+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 char *vty_get_cwd(void);
+extern void vty_update_xpath(const char *oldpath, const char *newpath);
+extern int vty_config_enter(struct vty *vty, bool private_config,
+ bool exclusive);
+extern void vty_config_exit(struct vty *);
+extern int vty_config_node_exit(struct vty *);
+extern int vty_shell(struct vty *);
+extern int vty_shell_serv(struct vty *);
+extern void vty_hello(struct vty *);
+
+/* ^Z / SIGTSTP handling */
+extern void vty_stdio_suspend(void);
+extern void vty_stdio_resume(void);
+extern void vty_stdio_close(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ZEBRA_VTY_H */
diff --git a/lib/vxlan.h b/lib/vxlan.h
new file mode 100644
index 0000000..220fd8d
--- /dev/null
+++ b/lib/vxlan.h
@@ -0,0 +1,58 @@
+/* VxLAN common header.
+ * Copyright (C) 2016, 2017 Cumulus Networks, Inc.
+ *
+ * This file is part of FRR.
+ *
+ * FRR 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, or (at your option) any
+ * later version.
+ *
+ * FRR 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 FRR; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef __VXLAN_H__
+#define __VXLAN_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* EVPN MH DF election algorithm */
+#define EVPN_MH_DF_ALG_SERVICE_CARVING 0
+#define EVPN_MH_DF_ALG_HRW 1
+#define EVPN_MH_DF_ALG_PREF 2
+
+/* preference range for DF election */
+#define EVPN_MH_DF_PREF_MIN 0
+#define EVPN_MH_DF_PREF_DEFAULT 32767
+#define EVPN_MH_DF_PREF_MAX 65535
+
+/* VxLAN Network Identifier - 24-bit (RFC 7348) */
+typedef uint32_t vni_t;
+#define VNI_MAX 16777215 /* (2^24 - 1) */
+
+/* Flooding mechanisms for BUM packets. */
+/* Currently supported mechanisms are head-end (ingress) replication
+ * (which is the default) and no flooding. Future options could be
+ * using PIM-SM, PIM-Bidir etc.
+ */
+enum vxlan_flood_control {
+ VXLAN_FLOOD_HEAD_END_REPL = 0,
+ VXLAN_FLOOD_DISABLED,
+ VXLAN_FLOOD_PIM_SM,
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __VXLAN_H__ */
diff --git a/lib/wheel.c b/lib/wheel.c
new file mode 100644
index 0000000..6e9c88d
--- /dev/null
+++ b/lib/wheel.c
@@ -0,0 +1,162 @@
+/*
+ * Timer Wheel
+ * Copyright (C) 2016 Cumulus Networks, Inc.
+ * Donald Sharp
+ *
+ * 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 "linklist.h"
+#include "thread.h"
+#include "memory.h"
+#include "wheel.h"
+#include "log.h"
+
+DEFINE_MTYPE_STATIC(LIB, TIMER_WHEEL, "Timer Wheel");
+DEFINE_MTYPE_STATIC(LIB, TIMER_WHEEL_LIST, "Timer Wheel Slot List");
+
+static int debug_timer_wheel = 0;
+
+static void wheel_timer_thread(struct thread *t);
+
+static void wheel_timer_thread_helper(struct thread *t)
+{
+ struct listnode *node, *nextnode;
+ unsigned long long curr_slot;
+ unsigned int slots_to_skip = 1;
+ struct timer_wheel *wheel;
+ void *data;
+
+ wheel = THREAD_ARG(t);
+
+ wheel->curr_slot += wheel->slots_to_skip;
+
+ curr_slot = wheel->curr_slot % wheel->slots;
+
+ if (debug_timer_wheel)
+ zlog_debug("%s: Wheel Slot: %lld(%lld) count: %d", __func__,
+ wheel->curr_slot, curr_slot,
+ listcount(wheel->wheel_slot_lists[curr_slot]));
+
+ for (ALL_LIST_ELEMENTS(wheel->wheel_slot_lists[curr_slot], node,
+ nextnode, data))
+ (*wheel->slot_run)(data);
+
+ while (list_isempty(wheel->wheel_slot_lists[(curr_slot + slots_to_skip)
+ % wheel->slots])
+ && (curr_slot + slots_to_skip) % wheel->slots != curr_slot)
+ slots_to_skip++;
+
+ wheel->slots_to_skip = slots_to_skip;
+ thread_add_timer_msec(wheel->master, wheel_timer_thread, wheel,
+ wheel->nexttime * slots_to_skip, &wheel->timer);
+}
+
+static void wheel_timer_thread(struct thread *t)
+{
+ struct timer_wheel *wheel;
+
+ wheel = THREAD_ARG(t);
+
+ thread_execute(wheel->master, wheel_timer_thread_helper, wheel, 0);
+}
+
+struct timer_wheel *wheel_init(struct thread_master *master, int period,
+ size_t slots, unsigned int (*slot_key)(const void *),
+ void (*slot_run)(void *),
+ const char *run_name)
+{
+ struct timer_wheel *wheel;
+ size_t i;
+
+ wheel = XCALLOC(MTYPE_TIMER_WHEEL, sizeof(struct timer_wheel));
+
+ wheel->name = XSTRDUP(MTYPE_TIMER_WHEEL, run_name);
+ wheel->slot_key = slot_key;
+ wheel->slot_run = slot_run;
+
+ wheel->period = period;
+ wheel->slots = slots;
+ wheel->curr_slot = 0;
+ wheel->master = master;
+ wheel->nexttime = period / slots;
+
+ wheel->wheel_slot_lists = XCALLOC(MTYPE_TIMER_WHEEL_LIST,
+ slots * sizeof(struct list *));
+ for (i = 0; i < slots; i++)
+ wheel->wheel_slot_lists[i] = list_new();
+
+ thread_add_timer_msec(wheel->master, wheel_timer_thread, wheel,
+ wheel->nexttime, &wheel->timer);
+
+ return wheel;
+}
+
+void wheel_delete(struct timer_wheel *wheel)
+{
+ int i;
+
+ for (i = 0; i < wheel->slots; i++) {
+ list_delete(&wheel->wheel_slot_lists[i]);
+ }
+
+ THREAD_OFF(wheel->timer);
+ XFREE(MTYPE_TIMER_WHEEL_LIST, wheel->wheel_slot_lists);
+ XFREE(MTYPE_TIMER_WHEEL, wheel->name);
+ XFREE(MTYPE_TIMER_WHEEL, wheel);
+}
+
+int wheel_stop(struct timer_wheel *wheel)
+{
+ THREAD_OFF(wheel->timer);
+ return 0;
+}
+
+int wheel_start(struct timer_wheel *wheel)
+{
+ if (!wheel->timer)
+ thread_add_timer_msec(wheel->master, wheel_timer_thread, wheel,
+ wheel->nexttime, &wheel->timer);
+
+ return 0;
+}
+
+int wheel_add_item(struct timer_wheel *wheel, void *item)
+{
+ long long slot;
+
+ slot = (*wheel->slot_key)(item);
+
+ if (debug_timer_wheel)
+ zlog_debug("%s: Inserting %p: %lld %lld", __func__, item, slot,
+ slot % wheel->slots);
+ listnode_add(wheel->wheel_slot_lists[slot % wheel->slots], item);
+
+ return 0;
+}
+
+int wheel_remove_item(struct timer_wheel *wheel, void *item)
+{
+ long long slot;
+
+ slot = (*wheel->slot_key)(item);
+
+ if (debug_timer_wheel)
+ zlog_debug("%s: Removing %p: %lld %lld", __func__, item, slot,
+ slot % wheel->slots);
+ listnode_delete(wheel->wheel_slot_lists[slot % wheel->slots], item);
+
+ return 0;
+}
diff --git a/lib/wheel.h b/lib/wheel.h
new file mode 100644
index 0000000..401789e
--- /dev/null
+++ b/lib/wheel.h
@@ -0,0 +1,126 @@
+/*
+ * Timer Wheel
+ * Copyright (C) 2016 Cumulus Networks, Inc.
+ * Donald Sharp
+ *
+ * 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 __WHEEL_H__
+#define __WHEEL_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct timer_wheel {
+ char *name;
+ struct thread_master *master;
+ int slots;
+ long long curr_slot;
+ unsigned int period;
+ unsigned int nexttime;
+ unsigned int slots_to_skip;
+
+ struct list **wheel_slot_lists;
+ struct thread *timer;
+ /*
+ * Key to determine what slot the item belongs in
+ */
+ unsigned int (*slot_key)(const void *);
+
+ void (*slot_run)(void *);
+};
+
+/*
+ * Creates a timer wheel
+ *
+ * master - Thread master structure for the process
+ * period - The Time in seconds that the timer wheel will
+ * take before it starts issuing commands again
+ * for items in each slot
+ * slots - The number of slots to have in this particular
+ * timer wheel
+ * slot_key - A hashing function of some sort that will allow
+ * the timer wheel to put items into individual slots
+ * slot_run - The function to run over each item in a particular slot
+ *
+ * Creates a timer wheel that will wake up 'slots' times over the entire
+ * wheel. Each time the timer wheel wakes up it will iterate through
+ * and run the slot_run function for each item stored in that particular
+ * slot.
+ *
+ * The timer code is 'intelligent' in that it notices if anything is
+ * in a particular slot and can schedule the next timer to skip
+ * the empty slot.
+ *
+ * The general purpose of a timer wheel is to reduce events in a system.
+ * A perfect example of usage for this is say hello packets that need
+ * to be sent out to all your neighbors. Suppose a large routing protocol
+ * has to send keepalive packets every Y seconds to each of it's peers.
+ * At scale we can have a very large number of peers, X.
+ * This means that we will have X timing events every Y seconds.
+ * If you replace these events with a timer wheel that has Z slots
+ * you will have at most Y/Z timer events if each slot has a work item
+ * in it.
+ *
+ * When X is large the number of events in a system can quickly escalate
+ * and cause significant amount of time handling thread events instead
+ * of running your code.
+ */
+struct timer_wheel *wheel_init(struct thread_master *master, int period,
+ size_t slots,
+ unsigned int (*slot_key)(const void *),
+ void (*slot_run)(void *), const char *run_name);
+
+/*
+ * Delete the specified timer wheel created
+ */
+void wheel_delete(struct timer_wheel *);
+
+/*
+ * Pause the Wheel from running
+ */
+int wheel_stop(struct timer_wheel *wheel);
+
+/*
+ * Start the wheel running again
+ */
+int wheel_start(struct timer_wheel *wheel);
+
+/*
+ * wheel - The Timer wheel being modified
+ * item - The generic data structure that will be handed
+ * to the slot_run function.
+ *
+ * Add item to a slot setup by the slot_key,
+ * possibly change next time pop.
+ */
+int wheel_add_item(struct timer_wheel *wheel, void *item);
+
+/*
+ * wheel - The Timer wheel being modified.
+ * item - The item to remove from one of the slots in
+ * the timer wheel.
+ *
+ * Remove a item to a slot setup by the slot_key,
+ * possibly change next time pop.
+ */
+int wheel_remove_item(struct timer_wheel *wheel, void *item);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/workqueue.c b/lib/workqueue.c
new file mode 100644
index 0000000..c703de9
--- /dev/null
+++ b/lib/workqueue.c
@@ -0,0 +1,384 @@
+/*
+ * Quagga Work Queue Support.
+ *
+ * Copyright (C) 2005 Sun Microsystems, Inc.
+ *
+ * This file is part of GNU Zebra.
+ *
+ * Quagga 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, or (at your option) any
+ * later version.
+ *
+ * Quagga 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 "thread.h"
+#include "memory.h"
+#include "workqueue.h"
+#include "linklist.h"
+#include "command.h"
+#include "log.h"
+
+DEFINE_MTYPE(LIB, WORK_QUEUE, "Work queue");
+DEFINE_MTYPE_STATIC(LIB, WORK_QUEUE_ITEM, "Work queue item");
+DEFINE_MTYPE_STATIC(LIB, WORK_QUEUE_NAME, "Work queue name string");
+
+/* master list of work_queues */
+static struct list _work_queues;
+/* pointer primarily to avoid an otherwise harmless warning on
+ * ALL_LIST_ELEMENTS_RO
+ */
+static struct list *work_queues = &_work_queues;
+
+#define WORK_QUEUE_MIN_GRANULARITY 1
+
+static struct work_queue_item *work_queue_item_new(struct work_queue *wq)
+{
+ struct work_queue_item *item;
+ assert(wq);
+
+ item = XCALLOC(MTYPE_WORK_QUEUE_ITEM, sizeof(struct work_queue_item));
+
+ return item;
+}
+
+static void work_queue_item_free(struct work_queue_item *item)
+{
+ XFREE(MTYPE_WORK_QUEUE_ITEM, item);
+ return;
+}
+
+static void work_queue_item_remove(struct work_queue *wq,
+ struct work_queue_item *item)
+{
+ assert(item && item->data);
+
+ /* call private data deletion callback if needed */
+ if (wq->spec.del_item_data)
+ wq->spec.del_item_data(wq, item->data);
+
+ work_queue_item_dequeue(wq, item);
+
+ work_queue_item_free(item);
+
+ return;
+}
+
+/* create new work queue */
+struct work_queue *work_queue_new(struct thread_master *m,
+ const char *queue_name)
+{
+ struct work_queue *new;
+
+ new = XCALLOC(MTYPE_WORK_QUEUE, sizeof(struct work_queue));
+
+ new->name = XSTRDUP(MTYPE_WORK_QUEUE_NAME, queue_name);
+ new->master = m;
+ SET_FLAG(new->flags, WQ_UNPLUGGED);
+
+ STAILQ_INIT(&new->items);
+
+ listnode_add(work_queues, new);
+
+ new->cycles.granularity = WORK_QUEUE_MIN_GRANULARITY;
+
+ /* Default values, can be overridden by caller */
+ new->spec.hold = WORK_QUEUE_DEFAULT_HOLD;
+ new->spec.yield = THREAD_YIELD_TIME_SLOT;
+ new->spec.retry = WORK_QUEUE_DEFAULT_RETRY;
+
+ return new;
+}
+
+void work_queue_free_and_null(struct work_queue **wqp)
+{
+ struct work_queue *wq = *wqp;
+
+ THREAD_OFF(wq->thread);
+
+ while (!work_queue_empty(wq)) {
+ struct work_queue_item *item = work_queue_last_item(wq);
+
+ work_queue_item_remove(wq, item);
+ }
+
+ listnode_delete(work_queues, wq);
+
+ XFREE(MTYPE_WORK_QUEUE_NAME, wq->name);
+ XFREE(MTYPE_WORK_QUEUE, wq);
+
+ *wqp = NULL;
+}
+
+bool work_queue_is_scheduled(struct work_queue *wq)
+{
+ return thread_is_scheduled(wq->thread);
+}
+
+static int work_queue_schedule(struct work_queue *wq, unsigned int delay)
+{
+ /* if appropriate, schedule work queue thread */
+ if (CHECK_FLAG(wq->flags, WQ_UNPLUGGED) &&
+ !thread_is_scheduled(wq->thread) && !work_queue_empty(wq)) {
+ /* Schedule timer if there's a delay, otherwise just schedule
+ * as an 'event'
+ */
+ if (delay > 0) {
+ thread_add_timer_msec(wq->master, work_queue_run, wq,
+ delay, &wq->thread);
+ thread_ignore_late_timer(wq->thread);
+ } else
+ thread_add_event(wq->master, work_queue_run, wq, 0,
+ &wq->thread);
+
+ /* set thread yield time, if needed */
+ if (thread_is_scheduled(wq->thread) &&
+ wq->spec.yield != THREAD_YIELD_TIME_SLOT)
+ thread_set_yield_time(wq->thread, wq->spec.yield);
+ return 1;
+ } else
+ return 0;
+}
+
+void work_queue_add(struct work_queue *wq, void *data)
+{
+ struct work_queue_item *item;
+
+ assert(wq);
+
+ item = work_queue_item_new(wq);
+
+ item->data = data;
+ work_queue_item_enqueue(wq, item);
+
+ work_queue_schedule(wq, wq->spec.hold);
+
+ return;
+}
+
+static void work_queue_item_requeue(struct work_queue *wq,
+ struct work_queue_item *item)
+{
+ work_queue_item_dequeue(wq, item);
+
+ /* attach to end of list */
+ work_queue_item_enqueue(wq, item);
+}
+
+DEFUN (show_work_queues,
+ show_work_queues_cmd,
+ "show work-queues",
+ SHOW_STR
+ "Work Queue information\n")
+{
+ struct listnode *node;
+ struct work_queue *wq;
+
+ vty_out(vty, "%c %8s %5s %8s %8s %21s\n", ' ', "List", "(ms) ",
+ "Q. Runs", "Yields", "Cycle Counts ");
+ vty_out(vty, "%c %8s %5s %8s %8s %7s %6s %8s %6s %s\n", 'P', "Items",
+ "Hold", "Total", "Total", "Best", "Gran.", "Total", "Avg.",
+ "Name");
+
+ for (ALL_LIST_ELEMENTS_RO(work_queues, node, wq)) {
+ vty_out(vty, "%c %8d %5d %8ld %8ld %7d %6d %8ld %6u %s\n",
+ (CHECK_FLAG(wq->flags, WQ_UNPLUGGED) ? ' ' : 'P'),
+ work_queue_item_count(wq), wq->spec.hold, wq->runs,
+ wq->yields, wq->cycles.best, wq->cycles.granularity,
+ wq->cycles.total,
+ (wq->runs) ? (unsigned int)(wq->cycles.total / wq->runs)
+ : 0,
+ wq->name);
+ }
+
+ return CMD_SUCCESS;
+}
+
+void workqueue_cmd_init(void)
+{
+ install_element(VIEW_NODE, &show_work_queues_cmd);
+}
+
+/* 'plug' a queue: Stop it from being scheduled,
+ * ie: prevent the queue from draining.
+ */
+void work_queue_plug(struct work_queue *wq)
+{
+ THREAD_OFF(wq->thread);
+
+ UNSET_FLAG(wq->flags, WQ_UNPLUGGED);
+}
+
+/* unplug queue, schedule it again, if appropriate
+ * Ie: Allow the queue to be drained again
+ */
+void work_queue_unplug(struct work_queue *wq)
+{
+ SET_FLAG(wq->flags, WQ_UNPLUGGED);
+
+ /* if thread isnt already waiting, add one */
+ work_queue_schedule(wq, wq->spec.hold);
+}
+
+/* timer thread to process a work queue
+ * will reschedule itself if required,
+ * otherwise work_queue_item_add
+ */
+void work_queue_run(struct thread *thread)
+{
+ struct work_queue *wq;
+ struct work_queue_item *item, *titem;
+ wq_item_status ret = WQ_SUCCESS;
+ unsigned int cycles = 0;
+ char yielded = 0;
+
+ wq = THREAD_ARG(thread);
+
+ assert(wq);
+
+ /* calculate cycle granularity:
+ * list iteration == 1 run
+ * listnode processing == 1 cycle
+ * granularity == # cycles between checks whether we should yield.
+ *
+ * granularity should be > 0, and can increase slowly after each run to
+ * provide some hysteris, but not past cycles.best or 2*cycles.
+ *
+ * Best: starts low, can only increase
+ *
+ * Granularity: starts at WORK_QUEUE_MIN_GRANULARITY, can be decreased
+ * if we run to end of time slot, can increase otherwise
+ * by a small factor.
+ *
+ * We could use just the average and save some work, however we want to
+ * be
+ * able to adjust quickly to CPU pressure. Average wont shift much if
+ * daemon has been running a long time.
+ */
+ if (wq->cycles.granularity == 0)
+ wq->cycles.granularity = WORK_QUEUE_MIN_GRANULARITY;
+
+ STAILQ_FOREACH_SAFE (item, &wq->items, wq, titem) {
+ assert(item->data);
+
+ /* dont run items which are past their allowed retries */
+ if (item->ran > wq->spec.max_retries) {
+ /* run error handler, if any */
+ if (wq->spec.errorfunc)
+ wq->spec.errorfunc(wq, item);
+ work_queue_item_remove(wq, item);
+ continue;
+ }
+
+ /* run and take care of items that want to be retried
+ * immediately */
+ do {
+ ret = wq->spec.workfunc(wq, item->data);
+ item->ran++;
+ } while ((ret == WQ_RETRY_NOW)
+ && (item->ran < wq->spec.max_retries));
+
+ switch (ret) {
+ case WQ_QUEUE_BLOCKED: {
+ /* decrement item->ran again, cause this isn't an item
+ * specific error, and fall through to WQ_RETRY_LATER
+ */
+ item->ran--;
+ }
+ case WQ_RETRY_LATER: {
+ goto stats;
+ }
+ case WQ_REQUEUE: {
+ item->ran--;
+ work_queue_item_requeue(wq, item);
+ /* If a single node is being used with a meta-queue
+ * (e.g., zebra),
+ * update the next node as we don't want to exit the
+ * thread and
+ * reschedule it after every node. By definition,
+ * WQ_REQUEUE is
+ * meant to continue the processing; the yield logic
+ * will kick in
+ * to terminate the thread when time has exceeded.
+ */
+ if (titem == NULL)
+ titem = item;
+ break;
+ }
+ case WQ_RETRY_NOW:
+ /* a RETRY_NOW that gets here has exceeded max_tries, same as
+ * ERROR */
+ case WQ_ERROR: {
+ if (wq->spec.errorfunc)
+ wq->spec.errorfunc(wq, item);
+ }
+ /* fallthru */
+ case WQ_SUCCESS:
+ default: {
+ work_queue_item_remove(wq, item);
+ break;
+ }
+ }
+
+ /* completed cycle */
+ cycles++;
+
+ /* test if we should yield */
+ if (!(cycles % wq->cycles.granularity)
+ && thread_should_yield(thread)) {
+ yielded = 1;
+ goto stats;
+ }
+ }
+
+stats:
+
+#define WQ_HYSTERESIS_FACTOR 4
+
+ /* we yielded, check whether granularity should be reduced */
+ if (yielded && (cycles < wq->cycles.granularity)) {
+ wq->cycles.granularity =
+ ((cycles > 0) ? cycles : WORK_QUEUE_MIN_GRANULARITY);
+ }
+ /* otherwise, should granularity increase? */
+ else if (cycles >= (wq->cycles.granularity)) {
+ if (cycles > wq->cycles.best)
+ wq->cycles.best = cycles;
+
+ /* along with yielded check, provides hysteresis for granularity
+ */
+ if (cycles > (wq->cycles.granularity * WQ_HYSTERESIS_FACTOR
+ * WQ_HYSTERESIS_FACTOR))
+ wq->cycles.granularity *=
+ WQ_HYSTERESIS_FACTOR; /* quick ramp-up */
+ else if (cycles
+ > (wq->cycles.granularity * WQ_HYSTERESIS_FACTOR))
+ wq->cycles.granularity += WQ_HYSTERESIS_FACTOR;
+ }
+#undef WQ_HYSTERIS_FACTOR
+
+ wq->runs++;
+ wq->cycles.total += cycles;
+ if (yielded)
+ wq->yields++;
+
+ /* Is the queue done yet? If it is, call the completion callback. */
+ if (!work_queue_empty(wq)) {
+ if (ret == WQ_RETRY_LATER ||
+ ret == WQ_QUEUE_BLOCKED)
+ work_queue_schedule(wq, wq->spec.retry);
+ else
+ work_queue_schedule(wq, 0);
+
+ } else if (wq->spec.completion_func)
+ wq->spec.completion_func(wq);
+}
diff --git a/lib/workqueue.h b/lib/workqueue.h
new file mode 100644
index 0000000..27fb138
--- /dev/null
+++ b/lib/workqueue.h
@@ -0,0 +1,189 @@
+/*
+ * Quagga Work Queues.
+ *
+ * Copyright (C) 2005 Sun Microsystems, Inc.
+ *
+ * This file is part of Quagga.
+ *
+ * Quagga 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, or (at your option) any
+ * later version.
+ *
+ * Quagga 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 _QUAGGA_WORK_QUEUE_H
+#define _QUAGGA_WORK_QUEUE_H
+
+#include "memory.h"
+#include "queue.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+DECLARE_MTYPE(WORK_QUEUE);
+
+/* Hold time for the initial schedule of a queue run, in millisec */
+#define WORK_QUEUE_DEFAULT_HOLD 50
+
+/* Retry for queue that is 'blocked' or 'retry later' */
+#define WORK_QUEUE_DEFAULT_RETRY 0
+
+/* action value, for use by item processor and item error handlers */
+typedef enum {
+ WQ_SUCCESS = 0,
+ WQ_ERROR, /* Error, run error handler if provided */
+ WQ_RETRY_NOW, /* retry immediately */
+ WQ_RETRY_LATER, /* retry later, cease processing work queue */
+ WQ_REQUEUE, /* requeue item, continue processing work queue */
+ WQ_QUEUE_BLOCKED, /* Queue cant be processed at this time.
+ * Similar to WQ_RETRY_LATER, but doesn't penalise
+ * the particular item.. */
+} wq_item_status;
+
+/* A single work queue item, unsurprisingly */
+struct work_queue_item {
+ STAILQ_ENTRY(work_queue_item) wq;
+ void *data; /* opaque data */
+ unsigned short ran; /* # of times item has been run */
+};
+
+#define WQ_UNPLUGGED (1 << 0) /* available for draining */
+
+struct work_queue {
+ /* Everything but the specification struct is private
+ * the following may be read
+ */
+ struct thread_master *master; /* thread master */
+ struct thread *thread; /* thread, if one is active */
+ char *name; /* work queue name */
+
+ /* Specification for this work queue.
+ * Public, must be set before use by caller. May be modified at will.
+ */
+ struct {
+ /* optional opaque user data, global to the queue. */
+ void *data;
+
+ /* work function to process items with:
+ * First argument is the workqueue queue.
+ * Second argument is the item data
+ */
+ wq_item_status (*workfunc)(struct work_queue *, void *);
+
+ /* error handling function, optional */
+ void (*errorfunc)(struct work_queue *,
+ struct work_queue_item *);
+
+ /* callback to delete user specific item data */
+ void (*del_item_data)(struct work_queue *, void *);
+
+ /* completion callback, called when queue is emptied, optional
+ */
+ void (*completion_func)(struct work_queue *);
+
+ /* max number of retries to make for item that errors */
+ unsigned int max_retries;
+
+ unsigned int hold; /* hold time for first run, in ms */
+
+ unsigned long
+ yield; /* yield time in us for associated thread */
+
+ uint32_t retry; /* Optional retry timeout if queue is blocked */
+ } spec;
+
+ /* remaining fields should be opaque to users */
+ STAILQ_HEAD(work_queue_items, work_queue_item)
+ items; /* queue item list */
+ int item_count; /* queued items */
+ unsigned long runs; /* runs count */
+ unsigned long yields; /* yields count */
+
+ struct {
+ unsigned int best;
+ unsigned int granularity;
+ unsigned long total;
+ } cycles; /* cycle counts */
+
+ /* private state */
+ uint16_t flags; /* user set flag */
+};
+
+/* User API */
+
+static inline int work_queue_item_count(struct work_queue *wq)
+{
+ return wq->item_count;
+}
+
+static inline bool work_queue_empty(struct work_queue *wq)
+{
+ return (wq->item_count == 0) ? true : false;
+}
+
+static inline struct work_queue_item *
+work_queue_last_item(struct work_queue *wq)
+{
+ return STAILQ_LAST(&wq->items, work_queue_item, wq);
+}
+
+static inline void work_queue_item_enqueue(struct work_queue *wq,
+ struct work_queue_item *item)
+{
+ STAILQ_INSERT_TAIL(&wq->items, item, wq);
+ wq->item_count++;
+}
+
+static inline void work_queue_item_dequeue(struct work_queue *wq,
+ struct work_queue_item *item)
+{
+ assert(wq->item_count > 0);
+
+ wq->item_count--;
+ STAILQ_REMOVE(&wq->items, item, work_queue_item, wq);
+}
+
+/* create a new work queue, of given name.
+ * user must fill in the spec of the returned work queue before adding
+ * anything to it
+ */
+extern struct work_queue *work_queue_new(struct thread_master *m,
+ const char *queue_name);
+
+/* destroy work queue */
+/*
+ * The usage of work_queue_free is being transitioned to pass
+ * in the double pointer to remove use after free's.
+ */
+extern void work_queue_free_and_null(struct work_queue **wqp);
+
+/* Add the supplied data as an item onto the workqueue */
+extern void work_queue_add(struct work_queue *wq, void *item);
+
+/* plug the queue, ie prevent it from being drained / processed */
+extern void work_queue_plug(struct work_queue *wq);
+/* unplug the queue, allow it to be drained again */
+extern void work_queue_unplug(struct work_queue *wq);
+
+bool work_queue_is_scheduled(struct work_queue *wq);
+
+/* Helpers, exported for thread.c and command.c */
+extern void work_queue_run(struct thread *thread);
+
+extern void workqueue_cmd_init(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QUAGGA_WORK_QUEUE_H */
diff --git a/lib/xref.c b/lib/xref.c
new file mode 100644
index 0000000..0d3549d
--- /dev/null
+++ b/lib/xref.c
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2017-20 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <pthread.h>
+#include <signal.h>
+#include <inttypes.h>
+
+#include "xref.h"
+#include "vty.h"
+#include "jhash.h"
+#include "sha256.h"
+#include "memory.h"
+#include "hash.h"
+
+struct xref_block *xref_blocks;
+static struct xref_block **xref_block_last = &xref_blocks;
+
+struct xrefdata_uid_head xrefdata_uid = INIT_RBTREE_UNIQ(xrefdata_uid);
+
+static void base32(uint8_t **inpos, int *bitpos,
+ char *out, size_t n_chars)
+{
+ static const char base32ch[] = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
+
+ char *opos = out;
+ uint8_t *in = *inpos;
+ int bp = *bitpos;
+
+ while (opos < out + n_chars) {
+ uint32_t bits = in[0] | (in[1] << 8);
+
+ if (bp == -1)
+ bits |= 0x10;
+ else
+ bits >>= bp;
+
+ *opos++ = base32ch[bits & 0x1f];
+
+ bp += 5;
+ if (bp >= 8)
+ in++, bp -= 8;
+ }
+ *opos = '\0';
+ *inpos = in;
+ *bitpos = bp;
+}
+
+static void xref_add_one(const struct xref *xref)
+{
+ SHA256_CTX sha;
+ struct xrefdata *xrefdata;
+
+ const char *filename, *p, *q;
+ uint8_t hash[32], *h = hash;
+ uint32_t be_val;
+ int bitpos;
+
+ if (!xref || !xref->xrefdata)
+ return;
+
+ xrefdata = xref->xrefdata;
+ xrefdata->xref = xref;
+
+ if (!xrefdata->hashstr)
+ return;
+
+ /* as far as the unique ID is concerned, only use the last
+ * directory name + filename, e.g. "bgpd/bgp_route.c". This
+ * gives a little leeway in moving things and avoids IDs being
+ * screwed up by out of tree builds or absolute pathnames.
+ */
+ filename = xref->file;
+ p = strrchr(filename, '/');
+ if (p) {
+ q = memrchr(filename, '/', p - filename);
+ if (q)
+ filename = q + 1;
+ }
+
+ SHA256_Init(&sha);
+ SHA256_Update(&sha, filename, strlen(filename));
+ SHA256_Update(&sha, xrefdata->hashstr,
+ strlen(xrefdata->hashstr));
+ be_val = htonl(xrefdata->hashu32[0]);
+ SHA256_Update(&sha, &be_val, sizeof(be_val));
+ be_val = htonl(xrefdata->hashu32[1]);
+ SHA256_Update(&sha, &be_val, sizeof(be_val));
+ SHA256_Final(hash, &sha);
+
+ bitpos = -1;
+ base32(&h, &bitpos, &xrefdata->uid[0], 5);
+ xrefdata->uid[5] = '-';
+ base32(&h, &bitpos, &xrefdata->uid[6], 5);
+
+ xrefdata_uid_add(&xrefdata_uid, xrefdata);
+}
+
+void xref_gcc_workaround(const struct xref *xref)
+{
+ xref_add_one(xref);
+}
+
+void xref_block_add(struct xref_block *block)
+{
+ const struct xref * const *xrefp;
+
+ *xref_block_last = block;
+ xref_block_last = &block->next;
+
+ for (xrefp = block->start; xrefp < block->stop; xrefp++)
+ xref_add_one(*xrefp);
+}
diff --git a/lib/xref.h b/lib/xref.h
new file mode 100644
index 0000000..37242bd
--- /dev/null
+++ b/lib/xref.h
@@ -0,0 +1,310 @@
+/*
+ * Copyright (c) 2017-20 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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_XREF_H
+#define _FRR_XREF_H
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <errno.h>
+#include "compiler.h"
+#include "typesafe.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum xref_type {
+ XREFT_NONE = 0,
+
+ XREFT_THREADSCHED = 0x100,
+
+ XREFT_LOGMSG = 0x200,
+ XREFT_ASSERT = 0x280,
+
+ XREFT_DEFUN = 0x300,
+ XREFT_INSTALL_ELEMENT = 0x301,
+};
+
+/* struct xref is the "const" part; struct xrefdata is the writable part. */
+struct xref;
+struct xrefdata;
+
+struct xref {
+ /* this may be NULL, depending on the type of the xref.
+ * if it is NULL, the xref has no unique ID and cannot be accessed
+ * through that mechanism.
+ */
+ struct xrefdata *xrefdata;
+
+ /* type isn't generally needed at runtime */
+ enum xref_type type;
+
+ /* code location */
+ int line;
+ const char *file;
+ const char *func;
+
+ /* -- 32 bytes (on 64bit) -- */
+
+ /* type-specific bits appended by embedding this struct */
+};
+
+PREDECL_RBTREE_UNIQ(xrefdata_uid);
+
+struct xrefdata {
+ /* pointer back to the const part; this will be initialized at
+ * program startup by xref_block_add(). (Creating structs with
+ * cyclic pointers to each other is not easily possible for
+ * function-scoped static variables.)
+ *
+ * There is no xrefdata w/o xref, but there are xref w/o xrefdata.
+ */
+ const struct xref *xref;
+
+ /* base32(crockford) of unique ID. not all bytes are used, but
+ * let's pad to 16 for simplicity
+ */
+ char uid[16];
+
+ /* hash/uid input
+ * if hashstr is NULL, no UID is assigned/calculated. Use macro
+ * string concatenation if multiple values need to be fed in.
+ * (This is here to not make the UID calculation independent of
+ * xref type.)
+ */
+ const char *hashstr;
+ uint32_t hashu32[2];
+
+ /* -- 32 bytes (on 64bit) -- */
+ struct xrefdata_uid_item xui;
+};
+
+static inline int xrefdata_uid_cmp(const struct xrefdata *a,
+ const struct xrefdata *b)
+{
+ return strcmp(a->uid, b->uid);
+}
+
+DECLARE_RBTREE_UNIQ(xrefdata_uid, struct xrefdata, xui, xrefdata_uid_cmp);
+extern struct xrefdata_uid_head xrefdata_uid;
+
+/* linker "magic" is used to create an array of pointers to struct xref.
+ * the result is a contiguous block of pointers, each pointing to an xref
+ * somewhere in the code. The linker gives us start and end pointers, we
+ * stuff those into the struct below and hook up a constructor to run at
+ * program startup with the struct passed.
+ *
+ * Placing the xrefs themselves into an array doesn't work because they'd
+ * need to be constant size, but we're embedding struct xref into other
+ * container structs with extra data. Also this means that external code
+ * (like the python xref dumper) can safely ignore extra data at the end of
+ * xrefs without needing to account for size in iterating the array.
+ *
+ * If you're curious, this is also how __attribute__((constructor)) (and
+ * destructor) are implemented - there are 2 arrays, ".init_array" and
+ * ".fini_array", containing function pointers. The magic turns out to be
+ * quite mundane, actually ;)
+ *
+ * The slightly tricky bit is that this is a per-object (i.e. per shared
+ * library & daemon) thing and we need a bit of help (in XREF_SETUP) to
+ * initialize correctly.
+ */
+
+struct xref_block {
+ struct xref_block *next;
+ const struct xref * const *start;
+ const struct xref * const *stop;
+};
+
+extern struct xref_block *xref_blocks;
+extern void xref_block_add(struct xref_block *block);
+extern void xref_gcc_workaround(const struct xref *xref);
+
+#ifndef HAVE_SECTION_SYMS
+/* we have a build system patch to use GNU ld on Solaris; if that doesn't
+ * work we end up on Solaris ld which doesn't support the section start/end
+ * symbols.
+ */
+#define XREF_SETUP() \
+ CPP_NOTICE("Missing linker support for section arrays. Solaris ld?")
+#else
+/* the actual symbols that the linker provides for us. Note these are
+ * _symbols_ referring to the actual section start/end, i.e. they are very
+ * much NOT _pointers_, rather the symbol *value* is the pointer. Declaring
+ * them as size-1 arrays is the "best" / "right" thing.
+ */
+extern const struct xref * const __start_xref_array[1] DSO_LOCAL;
+extern const struct xref * const __stop_xref_array[1] DSO_LOCAL;
+
+#if defined(__has_feature)
+#if __has_feature(address_sanitizer)
+/* no redzone around each of the xref_p please, we're building an array out
+ * of variables here. kinda breaks things if there's redzones between each
+ * array item.
+ */
+#define xref_array_attr used, section("xref_array"), no_sanitize("address")
+#endif
+#endif
+#ifndef xref_array_attr
+#define xref_array_attr used, section("xref_array")
+#endif
+
+/* this macro is invoked once for each standalone DSO through
+ * FRR_MODULE_SETUP \
+ * }-> FRR_COREMOD_SETUP -> XREF_SETUP
+ * FRR_DAEMON_INFO /
+ */
+#define XREF_SETUP() \
+ static const struct xref _dummy_xref = { \
+ /* .xrefdata = */ NULL, \
+ /* .type = */ XREFT_NONE, \
+ /* .line = */ __LINE__, \
+ /* .file = */ __FILE__, \
+ /* .func = */ "dummy", \
+ }; \
+ static const struct xref * const _dummy_xref_p \
+ __attribute__((xref_array_attr)) = &_dummy_xref; \
+ static void __attribute__((used, _CONSTRUCTOR(1100))) \
+ _xref_init(void) { \
+ static struct xref_block _xref_block = { \
+ .next = NULL, \
+ .start = __start_xref_array, \
+ .stop = __stop_xref_array, \
+ }; \
+ xref_block_add(&_xref_block); \
+ } \
+ asm(XREF_NOTE); \
+ MACRO_REQUIRE_SEMICOLON() /* end */
+
+/* the following blurb emits an ELF note indicating start and end of the xref
+ * array in the binary. This is technically the "correct" entry point for
+ * external tools reading xrefs out of an ELF shared library or executable.
+ *
+ * right now, the extraction tools use the section header for "xref_array"
+ * instead; however, section headers are technically not necessarily preserved
+ * for fully linked libraries or executables. (In practice they are only
+ * stripped by obfuscation tools.)
+ *
+ * conversely, for reading xrefs out of a single relocatable object file (e.g.
+ * bar.o), section headers are the right thing to look at since the note is
+ * only emitted for the final binary once.
+ *
+ * FRR itself does not need this note to operate correctly, so if you have
+ * some build issue with it just add -DFRR_XREF_NO_NOTE to your build flags
+ * to disable it.
+ */
+#if defined(FRR_XREF_NO_NOTE) || defined(__mips64)
+#define XREF_NOTE ""
+
+/* mips64 note: MIPS64 (regardless of endianness, both mips64 & mips64el)
+ * does not have a 64-bit PC-relative relocation type. Unfortunately, a
+ * 64-bit PC-relative relocation is exactly what the below asm magic emits.
+ * Therefore, the xref ELF note is permanently disabled on MIPS64.
+ *
+ * For some context, refer to https://reviews.llvm.org/D80390
+ *
+ * As noted above, xref extraction still works through the section header
+ * path, so no functionality is lost.
+ */
+#else
+
+#if __SIZEOF_POINTER__ == 4
+#define _NOTE_2PTRSIZE "8"
+#define _NOTE_PTR ".long"
+#elif __SIZEOF_POINTER__ == 8
+#define _NOTE_2PTRSIZE "16"
+#define _NOTE_PTR ".quad"
+#else
+#error unsupported pointer size
+#endif
+
+#ifdef __arm__
+# define asmspecial "%"
+#else
+# define asmspecial "@"
+#endif
+
+#define XREF_NOTE \
+ "" "\n"\
+ " .type _frr_xref_note," asmspecial "object" "\n"\
+ " .pushsection .note.FRR,\"a\"," asmspecial "note" "\n"\
+ " .p2align 2" "\n"\
+ "_frr_xref_note:" "\n"\
+ " .long 9" "\n"\
+ " .long " _NOTE_2PTRSIZE "\n"\
+ " .ascii \"XREF\"" "\n"\
+ " .ascii \"FRRouting\\0\\0\\0\"" "\n"\
+ " " _NOTE_PTR " __start_xref_array-." "\n"\
+ " " _NOTE_PTR " __stop_xref_array-." "\n"\
+ " .size _frr_xref_note, .-_frr_xref_note" "\n"\
+ " .popsection" "\n"\
+ "" "\n"\
+ /* end */
+#endif
+
+#endif /* HAVE_SECTION_SYMS */
+
+/* emit the array entry / pointer to xref */
+#if defined(__clang__) || !defined(__cplusplus)
+#define XREF_LINK(dst) \
+ static const struct xref * const NAMECTR(xref_p_) \
+ __attribute__((xref_array_attr)) \
+ = &(dst) \
+ /* end */
+
+#else /* GCC && C++ */
+/* workaround for GCC bug 41091 (dated 2009), added in 2021...
+ *
+ * this breaks extraction of xrefs with xrelfo.py (because the xref_array
+ * entry will be missing), but provides full runtime functionality. To get
+ * the proper list of xrefs from C++ code, build with clang...
+ */
+struct _xref_p {
+ const struct xref * const ptr;
+
+ _xref_p(const struct xref *_ptr) : ptr(_ptr)
+ {
+ xref_gcc_workaround(_ptr);
+ }
+};
+
+#define XREF_LINK(dst) \
+ static const struct _xref_p __attribute__((used)) \
+ NAMECTR(xref_p_)(&(dst)) \
+ /* end */
+#endif
+
+/* initializer for a "struct xref" */
+#define XREF_INIT(type_, xrefdata_, func_) \
+ { \
+ /* .xrefdata = */ (xrefdata_), \
+ /* .type = */ (type_), \
+ /* .line = */ __LINE__, \
+ /* .file = */ __FILE__, \
+ /* .func = */ func_, \
+ } \
+ /* end */
+
+/* use with XREF_INIT when outside of a function, i.e. no __func__ */
+#define XREF_NO_FUNC "<global>"
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRR_XREF_H */
diff --git a/lib/yang.c b/lib/yang.c
new file mode 100644
index 0000000..ef1cf89
--- /dev/null
+++ b/lib/yang.c
@@ -0,0 +1,919 @@
+/*
+ * 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 "yang.h"
+#include "yang_translator.h"
+#include "northbound.h"
+
+DEFINE_MTYPE_STATIC(LIB, YANG_MODULE, "YANG module");
+DEFINE_MTYPE_STATIC(LIB, YANG_DATA, "YANG data structure");
+
+/* libyang container. */
+struct ly_ctx *ly_native_ctx;
+
+static struct yang_module_embed *embeds, **embedupd = &embeds;
+
+void yang_module_embed(struct yang_module_embed *embed)
+{
+ embed->next = NULL;
+ *embedupd = embed;
+ embedupd = &embed->next;
+}
+
+static LY_ERR yang_module_imp_clb(const char *mod_name, const char *mod_rev,
+ const char *submod_name,
+ const char *submod_rev, void *user_data,
+ LYS_INFORMAT *format,
+ const char **module_data,
+ void (**free_module_data)(void *, void *))
+{
+ struct yang_module_embed *e;
+
+ if (!strcmp(mod_name, "ietf-inet-types") ||
+ !strcmp(mod_name, "ietf-yang-types"))
+ /* libyang has these built in, don't try finding them here */
+ return LY_ENOTFOUND;
+
+ for (e = embeds; e; e = e->next) {
+ if (e->sub_mod_name && submod_name) {
+ if (strcmp(e->sub_mod_name, submod_name))
+ continue;
+
+ if (submod_rev && strcmp(e->sub_mod_rev, submod_rev))
+ continue;
+ } else {
+ if (strcmp(e->mod_name, mod_name))
+ continue;
+
+ if (mod_rev && strcmp(e->mod_rev, mod_rev))
+ continue;
+ }
+
+ *format = e->format;
+ *module_data = e->data;
+ return LY_SUCCESS;
+ }
+
+ /* We get here for indirect modules like ietf-inet-types */
+ zlog_debug(
+ "YANG model \"%s@%s\" \"%s@%s\"not embedded, trying external file",
+ mod_name, mod_rev ? mod_rev : "*",
+ submod_name ? submod_name : "*", submod_rev ? submod_rev : "*");
+
+ return LY_ENOTFOUND;
+}
+
+/* clang-format off */
+static const char *const frr_native_modules[] = {
+ "frr-interface",
+ "frr-vrf",
+ "frr-routing",
+ "frr-route-map",
+ "frr-nexthop",
+ "frr-ripd",
+ "frr-ripngd",
+ "frr-isisd",
+ "frr-vrrpd",
+ "frr-zebra",
+ "frr-pathd",
+};
+/* clang-format on */
+
+/* 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, 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;
+}
+
+void yang_module_load_all(void)
+{
+ for (size_t i = 0; i < array_size(frr_native_modules); i++)
+ yang_module_load(frr_native_modules[i]);
+}
+
+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);
+}
+
+int yang_snodes_iterate_subtree(const struct lysc_node *snode,
+ const struct lys_module *module,
+ yang_iterate_cb cb, uint16_t flags, void *arg)
+{
+ const struct lysc_node *child;
+ int ret = YANG_ITER_CONTINUE;
+
+ if (module && snode->module != module)
+ goto next;
+
+ switch (snode->nodetype) {
+ case LYS_CONTAINER:
+ if (CHECK_FLAG(flags, YANG_ITER_FILTER_NPCONTAINERS)) {
+ if (!CHECK_FLAG(snode->flags, LYS_PRESENCE))
+ goto next;
+ }
+ break;
+ case LYS_LEAF:
+ if (CHECK_FLAG(flags, YANG_ITER_FILTER_LIST_KEYS)) {
+ /* Ignore list keys. */
+ if (lysc_is_key(snode))
+ goto next;
+ }
+ break;
+ case LYS_INPUT:
+ case LYS_OUTPUT:
+ if (CHECK_FLAG(flags, YANG_ITER_FILTER_INPUT_OUTPUT))
+ goto next;
+ break;
+ default:
+ assert(snode->nodetype != LYS_AUGMENT
+ && snode->nodetype != LYS_GROUPING
+ && snode->nodetype != LYS_USES);
+ break;
+ }
+
+ ret = (*cb)(snode, arg);
+ if (ret == YANG_ITER_STOP)
+ return ret;
+
+next:
+ /*
+ * YANG leafs and leaf-lists can't have child nodes.
+ */
+ if (CHECK_FLAG(snode->nodetype, LYS_LEAF | LYS_LEAFLIST))
+ return YANG_ITER_CONTINUE;
+
+ LY_LIST_FOR (lysc_node_child(snode), child) {
+ ret = yang_snodes_iterate_subtree(child, module, cb, flags,
+ arg);
+ if (ret == YANG_ITER_STOP)
+ return ret;
+ }
+ return ret;
+}
+
+int yang_snodes_iterate(const struct lys_module *module, yang_iterate_cb cb,
+ uint16_t flags, void *arg)
+{
+ const struct lys_module *module_iter;
+ uint32_t idx = 0;
+ int ret = YANG_ITER_CONTINUE;
+
+ idx = ly_ctx_internal_modules_count(ly_native_ctx);
+ while ((module_iter = ly_ctx_get_module_iter(ly_native_ctx, &idx))) {
+ struct lysc_node *snode;
+
+ if (!module_iter->implemented)
+ continue;
+
+ LY_LIST_FOR (module_iter->compiled->data, snode) {
+ ret = yang_snodes_iterate_subtree(snode, module, cb,
+ flags, arg);
+ if (ret == YANG_ITER_STOP)
+ return ret;
+ }
+ LY_LIST_FOR (&module_iter->compiled->rpcs->node, snode) {
+ ret = yang_snodes_iterate_subtree(snode, module, cb,
+ flags, arg);
+ if (ret == YANG_ITER_STOP)
+ return ret;
+ }
+ LY_LIST_FOR (&module_iter->compiled->notifs->node, snode) {
+ ret = yang_snodes_iterate_subtree(snode, module, cb,
+ flags, arg);
+ if (ret == YANG_ITER_STOP)
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+void yang_snode_get_path(const struct lysc_node *snode,
+ enum yang_path_type type, char *xpath,
+ size_t xpath_len)
+{
+ switch (type) {
+ case YANG_PATH_SCHEMA:
+ (void)lysc_path(snode, LYSC_PATH_LOG, xpath, xpath_len);
+ break;
+ case YANG_PATH_DATA:
+ (void)lysc_path(snode, LYSC_PATH_DATA, xpath, xpath_len);
+ break;
+ default:
+ flog_err(EC_LIB_DEVELOPMENT, "%s: unknown yang path type: %u",
+ __func__, type);
+ exit(1);
+ }
+}
+
+struct lysc_node *yang_snode_real_parent(const struct lysc_node *snode)
+{
+ struct lysc_node *parent = snode->parent;
+
+ while (parent) {
+ switch (parent->nodetype) {
+ case LYS_CONTAINER:
+ if (CHECK_FLAG(parent->flags, LYS_PRESENCE))
+ return parent;
+ break;
+ case LYS_LIST:
+ return parent;
+ default:
+ break;
+ }
+ parent = parent->parent;
+ }
+
+ return NULL;
+}
+
+struct lysc_node *yang_snode_parent_list(const struct lysc_node *snode)
+{
+ struct lysc_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 lysc_node *snode)
+{
+ const struct lysc_node_leaf *sleaf;
+
+ switch (snode->nodetype) {
+ case LYS_LEAF:
+ sleaf = (struct lysc_node_leaf *)snode;
+ if (sleaf->type->basetype == LY_TYPE_EMPTY)
+ return true;
+ return false;
+ case LYS_LEAFLIST:
+ return false;
+ default:
+ return true;
+ }
+}
+
+const char *yang_snode_get_default(const struct lysc_node *snode)
+{
+ const struct lysc_node_leaf *sleaf;
+
+ switch (snode->nodetype) {
+ case LYS_LEAF:
+ sleaf = (const struct lysc_node_leaf *)snode;
+ return sleaf->dflt ? lyd_value_get_canonical(sleaf->module->ctx,
+ sleaf->dflt)
+ : NULL;
+ case LYS_LEAFLIST:
+ /* TODO: check leaf-list default values */
+ return NULL;
+ default:
+ return NULL;
+ }
+}
+
+const struct lysc_type *yang_snode_get_type(const struct lysc_node *snode)
+{
+ struct lysc_node_leaf *sleaf = (struct lysc_node_leaf *)snode;
+ struct lysc_type *type;
+
+ if (!CHECK_FLAG(sleaf->nodetype, LYS_LEAF | LYS_LEAFLIST))
+ return NULL;
+
+ type = sleaf->type;
+ while (type->basetype == LY_TYPE_LEAFREF)
+ type = ((struct lysc_type_leafref *)type)->realtype;
+
+ return type;
+}
+
+unsigned int yang_snode_num_keys(const struct lysc_node *snode)
+{
+ const struct lysc_node_leaf *skey;
+ uint count = 0;
+
+ if (!CHECK_FLAG(snode->nodetype, LYS_LIST))
+ return 0;
+
+ /* Walk list of children */
+ LY_FOR_KEYS (snode, skey) {
+ count++;
+ }
+ return count;
+}
+
+void yang_dnode_get_path(const struct lyd_node *dnode, char *xpath,
+ size_t xpath_len)
+{
+ lyd_path(dnode, LYD_PATH_STD, xpath, xpath_len);
+}
+
+const char *yang_dnode_get_schema_name(const struct lyd_node *dnode,
+ const char *xpath_fmt, ...)
+{
+ 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);
+ if (!dnode) {
+ flog_err(EC_LIB_YANG_DNODE_NOT_FOUND,
+ "%s: couldn't find %s", __func__, xpath);
+ zlog_backtrace(LOG_ERR);
+ abort();
+ }
+ }
+
+ return dnode->schema->name;
+}
+
+struct lyd_node *yang_dnode_get(const struct lyd_node *dnode, const char *xpath)
+{
+ struct ly_set *set = NULL;
+ struct lyd_node *dnode_ret = NULL;
+
+ /*
+ * XXX a lot of the code uses this for style I guess. It shouldn't, as
+ * it adds to the xpath parsing complexity in libyang.
+ */
+ if (xpath[0] == '.' && xpath[1] == '/')
+ xpath += 2;
+
+ if (lyd_find_xpath(dnode, xpath, &set)) {
+ assert(0); /* XXX replicates old libyang1 base code */
+ goto exit;
+ }
+ if (set->count == 0)
+ goto exit;
+
+ if (set->count > 1) {
+ flog_warn(EC_LIB_YANG_DNODE_NOT_FOUND,
+ "%s: found %u elements (expected 0 or 1) [xpath %s]",
+ __func__, set->count, xpath);
+ goto exit;
+ }
+
+ dnode_ret = set->dnodes[0];
+
+exit:
+ ly_set_free(set, NULL);
+
+ return dnode_ret;
+}
+
+struct lyd_node *yang_dnode_getf(const struct lyd_node *dnode,
+ const char *xpath_fmt, ...)
+{
+ va_list ap;
+ char xpath[XPATH_MAXLEN];
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+
+ return yang_dnode_get(dnode, xpath);
+}
+
+bool yang_dnode_exists(const struct lyd_node *dnode, const char *xpath)
+{
+ struct ly_set *set = NULL;
+ bool exists = false;
+
+ if (xpath[0] == '.' && xpath[1] == '/')
+ xpath += 2;
+ if (lyd_find_xpath(dnode, xpath, &set))
+ return false;
+ exists = set->count > 0;
+ ly_set_free(set, NULL);
+ return exists;
+}
+
+bool yang_dnode_existsf(const struct lyd_node *dnode, const char *xpath_fmt,
+ ...)
+{
+ va_list ap;
+ char xpath[XPATH_MAXLEN];
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+
+ return yang_dnode_exists(dnode, xpath);
+}
+
+void yang_dnode_iterate(yang_dnode_iter_cb cb, void *arg,
+ const struct lyd_node *dnode, const char *xpath_fmt,
+ ...)
+{
+ va_list ap;
+ char xpath[XPATH_MAXLEN];
+ struct ly_set *set;
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+
+ if (lyd_find_xpath(dnode, xpath, &set)) {
+ assert(0); /* XXX libyang2: ly1 code asserted success */
+ return;
+ }
+ for (unsigned int i = 0; i < set->count; i++) {
+ int ret;
+
+ ret = (*cb)(set->dnodes[i], arg);
+ if (ret == YANG_ITER_STOP)
+ break;
+ }
+
+ ly_set_free(set, NULL);
+}
+
+bool yang_dnode_is_default(const struct lyd_node *dnode, const char *xpath)
+{
+ const struct lysc_node *snode;
+ struct lysc_node_leaf *sleaf;
+
+ if (xpath)
+ dnode = yang_dnode_get(dnode, xpath);
+
+ assert(dnode);
+ snode = dnode->schema;
+ switch (snode->nodetype) {
+ case LYS_LEAF:
+ sleaf = (struct lysc_node_leaf *)snode;
+ if (sleaf->type->basetype == LY_TYPE_EMPTY)
+ return false;
+ return lyd_is_default(dnode);
+ case LYS_LEAFLIST:
+ /* TODO: check leaf-list default values */
+ return false;
+ case LYS_CONTAINER:
+ if (CHECK_FLAG(snode->flags, LYS_PRESENCE))
+ return false;
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool yang_dnode_is_defaultf(const struct lyd_node *dnode, const char *xpath_fmt,
+ ...)
+{
+ if (!xpath_fmt)
+ return yang_dnode_is_default(dnode, NULL);
+ else {
+ va_list ap;
+ char xpath[XPATH_MAXLEN];
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+
+ return yang_dnode_is_default(dnode, xpath);
+ }
+}
+
+bool yang_dnode_is_default_recursive(const struct lyd_node *dnode)
+{
+ struct lyd_node *root, *dnode_iter;
+
+ if (!yang_dnode_is_default(dnode, NULL))
+ return false;
+
+ if (CHECK_FLAG(dnode->schema->nodetype, LYS_LEAF | LYS_LEAFLIST))
+ return true;
+
+ LY_LIST_FOR (lyd_child(dnode), root) {
+ LYD_TREE_DFS_BEGIN (root, dnode_iter) {
+ if (!yang_dnode_is_default(dnode_iter, NULL))
+ return false;
+
+ LYD_TREE_DFS_END(root, dnode_iter);
+ }
+ }
+
+ return true;
+}
+
+void yang_dnode_change_leaf(struct lyd_node *dnode, const char *value)
+{
+ assert(dnode->schema->nodetype == LYS_LEAF);
+ lyd_change_term(dnode, value);
+}
+
+struct lyd_node *yang_dnode_new(struct ly_ctx *ly_ctx, bool config_only)
+{
+ struct lyd_node *dnode = NULL;
+ int options = config_only ? LYD_VALIDATE_NO_STATE : 0;
+
+ if (lyd_validate_all(&dnode, ly_ctx, options, NULL) != 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)
+{
+ struct lyd_node *dup = NULL;
+ LY_ERR err;
+ err = lyd_dup_siblings(dnode, NULL, LYD_DUP_RECURSIVE, &dup);
+ assert(!err);
+ return dup;
+}
+
+void yang_dnode_free(struct lyd_node *dnode)
+{
+ while (dnode->parent)
+ dnode = lyd_parent(dnode);
+ lyd_free_all(dnode);
+}
+
+struct yang_data *yang_data_new(const char *xpath, const char *value)
+{
+ struct yang_data *data;
+
+ data = XCALLOC(MTYPE_YANG_DATA, sizeof(*data));
+ strlcpy(data->xpath, xpath, sizeof(data->xpath));
+ 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;
+}
+
+struct yang_data *yang_data_list_find(const struct list *list,
+ const char *xpath_fmt, ...)
+{
+ char xpath[XPATH_MAXLEN];
+ struct yang_data *data;
+ struct listnode *node;
+ va_list ap;
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+
+ for (ALL_LIST_ELEMENTS_RO(list, node, data))
+ if (strmatch(data->xpath, xpath))
+ return data;
+
+ return NULL;
+}
+
+/* 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 = LOG_ERR;
+
+ switch (level) {
+ case LY_LLERR:
+ priority = LOG_ERR;
+ break;
+ case LY_LLWRN:
+ priority = LOG_WARNING;
+ break;
+ case LY_LLVRB:
+ case LY_LLDBG:
+ priority = LOG_DEBUG;
+ break;
+ }
+
+ if (path)
+ zlog(priority, "libyang: %s (%s)", msg, path);
+ else
+ zlog(priority, "libyang: %s", msg);
+}
+
+const char *yang_print_errors(struct ly_ctx *ly_ctx, char *buf, size_t buf_len)
+{
+ struct ly_err_item *ei;
+ const char *path;
+
+ ei = ly_err_first(ly_ctx);
+ if (!ei)
+ return "";
+
+ strlcpy(buf, "YANG error(s):\n", buf_len);
+ for (; ei; ei = ei->next) {
+ strlcat(buf, " ", buf_len);
+ strlcat(buf, ei->msg, buf_len);
+ strlcat(buf, "\n", buf_len);
+ }
+
+ path = ly_errpath(ly_ctx);
+ if (path) {
+ strlcat(buf, " YANG path: ", buf_len);
+ strlcat(buf, path, buf_len);
+ strlcat(buf, "\n", buf_len);
+ }
+
+ ly_err_clean(ly_ctx, NULL);
+
+ return buf;
+}
+
+void yang_debugging_set(bool enable)
+{
+ if (enable) {
+ ly_log_level(LY_LLDBG);
+ ly_log_dbg_groups(0xFF);
+ } else {
+ ly_log_level(LY_LLERR);
+ ly_log_dbg_groups(0);
+ }
+}
+
+struct ly_ctx *yang_ctx_new_setup(bool embedded_modules, bool explicit_compile)
+{
+ struct ly_ctx *ctx = NULL;
+ const char *yang_models_path = YANG_MODELS_PATH;
+ LY_ERR err;
+
+ if (access(yang_models_path, R_OK | X_OK)) {
+ yang_models_path = NULL;
+ if (errno == ENOENT)
+ zlog_info("yang model directory \"%s\" does not exist",
+ YANG_MODELS_PATH);
+ else
+ flog_err_sys(EC_LIB_LIBYANG,
+ "cannot access yang model directory \"%s\"",
+ YANG_MODELS_PATH);
+ }
+
+ uint options = LY_CTX_NO_YANGLIBRARY | LY_CTX_DISABLE_SEARCHDIR_CWD;
+ if (explicit_compile)
+ options |= LY_CTX_EXPLICIT_COMPILE;
+ err = ly_ctx_new(yang_models_path, options, &ctx);
+ if (err)
+ return NULL;
+
+ if (embedded_modules)
+ ly_ctx_set_module_imp_clb(ctx, yang_module_imp_clb, NULL);
+
+ return ctx;
+}
+
+void yang_init(bool embedded_modules, bool defer_compile)
+{
+ /* 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 = yang_ctx_new_setup(embedded_modules, defer_compile);
+ if (!ly_native_ctx) {
+ flog_err(EC_LIB_LIBYANG, "%s: ly_ctx_new() failed", __func__);
+ exit(1);
+ }
+
+ yang_translator_init();
+}
+
+void yang_init_loading_complete(void)
+{
+ /* Compile everything */
+ if (ly_ctx_compile(ly_native_ctx) != LY_SUCCESS) {
+ flog_err(EC_LIB_YANG_MODULE_LOAD,
+ "%s: failed to compile loaded modules: %s", __func__,
+ ly_errmsg(ly_native_ctx));
+ exit(1);
+ }
+}
+
+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);
+}
+
+const struct lyd_node *yang_dnode_get_parent(const struct lyd_node *dnode,
+ const char *name)
+{
+ const struct lyd_node *orig_dnode = dnode;
+
+ while (orig_dnode) {
+ switch (orig_dnode->schema->nodetype) {
+ case LYS_LIST:
+ case LYS_CONTAINER:
+ if (!strcmp(orig_dnode->schema->name, name))
+ return orig_dnode;
+ break;
+ default:
+ break;
+ }
+
+ orig_dnode = lyd_parent(orig_dnode);
+ }
+
+ return NULL;
+}
+
+bool yang_is_last_list_dnode(const struct lyd_node *dnode)
+{
+ return (((dnode->next == NULL)
+ || (dnode->next
+ && (strcmp(dnode->next->schema->name, dnode->schema->name)
+ != 0)))
+ && dnode->prev
+ && ((dnode->prev == dnode)
+ || (strcmp(dnode->prev->schema->name, dnode->schema->name)
+ != 0)));
+}
+
+bool yang_is_last_level_dnode(const struct lyd_node *dnode)
+{
+ const struct lyd_node *parent;
+ const struct lyd_node *key_leaf;
+ uint8_t keys_size;
+
+ switch (dnode->schema->nodetype) {
+ case LYS_LIST:
+ assert(dnode->parent);
+ parent = lyd_parent(dnode);
+ uint snode_num_keys = yang_snode_num_keys(parent->schema);
+ /* XXX libyang2: q: really don't understand this code. */
+ key_leaf = dnode->prev;
+ for (keys_size = 1; keys_size < snode_num_keys; keys_size++)
+ key_leaf = key_leaf->prev;
+ if (key_leaf->prev == dnode)
+ return true;
+ break;
+ case LYS_CONTAINER:
+ return true;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+const struct lyd_node *
+yang_get_subtree_with_no_sibling(const struct lyd_node *dnode)
+{
+ bool parent = true;
+ const struct lyd_node *node;
+
+ node = dnode;
+ if (node->schema->nodetype != LYS_LIST)
+ return node;
+
+ while (parent) {
+ switch (node->schema->nodetype) {
+ case LYS_CONTAINER:
+ if (!CHECK_FLAG(node->schema->flags, LYS_PRESENCE)) {
+ if (node->parent
+ && (node->parent->schema->module
+ == dnode->schema->module))
+ node = lyd_parent(node);
+ else
+ parent = false;
+ } else
+ parent = false;
+ break;
+ case LYS_LIST:
+ if (yang_is_last_list_dnode(node)
+ && yang_is_last_level_dnode(node)) {
+ if (node->parent
+ && (node->parent->schema->module
+ == dnode->schema->module))
+ node = lyd_parent(node);
+ else
+ parent = false;
+ } else
+ parent = false;
+ break;
+ default:
+ parent = false;
+ break;
+ }
+ }
+ return node;
+}
+
+uint32_t yang_get_list_pos(const struct lyd_node *node)
+{
+ return lyd_list_pos(node);
+}
+
+uint32_t yang_get_list_elements_count(const struct lyd_node *node)
+{
+ unsigned int count;
+ const struct lysc_node *schema;
+
+ if (!node
+ || ((node->schema->nodetype != LYS_LIST)
+ && (node->schema->nodetype != LYS_LEAFLIST))) {
+ return 0;
+ }
+
+ schema = node->schema;
+ count = 0;
+ do {
+ if (node->schema == schema)
+ ++count;
+ node = node->next;
+ } while (node);
+ return count;
+}
diff --git a/lib/yang.h b/lib/yang.h
new file mode 100644
index 0000000..d625b24
--- /dev/null
+++ b/lib/yang.h
@@ -0,0 +1,689 @@
+/*
+ * 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"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Maximum XPath length. */
+#define XPATH_MAXLEN 1024
+
+/* 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_embed {
+ struct yang_module_embed *next;
+ const char *mod_name, *mod_rev;
+ const char *sub_mod_name;
+ const char *sub_mod_rev;
+ const char *data;
+ LYS_INFORMAT format;
+};
+
+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;
+ struct thread *sr_thread;
+#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];
+
+ /* Value encoded as a raw string. */
+ char *value;
+};
+
+struct yang_list_keys {
+ /* Number os keys (max: LIST_MAXKEYS). */
+ uint8_t num;
+
+ /* Value encoded as a raw string. */
+ char key[LIST_MAXKEYS][LIST_MAXKEYLEN];
+};
+
+enum yang_path_type {
+ YANG_PATH_SCHEMA = 0,
+ YANG_PATH_DATA,
+};
+
+enum yang_iter_flags {
+ /* Filter non-presence containers. */
+ YANG_ITER_FILTER_NPCONTAINERS = (1<<0),
+
+ /* Filter list keys (leafs). */
+ YANG_ITER_FILTER_LIST_KEYS = (1<<1),
+
+ /* Filter RPC input/output nodes. */
+ YANG_ITER_FILTER_INPUT_OUTPUT = (1<<2),
+};
+
+/* Callback used by the yang_snodes_iterate_*() family of functions. */
+typedef int (*yang_iterate_cb)(const struct lysc_node *snode, void *arg);
+
+/* Callback used by the yang_dnode_iterate() function. */
+typedef int (*yang_dnode_iter_cb)(const struct lyd_node *dnode, void *arg);
+
+/* Return values of the 'yang_iterate_cb' callback. */
+#define YANG_ITER_CONTINUE 0
+#define YANG_ITER_STOP -1
+
+/* 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);
+
+/*
+ * Load all FRR native YANG models.
+ */
+extern void yang_module_load_all(void);
+
+/*
+ * 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);
+
+/*
+ * Register a YANG module embedded in the binary file. Should be called
+ * from a constructor function.
+ *
+ * embed
+ * YANG module embedding structure to register. (static global provided
+ * by caller.)
+ */
+extern void yang_module_embed(struct yang_module_embed *embed);
+
+/*
+ * Iterate recursively over all children of a schema node.
+ *
+ * snode
+ * YANG schema node to operate on.
+ *
+ * module
+ * When set, iterate over all nodes of the specified module only.
+ *
+ * cb
+ * Function to call with each schema node.
+ *
+ * flags
+ * YANG_ITER_* flags to control how the iteration is performed.
+ *
+ * arg
+ * Arbitrary argument passed as the second parameter in each call to 'cb'.
+ *
+ * Returns:
+ * The return value of the last called callback.
+ */
+extern int yang_snodes_iterate_subtree(const struct lysc_node *snode,
+ const struct lys_module *module,
+ yang_iterate_cb cb, uint16_t flags,
+ void *arg);
+
+/*
+ * Iterate over all libyang schema nodes from all loaded modules of the
+ * given YANG module.
+ *
+ * module
+ * When set, iterate over all nodes of the specified module only.
+ *
+ * cb
+ * Function to call with each schema node.
+ *
+ * flags
+ * YANG_ITER_* flags to control how the iteration is performed.
+ *
+ * arg
+ * Arbitrary argument passed as the second parameter in each call to 'cb'.
+ *
+ * Returns:
+ * The return value of the last called callback.
+ */
+extern int yang_snodes_iterate(const struct lys_module *module,
+ yang_iterate_cb cb, uint16_t flags, void *arg);
+
+/*
+ * 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 lysc_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 lysc_node *yang_snode_real_parent(const struct lysc_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 lysc_node *yang_snode_parent_list(const struct lysc_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 lysc_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 lysc_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 lysc_type *
+yang_snode_get_type(const struct lysc_node *snode);
+
+/*
+ * Get the number of key nodes for the given list.
+ *
+ * snode
+ * libyang (LYS_LIST) schema node to operate on.
+ *
+ * Returns:
+ * The number of key LYS_LEAFs as children of this list node.
+ */
+extern unsigned int yang_snode_num_keys(const struct lysc_node *snode);
+
+#define LY_FOR_KEYS(snode, skey) \
+ for ((skey) = (const struct lysc_node_leaf *)lysc_node_child((snode)); \
+ (skey); (skey) = (const struct lysc_node_leaf *)((skey)->next)) \
+ if (!lysc_is_key(skey)) { \
+ break; \
+ } else
+
+
+/*
+ * 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);
+
+/*
+ * Return the schema name of the given libyang data node.
+ *
+ * dnode
+ * libyang data node.
+ *
+ * xpath_fmt
+ * Optional XPath expression (absolute or relative) to specify a different
+ * data node to operate on in the same data tree.
+ *
+ * Returns:
+ * Schema name of the libyang data node.
+ */
+extern const char *yang_dnode_get_schema_name(const struct lyd_node *dnode,
+ const char *xpath_fmt, ...);
+
+/*
+ * Find a libyang data node by its YANG data path.
+ *
+ * dnode
+ * Base libyang data node to operate on.
+ *
+ * xpath
+ * Limited XPath (absolute or relative) string. See Path in libyang
+ * documentation for restrictions.
+ *
+ * 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);
+
+/*
+ * Find a libyang data node by its YANG data path.
+ *
+ * dnode
+ * Base libyang data node to operate on.
+ *
+ * xpath_fmt
+ * Limited XPath (absolute or relative) format string. See Path in libyang
+ * documentation for restrictions.
+ *
+ * ...
+ * any parameters for xpath_fmt.
+ *
+ * Returns:
+ * The libyang data node if found, or NULL if not found.
+ */
+extern struct lyd_node *yang_dnode_getf(const struct lyd_node *dnode,
+ const char *path_fmt, ...);
+
+/*
+ * Check if a libyang data node exists.
+ *
+ * dnode
+ * Base libyang data node to operate on.
+ *
+ * xpath
+ * Limited XPath (absolute or relative) string. See Path in libyang
+ * documentation for restrictions.
+ *
+ * Returns:
+ * true if a libyang data node was found, false otherwise.
+ */
+extern bool yang_dnode_exists(const struct lyd_node *dnode, const char *xpath);
+
+/*
+ * Check if a libyang data node exists.
+ *
+ * dnode
+ * Base libyang data node to operate on.
+ *
+ * xpath_fmt
+ * Limited XPath (absolute or relative) format string. See Path in
+ * libyang documentation for restrictions.
+ *
+ * ...
+ * any parameters for xpath_fmt.
+ *
+ * Returns:
+ * true if a libyang data node was found, false otherwise.
+ */
+extern bool yang_dnode_existsf(const struct lyd_node *dnode,
+ const char *xpath_fmt, ...);
+
+/*
+ * Iterate over all libyang data nodes that satisfy an XPath query.
+ *
+ * cb
+ * Function to call with each data node.
+ *
+ * arg
+ * Arbitrary argument passed as the second parameter in each call to 'cb'.
+ *
+ * dnode
+ * Base libyang data node to operate on.
+ *
+ * xpath_fmt
+ * XPath expression (absolute or relative).
+ *
+ * ...
+ * any parameters for xpath_fmt.
+ */
+void yang_dnode_iterate(yang_dnode_iter_cb cb, void *arg,
+ 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
+ * 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);
+
+/*
+ * 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
+ * Optional limited XPath (absolute or relative) format string. See Path in
+ * libyang documentation for restrictions.
+ *
+ * ...
+ * any parameters for xpath_fmt.
+ *
+ * Returns:
+ * true if the data node contains the default value, false otherwise.
+ */
+extern bool yang_dnode_is_defaultf(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);
+
+/*
+ * Create a new libyang data node.
+ *
+ * ly_ctx
+ * libyang context to operate on.
+ *
+ * config
+ * Specify whether the data node will contain only configuration data (true)
+ * or both configuration data and state data (false).
+ *
+ * Returns:
+ * Pointer to newly created libyang data node.
+ */
+extern struct lyd_node *yang_dnode_new(struct ly_ctx *ly_ctx, bool config_only);
+
+/*
+ * 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 along with
+ * the entire tree it belongs to.
+ */
+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);
+
+/*
+ * Find the yang_data structure corresponding to an XPath in a list.
+ *
+ * list
+ * list of yang_data structures to operate on.
+ *
+ * xpath_fmt
+ * XPath to search for (format string).
+ *
+ * Returns:
+ * Pointer to yang_data if found, NULL otherwise.
+ */
+extern struct yang_data *yang_data_list_find(const struct list *list,
+ const char *xpath_fmt, ...);
+
+/*
+ * Create and set up a libyang context (for use by the translator)
+ *
+ * embedded_modules
+ * Specify whether libyang should attempt to look for embedded YANG modules.
+ *
+ * explicit_compile
+ * True if the caller will later call ly_ctx_compile to compile all loaded
+ * modules at once.
+ */
+extern struct ly_ctx *yang_ctx_new_setup(bool embedded_modules,
+ bool explicit_compile);
+
+/*
+ * Enable or disable libyang verbose debugging.
+ *
+ * enable
+ * When set to true, enable libyang verbose debugging, otherwise disable it.
+ */
+extern void yang_debugging_set(bool enable);
+
+/*
+ * Print libyang error messages into the provided buffer.
+ *
+ * ly_ctx
+ * libyang context to operate on.
+ *
+ * buf
+ * Buffer to store the libyang error messages.
+ *
+ * buf_len
+ * Size of buf.
+ *
+ * Returns:
+ * The provided buffer.
+ */
+extern const char *yang_print_errors(struct ly_ctx *ly_ctx, char *buf,
+ size_t buf_len);
+
+/*
+ * Initialize the YANG subsystem. Should be called only once during the
+ * daemon initialization process.
+ *
+ * embedded_modules
+ * Specify whether libyang should attempt to look for embedded YANG modules.
+ * defer_compile
+ * Hold off on compiling modules until yang_init_loading_complete is called.
+ */
+extern void yang_init(bool embedded_modules, bool defer_compile);
+
+/*
+ * Should be called after yang_init and all yang_module_load()s have been done,
+ * compiles all modules loaded into the yang context.
+ */
+extern void yang_init_loading_complete(void);
+
+/*
+ * Finish the YANG subsystem gracefully. Should be called only when the daemon
+ * is exiting.
+ */
+extern void yang_terminate(void);
+
+/*
+ * API to return the parent dnode having a given schema-node name
+ * Use case: One has to access the parent dnode's private pointer
+ * for a given child node.
+ * For that there is a need to find parent dnode first.
+ *
+ * dnode The starting node to work on
+ *
+ * name The name of container/list schema-node
+ *
+ * Returns The dnode matched with the given name
+ */
+extern const struct lyd_node *
+yang_dnode_get_parent(const struct lyd_node *dnode, const char *name);
+
+
+/*
+ * In some cases there is a need to auto delete the parent nodes
+ * if the given node is last in the list.
+ * It tries to delete all the parents in a given tree in a given module.
+ * The use case is with static routes and route maps
+ * example : ip route 1.1.1.1/32 ens33
+ * ip route 1.1.1.1/32 ens34
+ * After this no ip route 1.1.1.1/32 ens34 came, now staticd
+ * has to find out upto which level it has to delete the dnodes.
+ * For this case it has to send delete nexthop
+ * After this no ip route 1.1.1.1/32 ens33 came, now staticd has to
+ * clear nexthop, path and route nodes.
+ * The same scheme is required for routemaps also
+ * dnode The starting node to work on
+ *
+ * Returns The final parent node selected for deletion
+ */
+extern const struct lyd_node *
+yang_get_subtree_with_no_sibling(const struct lyd_node *dnode);
+
+/* To get the relative position of a node in list */
+extern uint32_t yang_get_list_pos(const struct lyd_node *node);
+
+/* To get the number of elements in a list
+ *
+ * dnode : The head of list
+ * Returns : The number of dnodes present in the list
+ */
+extern uint32_t yang_get_list_elements_count(const struct lyd_node *node);
+
+/* API to check if the given node is last node in the list */
+bool yang_is_last_list_dnode(const struct lyd_node *dnode);
+
+/* API to check if the given node is last node in the data tree level */
+bool yang_is_last_level_dnode(const struct lyd_node *dnode);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRR_YANG_H_ */
diff --git a/lib/yang_translator.c b/lib/yang_translator.c
new file mode 100644
index 0000000..a2f6e9d
--- /dev/null
+++ b/lib/yang_translator.c
@@ -0,0 +1,547 @@
+/*
+ * 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"
+#include "frrstr.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);
+
+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(const 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 lysc_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));
+
+ const char *keys[] = {"KEY1", "KEY2", "KEY3", "KEY4"};
+ char *xpfmt;
+
+ for (unsigned int i = 0; i < array_size(keys); i++) {
+ xpfmt = frrstr_replace(mapping->xpath_from_fmt, keys[i],
+ "%[^']");
+ strlcpy(mapping->xpath_from_fmt, xpfmt,
+ sizeof(mapping->xpath_from_fmt));
+ XFREE(MTYPE_TMP, xpfmt);
+ }
+
+ for (unsigned int i = 0; i < array_size(keys); i++) {
+ xpfmt = frrstr_replace(mapping->xpath_to_fmt, keys[i], "%s");
+ strlcpy(mapping->xpath_to_fmt, xpfmt,
+ sizeof(mapping->xpath_to_fmt));
+ XFREE(MTYPE_TMP, xpfmt);
+ }
+}
+
+static void yang_tmodule_delete(struct yang_tmodule *tmodule)
+{
+ XFREE(MTYPE_YANG_TRANSLATOR_MODULE, tmodule);
+}
+
+struct yang_translator *yang_translator_load(const char *path)
+{
+ struct yang_translator *translator;
+ struct yang_tmodule *tmodule = NULL;
+ const char *family;
+ struct lyd_node *dnode;
+ struct ly_set *set;
+ struct listnode *ln;
+ LY_ERR err;
+
+ /* Load module translator (JSON file). */
+ err = lyd_parse_data_path(ly_translator_ctx, path, LYD_JSON,
+ LYD_PARSE_NO_STATE, LYD_VALIDATE_NO_STATE,
+ &dnode);
+ if (err) {
+ flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD,
+ "%s: lyd_parse_path() failed: %d", __func__, err);
+ 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);
+ yang_dnode_free(dnode);
+ 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 = yang_ctx_new_setup(false, false);
+ if (!translator->ly_ctx) {
+ flog_warn(EC_LIB_LIBYANG, "%s: ly_ctx_new() failed", __func__);
+ goto error;
+ }
+
+ /* Load modules */
+ if (lyd_find_xpath(dnode, "./module", &set) != LY_SUCCESS)
+ assert(0); /* XXX libyang2: old ly1 code asserted success */
+
+ for (size_t i = 0; i < set->count; i++) {
+ const char *module_name;
+
+ tmodule =
+ XCALLOC(MTYPE_YANG_TRANSLATOR_MODULE, sizeof(*tmodule));
+
+ module_name = yang_dnode_get_string(set->dnodes[i], "./name");
+ tmodule->module = ly_ctx_load_module(translator->ly_ctx,
+ module_name, NULL, NULL);
+ if (!tmodule->module) {
+ flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD,
+ "%s: failed to load module: %s", __func__,
+ module_name);
+ ly_set_free(set, NULL);
+ goto error;
+ }
+ }
+
+ /* Count nodes in modules. */
+ for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) {
+ tmodule->nodes_before_deviations =
+ yang_module_nodes_count(tmodule->module);
+ }
+
+ /* Load the deviations and count nodes again */
+ for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) {
+ const char *module_name = tmodule->module->name;
+ tmodule->deviations = ly_ctx_load_module(
+ translator->ly_ctx, module_name, NULL, NULL);
+ if (!tmodule->deviations) {
+ flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD,
+ "%s: failed to load module: %s", __func__,
+ module_name);
+ ly_set_free(set, NULL);
+ goto error;
+ }
+
+ tmodule->nodes_after_deviations =
+ yang_module_nodes_count(tmodule->module);
+ }
+ ly_set_free(set, NULL);
+
+ /* Calculate the coverage. */
+ for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) {
+ tmodule->coverage = ((double)tmodule->nodes_after_deviations
+ / (double)tmodule->nodes_before_deviations)
+ * 100;
+ }
+
+ /* Load mappings. */
+ if (lyd_find_xpath(dnode, "./module/mappings", &set) != LY_SUCCESS)
+ assert(0); /* XXX libyang2: old ly1 code asserted success */
+ for (size_t i = 0; i < set->count; i++) {
+ const char *xpath_custom, *xpath_native;
+ const struct lysc_node *snode_custom, *snode_native;
+
+ xpath_custom =
+ yang_dnode_get_string(set->dnodes[i], "./custom");
+
+ snode_custom = lys_find_path(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, NULL);
+ goto error;
+ }
+
+ xpath_native =
+ yang_dnode_get_string(set->dnodes[i], "./native");
+ snode_native =
+ lys_find_path(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, NULL);
+ 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, NULL);
+
+ /* 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);
+ yang_tmodule_delete(tmodule);
+
+ return NULL;
+}
+
+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);
+ 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 lysc_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 = lys_find_path(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, *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, false);
+
+ /* Iterate over all nodes from the data tree. */
+ LY_LIST_FOR (*dnode, root) {
+ LYD_TREE_DFS_BEGIN (root, 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. */
+ if (lyd_new_path(new, ly_ctx, xpath,
+ (void *)yang_dnode_get_string(
+ dnode_iter, NULL),
+ LYD_NEW_PATH_UPDATE, NULL)) {
+ flog_err(EC_LIB_LIBYANG,
+ "%s: lyd_new_path() failed", __func__);
+ goto error;
+ }
+
+ next:
+ LYD_TREE_DFS_END(root, 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;
+}
+
+struct translator_validate_args {
+ struct yang_translator *translator;
+ unsigned int errors;
+};
+
+static int yang_translator_validate_cb(const struct lysc_node *snode_custom,
+ void *arg)
+{
+ struct translator_validate_args *args = arg;
+ struct yang_mapping_node *mapping;
+ const struct lysc_node *snode_native;
+ const struct lysc_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(args->translator,
+ YANG_TRANSLATE_TO_NATIVE, xpath);
+ if (!mapping) {
+ flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD,
+ "%s: missing mapping for \"%s\"", __func__, xpath);
+ args->errors += 1;
+ return YANG_ITER_CONTINUE;
+ }
+
+ snode_native =
+ lys_find_path(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->basetype != stype_native->basetype) {
+ flog_warn(
+ EC_LIB_YANG_TRANSLATOR_LOAD,
+ "%s: YANG types are incompatible (xpath: \"%s\")",
+ __func__, xpath);
+ args->errors += 1;
+ return YANG_ITER_CONTINUE;
+ }
+
+ /* TODO: check if the value spaces are identical. */
+ }
+
+ return YANG_ITER_CONTINUE;
+}
+
+/*
+ * 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;
+ struct translator_validate_args args;
+
+ args.translator = translator;
+ args.errors = 0;
+
+ for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) {
+ yang_snodes_iterate(tmodule->module,
+ yang_translator_validate_cb,
+ YANG_ITER_FILTER_NPCONTAINERS
+ | YANG_ITER_FILTER_LIST_KEYS
+ | YANG_ITER_FILTER_INPUT_OUTPUT,
+ &args);
+ }
+
+ if (args.errors)
+ flog_warn(
+ EC_LIB_YANG_TRANSLATOR_LOAD,
+ "%s: failed to validate \"%s\" module translator: %u error(s)",
+ __func__, translator->family, args.errors);
+
+ return args.errors;
+}
+
+static int yang_module_nodes_count_cb(const struct lysc_node *snode, void *arg)
+{
+ unsigned int *total = arg;
+
+ *total += 1;
+
+ return YANG_ITER_CONTINUE;
+}
+
+/* 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_snodes_iterate(module, yang_module_nodes_count_cb,
+ YANG_ITER_FILTER_NPCONTAINERS
+ | YANG_ITER_FILTER_LIST_KEYS
+ | YANG_ITER_FILTER_INPUT_OUTPUT,
+ &total);
+
+ return total;
+}
+
+void yang_translator_init(void)
+{
+ ly_translator_ctx = yang_ctx_new_setup(true, false);
+ if (!ly_translator_ctx) {
+ flog_err(EC_LIB_LIBYANG, "%s: ly_ctx_new() failed", __func__);
+ exit(1);
+ }
+
+ if (!ly_ctx_load_module(ly_translator_ctx, "frr-module-translator",
+ NULL, 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);
+}
diff --git a/lib/yang_translator.h b/lib/yang_translator.h
new file mode 100644
index 0000000..55f396a
--- /dev/null
+++ b/lib/yang_translator.h
@@ -0,0 +1,152 @@
+/*
+ * 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_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#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);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRR_YANG_TRANSLATOR_H_ */
diff --git a/lib/yang_wrappers.c b/lib/yang_wrappers.c
new file mode 100644
index 0000000..ea21d13
--- /dev/null
+++ b/lib/yang_wrappers.c
@@ -0,0 +1,1155 @@
+/*
+ * 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 "base64.h"
+#include "log.h"
+#include "lib_errors.h"
+#include "northbound.h"
+#include "printfrr.h"
+#include "nexthop.h"
+#include "printfrr.h"
+
+
+#define YANG_DNODE_XPATH_GET_VALUE(dnode, xpath_fmt) \
+ ({ \
+ va_list __ap; \
+ va_start(__ap, (xpath_fmt)); \
+ const struct lyd_value *__dvalue = \
+ yang_dnode_xpath_get_value(dnode, xpath_fmt, __ap); \
+ va_end(__ap); \
+ __dvalue; \
+ })
+
+#define YANG_DNODE_XPATH_GET_CANON(dnode, xpath_fmt) \
+ ({ \
+ va_list __ap; \
+ va_start(__ap, (xpath_fmt)); \
+ const char *__canon = \
+ yang_dnode_xpath_get_canon(dnode, xpath_fmt, __ap); \
+ va_end(__ap); \
+ __canon; \
+ })
+
+#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)
+
+static inline const char *
+yang_dnode_xpath_get_canon(const struct lyd_node *dnode, const char *xpath_fmt,
+ va_list ap)
+{
+ const struct lyd_node_term *__dleaf =
+ (const struct lyd_node_term *)dnode;
+ assert(__dleaf);
+ if (xpath_fmt) {
+ char __xpath[XPATH_MAXLEN];
+ vsnprintf(__xpath, sizeof(__xpath), xpath_fmt, ap);
+ __dleaf = (const struct lyd_node_term *)yang_dnode_get(dnode,
+ __xpath);
+ YANG_DNODE_GET_ASSERT(__dleaf, __xpath);
+ }
+ return lyd_get_value(&__dleaf->node);
+}
+
+static inline const struct lyd_value *
+yang_dnode_xpath_get_value(const struct lyd_node *dnode, const char *xpath_fmt,
+ va_list ap)
+{
+ const struct lyd_node_term *__dleaf =
+ (const struct lyd_node_term *)dnode;
+ assert(__dleaf);
+ if (xpath_fmt) {
+ char __xpath[XPATH_MAXLEN];
+ vsnprintf(__xpath, sizeof(__xpath), xpath_fmt, ap);
+ __dleaf = (const struct lyd_node_term *)yang_dnode_get(dnode,
+ __xpath);
+ YANG_DNODE_GET_ASSERT(__dleaf, __xpath);
+ }
+ const struct lyd_value *__dvalue = &__dleaf->value;
+ if (__dvalue->realtype->basetype == LY_TYPE_UNION)
+ __dvalue = &__dvalue->subvalue->value;
+ return __dvalue;
+}
+
+static const char *yang_get_default_value(const char *xpath)
+{
+ const struct lysc_node *snode;
+ const char *value;
+
+ snode = lys_find_path(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;
+}
+
+/*
+ * 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" : "false");
+}
+
+bool yang_dnode_get_bool(const struct lyd_node *dnode, const char *xpath_fmt,
+ ...)
+{
+ const struct lyd_value *dvalue;
+ dvalue = YANG_DNODE_XPATH_GET_VALUE(dnode, xpath_fmt);
+ assert(dvalue->realtype->basetype == LY_TYPE_BOOL);
+ return dvalue->boolean;
+}
+
+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 double denom[19] = {1e0, 1e-1, 1e-2, 1e-3, 1e-4,
+ 1e-5, 1e-6, 1e-7, 1e-8, 1e-9,
+ 1e-10, 1e-11, 1e-12, 1e-13, 1e-14,
+ 1e-15, 1e-16, 1e-17, 1e-18};
+ const struct lysc_type_dec *dectype;
+ const struct lyd_value *dvalue;
+
+ dvalue = YANG_DNODE_XPATH_GET_VALUE(dnode, xpath_fmt);
+ dectype = (const struct lysc_type_dec *)dvalue->realtype;
+ assert(dectype->basetype == LY_TYPE_DEC64);
+ assert(dectype->fraction_digits < sizeof(denom) / sizeof(*denom));
+ return (double)dvalue->dec64 * denom[dectype->fraction_digits];
+}
+
+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 lysc_node *snode;
+ const struct lysc_node_leaf *sleaf;
+ const struct lysc_type_enum *type;
+ const struct lysc_type_bitenum_item *enums;
+
+ snode = lys_find_path(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();
+ }
+
+ assert(snode->nodetype == LYS_LEAF);
+ sleaf = (const struct lysc_node_leaf *)snode;
+ type = (const struct lysc_type_enum *)sleaf->type;
+ assert(type->basetype == LY_TYPE_ENUM);
+ enums = type->enums;
+ unsigned int count = LY_ARRAY_COUNT(enums);
+ for (unsigned int i = 0; i < count; i++) {
+ if (strmatch(value, enums[i].name)) {
+ assert(CHECK_FLAG(enums[i].flags, LYS_SET_VALUE));
+ return enums[i].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 lysc_node *snode;
+ const struct lysc_node_leaf *sleaf;
+ const struct lysc_type_enum *type;
+ const struct lysc_type_bitenum_item *enums;
+
+ snode = lys_find_path(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();
+ }
+
+ assert(snode->nodetype == LYS_LEAF);
+ sleaf = (const struct lysc_node_leaf *)snode;
+ type = (const struct lysc_type_enum *)sleaf->type;
+ assert(type->basetype == LY_TYPE_ENUM);
+ enums = type->enums;
+ unsigned int count = LY_ARRAY_COUNT(enums);
+ for (unsigned int i = 0; i < count; i++) {
+ if (CHECK_FLAG(enums[i].flags, LYS_SET_VALUE)
+ && value == enums[i].value)
+ return yang_data_new(xpath, enums[i].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_value *dvalue;
+
+ dvalue = YANG_DNODE_XPATH_GET_VALUE(dnode, xpath_fmt);
+ assert(dvalue->realtype->basetype == LY_TYPE_ENUM);
+ assert(dvalue->enum_item->flags & LYS_SET_VALUE);
+ return dvalue->enum_item->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_value *dvalue;
+ dvalue = YANG_DNODE_XPATH_GET_VALUE(dnode, xpath_fmt);
+ assert(dvalue->realtype->basetype == LY_TYPE_INT8);
+ return dvalue->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_value *dvalue;
+ dvalue = YANG_DNODE_XPATH_GET_VALUE(dnode, xpath_fmt);
+ assert(dvalue->realtype->basetype == LY_TYPE_INT16);
+ return dvalue->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_value *dvalue;
+ dvalue = YANG_DNODE_XPATH_GET_VALUE(dnode, xpath_fmt);
+ assert(dvalue->realtype->basetype == LY_TYPE_INT32);
+ return dvalue->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];
+
+ snprintfrr(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_value *dvalue;
+ dvalue = YANG_DNODE_XPATH_GET_VALUE(dnode, xpath_fmt);
+ assert(dvalue->realtype->basetype == LY_TYPE_INT64);
+ return dvalue->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_value *dvalue;
+ dvalue = YANG_DNODE_XPATH_GET_VALUE(dnode, xpath_fmt);
+ assert(dvalue->realtype->basetype == LY_TYPE_UINT8);
+ return dvalue->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_value *dvalue;
+ dvalue = YANG_DNODE_XPATH_GET_VALUE(dnode, xpath_fmt);
+ assert(dvalue->realtype->basetype == LY_TYPE_UINT16);
+ return dvalue->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_value *dvalue;
+ dvalue = YANG_DNODE_XPATH_GET_VALUE(dnode, xpath_fmt);
+ assert(dvalue->realtype->basetype == LY_TYPE_UINT32);
+ return dvalue->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];
+
+ snprintfrr(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_value *dvalue;
+ dvalue = YANG_DNODE_XPATH_GET_VALUE(dnode, xpath_fmt);
+ assert(dvalue->realtype->basetype == LY_TYPE_UINT64);
+ return dvalue->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, ...)
+{
+ return YANG_DNODE_XPATH_GET_CANON(dnode, xpath_fmt);
+}
+
+void yang_dnode_get_string_buf(char *buf, size_t size,
+ const struct lyd_node *dnode,
+ const char *xpath_fmt, ...)
+{
+ const char *canon = YANG_DNODE_XPATH_GET_CANON(dnode, xpath_fmt);
+ if (strlcpy(buf, canon, 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);
+}
+
+/*
+ * Primitive type: binary.
+ */
+struct yang_data *yang_data_new_binary(const char *xpath, const char *value,
+ size_t len)
+{
+ char *value_str;
+ struct base64_encodestate s;
+ int cnt;
+ char *c;
+ struct yang_data *data;
+
+ value_str = (char *)malloc(len * 2);
+ base64_init_encodestate(&s);
+ cnt = base64_encode_block(value, len, value_str, &s);
+ c = value_str + cnt;
+ cnt = base64_encode_blockend(c, &s);
+ c += cnt;
+ *c = 0;
+ data = yang_data_new(xpath, value_str);
+ free(value_str);
+ return data;
+}
+
+size_t yang_dnode_get_binary_buf(char *buf, size_t size,
+ const struct lyd_node *dnode,
+ const char *xpath_fmt, ...)
+{
+ const char *canon;
+ size_t cannon_len;
+ size_t decode_len;
+ size_t ret_len;
+ size_t cnt;
+ char *value_str;
+ struct base64_decodestate s;
+
+ canon = YANG_DNODE_XPATH_GET_CANON(dnode, xpath_fmt);
+ cannon_len = strlen(canon);
+ decode_len = cannon_len + 1;
+ value_str = (char *)malloc(decode_len);
+ base64_init_decodestate(&s);
+ cnt = base64_decode_block(canon, cannon_len, value_str, &s);
+
+ ret_len = size > cnt ? cnt : size;
+ memcpy(buf, value_str, ret_len);
+ if (size < cnt) {
+ 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);
+ }
+ free(value_str);
+ return ret_len;
+}
+
+
+/*
+ * Primitive type: empty.
+ */
+struct yang_data *yang_data_new_empty(const char *xpath)
+{
+ return yang_data_new(xpath, NULL);
+}
+
+bool yang_dnode_get_empty(const struct lyd_node *dnode, const char *xpath_fmt,
+ ...)
+{
+ va_list ap;
+ char xpath[XPATH_MAXLEN];
+ const struct lyd_node_term *dleaf;
+
+ assert(dnode);
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+
+ dnode = yang_dnode_get(dnode, xpath);
+ if (dnode) {
+ dleaf = (const struct lyd_node_term *)dnode;
+ if (dleaf->value.realtype->basetype == LY_TYPE_EMPTY)
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * Derived type: IP prefix.
+ */
+void yang_str2prefix(const char *value, union prefixptr prefix)
+{
+ (void)str2prefix(value, prefix.p);
+ apply_mask(prefix.p);
+}
+
+struct yang_data *yang_data_new_prefix(const char *xpath,
+ union prefixconstptr 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_prefix(struct prefix *prefix, const struct lyd_node *dnode,
+ const char *xpath_fmt, ...)
+{
+ const char *canon;
+ /*
+ * Initialize prefix to avoid static analyzer complaints about
+ * uninitialized memory.
+ */
+ memset(prefix, 0, sizeof(*prefix));
+
+ /* XXX ip_prefix is a native type now in ly2, leverage? */
+ canon = YANG_DNODE_XPATH_GET_CANON(dnode, xpath_fmt);
+ (void)str2prefix(canon, prefix);
+}
+
+void yang_get_default_prefix(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_str2prefix(value, var);
+}
+
+/*
+ * 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, ...)
+{
+ /* XXX libyang2 IPv4 address is a native type now in ly2 */
+ const char *canon = YANG_DNODE_XPATH_GET_CANON(dnode, xpath_fmt);
+ (void)inet_pton(AF_INET, canon, 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,
+ union prefixconstptr 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, ...)
+{
+ struct prefix_ipv4 *prefix4 = prefix.p4;
+ /* XXX libyang2: ipv4/6 address is a native type now in ly2 */
+ const char *canon = YANG_DNODE_XPATH_GET_CANON(dnode, xpath_fmt);
+ (void)str2prefix_ipv4(canon, 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, ...)
+{
+ /* XXX libyang2: IPv6 address is a native type now, leverage. */
+ const char *canon = YANG_DNODE_XPATH_GET_CANON(dnode, xpath_fmt);
+ (void)inet_pton(AF_INET6, canon, 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,
+ union prefixconstptr 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, ...)
+{
+ struct prefix_ipv6 *prefix6 = prefix.p6;
+
+ /* XXX IPv6 address is a native type now in ly2 -- can we leverage? */
+ const char *canon = YANG_DNODE_XPATH_GET_CANON(dnode, xpath_fmt);
+ (void)str2prefix_ipv6(canon, 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);
+}
+
+/*
+ * Derived type: ip.
+ */
+void yang_str2ip(const char *value, struct ipaddr *ip)
+{
+ (void)str2ipaddr(value, ip);
+}
+
+struct yang_data *yang_data_new_ip(const char *xpath, const struct ipaddr *addr)
+{
+ size_t sz = IS_IPADDR_V4(addr) ? INET_ADDRSTRLEN : INET6_ADDRSTRLEN;
+ char value_str[sz];
+
+ ipaddr2str(addr, value_str, sizeof(value_str));
+ return yang_data_new(xpath, value_str);
+}
+
+void yang_dnode_get_ip(struct ipaddr *addr, const struct lyd_node *dnode,
+ const char *xpath_fmt, ...)
+{
+ /* XXX IPv4 address could be a plugin type now in ly2, leverage? */
+ const char *canon = YANG_DNODE_XPATH_GET_CANON(dnode, xpath_fmt);
+ (void)str2ipaddr(canon, addr);
+}
+
+void yang_get_default_ip(struct ipaddr *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_str2ip(value, var);
+}
+
+struct yang_data *yang_data_new_mac(const char *xpath,
+ const struct ethaddr *mac)
+{
+ char value_str[ETHER_ADDR_STRLEN];
+
+ prefix_mac2str(mac, value_str, sizeof(value_str));
+ return yang_data_new(xpath, value_str);
+}
+
+void yang_str2mac(const char *value, struct ethaddr *mac)
+{
+ (void)prefix_str2mac(value, mac);
+}
+
+struct yang_data *yang_data_new_date_and_time(const char *xpath, time_t time)
+{
+ struct tm tm;
+ char timebuf[MONOTIME_STRLEN];
+ struct timeval _time, time_real;
+ char *ts_dot;
+ uint16_t buflen;
+
+ _time.tv_sec = time;
+ _time.tv_usec = 0;
+ monotime_to_realtime(&_time, &time_real);
+
+ gmtime_r(&time_real.tv_sec, &tm);
+
+ /* rfc-3339 format */
+ strftime(timebuf, sizeof(timebuf), "%Y-%m-%dT%H:%M:%S", &tm);
+ buflen = strlen(timebuf);
+ ts_dot = timebuf + buflen;
+
+ /* microseconds and appends Z */
+ snprintfrr(ts_dot, sizeof(timebuf) - buflen, ".%06luZ",
+ (unsigned long)time_real.tv_usec);
+
+ return yang_data_new(xpath, timebuf);
+}
+
+const char *yang_nexthop_type2str(uint32_t ntype)
+{
+ switch (ntype) {
+ case NEXTHOP_TYPE_IFINDEX:
+ return "ifindex";
+ break;
+ case NEXTHOP_TYPE_IPV4:
+ return "ip4";
+ break;
+ case NEXTHOP_TYPE_IPV4_IFINDEX:
+ return "ip4-ifindex";
+ break;
+ case NEXTHOP_TYPE_IPV6:
+ return "ip6";
+ break;
+ case NEXTHOP_TYPE_IPV6_IFINDEX:
+ return "ip6-ifindex";
+ break;
+ case NEXTHOP_TYPE_BLACKHOLE:
+ return "blackhole";
+ break;
+ default:
+ return "unknown";
+ break;
+ }
+}
+
+
+const char *yang_afi_safi_value2identity(afi_t afi, safi_t safi)
+{
+ if (afi == AFI_IP && safi == SAFI_UNICAST)
+ return "frr-routing:ipv4-unicast";
+ if (afi == AFI_IP6 && safi == SAFI_UNICAST)
+ return "frr-routing:ipv6-unicast";
+ if (afi == AFI_IP && safi == SAFI_MULTICAST)
+ return "frr-routing:ipv4-multicast";
+ if (afi == AFI_IP6 && safi == SAFI_MULTICAST)
+ return "frr-routing:ipv6-multicast";
+ if (afi == AFI_IP && safi == SAFI_MPLS_VPN)
+ return "frr-routing:l3vpn-ipv4-unicast";
+ if (afi == AFI_IP6 && safi == SAFI_MPLS_VPN)
+ return "frr-routing:l3vpn-ipv6-unicast";
+ if (afi == AFI_L2VPN && safi == SAFI_EVPN)
+ return "frr-routing:l2vpn-evpn";
+ if (afi == AFI_IP && safi == SAFI_LABELED_UNICAST)
+ return "frr-routing:ipv4-labeled-unicast";
+ if (afi == AFI_IP6 && safi == SAFI_LABELED_UNICAST)
+ return "frr-routing:ipv6-labeled-unicast";
+ if (afi == AFI_IP && safi == SAFI_FLOWSPEC)
+ return "frr-routing:ipv4-flowspec";
+ if (afi == AFI_IP6 && safi == SAFI_FLOWSPEC)
+ return "frr-routing:ipv6-flowspec";
+
+ return NULL;
+}
+
+void yang_afi_safi_identity2value(const char *key, afi_t *afi, safi_t *safi)
+{
+ if (strmatch(key, "frr-routing:ipv4-unicast")) {
+ *afi = AFI_IP;
+ *safi = SAFI_UNICAST;
+ } else if (strmatch(key, "frr-routing:ipv6-unicast")) {
+ *afi = AFI_IP6;
+ *safi = SAFI_UNICAST;
+ } else if (strmatch(key, "frr-routing:ipv4-multicast")) {
+ *afi = AFI_IP;
+ *safi = SAFI_MULTICAST;
+ } else if (strmatch(key, "frr-routing:ipv6-multicast")) {
+ *afi = AFI_IP6;
+ *safi = SAFI_MULTICAST;
+ } else if (strmatch(key, "frr-routing:l3vpn-ipv4-unicast")) {
+ *afi = AFI_IP;
+ *safi = SAFI_MPLS_VPN;
+ } else if (strmatch(key, "frr-routing:l3vpn-ipv6-unicast")) {
+ *afi = AFI_IP6;
+ *safi = SAFI_MPLS_VPN;
+ } else if (strmatch(key, "frr-routing:ipv4-labeled-unicast")) {
+ *afi = AFI_IP;
+ *safi = SAFI_LABELED_UNICAST;
+ } else if (strmatch(key, "frr-routing:ipv6-labeled-unicast")) {
+ *afi = AFI_IP6;
+ *safi = SAFI_LABELED_UNICAST;
+ } else if (strmatch(key, "frr-routing:l2vpn-evpn")) {
+ *afi = AFI_L2VPN;
+ *safi = SAFI_EVPN;
+ } else if (strmatch(key, "frr-routing:ipv4-flowspec")) {
+ *afi = AFI_IP;
+ *safi = SAFI_FLOWSPEC;
+ } else if (strmatch(key, "frr-routing:ipv6-flowspec")) {
+ *afi = AFI_IP6;
+ *safi = SAFI_FLOWSPEC;
+ } else {
+ *afi = AFI_UNSPEC;
+ *safi = SAFI_UNSPEC;
+ }
+}
diff --git a/lib/yang_wrappers.h b/lib/yang_wrappers.h
new file mode 100644
index 0000000..56b3148
--- /dev/null
+++ b/lib/yang_wrappers.h
@@ -0,0 +1,210 @@
+/*
+ * 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"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* 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, ...);
+
+/* binary */
+extern struct yang_data *yang_data_new_binary(const char *xpath,
+ const char *value, size_t len);
+extern size_t yang_dnode_get_binary_buf(char *buf, size_t size,
+ const struct lyd_node *dnode,
+ const char *xpath_fmt, ...);
+
+/* empty */
+extern struct yang_data *yang_data_new_empty(const char *xpath);
+extern bool yang_dnode_get_empty(const struct lyd_node *dnode,
+ const char *xpath_fmt, ...);
+
+/* ip prefix */
+extern void yang_str2prefix(const char *value, union prefixptr prefix);
+extern struct yang_data *yang_data_new_prefix(const char *xpath,
+ union prefixconstptr prefix);
+extern void yang_dnode_get_prefix(struct prefix *prefix,
+ const struct lyd_node *dnode,
+ const char *xpath_fmt, ...);
+extern void yang_get_default_prefix(union prefixptr var, 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,
+ union prefixconstptr 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,
+ union prefixconstptr 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,
+ ...);
+
+/* ip */
+extern void yang_str2ip(const char *value, struct ipaddr *addr);
+extern struct yang_data *yang_data_new_ip(const char *xpath,
+ const struct ipaddr *addr);
+extern void yang_dnode_get_ip(struct ipaddr *addr, const struct lyd_node *dnode,
+ const char *xpath_fmt, ...);
+extern void yang_get_default_ip(struct ipaddr *var, const char *xpath_fmt, ...);
+
+/* mac */
+extern struct yang_data *yang_data_new_mac(const char *xpath,
+ const struct ethaddr *mac);
+extern void yang_str2mac(const char *value, struct ethaddr *mac);
+
+/*data-and-time */
+extern struct yang_data *yang_data_new_date_and_time(const char *xpath,
+ time_t time);
+
+/* nexthop enum2str */
+extern const char *yang_nexthop_type2str(uint32_t ntype);
+
+const char *yang_afi_safi_value2identity(afi_t afi, safi_t safi);
+void yang_afi_safi_identity2value(const char *key, afi_t *afi, safi_t *safi);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRR_NORTHBOUND_WRAPPERS_H_ */
diff --git a/lib/zclient.c b/lib/zclient.c
new file mode 100644
index 0000000..8ec82ab
--- /dev/null
+++ b/lib/zclient.c
@@ -0,0 +1,4329 @@
+/* Zebra's client library.
+ * Copyright (C) 1999 Kunihiro Ishiguro
+ * Copyright (C) 2005 Andrew J. Schorr
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra 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 "stream.h"
+#include "buffer.h"
+#include "network.h"
+#include "vrf.h"
+#include "vrf_int.h"
+#include "if.h"
+#include "log.h"
+#include "thread.h"
+#include "zclient.h"
+#include "memory.h"
+#include "table.h"
+#include "nexthop.h"
+#include "mpls.h"
+#include "sockopt.h"
+#include "pbr.h"
+#include "nexthop_group.h"
+#include "lib_errors.h"
+#include "srte.h"
+#include "printfrr.h"
+#include "srv6.h"
+
+DEFINE_MTYPE_STATIC(LIB, ZCLIENT, "Zclient");
+DEFINE_MTYPE_STATIC(LIB, REDIST_INST, "Redistribution instance IDs");
+
+/* Zebra client events. */
+enum zclient_event { ZCLIENT_SCHEDULE, ZCLIENT_READ, ZCLIENT_CONNECT };
+
+/* Prototype for event manager. */
+static void zclient_event(enum zclient_event, struct zclient *);
+
+static void zebra_interface_if_set_value(struct stream *s,
+ struct interface *ifp);
+
+struct zclient_options zclient_options_default = {.receive_notify = false,
+ .synchronous = false};
+
+struct sockaddr_storage zclient_addr;
+socklen_t zclient_addr_len;
+
+/* This file local debug flag. */
+static int zclient_debug;
+
+/* Allocate zclient structure. */
+struct zclient *zclient_new(struct thread_master *master,
+ struct zclient_options *opt,
+ zclient_handler *const *handlers, size_t n_handlers)
+{
+ struct zclient *zclient;
+ size_t stream_size =
+ MAX(ZEBRA_MAX_PACKET_SIZ, sizeof(struct zapi_route));
+
+ zclient = XCALLOC(MTYPE_ZCLIENT, sizeof(struct zclient));
+
+ zclient->ibuf = stream_new(stream_size);
+ zclient->obuf = stream_new(stream_size);
+ zclient->wb = buffer_new(0);
+ zclient->master = master;
+
+ zclient->handlers = handlers;
+ zclient->n_handlers = n_handlers;
+
+ zclient->receive_notify = opt->receive_notify;
+ zclient->synchronous = opt->synchronous;
+
+ return zclient;
+}
+
+/* This function is only called when exiting, because
+ many parts of the code do not check for I/O errors, so they could
+ reference an invalid pointer if the structure was ever freed.
+
+ Free zclient structure. */
+void zclient_free(struct zclient *zclient)
+{
+ if (zclient->ibuf)
+ stream_free(zclient->ibuf);
+ if (zclient->obuf)
+ stream_free(zclient->obuf);
+ if (zclient->wb)
+ buffer_free(zclient->wb);
+
+ XFREE(MTYPE_ZCLIENT, zclient);
+}
+
+unsigned short *redist_check_instance(struct redist_proto *red,
+ unsigned short instance)
+{
+ struct listnode *node;
+ unsigned short *id;
+
+ if (!red->instances)
+ return NULL;
+
+ for (ALL_LIST_ELEMENTS_RO(red->instances, node, id))
+ if (*id == instance)
+ return id;
+
+ return NULL;
+}
+
+void redist_add_instance(struct redist_proto *red, unsigned short instance)
+{
+ unsigned short *in;
+
+ red->enabled = 1;
+
+ if (!red->instances)
+ red->instances = list_new();
+
+ in = XMALLOC(MTYPE_REDIST_INST, sizeof(unsigned short));
+ *in = instance;
+ listnode_add(red->instances, in);
+}
+
+void redist_del_instance(struct redist_proto *red, unsigned short instance)
+{
+ unsigned short *id;
+
+ id = redist_check_instance(red, instance);
+ if (!id)
+ return;
+
+ listnode_delete(red->instances, id);
+ XFREE(MTYPE_REDIST_INST, id);
+ if (!red->instances->count) {
+ red->enabled = 0;
+ list_delete(&red->instances);
+ }
+}
+
+void redist_del_all_instances(struct redist_proto *red)
+{
+ struct listnode *ln, *nn;
+ unsigned short *id;
+
+ if (!red->instances)
+ return;
+
+ for (ALL_LIST_ELEMENTS(red->instances, ln, nn, id))
+ redist_del_instance(red, *id);
+}
+
+/* Stop zebra client services. */
+void zclient_stop(struct zclient *zclient)
+{
+ afi_t afi;
+ int i;
+
+ if (zclient_debug)
+ zlog_debug("zclient %p stopped", zclient);
+
+ /* Stop threads. */
+ THREAD_OFF(zclient->t_read);
+ THREAD_OFF(zclient->t_connect);
+ THREAD_OFF(zclient->t_write);
+
+ /* Reset streams. */
+ stream_reset(zclient->ibuf);
+ stream_reset(zclient->obuf);
+
+ /* Empty the write buffer. */
+ buffer_reset(zclient->wb);
+
+ /* Close socket. */
+ if (zclient->sock >= 0) {
+ close(zclient->sock);
+ zclient->sock = -1;
+ }
+ zclient->fail = 0;
+
+ for (afi = AFI_IP; afi < AFI_MAX; afi++) {
+ for (i = 0; i < ZEBRA_ROUTE_MAX; i++) {
+ vrf_bitmap_free(zclient->redist[afi][i]);
+ zclient->redist[afi][i] = VRF_BITMAP_NULL;
+ }
+ redist_del_instance(
+ &zclient->mi_redist[afi][zclient->redist_default],
+ zclient->instance);
+
+ vrf_bitmap_free(zclient->default_information[afi]);
+ zclient->default_information[afi] = VRF_BITMAP_NULL;
+ }
+}
+
+void zclient_reset(struct zclient *zclient)
+{
+ afi_t afi;
+
+ zclient_stop(zclient);
+
+ for (afi = AFI_IP; afi < AFI_MAX; afi++)
+ redist_del_instance(
+ &zclient->mi_redist[afi][zclient->redist_default],
+ zclient->instance);
+
+ zclient_init(zclient, zclient->redist_default, zclient->instance,
+ zclient->privs);
+}
+
+/**
+ * Connect to zebra daemon.
+ * @param zclient a pointer to zclient structure
+ * @return socket fd just to make sure that connection established
+ * @see zclient_init
+ * @see zclient_new
+ */
+int zclient_socket_connect(struct zclient *zclient)
+{
+ int sock;
+ int ret;
+
+ /* We should think about IPv6 connection. */
+ sock = socket(zclient_addr.ss_family, SOCK_STREAM, 0);
+ if (sock < 0)
+ return -1;
+
+ set_cloexec(sock);
+ setsockopt_so_sendbuf(sock, 1048576);
+
+ /* Connect to zebra. */
+ ret = connect(sock, (struct sockaddr *)&zclient_addr, zclient_addr_len);
+ if (ret < 0) {
+ if (zclient_debug)
+ zlog_debug("%s connect failure: %d(%s)", __func__,
+ errno, safe_strerror(errno));
+ close(sock);
+ return -1;
+ }
+
+ zclient->sock = sock;
+ return sock;
+}
+
+static enum zclient_send_status zclient_failed(struct zclient *zclient)
+{
+ zclient->fail++;
+ zclient_stop(zclient);
+ zclient_event(ZCLIENT_CONNECT, zclient);
+ return ZCLIENT_SEND_FAILURE;
+}
+
+static void zclient_flush_data(struct thread *thread)
+{
+ struct zclient *zclient = THREAD_ARG(thread);
+
+ zclient->t_write = NULL;
+ if (zclient->sock < 0)
+ return;
+ switch (buffer_flush_available(zclient->wb, zclient->sock)) {
+ case BUFFER_ERROR:
+ flog_err(
+ EC_LIB_ZAPI_SOCKET,
+ "%s: buffer_flush_available failed on zclient fd %d, closing",
+ __func__, zclient->sock);
+ zclient_failed(zclient);
+ return;
+ case BUFFER_PENDING:
+ zclient->t_write = NULL;
+ thread_add_write(zclient->master, zclient_flush_data, zclient,
+ zclient->sock, &zclient->t_write);
+ break;
+ case BUFFER_EMPTY:
+ if (zclient->zebra_buffer_write_ready)
+ (*zclient->zebra_buffer_write_ready)();
+ break;
+ }
+}
+
+/*
+ * Returns:
+ * ZCLIENT_SEND_FAILED - is a failure
+ * ZCLIENT_SEND_SUCCESS - means we sent data to zebra
+ * ZCLIENT_SEND_BUFFERED - means we are buffering
+ */
+enum zclient_send_status zclient_send_message(struct zclient *zclient)
+{
+ if (zclient->sock < 0)
+ return ZCLIENT_SEND_FAILURE;
+ switch (buffer_write(zclient->wb, zclient->sock,
+ STREAM_DATA(zclient->obuf),
+ stream_get_endp(zclient->obuf))) {
+ case BUFFER_ERROR:
+ flog_err(EC_LIB_ZAPI_SOCKET,
+ "%s: buffer_write failed to zclient fd %d, closing",
+ __func__, zclient->sock);
+ return zclient_failed(zclient);
+ case BUFFER_EMPTY:
+ THREAD_OFF(zclient->t_write);
+ return ZCLIENT_SEND_SUCCESS;
+ case BUFFER_PENDING:
+ thread_add_write(zclient->master, zclient_flush_data, zclient,
+ zclient->sock, &zclient->t_write);
+ return ZCLIENT_SEND_BUFFERED;
+ }
+
+ /* should not get here */
+ return ZCLIENT_SEND_SUCCESS;
+}
+
+/*
+ * If we add more data to this structure please ensure that
+ * struct zmsghdr in lib/zclient.h is updated as appropriate.
+ */
+void zclient_create_header(struct stream *s, uint16_t command, vrf_id_t vrf_id)
+{
+ /* length placeholder, caller can update */
+ stream_putw(s, ZEBRA_HEADER_SIZE);
+ stream_putc(s, ZEBRA_HEADER_MARKER);
+ stream_putc(s, ZSERV_VERSION);
+ stream_putl(s, vrf_id);
+ stream_putw(s, command);
+}
+
+int zclient_read_header(struct stream *s, int sock, uint16_t *size,
+ uint8_t *marker, uint8_t *version, vrf_id_t *vrf_id,
+ uint16_t *cmd)
+{
+ if (stream_read(s, sock, ZEBRA_HEADER_SIZE) != ZEBRA_HEADER_SIZE)
+ return -1;
+
+ STREAM_GETW(s, *size);
+ *size -= ZEBRA_HEADER_SIZE;
+ STREAM_GETC(s, *marker);
+ STREAM_GETC(s, *version);
+ STREAM_GETL(s, *vrf_id);
+ STREAM_GETW(s, *cmd);
+
+ if (*version != ZSERV_VERSION || *marker != ZEBRA_HEADER_MARKER) {
+ flog_err(
+ EC_LIB_ZAPI_MISSMATCH,
+ "%s: socket %d version mismatch, marker %d, version %d",
+ __func__, sock, *marker, *version);
+ return -1;
+ }
+
+ if (*size && stream_read(s, sock, *size) != *size)
+ return -1;
+
+ return 0;
+stream_failure:
+ return -1;
+}
+
+bool zapi_parse_header(struct stream *zmsg, struct zmsghdr *hdr)
+{
+ STREAM_GETW(zmsg, hdr->length);
+ STREAM_GETC(zmsg, hdr->marker);
+ STREAM_GETC(zmsg, hdr->version);
+ STREAM_GETL(zmsg, hdr->vrf_id);
+ STREAM_GETW(zmsg, hdr->command);
+ return true;
+stream_failure:
+ return false;
+}
+
+/* Send simple Zebra message. */
+static enum zclient_send_status zebra_message_send(struct zclient *zclient,
+ int command, vrf_id_t vrf_id)
+{
+ struct stream *s;
+
+ /* Get zclient output buffer. */
+ s = zclient->obuf;
+ stream_reset(s);
+
+ /* Send very simple command only Zebra message. */
+ zclient_create_header(s, command, vrf_id);
+
+ return zclient_send_message(zclient);
+}
+
+enum zclient_send_status zclient_send_hello(struct zclient *zclient)
+{
+ struct stream *s;
+
+ if (zclient->redist_default || zclient->synchronous) {
+ s = zclient->obuf;
+ stream_reset(s);
+
+ /* The VRF ID in the HELLO message is always 0. */
+ zclient_create_header(s, ZEBRA_HELLO, VRF_DEFAULT);
+ stream_putc(s, zclient->redist_default);
+ stream_putw(s, zclient->instance);
+ stream_putl(s, zclient->session_id);
+ if (zclient->receive_notify)
+ stream_putc(s, 1);
+ else
+ stream_putc(s, 0);
+ if (zclient->synchronous)
+ stream_putc(s, 1);
+ else
+ stream_putc(s, 0);
+
+ stream_putw_at(s, 0, stream_get_endp(s));
+ return zclient_send_message(zclient);
+ }
+
+ return ZCLIENT_SEND_SUCCESS;
+}
+
+enum zclient_send_status zclient_send_vrf_label(struct zclient *zclient,
+ vrf_id_t vrf_id, afi_t afi,
+ mpls_label_t label,
+ enum lsp_types_t ltype)
+{
+ struct stream *s;
+
+ s = zclient->obuf;
+ stream_reset(s);
+
+ zclient_create_header(s, ZEBRA_VRF_LABEL, vrf_id);
+ stream_putl(s, label);
+ stream_putc(s, afi);
+ stream_putc(s, ltype);
+ stream_putw_at(s, 0, stream_get_endp(s));
+ return zclient_send_message(zclient);
+}
+
+enum zclient_send_status zclient_send_localsid(struct zclient *zclient,
+ const struct in6_addr *sid, ifindex_t oif,
+ enum seg6local_action_t action,
+ const struct seg6local_context *context)
+{
+ struct prefix_ipv6 p = {};
+ struct zapi_route api = {};
+ struct zapi_nexthop *znh;
+
+ p.family = AF_INET6;
+ p.prefixlen = IPV6_MAX_BITLEN;
+ p.prefix = *sid;
+
+ api.vrf_id = VRF_DEFAULT;
+ api.type = zclient->redist_default;
+ api.instance = 0;
+ api.safi = SAFI_UNICAST;
+ memcpy(&api.prefix, &p, sizeof(p));
+
+ if (action == ZEBRA_SEG6_LOCAL_ACTION_UNSPEC)
+ return zclient_route_send(ZEBRA_ROUTE_DELETE, zclient, &api);
+
+ SET_FLAG(api.flags, ZEBRA_FLAG_ALLOW_RECURSION);
+ SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP);
+
+ znh = &api.nexthops[0];
+
+ memset(znh, 0, sizeof(*znh));
+
+ znh->type = NEXTHOP_TYPE_IFINDEX;
+ znh->ifindex = oif;
+ SET_FLAG(znh->flags, ZAPI_NEXTHOP_FLAG_SEG6LOCAL);
+ znh->seg6local_action = action;
+ memcpy(&znh->seg6local_ctx, context, sizeof(struct seg6local_context));
+
+ api.nexthop_num = 1;
+
+ return zclient_route_send(ZEBRA_ROUTE_ADD, zclient, &api);
+}
+
+/* Send register requests to zebra daemon for the information in a VRF. */
+void zclient_send_reg_requests(struct zclient *zclient, vrf_id_t vrf_id)
+{
+ int i;
+ afi_t afi;
+
+ /* If not connected to the zebra yet. */
+ if (zclient->sock < 0)
+ return;
+
+ if (zclient_debug)
+ zlog_debug("%s: send register messages for VRF %u", __func__,
+ vrf_id);
+
+ /* We need router-id information. */
+ zclient_send_router_id_update(zclient, ZEBRA_ROUTER_ID_ADD, AFI_IP,
+ vrf_id);
+
+ /* We need interface information. */
+ zebra_message_send(zclient, ZEBRA_INTERFACE_ADD, vrf_id);
+
+ /* Set unwanted redistribute route. */
+ for (afi = AFI_IP; afi < AFI_MAX; afi++)
+ vrf_bitmap_set(zclient->redist[afi][zclient->redist_default],
+ vrf_id);
+
+ /* Flush all redistribute request. */
+ if (vrf_id == VRF_DEFAULT) {
+ for (afi = AFI_IP; afi < AFI_MAX; afi++) {
+ for (i = 0; i < ZEBRA_ROUTE_MAX; i++) {
+ if (!zclient->mi_redist[afi][i].enabled)
+ continue;
+
+ struct listnode *node;
+ unsigned short *id;
+
+ for (ALL_LIST_ELEMENTS_RO(
+ zclient->mi_redist[afi][i]
+ .instances,
+ node, id))
+ if (!(i == zclient->redist_default
+ && *id == zclient->instance))
+ zebra_redistribute_send(
+ ZEBRA_REDISTRIBUTE_ADD,
+ zclient, afi, i, *id,
+ VRF_DEFAULT);
+ }
+ }
+ }
+
+ /* Resend all redistribute request. */
+ for (afi = AFI_IP; afi < AFI_MAX; afi++) {
+ for (i = 0; i < ZEBRA_ROUTE_MAX; i++)
+ if (i != zclient->redist_default
+ && vrf_bitmap_check(zclient->redist[afi][i],
+ vrf_id))
+ zebra_redistribute_send(ZEBRA_REDISTRIBUTE_ADD,
+ zclient, afi, i, 0,
+ vrf_id);
+
+ /* If default information is needed. */
+ if (vrf_bitmap_check(zclient->default_information[afi], vrf_id))
+ zebra_redistribute_default_send(
+ ZEBRA_REDISTRIBUTE_DEFAULT_ADD, zclient, afi,
+ vrf_id);
+ }
+}
+
+/* Send unregister requests to zebra daemon for the information in a VRF. */
+void zclient_send_dereg_requests(struct zclient *zclient, vrf_id_t vrf_id)
+{
+ int i;
+ afi_t afi;
+
+ /* If not connected to the zebra yet. */
+ if (zclient->sock < 0)
+ return;
+
+ if (zclient_debug)
+ zlog_debug("%s: send deregister messages for VRF %u", __func__,
+ vrf_id);
+
+ /* We need router-id information. */
+ zclient_send_router_id_update(zclient, ZEBRA_ROUTER_ID_DELETE, AFI_IP,
+ vrf_id);
+
+ zebra_message_send(zclient, ZEBRA_INTERFACE_DELETE, vrf_id);
+
+ /* Set unwanted redistribute route. */
+ for (afi = AFI_IP; afi < AFI_MAX; afi++)
+ vrf_bitmap_unset(zclient->redist[afi][zclient->redist_default],
+ vrf_id);
+
+ /* Flush all redistribute request. */
+ if (vrf_id == VRF_DEFAULT) {
+ for (afi = AFI_IP; afi < AFI_MAX; afi++) {
+ for (i = 0; i < ZEBRA_ROUTE_MAX; i++) {
+ if (!zclient->mi_redist[afi][i].enabled)
+ continue;
+
+ struct listnode *node;
+ unsigned short *id;
+
+ for (ALL_LIST_ELEMENTS_RO(
+ zclient->mi_redist[afi][i]
+ .instances,
+ node, id))
+ if (!(i == zclient->redist_default
+ && *id == zclient->instance))
+ zebra_redistribute_send(
+ ZEBRA_REDISTRIBUTE_DELETE,
+ zclient, afi, i, *id,
+ VRF_DEFAULT);
+ }
+ }
+ }
+
+ /* Flush all redistribute request. */
+ for (afi = AFI_IP; afi < AFI_MAX; afi++) {
+ for (i = 0; i < ZEBRA_ROUTE_MAX; i++)
+ if (i != zclient->redist_default
+ && vrf_bitmap_check(zclient->redist[afi][i],
+ vrf_id))
+ zebra_redistribute_send(
+ ZEBRA_REDISTRIBUTE_DELETE, zclient, afi,
+ i, 0, vrf_id);
+
+ /* If default information is needed. */
+ if (vrf_bitmap_check(zclient->default_information[afi], vrf_id))
+ zebra_redistribute_default_send(
+ ZEBRA_REDISTRIBUTE_DEFAULT_DELETE, zclient, afi,
+ vrf_id);
+ }
+}
+
+enum zclient_send_status
+zclient_send_router_id_update(struct zclient *zclient,
+ zebra_message_types_t type, afi_t afi,
+ vrf_id_t vrf_id)
+{
+ struct stream *s = zclient->obuf;
+ stream_reset(s);
+ zclient_create_header(s, type, vrf_id);
+ stream_putw(s, afi);
+ stream_putw_at(s, 0, stream_get_endp(s));
+ return zclient_send_message(zclient);
+}
+
+/* Send request to zebra daemon to start or stop RA. */
+enum zclient_send_status
+zclient_send_interface_radv_req(struct zclient *zclient, vrf_id_t vrf_id,
+ struct interface *ifp, int enable,
+ uint32_t ra_interval)
+{
+ struct stream *s;
+
+ /* If not connected to the zebra yet. */
+ if (zclient->sock < 0)
+ return ZCLIENT_SEND_FAILURE;
+
+ /* Form and send message. */
+ s = zclient->obuf;
+ stream_reset(s);
+
+ if (enable)
+ zclient_create_header(s, ZEBRA_INTERFACE_ENABLE_RADV, vrf_id);
+ else
+ zclient_create_header(s, ZEBRA_INTERFACE_DISABLE_RADV, vrf_id);
+
+ stream_putl(s, ifp->ifindex);
+ stream_putl(s, ra_interval);
+
+ stream_putw_at(s, 0, stream_get_endp(s));
+
+ return zclient_send_message(zclient);
+}
+
+enum zclient_send_status
+zclient_send_interface_protodown(struct zclient *zclient, vrf_id_t vrf_id,
+ struct interface *ifp, bool down)
+{
+ struct stream *s;
+
+ if (zclient->sock < 0)
+ return ZCLIENT_SEND_FAILURE;
+
+ s = zclient->obuf;
+ stream_reset(s);
+ zclient_create_header(s, ZEBRA_INTERFACE_SET_PROTODOWN, vrf_id);
+ stream_putl(s, ifp->ifindex);
+ stream_putc(s, !!down);
+ stream_putw_at(s, 0, stream_get_endp(s));
+ return zclient_send_message(zclient);
+}
+
+/* Make connection to zebra daemon. */
+int zclient_start(struct zclient *zclient)
+{
+ if (zclient_debug)
+ zlog_info("zclient_start is called");
+
+ /* If already connected to the zebra. */
+ if (zclient->sock >= 0)
+ return 0;
+
+ /* Check connect thread. */
+ if (zclient->t_connect)
+ return 0;
+
+ if (zclient_socket_connect(zclient) < 0) {
+ if (zclient_debug)
+ zlog_debug("zclient connection fail");
+ zclient->fail++;
+ zclient_event(ZCLIENT_CONNECT, zclient);
+ return -1;
+ }
+
+ if (set_nonblocking(zclient->sock) < 0)
+ flog_err(EC_LIB_ZAPI_SOCKET, "%s: set_nonblocking(%d) failed",
+ __func__, zclient->sock);
+
+ /* Clear fail count. */
+ zclient->fail = 0;
+ if (zclient_debug)
+ zlog_debug("zclient connect success with socket [%d]",
+ zclient->sock);
+
+ /* Create read thread. */
+ zclient_event(ZCLIENT_READ, zclient);
+
+ zclient_send_hello(zclient);
+
+ zebra_message_send(zclient, ZEBRA_INTERFACE_ADD, VRF_DEFAULT);
+
+ /* Inform the successful connection. */
+ if (zclient->zebra_connected)
+ (*zclient->zebra_connected)(zclient);
+
+ return 0;
+}
+
+/* Initialize zebra client. Argument redist_default is unwanted
+ redistribute route type. */
+void zclient_init(struct zclient *zclient, int redist_default,
+ unsigned short instance, struct zebra_privs_t *privs)
+{
+ int afi, i;
+
+ /* Set -1 to the default socket value. */
+ zclient->sock = -1;
+ zclient->privs = privs;
+
+ /* Clear redistribution flags. */
+ for (afi = AFI_IP; afi < AFI_MAX; afi++)
+ for (i = 0; i < ZEBRA_ROUTE_MAX; i++)
+ zclient->redist[afi][i] = vrf_bitmap_init();
+
+ /* Set unwanted redistribute route. bgpd does not need BGP route
+ redistribution. */
+ zclient->redist_default = redist_default;
+ zclient->instance = instance;
+ /* Pending: make afi(s) an arg. */
+ for (afi = AFI_IP; afi < AFI_MAX; afi++) {
+ redist_add_instance(&zclient->mi_redist[afi][redist_default],
+ instance);
+
+ /* Set default-information redistribute to zero. */
+ zclient->default_information[afi] = vrf_bitmap_init();
+ }
+
+ if (zclient_debug)
+ zlog_debug("scheduling zclient connection");
+
+ zclient_event(ZCLIENT_SCHEDULE, zclient);
+}
+
+/* This function is a wrapper function for calling zclient_start from
+ timer or event thread. */
+static void zclient_connect(struct thread *t)
+{
+ struct zclient *zclient;
+
+ zclient = THREAD_ARG(t);
+ zclient->t_connect = NULL;
+
+ if (zclient_debug)
+ zlog_debug("zclient_connect is called");
+
+ zclient_start(zclient);
+}
+
+enum zclient_send_status zclient_send_rnh(struct zclient *zclient, int command,
+ const struct prefix *p, safi_t safi,
+ bool connected, bool resolve_via_def,
+ vrf_id_t vrf_id)
+{
+ struct stream *s;
+
+ s = zclient->obuf;
+ stream_reset(s);
+ zclient_create_header(s, command, vrf_id);
+ stream_putc(s, (connected) ? 1 : 0);
+ stream_putc(s, (resolve_via_def) ? 1 : 0);
+ stream_putw(s, safi);
+ stream_putw(s, PREFIX_FAMILY(p));
+ stream_putc(s, p->prefixlen);
+ switch (PREFIX_FAMILY(p)) {
+ case AF_INET:
+ stream_put_in_addr(s, &p->u.prefix4);
+ break;
+ case AF_INET6:
+ stream_put(s, &(p->u.prefix6), 16);
+ break;
+ default:
+ break;
+ }
+ stream_putw_at(s, 0, stream_get_endp(s));
+
+ return zclient_send_message(zclient);
+}
+
+/*
+ * "xdr_encode"-like interface that allows daemon (client) to send
+ * a message to zebra server for a route that needs to be
+ * added/deleted to the kernel. Info about the route is specified
+ * by the caller in a struct zapi_route. zapi_route_encode() then writes
+ * the info down the zclient socket using the stream_* functions.
+ *
+ * The corresponding read ("xdr_decode") function on the server
+ * side is zapi_route_decode().
+ *
+ * If ZAPI_MESSAGE_DISTANCE is set, the distance value is written as a 1
+ * byte value.
+ *
+ * If ZAPI_MESSAGE_METRIC is set, the metric value is written as a 4
+ * byte value.
+ *
+ * If ZAPI_MESSAGE_TAG is set, the tag value is written as a 4 byte value
+ *
+ * If ZAPI_MESSAGE_MTU is set, the mtu value is written as a 4 byte value
+ *
+ * XXX: No attention paid to alignment.
+ */
+enum zclient_send_status
+zclient_route_send(uint8_t cmd, struct zclient *zclient, struct zapi_route *api)
+{
+ if (zapi_route_encode(cmd, zclient->obuf, api) < 0)
+ return ZCLIENT_SEND_FAILURE;
+ return zclient_send_message(zclient);
+}
+
+static int zapi_nexthop_labels_cmp(const struct zapi_nexthop *next1,
+ const struct zapi_nexthop *next2)
+{
+ if (next1->label_num > next2->label_num)
+ return 1;
+
+ if (next1->label_num < next2->label_num)
+ return -1;
+
+ return memcmp(next1->labels, next2->labels, next1->label_num);
+}
+
+static int zapi_nexthop_srv6_cmp(const struct zapi_nexthop *next1,
+ const struct zapi_nexthop *next2)
+{
+ int ret = 0;
+
+ ret = memcmp(&next1->seg6_segs, &next2->seg6_segs,
+ sizeof(struct in6_addr));
+ if (ret != 0)
+ return ret;
+
+ if (next1->seg6local_action > next2->seg6local_action)
+ return 1;
+
+ if (next1->seg6local_action < next2->seg6local_action)
+ return -1;
+
+ return memcmp(&next1->seg6local_ctx, &next2->seg6local_ctx,
+ sizeof(struct seg6local_context));
+}
+
+static int zapi_nexthop_cmp_no_labels(const struct zapi_nexthop *next1,
+ const struct zapi_nexthop *next2)
+{
+ int ret = 0;
+
+ if (next1->vrf_id < next2->vrf_id)
+ return -1;
+
+ if (next1->vrf_id > next2->vrf_id)
+ return 1;
+
+ if (next1->type < next2->type)
+ return -1;
+
+ if (next1->type > next2->type)
+ return 1;
+
+ if (next1->weight < next2->weight)
+ return -1;
+
+ if (next1->weight > next2->weight)
+ return 1;
+
+ switch (next1->type) {
+ case NEXTHOP_TYPE_IPV4:
+ case NEXTHOP_TYPE_IPV6:
+ ret = nexthop_g_addr_cmp(next1->type, &next1->gate,
+ &next2->gate);
+ if (ret != 0)
+ return ret;
+ break;
+ case NEXTHOP_TYPE_IPV4_IFINDEX:
+ case NEXTHOP_TYPE_IPV6_IFINDEX:
+ ret = nexthop_g_addr_cmp(next1->type, &next1->gate,
+ &next2->gate);
+ if (ret != 0)
+ return ret;
+ /* Intentional Fall-Through */
+ case NEXTHOP_TYPE_IFINDEX:
+ if (next1->ifindex < next2->ifindex)
+ return -1;
+
+ if (next1->ifindex > next2->ifindex)
+ return 1;
+ break;
+ case NEXTHOP_TYPE_BLACKHOLE:
+ if (next1->bh_type < next2->bh_type)
+ return -1;
+
+ if (next1->bh_type > next2->bh_type)
+ return 1;
+ break;
+ }
+
+ if (next1->srte_color < next2->srte_color)
+ return -1;
+ if (next1->srte_color > next2->srte_color)
+ return 1;
+
+ if (CHECK_FLAG(next1->flags, NEXTHOP_FLAG_HAS_BACKUP) ||
+ CHECK_FLAG(next2->flags, NEXTHOP_FLAG_HAS_BACKUP)) {
+
+ if (!CHECK_FLAG(next1->flags, NEXTHOP_FLAG_HAS_BACKUP) &&
+ CHECK_FLAG(next2->flags, NEXTHOP_FLAG_HAS_BACKUP))
+ return -1;
+
+ if (CHECK_FLAG(next1->flags, NEXTHOP_FLAG_HAS_BACKUP) &&
+ !CHECK_FLAG(next2->flags, NEXTHOP_FLAG_HAS_BACKUP))
+ return 1;
+
+ if (next1->backup_num > 0 || next2->backup_num > 0) {
+
+ if (next1->backup_num < next2->backup_num)
+ return -1;
+
+ if (next1->backup_num > next2->backup_num)
+ return 1;
+
+ ret = memcmp(next1->backup_idx,
+ next2->backup_idx, next1->backup_num);
+ if (ret != 0)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int zapi_nexthop_cmp(const void *item1, const void *item2)
+{
+ int ret = 0;
+
+ const struct zapi_nexthop *next1 = item1;
+ const struct zapi_nexthop *next2 = item2;
+
+ ret = zapi_nexthop_cmp_no_labels(next1, next2);
+ if (ret != 0)
+ return ret;
+
+ ret = zapi_nexthop_labels_cmp(next1, next2);
+ if (ret != 0)
+ return ret;
+
+ ret = zapi_nexthop_srv6_cmp(next1, next2);
+
+ return ret;
+}
+
+static void zapi_nexthop_group_sort(struct zapi_nexthop *nh_grp,
+ uint16_t nexthop_num)
+{
+ qsort(nh_grp, nexthop_num, sizeof(struct zapi_nexthop),
+ &zapi_nexthop_cmp);
+}
+
+/*
+ * Encode a single zapi nexthop
+ */
+int zapi_nexthop_encode(struct stream *s, const struct zapi_nexthop *api_nh,
+ uint32_t api_flags, uint32_t api_message)
+{
+ int i, ret = 0;
+ int nh_flags = api_nh->flags;
+
+ stream_putl(s, api_nh->vrf_id);
+ stream_putc(s, api_nh->type);
+
+ /* If needed, set 'labelled nexthop' flag */
+ if (api_nh->label_num > 0) {
+ SET_FLAG(nh_flags, ZAPI_NEXTHOP_FLAG_LABEL);
+
+ /* Validate label count */
+ if (api_nh->label_num > MPLS_MAX_LABELS) {
+ ret = -1;
+ goto done;
+ }
+ }
+
+ /* If present, set 'weight' flag before encoding flags */
+ if (api_nh->weight)
+ SET_FLAG(nh_flags, ZAPI_NEXTHOP_FLAG_WEIGHT);
+
+ /* Note that we're only encoding a single octet */
+ stream_putc(s, nh_flags);
+
+ switch (api_nh->type) {
+ case NEXTHOP_TYPE_BLACKHOLE:
+ stream_putc(s, api_nh->bh_type);
+ break;
+ case NEXTHOP_TYPE_IPV4:
+ case NEXTHOP_TYPE_IPV4_IFINDEX:
+ stream_put_in_addr(s, &api_nh->gate.ipv4);
+ stream_putl(s, api_nh->ifindex);
+ break;
+ case NEXTHOP_TYPE_IFINDEX:
+ stream_putl(s, api_nh->ifindex);
+ break;
+ case NEXTHOP_TYPE_IPV6:
+ case NEXTHOP_TYPE_IPV6_IFINDEX:
+ stream_write(s, (uint8_t *)&api_nh->gate.ipv6,
+ 16);
+ stream_putl(s, api_nh->ifindex);
+ break;
+ }
+
+ /* We only encode labels if we have >0 - we use
+ * the per-nexthop flag above to signal that the count
+ * is present in the payload.
+ */
+ if (api_nh->label_num > 0) {
+ stream_putc(s, api_nh->label_num);
+ stream_put(s, &api_nh->labels[0],
+ api_nh->label_num * sizeof(mpls_label_t));
+ }
+
+ if (api_nh->weight)
+ stream_putl(s, api_nh->weight);
+
+ /* Router MAC for EVPN routes. */
+ if (CHECK_FLAG(nh_flags, ZAPI_NEXTHOP_FLAG_EVPN))
+ stream_put(s, &(api_nh->rmac),
+ sizeof(struct ethaddr));
+
+ /* Color for Segment Routing TE. */
+ if (CHECK_FLAG(api_message, ZAPI_MESSAGE_SRTE))
+ stream_putl(s, api_nh->srte_color);
+
+ /* Index of backup nexthop */
+ if (CHECK_FLAG(nh_flags, ZAPI_NEXTHOP_FLAG_HAS_BACKUP)) {
+ /* Validate backup count */
+ if (api_nh->backup_num > NEXTHOP_MAX_BACKUPS) {
+ ret = -1;
+ goto done;
+ }
+
+ stream_putc(s, api_nh->backup_num);
+ for (i = 0; i < api_nh->backup_num; i++)
+ stream_putc(s, api_nh->backup_idx[i]);
+ }
+
+ if (CHECK_FLAG(nh_flags, ZAPI_NEXTHOP_FLAG_SEG6LOCAL)) {
+ stream_putl(s, api_nh->seg6local_action);
+ stream_write(s, &api_nh->seg6local_ctx,
+ sizeof(struct seg6local_context));
+ }
+
+ if (CHECK_FLAG(nh_flags, ZAPI_NEXTHOP_FLAG_SEG6))
+ stream_write(s, &api_nh->seg6_segs,
+ sizeof(struct in6_addr));
+
+done:
+ return ret;
+}
+
+int zapi_srv6_locator_chunk_encode(struct stream *s,
+ const struct srv6_locator_chunk *c)
+{
+ stream_putw(s, strlen(c->locator_name));
+ stream_put(s, c->locator_name, strlen(c->locator_name));
+ stream_putw(s, c->prefix.prefixlen);
+ stream_put(s, &c->prefix.prefix, sizeof(c->prefix.prefix));
+ stream_putc(s, c->block_bits_length);
+ stream_putc(s, c->node_bits_length);
+ stream_putc(s, c->function_bits_length);
+ stream_putc(s, c->argument_bits_length);
+ return 0;
+}
+
+int zapi_srv6_locator_chunk_decode(struct stream *s,
+ struct srv6_locator_chunk *c)
+{
+ uint16_t len = 0;
+
+ c->prefix.family = AF_INET6;
+
+ STREAM_GETW(s, len);
+ if (len > SRV6_LOCNAME_SIZE)
+ goto stream_failure;
+
+ STREAM_GET(c->locator_name, s, len);
+ STREAM_GETW(s, c->prefix.prefixlen);
+ STREAM_GET(&c->prefix.prefix, s, sizeof(c->prefix.prefix));
+ STREAM_GETC(s, c->block_bits_length);
+ STREAM_GETC(s, c->node_bits_length);
+ STREAM_GETC(s, c->function_bits_length);
+ STREAM_GETC(s, c->argument_bits_length);
+ return 0;
+
+stream_failure:
+ return -1;
+}
+
+int zapi_srv6_locator_encode(struct stream *s, const struct srv6_locator *l)
+{
+ stream_putw(s, strlen(l->name));
+ stream_put(s, l->name, strlen(l->name));
+ stream_putw(s, l->prefix.prefixlen);
+ stream_put(s, &l->prefix.prefix, sizeof(l->prefix.prefix));
+ return 0;
+}
+
+int zapi_srv6_locator_decode(struct stream *s, struct srv6_locator *l)
+{
+ uint16_t len = 0;
+
+ STREAM_GETW(s, len);
+ if (len > SRV6_LOCNAME_SIZE)
+ goto stream_failure;
+
+ STREAM_GET(l->name, s, len);
+ STREAM_GETW(s, l->prefix.prefixlen);
+ STREAM_GET(&l->prefix.prefix, s, sizeof(l->prefix.prefix));
+ l->prefix.family = AF_INET6;
+ return 0;
+
+stream_failure:
+ return -1;
+}
+
+static int zapi_nhg_encode(struct stream *s, int cmd, struct zapi_nhg *api_nhg)
+{
+ int i;
+
+ if (cmd != ZEBRA_NHG_DEL && cmd != ZEBRA_NHG_ADD) {
+ flog_err(EC_LIB_ZAPI_ENCODE,
+ "%s: Specified zapi NHG command (%d) doesn't exist",
+ __func__, cmd);
+ return -1;
+ }
+
+ if (api_nhg->nexthop_num >= MULTIPATH_NUM ||
+ api_nhg->backup_nexthop_num >= MULTIPATH_NUM) {
+ flog_err(EC_LIB_ZAPI_ENCODE,
+ "%s: zapi NHG encode with invalid input", __func__);
+ return -1;
+ }
+
+ stream_reset(s);
+ zclient_create_header(s, cmd, VRF_DEFAULT);
+
+ stream_putw(s, api_nhg->proto);
+ stream_putl(s, api_nhg->id);
+
+ if (cmd == ZEBRA_NHG_ADD) {
+ /* Nexthops */
+ zapi_nexthop_group_sort(api_nhg->nexthops,
+ api_nhg->nexthop_num);
+
+ stream_putw(s, api_nhg->nexthop_num);
+
+ for (i = 0; i < api_nhg->nexthop_num; i++)
+ zapi_nexthop_encode(s, &api_nhg->nexthops[i], 0, 0);
+
+ /* Backup nexthops */
+ stream_putw(s, api_nhg->backup_nexthop_num);
+
+ for (i = 0; i < api_nhg->backup_nexthop_num; i++)
+ zapi_nexthop_encode(s, &api_nhg->backup_nexthops[i], 0,
+ 0);
+ }
+
+ stream_putw_at(s, 0, stream_get_endp(s));
+
+ return 0;
+}
+
+enum zclient_send_status zclient_nhg_send(struct zclient *zclient, int cmd,
+ struct zapi_nhg *api_nhg)
+{
+ api_nhg->proto = zclient->redist_default;
+
+ if (zapi_nhg_encode(zclient->obuf, cmd, api_nhg))
+ return -1;
+
+ return zclient_send_message(zclient);
+}
+
+int zapi_route_encode(uint8_t cmd, struct stream *s, struct zapi_route *api)
+{
+ struct zapi_nexthop *api_nh;
+ int i;
+ int psize;
+
+ stream_reset(s);
+ zclient_create_header(s, cmd, api->vrf_id);
+
+ if (api->type >= ZEBRA_ROUTE_MAX) {
+ flog_err(EC_LIB_ZAPI_ENCODE,
+ "%s: Specified route type (%u) is not a legal value",
+ __func__, api->type);
+ return -1;
+ }
+ stream_putc(s, api->type);
+
+ stream_putw(s, api->instance);
+ stream_putl(s, api->flags);
+ stream_putl(s, api->message);
+
+ if (api->safi < SAFI_UNICAST || api->safi >= SAFI_MAX) {
+ flog_err(EC_LIB_ZAPI_ENCODE,
+ "%s: Specified route SAFI (%u) is not a legal value",
+ __func__, api->safi);
+ return -1;
+ }
+ stream_putc(s, api->safi);
+
+ /* Put prefix information. */
+ stream_putc(s, api->prefix.family);
+ psize = PSIZE(api->prefix.prefixlen);
+ stream_putc(s, api->prefix.prefixlen);
+ stream_write(s, &api->prefix.u.prefix, psize);
+
+ if (CHECK_FLAG(api->message, ZAPI_MESSAGE_SRCPFX)) {
+ psize = PSIZE(api->src_prefix.prefixlen);
+ stream_putc(s, api->src_prefix.prefixlen);
+ stream_write(s, (uint8_t *)&api->src_prefix.prefix, psize);
+ }
+
+ if (CHECK_FLAG(api->message, ZAPI_MESSAGE_NHG))
+ stream_putl(s, api->nhgid);
+
+ /* Nexthops. */
+ if (CHECK_FLAG(api->message, ZAPI_MESSAGE_NEXTHOP)) {
+ /* limit the number of nexthops if necessary */
+ if (api->nexthop_num > MULTIPATH_NUM) {
+ flog_err(
+ EC_LIB_ZAPI_ENCODE,
+ "%s: prefix %pFX: can't encode %u nexthops (maximum is %u)",
+ __func__, &api->prefix, api->nexthop_num,
+ MULTIPATH_NUM);
+ return -1;
+ }
+
+ /* We canonicalize the nexthops by sorting them; this allows
+ * zebra to resolve the list of nexthops to a nexthop-group
+ * more efficiently.
+ */
+ zapi_nexthop_group_sort(api->nexthops, api->nexthop_num);
+
+ stream_putw(s, api->nexthop_num);
+
+ for (i = 0; i < api->nexthop_num; i++) {
+ api_nh = &api->nexthops[i];
+
+ /* MPLS labels for BGP-LU or Segment Routing */
+ if (api_nh->label_num > MPLS_MAX_LABELS) {
+ flog_err(
+ EC_LIB_ZAPI_ENCODE,
+ "%s: prefix %pFX: can't encode %u labels (maximum is %u)",
+ __func__, &api->prefix,
+ api_nh->label_num, MPLS_MAX_LABELS);
+ return -1;
+ }
+
+ if (zapi_nexthop_encode(s, api_nh, api->flags,
+ api->message)
+ != 0)
+ return -1;
+ }
+ }
+
+ /* Backup nexthops */
+ if (CHECK_FLAG(api->message, ZAPI_MESSAGE_BACKUP_NEXTHOPS)) {
+ /* limit the number of nexthops if necessary */
+ if (api->backup_nexthop_num > MULTIPATH_NUM) {
+ flog_err(
+ EC_LIB_ZAPI_ENCODE,
+ "%s: prefix %pFX: can't encode %u backup nexthops (maximum is %u)",
+ __func__, &api->prefix, api->backup_nexthop_num,
+ MULTIPATH_NUM);
+ return -1;
+ }
+
+ /* Note that we do not sort the list of backup nexthops -
+ * this list is treated as an array and indexed by each
+ * primary nexthop that is associated with a backup.
+ */
+
+ stream_putw(s, api->backup_nexthop_num);
+
+ for (i = 0; i < api->backup_nexthop_num; i++) {
+ api_nh = &api->backup_nexthops[i];
+
+ /* MPLS labels for BGP-LU or Segment Routing */
+ if (api_nh->label_num > MPLS_MAX_LABELS) {
+ flog_err(
+ EC_LIB_ZAPI_ENCODE,
+ "%s: prefix %pFX: backup: can't encode %u labels (maximum is %u)",
+ __func__, &api->prefix,
+ api_nh->label_num, MPLS_MAX_LABELS);
+ return -1;
+ }
+
+ if (zapi_nexthop_encode(s, api_nh, api->flags,
+ api->message)
+ != 0)
+ return -1;
+ }
+ }
+
+ /* Attributes. */
+ if (CHECK_FLAG(api->message, ZAPI_MESSAGE_DISTANCE))
+ stream_putc(s, api->distance);
+ if (CHECK_FLAG(api->message, ZAPI_MESSAGE_METRIC))
+ stream_putl(s, api->metric);
+ if (CHECK_FLAG(api->message, ZAPI_MESSAGE_TAG))
+ stream_putl(s, api->tag);
+ if (CHECK_FLAG(api->message, ZAPI_MESSAGE_MTU))
+ stream_putl(s, api->mtu);
+ if (CHECK_FLAG(api->message, ZAPI_MESSAGE_TABLEID))
+ stream_putl(s, api->tableid);
+
+ if (CHECK_FLAG(api->message, ZAPI_MESSAGE_OPAQUE)) {
+ if (api->opaque.length > ZAPI_MESSAGE_OPAQUE_LENGTH) {
+ flog_err(
+ EC_LIB_ZAPI_ENCODE,
+ "%s: opaque length %u is greater than allowed value",
+ __func__, api->opaque.length);
+ return -1;
+ }
+
+ stream_putw(s, api->opaque.length);
+ stream_write(s, api->opaque.data, api->opaque.length);
+ }
+ /* Put length at the first point of the stream. */
+ stream_putw_at(s, 0, stream_get_endp(s));
+
+ return 0;
+}
+
+/*
+ * Decode a single zapi nexthop object
+ */
+int zapi_nexthop_decode(struct stream *s, struct zapi_nexthop *api_nh,
+ uint32_t api_flags, uint32_t api_message)
+{
+ int i, ret = -1;
+
+ STREAM_GETL(s, api_nh->vrf_id);
+ STREAM_GETC(s, api_nh->type);
+
+ /* Note that we're only using a single octet of flags */
+ STREAM_GETC(s, api_nh->flags);
+
+ switch (api_nh->type) {
+ case NEXTHOP_TYPE_BLACKHOLE:
+ STREAM_GETC(s, api_nh->bh_type);
+ break;
+ case NEXTHOP_TYPE_IPV4:
+ case NEXTHOP_TYPE_IPV4_IFINDEX:
+ STREAM_GET(&api_nh->gate.ipv4.s_addr, s,
+ IPV4_MAX_BYTELEN);
+ STREAM_GETL(s, api_nh->ifindex);
+ break;
+ case NEXTHOP_TYPE_IFINDEX:
+ STREAM_GETL(s, api_nh->ifindex);
+ break;
+ case NEXTHOP_TYPE_IPV6:
+ case NEXTHOP_TYPE_IPV6_IFINDEX:
+ STREAM_GET(&api_nh->gate.ipv6, s, 16);
+ STREAM_GETL(s, api_nh->ifindex);
+ break;
+ }
+
+ /* MPLS labels for BGP-LU or Segment Routing */
+ if (CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_LABEL)) {
+ STREAM_GETC(s, api_nh->label_num);
+ if (api_nh->label_num > MPLS_MAX_LABELS) {
+ flog_err(
+ EC_LIB_ZAPI_ENCODE,
+ "%s: invalid number of MPLS labels (%u)",
+ __func__, api_nh->label_num);
+ return -1;
+ }
+
+ STREAM_GET(&api_nh->labels[0], s,
+ api_nh->label_num * sizeof(mpls_label_t));
+ }
+
+ if (CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_WEIGHT))
+ STREAM_GETL(s, api_nh->weight);
+
+ /* Router MAC for EVPN routes. */
+ if (CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_EVPN))
+ STREAM_GET(&(api_nh->rmac), s,
+ sizeof(struct ethaddr));
+
+ /* Color for Segment Routing TE. */
+ if (CHECK_FLAG(api_message, ZAPI_MESSAGE_SRTE))
+ STREAM_GETL(s, api_nh->srte_color);
+
+ /* Backup nexthop index */
+ if (CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_HAS_BACKUP)) {
+ STREAM_GETC(s, api_nh->backup_num);
+
+ if (api_nh->backup_num > NEXTHOP_MAX_BACKUPS)
+ return -1;
+
+ for (i = 0; i < api_nh->backup_num; i++)
+ STREAM_GETC(s, api_nh->backup_idx[i]);
+ }
+
+ if (CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_SEG6LOCAL)) {
+ STREAM_GETL(s, api_nh->seg6local_action);
+ STREAM_GET(&api_nh->seg6local_ctx, s,
+ sizeof(struct seg6local_context));
+ }
+
+ if (CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_SEG6))
+ STREAM_GET(&api_nh->seg6_segs, s,
+ sizeof(struct in6_addr));
+
+ /* Success */
+ ret = 0;
+
+stream_failure:
+
+ return ret;
+}
+
+int zapi_route_decode(struct stream *s, struct zapi_route *api)
+{
+ struct zapi_nexthop *api_nh;
+ int i;
+
+ memset(api, 0, sizeof(*api));
+
+ /* Type, flags, message. */
+ STREAM_GETC(s, api->type);
+ if (api->type >= ZEBRA_ROUTE_MAX) {
+ flog_err(EC_LIB_ZAPI_ENCODE,
+ "%s: Specified route type: %d is not a legal value",
+ __func__, api->type);
+ return -1;
+ }
+
+ STREAM_GETW(s, api->instance);
+ STREAM_GETL(s, api->flags);
+ STREAM_GETL(s, api->message);
+ STREAM_GETC(s, api->safi);
+ if (api->safi < SAFI_UNICAST || api->safi >= SAFI_MAX) {
+ flog_err(EC_LIB_ZAPI_ENCODE,
+ "%s: Specified route SAFI (%u) is not a legal value",
+ __func__, api->safi);
+ return -1;
+ }
+
+ /* Prefix. */
+ STREAM_GETC(s, api->prefix.family);
+ STREAM_GETC(s, api->prefix.prefixlen);
+ switch (api->prefix.family) {
+ case AF_INET:
+ if (api->prefix.prefixlen > IPV4_MAX_BITLEN) {
+ flog_err(
+ EC_LIB_ZAPI_ENCODE,
+ "%s: V4 prefixlen is %d which should not be more than 32",
+ __func__, api->prefix.prefixlen);
+ return -1;
+ }
+ break;
+ case AF_INET6:
+ if (api->prefix.prefixlen > IPV6_MAX_BITLEN) {
+ flog_err(
+ EC_LIB_ZAPI_ENCODE,
+ "%s: v6 prefixlen is %d which should not be more than 128",
+ __func__, api->prefix.prefixlen);
+ return -1;
+ }
+ break;
+ default:
+ flog_err(EC_LIB_ZAPI_ENCODE,
+ "%s: Specified family %d is not v4 or v6", __func__,
+ api->prefix.family);
+ return -1;
+ }
+ STREAM_GET(&api->prefix.u.prefix, s, PSIZE(api->prefix.prefixlen));
+
+ if (CHECK_FLAG(api->message, ZAPI_MESSAGE_SRCPFX)) {
+ api->src_prefix.family = AF_INET6;
+ STREAM_GETC(s, api->src_prefix.prefixlen);
+ if (api->src_prefix.prefixlen > IPV6_MAX_BITLEN) {
+ flog_err(
+ EC_LIB_ZAPI_ENCODE,
+ "%s: SRC Prefix prefixlen received: %d is too large",
+ __func__, api->src_prefix.prefixlen);
+ return -1;
+ }
+ STREAM_GET(&api->src_prefix.prefix, s,
+ PSIZE(api->src_prefix.prefixlen));
+
+ if (api->prefix.family != AF_INET6
+ || api->src_prefix.prefixlen == 0) {
+ flog_err(
+ EC_LIB_ZAPI_ENCODE,
+ "%s: SRC prefix specified in some manner that makes no sense",
+ __func__);
+ return -1;
+ }
+ }
+
+ if (CHECK_FLAG(api->message, ZAPI_MESSAGE_NHG))
+ STREAM_GETL(s, api->nhgid);
+
+ /* Nexthops. */
+ if (CHECK_FLAG(api->message, ZAPI_MESSAGE_NEXTHOP)) {
+ STREAM_GETW(s, api->nexthop_num);
+ if (api->nexthop_num > MULTIPATH_NUM) {
+ flog_err(EC_LIB_ZAPI_ENCODE,
+ "%s: invalid number of nexthops (%u)",
+ __func__, api->nexthop_num);
+ return -1;
+ }
+
+ for (i = 0; i < api->nexthop_num; i++) {
+ api_nh = &api->nexthops[i];
+
+ if (zapi_nexthop_decode(s, api_nh, api->flags,
+ api->message)
+ != 0)
+ return -1;
+ }
+ }
+
+ /* Backup nexthops. */
+ if (CHECK_FLAG(api->message, ZAPI_MESSAGE_BACKUP_NEXTHOPS)) {
+ STREAM_GETW(s, api->backup_nexthop_num);
+ if (api->backup_nexthop_num > MULTIPATH_NUM) {
+ flog_err(EC_LIB_ZAPI_ENCODE,
+ "%s: invalid number of backup nexthops (%u)",
+ __func__, api->backup_nexthop_num);
+ return -1;
+ }
+
+ for (i = 0; i < api->backup_nexthop_num; i++) {
+ api_nh = &api->backup_nexthops[i];
+
+ if (zapi_nexthop_decode(s, api_nh, api->flags,
+ api->message)
+ != 0)
+ return -1;
+ }
+ }
+
+ /* Attributes. */
+ if (CHECK_FLAG(api->message, ZAPI_MESSAGE_DISTANCE))
+ STREAM_GETC(s, api->distance);
+ if (CHECK_FLAG(api->message, ZAPI_MESSAGE_METRIC))
+ STREAM_GETL(s, api->metric);
+ if (CHECK_FLAG(api->message, ZAPI_MESSAGE_TAG))
+ STREAM_GETL(s, api->tag);
+ if (CHECK_FLAG(api->message, ZAPI_MESSAGE_MTU))
+ STREAM_GETL(s, api->mtu);
+ if (CHECK_FLAG(api->message, ZAPI_MESSAGE_TABLEID))
+ STREAM_GETL(s, api->tableid);
+
+ if (CHECK_FLAG(api->message, ZAPI_MESSAGE_OPAQUE)) {
+ STREAM_GETW(s, api->opaque.length);
+ if (api->opaque.length > ZAPI_MESSAGE_OPAQUE_LENGTH) {
+ flog_err(
+ EC_LIB_ZAPI_ENCODE,
+ "%s: opaque length %u is greater than allowed value",
+ __func__, api->opaque.length);
+ return -1;
+ }
+
+ STREAM_GET(api->opaque.data, s, api->opaque.length);
+ }
+
+ return 0;
+stream_failure:
+ return -1;
+}
+
+static void zapi_encode_prefix(struct stream *s, struct prefix *p,
+ uint8_t family)
+{
+ struct prefix any;
+
+ if (!p) {
+ memset(&any, 0, sizeof(any));
+ any.family = family;
+ p = &any;
+ }
+
+ stream_putc(s, p->family);
+ stream_putc(s, p->prefixlen);
+ stream_put(s, &p->u.prefix, prefix_blen(p));
+}
+
+int zapi_pbr_rule_encode(uint8_t cmd, struct stream *s, struct pbr_rule *zrule)
+{
+ stream_reset(s);
+ zclient_create_header(s, cmd, zrule->vrf_id);
+
+ /*
+ * We are sending one item at a time at the moment
+ */
+ stream_putl(s, 1);
+
+ stream_putl(s, zrule->seq);
+ stream_putl(s, zrule->priority);
+ stream_putl(s, zrule->unique);
+
+ zapi_encode_prefix(s, &(zrule->filter.src_ip),
+ zrule->filter.src_ip.family);
+ stream_putw(s, zrule->filter.src_port); /* src port */
+ zapi_encode_prefix(s, &(zrule->filter.dst_ip),
+ zrule->filter.src_ip.family);
+ stream_putw(s, zrule->filter.dst_port); /* dst port */
+ stream_putw(s, zrule->filter.fwmark); /* fwmark */
+
+ stream_putl(s, zrule->action.table);
+ stream_put(s, zrule->ifname, INTERFACE_NAMSIZ);
+
+ /* Put length at the first point of the stream. */
+ stream_putw_at(s, 0, stream_get_endp(s));
+
+ return 0;
+}
+
+bool zapi_nhg_notify_decode(struct stream *s, uint32_t *id,
+ enum zapi_nhg_notify_owner *note)
+{
+ uint32_t read_id;
+
+ STREAM_GET(note, s, sizeof(*note));
+ STREAM_GETL(s, read_id);
+
+ *id = read_id;
+
+ return true;
+
+stream_failure:
+ return false;
+}
+
+bool zapi_route_notify_decode(struct stream *s, struct prefix *p,
+ uint32_t *tableid,
+ enum zapi_route_notify_owner *note,
+ afi_t *afi, safi_t *safi)
+{
+ uint32_t t;
+ afi_t afi_val;
+ safi_t safi_val;
+
+ STREAM_GET(note, s, sizeof(*note));
+
+ STREAM_GETC(s, p->family);
+ STREAM_GETC(s, p->prefixlen);
+ STREAM_GET(&p->u.prefix, s, prefix_blen(p));
+ STREAM_GETL(s, t);
+ STREAM_GETC(s, afi_val);
+ STREAM_GETC(s, safi_val);
+
+ *tableid = t;
+
+ if (afi)
+ *afi = afi_val;
+ if (safi)
+ *safi = safi_val;
+
+ return true;
+
+stream_failure:
+ return false;
+}
+
+bool zapi_rule_notify_decode(struct stream *s, uint32_t *seqno,
+ uint32_t *priority, uint32_t *unique, char *ifname,
+ enum zapi_rule_notify_owner *note)
+{
+ uint32_t prio, seq, uni;
+
+ STREAM_GET(note, s, sizeof(*note));
+
+ STREAM_GETL(s, seq);
+ STREAM_GETL(s, prio);
+ STREAM_GETL(s, uni);
+ STREAM_GET(ifname, s, INTERFACE_NAMSIZ);
+
+ if (zclient_debug)
+ zlog_debug("%s: %u %u %u %s", __func__, seq, prio, uni, ifname);
+ *seqno = seq;
+ *priority = prio;
+ *unique = uni;
+
+ return true;
+
+stream_failure:
+ return false;
+}
+
+bool zapi_ipset_notify_decode(struct stream *s, uint32_t *unique,
+ enum zapi_ipset_notify_owner *note)
+{
+ uint32_t uni;
+ uint16_t notew;
+
+ STREAM_GETW(s, notew);
+
+ STREAM_GETL(s, uni);
+
+ if (zclient_debug)
+ zlog_debug("%s: %u", __func__, uni);
+ *unique = uni;
+ *note = (enum zapi_ipset_notify_owner)notew;
+ return true;
+
+stream_failure:
+ return false;
+}
+
+bool zapi_ipset_entry_notify_decode(struct stream *s, uint32_t *unique,
+ char *ipset_name,
+ enum zapi_ipset_entry_notify_owner *note)
+{
+ uint32_t uni;
+ uint16_t notew;
+
+ STREAM_GETW(s, notew);
+
+ STREAM_GETL(s, uni);
+
+ STREAM_GET(ipset_name, s, ZEBRA_IPSET_NAME_SIZE);
+
+ if (zclient_debug)
+ zlog_debug("%s: %u", __func__, uni);
+ *unique = uni;
+ *note = (enum zapi_ipset_entry_notify_owner)notew;
+
+ return true;
+
+stream_failure:
+ return false;
+}
+
+bool zapi_iptable_notify_decode(struct stream *s,
+ uint32_t *unique,
+ enum zapi_iptable_notify_owner *note)
+{
+ uint32_t uni;
+ uint16_t notew;
+
+ STREAM_GETW(s, notew);
+
+ STREAM_GETL(s, uni);
+
+ if (zclient_debug)
+ zlog_debug("%s: %u", __func__, uni);
+ *unique = uni;
+ *note = (enum zapi_iptable_notify_owner)notew;
+
+ return true;
+
+stream_failure:
+ return false;
+}
+
+struct nexthop *nexthop_from_zapi_nexthop(const struct zapi_nexthop *znh)
+{
+ struct nexthop *n = nexthop_new();
+
+ n->type = znh->type;
+ n->vrf_id = znh->vrf_id;
+ n->ifindex = znh->ifindex;
+ n->gate = znh->gate;
+ n->srte_color = znh->srte_color;
+
+ /*
+ * This function currently handles labels
+ */
+ if (znh->label_num) {
+ nexthop_add_labels(n, ZEBRA_LSP_NONE, znh->label_num,
+ znh->labels);
+ }
+
+ if (CHECK_FLAG(znh->flags, ZAPI_NEXTHOP_FLAG_HAS_BACKUP)) {
+ SET_FLAG(n->flags, NEXTHOP_FLAG_HAS_BACKUP);
+ n->backup_num = znh->backup_num;
+ memcpy(n->backup_idx, znh->backup_idx, n->backup_num);
+ }
+
+ if (znh->seg6local_action != ZEBRA_SEG6_LOCAL_ACTION_UNSPEC)
+ nexthop_add_srv6_seg6local(n, znh->seg6local_action,
+ &znh->seg6local_ctx);
+
+ if (!sid_zero(&znh->seg6_segs))
+ nexthop_add_srv6_seg6(n, &znh->seg6_segs);
+
+ return n;
+}
+
+/*
+ * Convert nexthop to zapi nexthop
+ */
+int zapi_nexthop_from_nexthop(struct zapi_nexthop *znh,
+ const struct nexthop *nh)
+{
+ int i;
+
+ memset(znh, 0, sizeof(*znh));
+
+ znh->type = nh->type;
+ znh->vrf_id = nh->vrf_id;
+ znh->weight = nh->weight;
+ znh->ifindex = nh->ifindex;
+ znh->gate = nh->gate;
+
+ if (CHECK_FLAG(nh->flags, NEXTHOP_FLAG_ONLINK))
+ SET_FLAG(znh->flags, ZAPI_NEXTHOP_FLAG_ONLINK);
+
+ if (CHECK_FLAG(nh->flags, NEXTHOP_FLAG_EVPN))
+ SET_FLAG(znh->flags, ZAPI_NEXTHOP_FLAG_EVPN);
+
+ if (nh->nh_label && (nh->nh_label->num_labels > 0)) {
+
+ /* Validate */
+ if (nh->nh_label->num_labels > MPLS_MAX_LABELS)
+ return -1;
+
+ for (i = 0; i < nh->nh_label->num_labels; i++)
+ znh->labels[i] = nh->nh_label->label[i];
+
+ znh->label_num = i;
+ SET_FLAG(znh->flags, ZAPI_NEXTHOP_FLAG_LABEL);
+ }
+
+ if (CHECK_FLAG(nh->flags, NEXTHOP_FLAG_HAS_BACKUP)) {
+ if (nh->backup_num > NEXTHOP_MAX_BACKUPS)
+ return -1;
+
+ SET_FLAG(znh->flags, ZAPI_NEXTHOP_FLAG_HAS_BACKUP);
+ znh->backup_num = nh->backup_num;
+ memcpy(znh->backup_idx, nh->backup_idx, znh->backup_num);
+ }
+
+ if (nh->nh_srv6) {
+ if (nh->nh_srv6->seg6local_action !=
+ ZEBRA_SEG6_LOCAL_ACTION_UNSPEC) {
+ SET_FLAG(znh->flags, ZAPI_NEXTHOP_FLAG_SEG6LOCAL);
+ znh->seg6local_action = nh->nh_srv6->seg6local_action;
+ memcpy(&znh->seg6local_ctx,
+ &nh->nh_srv6->seg6local_ctx,
+ sizeof(struct seg6local_context));
+ }
+
+ if (!sid_zero(&nh->nh_srv6->seg6_segs)) {
+ SET_FLAG(znh->flags, ZAPI_NEXTHOP_FLAG_SEG6);
+ memcpy(&znh->seg6_segs, &nh->nh_srv6->seg6_segs,
+ sizeof(struct in6_addr));
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Wrapper that converts backup nexthop
+ */
+int zapi_backup_nexthop_from_nexthop(struct zapi_nexthop *znh,
+ const struct nexthop *nh)
+{
+ int ret;
+
+ /* Ensure that zapi flags are correct: backups don't have backups */
+ ret = zapi_nexthop_from_nexthop(znh, nh);
+ if (ret == 0)
+ UNSET_FLAG(znh->flags, ZAPI_NEXTHOP_FLAG_HAS_BACKUP);
+
+ return ret;
+}
+
+/*
+ * Format some info about a zapi nexthop, for debug or logging.
+ */
+const char *zapi_nexthop2str(const struct zapi_nexthop *znh, char *buf,
+ int bufsize)
+{
+ char tmp[INET6_ADDRSTRLEN];
+
+ switch (znh->type) {
+ case NEXTHOP_TYPE_IFINDEX:
+ snprintf(buf, bufsize, "if %u", znh->ifindex);
+ break;
+ case NEXTHOP_TYPE_IPV4:
+ case NEXTHOP_TYPE_IPV4_IFINDEX:
+ inet_ntop(AF_INET, &znh->gate.ipv4, tmp, sizeof(tmp));
+ snprintf(buf, bufsize, "%s if %u", tmp, znh->ifindex);
+ break;
+ case NEXTHOP_TYPE_IPV6:
+ case NEXTHOP_TYPE_IPV6_IFINDEX:
+ inet_ntop(AF_INET6, &znh->gate.ipv6, tmp, sizeof(tmp));
+ snprintf(buf, bufsize, "%s if %u", tmp, znh->ifindex);
+ break;
+ case NEXTHOP_TYPE_BLACKHOLE:
+ snprintf(buf, bufsize, "blackhole");
+ break;
+ default:
+ snprintf(buf, bufsize, "unknown");
+ break;
+ }
+
+ return buf;
+}
+
+/*
+ * Decode the nexthop-tracking update message
+ */
+bool zapi_nexthop_update_decode(struct stream *s, struct prefix *match,
+ struct zapi_route *nhr)
+{
+ uint32_t i;
+
+ memset(nhr, 0, sizeof(*nhr));
+
+ STREAM_GETL(s, nhr->message);
+ STREAM_GETW(s, nhr->safi);
+ STREAM_GETW(s, match->family);
+ STREAM_GETC(s, match->prefixlen);
+ /*
+ * What we got told to match against
+ */
+ switch (match->family) {
+ case AF_INET:
+ STREAM_GET(&match->u.prefix4.s_addr, s, IPV4_MAX_BYTELEN);
+ break;
+ case AF_INET6:
+ STREAM_GET(&match->u.prefix6, s, IPV6_MAX_BYTELEN);
+ break;
+ }
+ /*
+ * What we matched against
+ */
+ STREAM_GETW(s, nhr->prefix.family);
+ STREAM_GETC(s, nhr->prefix.prefixlen);
+ switch (nhr->prefix.family) {
+ case AF_INET:
+ STREAM_GET(&nhr->prefix.u.prefix4.s_addr, s, IPV4_MAX_BYTELEN);
+ break;
+ case AF_INET6:
+ STREAM_GET(&nhr->prefix.u.prefix6, s, IPV6_MAX_BYTELEN);
+ break;
+ default:
+ break;
+ }
+ if (CHECK_FLAG(nhr->message, ZAPI_MESSAGE_SRTE))
+ STREAM_GETL(s, nhr->srte_color);
+
+ STREAM_GETC(s, nhr->type);
+ STREAM_GETW(s, nhr->instance);
+ STREAM_GETC(s, nhr->distance);
+ STREAM_GETL(s, nhr->metric);
+ STREAM_GETC(s, nhr->nexthop_num);
+
+ for (i = 0; i < nhr->nexthop_num; i++) {
+ if (zapi_nexthop_decode(s, &(nhr->nexthops[i]), 0, 0) != 0)
+ return false;
+ }
+
+ return true;
+stream_failure:
+ return false;
+}
+
+bool zapi_error_decode(struct stream *s, enum zebra_error_types *error)
+{
+ memset(error, 0, sizeof(*error));
+
+ STREAM_GET(error, s, sizeof(*error));
+
+ if (zclient_debug)
+ zlog_debug("%s: type: %s", __func__,
+ zebra_error_type2str(*error));
+
+ return true;
+stream_failure:
+ return false;
+}
+
+/*
+ * send a ZEBRA_REDISTRIBUTE_ADD or ZEBRA_REDISTRIBUTE_DELETE
+ * for the route type (ZEBRA_ROUTE_KERNEL etc.). The zebra server will
+ * then set/unset redist[type] in the client handle (a struct zserv) for the
+ * sending client
+ */
+enum zclient_send_status
+zebra_redistribute_send(int command, struct zclient *zclient, afi_t afi,
+ int type, unsigned short instance, vrf_id_t vrf_id)
+{
+ struct stream *s;
+
+ s = zclient->obuf;
+ stream_reset(s);
+
+ zclient_create_header(s, command, vrf_id);
+ stream_putc(s, afi);
+ stream_putc(s, type);
+ stream_putw(s, instance);
+
+ stream_putw_at(s, 0, stream_get_endp(s));
+
+ return zclient_send_message(zclient);
+}
+
+enum zclient_send_status
+zebra_redistribute_default_send(int command, struct zclient *zclient, afi_t afi,
+ vrf_id_t vrf_id)
+{
+ struct stream *s;
+
+ s = zclient->obuf;
+ stream_reset(s);
+
+ zclient_create_header(s, command, vrf_id);
+ stream_putc(s, afi);
+
+ stream_putw_at(s, 0, stream_get_endp(s));
+
+ return zclient_send_message(zclient);
+}
+
+/* Send route notify request to zebra */
+int zebra_route_notify_send(int command, struct zclient *zclient, bool set)
+{
+ struct stream *s;
+
+ s = zclient->obuf;
+ stream_reset(s);
+
+ zclient_create_header(s, command, 0);
+ stream_putc(s, !!set);
+
+ stream_putw_at(s, 0, stream_get_endp(s));
+
+ return zclient_send_message(zclient);
+}
+
+/* Get prefix in ZServ format; family should be filled in on prefix */
+static int zclient_stream_get_prefix(struct stream *s, struct prefix *p)
+{
+ size_t plen = prefix_blen(p);
+ uint8_t c;
+ p->prefixlen = 0;
+
+ if (plen == 0)
+ return -1;
+
+ STREAM_GET(&p->u.prefix, s, plen);
+ STREAM_GETC(s, c);
+ p->prefixlen = MIN(plen * 8, c);
+
+ return 0;
+stream_failure:
+ return -1;
+}
+
+/* Router-id update from zebra daemon. */
+int zebra_router_id_update_read(struct stream *s, struct prefix *rid)
+{
+ /* Fetch interface address. */
+ STREAM_GETC(s, rid->family);
+
+ return zclient_stream_get_prefix(s, rid);
+
+stream_failure:
+ return -1;
+}
+
+/* Interface addition from zebra daemon. */
+/*
+ * The format of the message sent with type ZEBRA_INTERFACE_ADD or
+ * ZEBRA_INTERFACE_DELETE from zebra to the client is:
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | ifname |
+ * | |
+ * | |
+ * | |
+ * | |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | ifindex |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | status |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | if_flags |
+ * | |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | metric |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | speed |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | ifmtu |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | ifmtu6 |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | bandwidth |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | parent ifindex |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Link Layer Type |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Harware Address Length |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Hardware Address if HW length different from 0 |
+ * | ... max INTERFACE_HWADDR_MAX |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Link_params? | Whether a link-params follows: 1 or 0.
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Link_params 0 or 1 INTERFACE_LINK_PARAMS_SIZE sized |
+ * | .... (struct if_link_params). |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+
+static int zclient_vrf_add(ZAPI_CALLBACK_ARGS)
+{
+ struct vrf *vrf;
+ char vrfname_tmp[VRF_NAMSIZ + 1] = {};
+ struct vrf_data data;
+
+ STREAM_GET(&data, zclient->ibuf, sizeof(struct vrf_data));
+ /* Read interface name. */
+ STREAM_GET(vrfname_tmp, zclient->ibuf, VRF_NAMSIZ);
+
+ if (strlen(vrfname_tmp) == 0)
+ goto stream_failure;
+
+ /* Lookup/create vrf by name, then vrf_id. */
+ vrf = vrf_get(vrf_id, vrfname_tmp);
+
+ /* If there's already a VRF with this name, don't create vrf */
+ if (!vrf)
+ return 0;
+
+ vrf->data.l.table_id = data.l.table_id;
+ memcpy(vrf->data.l.netns_name, data.l.netns_name, NS_NAMSIZ);
+ vrf_enable(vrf);
+
+ return 0;
+stream_failure:
+ return -1;
+}
+
+static int zclient_vrf_delete(ZAPI_CALLBACK_ARGS)
+{
+ struct vrf *vrf;
+
+ /* Lookup vrf by vrf_id. */
+ vrf = vrf_lookup_by_id(vrf_id);
+
+ /*
+ * If a routing protocol doesn't know about a
+ * vrf that is about to be deleted. There is
+ * no point in attempting to delete it.
+ */
+ if (!vrf)
+ return 0;
+
+ vrf_delete(vrf);
+ return 0;
+}
+
+static int zclient_interface_add(ZAPI_CALLBACK_ARGS)
+{
+ struct interface *ifp;
+ char ifname_tmp[INTERFACE_NAMSIZ + 1] = {};
+ struct stream *s = zclient->ibuf;
+ struct vrf *vrf;
+
+ /* Read interface name. */
+ STREAM_GET(ifname_tmp, s, INTERFACE_NAMSIZ);
+
+ /* Lookup/create interface by name. */
+ vrf = vrf_lookup_by_id(vrf_id);
+ if (!vrf) {
+ zlog_debug(
+ "Rx'd interface add from Zebra, but VRF %u does not exist",
+ vrf_id);
+ return -1;
+ }
+
+ ifp = if_get_by_name(ifname_tmp, vrf_id, vrf->name);
+
+ zebra_interface_if_set_value(s, ifp);
+
+ if_new_via_zapi(ifp);
+
+ return 0;
+stream_failure:
+ return -1;
+}
+
+/*
+ * Read interface up/down msg (ZEBRA_INTERFACE_UP/ZEBRA_INTERFACE_DOWN)
+ * from zebra server. The format of this message is the same as
+ * that sent for ZEBRA_INTERFACE_ADD/ZEBRA_INTERFACE_DELETE,
+ * except that no sockaddr_dl is sent at the tail of the message.
+ */
+struct interface *zebra_interface_state_read(struct stream *s, vrf_id_t vrf_id)
+{
+ struct interface *ifp;
+ char ifname_tmp[INTERFACE_NAMSIZ + 1] = {};
+
+ /* Read interface name. */
+ STREAM_GET(ifname_tmp, s, INTERFACE_NAMSIZ);
+
+ /* Lookup this by interface index. */
+ ifp = if_lookup_by_name(ifname_tmp, vrf_id);
+ if (ifp == NULL) {
+ flog_err(EC_LIB_ZAPI_ENCODE,
+ "INTERFACE_STATE: Cannot find IF %s in VRF %d",
+ ifname_tmp, vrf_id);
+ return NULL;
+ }
+
+ zebra_interface_if_set_value(s, ifp);
+
+ return ifp;
+stream_failure:
+ return NULL;
+}
+
+static int zclient_interface_delete(ZAPI_CALLBACK_ARGS)
+{
+ struct interface *ifp;
+ struct stream *s = zclient->ibuf;
+
+ ifp = zebra_interface_state_read(s, vrf_id);
+
+ if (ifp == NULL)
+ return 0;
+
+ if_destroy_via_zapi(ifp);
+ return 0;
+}
+
+static int zclient_interface_up(ZAPI_CALLBACK_ARGS)
+{
+ struct interface *ifp;
+ struct stream *s = zclient->ibuf;
+
+ ifp = zebra_interface_state_read(s, vrf_id);
+
+ if (!ifp)
+ return 0;
+
+ if_up_via_zapi(ifp);
+ return 0;
+}
+
+static int zclient_interface_down(ZAPI_CALLBACK_ARGS)
+{
+ struct interface *ifp;
+ struct stream *s = zclient->ibuf;
+
+ ifp = zebra_interface_state_read(s, vrf_id);
+
+ if (!ifp)
+ return 0;
+
+ if_down_via_zapi(ifp);
+ return 0;
+}
+
+static int zclient_handle_error(ZAPI_CALLBACK_ARGS)
+{
+ enum zebra_error_types error;
+ struct stream *s = zclient->ibuf;
+
+ zapi_error_decode(s, &error);
+
+ if (zclient->handle_error)
+ (*zclient->handle_error)(error);
+ return 0;
+}
+
+static int link_params_set_value(struct stream *s, struct if_link_params *iflp)
+{
+
+ if (iflp == NULL)
+ return -1;
+
+ uint32_t bwclassnum;
+
+ STREAM_GETL(s, iflp->lp_status);
+ STREAM_GETL(s, iflp->te_metric);
+ STREAM_GETF(s, iflp->max_bw);
+ STREAM_GETF(s, iflp->max_rsv_bw);
+ STREAM_GETL(s, bwclassnum);
+ {
+ unsigned int i;
+ for (i = 0; i < bwclassnum && i < MAX_CLASS_TYPE; i++)
+ STREAM_GETF(s, iflp->unrsv_bw[i]);
+ if (i < bwclassnum)
+ flog_err(
+ EC_LIB_ZAPI_MISSMATCH,
+ "%s: received %d > %d (MAX_CLASS_TYPE) bw entries - outdated library?",
+ __func__, bwclassnum, MAX_CLASS_TYPE);
+ }
+ STREAM_GETL(s, iflp->admin_grp);
+ STREAM_GETL(s, iflp->rmt_as);
+ iflp->rmt_ip.s_addr = stream_get_ipv4(s);
+
+ STREAM_GETL(s, iflp->av_delay);
+ STREAM_GETL(s, iflp->min_delay);
+ STREAM_GETL(s, iflp->max_delay);
+ STREAM_GETL(s, iflp->delay_var);
+
+ STREAM_GETF(s, iflp->pkt_loss);
+ STREAM_GETF(s, iflp->res_bw);
+ STREAM_GETF(s, iflp->ava_bw);
+ STREAM_GETF(s, iflp->use_bw);
+
+ return 0;
+stream_failure:
+ return -1;
+}
+
+struct interface *zebra_interface_link_params_read(struct stream *s,
+ vrf_id_t vrf_id,
+ bool *changed)
+{
+ struct if_link_params *iflp;
+ struct if_link_params iflp_copy;
+ ifindex_t ifindex;
+ bool params_changed = false;
+
+ STREAM_GETL(s, ifindex);
+
+ struct interface *ifp = if_lookup_by_index(ifindex, vrf_id);
+
+ if (ifp == NULL) {
+ flog_err(EC_LIB_ZAPI_ENCODE,
+ "%s: unknown ifindex %u, shouldn't happen", __func__,
+ ifindex);
+ return NULL;
+ }
+
+ if (ifp->link_params == NULL)
+ params_changed = true;
+
+ if ((iflp = if_link_params_get(ifp)) == NULL)
+ return NULL;
+
+ memcpy(&iflp_copy, iflp, sizeof(iflp_copy));
+
+ if (link_params_set_value(s, iflp) != 0)
+ goto stream_failure;
+
+ if (memcmp(&iflp_copy, iflp, sizeof(iflp_copy)))
+ params_changed = true;
+
+ if (changed)
+ *changed = params_changed;
+
+ return ifp;
+
+stream_failure:
+ return NULL;
+}
+
+static void zebra_interface_if_set_value(struct stream *s,
+ struct interface *ifp)
+{
+ uint8_t link_params_status = 0;
+ ifindex_t old_ifindex, new_ifindex;
+
+ old_ifindex = ifp->oldifindex;
+ /* Read interface's index. */
+ STREAM_GETL(s, new_ifindex);
+ if_set_index(ifp, new_ifindex);
+ STREAM_GETC(s, ifp->status);
+
+ /* Read interface's value. */
+ STREAM_GETQ(s, ifp->flags);
+ STREAM_GETC(s, ifp->ptm_enable);
+ STREAM_GETC(s, ifp->ptm_status);
+ STREAM_GETL(s, ifp->metric);
+ STREAM_GETL(s, ifp->speed);
+ STREAM_GETL(s, ifp->mtu);
+ STREAM_GETL(s, ifp->mtu6);
+ STREAM_GETL(s, ifp->bandwidth);
+ STREAM_GETL(s, ifp->link_ifindex);
+ STREAM_GETL(s, ifp->ll_type);
+ STREAM_GETL(s, ifp->hw_addr_len);
+ if (ifp->hw_addr_len)
+ STREAM_GET(ifp->hw_addr, s,
+ MIN(ifp->hw_addr_len, INTERFACE_HWADDR_MAX));
+
+ /* Read Traffic Engineering status */
+ link_params_status = stream_getc(s);
+ /* Then, Traffic Engineering parameters if any */
+ if (link_params_status) {
+ struct if_link_params *iflp = if_link_params_get(ifp);
+ link_params_set_value(s, iflp);
+ }
+
+ nexthop_group_interface_state_change(ifp, old_ifindex);
+
+ return;
+stream_failure:
+ zlog_err("Could not parse interface values; aborting");
+ assert(!"Failed to parse interface values");
+}
+
+size_t zebra_interface_link_params_write(struct stream *s,
+ struct interface *ifp)
+{
+ size_t w;
+ struct if_link_params *iflp;
+ int i;
+
+ if (s == NULL || ifp == NULL || ifp->link_params == NULL)
+ return 0;
+
+ iflp = ifp->link_params;
+ w = 0;
+
+ w += stream_putl(s, iflp->lp_status);
+
+ w += stream_putl(s, iflp->te_metric);
+ w += stream_putf(s, iflp->max_bw);
+ w += stream_putf(s, iflp->max_rsv_bw);
+
+ w += stream_putl(s, MAX_CLASS_TYPE);
+ for (i = 0; i < MAX_CLASS_TYPE; i++)
+ w += stream_putf(s, iflp->unrsv_bw[i]);
+
+ w += stream_putl(s, iflp->admin_grp);
+ w += stream_putl(s, iflp->rmt_as);
+ w += stream_put_in_addr(s, &iflp->rmt_ip);
+
+ w += stream_putl(s, iflp->av_delay);
+ w += stream_putl(s, iflp->min_delay);
+ w += stream_putl(s, iflp->max_delay);
+ w += stream_putl(s, iflp->delay_var);
+
+ w += stream_putf(s, iflp->pkt_loss);
+ w += stream_putf(s, iflp->res_bw);
+ w += stream_putf(s, iflp->ava_bw);
+ w += stream_putf(s, iflp->use_bw);
+
+ return w;
+}
+
+/*
+ * format of message for address addition is:
+ * 0
+ * 0 1 2 3 4 5 6 7
+ * +-+-+-+-+-+-+-+-+
+ * | type | ZEBRA_INTERFACE_ADDRESS_ADD or
+ * +-+-+-+-+-+-+-+-+ ZEBRA_INTERFACE_ADDRES_DELETE
+ * | |
+ * + +
+ * | ifindex |
+ * + +
+ * | |
+ * + +
+ * | |
+ * +-+-+-+-+-+-+-+-+
+ * | ifc_flags | flags for connected address
+ * +-+-+-+-+-+-+-+-+
+ * | addr_family |
+ * +-+-+-+-+-+-+-+-+
+ * | addr... |
+ * : :
+ * | |
+ * +-+-+-+-+-+-+-+-+
+ * | addr_len | len of addr. E.g., addr_len = 4 for ipv4 addrs.
+ * +-+-+-+-+-+-+-+-+
+ * | daddr.. |
+ * : :
+ * | |
+ * +-+-+-+-+-+-+-+-+
+ */
+
+static int memconstant(const void *s, int c, size_t n)
+{
+ const uint8_t *p = s;
+
+ while (n-- > 0)
+ if (*p++ != c)
+ return 0;
+ return 1;
+}
+
+
+struct connected *zebra_interface_address_read(int type, struct stream *s,
+ vrf_id_t vrf_id)
+{
+ ifindex_t ifindex;
+ struct interface *ifp;
+ struct connected *ifc;
+ struct prefix p, d, *dp;
+ int plen;
+ uint8_t ifc_flags;
+
+ memset(&p, 0, sizeof(p));
+ memset(&d, 0, sizeof(d));
+
+ /* Get interface index. */
+ STREAM_GETL(s, ifindex);
+
+ /* Lookup index. */
+ ifp = if_lookup_by_index(ifindex, vrf_id);
+ if (ifp == NULL) {
+ flog_err(EC_LIB_ZAPI_ENCODE,
+ "INTERFACE_ADDRESS_%s: Cannot find IF %u in VRF %d",
+ (type == ZEBRA_INTERFACE_ADDRESS_ADD) ? "ADD" : "DEL",
+ ifindex, vrf_id);
+ return NULL;
+ }
+
+ /* Fetch flag. */
+ STREAM_GETC(s, ifc_flags);
+
+ /* Fetch interface address. */
+ STREAM_GETC(s, d.family);
+ p.family = d.family;
+ plen = prefix_blen(&d);
+
+ if (zclient_stream_get_prefix(s, &p) != 0)
+ goto stream_failure;
+
+ /* Fetch destination address. */
+ STREAM_GET(&d.u.prefix, s, plen);
+
+ /* N.B. NULL destination pointers are encoded as all zeroes */
+ dp = memconstant(&d.u.prefix, 0, plen) ? NULL : &d;
+
+ if (type == ZEBRA_INTERFACE_ADDRESS_ADD) {
+ ifc = connected_lookup_prefix_exact(ifp, &p);
+ if (!ifc) {
+ /* N.B. NULL destination pointers are encoded as all
+ * zeroes */
+ ifc = connected_add_by_prefix(ifp, &p, dp);
+ }
+ if (ifc) {
+ ifc->flags = ifc_flags;
+ if (ifc->destination)
+ ifc->destination->prefixlen =
+ ifc->address->prefixlen;
+ else if (CHECK_FLAG(ifc->flags, ZEBRA_IFA_PEER)) {
+ /* carp interfaces on OpenBSD with 0.0.0.0/0 as
+ * "peer" */
+ flog_err(
+ EC_LIB_ZAPI_ENCODE,
+ "interface %s address %pFX with peer flag set, but no peer address!",
+ ifp->name, ifc->address);
+ UNSET_FLAG(ifc->flags, ZEBRA_IFA_PEER);
+ }
+ }
+ } else {
+ assert(type == ZEBRA_INTERFACE_ADDRESS_DELETE);
+ ifc = connected_delete_by_prefix(ifp, &p);
+ }
+
+ return ifc;
+
+stream_failure:
+ return NULL;
+}
+
+/*
+ * format of message for neighbor connected address is:
+ * 0
+ * 0 1 2 3 4 5 6 7
+ * +-+-+-+-+-+-+-+-+
+ * | type | ZEBRA_INTERFACE_NBR_ADDRESS_ADD or
+ * +-+-+-+-+-+-+-+-+ ZEBRA_INTERFACE_NBR_ADDRES_DELETE
+ * | |
+ * + +
+ * | ifindex |
+ * + +
+ * | |
+ * + +
+ * | |
+ * +-+-+-+-+-+-+-+-+
+ * | addr_family |
+ * +-+-+-+-+-+-+-+-+
+ * | addr... |
+ * : :
+ * | |
+ * +-+-+-+-+-+-+-+-+
+ * | addr_len | len of addr.
+ * +-+-+-+-+-+-+-+-+
+ */
+struct nbr_connected *
+zebra_interface_nbr_address_read(int type, struct stream *s, vrf_id_t vrf_id)
+{
+ unsigned int ifindex;
+ struct interface *ifp;
+ struct prefix p;
+ struct nbr_connected *ifc;
+
+ /* Get interface index. */
+ STREAM_GETL(s, ifindex);
+
+ /* Lookup index. */
+ ifp = if_lookup_by_index(ifindex, vrf_id);
+ if (ifp == NULL) {
+ flog_err(EC_LIB_ZAPI_ENCODE,
+ "INTERFACE_NBR_%s: Cannot find IF %u in VRF %d",
+ (type == ZEBRA_INTERFACE_NBR_ADDRESS_ADD) ? "ADD"
+ : "DELETE",
+ ifindex, vrf_id);
+ return NULL;
+ }
+
+ STREAM_GETC(s, p.family);
+ STREAM_GET(&p.u.prefix, s, prefix_blen(&p));
+ STREAM_GETC(s, p.prefixlen);
+
+ if (type == ZEBRA_INTERFACE_NBR_ADDRESS_ADD) {
+ /* Currently only supporting P2P links, so any new RA source
+ address is
+ considered as the replacement of the previously learnt
+ Link-Local address. */
+ if (!(ifc = listnode_head(ifp->nbr_connected))) {
+ ifc = nbr_connected_new();
+ ifc->address = prefix_new();
+ ifc->ifp = ifp;
+ listnode_add(ifp->nbr_connected, ifc);
+ }
+
+ prefix_copy(ifc->address, &p);
+ } else {
+ assert(type == ZEBRA_INTERFACE_NBR_ADDRESS_DELETE);
+
+ ifc = nbr_connected_check(ifp, &p);
+ if (ifc)
+ listnode_delete(ifp->nbr_connected, ifc);
+ }
+
+ return ifc;
+
+stream_failure:
+ return NULL;
+}
+
+struct interface *zebra_interface_vrf_update_read(struct stream *s,
+ vrf_id_t vrf_id,
+ vrf_id_t *new_vrf_id)
+{
+ char ifname[INTERFACE_NAMSIZ + 1] = {};
+ struct interface *ifp;
+ vrf_id_t new_id;
+
+ /* Read interface name. */
+ STREAM_GET(ifname, s, INTERFACE_NAMSIZ);
+
+ /* Lookup interface. */
+ ifp = if_lookup_by_name(ifname, vrf_id);
+ if (ifp == NULL) {
+ flog_err(EC_LIB_ZAPI_ENCODE,
+ "INTERFACE_VRF_UPDATE: Cannot find IF %s in VRF %d",
+ ifname, vrf_id);
+ return NULL;
+ }
+
+ /* Fetch new VRF Id. */
+ STREAM_GETL(s, new_id);
+
+ *new_vrf_id = new_id;
+ return ifp;
+
+stream_failure:
+ return NULL;
+}
+
+/* filter unwanted messages until the expected one arrives */
+static int zclient_read_sync_response(struct zclient *zclient,
+ uint16_t expected_cmd)
+{
+ struct stream *s;
+ uint16_t size = -1;
+ uint8_t marker;
+ uint8_t version;
+ vrf_id_t vrf_id;
+ uint16_t cmd;
+ fd_set readfds;
+ int ret;
+
+ ret = 0;
+ cmd = expected_cmd + 1;
+ while (ret == 0 && cmd != expected_cmd) {
+ s = zclient->ibuf;
+ stream_reset(s);
+
+ /* wait until response arrives */
+ FD_ZERO(&readfds);
+ FD_SET(zclient->sock, &readfds);
+ select(zclient->sock + 1, &readfds, NULL, NULL, NULL);
+ if (!FD_ISSET(zclient->sock, &readfds))
+ continue;
+ /* read response */
+ ret = zclient_read_header(s, zclient->sock, &size, &marker,
+ &version, &vrf_id, &cmd);
+ if (zclient_debug)
+ zlog_debug("%s: Response (%d bytes) received", __func__,
+ size);
+ }
+ if (ret != 0) {
+ flog_err(EC_LIB_ZAPI_ENCODE, "%s: Invalid Sync Message Reply",
+ __func__);
+ return -1;
+ }
+
+ return 0;
+}
+/**
+ * Connect to label manager in a synchronous way
+ *
+ * It first writes the request to zclient output buffer and then
+ * immediately reads the answer from the input buffer.
+ *
+ * @param zclient Zclient used to connect to label manager (zebra)
+ * @param async Synchronous (0) or asynchronous (1) operation
+ * @result Result of response
+ */
+int lm_label_manager_connect(struct zclient *zclient, int async)
+{
+ int ret;
+ struct stream *s;
+ uint8_t result;
+ uint16_t cmd = async ? ZEBRA_LABEL_MANAGER_CONNECT_ASYNC :
+ ZEBRA_LABEL_MANAGER_CONNECT;
+
+ if (zclient_debug)
+ zlog_debug("Connecting to Label Manager (LM)");
+
+ if (zclient->sock < 0) {
+ zlog_debug("%s: invalid zclient socket", __func__);
+ return -1;
+ }
+
+ /* send request */
+ s = zclient->obuf;
+ stream_reset(s);
+ zclient_create_header(s, cmd, VRF_DEFAULT);
+
+ /* proto */
+ stream_putc(s, zclient->redist_default);
+ /* instance */
+ stream_putw(s, zclient->instance);
+
+ /* Put length at the first point of the stream. */
+ stream_putw_at(s, 0, stream_get_endp(s));
+
+ ret = writen(zclient->sock, s->data, stream_get_endp(s));
+ if (ret < 0) {
+ flog_err(EC_LIB_ZAPI_SOCKET, "Can't write to zclient sock");
+ close(zclient->sock);
+ zclient->sock = -1;
+ return -1;
+ }
+ if (ret == 0) {
+ flog_err(EC_LIB_ZAPI_SOCKET, "Zclient sock closed");
+ close(zclient->sock);
+ zclient->sock = -1;
+ return -1;
+ }
+ if (zclient_debug)
+ zlog_debug("LM connect request sent (%d bytes)", ret);
+
+ if (async)
+ return 0;
+
+ /* read response */
+ if (zclient_read_sync_response(zclient, cmd)
+ != 0)
+ return -1;
+
+ s = zclient->ibuf;
+
+ /* read instance and proto */
+ uint8_t proto;
+ uint16_t instance;
+
+ STREAM_GETC(s, proto);
+ STREAM_GETW(s, instance);
+
+ /* sanity */
+ if (proto != zclient->redist_default)
+ flog_err(
+ EC_LIB_ZAPI_ENCODE,
+ "Wrong proto (%u) in LM connect response. Should be %u",
+ proto, zclient->redist_default);
+ if (instance != zclient->instance)
+ flog_err(
+ EC_LIB_ZAPI_ENCODE,
+ "Wrong instId (%u) in LM connect response. Should be %u",
+ instance, zclient->instance);
+
+ /* result code */
+ STREAM_GETC(s, result);
+ if (zclient_debug)
+ zlog_debug("LM connect-response received, result %u", result);
+
+ return (int)result;
+
+stream_failure:
+ return -1;
+}
+
+/**
+ * Function to request a srv6-locator chunk in an asynchronous way
+ *
+ * @param zclient Zclient used to connect to table manager (zebra)
+ * @param locator_name Name of SRv6-locator
+ * @result 0 on success, -1 otherwise
+ */
+int srv6_manager_get_locator_chunk(struct zclient *zclient,
+ const char *locator_name)
+{
+ struct stream *s;
+ const size_t len = strlen(locator_name);
+
+ if (zclient_debug)
+ zlog_debug("Getting SRv6-Locator Chunk %s", locator_name);
+
+ if (zclient->sock < 0)
+ return -1;
+
+ /* send request */
+ s = zclient->obuf;
+ stream_reset(s);
+ zclient_create_header(s, ZEBRA_SRV6_MANAGER_GET_LOCATOR_CHUNK,
+ VRF_DEFAULT);
+
+ /* locator_name */
+ stream_putw(s, len);
+ stream_put(s, locator_name, len);
+
+ /* Put length at the first point of the stream. */
+ stream_putw_at(s, 0, stream_get_endp(s));
+
+ return zclient_send_message(zclient);
+}
+
+/**
+ * Function to release a srv6-locator chunk
+ *
+ * @param zclient Zclient used to connect to table manager (zebra)
+ * @param locator_name Name of SRv6-locator
+ * @result 0 on success, -1 otherwise
+ */
+int srv6_manager_release_locator_chunk(struct zclient *zclient,
+ const char *locator_name)
+{
+ struct stream *s;
+ const size_t len = strlen(locator_name);
+
+ if (zclient_debug)
+ zlog_debug("Releasing SRv6-Locator Chunk %s", locator_name);
+
+ if (zclient->sock < 0)
+ return -1;
+
+ /* send request */
+ s = zclient->obuf;
+ stream_reset(s);
+ zclient_create_header(s, ZEBRA_SRV6_MANAGER_RELEASE_LOCATOR_CHUNK,
+ VRF_DEFAULT);
+
+ /* locator_name */
+ stream_putw(s, len);
+ stream_put(s, locator_name, len);
+
+ /* Put length at the first point of the stream. */
+ stream_putw_at(s, 0, stream_get_endp(s));
+
+ return zclient_send_message(zclient);
+}
+
+/*
+ * Asynchronous label chunk request
+ *
+ * @param zclient Zclient used to connect to label manager (zebra)
+ * @param keep Avoid garbage collection
+ * @param chunk_size Amount of labels requested
+ * @param base Base for the label chunk. if MPLS_LABEL_BASE_ANY we do not care
+ * @result 0 on success, -1 otherwise
+ */
+enum zclient_send_status zclient_send_get_label_chunk(struct zclient *zclient,
+ uint8_t keep,
+ uint32_t chunk_size,
+ uint32_t base)
+{
+ struct stream *s;
+
+ if (zclient_debug)
+ zlog_debug("Getting Label Chunk");
+
+ if (zclient->sock < 0)
+ return ZCLIENT_SEND_FAILURE;
+
+ s = zclient->obuf;
+ stream_reset(s);
+
+ zclient_create_header(s, ZEBRA_GET_LABEL_CHUNK, VRF_DEFAULT);
+ /* proto */
+ stream_putc(s, zclient->redist_default);
+ /* instance */
+ stream_putw(s, zclient->instance);
+ stream_putc(s, keep);
+ stream_putl(s, chunk_size);
+ stream_putl(s, base);
+
+ /* Put length at the first point of the stream. */
+ stream_putw_at(s, 0, stream_get_endp(s));
+
+ return zclient_send_message(zclient);
+}
+
+/**
+ * Function to request a label chunk in a synchronous way
+ *
+ * It first writes the request to zclient output buffer and then
+ * immediately reads the answer from the input buffer.
+ *
+ * @param zclient Zclient used to connect to label manager (zebra)
+ * @param keep Avoid garbage collection
+ * @param chunk_size Amount of labels requested
+ * @param start To write first assigned chunk label to
+ * @param end To write last assigned chunk label to
+ * @result 0 on success, -1 otherwise
+ */
+int lm_get_label_chunk(struct zclient *zclient, uint8_t keep, uint32_t base,
+ uint32_t chunk_size, uint32_t *start, uint32_t *end)
+{
+ int ret;
+ struct stream *s;
+ uint8_t response_keep;
+
+ if (zclient_debug)
+ zlog_debug("Getting Label Chunk");
+
+ if (zclient->sock < 0)
+ return -1;
+
+ /* send request */
+ s = zclient->obuf;
+ stream_reset(s);
+ zclient_create_header(s, ZEBRA_GET_LABEL_CHUNK, VRF_DEFAULT);
+ /* proto */
+ stream_putc(s, zclient->redist_default);
+ /* instance */
+ stream_putw(s, zclient->instance);
+ /* keep */
+ stream_putc(s, keep);
+ /* chunk size */
+ stream_putl(s, chunk_size);
+ /* requested chunk base */
+ stream_putl(s, base);
+ /* Put length at the first point of the stream. */
+ stream_putw_at(s, 0, stream_get_endp(s));
+
+ ret = writen(zclient->sock, s->data, stream_get_endp(s));
+ if (ret < 0) {
+ flog_err(EC_LIB_ZAPI_SOCKET, "Can't write to zclient sock");
+ close(zclient->sock);
+ zclient->sock = -1;
+ return -1;
+ }
+ if (ret == 0) {
+ flog_err(EC_LIB_ZAPI_SOCKET, "Zclient sock closed");
+ close(zclient->sock);
+ zclient->sock = -1;
+ return -1;
+ }
+ if (zclient_debug)
+ zlog_debug("Label chunk request (%d bytes) sent", ret);
+
+ /* read response */
+ if (zclient_read_sync_response(zclient, ZEBRA_GET_LABEL_CHUNK) != 0)
+ return -1;
+
+ /* parse response */
+ s = zclient->ibuf;
+
+ /* read proto and instance */
+ uint8_t proto;
+ uint8_t instance;
+
+ STREAM_GETC(s, proto);
+ STREAM_GETW(s, instance);
+
+ /* sanities */
+ if (proto != zclient->redist_default)
+ flog_err(EC_LIB_ZAPI_ENCODE,
+ "Wrong proto (%u) in get chunk response. Should be %u",
+ proto, zclient->redist_default);
+ if (instance != zclient->instance)
+ flog_err(EC_LIB_ZAPI_ENCODE,
+ "Wrong instId (%u) in get chunk response Should be %u",
+ instance, zclient->instance);
+
+ /* if we requested a specific chunk and it could not be allocated, the
+ * response message will end here
+ */
+ if (!STREAM_READABLE(s)) {
+ zlog_info("Unable to assign Label Chunk to %s instance %u",
+ zebra_route_string(proto), instance);
+ return -1;
+ }
+
+ /* keep */
+ STREAM_GETC(s, response_keep);
+ /* start and end labels */
+ STREAM_GETL(s, *start);
+ STREAM_GETL(s, *end);
+
+ /* not owning this response */
+ if (keep != response_keep) {
+ flog_err(
+ EC_LIB_ZAPI_ENCODE,
+ "Invalid Label chunk: %u - %u, keeps mismatch %u != %u",
+ *start, *end, keep, response_keep);
+ }
+ /* sanity */
+ if (*start > *end || *start < MPLS_LABEL_UNRESERVED_MIN
+ || *end > MPLS_LABEL_UNRESERVED_MAX) {
+ flog_err(EC_LIB_ZAPI_ENCODE, "Invalid Label chunk: %u - %u",
+ *start, *end);
+ return -1;
+ }
+
+ if (zclient_debug)
+ zlog_debug("Label Chunk assign: %u - %u (%u)", *start, *end,
+ response_keep);
+
+ return 0;
+
+stream_failure:
+ return -1;
+}
+
+/**
+ * Function to release a label chunk
+ *
+ * @param zclient Zclient used to connect to label manager (zebra)
+ * @param start First label of chunk
+ * @param end Last label of chunk
+ * @result 0 on success, -1 otherwise
+ */
+int lm_release_label_chunk(struct zclient *zclient, uint32_t start,
+ uint32_t end)
+{
+ int ret;
+ struct stream *s;
+
+ if (zclient_debug)
+ zlog_debug("Releasing Label Chunk %u - %u", start, end);
+
+ if (zclient->sock < 0)
+ return -1;
+
+ /* send request */
+ s = zclient->obuf;
+ stream_reset(s);
+ zclient_create_header(s, ZEBRA_RELEASE_LABEL_CHUNK, VRF_DEFAULT);
+
+ /* proto */
+ stream_putc(s, zclient->redist_default);
+ /* instance */
+ stream_putw(s, zclient->instance);
+ /* start */
+ stream_putl(s, start);
+ /* end */
+ stream_putl(s, end);
+
+ /* Put length at the first point of the stream. */
+ stream_putw_at(s, 0, stream_get_endp(s));
+
+ ret = writen(zclient->sock, s->data, stream_get_endp(s));
+ if (ret < 0) {
+ flog_err(EC_LIB_ZAPI_SOCKET, "Can't write to zclient sock");
+ close(zclient->sock);
+ zclient->sock = -1;
+ return -1;
+ }
+ if (ret == 0) {
+ flog_err(EC_LIB_ZAPI_SOCKET, "Zclient sock connection closed");
+ close(zclient->sock);
+ zclient->sock = -1;
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * Connect to table manager in a synchronous way
+ *
+ * It first writes the request to zclient output buffer and then
+ * immediately reads the answer from the input buffer.
+ *
+ * @param zclient Zclient used to connect to table manager (zebra)
+ * @result Result of response
+ */
+int tm_table_manager_connect(struct zclient *zclient)
+{
+ int ret;
+ struct stream *s;
+ uint8_t result;
+
+ if (zclient_debug)
+ zlog_debug("Connecting to Table Manager");
+
+ if (zclient->sock < 0)
+ return ZCLIENT_SEND_FAILURE;
+
+ /* send request */
+ s = zclient->obuf;
+ stream_reset(s);
+ zclient_create_header(s, ZEBRA_TABLE_MANAGER_CONNECT, VRF_DEFAULT);
+
+ /* proto */
+ stream_putc(s, zclient->redist_default);
+ /* instance */
+ stream_putw(s, zclient->instance);
+
+ /* Put length at the first point of the stream. */
+ stream_putw_at(s, 0, stream_get_endp(s));
+
+ ret = zclient_send_message(zclient);
+ if (ret == ZCLIENT_SEND_FAILURE)
+ return -1;
+
+ if (zclient_debug)
+ zlog_debug("%s: Table manager connect request sent", __func__);
+
+ /* read response */
+ if (zclient_read_sync_response(zclient, ZEBRA_TABLE_MANAGER_CONNECT)
+ != 0)
+ return -1;
+
+ /* result */
+ s = zclient->ibuf;
+ STREAM_GETC(s, result);
+ if (zclient_debug)
+ zlog_debug(
+ "%s: Table Manager connect response received, result %u",
+ __func__, result);
+
+ return (int)result;
+stream_failure:
+ return -1;
+}
+
+/**
+ * Function to request a table chunk in a synchronous way
+ *
+ * It first writes the request to zclient output buffer and then
+ * immediately reads the answer from the input buffer.
+ *
+ * @param zclient Zclient used to connect to table manager (zebra)
+ * @param chunk_size Amount of table requested
+ * @param start to write first assigned chunk table RT ID to
+ * @param end To write last assigned chunk table RT ID to
+ * @result 0 on success, -1 otherwise
+ */
+int tm_get_table_chunk(struct zclient *zclient, uint32_t chunk_size,
+ uint32_t *start, uint32_t *end)
+{
+ int ret;
+ struct stream *s;
+
+ if (zclient_debug)
+ zlog_debug("Getting Table Chunk");
+
+ if (zclient->sock < 0)
+ return -1;
+
+ /* send request */
+ s = zclient->obuf;
+ stream_reset(s);
+ zclient_create_header(s, ZEBRA_GET_TABLE_CHUNK, VRF_DEFAULT);
+ /* chunk size */
+ stream_putl(s, chunk_size);
+ /* Put length at the first point of the stream. */
+ stream_putw_at(s, 0, stream_get_endp(s));
+
+ ret = writen(zclient->sock, s->data, stream_get_endp(s));
+ if (ret < 0) {
+ flog_err(EC_LIB_ZAPI_SOCKET, "%s: can't write to zclient->sock",
+ __func__);
+ close(zclient->sock);
+ zclient->sock = -1;
+ return -1;
+ }
+ if (ret == 0) {
+ flog_err(EC_LIB_ZAPI_SOCKET,
+ "%s: zclient->sock connection closed", __func__);
+ close(zclient->sock);
+ zclient->sock = -1;
+ return -1;
+ }
+ if (zclient_debug)
+ zlog_debug("%s: Table chunk request (%d bytes) sent", __func__,
+ ret);
+
+ /* read response */
+ if (zclient_read_sync_response(zclient, ZEBRA_GET_TABLE_CHUNK) != 0)
+ return -1;
+
+ s = zclient->ibuf;
+ /* start and end table IDs */
+ STREAM_GETL(s, *start);
+ STREAM_GETL(s, *end);
+
+ if (zclient_debug)
+ zlog_debug("Table Chunk assign: %u - %u ", *start, *end);
+
+ return 0;
+stream_failure:
+ return -1;
+}
+
+/**
+ * Function to release a table chunk
+ *
+ * @param zclient Zclient used to connect to table manager (zebra)
+ * @param start First label of table
+ * @param end Last label of chunk
+ * @result 0 on success, -1 otherwise
+ */
+int tm_release_table_chunk(struct zclient *zclient, uint32_t start,
+ uint32_t end)
+{
+ struct stream *s;
+
+ if (zclient_debug)
+ zlog_debug("Releasing Table Chunk");
+
+ if (zclient->sock < 0)
+ return -1;
+
+ /* send request */
+ s = zclient->obuf;
+ stream_reset(s);
+ zclient_create_header(s, ZEBRA_RELEASE_TABLE_CHUNK, VRF_DEFAULT);
+
+ /* start */
+ stream_putl(s, start);
+ /* end */
+ stream_putl(s, end);
+
+ /* Put length at the first point of the stream. */
+ stream_putw_at(s, 0, stream_get_endp(s));
+
+ if (zclient_send_message(zclient) == ZCLIENT_SEND_FAILURE)
+ return -1;
+
+ return 0;
+}
+
+enum zclient_send_status zebra_send_sr_policy(struct zclient *zclient, int cmd,
+ struct zapi_sr_policy *zp)
+{
+ if (zapi_sr_policy_encode(zclient->obuf, cmd, zp) < 0)
+ return ZCLIENT_SEND_FAILURE;
+ return zclient_send_message(zclient);
+}
+
+int zapi_sr_policy_encode(struct stream *s, int cmd, struct zapi_sr_policy *zp)
+{
+ struct zapi_srte_tunnel *zt = &zp->segment_list;
+
+ stream_reset(s);
+
+ zclient_create_header(s, cmd, VRF_DEFAULT);
+ stream_putl(s, zp->color);
+ stream_put_ipaddr(s, &zp->endpoint);
+ stream_write(s, &zp->name, SRTE_POLICY_NAME_MAX_LENGTH);
+
+ stream_putc(s, zt->type);
+ stream_putl(s, zt->local_label);
+
+ if (zt->label_num > MPLS_MAX_LABELS) {
+ flog_err(EC_LIB_ZAPI_ENCODE,
+ "%s: label %u: can't encode %u labels (maximum is %u)",
+ __func__, zt->local_label, zt->label_num,
+ MPLS_MAX_LABELS);
+ return -1;
+ }
+ stream_putw(s, zt->label_num);
+
+ for (int i = 0; i < zt->label_num; i++)
+ stream_putl(s, zt->labels[i]);
+
+ /* Put length at the first point of the stream. */
+ stream_putw_at(s, 0, stream_get_endp(s));
+
+ return 0;
+}
+
+int zapi_sr_policy_decode(struct stream *s, struct zapi_sr_policy *zp)
+{
+ memset(zp, 0, sizeof(*zp));
+
+ struct zapi_srte_tunnel *zt = &zp->segment_list;
+
+ STREAM_GETL(s, zp->color);
+ STREAM_GET_IPADDR(s, &zp->endpoint);
+ STREAM_GET(&zp->name, s, SRTE_POLICY_NAME_MAX_LENGTH);
+
+ /* segment list of active candidate path */
+ STREAM_GETC(s, zt->type);
+ STREAM_GETL(s, zt->local_label);
+ STREAM_GETW(s, zt->label_num);
+ if (zt->label_num > MPLS_MAX_LABELS) {
+ flog_err(EC_LIB_ZAPI_ENCODE,
+ "%s: label %u: can't decode %u labels (maximum is %u)",
+ __func__, zt->local_label, zt->label_num,
+ MPLS_MAX_LABELS);
+ return -1;
+ }
+ for (int i = 0; i < zt->label_num; i++)
+ STREAM_GETL(s, zt->labels[i]);
+
+ return 0;
+
+stream_failure:
+ return -1;
+}
+
+int zapi_sr_policy_notify_status_decode(struct stream *s,
+ struct zapi_sr_policy *zp)
+{
+ memset(zp, 0, sizeof(*zp));
+
+ STREAM_GETL(s, zp->color);
+ STREAM_GET_IPADDR(s, &zp->endpoint);
+ STREAM_GET(&zp->name, s, SRTE_POLICY_NAME_MAX_LENGTH);
+ STREAM_GETL(s, zp->status);
+
+ return 0;
+
+stream_failure:
+ return -1;
+}
+
+enum zclient_send_status zebra_send_mpls_labels(struct zclient *zclient,
+ int cmd, struct zapi_labels *zl)
+{
+ if (zapi_labels_encode(zclient->obuf, cmd, zl) < 0)
+ return ZCLIENT_SEND_FAILURE;
+ return zclient_send_message(zclient);
+}
+
+int zapi_labels_encode(struct stream *s, int cmd, struct zapi_labels *zl)
+{
+ struct zapi_nexthop *znh;
+
+ stream_reset(s);
+
+ zclient_create_header(s, cmd, VRF_DEFAULT);
+ stream_putc(s, zl->message);
+ stream_putc(s, zl->type);
+ stream_putl(s, zl->local_label);
+
+ if (CHECK_FLAG(zl->message, ZAPI_LABELS_FTN)) {
+ stream_putw(s, zl->route.prefix.family);
+ stream_put_prefix(s, &zl->route.prefix);
+ stream_putc(s, zl->route.type);
+ stream_putw(s, zl->route.instance);
+ }
+
+ if (zl->nexthop_num > MULTIPATH_NUM) {
+ flog_err(
+ EC_LIB_ZAPI_ENCODE,
+ "%s: label %u: can't encode %u nexthops (maximum is %u)",
+ __func__, zl->local_label, zl->nexthop_num,
+ MULTIPATH_NUM);
+ return -1;
+ }
+ stream_putw(s, zl->nexthop_num);
+
+ for (int i = 0; i < zl->nexthop_num; i++) {
+ znh = &zl->nexthops[i];
+
+ if (zapi_nexthop_encode(s, znh, 0, 0) < 0)
+ return -1;
+ }
+
+ if (CHECK_FLAG(zl->message, ZAPI_LABELS_HAS_BACKUPS)) {
+
+ if (zl->backup_nexthop_num > MULTIPATH_NUM) {
+ flog_err(
+ EC_LIB_ZAPI_ENCODE,
+ "%s: label %u: can't encode %u nexthops (maximum is %u)",
+ __func__, zl->local_label, zl->nexthop_num,
+ MULTIPATH_NUM);
+ return -1;
+ }
+ stream_putw(s, zl->backup_nexthop_num);
+
+ for (int i = 0; i < zl->backup_nexthop_num; i++) {
+ znh = &zl->backup_nexthops[i];
+
+ if (zapi_nexthop_encode(s, znh, 0, 0) < 0)
+ return -1;
+ }
+
+ }
+
+ /* Put length at the first point of the stream. */
+ stream_putw_at(s, 0, stream_get_endp(s));
+
+ return 0;
+}
+
+int zapi_labels_decode(struct stream *s, struct zapi_labels *zl)
+{
+ struct zapi_nexthop *znh;
+
+ memset(zl, 0, sizeof(*zl));
+
+ /* Get data. */
+ STREAM_GETC(s, zl->message);
+ STREAM_GETC(s, zl->type);
+ STREAM_GETL(s, zl->local_label);
+
+ if (CHECK_FLAG(zl->message, ZAPI_LABELS_FTN)) {
+ size_t psize;
+
+ STREAM_GETW(s, zl->route.prefix.family);
+ STREAM_GETC(s, zl->route.prefix.prefixlen);
+
+ psize = PSIZE(zl->route.prefix.prefixlen);
+ switch (zl->route.prefix.family) {
+ case AF_INET:
+ if (zl->route.prefix.prefixlen > IPV4_MAX_BITLEN) {
+ zlog_debug(
+ "%s: Specified prefix length %d is greater than a v4 address can support",
+ __func__, zl->route.prefix.prefixlen);
+ return -1;
+ }
+ STREAM_GET(&zl->route.prefix.u.prefix4.s_addr, s,
+ psize);
+ break;
+ case AF_INET6:
+ if (zl->route.prefix.prefixlen > IPV6_MAX_BITLEN) {
+ zlog_debug(
+ "%s: Specified prefix length %d is greater than a v6 address can support",
+ __func__, zl->route.prefix.prefixlen);
+ return -1;
+ }
+ STREAM_GET(&zl->route.prefix.u.prefix6, s, psize);
+ break;
+ default:
+ flog_err(EC_LIB_ZAPI_ENCODE,
+ "%s: Specified family %u is not v4 or v6",
+ __func__, zl->route.prefix.family);
+ return -1;
+ }
+
+ STREAM_GETC(s, zl->route.type);
+ STREAM_GETW(s, zl->route.instance);
+ }
+
+ STREAM_GETW(s, zl->nexthop_num);
+
+ if (zl->nexthop_num > MULTIPATH_NUM) {
+ flog_warn(
+ EC_LIB_ZAPI_ENCODE,
+ "%s: Prefix %pFX has %d nexthops, but we can only use the first %d",
+ __func__, &zl->route.prefix, zl->nexthop_num,
+ MULTIPATH_NUM);
+ }
+
+ zl->nexthop_num = MIN(MULTIPATH_NUM, zl->nexthop_num);
+
+ for (int i = 0; i < zl->nexthop_num; i++) {
+ znh = &zl->nexthops[i];
+
+ if (zapi_nexthop_decode(s, znh, 0, 0) < 0)
+ return -1;
+
+ if (znh->type == NEXTHOP_TYPE_BLACKHOLE) {
+ flog_warn(
+ EC_LIB_ZAPI_ENCODE,
+ "%s: Prefix %pFX has a blackhole nexthop which we cannot use for a label",
+ __func__, &zl->route.prefix);
+ return -1;
+ }
+ }
+
+ if (CHECK_FLAG(zl->message, ZAPI_LABELS_HAS_BACKUPS)) {
+ STREAM_GETW(s, zl->backup_nexthop_num);
+
+ if (zl->backup_nexthop_num > MULTIPATH_NUM) {
+ flog_warn(
+ EC_LIB_ZAPI_ENCODE,
+ "%s: Prefix %pFX has %d backup nexthops, but we can only use the first %d",
+ __func__, &zl->route.prefix,
+ zl->backup_nexthop_num, MULTIPATH_NUM);
+ }
+
+ zl->backup_nexthop_num = MIN(MULTIPATH_NUM,
+ zl->backup_nexthop_num);
+
+ for (int i = 0; i < zl->backup_nexthop_num; i++) {
+ znh = &zl->backup_nexthops[i];
+
+ if (zapi_nexthop_decode(s, znh, 0, 0) < 0)
+ return -1;
+
+ if (znh->type == NEXTHOP_TYPE_BLACKHOLE) {
+ flog_warn(
+ EC_LIB_ZAPI_ENCODE,
+ "%s: Prefix %pFX has a backup blackhole nexthop which we cannot use for a label",
+ __func__, &zl->route.prefix);
+ return -1;
+ }
+ }
+ }
+
+ return 0;
+stream_failure:
+ return -1;
+}
+
+enum zclient_send_status zebra_send_pw(struct zclient *zclient, int command,
+ struct zapi_pw *pw)
+{
+ struct stream *s;
+
+ /* Reset stream. */
+ s = zclient->obuf;
+ stream_reset(s);
+
+ zclient_create_header(s, command, VRF_DEFAULT);
+ stream_write(s, pw->ifname, INTERFACE_NAMSIZ);
+ stream_putl(s, pw->ifindex);
+
+ /* Put type */
+ stream_putl(s, pw->type);
+
+ /* Put nexthop */
+ stream_putl(s, pw->af);
+ switch (pw->af) {
+ case AF_INET:
+ stream_put_in_addr(s, &pw->nexthop.ipv4);
+ break;
+ case AF_INET6:
+ stream_write(s, (uint8_t *)&pw->nexthop.ipv6, 16);
+ break;
+ default:
+ flog_err(EC_LIB_ZAPI_ENCODE, "%s: unknown af", __func__);
+ return ZCLIENT_SEND_FAILURE;
+ }
+
+ /* Put labels */
+ stream_putl(s, pw->local_label);
+ stream_putl(s, pw->remote_label);
+
+ /* Put flags */
+ stream_putc(s, pw->flags);
+
+ /* Protocol specific fields */
+ stream_write(s, &pw->data, sizeof(union pw_protocol_fields));
+
+ /* Put length at the first point of the stream. */
+ stream_putw_at(s, 0, stream_get_endp(s));
+
+ return zclient_send_message(zclient);
+}
+
+/*
+ * Receive PW status update from Zebra and send it to LDE process.
+ */
+int zebra_read_pw_status_update(ZAPI_CALLBACK_ARGS, struct zapi_pw_status *pw)
+{
+ struct stream *s;
+
+ memset(pw, 0, sizeof(struct zapi_pw_status));
+ s = zclient->ibuf;
+
+ /* Get data. */
+ stream_get(pw->ifname, s, INTERFACE_NAMSIZ);
+ STREAM_GETL(s, pw->ifindex);
+ STREAM_GETL(s, pw->status);
+
+ return 0;
+stream_failure:
+ return -1;
+}
+
+static int zclient_capability_decode(ZAPI_CALLBACK_ARGS)
+{
+ struct zclient_capabilities cap;
+ struct stream *s = zclient->ibuf;
+ int vrf_backend;
+ uint8_t mpls_enabled;
+
+ STREAM_GETL(s, vrf_backend);
+
+ if (vrf_backend < 0 || vrf_configure_backend(vrf_backend)) {
+ flog_err(EC_LIB_ZAPI_ENCODE,
+ "%s: Garbage VRF backend type: %d", __func__,
+ vrf_backend);
+ goto stream_failure;
+ }
+
+
+ memset(&cap, 0, sizeof(cap));
+ STREAM_GETC(s, mpls_enabled);
+ cap.mpls_enabled = !!mpls_enabled;
+ STREAM_GETL(s, cap.ecmp);
+ STREAM_GETC(s, cap.role);
+
+ if (zclient->zebra_capabilities)
+ (*zclient->zebra_capabilities)(&cap);
+
+stream_failure:
+ return 0;
+}
+
+enum zclient_send_status zclient_send_mlag_register(struct zclient *client,
+ uint32_t bit_map)
+{
+ struct stream *s;
+
+ s = client->obuf;
+ stream_reset(s);
+
+ zclient_create_header(s, ZEBRA_MLAG_CLIENT_REGISTER, VRF_DEFAULT);
+ stream_putl(s, bit_map);
+
+ stream_putw_at(s, 0, stream_get_endp(s));
+ return zclient_send_message(client);
+}
+
+enum zclient_send_status zclient_send_mlag_deregister(struct zclient *client)
+{
+ return zebra_message_send(client, ZEBRA_MLAG_CLIENT_UNREGISTER,
+ VRF_DEFAULT);
+}
+
+enum zclient_send_status zclient_send_mlag_data(struct zclient *client,
+ struct stream *client_s)
+{
+ struct stream *s;
+
+ s = client->obuf;
+ stream_reset(s);
+
+ zclient_create_header(s, ZEBRA_MLAG_FORWARD_MSG, VRF_DEFAULT);
+ stream_put(s, client_s->data, client_s->endp);
+
+ stream_putw_at(s, 0, stream_get_endp(s));
+ return zclient_send_message(client);
+}
+
+/*
+ * Send an OPAQUE message, contents opaque to zebra. The message header
+ * is a message subtype.
+ */
+enum zclient_send_status zclient_send_opaque(struct zclient *zclient,
+ uint32_t type, const uint8_t *data,
+ size_t datasize)
+{
+ struct stream *s;
+ uint16_t flags = 0;
+
+ /* Check buffer size */
+ if (STREAM_SIZE(zclient->obuf) <
+ (ZEBRA_HEADER_SIZE + sizeof(type) + datasize))
+ return ZCLIENT_SEND_FAILURE;
+
+ s = zclient->obuf;
+ stream_reset(s);
+
+ zclient_create_header(s, ZEBRA_OPAQUE_MESSAGE, VRF_DEFAULT);
+
+ /* Send sub-type and flags */
+ stream_putl(s, type);
+ stream_putw(s, flags);
+
+ /* Send opaque data */
+ stream_write(s, data, datasize);
+
+ /* Put length into the header at the start of the stream. */
+ stream_putw_at(s, 0, stream_get_endp(s));
+
+ return zclient_send_message(zclient);
+}
+
+/*
+ * Send an OPAQUE message to a specific zclient. The contents are opaque
+ * to zebra.
+ */
+enum zclient_send_status
+zclient_send_opaque_unicast(struct zclient *zclient, uint32_t type,
+ uint8_t proto, uint16_t instance,
+ uint32_t session_id, const uint8_t *data,
+ size_t datasize)
+{
+ struct stream *s;
+ uint16_t flags = 0;
+
+ /* Check buffer size */
+ if (STREAM_SIZE(zclient->obuf) <
+ (ZEBRA_HEADER_SIZE + sizeof(struct zapi_opaque_msg) + datasize))
+ return ZCLIENT_SEND_FAILURE;
+
+ s = zclient->obuf;
+ stream_reset(s);
+
+ zclient_create_header(s, ZEBRA_OPAQUE_MESSAGE, VRF_DEFAULT);
+
+ /* Send sub-type and flags */
+ SET_FLAG(flags, ZAPI_OPAQUE_FLAG_UNICAST);
+ stream_putl(s, type);
+ stream_putw(s, flags);
+
+ /* Send destination client info */
+ stream_putc(s, proto);
+ stream_putw(s, instance);
+ stream_putl(s, session_id);
+
+ /* Send opaque data */
+ stream_write(s, data, datasize);
+
+ /* Put length into the header at the start of the stream. */
+ stream_putw_at(s, 0, stream_get_endp(s));
+
+ return zclient_send_message(zclient);
+}
+
+/*
+ * Decode incoming opaque message into info struct
+ */
+int zclient_opaque_decode(struct stream *s, struct zapi_opaque_msg *info)
+{
+ memset(info, 0, sizeof(*info));
+
+ /* Decode subtype and flags */
+ STREAM_GETL(s, info->type);
+ STREAM_GETW(s, info->flags);
+
+ /* Decode unicast client info if present */
+ if (CHECK_FLAG(info->flags, ZAPI_OPAQUE_FLAG_UNICAST)) {
+ STREAM_GETC(s, info->proto);
+ STREAM_GETW(s, info->instance);
+ STREAM_GETL(s, info->session_id);
+ }
+
+ info->len = STREAM_READABLE(s);
+
+ return 0;
+
+stream_failure:
+
+ return -1;
+}
+
+/*
+ * Send a registration request for opaque messages with a specified subtype.
+ */
+enum zclient_send_status zclient_register_opaque(struct zclient *zclient,
+ uint32_t type)
+{
+ struct stream *s;
+
+ s = zclient->obuf;
+ stream_reset(s);
+
+ zclient_create_header(s, ZEBRA_OPAQUE_REGISTER, VRF_DEFAULT);
+
+ /* Send sub-type */
+ stream_putl(s, type);
+
+ /* Add zclient info */
+ stream_putc(s, zclient->redist_default);
+ stream_putw(s, zclient->instance);
+ stream_putl(s, zclient->session_id);
+
+ /* Put length at the first point of the stream. */
+ stream_putw_at(s, 0, stream_get_endp(s));
+
+ return zclient_send_message(zclient);
+}
+
+/*
+ * Send an un-registration request for a specified opaque subtype.
+ */
+enum zclient_send_status zclient_unregister_opaque(struct zclient *zclient,
+ uint32_t type)
+{
+ struct stream *s;
+
+ s = zclient->obuf;
+ stream_reset(s);
+
+ zclient_create_header(s, ZEBRA_OPAQUE_UNREGISTER, VRF_DEFAULT);
+
+ /* Send sub-type */
+ stream_putl(s, type);
+
+ /* Add zclient info */
+ stream_putc(s, zclient->redist_default);
+ stream_putw(s, zclient->instance);
+ stream_putl(s, zclient->session_id);
+
+ /* Put length at the first point of the stream. */
+ stream_putw_at(s, 0, stream_get_endp(s));
+
+ return zclient_send_message(zclient);
+}
+
+/* Utility to decode opaque registration info */
+int zapi_opaque_reg_decode(struct stream *s, struct zapi_opaque_reg_info *info)
+{
+ STREAM_GETL(s, info->type);
+ STREAM_GETC(s, info->proto);
+ STREAM_GETW(s, info->instance);
+ STREAM_GETL(s, info->session_id);
+
+ return 0;
+
+stream_failure:
+
+ return -1;
+}
+
+/* Utility to decode client close notify info */
+int zapi_client_close_notify_decode(struct stream *s,
+ struct zapi_client_close_info *info)
+{
+ memset(info, 0, sizeof(*info));
+
+ STREAM_GETC(s, info->proto);
+ STREAM_GETW(s, info->instance);
+ STREAM_GETL(s, info->session_id);
+
+ return 0;
+
+stream_failure:
+
+ return -1;
+}
+
+static zclient_handler *const lib_handlers[] = {
+ /* fundamentals */
+ [ZEBRA_CAPABILITIES] = zclient_capability_decode,
+ [ZEBRA_ERROR] = zclient_handle_error,
+
+ /* VRF & interface code is shared in lib */
+ [ZEBRA_VRF_ADD] = zclient_vrf_add,
+ [ZEBRA_VRF_DELETE] = zclient_vrf_delete,
+ [ZEBRA_INTERFACE_ADD] = zclient_interface_add,
+ [ZEBRA_INTERFACE_DELETE] = zclient_interface_delete,
+ [ZEBRA_INTERFACE_UP] = zclient_interface_up,
+ [ZEBRA_INTERFACE_DOWN] = zclient_interface_down,
+
+ /* BFD */
+ [ZEBRA_BFD_DEST_REPLAY] = zclient_bfd_session_replay,
+ [ZEBRA_INTERFACE_BFD_DEST_UPDATE] = zclient_bfd_session_update,
+};
+
+/* Zebra client message read function. */
+static void zclient_read(struct thread *thread)
+{
+ size_t already;
+ uint16_t length, command;
+ uint8_t marker, version;
+ vrf_id_t vrf_id;
+ struct zclient *zclient;
+
+ /* Get socket to zebra. */
+ zclient = THREAD_ARG(thread);
+ zclient->t_read = NULL;
+
+ /* Read zebra header (if we don't have it already). */
+ already = stream_get_endp(zclient->ibuf);
+ if (already < ZEBRA_HEADER_SIZE) {
+ ssize_t nbyte;
+ if (((nbyte = stream_read_try(zclient->ibuf, zclient->sock,
+ ZEBRA_HEADER_SIZE - already))
+ == 0)
+ || (nbyte == -1)) {
+ if (zclient_debug)
+ zlog_debug(
+ "zclient connection closed socket [%d].",
+ zclient->sock);
+ zclient_failed(zclient);
+ return;
+ }
+ if (nbyte != (ssize_t)(ZEBRA_HEADER_SIZE - already)) {
+ zclient_event(ZCLIENT_READ, zclient);
+ return;
+ }
+ already = ZEBRA_HEADER_SIZE;
+ }
+
+ /* Reset to read from the beginning of the incoming packet. */
+ stream_set_getp(zclient->ibuf, 0);
+
+ /* Fetch header values. */
+ length = stream_getw(zclient->ibuf);
+ marker = stream_getc(zclient->ibuf);
+ version = stream_getc(zclient->ibuf);
+ vrf_id = stream_getl(zclient->ibuf);
+ command = stream_getw(zclient->ibuf);
+
+ if (marker != ZEBRA_HEADER_MARKER || version != ZSERV_VERSION) {
+ flog_err(
+ EC_LIB_ZAPI_MISSMATCH,
+ "%s: socket %d version mismatch, marker %d, version %d",
+ __func__, zclient->sock, marker, version);
+ zclient_failed(zclient);
+ return;
+ }
+
+ if (length < ZEBRA_HEADER_SIZE) {
+ flog_err(EC_LIB_ZAPI_MISSMATCH,
+ "%s: socket %d message length %u is less than %d ",
+ __func__, zclient->sock, length, ZEBRA_HEADER_SIZE);
+ zclient_failed(zclient);
+ return;
+ }
+
+ /* Length check. */
+ if (length > STREAM_SIZE(zclient->ibuf)) {
+ struct stream *ns;
+ flog_err(
+ EC_LIB_ZAPI_ENCODE,
+ "%s: message size %u exceeds buffer size %lu, expanding...",
+ __func__, length,
+ (unsigned long)STREAM_SIZE(zclient->ibuf));
+ ns = stream_new(length);
+ stream_copy(ns, zclient->ibuf);
+ stream_free(zclient->ibuf);
+ zclient->ibuf = ns;
+ }
+
+ /* Read rest of zebra packet. */
+ if (already < length) {
+ ssize_t nbyte;
+ if (((nbyte = stream_read_try(zclient->ibuf, zclient->sock,
+ length - already))
+ == 0)
+ || (nbyte == -1)) {
+ if (zclient_debug)
+ zlog_debug(
+ "zclient connection closed socket [%d].",
+ zclient->sock);
+ zclient_failed(zclient);
+ return;
+ }
+ if (nbyte != (ssize_t)(length - already)) {
+ /* Try again later. */
+ zclient_event(ZCLIENT_READ, zclient);
+ return;
+ }
+ }
+
+ length -= ZEBRA_HEADER_SIZE;
+
+ if (zclient_debug)
+ zlog_debug("zclient %p command %s VRF %u", zclient,
+ zserv_command_string(command), vrf_id);
+
+ if (command < array_size(lib_handlers) && lib_handlers[command])
+ lib_handlers[command](command, zclient, length, vrf_id);
+ if (command < zclient->n_handlers && zclient->handlers[command])
+ zclient->handlers[command](command, zclient, length, vrf_id);
+
+ if (zclient->sock < 0)
+ /* Connection was closed during packet processing. */
+ return;
+
+ /* Register read thread. */
+ stream_reset(zclient->ibuf);
+ zclient_event(ZCLIENT_READ, zclient);
+}
+
+void zclient_redistribute(int command, struct zclient *zclient, afi_t afi,
+ int type, unsigned short instance, vrf_id_t vrf_id)
+{
+
+ if (instance) {
+ if (command == ZEBRA_REDISTRIBUTE_ADD) {
+ if (redist_check_instance(
+ &zclient->mi_redist[afi][type], instance))
+ return;
+ redist_add_instance(&zclient->mi_redist[afi][type],
+ instance);
+ } else {
+ if (!redist_check_instance(
+ &zclient->mi_redist[afi][type], instance))
+ return;
+ redist_del_instance(&zclient->mi_redist[afi][type],
+ instance);
+ }
+
+ } else {
+ if (command == ZEBRA_REDISTRIBUTE_ADD) {
+ if (vrf_bitmap_check(zclient->redist[afi][type],
+ vrf_id))
+ return;
+ vrf_bitmap_set(zclient->redist[afi][type], vrf_id);
+ } else {
+ if (!vrf_bitmap_check(zclient->redist[afi][type],
+ vrf_id))
+ return;
+ vrf_bitmap_unset(zclient->redist[afi][type], vrf_id);
+ }
+ }
+
+ if (zclient->sock > 0)
+ zebra_redistribute_send(command, zclient, afi, type, instance,
+ vrf_id);
+}
+
+
+void zclient_redistribute_default(int command, struct zclient *zclient,
+ afi_t afi, vrf_id_t vrf_id)
+{
+
+ if (command == ZEBRA_REDISTRIBUTE_DEFAULT_ADD) {
+ if (vrf_bitmap_check(zclient->default_information[afi], vrf_id))
+ return;
+ vrf_bitmap_set(zclient->default_information[afi], vrf_id);
+ } else {
+ if (!vrf_bitmap_check(zclient->default_information[afi],
+ vrf_id))
+ return;
+ vrf_bitmap_unset(zclient->default_information[afi], vrf_id);
+ }
+
+ if (zclient->sock > 0)
+ zebra_redistribute_default_send(command, zclient, afi, vrf_id);
+}
+
+static void zclient_event(enum zclient_event event, struct zclient *zclient)
+{
+ switch (event) {
+ case ZCLIENT_SCHEDULE:
+ thread_add_event(zclient->master, zclient_connect, zclient, 0,
+ &zclient->t_connect);
+ break;
+ case ZCLIENT_CONNECT:
+ if (zclient_debug)
+ zlog_debug(
+ "zclient connect failures: %d schedule interval is now %d",
+ zclient->fail, zclient->fail < 3 ? 10 : 60);
+ thread_add_timer(zclient->master, zclient_connect, zclient,
+ zclient->fail < 3 ? 10 : 60,
+ &zclient->t_connect);
+ break;
+ case ZCLIENT_READ:
+ zclient->t_read = NULL;
+ thread_add_read(zclient->master, zclient_read, zclient,
+ zclient->sock, &zclient->t_read);
+ break;
+ }
+}
+
+enum zclient_send_status zclient_interface_set_master(struct zclient *client,
+ struct interface *master,
+ struct interface *slave)
+{
+ struct stream *s;
+
+ s = client->obuf;
+ stream_reset(s);
+
+ zclient_create_header(s, ZEBRA_INTERFACE_SET_MASTER,
+ master->vrf->vrf_id);
+
+ stream_putl(s, master->vrf->vrf_id);
+ stream_putl(s, master->ifindex);
+ stream_putl(s, slave->vrf->vrf_id);
+ stream_putl(s, slave->ifindex);
+
+ stream_putw_at(s, 0, stream_get_endp(s));
+ return zclient_send_message(client);
+}
+
+/*
+ * Send capabilities message to zebra
+ */
+enum zclient_send_status zclient_capabilities_send(uint32_t cmd,
+ struct zclient *zclient,
+ struct zapi_cap *api)
+{
+
+ struct stream *s;
+
+ if (zclient == NULL)
+ return ZCLIENT_SEND_FAILURE;
+
+ s = zclient->obuf;
+ stream_reset(s);
+ zclient_create_header(s, cmd, 0);
+ stream_putl(s, api->cap);
+
+ switch (api->cap) {
+ case ZEBRA_CLIENT_GR_CAPABILITIES:
+ case ZEBRA_CLIENT_RIB_STALE_TIME:
+ stream_putl(s, api->stale_removal_time);
+ stream_putl(s, api->vrf_id);
+ break;
+ case ZEBRA_CLIENT_ROUTE_UPDATE_COMPLETE:
+ case ZEBRA_CLIENT_ROUTE_UPDATE_PENDING:
+ stream_putl(s, api->afi);
+ stream_putl(s, api->safi);
+ stream_putl(s, api->vrf_id);
+ break;
+ case ZEBRA_CLIENT_GR_DISABLE:
+ stream_putl(s, api->vrf_id);
+ break;
+ }
+
+ /* Put length at the first point of the stream */
+ stream_putw_at(s, 0, stream_get_endp(s));
+
+ return zclient_send_message(zclient);
+}
+
+/*
+ * Process capabilities message from zebra
+ */
+int32_t zapi_capabilities_decode(struct stream *s, struct zapi_cap *api)
+{
+
+ memset(api, 0, sizeof(*api));
+
+ STREAM_GETL(s, api->cap);
+ switch (api->cap) {
+ case ZEBRA_CLIENT_GR_CAPABILITIES:
+ case ZEBRA_CLIENT_RIB_STALE_TIME:
+ STREAM_GETL(s, api->stale_removal_time);
+ STREAM_GETL(s, api->vrf_id);
+ break;
+ case ZEBRA_CLIENT_ROUTE_UPDATE_COMPLETE:
+ case ZEBRA_CLIENT_ROUTE_UPDATE_PENDING:
+ STREAM_GETL(s, api->afi);
+ STREAM_GETL(s, api->safi);
+ STREAM_GETL(s, api->vrf_id);
+ break;
+ case ZEBRA_CLIENT_GR_DISABLE:
+ STREAM_GETL(s, api->vrf_id);
+ break;
+ }
+stream_failure:
+ return 0;
+}
+
+enum zclient_send_status
+zclient_send_neigh_discovery_req(struct zclient *zclient,
+ const struct interface *ifp,
+ const struct prefix *p)
+{
+ struct stream *s;
+
+ s = zclient->obuf;
+ stream_reset(s);
+
+ zclient_create_header(s, ZEBRA_NEIGH_DISCOVER, ifp->vrf->vrf_id);
+ stream_putl(s, ifp->ifindex);
+
+ stream_putc(s, p->family);
+ stream_putc(s, p->prefixlen);
+ stream_put(s, &p->u.prefix, prefix_blen(p));
+
+ stream_putw_at(s, 0, stream_get_endp(s));
+ return zclient_send_message(zclient);
+}
+
+/*
+ * Get a starting nhg point for a routing protocol
+ */
+uint32_t zclient_get_nhg_start(uint32_t proto)
+{
+ assert(proto < ZEBRA_ROUTE_MAX);
+
+ return ZEBRA_NHG_PROTO_SPACING * proto;
+}
+
+char *zclient_dump_route_flags(uint32_t flags, char *buf, size_t len)
+{
+ if (flags == 0) {
+ snprintfrr(buf, len, "None ");
+ return buf;
+ }
+
+ snprintfrr(
+ buf, len, "%s%s%s%s%s%s%s%s%s%s",
+ CHECK_FLAG(flags, ZEBRA_FLAG_ALLOW_RECURSION) ? "Recursion "
+ : "",
+ CHECK_FLAG(flags, ZEBRA_FLAG_SELFROUTE) ? "Self " : "",
+ CHECK_FLAG(flags, ZEBRA_FLAG_IBGP) ? "iBGP " : "",
+ CHECK_FLAG(flags, ZEBRA_FLAG_SELECTED) ? "Selected " : "",
+ CHECK_FLAG(flags, ZEBRA_FLAG_FIB_OVERRIDE) ? "Override " : "",
+ CHECK_FLAG(flags, ZEBRA_FLAG_EVPN_ROUTE) ? "Evpn " : "",
+ CHECK_FLAG(flags, ZEBRA_FLAG_RR_USE_DISTANCE) ? "RR Distance "
+ : "",
+ CHECK_FLAG(flags, ZEBRA_FLAG_TRAPPED) ? "Trapped " : "",
+ CHECK_FLAG(flags, ZEBRA_FLAG_OFFLOADED) ? "Offloaded " : "",
+ CHECK_FLAG(flags, ZEBRA_FLAG_OFFLOAD_FAILED) ? "Offload Failed "
+ : "");
+ return buf;
+}
+
+char *zclient_evpn_dump_macip_flags(uint8_t flags, char *buf, size_t len)
+{
+ if (flags == 0) {
+ snprintfrr(buf, len, "None ");
+ return buf;
+ }
+
+ snprintfrr(
+ buf, len, "%s%s%s%s%s%s%s",
+ CHECK_FLAG(flags, ZEBRA_MACIP_TYPE_STICKY) ? "Sticky MAC " : "",
+ CHECK_FLAG(flags, ZEBRA_MACIP_TYPE_GW) ? "Gateway MAC " : "",
+ CHECK_FLAG(flags, ZEBRA_MACIP_TYPE_ROUTER_FLAG) ? "Router "
+ : "",
+ CHECK_FLAG(flags, ZEBRA_MACIP_TYPE_OVERRIDE_FLAG) ? "Override "
+ : "",
+ CHECK_FLAG(flags, ZEBRA_MACIP_TYPE_SVI_IP) ? "SVI MAC " : "",
+ CHECK_FLAG(flags, ZEBRA_MACIP_TYPE_PROXY_ADVERT) ? "Proxy "
+ : "",
+ CHECK_FLAG(flags, ZEBRA_MACIP_TYPE_SYNC_PATH) ? "Sync " : "");
+
+ return buf;
+}
+
+static int zclient_neigh_ip_read_entry(struct stream *s, struct ipaddr *add)
+{
+ uint8_t family;
+
+ STREAM_GETC(s, family);
+ if (family != AF_INET && family != AF_INET6)
+ return -1;
+
+ STREAM_GET(&add->ip.addr, s, family2addrsize(family));
+ add->ipa_type = family;
+ return 0;
+ stream_failure:
+ return -1;
+}
+
+int zclient_neigh_ip_encode(struct stream *s, uint16_t cmd, union sockunion *in,
+ union sockunion *out, struct interface *ifp,
+ int ndm_state)
+{
+ int ret = 0;
+
+ zclient_create_header(s, cmd, ifp->vrf->vrf_id);
+ stream_putc(s, sockunion_family(in));
+ stream_write(s, sockunion_get_addr(in), sockunion_get_addrlen(in));
+ if (out && sockunion_family(out) != AF_UNSPEC) {
+ stream_putc(s, sockunion_family(out));
+ stream_write(s, sockunion_get_addr(out),
+ sockunion_get_addrlen(out));
+ } else
+ stream_putc(s, AF_UNSPEC);
+ stream_putl(s, ifp->ifindex);
+ if (out)
+ stream_putl(s, ndm_state);
+ else
+ stream_putl(s, ZEBRA_NEIGH_STATE_FAILED);
+ return ret;
+}
+
+int zclient_neigh_ip_decode(struct stream *s, struct zapi_neigh_ip *api)
+{
+ int ret;
+
+ ret = zclient_neigh_ip_read_entry(s, &api->ip_in);
+ if (ret < 0)
+ return -1;
+ zclient_neigh_ip_read_entry(s, &api->ip_out);
+
+ STREAM_GETL(s, api->index);
+ STREAM_GETL(s, api->ndm_state);
+ return 0;
+ stream_failure:
+ return -1;
+}
+
+int zclient_send_zebra_gre_request(struct zclient *client,
+ struct interface *ifp)
+{
+ struct stream *s;
+
+ if (!client || client->sock < 0) {
+ zlog_err("%s : zclient not ready", __func__);
+ return -1;
+ }
+ s = client->obuf;
+ stream_reset(s);
+ zclient_create_header(s, ZEBRA_GRE_GET, ifp->vrf->vrf_id);
+ stream_putl(s, ifp->ifindex);
+ stream_putw_at(s, 0, stream_get_endp(s));
+ zclient_send_message(client);
+ return 0;
+}
diff --git a/lib/zclient.h b/lib/zclient.h
new file mode 100644
index 0000000..c3ea2a1
--- /dev/null
+++ b/lib/zclient.h
@@ -0,0 +1,1261 @@
+/* Zebra's client header.
+ * Copyright (C) 1999 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option)
+ * any later version.
+ *
+ * GNU Zebra 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 _ZEBRA_ZCLIENT_H
+#define _ZEBRA_ZCLIENT_H
+
+struct zclient;
+
+/* For struct zapi_route. */
+#include "prefix.h"
+#include "ipaddr.h"
+
+/* For struct interface and struct connected. */
+#include "if.h"
+
+/* For vrf_bitmap_t. */
+#include "vrf.h"
+
+/* For union g_addr */
+#include "nexthop.h"
+
+/* For union pw_protocol_fields */
+#include "pw.h"
+
+#include "mlag.h"
+#include "srte.h"
+#include "srv6.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Zebra types. Used in Zserv message header. */
+typedef uint16_t zebra_size_t;
+
+/* Marker value used in new Zserv, in the byte location corresponding
+ * the command value in the old zserv header. To allow old and new
+ * Zserv headers to be distinguished from each other.
+ */
+#define ZEBRA_HEADER_MARKER 254
+
+/* For input/output buffer to zebra. */
+#define ZEBRA_MAX_PACKET_SIZ 16384U
+#define ZEBRA_SMALL_PACKET_SIZE 200U
+
+/* Zebra header size. */
+#define ZEBRA_HEADER_SIZE 10
+
+/* special socket path name to use TCP
+ * @ is used as first character because that's abstract socket names on Linux
+ */
+#define ZAPI_TCP_PATHNAME "@tcp"
+
+/* IPset size name stands for the name of the ipset entry
+ * that can be created by using some zapi interfaces
+ */
+#define ZEBRA_IPSET_NAME_SIZE 32
+
+/* IPTable action is defined by two values: either
+ * forward or drop
+ */
+#define ZEBRA_IPTABLES_FORWARD 0
+#define ZEBRA_IPTABLES_DROP 1
+
+/* Zebra FEC register command flags. */
+#define ZEBRA_FEC_REGISTER_LABEL 0x1
+#define ZEBRA_FEC_REGISTER_LABEL_INDEX 0x2
+
+/* Client capabilities */
+enum zserv_client_capabilities {
+ ZEBRA_CLIENT_GR_CAPABILITIES = 1,
+ ZEBRA_CLIENT_ROUTE_UPDATE_COMPLETE = 2,
+ ZEBRA_CLIENT_ROUTE_UPDATE_PENDING = 3,
+ ZEBRA_CLIENT_GR_DISABLE = 4,
+ ZEBRA_CLIENT_RIB_STALE_TIME
+};
+
+/* Macro to check if there GR enabled. */
+#define ZEBRA_CLIENT_GR_ENABLED(X) (X == ZEBRA_CLIENT_GR_CAPABILITIES)
+
+#define ZEBRA_SR_POLICY_NAME_MAX_LENGTH 100
+
+extern struct sockaddr_storage zclient_addr;
+extern socklen_t zclient_addr_len;
+
+/* Zebra message types. */
+typedef enum {
+ ZEBRA_INTERFACE_ADD,
+ ZEBRA_INTERFACE_DELETE,
+ ZEBRA_INTERFACE_ADDRESS_ADD,
+ ZEBRA_INTERFACE_ADDRESS_DELETE,
+ ZEBRA_INTERFACE_UP,
+ ZEBRA_INTERFACE_DOWN,
+ ZEBRA_INTERFACE_SET_MASTER,
+ ZEBRA_INTERFACE_SET_PROTODOWN,
+ ZEBRA_ROUTE_ADD,
+ ZEBRA_ROUTE_DELETE,
+ ZEBRA_ROUTE_NOTIFY_OWNER,
+ ZEBRA_REDISTRIBUTE_ADD,
+ ZEBRA_REDISTRIBUTE_DELETE,
+ ZEBRA_REDISTRIBUTE_DEFAULT_ADD,
+ ZEBRA_REDISTRIBUTE_DEFAULT_DELETE,
+ ZEBRA_ROUTER_ID_ADD,
+ ZEBRA_ROUTER_ID_DELETE,
+ ZEBRA_ROUTER_ID_UPDATE,
+ ZEBRA_HELLO,
+ ZEBRA_CAPABILITIES,
+ ZEBRA_NEXTHOP_REGISTER,
+ ZEBRA_NEXTHOP_UNREGISTER,
+ ZEBRA_NEXTHOP_UPDATE,
+ ZEBRA_INTERFACE_NBR_ADDRESS_ADD,
+ ZEBRA_INTERFACE_NBR_ADDRESS_DELETE,
+ ZEBRA_INTERFACE_BFD_DEST_UPDATE,
+ ZEBRA_BFD_DEST_REGISTER,
+ ZEBRA_BFD_DEST_DEREGISTER,
+ ZEBRA_BFD_DEST_UPDATE,
+ ZEBRA_BFD_DEST_REPLAY,
+ ZEBRA_REDISTRIBUTE_ROUTE_ADD,
+ ZEBRA_REDISTRIBUTE_ROUTE_DEL,
+ ZEBRA_VRF_UNREGISTER,
+ ZEBRA_VRF_ADD,
+ ZEBRA_VRF_DELETE,
+ ZEBRA_VRF_LABEL,
+ ZEBRA_INTERFACE_VRF_UPDATE,
+ ZEBRA_BFD_CLIENT_REGISTER,
+ ZEBRA_BFD_CLIENT_DEREGISTER,
+ ZEBRA_INTERFACE_ENABLE_RADV,
+ ZEBRA_INTERFACE_DISABLE_RADV,
+ ZEBRA_NEXTHOP_LOOKUP_MRIB,
+ ZEBRA_INTERFACE_LINK_PARAMS,
+ ZEBRA_MPLS_LABELS_ADD,
+ ZEBRA_MPLS_LABELS_DELETE,
+ ZEBRA_MPLS_LABELS_REPLACE,
+ ZEBRA_SR_POLICY_SET,
+ ZEBRA_SR_POLICY_DELETE,
+ ZEBRA_SR_POLICY_NOTIFY_STATUS,
+ ZEBRA_IPMR_ROUTE_STATS,
+ ZEBRA_LABEL_MANAGER_CONNECT,
+ ZEBRA_LABEL_MANAGER_CONNECT_ASYNC,
+ ZEBRA_GET_LABEL_CHUNK,
+ ZEBRA_RELEASE_LABEL_CHUNK,
+ ZEBRA_FEC_REGISTER,
+ ZEBRA_FEC_UNREGISTER,
+ ZEBRA_FEC_UPDATE,
+ ZEBRA_ADVERTISE_DEFAULT_GW,
+ ZEBRA_ADVERTISE_SVI_MACIP,
+ ZEBRA_ADVERTISE_SUBNET,
+ ZEBRA_ADVERTISE_ALL_VNI,
+ ZEBRA_LOCAL_ES_ADD,
+ ZEBRA_LOCAL_ES_DEL,
+ ZEBRA_REMOTE_ES_VTEP_ADD,
+ ZEBRA_REMOTE_ES_VTEP_DEL,
+ ZEBRA_LOCAL_ES_EVI_ADD,
+ ZEBRA_LOCAL_ES_EVI_DEL,
+ ZEBRA_VNI_ADD,
+ ZEBRA_VNI_DEL,
+ ZEBRA_L3VNI_ADD,
+ ZEBRA_L3VNI_DEL,
+ ZEBRA_REMOTE_VTEP_ADD,
+ ZEBRA_REMOTE_VTEP_DEL,
+ ZEBRA_MACIP_ADD,
+ ZEBRA_MACIP_DEL,
+ ZEBRA_IP_PREFIX_ROUTE_ADD,
+ ZEBRA_IP_PREFIX_ROUTE_DEL,
+ ZEBRA_REMOTE_MACIP_ADD,
+ ZEBRA_REMOTE_MACIP_DEL,
+ ZEBRA_DUPLICATE_ADDR_DETECTION,
+ ZEBRA_PW_ADD,
+ ZEBRA_PW_DELETE,
+ ZEBRA_PW_SET,
+ ZEBRA_PW_UNSET,
+ ZEBRA_PW_STATUS_UPDATE,
+ ZEBRA_RULE_ADD,
+ ZEBRA_RULE_DELETE,
+ ZEBRA_RULE_NOTIFY_OWNER,
+ ZEBRA_TABLE_MANAGER_CONNECT,
+ ZEBRA_GET_TABLE_CHUNK,
+ ZEBRA_RELEASE_TABLE_CHUNK,
+ ZEBRA_IPSET_CREATE,
+ ZEBRA_IPSET_DESTROY,
+ ZEBRA_IPSET_ENTRY_ADD,
+ ZEBRA_IPSET_ENTRY_DELETE,
+ ZEBRA_IPSET_NOTIFY_OWNER,
+ ZEBRA_IPSET_ENTRY_NOTIFY_OWNER,
+ ZEBRA_IPTABLE_ADD,
+ ZEBRA_IPTABLE_DELETE,
+ ZEBRA_IPTABLE_NOTIFY_OWNER,
+ ZEBRA_VXLAN_FLOOD_CONTROL,
+ ZEBRA_VXLAN_SG_ADD,
+ ZEBRA_VXLAN_SG_DEL,
+ ZEBRA_VXLAN_SG_REPLAY,
+ ZEBRA_MLAG_PROCESS_UP,
+ ZEBRA_MLAG_PROCESS_DOWN,
+ ZEBRA_MLAG_CLIENT_REGISTER,
+ ZEBRA_MLAG_CLIENT_UNREGISTER,
+ ZEBRA_MLAG_FORWARD_MSG,
+ ZEBRA_NHG_ADD,
+ ZEBRA_NHG_DEL,
+ ZEBRA_NHG_NOTIFY_OWNER,
+ ZEBRA_EVPN_REMOTE_NH_ADD,
+ ZEBRA_EVPN_REMOTE_NH_DEL,
+ ZEBRA_SRV6_LOCATOR_ADD,
+ ZEBRA_SRV6_LOCATOR_DELETE,
+ ZEBRA_SRV6_MANAGER_GET_LOCATOR_CHUNK,
+ ZEBRA_SRV6_MANAGER_RELEASE_LOCATOR_CHUNK,
+ ZEBRA_ERROR,
+ ZEBRA_CLIENT_CAPABILITIES,
+ ZEBRA_OPAQUE_MESSAGE,
+ ZEBRA_OPAQUE_REGISTER,
+ ZEBRA_OPAQUE_UNREGISTER,
+ ZEBRA_NEIGH_DISCOVER,
+ ZEBRA_ROUTE_NOTIFY_REQUEST,
+ ZEBRA_CLIENT_CLOSE_NOTIFY,
+ ZEBRA_NHRP_NEIGH_ADDED,
+ ZEBRA_NHRP_NEIGH_REMOVED,
+ ZEBRA_NHRP_NEIGH_GET,
+ ZEBRA_NHRP_NEIGH_REGISTER,
+ ZEBRA_NHRP_NEIGH_UNREGISTER,
+ ZEBRA_NEIGH_IP_ADD,
+ ZEBRA_NEIGH_IP_DEL,
+ ZEBRA_CONFIGURE_ARP,
+ ZEBRA_GRE_GET,
+ ZEBRA_GRE_UPDATE,
+ ZEBRA_GRE_SOURCE_SET,
+} zebra_message_types_t;
+
+enum zebra_error_types {
+ ZEBRA_UNKNOWN_ERROR, /* Error of unknown type */
+ ZEBRA_NO_VRF, /* Vrf in header was not found */
+ ZEBRA_INVALID_MSG_TYPE, /* No handler found for msg type */
+};
+
+static inline const char *zebra_error_type2str(enum zebra_error_types type)
+{
+ const char *ret = "UNKNOWN";
+
+ switch (type) {
+ case ZEBRA_UNKNOWN_ERROR:
+ ret = "ZEBRA_UNKNOWN_ERROR";
+ break;
+ case ZEBRA_NO_VRF:
+ ret = "ZEBRA_NO_VRF";
+ break;
+ case ZEBRA_INVALID_MSG_TYPE:
+ ret = "ZEBRA_INVALID_MSG_TYPE";
+ break;
+ }
+
+ return ret;
+}
+
+struct redist_proto {
+ uint8_t enabled;
+ struct list *instances;
+};
+
+struct zclient_capabilities {
+ uint32_t ecmp;
+ bool mpls_enabled;
+ enum mlag_role role;
+};
+
+/* Graceful Restart Capabilities message */
+struct zapi_cap {
+ enum zserv_client_capabilities cap;
+ uint32_t stale_removal_time;
+ afi_t afi;
+ safi_t safi;
+ vrf_id_t vrf_id;
+};
+
+/* clang-format off */
+#define ZAPI_CALLBACK_ARGS \
+ int cmd, struct zclient *zclient, uint16_t length, vrf_id_t vrf_id
+
+/* function-type typedef (pointer not included) */
+typedef int (zclient_handler)(ZAPI_CALLBACK_ARGS);
+/* clang-format on */
+
+/* Structure for the zebra client. */
+struct zclient {
+ /* The thread master we schedule ourselves on */
+ struct thread_master *master;
+
+ /* Privileges to change socket values */
+ struct zebra_privs_t *privs;
+
+ /* Do we care about failure events for route install? */
+ bool receive_notify;
+
+ /* Is this a synchronous client? */
+ bool synchronous;
+
+ /* BFD enabled with bfd_protocol_integration_init() */
+ bool bfd_integration;
+
+ /* Session id (optional) to support clients with multiple sessions */
+ uint32_t session_id;
+
+ /* Socket to zebra daemon. */
+ int sock;
+
+ /* Connection failure count. */
+ int fail;
+
+ /* Input buffer for zebra message. */
+ struct stream *ibuf;
+
+ /* Output buffer for zebra message. */
+ struct stream *obuf;
+
+ /* Buffer of data waiting to be written to zebra. */
+ struct buffer *wb;
+
+ /* Read and connect thread. */
+ struct thread *t_read;
+ struct thread *t_connect;
+
+ /* Thread to write buffered data to zebra. */
+ struct thread *t_write;
+
+ /* Redistribute information. */
+ uint8_t redist_default; /* clients protocol */
+ unsigned short instance;
+ struct redist_proto mi_redist[AFI_MAX][ZEBRA_ROUTE_MAX];
+ vrf_bitmap_t redist[AFI_MAX][ZEBRA_ROUTE_MAX];
+
+ /* Redistribute default. */
+ vrf_bitmap_t default_information[AFI_MAX];
+
+ /* Pointer to the callback functions. */
+ void (*zebra_connected)(struct zclient *);
+ void (*zebra_capabilities)(struct zclient_capabilities *cap);
+
+ int (*handle_error)(enum zebra_error_types error);
+
+ /*
+ * When the zclient attempts to write the stream data to
+ * it's named pipe to/from zebra, we may have a situation
+ * where the other daemon has not fully drained the data
+ * from the socket. In this case provide a mechanism
+ * where we will *still* buffer the data to be sent
+ * and also provide a callback mechanism to the appropriate
+ * place where we can signal that we're ready to receive
+ * more data.
+ */
+ void (*zebra_buffer_write_ready)(void);
+
+ zclient_handler *const *handlers;
+ size_t n_handlers;
+};
+
+/* lib handlers added in bfd.c */
+extern int zclient_bfd_session_replay(ZAPI_CALLBACK_ARGS);
+extern int zclient_bfd_session_update(ZAPI_CALLBACK_ARGS);
+
+/* Zebra API message flag. */
+#define ZAPI_MESSAGE_NEXTHOP 0x01
+#define ZAPI_MESSAGE_DISTANCE 0x02
+#define ZAPI_MESSAGE_METRIC 0x04
+#define ZAPI_MESSAGE_TAG 0x08
+#define ZAPI_MESSAGE_MTU 0x10
+#define ZAPI_MESSAGE_SRCPFX 0x20
+/* Backup nexthops are present */
+#define ZAPI_MESSAGE_BACKUP_NEXTHOPS 0x40
+#define ZAPI_MESSAGE_NHG 0x80
+/*
+ * This should only be used by a DAEMON that needs to communicate
+ * the table being used is not in the VRF. You must pass the
+ * default vrf, else this will be ignored.
+ */
+#define ZAPI_MESSAGE_TABLEID 0x0100
+#define ZAPI_MESSAGE_SRTE 0x0200
+#define ZAPI_MESSAGE_OPAQUE 0x0400
+
+#define ZSERV_VERSION 6
+/* Zserv protocol message header */
+struct zmsghdr {
+ uint16_t length;
+ /* Always set to 255 in new zserv */
+ uint8_t marker;
+ uint8_t version;
+ vrf_id_t vrf_id;
+ uint16_t command;
+} __attribute__((packed));
+#define ZAPI_HEADER_CMD_LOCATION offsetof(struct zmsghdr, command)
+
+/*
+ * ZAPI nexthop. Note that these are sorted when associated with ZAPI routes,
+ * and that sorting must be aligned with the sorting of nexthops in
+ * lib/nexthop.c. Any new fields must be accounted for in zapi_nexthop_cmp().
+ */
+struct zapi_nexthop {
+ enum nexthop_types_t type;
+ vrf_id_t vrf_id;
+ ifindex_t ifindex;
+ uint8_t flags;
+ union {
+ union g_addr gate;
+ enum blackhole_type bh_type;
+ };
+
+ /* MPLS labels for BGP-LU or Segment Routing */
+ uint8_t label_num;
+ mpls_label_t labels[MPLS_MAX_LABELS];
+
+ struct ethaddr rmac;
+
+ uint32_t weight;
+
+ /* Backup nexthops, for IP-FRR, TI-LFA, etc */
+ uint8_t backup_num;
+ uint8_t backup_idx[NEXTHOP_MAX_BACKUPS];
+
+ /* SR-TE color. */
+ uint32_t srte_color;
+
+ /* SRv6 localsid info for Endpoint-behaviour */
+ uint32_t seg6local_action;
+ struct seg6local_context seg6local_ctx;
+
+ /* SRv6 Headend-behaviour */
+ struct in6_addr seg6_segs;
+};
+
+/*
+ * ZAPI nexthop flags values - we're encoding a single octet
+ * initially, so ensure that the on-the-wire encoding continues
+ * to match the number of valid flags.
+ */
+
+#define ZAPI_NEXTHOP_FLAG_ONLINK 0x01
+#define ZAPI_NEXTHOP_FLAG_LABEL 0x02
+#define ZAPI_NEXTHOP_FLAG_WEIGHT 0x04
+#define ZAPI_NEXTHOP_FLAG_HAS_BACKUP 0x08 /* Nexthop has a backup */
+#define ZAPI_NEXTHOP_FLAG_SEG6 0x10
+#define ZAPI_NEXTHOP_FLAG_SEG6LOCAL 0x20
+#define ZAPI_NEXTHOP_FLAG_EVPN 0x40
+
+/*
+ * ZAPI Nexthop Group. For use with protocol creation of nexthop groups.
+ */
+struct zapi_nhg {
+ uint16_t proto;
+ uint32_t id;
+
+ uint16_t nexthop_num;
+ struct zapi_nexthop nexthops[MULTIPATH_NUM];
+
+ uint16_t backup_nexthop_num;
+ struct zapi_nexthop backup_nexthops[MULTIPATH_NUM];
+};
+
+/*
+ * Some of these data structures do not map easily to
+ * a actual data structure size giving different compilers
+ * and systems. For those data structures we need
+ * to use the smallest available stream_getX/putX functions
+ * to encode/decode.
+ */
+struct zapi_route {
+ uint8_t type;
+ unsigned short instance;
+
+ /* If you add flags, update zclient_dump_route_flags */
+ uint32_t flags;
+/*
+ * Cause Zebra to consider this routes nexthops recursively
+ */
+#define ZEBRA_FLAG_ALLOW_RECURSION 0x01
+/*
+ * This is a route that is read in on startup that was left around
+ * from a previous run of FRR
+ */
+#define ZEBRA_FLAG_SELFROUTE 0x02
+/*
+ * This flag is used to tell Zebra that the BGP route being passed
+ * down is a IBGP route
+ */
+#define ZEBRA_FLAG_IBGP 0x04
+/*
+ * This is a route that has been selected for FIB installation.
+ * This flag is set in zebra and can be passed up to routing daemons
+ */
+#define ZEBRA_FLAG_SELECTED 0x08
+/*
+ * This is a route that we are telling Zebra that this route *must*
+ * win and will be installed even over ZEBRA_FLAG_SELECTED
+ */
+#define ZEBRA_FLAG_FIB_OVERRIDE 0x10
+/*
+ * This flag tells Zebra that the route is a EVPN route and should
+ * be treated specially
+ */
+#define ZEBRA_FLAG_EVPN_ROUTE 0x20
+/*
+ * This flag tells Zebra that it should treat the distance passed
+ * down as an additional discriminator for route selection of the
+ * route entry. This mainly is used for backup static routes.
+ */
+#define ZEBRA_FLAG_RR_USE_DISTANCE 0x40
+/*
+ * This flag tells everyone that the route was intentionally
+ * not offloaded and the route will be sent to the cpu for
+ * forwarding. This flag makes no sense unless you are in
+ * an asic offload situation
+ */
+#define ZEBRA_FLAG_TRAPPED 0x80
+/*
+ * This flag tells everyone that the route has been
+ * successfully offloaded to an asic for forwarding.
+ * This flag makes no sense unless you are in an asic
+ * offload situation.
+ */
+#define ZEBRA_FLAG_OFFLOADED 0x100
+/*
+ * This flag tells everyone that the route has
+ * failed offloading.
+ * This flag makes no sense unless you are in an asic
+ * offload situation.
+ */
+#define ZEBRA_FLAG_OFFLOAD_FAILED 0x200
+
+ /* The older XXX_MESSAGE flags live here */
+ uint32_t message;
+
+ /*
+ * This is an enum but we are going to treat it as a uint8_t
+ * for purpose of encoding/decoding
+ */
+ safi_t safi;
+
+ struct prefix prefix;
+ struct prefix_ipv6 src_prefix;
+
+ uint16_t nexthop_num;
+ struct zapi_nexthop nexthops[MULTIPATH_NUM];
+
+ /* Support backup routes for IP FRR, TI-LFA, traffic engineering */
+ uint16_t backup_nexthop_num;
+ struct zapi_nexthop backup_nexthops[MULTIPATH_NUM];
+
+ uint32_t nhgid;
+
+ uint8_t distance;
+
+ uint32_t metric;
+
+ route_tag_t tag;
+
+ uint32_t mtu;
+
+ vrf_id_t vrf_id;
+
+ uint32_t tableid;
+
+ /* SR-TE color (used for nexthop updates only). */
+ uint32_t srte_color;
+
+#define ZAPI_MESSAGE_OPAQUE_LENGTH 1024
+ struct {
+ uint16_t length;
+ uint8_t data[ZAPI_MESSAGE_OPAQUE_LENGTH];
+ } opaque;
+};
+
+extern char *zclient_dump_route_flags(uint32_t flags, char *buf, size_t len);
+
+struct zapi_labels {
+ uint8_t message;
+#define ZAPI_LABELS_FTN 0x01
+#define ZAPI_LABELS_HAS_BACKUPS 0x02
+ enum lsp_types_t type;
+ mpls_label_t local_label;
+ struct {
+ struct prefix prefix;
+ uint8_t type;
+ unsigned short instance;
+ } route;
+
+ uint16_t nexthop_num;
+ struct zapi_nexthop nexthops[MULTIPATH_NUM];
+
+ /* Backup nexthops, if present */
+ uint16_t backup_nexthop_num;
+ struct zapi_nexthop backup_nexthops[MULTIPATH_NUM];
+};
+
+struct zapi_srte_tunnel {
+ enum lsp_types_t type;
+ mpls_label_t local_label;
+ uint8_t label_num;
+ mpls_label_t labels[MPLS_MAX_LABELS];
+};
+
+struct zapi_sr_policy {
+ uint32_t color;
+ struct ipaddr endpoint;
+ char name[SRTE_POLICY_NAME_MAX_LENGTH];
+ struct zapi_srte_tunnel segment_list;
+ int status;
+};
+
+struct zapi_pw {
+ char ifname[INTERFACE_NAMSIZ];
+ ifindex_t ifindex;
+ int type;
+ int af;
+ union g_addr nexthop;
+ uint32_t local_label;
+ uint32_t remote_label;
+ uint8_t flags;
+ union pw_protocol_fields data;
+ uint8_t protocol;
+};
+
+struct zapi_pw_status {
+ char ifname[INTERFACE_NAMSIZ];
+ ifindex_t ifindex;
+ uint32_t status;
+};
+
+/* IGP instance data associated to a RLFA. */
+struct zapi_rlfa_igp {
+ vrf_id_t vrf_id;
+ int protocol;
+ union {
+ struct {
+ char area_tag[32];
+ struct {
+ int tree_id;
+ int level;
+ unsigned int run_id;
+ } spf;
+ } isis;
+ };
+};
+
+/* IGP -> LDP RLFA (un)registration message. */
+struct zapi_rlfa_request {
+ /* IGP instance data. */
+ struct zapi_rlfa_igp igp;
+
+ /* Destination prefix. */
+ struct prefix destination;
+
+ /* PQ node address. */
+ struct in_addr pq_address;
+};
+
+/* LDP -> IGP RLFA label update. */
+struct zapi_rlfa_response {
+ /* IGP instance data. */
+ struct zapi_rlfa_igp igp;
+
+ /* Destination prefix. */
+ struct prefix destination;
+
+ /* Resolved LDP labels. */
+ mpls_label_t pq_label;
+ uint16_t nexthop_num;
+ struct {
+ int family;
+ union g_addr gate;
+ mpls_label_t label;
+ } nexthops[MULTIPATH_NUM];
+};
+
+enum zapi_route_notify_owner {
+ ZAPI_ROUTE_FAIL_INSTALL,
+ ZAPI_ROUTE_BETTER_ADMIN_WON,
+ ZAPI_ROUTE_INSTALLED,
+ ZAPI_ROUTE_REMOVED,
+ ZAPI_ROUTE_REMOVE_FAIL,
+};
+
+enum zapi_nhg_notify_owner {
+ ZAPI_NHG_FAIL_INSTALL,
+ ZAPI_NHG_INSTALLED,
+ ZAPI_NHG_REMOVED,
+ ZAPI_NHG_REMOVE_FAIL,
+};
+
+enum zapi_rule_notify_owner {
+ ZAPI_RULE_FAIL_INSTALL,
+ ZAPI_RULE_INSTALLED,
+ ZAPI_RULE_REMOVED,
+ ZAPI_RULE_FAIL_REMOVE,
+};
+
+enum ipset_type {
+ IPSET_NET_NET = 1,
+ IPSET_NET_PORT_NET,
+ IPSET_NET_PORT,
+ IPSET_NET
+};
+
+enum zapi_ipset_notify_owner {
+ ZAPI_IPSET_FAIL_INSTALL = 0,
+ ZAPI_IPSET_INSTALLED,
+ ZAPI_IPSET_REMOVED,
+ ZAPI_IPSET_FAIL_REMOVE,
+};
+
+enum zapi_ipset_entry_notify_owner {
+ ZAPI_IPSET_ENTRY_FAIL_INSTALL = 0,
+ ZAPI_IPSET_ENTRY_INSTALLED,
+ ZAPI_IPSET_ENTRY_REMOVED,
+ ZAPI_IPSET_ENTRY_FAIL_REMOVE,
+};
+
+enum zapi_iptable_notify_owner {
+ ZAPI_IPTABLE_FAIL_INSTALL = 0,
+ ZAPI_IPTABLE_INSTALLED,
+ ZAPI_IPTABLE_REMOVED,
+ ZAPI_IPTABLE_FAIL_REMOVE,
+};
+
+enum zclient_send_status {
+ ZCLIENT_SEND_FAILURE = -1,
+ ZCLIENT_SEND_SUCCESS = 0,
+ ZCLIENT_SEND_BUFFERED = 1
+};
+
+static inline const char *
+zapi_nhg_notify_owner2str(enum zapi_nhg_notify_owner note)
+{
+ const char *ret = "UNKNOWN";
+
+ switch (note) {
+ case ZAPI_NHG_FAIL_INSTALL:
+ ret = "ZAPI_NHG_FAIL_INSTALL";
+ break;
+ case ZAPI_NHG_INSTALLED:
+ ret = "ZAPI_NHG_INSTALLED";
+ break;
+ case ZAPI_NHG_REMOVE_FAIL:
+ ret = "ZAPI_NHG_REMOVE_FAIL";
+ break;
+ case ZAPI_NHG_REMOVED:
+ ret = "ZAPI_NHG_REMOVED";
+ break;
+ }
+
+ return ret;
+}
+
+static inline const char *
+zapi_rule_notify_owner2str(enum zapi_rule_notify_owner note)
+{
+ const char *ret = "UNKNOWN";
+
+ switch (note) {
+ case ZAPI_RULE_FAIL_INSTALL:
+ ret = "ZAPI_RULE_FAIL_INSTALL";
+ break;
+ case ZAPI_RULE_INSTALLED:
+ ret = "ZAPI_RULE_INSTALLED";
+ break;
+ case ZAPI_RULE_FAIL_REMOVE:
+ ret = "ZAPI_RULE_FAIL_REMOVE";
+ break;
+ case ZAPI_RULE_REMOVED:
+ ret = "ZAPI_RULE_REMOVED";
+ break;
+ }
+
+ return ret;
+}
+
+/* Zebra MAC types */
+#define ZEBRA_MACIP_TYPE_STICKY 0x01 /* Sticky MAC*/
+#define ZEBRA_MACIP_TYPE_GW 0x02 /* gateway (SVI) mac*/
+#define ZEBRA_MACIP_TYPE_ROUTER_FLAG 0x04 /* Router Flag - proxy NA */
+#define ZEBRA_MACIP_TYPE_OVERRIDE_FLAG 0x08 /* Override Flag */
+#define ZEBRA_MACIP_TYPE_SVI_IP 0x10 /* SVI MAC-IP */
+#define ZEBRA_MACIP_TYPE_PROXY_ADVERT 0x20 /* Not locally active */
+#define ZEBRA_MACIP_TYPE_SYNC_PATH 0x40 /* sync path */
+/* XXX - flags is an u8; that needs to be changed to u32 if you need
+ * to allocate past 0x80. Additionally touch zclient_evpn_dump_macip_flags
+ */
+#define MACIP_BUF_SIZE 128
+extern char *zclient_evpn_dump_macip_flags(uint8_t flags, char *buf,
+ size_t len);
+
+/* Zebra ES VTEP flags (ZEBRA_REMOTE_ES_VTEP_ADD) */
+/* ESR has been rxed from the VTEP. Only VTEPs that have advertised the
+ * Type-4 route can participate in DF election.
+ */
+#define ZAPI_ES_VTEP_FLAG_ESR_RXED (1 << 0)
+
+enum zebra_neigh_state { ZEBRA_NEIGH_INACTIVE = 0, ZEBRA_NEIGH_ACTIVE = 1 };
+
+struct zclient_options {
+ bool receive_notify;
+ bool synchronous;
+};
+
+extern struct zclient_options zclient_options_default;
+
+/* link layer representation for GRE like interfaces
+ * ip_in is the underlay IP, ip_out is the tunnel dest
+ * index stands for the index of the interface
+ * ndm state stands for the NDM value in netlink
+ * (see linux/neighbour.h)
+ */
+#define ZEBRA_NEIGH_STATE_INCOMPLETE (0x01)
+#define ZEBRA_NEIGH_STATE_REACHABLE (0x02)
+#define ZEBRA_NEIGH_STATE_STALE (0x04)
+#define ZEBRA_NEIGH_STATE_DELAY (0x08)
+#define ZEBRA_NEIGH_STATE_PROBE (0x10)
+#define ZEBRA_NEIGH_STATE_FAILED (0x20)
+#define ZEBRA_NEIGH_STATE_NOARP (0x40)
+#define ZEBRA_NEIGH_STATE_PERMANENT (0x80)
+#define ZEBRA_NEIGH_STATE_NONE (0x00)
+
+struct zapi_neigh_ip {
+ int cmd;
+ struct ipaddr ip_in;
+ struct ipaddr ip_out;
+ ifindex_t index;
+ uint32_t ndm_state;
+};
+int zclient_neigh_ip_decode(struct stream *s, struct zapi_neigh_ip *api);
+int zclient_neigh_ip_encode(struct stream *s, uint16_t cmd, union sockunion *in,
+ union sockunion *out, struct interface *ifp,
+ int ndm_state);
+
+/*
+ * We reserve the top 4 bits for l2-NHG, everything else
+ * is for zebra/proto l3-NHG.
+ *
+ * Each client is going to get it's own nexthop group space
+ * and we'll separate them, we'll figure out where to start based upon
+ * the route_types.h
+ */
+#define ZEBRA_NHG_PROTO_UPPER \
+ ((uint32_t)250000000) /* Bottom 28 bits then rounded down */
+#define ZEBRA_NHG_PROTO_SPACING (ZEBRA_NHG_PROTO_UPPER / ZEBRA_ROUTE_MAX)
+#define ZEBRA_NHG_PROTO_LOWER \
+ (ZEBRA_NHG_PROTO_SPACING * (ZEBRA_ROUTE_CONNECT + 1))
+
+extern uint32_t zclient_get_nhg_start(uint32_t proto);
+
+extern struct zclient *zclient_new(struct thread_master *m,
+ struct zclient_options *opt,
+ zclient_handler *const *handlers,
+ size_t n_handlers);
+
+extern void zclient_init(struct zclient *, int, unsigned short,
+ struct zebra_privs_t *privs);
+extern int zclient_start(struct zclient *);
+extern void zclient_stop(struct zclient *);
+extern void zclient_reset(struct zclient *);
+extern void zclient_free(struct zclient *);
+
+extern int zclient_socket_connect(struct zclient *);
+
+extern unsigned short *redist_check_instance(struct redist_proto *,
+ unsigned short);
+extern void redist_add_instance(struct redist_proto *, unsigned short);
+extern void redist_del_instance(struct redist_proto *, unsigned short);
+extern void redist_del_all_instances(struct redist_proto *red);
+
+/*
+ * Send to zebra that the specified vrf is using label to resolve
+ * itself for L3VPN's. Repeated calls of this function with
+ * different labels will cause an effective update of the
+ * label for lookup. If you pass in MPLS_LABEL_NONE
+ * we will cause a delete action and remove this label pop
+ * operation.
+ *
+ * The underlying AF_MPLS doesn't care about afi's
+ * but we can make the zebra_vrf keep track of what
+ * we have installed and play some special games
+ * to get them both installed.
+ */
+extern enum zclient_send_status
+zclient_send_vrf_label(struct zclient *zclient, vrf_id_t vrf_id, afi_t afi,
+ mpls_label_t label, enum lsp_types_t ltype);
+
+extern enum zclient_send_status
+zclient_send_localsid(struct zclient *zclient, const struct in6_addr *sid,
+ ifindex_t oif, enum seg6local_action_t action,
+ const struct seg6local_context *context);
+
+extern void zclient_send_reg_requests(struct zclient *, vrf_id_t);
+extern void zclient_send_dereg_requests(struct zclient *, vrf_id_t);
+extern enum zclient_send_status
+zclient_send_router_id_update(struct zclient *zclient,
+ zebra_message_types_t type, afi_t afi,
+ vrf_id_t vrf_id);
+
+extern enum zclient_send_status
+zclient_send_interface_radv_req(struct zclient *zclient, vrf_id_t vrf_id,
+ struct interface *ifp, int enable,
+ uint32_t ra_interval);
+extern enum zclient_send_status
+zclient_send_interface_protodown(struct zclient *zclient, vrf_id_t vrf_id,
+ struct interface *ifp, bool down);
+
+/* Send redistribute command to zebra daemon. Do not update zclient state. */
+extern enum zclient_send_status
+zebra_redistribute_send(int command, struct zclient *, afi_t, int type,
+ unsigned short instance, vrf_id_t vrf_id);
+
+extern enum zclient_send_status
+zebra_redistribute_default_send(int command, struct zclient *zclient, afi_t afi,
+ vrf_id_t vrf_id);
+
+/* Send route notify request to zebra */
+extern int zebra_route_notify_send(int command, struct zclient *zclient,
+ bool set);
+
+/* If state has changed, update state and call zebra_redistribute_send. */
+extern void zclient_redistribute(int command, struct zclient *, afi_t, int type,
+ unsigned short instance, vrf_id_t vrf_id);
+
+/* If state has changed, update state and send the command to zebra. */
+extern void zclient_redistribute_default(int command, struct zclient *,
+ afi_t, vrf_id_t vrf_id);
+
+/*
+ * Send the message in zclient->obuf to the zebra daemon (or enqueue it).
+ * Returns:
+ * -1 on a I/O error
+ * 0 data was successfully sent
+ * 1 data was buffered for future usage
+ */
+extern enum zclient_send_status zclient_send_message(struct zclient *);
+
+/* create header for command, length to be filled in by user later */
+extern void zclient_create_header(struct stream *, uint16_t, vrf_id_t);
+/*
+ * Read sizeof(struct zmsghdr) bytes from the provided socket and parse the
+ * received data into the specified fields. If this is successful, read the
+ * rest of the packet into the provided stream.
+ *
+ * s
+ * The stream to read into
+ *
+ * sock
+ * The socket to read from
+ *
+ * size
+ * Parsed message size will be placed in the pointed-at integer
+ *
+ * marker
+ * Parsed marker will be placed in the pointed-at byte
+ *
+ * version
+ * Parsed version will be placed in the pointed-at byte
+ *
+ * vrf_id
+ * Parsed VRF ID will be placed in the pointed-at vrf_id_t
+ *
+ * cmd
+ * Parsed command number will be placed in the pointed-at integer
+ *
+ * Returns:
+ * -1 if:
+ * - insufficient data for header was read
+ * - a version mismatch was detected
+ * - a marker mismatch was detected
+ * - header size field specified more data than could be read
+ */
+extern int zclient_read_header(struct stream *s, int sock, uint16_t *size,
+ uint8_t *marker, uint8_t *version,
+ vrf_id_t *vrf_id, uint16_t *cmd);
+/*
+ * Parse header from ZAPI message stream into struct zmsghdr.
+ * This function assumes the stream getp points at the first byte of the header.
+ * If the function is successful then the stream getp will point to the byte
+ * immediately after the last byte of the header.
+ *
+ * zmsg
+ * The stream containing the header
+ *
+ * hdr
+ * The header struct to parse into.
+ *
+ * Returns:
+ * true if parsing succeeded, false otherwise
+ */
+extern bool zapi_parse_header(struct stream *zmsg, struct zmsghdr *hdr);
+
+extern enum zclient_send_status
+zclient_interface_set_master(struct zclient *client, struct interface *master,
+ struct interface *slave);
+extern struct interface *zebra_interface_state_read(struct stream *s, vrf_id_t);
+extern struct connected *zebra_interface_address_read(int, struct stream *,
+ vrf_id_t);
+extern struct nbr_connected *
+zebra_interface_nbr_address_read(int, struct stream *, vrf_id_t);
+extern struct interface *zebra_interface_vrf_update_read(struct stream *s,
+ vrf_id_t vrf_id,
+ vrf_id_t *new_vrf_id);
+extern int zebra_router_id_update_read(struct stream *s, struct prefix *rid);
+
+extern struct interface *zebra_interface_link_params_read(struct stream *s,
+ vrf_id_t vrf_id,
+ bool *changed);
+extern size_t zebra_interface_link_params_write(struct stream *,
+ struct interface *);
+extern enum zclient_send_status
+zclient_send_get_label_chunk(struct zclient *zclient, uint8_t keep,
+ uint32_t chunk_size, uint32_t base);
+
+extern int lm_label_manager_connect(struct zclient *zclient, int async);
+extern int lm_get_label_chunk(struct zclient *zclient, uint8_t keep,
+ uint32_t base, uint32_t chunk_size,
+ uint32_t *start, uint32_t *end);
+extern int lm_release_label_chunk(struct zclient *zclient, uint32_t start,
+ uint32_t end);
+extern int tm_table_manager_connect(struct zclient *zclient);
+extern int tm_get_table_chunk(struct zclient *zclient, uint32_t chunk_size,
+ uint32_t *start, uint32_t *end);
+extern int tm_release_table_chunk(struct zclient *zclient, uint32_t start,
+ uint32_t end);
+extern int srv6_manager_get_locator_chunk(struct zclient *zclient,
+ const char *locator_name);
+extern int srv6_manager_release_locator_chunk(struct zclient *zclient,
+ const char *locator_name);
+
+extern enum zclient_send_status zebra_send_sr_policy(struct zclient *zclient,
+ int cmd,
+ struct zapi_sr_policy *zp);
+extern int zapi_sr_policy_encode(struct stream *s, int cmd,
+ struct zapi_sr_policy *zp);
+extern int zapi_sr_policy_decode(struct stream *s, struct zapi_sr_policy *zp);
+extern int zapi_sr_policy_notify_status_decode(struct stream *s,
+ struct zapi_sr_policy *zp);
+
+extern enum zclient_send_status zebra_send_mpls_labels(struct zclient *zclient,
+ int cmd,
+ struct zapi_labels *zl);
+extern int zapi_labels_encode(struct stream *s, int cmd,
+ struct zapi_labels *zl);
+extern int zapi_labels_decode(struct stream *s, struct zapi_labels *zl);
+
+extern int zapi_srv6_locator_encode(struct stream *s,
+ const struct srv6_locator *l);
+extern int zapi_srv6_locator_decode(struct stream *s, struct srv6_locator *l);
+extern int zapi_srv6_locator_chunk_encode(struct stream *s,
+ const struct srv6_locator_chunk *c);
+extern int zapi_srv6_locator_chunk_decode(struct stream *s,
+ struct srv6_locator_chunk *c);
+
+extern enum zclient_send_status zebra_send_pw(struct zclient *zclient,
+ int command, struct zapi_pw *pw);
+extern int zebra_read_pw_status_update(ZAPI_CALLBACK_ARGS,
+ struct zapi_pw_status *pw);
+
+extern enum zclient_send_status zclient_route_send(uint8_t, struct zclient *,
+ struct zapi_route *);
+extern enum zclient_send_status
+zclient_send_rnh(struct zclient *zclient, int command, const struct prefix *p,
+ safi_t safi, bool connected, bool resolve_via_default,
+ vrf_id_t vrf_id);
+int zapi_nexthop_encode(struct stream *s, const struct zapi_nexthop *api_nh,
+ uint32_t api_flags, uint32_t api_message);
+extern int zapi_route_encode(uint8_t, struct stream *, struct zapi_route *);
+extern int zapi_route_decode(struct stream *s, struct zapi_route *api);
+extern int zapi_nexthop_decode(struct stream *s, struct zapi_nexthop *api_nh,
+ uint32_t api_flags, uint32_t api_message);
+bool zapi_nhg_notify_decode(struct stream *s, uint32_t *id,
+ enum zapi_nhg_notify_owner *note);
+bool zapi_route_notify_decode(struct stream *s, struct prefix *p,
+ uint32_t *tableid,
+ enum zapi_route_notify_owner *note,
+ afi_t *afi, safi_t *safi);
+bool zapi_rule_notify_decode(struct stream *s, uint32_t *seqno,
+ uint32_t *priority, uint32_t *unique, char *ifname,
+ enum zapi_rule_notify_owner *note);
+bool zapi_ipset_notify_decode(struct stream *s,
+ uint32_t *unique,
+ enum zapi_ipset_notify_owner *note);
+
+/* Nexthop-group message apis */
+extern enum zclient_send_status
+zclient_nhg_send(struct zclient *zclient, int cmd, struct zapi_nhg *api_nhg);
+
+#define ZEBRA_IPSET_NAME_SIZE 32
+
+bool zapi_ipset_entry_notify_decode(struct stream *s,
+ uint32_t *unique,
+ char *ipset_name,
+ enum zapi_ipset_entry_notify_owner *note);
+bool zapi_iptable_notify_decode(struct stream *s,
+ uint32_t *unique,
+ enum zapi_iptable_notify_owner *note);
+
+extern struct nexthop *
+nexthop_from_zapi_nexthop(const struct zapi_nexthop *znh);
+int zapi_nexthop_from_nexthop(struct zapi_nexthop *znh,
+ const struct nexthop *nh);
+int zapi_backup_nexthop_from_nexthop(struct zapi_nexthop *znh,
+ const struct nexthop *nh);
+/*
+ * match -> is the prefix that the calling daemon asked to be matched
+ * against.
+ * nhr->prefix -> is the actual prefix that was matched against in the
+ * rib itself.
+ *
+ * This distinction is made because a LPM can be made if there is a
+ * covering route. This way the upper level protocol can make a decision
+ * point about whether or not it wants to use the match or not.
+ */
+extern bool zapi_nexthop_update_decode(struct stream *s, struct prefix *match,
+ struct zapi_route *nhr);
+const char *zapi_nexthop2str(const struct zapi_nexthop *znh, char *buf,
+ int bufsize);
+
+/* Decode the zebra error message */
+extern bool zapi_error_decode(struct stream *s, enum zebra_error_types *error);
+
+/* Encode and decode restart capabilities */
+extern enum zclient_send_status
+zclient_capabilities_send(uint32_t cmd, struct zclient *zclient,
+ struct zapi_cap *api);
+extern int32_t zapi_capabilities_decode(struct stream *s, struct zapi_cap *api);
+
+static inline void zapi_route_set_blackhole(struct zapi_route *api,
+ enum blackhole_type bh_type)
+{
+ api->nexthop_num = 1;
+ api->nexthops[0].type = NEXTHOP_TYPE_BLACKHOLE;
+ api->nexthops[0].vrf_id = VRF_DEFAULT;
+ api->nexthops[0].bh_type = bh_type;
+ SET_FLAG(api->message, ZAPI_MESSAGE_NEXTHOP);
+};
+
+extern enum zclient_send_status
+zclient_send_mlag_register(struct zclient *client, uint32_t bit_map);
+extern enum zclient_send_status
+zclient_send_mlag_deregister(struct zclient *client);
+
+extern enum zclient_send_status zclient_send_mlag_data(struct zclient *client,
+ struct stream *client_s);
+
+/*
+ * Send an OPAQUE message, contents opaque to zebra - but note that
+ * the length of the payload is restricted by the zclient's
+ * outgoing message buffer.
+ * The message header is a message subtype; please use the registry
+ * below to avoid sub-type collisions. Clients use the registration
+ * apis to manage the specific opaque subtypes they want to receive.
+ */
+enum zclient_send_status zclient_send_opaque(struct zclient *zclient,
+ uint32_t type, const uint8_t *data,
+ size_t datasize);
+
+enum zclient_send_status
+zclient_send_opaque_unicast(struct zclient *zclient, uint32_t type,
+ uint8_t proto, uint16_t instance,
+ uint32_t session_id, const uint8_t *data,
+ size_t datasize);
+
+/* Struct representing the decoded opaque header info */
+struct zapi_opaque_msg {
+ uint32_t type; /* Subtype */
+ uint16_t len; /* len after zapi header and this info */
+ uint16_t flags;
+
+ /* Client-specific info - *if* UNICAST flag is set */
+ uint8_t proto;
+ uint16_t instance;
+ uint32_t session_id;
+};
+
+#define ZAPI_OPAQUE_FLAG_UNICAST 0x01
+
+/* Simple struct to convey registration/unreg requests */
+struct zapi_opaque_reg_info {
+ /* Message subtype */
+ uint32_t type;
+
+ /* Client session tuple */
+ uint8_t proto;
+ uint16_t instance;
+ uint32_t session_id;
+};
+
+/* Decode incoming opaque */
+int zclient_opaque_decode(struct stream *msg, struct zapi_opaque_msg *info);
+
+enum zclient_send_status zclient_register_opaque(struct zclient *zclient,
+ uint32_t type);
+enum zclient_send_status zclient_unregister_opaque(struct zclient *zclient,
+ uint32_t type);
+int zapi_opaque_reg_decode(struct stream *msg,
+ struct zapi_opaque_reg_info *info);
+
+/*
+ * Registry of opaque message types. Please do not reuse an in-use
+ * type code; some daemons are likely relying on it.
+ */
+enum zapi_opaque_registry {
+ /* Request link-state database dump, at restart for example */
+ LINK_STATE_SYNC = 1,
+ /* Update containing link-state db info */
+ LINK_STATE_UPDATE = 2,
+ /* Request LDP-SYNC state from LDP */
+ LDP_IGP_SYNC_IF_STATE_REQUEST = 3,
+ /* Update containing LDP IGP Sync State info */
+ LDP_IGP_SYNC_IF_STATE_UPDATE = 4,
+ /* Announce that LDP is up */
+ LDP_IGP_SYNC_ANNOUNCE_UPDATE = 5,
+ /* Register RLFA with LDP */
+ LDP_RLFA_REGISTER = 7,
+ /* Unregister all RLFAs with LDP */
+ LDP_RLFA_UNREGISTER_ALL = 8,
+ /* Announce LDP labels associated to a previously registered RLFA */
+ LDP_RLFA_LABELS = 9,
+};
+
+/* Send the hello message.
+ * Returns 0 for success or -1 on an I/O error.
+ */
+extern enum zclient_send_status zclient_send_hello(struct zclient *client);
+
+extern enum zclient_send_status
+zclient_send_neigh_discovery_req(struct zclient *zclient,
+ const struct interface *ifp,
+ const struct prefix *p);
+
+struct zapi_client_close_info {
+ /* Client session tuple */
+ uint8_t proto;
+ uint16_t instance;
+ uint32_t session_id;
+};
+
+/* Decode incoming client close notify */
+extern int zapi_client_close_notify_decode(struct stream *s,
+ struct zapi_client_close_info *info);
+
+extern int zclient_send_zebra_gre_request(struct zclient *client,
+ struct interface *ifp);
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ZEBRA_ZCLIENT_H */
diff --git a/lib/zebra.h b/lib/zebra.h
new file mode 100644
index 0000000..53ae5b4
--- /dev/null
+++ b/lib/zebra.h
@@ -0,0 +1,421 @@
+/* Zebra common header.
+ * Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra 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, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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 _ZEBRA_H
+#define _ZEBRA_H
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+#include "compiler.h"
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <string.h>
+#include <pwd.h>
+#include <grp.h>
+#ifdef HAVE_STROPTS_H
+#include <stropts.h>
+#endif /* HAVE_STROPTS_H */
+#include <sys/select.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#ifdef HAVE_SYS_SYSCTL_H
+#ifdef GNU_LINUX
+#include <linux/types.h>
+#else
+#include <sys/sysctl.h>
+#endif
+#endif /* HAVE_SYS_SYSCTL_H */
+#include <sys/ioctl.h>
+#ifdef HAVE_SYS_CONF_H
+#include <sys/conf.h>
+#endif /* HAVE_SYS_CONF_H */
+#ifdef HAVE_SYS_KSYM_H
+#include <sys/ksym.h>
+#endif /* HAVE_SYS_KSYM_H */
+#include <syslog.h>
+#include <sys/time.h>
+#include <time.h>
+#include <sys/uio.h>
+#include <sys/utsname.h>
+#include <sys/resource.h>
+#include <limits.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#ifdef HAVE_SYS_ENDIAN_H
+#include <sys/endian.h>
+#endif
+#ifdef HAVE_ENDIAN_H
+#include <endian.h>
+#endif
+
+/* machine dependent includes */
+#ifdef HAVE_LINUX_VERSION_H
+#include <linux/version.h>
+#endif /* HAVE_LINUX_VERSION_H */
+
+#ifdef HAVE_ASM_TYPES_H
+#include <asm/types.h>
+#endif /* HAVE_ASM_TYPES_H */
+
+/* misc include group */
+#include <stdarg.h>
+
+#ifdef HAVE_LCAPS
+#include <sys/capability.h>
+#include <sys/prctl.h>
+#endif /* HAVE_LCAPS */
+
+/* network include group */
+
+#include <sys/socket.h>
+
+#ifdef HAVE_SYS_SOCKIO_H
+#include <sys/sockio.h>
+#endif /* HAVE_SYS_SOCKIO_H */
+
+#ifdef __APPLE__
+#define __APPLE_USE_RFC_3542
+#endif
+
+#ifndef HAVE_LIBCRYPT
+#ifdef HAVE_LIBCRYPTO
+#include <openssl/des.h>
+# define crypt DES_crypt
+#endif
+#endif
+
+#ifdef CRYPTO_OPENSSL
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+#endif
+
+#include "openbsd-tree.h"
+
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <netinet/tcp.h>
+
+#ifdef HAVE_NET_NETOPT_H
+#include <net/netopt.h>
+#endif /* HAVE_NET_NETOPT_H */
+
+#include <net/if.h>
+
+#ifdef HAVE_NET_IF_DL_H
+#include <net/if_dl.h>
+#endif /* HAVE_NET_IF_DL_H */
+
+#ifdef HAVE_NET_IF_VAR_H
+#include <net/if_var.h>
+#endif /* HAVE_NET_IF_VAR_H */
+
+#include <net/route.h>
+
+#ifdef HAVE_NETLINK
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/filter.h>
+#else
+#define RT_TABLE_MAIN 0
+#endif /* HAVE_NETLINK */
+
+#include <netdb.h>
+#include <arpa/inet.h>
+
+#ifdef HAVE_INET_ND_H
+#include <inet/nd.h>
+#endif /* HAVE_INET_ND_H */
+
+#ifdef HAVE_NETINET_IN_VAR_H
+#include <netinet/in_var.h>
+#endif /* HAVE_NETINET_IN_VAR_H */
+
+#ifdef HAVE_NETINET6_IN6_VAR_H
+#include <netinet6/in6_var.h>
+#endif /* HAVE_NETINET6_IN6_VAR_H */
+
+#ifdef HAVE_NETINET_IN6_VAR_H
+#include <netinet/in6_var.h>
+#endif /* HAVE_NETINET_IN6_VAR_H */
+
+#ifdef HAVE_NETINET6_IN_H
+#include <netinet6/in.h>
+#endif /* HAVE_NETINET6_IN_H */
+
+
+#ifdef HAVE_NETINET6_IP6_H
+#include <netinet6/ip6.h>
+#endif /* HAVE_NETINET6_IP6_H */
+
+#include <netinet/icmp6.h>
+
+#ifdef HAVE_NETINET6_ND6_H
+#include <netinet6/nd6.h>
+#endif /* HAVE_NETINET6_ND6_H */
+
+/* Some systems do not define UINT32_MAX, etc.. from inttypes.h
+ * e.g. this makes life easier for FBSD 4.11 users.
+ */
+#ifndef INT16_MAX
+#define INT16_MAX (32767)
+#endif
+#ifndef INT32_MAX
+#define INT32_MAX (2147483647)
+#endif
+#ifndef UINT16_MAX
+#define UINT16_MAX (65535U)
+#endif
+#ifndef UINT32_MAX
+#define UINT32_MAX (4294967295U)
+#endif
+
+#ifdef HAVE_GLIBC_BACKTRACE
+#include <execinfo.h>
+#endif /* HAVE_GLIBC_BACKTRACE */
+
+/* Local includes: */
+#if !(defined(__GNUC__) || defined(VTYSH_EXTRACT_PL))
+#define __attribute__(x)
+#endif /* !__GNUC__ || VTYSH_EXTRACT_PL */
+
+#include <assert.h>
+
+/*
+ * Add explicit static cast only when using a C++ compiler.
+ */
+#ifdef __cplusplus
+#define static_cast(l, r) static_cast<decltype(l)>((r))
+#else
+#define static_cast(l, r) (r)
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef HAVE_STRLCAT
+size_t strlcat(char *__restrict dest,
+ const char *__restrict src, size_t destsize);
+#endif
+#ifndef HAVE_STRLCPY
+size_t strlcpy(char *__restrict dest,
+ const char *__restrict src, size_t destsize);
+#endif
+
+#ifndef HAVE_EXPLICIT_BZERO
+void explicit_bzero(void *buf, size_t len);
+#endif
+
+#if !defined(HAVE_STRUCT_MMSGHDR_MSG_HDR) || !defined(HAVE_SENDMMSG)
+/* avoid conflicts in case we have partial support */
+#define mmsghdr frr_mmsghdr
+#define sendmmsg frr_sendmmsg
+
+struct mmsghdr {
+ struct msghdr msg_hdr;
+ unsigned int msg_len;
+};
+
+/* just go 1 at a time here, the loop this is used in will handle the rest */
+static inline int sendmmsg(int fd, struct mmsghdr *mmh, unsigned int len,
+ int flags)
+{
+ int rv = sendmsg(fd, &mmh->msg_hdr, 0);
+
+ return rv > 0 ? 1 : rv;
+}
+#endif
+
+/*
+ * RFC 3542 defines several macros for using struct cmsghdr.
+ * Here, we define those that are not present
+ */
+
+/*
+ * Internal defines, for use only in this file.
+ * These are likely wrong on other than ILP32 machines, so warn.
+ */
+#ifndef _CMSG_DATA_ALIGN
+#define _CMSG_DATA_ALIGN(n) (((n) + 3) & ~3)
+#endif /* _CMSG_DATA_ALIGN */
+
+#ifndef _CMSG_HDR_ALIGN
+#define _CMSG_HDR_ALIGN(n) (((n) + 3) & ~3)
+#endif /* _CMSG_HDR_ALIGN */
+
+/*
+ * CMSG_SPACE and CMSG_LEN are required in RFC3542, but were new in that
+ * version.
+ */
+#ifndef CMSG_SPACE
+#define CMSG_SPACE(l) \
+ (_CMSG_DATA_ALIGN(sizeof(struct cmsghdr)) + _CMSG_HDR_ALIGN(l))
+#warning "assuming 4-byte alignment for CMSG_SPACE"
+#endif /* CMSG_SPACE */
+
+
+#ifndef CMSG_LEN
+#define CMSG_LEN(l) (_CMSG_DATA_ALIGN(sizeof(struct cmsghdr)) + (l))
+#warning "assuming 4-byte alignment for CMSG_LEN"
+#endif /* CMSG_LEN */
+
+
+/* The definition of struct in_pktinfo is missing in old version of
+ GLIBC 2.1 (Redhat 6.1). */
+#if defined(GNU_LINUX) && !defined(HAVE_STRUCT_IN_PKTINFO)
+struct in_pktinfo {
+ int ipi_ifindex;
+ struct in_addr ipi_spec_dst;
+ struct in_addr ipi_addr;
+};
+#endif
+
+/*
+ * IP_HDRINCL / struct ip byte order
+ *
+ * Linux: network byte order
+ * *BSD: network, except for length and offset. (cf Stevens)
+ * SunOS: nominally as per BSD. but bug: network order on LE.
+ * OpenBSD: network byte order, apart from older versions which are as per
+ * *BSD
+ */
+#if defined(__NetBSD__) \
+ || (defined(__FreeBSD__) && (__FreeBSD_version < 1100030)) \
+ || (defined(__OpenBSD__) && (OpenBSD < 200311)) \
+ || (defined(__APPLE__))
+#define HAVE_IP_HDRINCL_BSD_ORDER
+#endif
+
+/* autoconf macros for this are deprecated, just find endian.h */
+#ifndef BYTE_ORDER
+#error please locate an endian.h file appropriate to your platform
+#endif
+
+/* For old definition. */
+#ifndef IN6_ARE_ADDR_EQUAL
+#define IN6_ARE_ADDR_EQUAL IN6_IS_ADDR_EQUAL
+#endif /* IN6_ARE_ADDR_EQUAL */
+
+/* default zebra TCP port for zclient */
+#define ZEBRA_PORT 2600
+
+/*
+ * The compiler.h header is used for anyone using the CPP_NOTICE
+ * since this is universally needed, let's add it to zebra.h
+ */
+#include "compiler.h"
+
+/* Zebra route's types are defined in route_types.h */
+#include "lib/route_types.h"
+
+#define strmatch(a,b) (!strcmp((a), (b)))
+
+#ifndef INADDR_LOOPBACK
+#define INADDR_LOOPBACK 0x7f000001 /* Internet address 127.0.0.1. */
+#endif
+
+/* Address family numbers from RFC1700. */
+typedef enum {
+ AFI_UNSPEC = 0,
+ AFI_IP = 1,
+ AFI_IP6 = 2,
+ AFI_L2VPN = 3,
+ AFI_MAX = 4
+} afi_t;
+
+#define IS_VALID_AFI(a) ((a) > AFI_UNSPEC && (a) < AFI_MAX)
+
+/* Subsequent Address Family Identifier. */
+typedef enum {
+ SAFI_UNSPEC = 0,
+ SAFI_UNICAST = 1,
+ SAFI_MULTICAST = 2,
+ SAFI_MPLS_VPN = 3,
+ SAFI_ENCAP = 4,
+ SAFI_EVPN = 5,
+ SAFI_LABELED_UNICAST = 6,
+ SAFI_FLOWSPEC = 7,
+ SAFI_MAX = 8
+} safi_t;
+
+#define FOREACH_AFI_SAFI(afi, safi) \
+ for (afi = AFI_IP; afi < AFI_MAX; afi++) \
+ for (safi = SAFI_UNICAST; safi < SAFI_MAX; safi++)
+
+#define FOREACH_AFI_SAFI_NSF(afi, safi) \
+ for (afi = AFI_IP; afi < AFI_MAX; afi++) \
+ for (safi = SAFI_UNICAST; safi <= SAFI_MPLS_VPN; safi++)
+
+/* Default Administrative Distance of each protocol. */
+#define ZEBRA_KERNEL_DISTANCE_DEFAULT 0
+#define ZEBRA_CONNECT_DISTANCE_DEFAULT 0
+#define ZEBRA_STATIC_DISTANCE_DEFAULT 1
+#define ZEBRA_RIP_DISTANCE_DEFAULT 120
+#define ZEBRA_RIPNG_DISTANCE_DEFAULT 120
+#define ZEBRA_OSPF_DISTANCE_DEFAULT 110
+#define ZEBRA_OSPF6_DISTANCE_DEFAULT 110
+#define ZEBRA_ISIS_DISTANCE_DEFAULT 115
+#define ZEBRA_IBGP_DISTANCE_DEFAULT 200
+#define ZEBRA_EBGP_DISTANCE_DEFAULT 20
+#define ZEBRA_TABLE_DISTANCE_DEFAULT 15
+
+/* Flag manipulation macros. */
+#define CHECK_FLAG(V,F) ((V) & (F))
+#define SET_FLAG(V,F) (V) |= (F)
+#define UNSET_FLAG(V,F) (V) &= ~(F)
+#define RESET_FLAG(V) (V) = 0
+#define COND_FLAG(V, F, C) ((C) ? (SET_FLAG(V, F)) : (UNSET_FLAG(V, F)))
+
+/* Atomic flag manipulation macros. */
+#define CHECK_FLAG_ATOMIC(PV, F) \
+ ((atomic_load_explicit(PV, memory_order_seq_cst)) & (F))
+#define SET_FLAG_ATOMIC(PV, F) \
+ ((atomic_fetch_or_explicit(PV, (F), memory_order_seq_cst)))
+#define UNSET_FLAG_ATOMIC(PV, F) \
+ ((atomic_fetch_and_explicit(PV, ~(F), memory_order_seq_cst)))
+#define RESET_FLAG_ATOMIC(PV) \
+ ((atomic_store_explicit(PV, 0, memory_order_seq_cst)))
+
+/* VRF ID type. */
+typedef uint32_t vrf_id_t;
+
+typedef uint32_t route_tag_t;
+#define ROUTE_TAG_MAX UINT32_MAX
+#define ROUTE_TAG_PRI PRIu32
+
+/* Name of hook calls */
+#define ZEBRA_ON_RIB_PROCESS_HOOK_CALL "on_rib_process_dplane_results"
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ZEBRA_H */
diff --git a/lib/zlog.c b/lib/zlog.c
new file mode 100644
index 0000000..6a36a0b
--- /dev/null
+++ b/lib/zlog.c
@@ -0,0 +1,1061 @@
+/*
+ * Copyright (c) 2015-19 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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 <unistd.h>
+#include <sys/time.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <time.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <pthread.h>
+
+/* gettid() & co. */
+#ifdef HAVE_PTHREAD_NP_H
+#include <pthread_np.h>
+#endif
+#ifdef linux
+#include <sys/syscall.h>
+#endif
+#ifdef __FreeBSD__
+#include <sys/thr.h>
+#endif
+#ifdef __NetBSD__
+#include <lwp.h>
+#endif
+#ifdef __DragonFly__
+#include <sys/lwp.h>
+#endif
+#ifdef __APPLE__
+#include <mach/mach_traps.h>
+#endif
+
+#ifdef HAVE_LIBUNWIND
+#define UNW_LOCAL_ONLY
+#include <libunwind.h>
+#include <dlfcn.h>
+#endif
+
+#include "memory.h"
+#include "atomlist.h"
+#include "printfrr.h"
+#include "frrcu.h"
+#include "zlog.h"
+#include "libfrr_trace.h"
+#include "thread.h"
+
+DEFINE_MTYPE_STATIC(LIB, LOG_MESSAGE, "log message");
+DEFINE_MTYPE_STATIC(LIB, LOG_TLSBUF, "log thread-local buffer");
+
+DEFINE_HOOK(zlog_init, (const char *progname, const char *protoname,
+ unsigned short instance, uid_t uid, gid_t gid),
+ (progname, protoname, instance, uid, gid));
+DEFINE_KOOH(zlog_fini, (), ());
+DEFINE_HOOK(zlog_aux_init, (const char *prefix, int prio_min),
+ (prefix, prio_min));
+
+char zlog_prefix[128];
+size_t zlog_prefixsz;
+int zlog_tmpdirfd = -1;
+int zlog_instance = -1;
+
+static atomic_bool zlog_ec = true, zlog_xid = true;
+
+/* these are kept around because logging is initialized (and directories
+ * & files created) before zprivs code switches to the FRR user; therefore
+ * we need to chown() things so we don't get permission errors later when
+ * trying to delete things on shutdown
+ */
+static uid_t zlog_uid = -1;
+static gid_t zlog_gid = -1;
+
+DECLARE_ATOMLIST(zlog_targets, struct zlog_target, head);
+static struct zlog_targets_head zlog_targets;
+
+/* Global setting for buffered vs immediate output. The default is
+ * per-pthread buffering.
+ */
+static bool default_immediate;
+
+/* cf. zlog.h for additional comments on this struct.
+ *
+ * Note: you MUST NOT pass the format string + va_list to non-FRR format
+ * string functions (e.g. vsyslog, sd_journal_printv, ...) since FRR uses an
+ * extended prinf() with additional formats (%pI4 and the like).
+ *
+ * Also remember to use va_copy() on args.
+ */
+
+struct zlog_msg {
+ struct timespec ts;
+ int prio;
+
+ const char *fmt;
+ va_list args;
+ const struct xref_logmsg *xref;
+
+ char *stackbuf;
+ size_t stackbufsz;
+ char *text;
+ size_t textlen;
+ size_t hdrlen;
+
+ /* This is always ISO8601 with sub-second precision 9 here, it's
+ * converted for callers as needed. ts_dot points to the "."
+ * separating sub-seconds. ts_zonetail is "Z" or "+00:00" for the
+ * local time offset.
+ *
+ * Valid if ZLOG_TS_ISO8601 is set.
+ * (0 if timestamp has not been formatted yet)
+ */
+ char ts_str[32], *ts_dot, ts_zonetail[8];
+ uint32_t ts_flags;
+
+ /* "mmm dd hh:mm:ss" for 3164 legacy syslog - too dissimilar from
+ * the above, so just kept separately here.
+ */
+ uint32_t ts_3164_flags;
+ char ts_3164_str[16];
+
+ /* at the time of writing, 16 args was the actual maximum of arguments
+ * to a single zlog call. Particularly printing flag bitmasks seems
+ * to drive this. That said, the overhead of dynamically sizing this
+ * probably outweighs the value. If anything, a printfrr extension
+ * for printing flag bitmasks might be a good idea.
+ */
+ struct fmt_outpos argpos[24];
+ size_t n_argpos;
+};
+
+/* thread-local log message buffering
+ *
+ * This is strictly optional and set up by calling zlog_tls_buffer_init()
+ * on a particular thread.
+ *
+ * If in use, this will create a temporary file in /var/tmp which is used as
+ * memory-mapped MAP_SHARED log message buffer. The idea there is that buffer
+ * access doesn't require any syscalls, but in case of a crash the kernel
+ * knows to sync the memory back to disk. This way the user can still get the
+ * last log messages if there were any left unwritten in the buffer.
+ *
+ * Sizing this dynamically isn't particularly useful, so here's an 8k buffer
+ * with a message limit of 64 messages. Message metadata (e.g. priority,
+ * timestamp) aren't in the mmap region, so they're lost on crash, but we can
+ * live with that.
+ */
+
+#if defined(HAVE_OPENAT) && defined(HAVE_UNLINKAT)
+#define CAN_DO_TLS 1
+#endif
+
+#define TLS_LOG_BUF_SIZE 8192
+#define TLS_LOG_MAXMSG 64
+
+struct zlog_tls {
+ char *mmbuf;
+ size_t bufpos;
+ bool do_unlink;
+
+ size_t nmsgs;
+ struct zlog_msg msgs[TLS_LOG_MAXMSG];
+ struct zlog_msg *msgp[TLS_LOG_MAXMSG];
+};
+
+static inline void zlog_tls_free(void *arg);
+
+/* proper ELF TLS is a bit faster than pthread_[gs]etspecific, so if it's
+ * available we'll use it here
+ */
+
+#ifdef __OpenBSD__
+static pthread_key_t zlog_tls_key;
+
+static void zlog_tls_key_init(void) __attribute__((_CONSTRUCTOR(500)));
+static void zlog_tls_key_init(void)
+{
+ pthread_key_create(&zlog_tls_key, zlog_tls_free);
+}
+
+static void zlog_tls_key_fini(void) __attribute__((_DESTRUCTOR(500)));
+static void zlog_tls_key_fini(void)
+{
+ pthread_key_delete(zlog_tls_key);
+}
+
+static inline struct zlog_tls *zlog_tls_get(void)
+{
+ return pthread_getspecific(zlog_tls_key);
+}
+
+static inline void zlog_tls_set(struct zlog_tls *val)
+{
+ pthread_setspecific(zlog_tls_key, val);
+}
+#else
+# ifndef thread_local
+# define thread_local __thread
+# endif
+
+static thread_local struct zlog_tls *zlog_tls_var
+ __attribute__((tls_model("initial-exec")));
+
+static inline struct zlog_tls *zlog_tls_get(void)
+{
+ return zlog_tls_var;
+}
+
+static inline void zlog_tls_set(struct zlog_tls *val)
+{
+ zlog_tls_var = val;
+}
+#endif
+
+#ifdef CAN_DO_TLS
+static intmax_t zlog_gettid(void)
+{
+#ifndef __OpenBSD__
+ /* accessing a TLS variable is much faster than a syscall */
+ static thread_local intmax_t cached_tid = -1;
+ if (cached_tid != -1)
+ return cached_tid;
+#endif
+
+ long rv = -1;
+#ifdef HAVE_PTHREAD_GETTHREADID_NP
+ rv = pthread_getthreadid_np();
+#elif defined(linux)
+ rv = syscall(__NR_gettid);
+#elif defined(__NetBSD__)
+ rv = _lwp_self();
+#elif defined(__FreeBSD__)
+ thr_self(&rv);
+#elif defined(__DragonFly__)
+ rv = lwp_gettid();
+#elif defined(__OpenBSD__)
+ rv = getthrid();
+#elif defined(__sun)
+ rv = pthread_self();
+#elif defined(__APPLE__)
+ rv = mach_thread_self();
+ mach_port_deallocate(mach_task_self(), rv);
+#endif
+
+#ifndef __OpenBSD__
+ cached_tid = rv;
+#endif
+ return rv;
+}
+
+void zlog_tls_buffer_init(void)
+{
+ struct zlog_tls *zlog_tls;
+ char mmpath[MAXPATHLEN];
+ int mmfd;
+ size_t i;
+
+ zlog_tls = zlog_tls_get();
+
+ if (zlog_tls || zlog_tmpdirfd < 0)
+ return;
+
+ zlog_tls = XCALLOC(MTYPE_LOG_TLSBUF, sizeof(*zlog_tls));
+ for (i = 0; i < array_size(zlog_tls->msgp); i++)
+ zlog_tls->msgp[i] = &zlog_tls->msgs[i];
+
+ snprintfrr(mmpath, sizeof(mmpath), "logbuf.%jd", zlog_gettid());
+
+ mmfd = openat(zlog_tmpdirfd, mmpath,
+ O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0600);
+ if (mmfd < 0) {
+ zlog_err("failed to open thread log buffer \"%s\": %s",
+ mmpath, strerror(errno));
+ goto out_anon;
+ }
+ fchown(mmfd, zlog_uid, zlog_gid);
+
+#ifdef HAVE_POSIX_FALLOCATE
+ if (posix_fallocate(mmfd, 0, TLS_LOG_BUF_SIZE) != 0)
+ /* note next statement is under above if() */
+#endif
+ if (ftruncate(mmfd, TLS_LOG_BUF_SIZE) < 0) {
+ zlog_err("failed to allocate thread log buffer \"%s\": %s",
+ mmpath, strerror(errno));
+ goto out_anon_unlink;
+ }
+
+ zlog_tls->mmbuf = mmap(NULL, TLS_LOG_BUF_SIZE, PROT_READ | PROT_WRITE,
+ MAP_SHARED, mmfd, 0);
+ if (zlog_tls->mmbuf == MAP_FAILED) {
+ zlog_err("failed to mmap thread log buffer \"%s\": %s",
+ mmpath, strerror(errno));
+ goto out_anon_unlink;
+ }
+ zlog_tls->do_unlink = true;
+
+ close(mmfd);
+ zlog_tls_set(zlog_tls);
+ return;
+
+out_anon_unlink:
+ unlinkat(zlog_tmpdirfd, mmpath, 0);
+ close(mmfd);
+out_anon:
+
+#ifndef MAP_ANONYMOUS
+#define MAP_ANONYMOUS MAP_ANON
+#endif
+ zlog_tls->mmbuf = mmap(NULL, TLS_LOG_BUF_SIZE, PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+
+ if (!zlog_tls->mmbuf) {
+ zlog_err("failed to anonymous-mmap thread log buffer: %s",
+ strerror(errno));
+ XFREE(MTYPE_LOG_TLSBUF, zlog_tls);
+ zlog_tls_set(NULL);
+ return;
+ }
+
+ zlog_tls_set(zlog_tls);
+}
+
+void zlog_tls_buffer_fini(void)
+{
+ char mmpath[MAXPATHLEN];
+ struct zlog_tls *zlog_tls = zlog_tls_get();
+ bool do_unlink = zlog_tls ? zlog_tls->do_unlink : false;
+
+ zlog_tls_buffer_flush();
+
+ zlog_tls_free(zlog_tls);
+ zlog_tls_set(NULL);
+
+ snprintfrr(mmpath, sizeof(mmpath), "logbuf.%jd", zlog_gettid());
+ if (do_unlink && unlinkat(zlog_tmpdirfd, mmpath, 0))
+ zlog_err("unlink logbuf: %s (%d)", strerror(errno), errno);
+}
+
+#else /* !CAN_DO_TLS */
+void zlog_tls_buffer_init(void)
+{
+}
+
+void zlog_tls_buffer_fini(void)
+{
+}
+#endif
+
+void zlog_msg_pid(struct zlog_msg *msg, intmax_t *pid, intmax_t *tid)
+{
+#ifndef __OpenBSD__
+ static thread_local intmax_t cached_pid = -1;
+ if (cached_pid != -1)
+ *pid = cached_pid;
+ else
+ cached_pid = *pid = (intmax_t)getpid();
+#else
+ *pid = (intmax_t)getpid();
+#endif
+#ifdef CAN_DO_TLS
+ *tid = zlog_gettid();
+#else
+ *tid = *pid;
+#endif
+}
+
+static inline void zlog_tls_free(void *arg)
+{
+ struct zlog_tls *zlog_tls = arg;
+
+ if (!zlog_tls)
+ return;
+
+ munmap(zlog_tls->mmbuf, TLS_LOG_BUF_SIZE);
+ XFREE(MTYPE_LOG_TLSBUF, zlog_tls);
+}
+
+void zlog_tls_buffer_flush(void)
+{
+ struct zlog_target *zt;
+ struct zlog_tls *zlog_tls = zlog_tls_get();
+
+ if (!zlog_tls)
+ return;
+ if (!zlog_tls->nmsgs)
+ return;
+
+ rcu_read_lock();
+ frr_each_safe (zlog_targets, &zlog_targets, zt) {
+ if (!zt->logfn)
+ continue;
+
+ zt->logfn(zt, zlog_tls->msgp, zlog_tls->nmsgs);
+ }
+ rcu_read_unlock();
+
+ zlog_tls->bufpos = 0;
+ zlog_tls->nmsgs = 0;
+}
+
+
+static void vzlog_notls(const struct xref_logmsg *xref, int prio,
+ const char *fmt, va_list ap)
+{
+ struct zlog_target *zt;
+ struct zlog_msg stackmsg = {
+ .prio = prio & LOG_PRIMASK,
+ .fmt = fmt,
+ .xref = xref,
+ }, *msg = &stackmsg;
+ char stackbuf[512];
+
+ clock_gettime(CLOCK_REALTIME, &msg->ts);
+ va_copy(msg->args, ap);
+ msg->stackbuf = stackbuf;
+ msg->stackbufsz = sizeof(stackbuf);
+
+ rcu_read_lock();
+ frr_each_safe (zlog_targets, &zlog_targets, zt) {
+ if (prio > zt->prio_min)
+ continue;
+ if (!zt->logfn)
+ continue;
+
+ zt->logfn(zt, &msg, 1);
+ }
+ rcu_read_unlock();
+
+ va_end(msg->args);
+ if (msg->text && msg->text != stackbuf)
+ XFREE(MTYPE_LOG_MESSAGE, msg->text);
+}
+
+static void vzlog_tls(struct zlog_tls *zlog_tls, const struct xref_logmsg *xref,
+ int prio, const char *fmt, va_list ap)
+{
+ struct zlog_target *zt;
+ struct zlog_msg *msg;
+ char *buf;
+ bool ignoremsg = true;
+ bool immediate = default_immediate;
+
+ /* avoid further processing cost if no target wants this message */
+ rcu_read_lock();
+ frr_each (zlog_targets, &zlog_targets, zt) {
+ if (prio > zt->prio_min)
+ continue;
+ ignoremsg = false;
+ break;
+ }
+ rcu_read_unlock();
+
+ if (ignoremsg)
+ return;
+
+ msg = &zlog_tls->msgs[zlog_tls->nmsgs];
+ zlog_tls->nmsgs++;
+ if (zlog_tls->nmsgs == array_size(zlog_tls->msgs))
+ immediate = true;
+
+ memset(msg, 0, sizeof(*msg));
+ clock_gettime(CLOCK_REALTIME, &msg->ts);
+ va_copy(msg->args, ap);
+ msg->stackbuf = buf = zlog_tls->mmbuf + zlog_tls->bufpos;
+ msg->stackbufsz = TLS_LOG_BUF_SIZE - zlog_tls->bufpos - 1;
+ msg->fmt = fmt;
+ msg->prio = prio & LOG_PRIMASK;
+ msg->xref = xref;
+ if (msg->prio < LOG_INFO)
+ immediate = true;
+
+ if (!immediate) {
+ /* messages written later need to take the formatting cost
+ * immediately since we can't hold a reference on varargs
+ */
+ zlog_msg_text(msg, NULL);
+
+ if (msg->text != buf)
+ /* zlog_msg_text called malloc() on us :( */
+ immediate = true;
+ else {
+ zlog_tls->bufpos += msg->textlen + 1;
+ /* write a second \0 to mark current end position
+ * (in case of crash this signals end of unwritten log
+ * messages in mmap'd logbuf file)
+ */
+ zlog_tls->mmbuf[zlog_tls->bufpos] = '\0';
+
+ /* avoid malloc() for next message */
+ if (TLS_LOG_BUF_SIZE - zlog_tls->bufpos < 256)
+ immediate = true;
+ }
+ }
+
+ if (immediate)
+ zlog_tls_buffer_flush();
+
+ va_end(msg->args);
+ if (msg->text && msg->text != buf)
+ XFREE(MTYPE_LOG_MESSAGE, msg->text);
+}
+
+static void zlog_backtrace_msg(const struct xref_logmsg *xref, int prio)
+{
+ struct thread *tc = pthread_getspecific(thread_current);
+ const char *uid = xref->xref.xrefdata->uid;
+ bool found_thread = false;
+
+ zlog(prio, "| (%s) message in thread %jd, at %s(), %s:%d", uid,
+ zlog_gettid(), xref->xref.func, xref->xref.file, xref->xref.line);
+
+#ifdef HAVE_LIBUNWIND
+ const char *threadfunc = tc ? tc->xref->funcname : NULL;
+ bool found_caller = false;
+ unw_cursor_t cursor;
+ unw_context_t uc;
+ unw_word_t ip, off, sp;
+ Dl_info dlinfo;
+
+ unw_getcontext(&uc);
+
+ unw_init_local(&cursor, &uc);
+ while (unw_step(&cursor) > 0) {
+ char buf[96], name[128] = "?";
+ bool is_thread = false;
+
+ unw_get_reg(&cursor, UNW_REG_IP, &ip);
+ unw_get_reg(&cursor, UNW_REG_SP, &sp);
+
+ if (unw_is_signal_frame(&cursor))
+ zlog(prio, "| (%s) ---- signal ----", uid);
+
+ if (!unw_get_proc_name(&cursor, buf, sizeof(buf), &off)) {
+ if (!strcmp(buf, xref->xref.func))
+ found_caller = true;
+ if (threadfunc && !strcmp(buf, threadfunc))
+ found_thread = is_thread = true;
+
+ snprintf(name, sizeof(name), "%s+%#lx", buf, (long)off);
+ }
+
+ if (!found_caller)
+ continue;
+
+ if (dladdr((void *)ip, &dlinfo))
+ zlog(prio, "| (%s) %-36s %16lx+%08lx %16lx %s", uid,
+ name, (long)dlinfo.dli_fbase,
+ (long)ip - (long)dlinfo.dli_fbase, (long)sp,
+ dlinfo.dli_fname);
+ else
+ zlog(prio, "| (%s) %-36s %16lx %16lx", uid, name,
+ (long)ip, (long)sp);
+
+ if (is_thread)
+ zlog(prio, "| (%s) ^- scheduled from %s(), %s:%u", uid,
+ tc->xref->xref.func, tc->xref->xref.file,
+ tc->xref->xref.line);
+ }
+#elif defined(HAVE_GLIBC_BACKTRACE)
+ void *frames[64];
+ char **names = NULL;
+ int n_frames, i;
+
+ n_frames = backtrace(frames, array_size(frames));
+ if (n_frames < 0)
+ n_frames = 0;
+ if (n_frames)
+ names = backtrace_symbols(frames, n_frames);
+
+ for (i = 0; i < n_frames; i++) {
+ void *retaddr = frames[i];
+ char *loc = names[i];
+
+ zlog(prio, "| (%s) %16lx %-36s", uid, (long)retaddr, loc);
+ }
+ free(names);
+#endif
+ if (!found_thread && tc)
+ zlog(prio, "| (%s) scheduled from %s(), %s:%u", uid,
+ tc->xref->xref.func, tc->xref->xref.file,
+ tc->xref->xref.line);
+}
+
+void vzlogx(const struct xref_logmsg *xref, int prio,
+ const char *fmt, va_list ap)
+{
+ struct zlog_tls *zlog_tls = zlog_tls_get();
+
+#ifdef HAVE_LTTNG
+ va_list copy;
+ va_copy(copy, ap);
+ char *msg = vasprintfrr(MTYPE_LOG_MESSAGE, fmt, copy);
+
+ switch (prio) {
+ case LOG_ERR:
+ frrtracelog(TRACE_ERR, msg);
+ break;
+ case LOG_WARNING:
+ frrtracelog(TRACE_WARNING, msg);
+ break;
+ case LOG_DEBUG:
+ frrtracelog(TRACE_DEBUG, msg);
+ break;
+ case LOG_NOTICE:
+ frrtracelog(TRACE_DEBUG, msg);
+ break;
+ case LOG_INFO:
+ default:
+ frrtracelog(TRACE_INFO, msg);
+ break;
+ }
+
+ va_end(copy);
+ XFREE(MTYPE_LOG_MESSAGE, msg);
+#endif
+
+ if (zlog_tls)
+ vzlog_tls(zlog_tls, xref, prio, fmt, ap);
+ else
+ vzlog_notls(xref, prio, fmt, ap);
+
+ if (xref) {
+ struct xrefdata_logmsg *xrdl;
+
+ xrdl = container_of(xref->xref.xrefdata, struct xrefdata_logmsg,
+ xrefdata);
+ if (xrdl->fl_print_bt)
+ zlog_backtrace_msg(xref, prio);
+ }
+}
+
+void zlog_sigsafe(const char *text, size_t len)
+{
+ struct zlog_target *zt;
+ const char *end = text + len, *nlpos;
+
+ while (text < end) {
+ nlpos = memchr(text, '\n', end - text);
+ if (!nlpos)
+ nlpos = end;
+
+ frr_each (zlog_targets, &zlog_targets, zt) {
+ if (LOG_CRIT > zt->prio_min)
+ continue;
+ if (!zt->logfn_sigsafe)
+ continue;
+
+ zt->logfn_sigsafe(zt, text, nlpos - text);
+ }
+
+ if (nlpos == end)
+ break;
+ text = nlpos + 1;
+ }
+}
+
+void _zlog_assert_failed(const struct xref_assert *xref, const char *extra, ...)
+{
+ va_list ap;
+ static bool assert_in_assert; /* "global-ish" variable, init to 0 */
+
+ if (assert_in_assert)
+ abort();
+ assert_in_assert = true;
+
+ if (extra) {
+ struct va_format vaf;
+
+ va_start(ap, extra);
+ vaf.fmt = extra;
+ vaf.va = &ap;
+
+ zlog(LOG_CRIT,
+ "%s:%d: %s(): assertion (%s) failed, extra info: %pVA",
+ xref->xref.file, xref->xref.line, xref->xref.func,
+ xref->expr, &vaf);
+
+ va_end(ap);
+ } else
+ zlog(LOG_CRIT, "%s:%d: %s(): assertion (%s) failed",
+ xref->xref.file, xref->xref.line, xref->xref.func,
+ xref->expr);
+
+ /* abort() prints backtrace & memstats in SIGABRT handler */
+ abort();
+}
+
+int zlog_msg_prio(struct zlog_msg *msg)
+{
+ return msg->prio;
+}
+
+const struct xref_logmsg *zlog_msg_xref(struct zlog_msg *msg)
+{
+ return msg->xref;
+}
+
+const char *zlog_msg_text(struct zlog_msg *msg, size_t *textlen)
+{
+ if (!msg->text) {
+ va_list args;
+ bool do_xid, do_ec;
+ size_t need = 0, hdrlen;
+ struct fbuf fb = {
+ .buf = msg->stackbuf,
+ .pos = msg->stackbuf,
+ .len = msg->stackbufsz,
+ };
+
+ do_ec = atomic_load_explicit(&zlog_ec, memory_order_relaxed);
+ do_xid = atomic_load_explicit(&zlog_xid, memory_order_relaxed);
+
+ if (msg->xref && do_xid && msg->xref->xref.xrefdata->uid[0]) {
+ need += bputch(&fb, '[');
+ need += bputs(&fb, msg->xref->xref.xrefdata->uid);
+ need += bputch(&fb, ']');
+ }
+ if (msg->xref && do_ec && msg->xref->ec)
+ need += bprintfrr(&fb, "[EC %u]", msg->xref->ec);
+ if (need)
+ need += bputch(&fb, ' ');
+
+ msg->hdrlen = hdrlen = need;
+ assert(hdrlen < msg->stackbufsz);
+
+ fb.outpos = msg->argpos;
+ fb.outpos_n = array_size(msg->argpos);
+ fb.outpos_i = 0;
+
+ va_copy(args, msg->args);
+ need += vbprintfrr(&fb, msg->fmt, args);
+ va_end(args);
+
+ msg->textlen = need;
+ need += bputch(&fb, '\n');
+
+ if (need <= msg->stackbufsz)
+ msg->text = msg->stackbuf;
+ else {
+ msg->text = XMALLOC(MTYPE_LOG_MESSAGE, need);
+
+ memcpy(msg->text, msg->stackbuf, hdrlen);
+
+ fb.buf = msg->text;
+ fb.len = need;
+ fb.pos = msg->text + hdrlen;
+ fb.outpos_i = 0;
+
+ va_copy(args, msg->args);
+ vbprintfrr(&fb, msg->fmt, args);
+ va_end(args);
+
+ bputch(&fb, '\n');
+ }
+
+ msg->n_argpos = fb.outpos_i;
+ }
+ if (textlen)
+ *textlen = msg->textlen;
+ return msg->text;
+}
+
+void zlog_msg_args(struct zlog_msg *msg, size_t *hdrlen, size_t *n_argpos,
+ const struct fmt_outpos **argpos)
+{
+ if (!msg->text)
+ zlog_msg_text(msg, NULL);
+
+ if (hdrlen)
+ *hdrlen = msg->hdrlen;
+ if (n_argpos)
+ *n_argpos = msg->n_argpos;
+ if (argpos)
+ *argpos = msg->argpos;
+}
+
+#define ZLOG_TS_FORMAT (ZLOG_TS_ISO8601 | ZLOG_TS_LEGACY)
+#define ZLOG_TS_FLAGS ~ZLOG_TS_PREC
+
+size_t zlog_msg_ts(struct zlog_msg *msg, struct fbuf *out, uint32_t flags)
+{
+ size_t outsz = out ? (out->buf + out->len - out->pos) : 0;
+ size_t len1;
+
+ if (!(flags & ZLOG_TS_FORMAT))
+ return 0;
+
+ if (!(msg->ts_flags & ZLOG_TS_FORMAT) ||
+ ((msg->ts_flags ^ flags) & ZLOG_TS_UTC)) {
+ struct tm tm;
+
+ if (flags & ZLOG_TS_UTC)
+ gmtime_r(&msg->ts.tv_sec, &tm);
+ else
+ localtime_r(&msg->ts.tv_sec, &tm);
+
+ strftime(msg->ts_str, sizeof(msg->ts_str),
+ "%Y-%m-%dT%H:%M:%S", &tm);
+
+ if (flags & ZLOG_TS_UTC) {
+ msg->ts_zonetail[0] = 'Z';
+ msg->ts_zonetail[1] = '\0';
+ } else
+ snprintfrr(msg->ts_zonetail, sizeof(msg->ts_zonetail),
+ "%+03d:%02d",
+ (int)(tm.tm_gmtoff / 3600),
+ (int)(labs(tm.tm_gmtoff) / 60) % 60);
+
+ msg->ts_dot = msg->ts_str + strlen(msg->ts_str);
+ snprintfrr(msg->ts_dot,
+ msg->ts_str + sizeof(msg->ts_str) - msg->ts_dot,
+ ".%09lu", (unsigned long)msg->ts.tv_nsec);
+
+ msg->ts_flags = ZLOG_TS_ISO8601 | (flags & ZLOG_TS_UTC);
+ }
+
+ len1 = flags & ZLOG_TS_PREC;
+ len1 = (msg->ts_dot - msg->ts_str) + (len1 ? len1 + 1 : 0);
+
+ if (len1 > strlen(msg->ts_str))
+ len1 = strlen(msg->ts_str);
+
+ if (flags & ZLOG_TS_LEGACY) {
+ if (!out)
+ return len1;
+
+ if (len1 > outsz) {
+ memset(out->pos, 0, outsz);
+ out->pos += outsz;
+ return len1;
+ }
+
+ /* just swap out the formatting, faster than redoing it */
+ for (char *p = msg->ts_str; p < msg->ts_str + len1; p++) {
+ switch (*p) {
+ case '-':
+ *out->pos++ = '/';
+ break;
+ case 'T':
+ *out->pos++ = ' ';
+ break;
+ default:
+ *out->pos++ = *p;
+ }
+ }
+ return len1;
+ } else {
+ size_t len2 = strlen(msg->ts_zonetail);
+
+ if (!out)
+ return len1 + len2;
+
+ if (len1 + len2 > outsz) {
+ memset(out->pos, 0, outsz);
+ out->pos += outsz;
+ return len1 + len2;
+ }
+
+ memcpy(out->pos, msg->ts_str, len1);
+ out->pos += len1;
+ memcpy(out->pos, msg->ts_zonetail, len2);
+ out->pos += len2;
+ return len1 + len2;
+ }
+}
+
+size_t zlog_msg_ts_3164(struct zlog_msg *msg, struct fbuf *out, uint32_t flags)
+{
+ flags &= ZLOG_TS_UTC;
+
+ if (!msg->ts_3164_str[0] || flags != msg->ts_3164_flags) {
+ /* these are "hardcoded" in RFC3164, so they're here too... */
+ static const char *const months[12] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
+ };
+ struct tm tm;
+
+ /* RFC3164 explicitly asks for local time, but common usage
+ * also includes UTC.
+ */
+ if (flags & ZLOG_TS_UTC)
+ gmtime_r(&msg->ts.tv_sec, &tm);
+ else
+ localtime_r(&msg->ts.tv_sec, &tm);
+
+ snprintfrr(msg->ts_3164_str, sizeof(msg->ts_3164_str),
+ "%3s %2d %02d:%02d:%02d", months[tm.tm_mon],
+ tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
+
+ msg->ts_3164_flags = flags;
+ }
+ return bputs(out, msg->ts_3164_str);
+}
+
+void zlog_msg_tsraw(struct zlog_msg *msg, struct timespec *ts)
+{
+ memcpy(ts, &msg->ts, sizeof(*ts));
+}
+
+void zlog_set_prefix_ec(bool enable)
+{
+ atomic_store_explicit(&zlog_ec, enable, memory_order_relaxed);
+}
+
+bool zlog_get_prefix_ec(void)
+{
+ return atomic_load_explicit(&zlog_ec, memory_order_relaxed);
+}
+
+void zlog_set_prefix_xid(bool enable)
+{
+ atomic_store_explicit(&zlog_xid, enable, memory_order_relaxed);
+}
+
+bool zlog_get_prefix_xid(void)
+{
+ return atomic_load_explicit(&zlog_xid, memory_order_relaxed);
+}
+
+/* setup functions */
+
+struct zlog_target *zlog_target_clone(struct memtype *mt,
+ struct zlog_target *oldzt, size_t size)
+{
+ struct zlog_target *newzt;
+
+ newzt = XCALLOC(mt, size);
+ if (oldzt) {
+ newzt->prio_min = oldzt->prio_min;
+ newzt->logfn = oldzt->logfn;
+ newzt->logfn_sigsafe = oldzt->logfn_sigsafe;
+ }
+
+ return newzt;
+}
+
+struct zlog_target *zlog_target_replace(struct zlog_target *oldzt,
+ struct zlog_target *newzt)
+{
+ if (newzt)
+ zlog_targets_add_tail(&zlog_targets, newzt);
+ if (oldzt)
+ zlog_targets_del(&zlog_targets, oldzt);
+ return oldzt;
+}
+
+/*
+ * Enable or disable 'immediate' output - default is to buffer
+ * each pthread's messages.
+ */
+void zlog_set_immediate(bool set_p)
+{
+ default_immediate = set_p;
+}
+
+/* common init */
+
+#define TMPBASEDIR "/var/tmp/frr"
+
+static char zlog_tmpdir[MAXPATHLEN];
+
+void zlog_aux_init(const char *prefix, int prio_min)
+{
+ if (prefix)
+ strlcpy(zlog_prefix, prefix, sizeof(zlog_prefix));
+
+ hook_call(zlog_aux_init, prefix, prio_min);
+}
+
+void zlog_init(const char *progname, const char *protoname,
+ unsigned short instance, uid_t uid, gid_t gid)
+{
+ zlog_uid = uid;
+ zlog_gid = gid;
+ zlog_instance = instance;
+
+ if (instance) {
+ snprintfrr(zlog_tmpdir, sizeof(zlog_tmpdir), "%s/%s-%d.%ld",
+ TMPBASEDIR, progname, instance, (long)getpid());
+
+ zlog_prefixsz = snprintfrr(zlog_prefix, sizeof(zlog_prefix),
+ "%s[%d]: ", protoname, instance);
+ } else {
+ snprintfrr(zlog_tmpdir, sizeof(zlog_tmpdir), "%s/%s.%ld",
+ TMPBASEDIR, progname, (long)getpid());
+
+ zlog_prefixsz = snprintfrr(zlog_prefix, sizeof(zlog_prefix),
+ "%s: ", protoname);
+ }
+
+ if (mkdir(TMPBASEDIR, 0700) != 0) {
+ if (errno != EEXIST) {
+ zlog_err("failed to mkdir \"%s\": %s",
+ TMPBASEDIR, strerror(errno));
+ goto out_warn;
+ }
+ }
+ chown(TMPBASEDIR, zlog_uid, zlog_gid);
+
+ if (mkdir(zlog_tmpdir, 0700) != 0) {
+ zlog_err("failed to mkdir \"%s\": %s",
+ zlog_tmpdir, strerror(errno));
+ goto out_warn;
+ }
+
+#ifdef O_PATH
+ zlog_tmpdirfd = open(zlog_tmpdir,
+ O_PATH | O_RDONLY | O_CLOEXEC);
+#else
+ zlog_tmpdirfd = open(zlog_tmpdir,
+ O_DIRECTORY | O_RDONLY | O_CLOEXEC);
+#endif
+ if (zlog_tmpdirfd < 0) {
+ zlog_err("failed to open \"%s\": %s",
+ zlog_tmpdir, strerror(errno));
+ goto out_warn;
+ }
+
+#ifdef AT_EMPTY_PATH
+ fchownat(zlog_tmpdirfd, "", zlog_uid, zlog_gid, AT_EMPTY_PATH);
+#else
+ chown(zlog_tmpdir, zlog_uid, zlog_gid);
+#endif
+
+ hook_call(zlog_init, progname, protoname, instance, uid, gid);
+ return;
+
+out_warn:
+ zlog_err("crashlog and per-thread log buffering unavailable!");
+ hook_call(zlog_init, progname, protoname, instance, uid, gid);
+}
+
+void zlog_fini(void)
+{
+ hook_call(zlog_fini);
+
+ if (zlog_tmpdirfd >= 0) {
+ close(zlog_tmpdirfd);
+ zlog_tmpdirfd = -1;
+
+ if (rmdir(zlog_tmpdir))
+ zlog_err("failed to rmdir \"%s\": %s",
+ zlog_tmpdir, strerror(errno));
+ }
+}
diff --git a/lib/zlog.h b/lib/zlog.h
new file mode 100644
index 0000000..dcc0bf1
--- /dev/null
+++ b/lib/zlog.h
@@ -0,0 +1,297 @@
+/*
+ * Copyright (c) 2015-19 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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_ZLOG_H
+#define _FRR_ZLOG_H
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <sys/uio.h>
+
+#include <assert.h>
+
+#include "atomlist.h"
+#include "frrcu.h"
+#include "memory.h"
+#include "hook.h"
+#include "printfrr.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+DECLARE_MGROUP(LOG);
+
+extern char zlog_prefix[];
+extern size_t zlog_prefixsz;
+extern int zlog_tmpdirfd;
+extern int zlog_instance;
+extern const char *zlog_progname;
+
+struct xref_logmsg {
+ struct xref xref;
+
+ const char *fmtstring;
+ uint32_t priority;
+ uint32_t ec;
+ const char *args;
+};
+
+/* whether flag was added in config mode or enable mode */
+#define LOGMSG_FLAG_EPHEMERAL (1 << 0)
+#define LOGMSG_FLAG_PERSISTENT (1 << 1)
+
+struct xrefdata_logmsg {
+ struct xrefdata xrefdata;
+
+ uint8_t fl_print_bt;
+};
+
+/* These functions are set up to write to stdout/stderr without explicit
+ * initialization and/or before config load. There is no need to call e.g.
+ * fprintf(stderr, ...) just because it's "too early" at startup. Depending
+ * on context, it may still be the right thing to use fprintf though -- try to
+ * determine whether something is a log message or something else.
+ */
+
+extern void vzlogx(const struct xref_logmsg *xref, int prio,
+ const char *fmt, va_list ap);
+#define vzlog(prio, ...) vzlogx(NULL, prio, __VA_ARGS__)
+
+PRINTFRR(2, 3)
+static inline void zlog(int prio, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vzlog(prio, fmt, ap);
+ va_end(ap);
+}
+
+PRINTFRR(2, 3)
+static inline void zlog_ref(const struct xref_logmsg *xref,
+ const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vzlogx(xref, xref->priority, fmt, ap);
+ va_end(ap);
+}
+
+#define _zlog_ecref(ec_, prio, msg, ...) \
+ do { \
+ static struct xrefdata_logmsg _xrefdata = { \
+ .xrefdata = \
+ { \
+ .xref = NULL, \
+ .uid = {}, \
+ .hashstr = (msg), \
+ .hashu32 = {(prio), (ec_)}, \
+ }, \
+ }; \
+ static const struct xref_logmsg _xref __attribute__( \
+ (used)) = { \
+ .xref = XREF_INIT(XREFT_LOGMSG, &_xrefdata.xrefdata, \
+ __func__), \
+ .fmtstring = (msg), \
+ .priority = (prio), \
+ .ec = (ec_), \
+ .args = (#__VA_ARGS__), \
+ }; \
+ XREF_LINK(_xref.xref); \
+ zlog_ref(&_xref, (msg), ##__VA_ARGS__); \
+ } while (0)
+
+#define zlog_err(...) _zlog_ecref(0, LOG_ERR, __VA_ARGS__)
+#define zlog_warn(...) _zlog_ecref(0, LOG_WARNING, __VA_ARGS__)
+#define zlog_info(...) _zlog_ecref(0, LOG_INFO, __VA_ARGS__)
+#define zlog_notice(...) _zlog_ecref(0, LOG_NOTICE, __VA_ARGS__)
+#define zlog_debug(...) _zlog_ecref(0, LOG_DEBUG, __VA_ARGS__)
+
+#define flog_err(ferr_id, format, ...) \
+ _zlog_ecref(ferr_id, LOG_ERR, format, ## __VA_ARGS__)
+#define flog_warn(ferr_id, format, ...) \
+ _zlog_ecref(ferr_id, LOG_WARNING, format, ## __VA_ARGS__)
+
+#define flog_err_sys(ferr_id, format, ...) \
+ _zlog_ecref(ferr_id, LOG_ERR, format, ## __VA_ARGS__)
+
+extern void zlog_sigsafe(const char *text, size_t len);
+
+/* extra priority value to disable a target without deleting it */
+#define ZLOG_DISABLED (LOG_EMERG-1)
+
+/* zlog_msg encapsulates a particular logging call from somewhere in the code.
+ * The same struct is passed around to all zlog_targets.
+ *
+ * This is used to defer formatting the log message until it is actually
+ * requested by one of the targets. If none of the targets needs the message
+ * formatted, the formatting call is avoided entirely.
+ *
+ * This struct is opaque / private to the core zlog code. Logging targets
+ * should use zlog_msg_* functions to get text / timestamps / ... for a
+ * message.
+ */
+
+struct zlog_msg;
+
+extern int zlog_msg_prio(struct zlog_msg *msg);
+extern const struct xref_logmsg *zlog_msg_xref(struct zlog_msg *msg);
+
+/* text is NOT \0 terminated; instead there is a \n after textlen since the
+ * logging targets would jump extra hoops otherwise for a single byte. (the
+ * \n is not included in textlen)
+ *
+ * calling this with NULL textlen is likely wrong.
+ * use "%.*s", (int)textlen, text when passing to printf-like functions
+ */
+extern const char *zlog_msg_text(struct zlog_msg *msg, size_t *textlen);
+
+extern void zlog_msg_args(struct zlog_msg *msg, size_t *hdrlen,
+ size_t *n_argpos, const struct fmt_outpos **argpos);
+
+/* timestamp formatting control flags */
+
+/* sub-second digit count */
+#define ZLOG_TS_PREC 0xfU
+
+/* 8601: 0000-00-00T00:00:00Z (if used with ZLOG_TS_UTC)
+ * 0000-00-00T00:00:00+00:00 (otherwise)
+ * Legacy: 0000/00/00 00:00:00 (no TZ indicated!)
+ */
+#define ZLOG_TS_ISO8601 (1 << 8)
+#define ZLOG_TS_LEGACY (1 << 9)
+
+/* default is local time zone */
+#define ZLOG_TS_UTC (1 << 10)
+
+struct timespec;
+
+extern size_t zlog_msg_ts(struct zlog_msg *msg, struct fbuf *out,
+ uint32_t flags);
+extern void zlog_msg_tsraw(struct zlog_msg *msg, struct timespec *ts);
+
+/* "mmm dd hh:mm:ss" for RFC3164 syslog. Only ZLOG_TS_UTC for flags. */
+extern size_t zlog_msg_ts_3164(struct zlog_msg *msg, struct fbuf *out,
+ uint32_t flags);
+
+/* currently just returns the current PID/TID since we never write another
+ * thread's messages
+ */
+extern void zlog_msg_pid(struct zlog_msg *msg, intmax_t *pid, intmax_t *tid);
+
+/* This list & struct implements the actual logging targets. It is accessed
+ * lock-free from all threads, and thus MUST only be changed atomically, i.e.
+ * RCU.
+ *
+ * Since there's no atomic replace, the replacement action is an add followed
+ * by a delete. This means that during logging config changes, log messages
+ * may be duplicated in the log target that is being changed. The old entry
+ * being changed MUST also at the very least not crash or do other stupid
+ * things.
+ *
+ * This list and struct are NOT related to config. Logging config is kept
+ * separately, and results in creating appropriate zlog_target(s) to realize
+ * the config. Log targets may also be created from varying sources, e.g.
+ * command line options, or VTY commands ("log monitor").
+ *
+ * struct zlog_target is intended to be embedded into a larger structure that
+ * contains additional field for the specific logging target, e.g. an fd or
+ * additional options. It MUST be the first field in that larger struct.
+ */
+
+PREDECL_ATOMLIST(zlog_targets);
+struct zlog_target {
+ struct zlog_targets_item head;
+
+ int prio_min;
+
+ void (*logfn)(struct zlog_target *zt, struct zlog_msg *msg[],
+ size_t nmsgs);
+
+ /* for crash handlers, set to NULL if log target can't write crash logs
+ * without possibly deadlocking (AS-Safe)
+ *
+ * text is not \0 terminated & split up into lines (e.g. no \n)
+ */
+ void (*logfn_sigsafe)(struct zlog_target *zt, const char *text,
+ size_t len);
+
+ struct rcu_head rcu_head;
+};
+
+/* make a copy for RCUpdating. oldzt may be NULL to allocate a fresh one. */
+extern struct zlog_target *zlog_target_clone(struct memtype *mt,
+ struct zlog_target *oldzt,
+ size_t size);
+
+/* update the zlog_targets list; both oldzt and newzt may be NULL. You
+ * still need to zlog_target_free() the old target afterwards if it wasn't
+ * NULL.
+ *
+ * Returns oldzt so you can zlog_target_free(zlog_target_replace(old, new));
+ * (Some log targets may need extra cleanup inbetween, but remember the old
+ * target MUST remain functional until the end of the current RCU cycle.)
+ */
+extern struct zlog_target *zlog_target_replace(struct zlog_target *oldzt,
+ struct zlog_target *newzt);
+
+/* Mostly for symmetry for zlog_target_clone(), just rcu_free() internally. */
+#define zlog_target_free(mt, zt) \
+ rcu_free(mt, zt, rcu_head)
+
+extern void zlog_init(const char *progname, const char *protoname,
+ unsigned short instance, uid_t uid, gid_t gid);
+DECLARE_HOOK(zlog_init, (const char *progname, const char *protoname,
+ unsigned short instance, uid_t uid, gid_t gid),
+ (progname, protoname, instance, uid, gid));
+
+extern void zlog_fini(void);
+DECLARE_KOOH(zlog_fini, (), ());
+
+extern void zlog_set_prefix_ec(bool enable);
+extern bool zlog_get_prefix_ec(void);
+extern void zlog_set_prefix_xid(bool enable);
+extern bool zlog_get_prefix_xid(void);
+
+/* for tools & test programs, i.e. anything not a daemon.
+ * (no cleanup needed at exit)
+ */
+extern void zlog_aux_init(const char *prefix, int prio_min);
+DECLARE_HOOK(zlog_aux_init, (const char *prefix, int prio_min),
+ (prefix, prio_min));
+
+extern void zlog_startup_end(void);
+
+extern void zlog_tls_buffer_init(void);
+extern void zlog_tls_buffer_flush(void);
+extern void zlog_tls_buffer_fini(void);
+
+/* Enable or disable 'immediate' output - default is to buffer messages. */
+extern void zlog_set_immediate(bool set_p);
+
+extern const char *zlog_priority_str(int priority);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRR_ZLOG_H */
diff --git a/lib/zlog_5424.c b/lib/zlog_5424.c
new file mode 100644
index 0000000..9da7c55
--- /dev/null
+++ b/lib/zlog_5424.c
@@ -0,0 +1,1145 @@
+/*
+ * Copyright (c) 2015-21 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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.
+ */
+
+/* when you work on this code, please install a fuzzer (e.g. AFL) and run
+ * tests/lib/fuzz_zlog.c
+ *
+ * The most likely type of bug in this code is an off-by-one error in the
+ * buffer management pieces, and this isn't easily covered by an unit test
+ * or topotests. Fuzzing is the best tool here, but the CI can't do that
+ * since it's quite resource intensive.
+ */
+
+#include "zebra.h"
+
+#include "zlog_5424.h"
+
+#include <sys/un.h>
+#include <syslog.h>
+
+#include "memory.h"
+#include "frrcu.h"
+#include "printfrr.h"
+#include "typerb.h"
+#include "frr_pthread.h"
+#include "command.h"
+#include "monotime.h"
+#include "thread.h"
+
+#include "lib/version.h"
+#include "lib/lib_errors.h"
+
+DEFINE_MTYPE_STATIC(LOG, LOG_5424, "extended log target");
+DEFINE_MTYPE_STATIC(LOG, LOG_5424_ROTATE, "extended log rotate helper");
+
+/* the actual log target data structure
+ *
+ * remember this is RCU'd by the core zlog functions. Changing anything
+ * works by allocating a new struct, filling it, adding it, and removing the
+ * old one.
+ */
+struct zlt_5424 {
+ struct zlog_target zt;
+
+ atomic_uint_fast32_t fd;
+
+ enum zlog_5424_format fmt;
+ uint32_t ts_flags;
+ int facility;
+
+ /* the various extra pieces to add... */
+ bool kw_version : 1;
+ bool kw_location : 1;
+ bool kw_uid : 1;
+ bool kw_ec : 1;
+ bool kw_args : 1;
+
+ /* some formats may or may not include the trailing \n */
+ bool use_nl : 1;
+
+ /* for DGRAM & SEQPACKET sockets, send 1 log message per packet, since
+ * the socket preserves packet boundaries. On Linux, this uses
+ * sendmmsg() for efficiency, on other systems we need a syscall each.
+ */
+ bool packets : 1;
+
+ /* for DGRAM, in order to not have to reconnect, we need to use
+ * sendto()/sendmsg() with the destination given; otherwise we'll get
+ * ENOTCONN. (We do a connect(), which serves to verify the type of
+ * socket, but if the receiver goes away, the kernel disconnects the
+ * socket so writev() no longer works since the destination is now
+ * unspecified.)
+ */
+ struct sockaddr_storage sa;
+ socklen_t sa_len;
+
+ /* these are both getting set, but current_err is cleared on success,
+ * so we know whether the error is current or past.
+ */
+ int last_err, current_err;
+ atomic_size_t lost_msgs;
+ struct timeval last_err_ts;
+
+ struct rcu_head_close head_close;
+};
+
+static int zlog_5424_open(struct zlog_cfg_5424 *zcf, int sock_type);
+
+/* rough header length estimate
+ * ============================
+ *
+ * ^ = might grow
+ *
+ * 49^ longest filename (pceplib/test/pcep_utils_double_linked_list_test.h)
+ * 5^ highest line number (48530, bgpd/bgp_nb_config.c)
+ * 65^ longest function name
+ * (lib_prefix_list_entry_ipv6_prefix_length_greater_or_equal_destroy)
+ * 11 unique id ("XXXXX-XXXXX")
+ * 10 EC ("4294967295" or "0xffffffff")
+ * 35 ISO8601 TS at full length ("YYYY-MM-DD HH:MM:SS.NNNNNNNNN+ZZ:ZZ")
+ * ---
+ * 175
+ *
+ * rarely used (hopefully...):
+ * 26^ FRR_VERSION ("10.10.10-dev-gffffffffffff")
+ * ---
+ * 201
+ *
+ * x16 highest number of format parameters currently
+ * 40 estimate for hostname + 2*daemon + pid
+ *
+ * specific format overhead:
+ *
+ * RFC3164 - shorter than the others
+ * RFC5424 - 175 + "<999>1 "=7 + 52 (location@50145) + 40 (host/...)
+ * rarely: + 65 + 26 (for [origin])
+ * args: 16 * (8 + per-arg (20?)) = ~448
+ *
+ * so without "args@", origin or (future) keywords, around 256 seems OK
+ * with args@ and/or origin and/or keywords, 512 seems more reasonable
+ *
+ * but - note the code allocates this amount multiplied by the number of
+ * messages in the incoming batch (minimum 3), this means short messages and
+ * long messages smooth each other out.
+ *
+ * Since the code handles space-exceeded by grabbing a bunch of stack memory,
+ * a reasonable middle ground estimate is desirable here, so ...
+ * *drumroll*
+ * let's go with 128 + args?128. (remember the minimum 3 multiplier)
+ *
+ * low_space is the point where we don't try to fit another message in & just
+ * submit what we have to the kernel.
+ *
+ * The zlog code only buffers debug & informational messages, so in production
+ * usage most of the calls will be writing out only 1 message. This makes
+ * the min *3 multiplier quite useful.
+ */
+
+static inline size_t zlog_5424_bufsz(struct zlt_5424 *zte, size_t nmsgs,
+ size_t *low_space)
+{
+ size_t ret = 128;
+
+ if (zte->kw_args)
+ ret += 128;
+ *low_space = ret;
+ return ret * MAX(nmsgs, 3);
+}
+
+struct state {
+ struct fbuf *fbuf;
+ struct iovec *iov;
+};
+
+/* stack-based keyword support is likely to bump this to 3 or 4 */
+#define IOV_PER_MSG 2
+_Static_assert(IOV_MAX >= IOV_PER_MSG,
+ "this code won't work with IOV_MAX < IOV_PER_MSG");
+
+/* the following functions are quite similar, but trying to merge them just
+ * makes a big mess. check the others when touching one.
+ *
+ * timestamp keywords hostname
+ * RFC5424 ISO8601 yes yes
+ * RFC3164 RFC3164 no yes
+ * local RFC3164 no no
+ * journald ISO8601(unused) yes (unused)
+ */
+
+static size_t zlog_5424_one(struct zlt_5424 *zte, struct zlog_msg *msg,
+ struct state *state)
+{
+ size_t textlen;
+ struct fbuf *fbuf = state->fbuf;
+ char *orig_pos = fbuf->pos;
+ size_t need = 0;
+ int prio = zlog_msg_prio(msg);
+ intmax_t pid, tid;
+
+ zlog_msg_pid(msg, &pid, &tid);
+
+ need += bprintfrr(fbuf, "<%d>1 ", prio | zte->facility);
+ need += zlog_msg_ts(msg, fbuf, zte->ts_flags);
+ need += bprintfrr(fbuf, " %s %s %jd %.*s ", cmd_hostname_get() ?: "-",
+ zlog_progname, pid, (int)(zlog_prefixsz - 2),
+ zlog_prefix);
+
+ if (zte->kw_version)
+ need += bprintfrr(
+ fbuf,
+ "[origin enterpriseId=\"50145\" software=\"FRRouting\" swVersion=\"%s\"]",
+ FRR_VERSION);
+
+ const struct xref_logmsg *xref;
+ struct xrefdata *xrefdata;
+
+ need += bprintfrr(fbuf, "[location@50145 tid=\"%jd\"", tid);
+ if (zlog_instance > 0)
+ need += bprintfrr(fbuf, " instance=\"%d\"", zlog_instance);
+
+ xref = zlog_msg_xref(msg);
+ xrefdata = xref ? xref->xref.xrefdata : NULL;
+ if (xrefdata) {
+ if (zte->kw_uid)
+ need += bprintfrr(fbuf, " id=\"%s\"", xrefdata->uid);
+ if (zte->kw_ec && prio <= LOG_WARNING)
+ need += bprintfrr(fbuf, " ec=\"%u\"", xref->ec);
+ if (zte->kw_location)
+ need += bprintfrr(
+ fbuf, " file=\"%s\" line=\"%d\" func=\"%s\"",
+ xref->xref.file, xref->xref.line,
+ xref->xref.func);
+ }
+ need += bputch(fbuf, ']');
+
+ size_t hdrlen, n_argpos;
+ const struct fmt_outpos *argpos;
+ const char *text;
+
+ text = zlog_msg_text(msg, &textlen);
+ zlog_msg_args(msg, &hdrlen, &n_argpos, &argpos);
+
+ if (zte->kw_args && n_argpos) {
+ need += bputs(fbuf, "[args@50145");
+
+ for (size_t i = 0; i < n_argpos; i++) {
+ int len = argpos[i].off_end - argpos[i].off_start;
+
+ need += bprintfrr(fbuf, " arg%zu=%*pSQsq", i + 1, len,
+ text + argpos[i].off_start);
+ }
+
+ need += bputch(fbuf, ']');
+ }
+
+ need += bputch(fbuf, ' ');
+
+ if (orig_pos + need > fbuf->buf + fbuf->len) {
+ /* not enough space in the buffer for headers. the loop in
+ * zlog_5424() will flush other messages that are already in
+ * the buffer, grab a bigger buffer if needed, and try again.
+ */
+ fbuf->pos = orig_pos;
+ return need;
+ }
+
+ /* NB: zlog_5424 below assumes we use max. IOV_PER_MSG iovs here */
+ state->iov->iov_base = orig_pos;
+ state->iov->iov_len = fbuf->pos - orig_pos;
+ state->iov++;
+
+ state->iov->iov_base = (char *)text + hdrlen;
+ state->iov->iov_len = textlen - hdrlen + zte->use_nl;
+ state->iov++;
+ return 0;
+}
+
+static size_t zlog_3164_one(struct zlt_5424 *zte, struct zlog_msg *msg,
+ struct state *state)
+{
+ size_t textlen;
+ struct fbuf *fbuf = state->fbuf;
+ char *orig_pos = fbuf->pos;
+ size_t need = 0;
+ int prio = zlog_msg_prio(msg);
+ intmax_t pid, tid;
+
+ zlog_msg_pid(msg, &pid, &tid);
+
+ need += bprintfrr(fbuf, "<%d>", prio | zte->facility);
+ need += zlog_msg_ts_3164(msg, fbuf, zte->ts_flags);
+ if (zte->fmt != ZLOG_FMT_LOCAL) {
+ need += bputch(fbuf, ' ');
+ need += bputs(fbuf, cmd_hostname_get() ?: "-");
+ }
+ need += bprintfrr(fbuf, " %s[%jd]: ", zlog_progname, pid);
+
+ if (orig_pos + need > fbuf->buf + fbuf->len) {
+ /* not enough space in the buffer for headers. loop in
+ * zlog_5424() will flush other messages that are already in
+ * the buffer, grab a bigger buffer if needed, and try again.
+ */
+ fbuf->pos = orig_pos;
+ return need;
+ }
+
+ /* NB: zlog_5424 below assumes we use max. IOV_PER_MSG iovs here */
+ state->iov->iov_base = orig_pos;
+ state->iov->iov_len = fbuf->pos - orig_pos;
+ state->iov++;
+
+ state->iov->iov_base = (char *)zlog_msg_text(msg, &textlen);
+ state->iov->iov_len = textlen + zte->use_nl;
+ state->iov++;
+ return 0;
+}
+
+static size_t zlog_journald_one(struct zlt_5424 *zte, struct zlog_msg *msg,
+ struct state *state)
+{
+ size_t textlen;
+ struct fbuf *fbuf = state->fbuf;
+ char *orig_pos = fbuf->pos;
+ size_t need = 0;
+ int prio = zlog_msg_prio(msg);
+ intmax_t pid, tid;
+
+ zlog_msg_pid(msg, &pid, &tid);
+
+ need += bprintfrr(fbuf,
+ "PRIORITY=%d\n"
+ "SYSLOG_FACILITY=%d\n"
+ "TID=%jd\n"
+ "FRR_DAEMON=%s\n"
+ "SYSLOG_TIMESTAMP=",
+ prio, zte->facility, tid, zlog_progname);
+ need += zlog_msg_ts(msg, fbuf, zte->ts_flags);
+ need += bputch(fbuf, '\n');
+ if (zlog_instance > 0)
+ need += bprintfrr(fbuf, "FRR_INSTANCE=%d\n", zlog_instance);
+
+ const struct xref_logmsg *xref;
+ struct xrefdata *xrefdata;
+
+ xref = zlog_msg_xref(msg);
+ xrefdata = xref ? xref->xref.xrefdata : NULL;
+ if (xrefdata) {
+ if (zte->kw_uid && xrefdata->uid[0])
+ need += bprintfrr(fbuf, "FRR_ID=%s\n", xrefdata->uid);
+ if (zte->kw_ec && prio <= LOG_WARNING)
+ need += bprintfrr(fbuf, "FRR_EC=%d\n", xref->ec);
+ if (zte->kw_location)
+ need += bprintfrr(fbuf,
+ "CODE_FILE=%s\n"
+ "CODE_LINE=%d\n"
+ "CODE_FUNC=%s\n",
+ xref->xref.file, xref->xref.line,
+ xref->xref.func);
+ }
+
+ size_t hdrlen, n_argpos;
+ const struct fmt_outpos *argpos;
+ const char *text;
+
+ text = zlog_msg_text(msg, &textlen);
+ zlog_msg_args(msg, &hdrlen, &n_argpos, &argpos);
+
+ if (zte->kw_args && n_argpos) {
+ for (size_t i = 0; i < n_argpos; i++) {
+ int len = argpos[i].off_end - argpos[i].off_start;
+
+ /* rather than escape the value, we could use
+ * journald's binary encoding, but that seems a bit
+ * excessive/unnecessary. 99% of things we print here
+ * will just output 1:1 with %pSE.
+ */
+ need += bprintfrr(fbuf, "FRR_ARG%zu=%*pSE\n", i + 1,
+ len, text + argpos[i].off_start);
+ }
+ }
+
+ need += bputs(fbuf, "MESSAGE=");
+
+ if (orig_pos + need > fbuf->buf + fbuf->len) {
+ /* not enough space in the buffer for headers. loop in
+ * zlog_5424() will flush other messages that are already in
+ * the buffer, grab a bigger buffer if needed, and try again.
+ */
+ fbuf->pos = orig_pos;
+ return need;
+ }
+
+ /* NB: zlog_5424 below assumes we use max. IOV_PER_MSG iovs here */
+ state->iov->iov_base = orig_pos;
+ state->iov->iov_len = fbuf->pos - orig_pos;
+ state->iov++;
+
+ state->iov->iov_base = (char *)text + hdrlen;
+ state->iov->iov_len = textlen - hdrlen + 1;
+ state->iov++;
+ return 0;
+}
+
+static size_t zlog_one(struct zlt_5424 *zte, struct zlog_msg *msg,
+ struct state *state)
+{
+ switch (zte->fmt) {
+ case ZLOG_FMT_5424:
+ return zlog_5424_one(zte, msg, state);
+ case ZLOG_FMT_3164:
+ case ZLOG_FMT_LOCAL:
+ return zlog_3164_one(zte, msg, state);
+ case ZLOG_FMT_JOURNALD:
+ return zlog_journald_one(zte, msg, state);
+ }
+ return 0;
+}
+
+static void zlog_5424_err(struct zlt_5424 *zte, size_t count)
+{
+ if (!count) {
+ zte->current_err = 0;
+ return;
+ }
+
+ /* only the counter is atomic because otherwise it'd be meaningless */
+ atomic_fetch_add_explicit(&zte->lost_msgs, count, memory_order_relaxed);
+
+ /* these are non-atomic and can provide wrong results when read, but
+ * since they're only for debugging / display, that's OK.
+ */
+ zte->current_err = zte->last_err = errno;
+ monotime(&zte->last_err_ts);
+}
+
+static void zlog_5424(struct zlog_target *zt, struct zlog_msg *msgs[],
+ size_t nmsgs)
+{
+ size_t i;
+ struct zlt_5424 *zte = container_of(zt, struct zlt_5424, zt);
+ int fd, ret;
+ size_t niov = MIN(IOV_PER_MSG * nmsgs, IOV_MAX);
+ struct iovec iov[niov], *iov_last = iov + niov;
+ struct mmsghdr mmsg[zte->packets ? nmsgs : 1], *mpos = mmsg;
+ size_t count = 0;
+
+ /* refer to size estimate at top of file */
+ size_t low_space;
+ char hdr_buf[zlog_5424_bufsz(zte, nmsgs, &low_space)];
+ struct fbuf hdr_pos = {
+ .buf = hdr_buf,
+ .pos = hdr_buf,
+ .len = sizeof(hdr_buf),
+ };
+ struct state state = {
+ .fbuf = &hdr_pos,
+ .iov = iov,
+ };
+
+ fd = atomic_load_explicit(&zte->fd, memory_order_relaxed);
+
+ memset(mmsg, 0, sizeof(mmsg));
+ if (zte->sa_len) {
+ for (i = 0; i < array_size(mmsg); i++) {
+ mmsg[i].msg_hdr.msg_name = (struct sockaddr *)&zte->sa;
+ mmsg[i].msg_hdr.msg_namelen = zte->sa_len;
+ }
+ }
+ mmsg[0].msg_hdr.msg_iov = iov;
+
+ for (i = 0; i < nmsgs; i++) {
+ int prio = zlog_msg_prio(msgs[i]);
+ size_t need = 0;
+
+ if (prio <= zte->zt.prio_min) {
+ if (zte->packets)
+ mpos->msg_hdr.msg_iov = state.iov;
+
+ need = zlog_one(zte, msgs[i], &state);
+
+ if (zte->packets) {
+ mpos->msg_hdr.msg_iovlen =
+ state.iov - mpos->msg_hdr.msg_iov;
+ mpos++;
+ }
+ count++;
+ }
+
+ /* clang-format off */
+ if ((need
+ || (size_t)(hdr_pos.buf + hdr_pos.len - hdr_pos.pos)
+ < low_space
+ || i + 1 == nmsgs
+ || state.iov + IOV_PER_MSG > iov_last)
+ && state.iov > iov) {
+ /* clang-format on */
+
+ if (zte->packets) {
+ struct mmsghdr *sendpos;
+
+ for (sendpos = mmsg; sendpos < mpos;) {
+ ret = sendmmsg(fd, sendpos,
+ mpos - sendpos, 0);
+ if (ret <= 0)
+ break;
+ sendpos += ret;
+ }
+ zlog_5424_err(zte, mpos - sendpos);
+ mpos = mmsg;
+ } else {
+ if (!zte->sa_len)
+ ret = writev(fd, iov, state.iov - iov);
+ else {
+ mpos->msg_hdr.msg_iovlen =
+ state.iov - iov;
+ ret = sendmsg(fd, &mpos->msg_hdr, 0);
+ }
+
+ if (ret < 0)
+ zlog_5424_err(zte, count);
+ else
+ zlog_5424_err(zte, 0);
+ }
+
+ count = 0;
+ hdr_pos.pos = hdr_buf;
+ state.iov = iov;
+ }
+
+ /* if need == 0, we just put a message (or nothing) in the
+ * buffer and are continuing for more to batch in a single
+ * writev()
+ */
+ if (need == 0)
+ continue;
+
+ if (need && need <= sizeof(hdr_buf)) {
+ /* don't need to allocate, just try this msg
+ * again without other msgs already using up
+ * buffer space
+ */
+ i--;
+ continue;
+ }
+
+ /* need > sizeof(hdr_buf), need to grab some memory. Taking
+ * it off the stack is fine here.
+ */
+ char buf2[need];
+ struct fbuf fbuf2 = {
+ .buf = buf2,
+ .pos = buf2,
+ .len = sizeof(buf2),
+ };
+
+ state.fbuf = &fbuf2;
+ need = zlog_one(zte, msgs[i], &state);
+ assert(need == 0);
+
+ if (!zte->sa_len)
+ ret = writev(fd, iov, state.iov - iov);
+ else {
+ mpos->msg_hdr.msg_iovlen = state.iov - iov;
+ ret = sendmsg(fd, &mpos->msg_hdr, 0);
+ }
+
+ if (ret < 0)
+ zlog_5424_err(zte, 1);
+ else
+ zlog_5424_err(zte, 0);
+
+ count = 0;
+ state.fbuf = &hdr_pos;
+ state.iov = iov;
+ mpos = mmsg;
+ }
+
+ assert(state.iov == iov);
+}
+
+/* strftime(), gmtime_r() and localtime_r() aren't AS-Safe (they access locale
+ * data), but we need an AS-Safe timestamp below :(
+ */
+static void gmtime_assafe(time_t ts, struct tm *res)
+{
+ res->tm_sec = ts % 60;
+ ts /= 60;
+ res->tm_min = ts % 60;
+ ts /= 60;
+ res->tm_hour = ts % 24;
+ ts /= 24;
+
+ ts -= 11017; /* start on 2020-03-01, 11017 days since 1970-01-01 */
+
+ /* 1461 days = 3 regular years + 1 leap year
+ * this works until 2100, which isn't a leap year
+ *
+ * struct tm.tm_year starts at 1900.
+ */
+ res->tm_year = 2000 - 1900 + 4 * (ts / 1461);
+ ts = ts % 1461;
+
+ if (ts == 1460) {
+ res->tm_year += 4;
+ res->tm_mon = 1;
+ res->tm_mday = 29;
+ return;
+ }
+ res->tm_year += ts / 365;
+ ts %= 365;
+
+ /* note we're starting in march like the romans did... */
+ if (ts >= 306) /* Jan 1 of next year */
+ res->tm_year++;
+
+ static unsigned int months[13] = {
+ 0, 31, 61, 92, 122, 153, 184, 214, 245, 275, 306, 337, 365,
+ };
+
+ for (size_t i = 0; i < array_size(months); i++) {
+ if ((unsigned int)ts < months[i + 1]) {
+ res->tm_mon = ((i + 2) % 12);
+ res->tm_mday = 1 + ts - months[i];
+ break;
+ }
+ }
+}
+
+/* one of the greatest advantages of this logging target: unlike syslog(),
+ * which is not AS-Safe, we can send crashlogs to syslog here.
+ */
+static void zlog_5424_sigsafe(struct zlog_target *zt, const char *text,
+ size_t len)
+{
+ static const char *const months_3164[12] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
+ };
+
+ struct zlt_5424 *zte = container_of(zt, struct zlt_5424, zt);
+ struct iovec iov[3], *iovp = iov;
+ char buf[256];
+ struct fbuf fbuf = {
+ .buf = buf,
+ .pos = buf,
+ .len = sizeof(buf),
+ };
+ int fd;
+ intmax_t pid = (intmax_t)getpid();
+ struct tm tm;
+
+ switch (zte->fmt) {
+ case ZLOG_FMT_5424:
+ gmtime_assafe(time(NULL), &tm);
+ bprintfrr(
+ &fbuf,
+ "<%d>1 %04u-%02u-%02uT%02u:%02u:%02uZ - %s %jd %.*s ",
+ zte->facility | LOG_CRIT, tm.tm_year + 1900,
+ tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min,
+ tm.tm_sec, zlog_progname, pid, (int)(zlog_prefixsz - 2),
+ zlog_prefix);
+ break;
+
+ case ZLOG_FMT_3164:
+ case ZLOG_FMT_LOCAL:
+ /* this will unfortuantely be wrong by the timezone offset
+ * if the user selected non-UTC. But not much we can do
+ * about that...
+ */
+ gmtime_assafe(time(NULL), &tm);
+ bprintfrr(&fbuf, "<%d>%3s %2u %02u:%02u:%02u %s%s[%jd]: ",
+ zte->facility | LOG_CRIT, months_3164[tm.tm_mon],
+ tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
+ (zte->fmt == ZLOG_FMT_LOCAL) ? "" : "- ",
+ zlog_progname, pid);
+ break;
+
+ case ZLOG_FMT_JOURNALD:
+ bprintfrr(&fbuf,
+ "PRIORITY=%d\n"
+ "SYSLOG_FACILITY=%d\n"
+ "FRR_DAEMON=%s\n"
+ "MESSAGE=",
+ LOG_CRIT, zte->facility, zlog_progname);
+ break;
+ }
+
+ iovp->iov_base = fbuf.buf;
+ iovp->iov_len = fbuf.pos - fbuf.buf;
+ iovp++;
+
+ iovp->iov_base = (char *)text;
+ iovp->iov_len = len;
+ iovp++;
+
+ if (zte->use_nl) {
+ iovp->iov_base = (char *)"\n";
+ iovp->iov_len = 1;
+ iovp++;
+ }
+
+ fd = atomic_load_explicit(&zte->fd, memory_order_relaxed);
+
+ if (!zte->sa_len)
+ writev(fd, iov, iovp - iov);
+ else {
+ struct msghdr mh = {};
+
+ mh.msg_name = (struct sockaddr *)&zte->sa;
+ mh.msg_namelen = zte->sa_len;
+ mh.msg_iov = iov;
+ mh.msg_iovlen = iovp - iov;
+ sendmsg(fd, &mh, 0);
+ }
+}
+
+/* housekeeping & configuration */
+
+void zlog_5424_init(struct zlog_cfg_5424 *zcf)
+{
+ pthread_mutex_init(&zcf->cfg_mtx, NULL);
+}
+
+static void zlog_5424_target_free(struct zlt_5424 *zlt)
+{
+ if (!zlt)
+ return;
+
+ rcu_close(&zlt->head_close, zlt->fd);
+ rcu_free(MTYPE_LOG_5424, zlt, zt.rcu_head);
+}
+
+void zlog_5424_fini(struct zlog_cfg_5424 *zcf, bool keepopen)
+{
+ if (keepopen)
+ zcf->active = NULL;
+
+ if (zcf->active) {
+ struct zlt_5424 *ztf;
+ struct zlog_target *zt;
+
+ zt = zlog_target_replace(&zcf->active->zt, NULL);
+ ztf = container_of(zt, struct zlt_5424, zt);
+ zlog_5424_target_free(ztf);
+ }
+ pthread_mutex_destroy(&zcf->cfg_mtx);
+}
+
+static void zlog_5424_cycle(struct zlog_cfg_5424 *zcf, int fd)
+{
+ struct zlog_target *old;
+ struct zlt_5424 *zlt = NULL, *oldt;
+
+ if (fd >= 0) {
+ struct zlog_target *zt;
+
+ /* all of this is swapped in by zlog_target_replace() below,
+ * the old target is RCU-freed afterwards.
+ */
+ zt = zlog_target_clone(MTYPE_LOG_5424, &zcf->active->zt,
+ sizeof(*zlt));
+ zlt = container_of(zt, struct zlt_5424, zt);
+
+ zlt->fd = fd;
+ zlt->kw_version = zcf->kw_version;
+ zlt->kw_location = zcf->kw_location;
+ zlt->kw_uid = zcf->kw_uid;
+ zlt->kw_ec = zcf->kw_ec;
+ zlt->kw_args = zcf->kw_args;
+ zlt->use_nl = true;
+ zlt->facility = zcf->facility;
+
+ /* DGRAM & SEQPACKET = 1 log message per packet */
+ zlt->packets = (zcf->sock_type == SOCK_DGRAM) ||
+ (zcf->sock_type == SOCK_SEQPACKET);
+ zlt->sa = zcf->sa;
+ zlt->sa_len = zcf->sa_len;
+ zlt->fmt = zcf->fmt;
+ zlt->zt.prio_min = zcf->prio_min;
+ zlt->zt.logfn = zlog_5424;
+ zlt->zt.logfn_sigsafe = zlog_5424_sigsafe;
+
+ switch (zcf->fmt) {
+ case ZLOG_FMT_5424:
+ case ZLOG_FMT_JOURNALD:
+ zlt->ts_flags = zcf->ts_flags;
+ zlt->ts_flags &= ZLOG_TS_PREC | ZLOG_TS_UTC;
+ zlt->ts_flags |= ZLOG_TS_ISO8601;
+ break;
+ case ZLOG_FMT_3164:
+ case ZLOG_FMT_LOCAL:
+ zlt->ts_flags = zcf->ts_flags & ZLOG_TS_UTC;
+ if (zlt->packets)
+ zlt->use_nl = false;
+ break;
+ }
+ }
+
+ old = zcf->active ? &zcf->active->zt : NULL;
+ old = zlog_target_replace(old, &zlt->zt);
+ zcf->active = zlt;
+
+ /* oldt->fd == fd happens for zlog_5424_apply_meta() */
+ oldt = container_of(old, struct zlt_5424, zt);
+ if (oldt && oldt->fd != (unsigned int)fd)
+ rcu_close(&oldt->head_close, oldt->fd);
+ rcu_free(MTYPE_LOG_5424, oldt, zt.rcu_head);
+}
+
+static void zlog_5424_reconnect(struct thread *t)
+{
+ struct zlog_cfg_5424 *zcf = THREAD_ARG(t);
+ int fd = THREAD_FD(t);
+ char dummy[256];
+ ssize_t ret;
+
+ if (zcf->active) {
+ ret = read(fd, dummy, sizeof(dummy));
+ if (ret > 0) {
+ /* logger is sending us something?!?! */
+ thread_add_read(t->master, zlog_5424_reconnect, zcf, fd,
+ &zcf->t_reconnect);
+ return;
+ }
+
+ if (ret == 0)
+ zlog_warn("logging socket %pSE closed by peer",
+ zcf->filename);
+ else
+ zlog_warn("logging socket %pSE error: %m",
+ zcf->filename);
+ }
+
+ /* do NOT close() anything here; other threads may still be writing
+ * and their messages need to be lost rather than end up on a random
+ * other fd that got reassigned the same number, like a BGP session!
+ */
+ fd = zlog_5424_open(zcf, -1);
+
+ frr_with_mutex (&zcf->cfg_mtx) {
+ zlog_5424_cycle(zcf, fd);
+ }
+}
+
+static int zlog_5424_unix(struct sockaddr_un *suna, int sock_type)
+{
+ int fd;
+ int size = 1 * 1024 * 1024, prev_size;
+ socklen_t opt_size;
+ int save_errno;
+
+ fd = socket(AF_UNIX, sock_type, 0);
+ if (fd < 0)
+ return -1;
+
+ if (connect(fd, (struct sockaddr *)suna, sizeof(*suna))) {
+ /* zlog_5424_open() will print the error for connect() */
+ save_errno = errno;
+ close(fd);
+ errno = save_errno;
+ return -1;
+ }
+
+ opt_size = sizeof(prev_size);
+ if (getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &prev_size, &opt_size))
+ return fd;
+
+ /* setsockopt_so_sendbuf() logs on error; we don't really care that
+ * much here. Also, never shrink the buffer below the initial size.
+ */
+ while (size > prev_size &&
+ setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size)) == -1)
+ size /= 2;
+
+ return fd;
+}
+
+static int zlog_5424_open(struct zlog_cfg_5424 *zcf, int sock_type)
+{
+ int fd = -1;
+ int flags = 0;
+ int err;
+ socklen_t optlen;
+ bool do_chown = false;
+ bool need_reconnect = false;
+ static const int unix_types[] = {
+ SOCK_STREAM,
+ SOCK_SEQPACKET,
+ SOCK_DGRAM,
+ };
+ struct sockaddr_un sa;
+
+ zcf->sock_type = -1;
+ zcf->sa_len = 0;
+
+ switch (zcf->dst) {
+ case ZLOG_5424_DST_NONE:
+ break;
+
+ case ZLOG_5424_DST_FD:
+ fd = dup(zcf->fd);
+
+ optlen = sizeof(sock_type);
+ if (!getsockopt(fd, SOL_SOCKET, SO_TYPE, &sock_type, &optlen)) {
+ zcf->sock_type = sock_type;
+ need_reconnect = (zcf->sock_type != SOCK_DGRAM);
+ }
+ break;
+
+ case ZLOG_5424_DST_FIFO:
+ if (!zcf->filename)
+ break;
+
+ if (!zcf->file_nocreate) {
+ frr_with_privs (lib_privs) {
+ mode_t prevmask;
+
+ prevmask = umask(0777 ^ zcf->file_mode);
+ err = mkfifo(zcf->filename, 0666);
+ umask(prevmask);
+ }
+ if (err == 0)
+ do_chown = true;
+ else if (errno != EEXIST)
+ break;
+ }
+
+ flags = O_NONBLOCK;
+ /* fallthru */
+
+ case ZLOG_5424_DST_FILE:
+ if (!zcf->filename)
+ break;
+
+ frr_with_privs (lib_privs) {
+ fd = open(zcf->filename, flags | O_WRONLY | O_APPEND |
+ O_CLOEXEC | O_NOCTTY);
+ }
+ if (fd >= 0)
+ break;
+ if (zcf->file_nocreate || flags) {
+ flog_err_sys(EC_LIB_SYSTEM_CALL,
+ "could not open log file %pSE: %m",
+ zcf->filename);
+ break;
+ }
+
+ frr_with_privs (lib_privs) {
+ mode_t prevmask;
+
+ prevmask = umask(0777 ^ zcf->file_mode);
+ fd = open(zcf->filename,
+ O_WRONLY | O_APPEND | O_CLOEXEC | O_NOCTTY |
+ O_CREAT | O_EXCL,
+ zcf->file_mode);
+ umask(prevmask);
+ }
+ if (fd >= 0) {
+ do_chown = true;
+ break;
+ }
+
+ frr_with_privs (lib_privs) {
+ fd = open(zcf->filename,
+ O_WRONLY | O_APPEND | O_CLOEXEC | O_NOCTTY);
+ }
+ if (fd >= 0)
+ break;
+
+ flog_err_sys(EC_LIB_SYSTEM_CALL,
+ "could not open or create log file %pSE: %m",
+ zcf->filename);
+ break;
+
+ case ZLOG_5424_DST_UNIX:
+ if (!zcf->filename)
+ break;
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sun_family = AF_UNIX;
+ strlcpy(sa.sun_path, zcf->filename, sizeof(sa.sun_path));
+
+ /* check if ZLOG_5424_DST_FD needs a touch when changing
+ * something here. the user can pass in a pre-opened unix
+ * socket through a fd at startup.
+ */
+ frr_with_privs (lib_privs) {
+ if (sock_type != -1)
+ fd = zlog_5424_unix(&sa, sock_type);
+ else {
+ for (size_t i = 0; i < array_size(unix_types);
+ i++) {
+ fd = zlog_5424_unix(&sa, unix_types[i]);
+ if (fd != -1) {
+ zcf->sock_type = unix_types[i];
+ break;
+ }
+ }
+ }
+ }
+ if (fd == -1) {
+ zcf->sock_type = -1;
+
+ flog_err_sys(
+ EC_LIB_SYSTEM_CALL,
+ "could not connect to log unix path %pSE: %m",
+ zcf->filename);
+ need_reconnect = true;
+ } else {
+ /* datagram sockets are connectionless, restarting
+ * the receiver may lose some packets but will resume
+ * working afterwards without any action from us.
+ */
+ need_reconnect = (zcf->sock_type != SOCK_DGRAM);
+ }
+ break;
+ }
+
+ /* viable on both DST_FD and DST_UNIX path */
+ if (zcf->sock_type == SOCK_DGRAM) {
+ zcf->sa_len = sizeof(zcf->sa);
+ if (getpeername(fd, (struct sockaddr *)&zcf->sa,
+ &zcf->sa_len)) {
+ flog_err_sys(
+ EC_LIB_SYSTEM_CALL,
+ "could not get remote address for log socket. logging may break if log receiver restarts.");
+ zcf->sa_len = 0;
+ }
+ }
+
+ if (do_chown) {
+ uid_t uid = zcf->file_uid;
+ gid_t gid = zcf->file_gid;
+
+ if (uid != (uid_t)-1 || gid != (gid_t)-1) {
+ frr_with_privs (lib_privs) {
+ err = fchown(fd, uid, gid);
+ }
+ if (err)
+ flog_err_sys(
+ EC_LIB_SYSTEM_CALL,
+ "failed to chown() log file %pSE: %m",
+ zcf->filename);
+ }
+ }
+
+ if (need_reconnect) {
+ assert(zcf->master);
+
+ if (fd != -1) {
+ thread_add_read(zcf->master, zlog_5424_reconnect, zcf,
+ fd, &zcf->t_reconnect);
+ zcf->reconn_backoff_cur = zcf->reconn_backoff;
+
+ } else {
+ thread_add_timer_msec(zcf->master, zlog_5424_reconnect,
+ zcf, zcf->reconn_backoff_cur,
+ &zcf->t_reconnect);
+
+ zcf->reconn_backoff_cur += zcf->reconn_backoff_cur / 2;
+ if (zcf->reconn_backoff_cur > zcf->reconn_backoff_max)
+ zcf->reconn_backoff_cur =
+ zcf->reconn_backoff_max;
+ }
+ }
+
+ return fd;
+}
+
+bool zlog_5424_apply_dst(struct zlog_cfg_5424 *zcf)
+{
+ int fd = -1;
+
+ thread_cancel(&zcf->t_reconnect);
+
+ if (zcf->prio_min != ZLOG_DISABLED)
+ fd = zlog_5424_open(zcf, -1);
+
+ frr_with_mutex (&zcf->cfg_mtx) {
+ zlog_5424_cycle(zcf, fd);
+ }
+ return fd != -1;
+}
+
+
+bool zlog_5424_apply_meta(struct zlog_cfg_5424 *zcf)
+{
+ frr_with_mutex (&zcf->cfg_mtx) {
+ if (zcf->active)
+ zlog_5424_cycle(zcf, zcf->active->fd);
+ }
+
+ return true;
+}
+
+void zlog_5424_state(struct zlog_cfg_5424 *zcf, size_t *lost_msgs,
+ int *last_errno, bool *stale_errno, struct timeval *err_ts)
+{
+ if (lost_msgs)
+ *lost_msgs =
+ zcf->active
+ ? atomic_load_explicit(&zcf->active->lost_msgs,
+ memory_order_relaxed)
+ : 0;
+ if (last_errno)
+ *last_errno = zcf->active ? zcf->active->last_err : 0;
+ if (stale_errno)
+ *stale_errno = zcf->active ? !zcf->active->current_err : 0;
+ if (err_ts && zcf->active)
+ *err_ts = zcf->active->last_err_ts;
+}
+
+struct rcu_close_rotate {
+ struct rcu_head_close head_close;
+ struct rcu_head head_self;
+};
+
+bool zlog_5424_rotate(struct zlog_cfg_5424 *zcf)
+{
+ struct rcu_close_rotate *rcr;
+ int fd;
+
+ frr_with_mutex (&zcf->cfg_mtx) {
+ if (!zcf->active)
+ return true;
+
+ thread_cancel(&zcf->t_reconnect);
+
+ /* need to retain the socket type because it also influences
+ * other fields (packets) and we can't atomically swap these
+ * out. But we really want the atomic swap so we neither lose
+ * nor duplicate log messages, particularly for file targets.
+ *
+ * (zlog_5424_apply_dst / zlog_target_replace will cause
+ * duplicate log messages if another thread logs something
+ * while we're right in the middle of swapping out the log
+ * target)
+ */
+ fd = zlog_5424_open(zcf, zcf->sock_type);
+ if (fd < 0)
+ return false;
+
+ fd = atomic_exchange_explicit(&zcf->active->fd,
+ (uint_fast32_t)fd,
+ memory_order_relaxed);
+ }
+
+ rcr = XCALLOC(MTYPE_LOG_5424_ROTATE, sizeof(*rcr));
+ rcu_close(&rcr->head_close, fd);
+ rcu_free(MTYPE_LOG_5424_ROTATE, rcr, head_self);
+
+ return true;
+}
diff --git a/lib/zlog_5424.h b/lib/zlog_5424.h
new file mode 100644
index 0000000..b4a12cc
--- /dev/null
+++ b/lib/zlog_5424.h
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2021 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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_ZLOG_5424_H
+#define _FRR_ZLOG_5424_H
+
+#include <sys/stat.h>
+
+#include "typerb.h"
+#include "zlog.h"
+#include "zlog_targets.h"
+#include "qobj.h"
+
+struct thread;
+struct thread_master;
+
+enum zlog_5424_dst {
+ /* can be used to disable a target temporarily */
+ ZLOG_5424_DST_NONE = 0,
+
+ ZLOG_5424_DST_FD,
+ ZLOG_5424_DST_FILE,
+ ZLOG_5424_DST_FIFO,
+ ZLOG_5424_DST_UNIX,
+
+#define ZLOG_5424_DST_LAST ZLOG_5424_DST_UNIX
+};
+
+enum zlog_5424_format {
+ ZLOG_FMT_5424 = 0,
+ ZLOG_FMT_3164,
+ ZLOG_FMT_LOCAL,
+ ZLOG_FMT_JOURNALD,
+
+#define ZLOG_FMT_LAST ZLOG_FMT_JOURNALD
+};
+
+/* actual RCU'd logging backend */
+struct zlt_5424;
+
+struct zlog_cfg_5424 {
+ struct zlt_5424 *active;
+
+ pthread_mutex_t cfg_mtx;
+
+ /* general settings for all dsts */
+ int facility;
+ int prio_min;
+ bool kw_version;
+ bool kw_location;
+ bool kw_uid;
+ bool kw_ec;
+ bool kw_args;
+
+ uint32_t ts_flags;
+
+ enum zlog_5424_format fmt;
+
+ /* destination specifics */
+ enum zlog_5424_dst dst;
+
+ /* pre-opened FD. not the actual fd we log to */
+ int fd;
+
+ /* file, fifo, unix */
+ bool file_nocreate;
+
+ const char *filename;
+ mode_t file_mode;
+ /* -1 = no change */
+ uid_t file_uid;
+ gid_t file_gid;
+
+ /* remaining fields are internally used & updated by the 5424
+ * code - *not* config. don't set these.
+ */
+
+ /* sockets only - read handler to reconnect on errors */
+ struct thread_master *master;
+ struct thread *t_reconnect;
+ unsigned int reconn_backoff, reconn_backoff_cur, reconn_backoff_max;
+ int sock_type;
+ struct sockaddr_storage sa;
+ socklen_t sa_len;
+};
+
+/* these don't do malloc/free to allow using a static global */
+extern void zlog_5424_init(struct zlog_cfg_5424 *zcf);
+
+/* keepopen = true => for shutdown, just zap the config, keep logging */
+extern void zlog_5424_fini(struct zlog_cfg_5424 *zcf, bool keepopen);
+
+/* apply metadata/config changes */
+extern bool zlog_5424_apply_meta(struct zlog_cfg_5424 *zcf);
+
+/* apply changes requiring (re-)opening the destination
+ *
+ * also does log cycling/rotate & applies _meta at the same time
+ */
+extern bool zlog_5424_apply_dst(struct zlog_cfg_5424 *zcf);
+
+/* SIGHUP log rotation */
+extern bool zlog_5424_rotate(struct zlog_cfg_5424 *zcf);
+
+extern void zlog_5424_state(struct zlog_cfg_5424 *zcf, size_t *lost_msgs,
+ int *last_errno, bool *stale_errno,
+ struct timeval *err_ts);
+
+/* this is the dynamically allocated "variant" */
+PREDECL_RBTREE_UNIQ(targets);
+
+struct zlog_cfg_5424_user {
+ struct targets_item targets_item;
+ char *name;
+
+ struct zlog_cfg_5424 cfg;
+
+ char *envvar;
+
+ /* non-const, always same as cfg.filename */
+ char *filename;
+
+ /* uid/gid strings to write back out in show config */
+ char *file_user;
+ char *file_group;
+
+ bool reconf_dst;
+ bool reconf_meta;
+
+ int unix_special;
+
+ QOBJ_FIELDS;
+};
+
+DECLARE_QOBJ_TYPE(zlog_cfg_5424_user);
+
+extern void log_5424_cmd_init(void);
+
+#endif /* _FRR_ZLOG_5424_H */
diff --git a/lib/zlog_5424_cli.c b/lib/zlog_5424_cli.c
new file mode 100644
index 0000000..dd8dbfa
--- /dev/null
+++ b/lib/zlog_5424_cli.c
@@ -0,0 +1,1006 @@
+/*
+ * Copyright (C) 2021 David Lamparter for NetDEF, Inc.
+ *
+ * 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 "zlog_5424.h"
+
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+
+#include "lib/command.h"
+#include "lib/libfrr.h"
+#include "lib/log_vty.h"
+
+DEFINE_MTYPE_STATIC(LOG, LOG_5424_CONFIG, "extended syslog config");
+DEFINE_MTYPE_STATIC(LOG, LOG_5424_DATA, "extended syslog config items");
+
+static int target_cmp(const struct zlog_cfg_5424_user *a,
+ const struct zlog_cfg_5424_user *b)
+{
+ return strcmp(a->name, b->name);
+}
+
+DECLARE_RBTREE_UNIQ(targets, struct zlog_cfg_5424_user, targets_item,
+ target_cmp);
+DEFINE_QOBJ_TYPE(zlog_cfg_5424_user);
+
+static struct targets_head targets = INIT_RBTREE_UNIQ(targets);
+static struct thread_master *log_5424_master;
+
+static void clear_dst(struct zlog_cfg_5424_user *cfg);
+
+struct log_option {
+ const char *name;
+ ptrdiff_t offs;
+ bool dflt;
+};
+
+/* clang-format off */
+static struct log_option log_opts[] = {
+ { "code-location", offsetof(struct zlog_cfg_5424, kw_location) },
+ { "version", offsetof(struct zlog_cfg_5424, kw_version) },
+ { "unique-id", offsetof(struct zlog_cfg_5424, kw_uid), true },
+ { "error-category", offsetof(struct zlog_cfg_5424, kw_ec), true },
+ { "format-args", offsetof(struct zlog_cfg_5424, kw_args) },
+ {},
+};
+
+#define DFLT_TS_FLAGS (6 | ZLOG_TS_UTC)
+#define DFLT_FACILITY LOG_DAEMON
+#define DFLT_PRIO_MIN LOG_DEBUG
+/* clang-format on */
+
+enum unix_special {
+ SPECIAL_NONE = 0,
+ SPECIAL_SYSLOG,
+ SPECIAL_JOURNALD,
+};
+
+static struct zlog_cfg_5424_user *log_5424_alloc(const char *name)
+{
+ struct zlog_cfg_5424_user *cfg;
+
+ cfg = XCALLOC(MTYPE_LOG_5424_CONFIG, sizeof(*cfg));
+ cfg->name = XSTRDUP(MTYPE_LOG_5424_DATA, name);
+
+ cfg->cfg.master = log_5424_master;
+ cfg->cfg.kw_location = true;
+ cfg->cfg.kw_version = false;
+ cfg->cfg.facility = DFLT_FACILITY;
+ cfg->cfg.prio_min = DFLT_PRIO_MIN;
+ cfg->cfg.ts_flags = DFLT_TS_FLAGS;
+ clear_dst(cfg);
+
+ for (struct log_option *opt = log_opts; opt->name; opt++) {
+ bool *ptr = (bool *)(((char *)&cfg->cfg) + opt->offs);
+ *ptr = opt->dflt;
+ }
+
+ zlog_5424_init(&cfg->cfg);
+
+ QOBJ_REG(cfg, zlog_cfg_5424_user);
+ targets_add(&targets, cfg);
+ return cfg;
+}
+
+static void log_5424_free(struct zlog_cfg_5424_user *cfg, bool keepopen)
+{
+ targets_del(&targets, cfg);
+ QOBJ_UNREG(cfg);
+
+ zlog_5424_fini(&cfg->cfg, keepopen);
+ clear_dst(cfg);
+
+ XFREE(MTYPE_LOG_5424_DATA, cfg->filename);
+ XFREE(MTYPE_LOG_5424_DATA, cfg->name);
+ XFREE(MTYPE_LOG_5424_CONFIG, cfg);
+}
+
+static void clear_dst(struct zlog_cfg_5424_user *cfg)
+{
+ XFREE(MTYPE_LOG_5424_DATA, cfg->filename);
+ cfg->cfg.filename = cfg->filename;
+
+ XFREE(MTYPE_LOG_5424_DATA, cfg->file_user);
+ XFREE(MTYPE_LOG_5424_DATA, cfg->file_group);
+ XFREE(MTYPE_LOG_5424_DATA, cfg->envvar);
+
+ cfg->cfg.fd = -1;
+ cfg->cfg.file_uid = -1;
+ cfg->cfg.file_gid = -1;
+ cfg->cfg.file_mode = LOGFILE_MASK & 0666;
+ cfg->cfg.file_nocreate = false;
+ cfg->cfg.dst = ZLOG_5424_DST_NONE;
+}
+
+static int reconf_dst(struct zlog_cfg_5424_user *cfg, struct vty *vty)
+{
+ if (!cfg->reconf_dst && !cfg->reconf_meta && vty->type != VTY_FILE)
+ vty_out(vty,
+ "%% Changes will be applied when exiting this config block\n");
+
+ cfg->reconf_dst = true;
+ return CMD_SUCCESS;
+}
+
+static int reconf_meta(struct zlog_cfg_5424_user *cfg, struct vty *vty)
+{
+ if (!cfg->reconf_dst && !cfg->reconf_meta && vty->type != VTY_FILE)
+ vty_out(vty,
+ "%% Changes will be applied when exiting this config block\n");
+
+ cfg->reconf_meta = true;
+ return CMD_SUCCESS;
+}
+
+static int reconf_clear_dst(struct zlog_cfg_5424_user *cfg, struct vty *vty)
+{
+ if (cfg->cfg.dst == ZLOG_5424_DST_NONE)
+ return CMD_SUCCESS;
+
+ clear_dst(cfg);
+ return reconf_dst(cfg, vty);
+}
+
+#ifndef VTYSH_EXTRACT_PL
+#include "lib/zlog_5424_cli_clippy.c"
+#endif
+
+DEFPY_NOSH(log_5424_target,
+ log_5424_target_cmd,
+ "log extended-syslog EXTLOGNAME",
+ "Logging control\n"
+ "Extended RFC5424 syslog (including file targets)\n"
+ "Name identifying this syslog target\n")
+{
+ struct zlog_cfg_5424_user *cfg, ref;
+
+ ref.name = (char *)extlogname;
+ cfg = targets_find(&targets, &ref);
+
+ if (!cfg)
+ cfg = log_5424_alloc(extlogname);
+
+ VTY_PUSH_CONTEXT(EXTLOG_NODE, cfg);
+ return CMD_SUCCESS;
+}
+
+DEFPY(no_log_5424_target,
+ no_log_5424_target_cmd,
+ "no log extended-syslog EXTLOGNAME",
+ NO_STR
+ "Logging control\n"
+ "Extended RFC5424 syslog (including file targets)\n"
+ "Name identifying this syslog target\n")
+{
+ struct zlog_cfg_5424_user *cfg, ref;
+
+ ref.name = (char *)extlogname;
+ cfg = targets_find(&targets, &ref);
+
+ if (!cfg) {
+ vty_out(vty, "%% No extended syslog target named \"%s\"\n",
+ extlogname);
+ return CMD_WARNING;
+ }
+
+ log_5424_free(cfg, false);
+ return CMD_SUCCESS;
+}
+
+/* "format <rfc3164|rfc5424|local-syslogd|journald>$fmt" */
+#define FORMAT_HELP \
+ "Select log message formatting\n" \
+ "RFC3164 (legacy) syslog\n" \
+ "RFC5424 (modern) syslog, supports structured data (default)\n" \
+ "modified RFC3164 without hostname for local syslogd (/dev/log)\n" \
+ "journald (systemd log) native format\n" \
+ /* end */
+
+static enum zlog_5424_format log_5424_fmt(const char *fmt,
+ enum zlog_5424_format dflt)
+{
+ if (!fmt)
+ return dflt;
+ else if (!strcmp(fmt, "rfc5424"))
+ return ZLOG_FMT_5424;
+ else if (!strcmp(fmt, "rfc3164"))
+ return ZLOG_FMT_3164;
+ else if (!strcmp(fmt, "local-syslogd"))
+ return ZLOG_FMT_LOCAL;
+ else if (!strcmp(fmt, "journald"))
+ return ZLOG_FMT_JOURNALD;
+
+ return dflt;
+}
+
+DEFPY(log_5424_destination_file,
+ log_5424_destination_file_cmd,
+ "[no] destination file$type PATH "
+ "[create$create [{user WORD|group WORD|mode PERMS}]"
+ "|no-create$nocreate] "
+ "[format <rfc3164|rfc5424|local-syslogd|journald>$fmt]",
+ NO_STR
+ "Log destination setup\n"
+ "Log to file\n"
+ "Path to destination\n"
+ "Create file if it does not exist\n"
+ "Set file owner\n"
+ "User name\n"
+ "Set file group\n"
+ "Group name\n"
+ "Set permissions\n"
+ "File permissions (octal)\n"
+ "Do not create file if it does not exist\n"
+ FORMAT_HELP)
+{
+ VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg);
+ enum zlog_5424_dst dst;
+ bool reconf = true, warn_perm = false;
+ char *prev_user, *prev_group;
+ mode_t perm_val = LOGFILE_MASK & 0666;
+ enum zlog_5424_format fmtv;
+
+ if (no)
+ return reconf_clear_dst(cfg, vty);
+
+ fmtv = log_5424_fmt(fmt, ZLOG_FMT_5424);
+
+ if (perms) {
+ char *errp = (char *)perms;
+
+ perm_val = strtoul(perms, &errp, 8);
+ if (*errp || errp == perms || perm_val == 0 ||
+ (perm_val & ~0666)) {
+ vty_out(vty, "%% Invalid permissions value \"%s\"\n",
+ perms);
+ return CMD_WARNING;
+ }
+ }
+
+ dst = (strcmp(type, "fifo") == 0) ? ZLOG_5424_DST_FIFO
+ : ZLOG_5424_DST_FILE;
+
+ if (cfg->filename && !strcmp(path, cfg->filename) &&
+ dst == cfg->cfg.dst && cfg->cfg.active && cfg->cfg.fmt == fmtv)
+ reconf = false;
+
+ /* keep for compare below */
+ prev_user = cfg->file_user;
+ prev_group = cfg->file_group;
+ cfg->file_user = NULL;
+ cfg->file_group = NULL;
+
+ clear_dst(cfg);
+
+ cfg->filename = XSTRDUP(MTYPE_LOG_5424_DATA, path);
+ cfg->cfg.dst = dst;
+ cfg->cfg.filename = cfg->filename;
+ cfg->cfg.fmt = fmtv;
+
+ if (nocreate)
+ cfg->cfg.file_nocreate = true;
+ else {
+ if (user) {
+ struct passwd *pwent;
+
+ warn_perm |= (prev_user && strcmp(user, prev_user));
+ cfg->file_user = XSTRDUP(MTYPE_LOG_5424_DATA, user);
+
+ errno = 0;
+ pwent = getpwnam(user);
+ if (!pwent)
+ vty_out(vty,
+ "%% Could not look up user \"%s\" (%s), file owner will be left untouched!\n",
+ user,
+ errno ? safe_strerror(errno)
+ : "No entry by this user name");
+ else
+ cfg->cfg.file_uid = pwent->pw_uid;
+ }
+ if (group) {
+ struct group *grent;
+
+ warn_perm |= (prev_group && strcmp(group, prev_group));
+ cfg->file_group = XSTRDUP(MTYPE_LOG_5424_DATA, group);
+
+ errno = 0;
+ grent = getgrnam(group);
+ if (!grent)
+ vty_out(vty,
+ "%% Could not look up group \"%s\" (%s), file group will be left untouched!\n",
+ group,
+ errno ? safe_strerror(errno)
+ : "No entry by this group name");
+ else
+ cfg->cfg.file_gid = grent->gr_gid;
+ }
+ }
+ XFREE(MTYPE_LOG_5424_DATA, prev_user);
+ XFREE(MTYPE_LOG_5424_DATA, prev_group);
+
+ if (cfg->cfg.file_uid != (uid_t)-1 || cfg->cfg.file_gid != (gid_t)-1) {
+ struct stat st;
+
+ if (stat(cfg->filename, &st) == 0) {
+ warn_perm |= (st.st_uid != cfg->cfg.file_uid);
+ warn_perm |= (st.st_gid != cfg->cfg.file_gid);
+ }
+ }
+ if (warn_perm)
+ vty_out(vty,
+ "%% Warning: ownership and permission bits are only applied when creating\n"
+ "%% log files. Use system tools to change existing files.\n"
+ "%% FRR may also be missing necessary privileges to set these.\n");
+
+ if (reconf)
+ return reconf_dst(cfg, vty);
+
+ return CMD_SUCCESS;
+}
+
+/* FIFOs are for legacy /dev/log implementations; using this is very much not
+ * recommended since it can unexpectedly block in logging calls. Also the fd
+ * would need to be reopened when the process at the other end restarts. None
+ * of this is handled - use at your own caution. It's _HIDDEN for a purpose.
+ */
+ALIAS_HIDDEN(log_5424_destination_file,
+ log_5424_destination_fifo_cmd,
+ "[no] destination fifo$type PATH "
+ "[create$create [{owner WORD|group WORD|permissions PERMS}]"
+ "|no-create$nocreate] "
+ "[format <rfc3164|rfc5424|local-syslogd|journald>$fmt]",
+ NO_STR
+ "Log destination setup\n"
+ "Log to filesystem FIFO\n"
+ "Path to destination\n"
+ "Create file if it does not exist\n"
+ "Set file owner\n"
+ "User name\n"
+ "Set file group\n"
+ "Group name\n"
+ "Set permissions\n"
+ "File permissions (octal)\n"
+ "Do not create file if it does not exist\n"
+ FORMAT_HELP)
+
+static int dst_unix(struct vty *vty, const char *no, const char *path,
+ enum zlog_5424_format fmt, enum unix_special special)
+{
+ VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg);
+
+ if (no)
+ return reconf_clear_dst(cfg, vty);
+
+ cfg->unix_special = special;
+
+ if (cfg->cfg.dst == ZLOG_5424_DST_UNIX && cfg->filename &&
+ !strcmp(path, cfg->filename) && cfg->cfg.active &&
+ cfg->cfg.fmt == fmt)
+ return CMD_SUCCESS;
+
+ clear_dst(cfg);
+
+ cfg->filename = XSTRDUP(MTYPE_LOG_5424_DATA, path);
+ cfg->cfg.dst = ZLOG_5424_DST_UNIX;
+ cfg->cfg.filename = cfg->filename;
+ cfg->cfg.fmt = fmt;
+
+ cfg->cfg.reconn_backoff = 25;
+ cfg->cfg.reconn_backoff_cur = 25;
+ cfg->cfg.reconn_backoff_max = 10000;
+ return reconf_dst(cfg, vty);
+}
+
+DEFPY(log_5424_destination_unix,
+ log_5424_destination_unix_cmd,
+ "[no] destination unix PATH "
+ "[format <rfc3164|rfc5424|local-syslogd|journald>$fmt]",
+ NO_STR
+ "Log destination setup\n"
+ "Log to unix socket\n"
+ "Unix socket path\n"
+ FORMAT_HELP)
+{
+ VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg);
+ enum zlog_5424_format fmtv = log_5424_fmt(fmt, ZLOG_FMT_5424);
+
+ return dst_unix(vty, no, path, fmtv, SPECIAL_NONE);
+}
+
+DEFPY(log_5424_destination_journald,
+ log_5424_destination_journald_cmd,
+ "[no] destination journald",
+ NO_STR
+ "Log destination setup\n"
+ "Log directly to systemd's journald\n")
+{
+ return dst_unix(vty, no, "/run/systemd/journal/socket",
+ ZLOG_FMT_JOURNALD, SPECIAL_JOURNALD);
+}
+
+#if defined(__FreeBSD_version) && (__FreeBSD_version >= 1200061)
+#define ZLOG_FMT_DEV_LOG ZLOG_FMT_5424
+#elif defined(__NetBSD_Version__) && (__NetBSD_Version__ >= 500000000)
+#define ZLOG_FMT_DEV_LOG ZLOG_FMT_5424
+#else
+#define ZLOG_FMT_DEV_LOG ZLOG_FMT_LOCAL
+#endif
+
+DEFPY(log_5424_destination_syslog,
+ log_5424_destination_syslog_cmd,
+ "[no] destination syslog [supports-rfc5424]$supp5424",
+ NO_STR
+ "Log destination setup\n"
+ "Log directly to syslog\n"
+ "Use RFC5424 format (please refer to documentation)\n")
+{
+ int format = supp5424 ? ZLOG_FMT_5424 : ZLOG_FMT_DEV_LOG;
+
+ /* unfortunately, there is no way to detect 5424 support */
+ return dst_unix(vty, no, "/dev/log", format, SPECIAL_SYSLOG);
+}
+
+/* could add something like
+ * "destination <udp|tcp>$proto <A.B.C.D|X:X::X:X> (1-65535)$port"
+ * here, but there are 2 reasons not to do that:
+ *
+ * - each FRR daemon would open its own connection, there's no system level
+ * aggregation. That's the system's syslogd's job. It likely also
+ * supports directing & filtering log messages with configurable rules.
+ * - we're likely not going to support DTLS or TLS for more secure logging;
+ * adding this would require a considerable amount of additional config
+ * and an entire TLS library to begin with. A proper syslogd implements
+ * all of this, why reinvent the wheel?
+ */
+
+DEFPY(log_5424_destination_fd,
+ log_5424_destination_fd_cmd,
+ "[no] destination <fd <(0-63)$fd|envvar WORD>|stdout$fd1|stderr$fd2>"
+ "[format <rfc3164|rfc5424|local-syslogd|journald>$fmt]",
+ NO_STR
+ "Log destination setup\n"
+ "Log to pre-opened file descriptor\n"
+ "File descriptor number (must be open at startup)\n"
+ "Read file descriptor number from environment variable\n"
+ "Environment variable name\n"
+ "Log to standard output\n"
+ "Log to standard error output\n"
+ FORMAT_HELP)
+{
+ VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg);
+ bool envvar_problem = false;
+ enum zlog_5424_format fmtv;
+
+ if (no)
+ return reconf_clear_dst(cfg, vty);
+
+ fmtv = log_5424_fmt(fmt, ZLOG_FMT_5424);
+
+ if (envvar) {
+ char *envval;
+
+ envval = getenv(envvar);
+ if (!envval)
+ envvar_problem = true;
+ else {
+ char *errp = envval;
+
+ fd = strtoul(envval, &errp, 0);
+ if (errp == envval || *errp)
+ envvar_problem = true;
+ }
+
+ if (envvar_problem)
+ fd = -1;
+ } else if (fd1)
+ fd = 1;
+ else if (fd2)
+ fd = 2;
+
+ if (cfg->cfg.dst == ZLOG_5424_DST_FD && cfg->cfg.fd == fd &&
+ cfg->cfg.active && cfg->cfg.fmt == fmtv)
+ return CMD_SUCCESS;
+
+ clear_dst(cfg);
+
+ cfg->cfg.dst = ZLOG_5424_DST_FD;
+ cfg->cfg.fd = fd;
+ cfg->cfg.fmt = fmtv;
+ if (envvar)
+ cfg->envvar = XSTRDUP(MTYPE_LOG_5424_DATA, envvar);
+
+ if (envvar_problem)
+ vty_out(vty,
+ "%% environment variable \"%s\" not present or invalid.\n",
+ envvar);
+ if (!frr_is_startup_fd(fd))
+ vty_out(vty,
+ "%% file descriptor %d was not open when this process was started\n",
+ (int)fd);
+ if (envvar_problem || !frr_is_startup_fd(fd))
+ vty_out(vty,
+ "%% configuration will be saved but has no effect currently\n");
+
+ return reconf_dst(cfg, vty);
+}
+
+DEFPY(log_5424_destination_none,
+ log_5424_destination_none_cmd,
+ "[no] destination [none]",
+ NO_STR
+ "Log destination setup\n"
+ "Deconfigure destination\n")
+{
+ VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg);
+
+ return reconf_clear_dst(cfg, vty);
+}
+
+/* end of destinations */
+
+DEFPY(log_5424_prio,
+ log_5424_prio_cmd,
+ "[no] priority <emergencies|alerts|critical|errors|warnings|notifications|informational|debugging>$levelarg",
+ NO_STR
+ "Set minimum message priority to include for this target\n"
+ LOG_LEVEL_DESC)
+{
+ VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg);
+ int prio_min = log_level_match(levelarg);
+
+ if (prio_min == cfg->cfg.prio_min)
+ return CMD_SUCCESS;
+
+ cfg->cfg.prio_min = prio_min;
+ return reconf_meta(cfg, vty);
+}
+
+DEFPY(log_5424_facility,
+ log_5424_facility_cmd,
+ "[no] facility <kern|user|mail|daemon|auth|syslog|lpr|news|uucp|cron|local0|local1|local2|local3|local4|local5|local6|local7>$facilityarg",
+ NO_STR
+ "Set syslog facility to use\n"
+ LOG_FACILITY_DESC)
+{
+ VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg);
+ int facility = facility_match(facilityarg);
+
+ if (cfg->cfg.facility == facility)
+ return CMD_SUCCESS;
+
+ cfg->cfg.facility = facility;
+ return reconf_meta(cfg, vty);
+}
+
+DEFPY(log_5424_meta,
+ log_5424_meta_cmd,
+ "[no] structured-data <code-location|version|unique-id|error-category|format-args>$option",
+ NO_STR
+ "Select structured data (key/value pairs) to include in each message\n"
+ "FRR source code location\n"
+ "FRR version\n"
+ "Unique message identifier (XXXXX-XXXXX)\n"
+ "Error category (EC numeric)\n"
+ "Individual formatted log message arguments\n")
+{
+ VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg);
+ bool val = !no, *ptr;
+ struct log_option *opt = log_opts;
+
+ while (opt->name && strcmp(opt->name, option))
+ opt++;
+ if (!opt->name)
+ return CMD_WARNING;
+
+ ptr = (bool *)(((char *)&cfg->cfg) + opt->offs);
+ if (*ptr == val)
+ return CMD_SUCCESS;
+
+ *ptr = val;
+ return reconf_meta(cfg, vty);
+}
+
+DEFPY(log_5424_ts_prec,
+ log_5424_ts_prec_cmd,
+ "[no] timestamp precision (0-9)",
+ NO_STR
+ "Timestamp options\n"
+ "Number of sub-second digits to include\n"
+ "Number of sub-second digits to include\n")
+{
+ VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg);
+ uint32_t ts_flags = cfg->cfg.ts_flags;
+
+ ts_flags &= ~ZLOG_TS_PREC;
+ if (no)
+ ts_flags |= DFLT_TS_FLAGS & ZLOG_TS_PREC;
+ else
+ ts_flags |= precision;
+
+ if (ts_flags == cfg->cfg.ts_flags)
+ return CMD_SUCCESS;
+
+ cfg->cfg.ts_flags = ts_flags;
+ return reconf_meta(cfg, vty);
+}
+
+DEFPY(log_5424_ts_local,
+ log_5424_ts_local_cmd,
+ "[no] timestamp local-time",
+ NO_STR
+ "Timestamp options\n"
+ "Use local system time zone rather than UTC\n")
+{
+ VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg);
+ uint32_t ts_flags = cfg->cfg.ts_flags;
+
+ ts_flags &= ~ZLOG_TS_UTC;
+ if (no)
+ ts_flags |= DFLT_TS_FLAGS & ZLOG_TS_UTC;
+ else
+ ts_flags |= (~DFLT_TS_FLAGS) & ZLOG_TS_UTC;
+
+ if (ts_flags == cfg->cfg.ts_flags)
+ return CMD_SUCCESS;
+
+ cfg->cfg.ts_flags = ts_flags;
+ return reconf_meta(cfg, vty);
+}
+
+static int log_5424_node_exit(struct vty *vty)
+{
+ VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg);
+
+ if ((cfg->reconf_dst || cfg->reconf_meta) && vty->type != VTY_FILE)
+ vty_out(vty, "%% applying changes.\n");
+
+ if (cfg->reconf_dst)
+ zlog_5424_apply_dst(&cfg->cfg);
+ else if (cfg->reconf_meta)
+ zlog_5424_apply_meta(&cfg->cfg);
+
+ cfg->reconf_dst = cfg->reconf_meta = false;
+ return 1;
+}
+
+static int log_5424_config_write(struct vty *vty)
+{
+ struct zlog_cfg_5424_user *cfg;
+
+ frr_each (targets, &targets, cfg) {
+ const char *fmt_str = "";
+
+ vty_out(vty, "log extended %s\n", cfg->name);
+
+ switch (cfg->cfg.fmt) {
+ case ZLOG_FMT_5424:
+ fmt_str = " format rfc5424";
+ break;
+ case ZLOG_FMT_3164:
+ fmt_str = " format rfc3164";
+ break;
+ case ZLOG_FMT_LOCAL:
+ fmt_str = " format local-syslogd";
+ break;
+ case ZLOG_FMT_JOURNALD:
+ fmt_str = " format journald";
+ break;
+ }
+
+ switch (cfg->cfg.dst) {
+ case ZLOG_5424_DST_NONE:
+ vty_out(vty, " ! no destination configured\n");
+ break;
+
+ case ZLOG_5424_DST_FD:
+ if (cfg->cfg.fmt == ZLOG_FMT_5424)
+ fmt_str = "";
+
+ if (cfg->envvar)
+ vty_out(vty, " destination fd envvar %s%s\n",
+ cfg->envvar, fmt_str);
+ else if (cfg->cfg.fd == 1)
+ vty_out(vty, " destination stdout%s\n",
+ fmt_str);
+ else if (cfg->cfg.fd == 2)
+ vty_out(vty, " destination stderr%s\n",
+ fmt_str);
+ else
+ vty_out(vty, " destination fd %d%s\n",
+ cfg->cfg.fd, fmt_str);
+ break;
+
+ case ZLOG_5424_DST_FILE:
+ case ZLOG_5424_DST_FIFO:
+ if (cfg->cfg.fmt == ZLOG_FMT_5424)
+ fmt_str = "";
+
+ vty_out(vty, " destination %s %s",
+ (cfg->cfg.dst == ZLOG_5424_DST_FIFO) ? "fifo"
+ : "file",
+ cfg->filename);
+
+ if (cfg->cfg.file_nocreate)
+ vty_out(vty, " no-create");
+ else if (cfg->file_user || cfg->file_group ||
+ cfg->cfg.file_mode != (LOGFILE_MASK & 0666)) {
+ vty_out(vty, " create");
+
+ if (cfg->file_user)
+ vty_out(vty, " user %s",
+ cfg->file_user);
+ if (cfg->file_group)
+ vty_out(vty, " group %s",
+ cfg->file_group);
+ if (cfg->cfg.file_mode != (LOGFILE_MASK & 0666))
+ vty_out(vty, " mode %04o",
+ cfg->cfg.file_mode);
+ }
+ vty_out(vty, "%s\n", fmt_str);
+ break;
+
+ case ZLOG_5424_DST_UNIX:
+ switch (cfg->unix_special) {
+ case SPECIAL_NONE:
+ vty_out(vty, " destination unix %s%s\n",
+ cfg->filename, fmt_str);
+ break;
+ case SPECIAL_SYSLOG:
+ if (cfg->cfg.fmt == ZLOG_FMT_DEV_LOG)
+ vty_out(vty, " destination syslog\n");
+ else
+ vty_out(vty,
+ " destination syslog supports-rfc5424\n");
+ break;
+ case SPECIAL_JOURNALD:
+ vty_out(vty, " destination journald\n");
+ break;
+ }
+ break;
+ }
+
+ if (cfg->cfg.prio_min != LOG_DEBUG)
+ vty_out(vty, " priority %s\n",
+ zlog_priority_str(cfg->cfg.prio_min));
+ if (cfg->cfg.facility != DFLT_FACILITY)
+ vty_out(vty, " facility %s\n",
+ facility_name(cfg->cfg.facility));
+
+ for (struct log_option *opt = log_opts; opt->name; opt++) {
+ bool *ptr = (bool *)(((char *)&cfg->cfg) + opt->offs);
+
+ if (*ptr != opt->dflt)
+ vty_out(vty, " %sstructured-data %s\n",
+ *ptr ? "" : "no ", opt->name);
+ }
+
+ if ((cfg->cfg.ts_flags ^ DFLT_TS_FLAGS) & ZLOG_TS_PREC)
+ vty_out(vty, " timestamp precision %u\n",
+ cfg->cfg.ts_flags & ZLOG_TS_PREC);
+
+ if ((cfg->cfg.ts_flags ^ DFLT_TS_FLAGS) & ZLOG_TS_UTC) {
+ if (cfg->cfg.ts_flags & ZLOG_TS_UTC)
+ vty_out(vty, " no timestamp local-time\n");
+ else
+ vty_out(vty, " timestamp local-time\n");
+ }
+
+ vty_out(vty, "!\n");
+ }
+ return 0;
+}
+
+static int log_5424_show(struct vty *vty)
+{
+ struct zlog_cfg_5424_user *cfg;
+
+ frr_each (targets, &targets, cfg) {
+ vty_out(vty, "\nExtended log target %pSQq\n", cfg->name);
+
+ switch (cfg->cfg.dst) {
+ case ZLOG_5424_DST_NONE:
+ vty_out(vty,
+ " Inactive (no destination configured)\n");
+ break;
+
+ case ZLOG_5424_DST_FD:
+ if (cfg->envvar)
+ vty_out(vty,
+ " logging to fd %d from environment variable %pSE\n",
+ cfg->cfg.fd, cfg->envvar);
+ else if (cfg->cfg.fd == 1)
+ vty_out(vty, " logging to stdout\n");
+ else if (cfg->cfg.fd == 2)
+ vty_out(vty, " logging to stderr\n");
+ else
+ vty_out(vty, " logging to fd %d\n",
+ cfg->cfg.fd);
+ break;
+
+ case ZLOG_5424_DST_FILE:
+ case ZLOG_5424_DST_FIFO:
+ case ZLOG_5424_DST_UNIX:
+ vty_out(vty, " logging to %s: %pSE\n",
+ (cfg->cfg.dst == ZLOG_5424_DST_FIFO) ? "fifo"
+ : (cfg->cfg.dst == ZLOG_5424_DST_UNIX)
+ ? "unix socket"
+ : "file",
+ cfg->filename);
+ break;
+ }
+
+ vty_out(vty, " log level: %s, facility: %s\n",
+ zlog_priority_str(cfg->cfg.prio_min),
+ facility_name(cfg->cfg.facility));
+
+ bool any_meta = false, first = true;
+
+ for (struct log_option *opt = log_opts; opt->name; opt++) {
+ bool *ptr = (bool *)(((char *)&cfg->cfg) + opt->offs);
+
+ any_meta |= *ptr;
+ }
+
+ if (!any_meta)
+ continue;
+
+ switch (cfg->cfg.fmt) {
+ case ZLOG_FMT_5424:
+ case ZLOG_FMT_JOURNALD:
+ vty_out(vty, " structured data: ");
+
+ for (struct log_option *opt = log_opts; opt->name;
+ opt++) {
+ bool *ptr = (bool *)(((char *)&cfg->cfg) +
+ opt->offs);
+
+ if (*ptr) {
+ vty_out(vty, "%s%s", first ? "" : ", ",
+ opt->name);
+ first = false;
+ }
+ }
+ break;
+
+ default:
+ vty_out(vty,
+ " structured data is not supported by the selected format\n");
+ break;
+ }
+
+ vty_out(vty, "\n");
+
+ size_t lost_msgs;
+ int last_errno;
+ bool stale_errno;
+ struct timeval err_ts;
+ int64_t since;
+
+ zlog_5424_state(&cfg->cfg, &lost_msgs, &last_errno,
+ &stale_errno, &err_ts);
+ vty_out(vty, " number of lost messages: %zu\n", lost_msgs);
+
+ if (last_errno == 0)
+ since = 0;
+ else
+ since = monotime_since(&err_ts, NULL);
+ vty_out(vty,
+ " last error: %s (%lld.%06llds ago, currently %s)\n",
+ last_errno ? safe_strerror(last_errno) : "none",
+ since / 1000000LL, since % 1000000LL,
+ stale_errno ? "OK" : "erroring");
+ }
+ return 0;
+}
+
+static struct cmd_node extlog_node = {
+ .name = "extended",
+ .node = EXTLOG_NODE,
+ .parent_node = CONFIG_NODE,
+ .prompt = "%s(config-ext-log)# ",
+
+ .config_write = log_5424_config_write,
+ .node_exit = log_5424_node_exit,
+};
+
+static void log_5424_autocomplete(vector comps, struct cmd_token *token)
+{
+ struct zlog_cfg_5424_user *cfg;
+
+ frr_each (targets, &targets, cfg)
+ vector_set(comps, XSTRDUP(MTYPE_COMPLETION, cfg->name));
+}
+
+static const struct cmd_variable_handler log_5424_var_handlers[] = {
+ {.tokenname = "EXTLOGNAME", .completions = log_5424_autocomplete},
+ {.completions = NULL},
+};
+
+void log_5424_cmd_init(void)
+{
+ hook_register(zlog_cli_show, log_5424_show);
+
+ cmd_variable_handler_register(log_5424_var_handlers);
+
+ /* CLI commands. */
+ install_node(&extlog_node);
+ install_default(EXTLOG_NODE);
+
+ install_element(CONFIG_NODE, &log_5424_target_cmd);
+ install_element(CONFIG_NODE, &no_log_5424_target_cmd);
+
+ install_element(EXTLOG_NODE, &log_5424_destination_file_cmd);
+ install_element(EXTLOG_NODE, &log_5424_destination_fifo_cmd);
+ install_element(EXTLOG_NODE, &log_5424_destination_unix_cmd);
+ install_element(EXTLOG_NODE, &log_5424_destination_journald_cmd);
+ install_element(EXTLOG_NODE, &log_5424_destination_syslog_cmd);
+ install_element(EXTLOG_NODE, &log_5424_destination_fd_cmd);
+
+ install_element(EXTLOG_NODE, &log_5424_meta_cmd);
+ install_element(EXTLOG_NODE, &log_5424_prio_cmd);
+ install_element(EXTLOG_NODE, &log_5424_facility_cmd);
+ install_element(EXTLOG_NODE, &log_5424_ts_prec_cmd);
+ install_element(EXTLOG_NODE, &log_5424_ts_local_cmd);
+}
+
+/* hooks */
+
+static int log_5424_early_init(struct thread_master *master);
+static int log_5424_rotate(void);
+static int log_5424_fini(void);
+
+__attribute__((_CONSTRUCTOR(475))) static void zlog_5424_startup_init(void)
+{
+ hook_register(frr_early_init, log_5424_early_init);
+ hook_register(zlog_rotate, log_5424_rotate);
+ hook_register(frr_fini, log_5424_fini);
+}
+
+static int log_5424_early_init(struct thread_master *master)
+{
+ log_5424_master = master;
+
+ return 0;
+}
+
+static int log_5424_rotate(void)
+{
+ struct zlog_cfg_5424_user *cfg;
+
+ frr_each (targets, &targets, cfg)
+ if (!zlog_5424_rotate(&cfg->cfg))
+ zlog_err(
+ "log rotation on extended log target %s failed",
+ cfg->name);
+
+ return 0;
+}
+
+static int log_5424_fini(void)
+{
+ struct zlog_cfg_5424_user *cfg;
+
+ while ((cfg = targets_pop(&targets)))
+ log_5424_free(cfg, true);
+
+ log_5424_master = NULL;
+
+ return 0;
+}
diff --git a/lib/zlog_live.c b/lib/zlog_live.c
new file mode 100644
index 0000000..931aa34
--- /dev/null
+++ b/lib/zlog_live.c
@@ -0,0 +1,277 @@
+/*
+ * Copyright (c) 2019-22 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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 "zlog_live.h"
+
+#include "memory.h"
+#include "frrcu.h"
+#include "zlog.h"
+#include "printfrr.h"
+#include "network.h"
+
+DEFINE_MTYPE_STATIC(LOG, LOG_LIVE, "log vtysh live target");
+
+enum {
+ STATE_NORMAL = 0,
+ STATE_FD_DEAD,
+ STATE_DISOWNED,
+};
+
+struct zlt_live {
+ struct zlog_target zt;
+
+ atomic_uint_fast32_t fd;
+ struct rcu_head_close head_close;
+ struct rcu_head head_self;
+
+ atomic_uint_fast32_t state;
+ atomic_uint_fast32_t lost_msgs;
+};
+
+static void zlog_live(struct zlog_target *zt, struct zlog_msg *msgs[],
+ size_t nmsgs)
+{
+ struct zlt_live *zte = container_of(zt, struct zlt_live, zt);
+ struct zlog_live_hdr hdrs[nmsgs], *hdr = hdrs;
+ struct mmsghdr mmhs[nmsgs], *mmh = mmhs;
+ struct iovec iovs[nmsgs * 3], *iov = iovs;
+ struct timespec ts;
+ size_t i, textlen;
+ int fd;
+ uint_fast32_t state;
+
+ fd = atomic_load_explicit(&zte->fd, memory_order_relaxed);
+
+ if (fd < 0)
+ return;
+
+ memset(mmhs, 0, sizeof(mmhs));
+ memset(hdrs, 0, sizeof(hdrs));
+
+ for (i = 0; i < nmsgs; i++) {
+ const struct fmt_outpos *argpos;
+ size_t n_argpos, texthdrlen;
+ struct zlog_msg *msg = msgs[i];
+ int prio = zlog_msg_prio(msg);
+ const struct xref_logmsg *xref;
+ intmax_t pid, tid;
+
+ if (prio > zt->prio_min)
+ continue;
+
+ zlog_msg_args(msg, &texthdrlen, &n_argpos, &argpos);
+
+ mmh->msg_hdr.msg_iov = iov;
+
+ iov->iov_base = hdr;
+ iov->iov_len = sizeof(*hdr);
+ iov++;
+
+ if (n_argpos) {
+ iov->iov_base = (char *)argpos;
+ iov->iov_len = sizeof(*argpos) * n_argpos;
+ iov++;
+ }
+
+ iov->iov_base = (char *)zlog_msg_text(msg, &textlen);
+ iov->iov_len = textlen;
+ iov++;
+
+ zlog_msg_tsraw(msg, &ts);
+ zlog_msg_pid(msg, &pid, &tid);
+ xref = zlog_msg_xref(msg);
+
+ hdr->ts_sec = ts.tv_sec;
+ hdr->ts_nsec = ts.tv_nsec;
+ hdr->pid = pid;
+ hdr->tid = tid;
+ hdr->lost_msgs = atomic_load_explicit(&zte->lost_msgs,
+ memory_order_relaxed);
+ hdr->prio = prio;
+ hdr->flags = 0;
+ hdr->textlen = textlen;
+ hdr->texthdrlen = texthdrlen;
+ hdr->n_argpos = n_argpos;
+ if (xref) {
+ memcpy(hdr->uid, xref->xref.xrefdata->uid,
+ sizeof(hdr->uid));
+ hdr->ec = xref->ec;
+ } else {
+ memset(hdr->uid, 0, sizeof(hdr->uid));
+ hdr->ec = 0;
+ }
+ hdr->hdrlen = sizeof(*hdr) + sizeof(*argpos) * n_argpos;
+
+ mmh->msg_hdr.msg_iovlen = iov - mmh->msg_hdr.msg_iov;
+ mmh++;
+ hdr++;
+ }
+
+ size_t msgtotal = mmh - mmhs;
+ ssize_t sent;
+
+ for (size_t msgpos = 0; msgpos < msgtotal; msgpos += sent) {
+ sent = sendmmsg(fd, mmhs + msgpos, msgtotal - msgpos, 0);
+
+ if (sent <= 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
+ atomic_fetch_add_explicit(&zte->lost_msgs,
+ msgtotal - msgpos,
+ memory_order_relaxed);
+ break;
+ }
+ if (sent <= 0)
+ goto out_err;
+ }
+ return;
+
+out_err:
+ fd = atomic_exchange_explicit(&zte->fd, -1, memory_order_relaxed);
+ if (fd < 0)
+ return;
+
+ rcu_close(&zte->head_close, fd);
+ zlog_target_replace(zt, NULL);
+
+ state = STATE_NORMAL;
+ atomic_compare_exchange_strong_explicit(
+ &zte->state, &state, STATE_FD_DEAD, memory_order_relaxed,
+ memory_order_relaxed);
+ if (state == STATE_DISOWNED)
+ rcu_free(MTYPE_LOG_LIVE, zte, head_self);
+}
+
+static void zlog_live_sigsafe(struct zlog_target *zt, const char *text,
+ size_t len)
+{
+ struct zlt_live *zte = container_of(zt, struct zlt_live, zt);
+ struct zlog_live_hdr hdr[1] = {};
+ struct iovec iovs[2], *iov = iovs;
+ struct timespec ts;
+ int fd;
+
+ fd = atomic_load_explicit(&zte->fd, memory_order_relaxed);
+ if (fd < 0)
+ return;
+
+ clock_gettime(CLOCK_REALTIME, &ts);
+
+ hdr->ts_sec = ts.tv_sec;
+ hdr->ts_nsec = ts.tv_nsec;
+ hdr->prio = LOG_CRIT;
+ hdr->textlen = len;
+
+ iov->iov_base = (char *)hdr;
+ iov->iov_len = sizeof(hdr);
+ iov++;
+
+ iov->iov_base = (char *)text;
+ iov->iov_len = len;
+ iov++;
+
+ writev(fd, iovs, iov - iovs);
+}
+
+void zlog_live_open(struct zlog_live_cfg *cfg, int prio_min, int *other_fd)
+{
+ int sockets[2];
+
+ if (cfg->target)
+ zlog_live_close(cfg);
+
+ *other_fd = -1;
+ if (prio_min == ZLOG_DISABLED)
+ return;
+
+ /* the only reason for SEQPACKET here is getting close notifications.
+ * otherwise if you open a bunch of vtysh connections with live logs
+ * and close them all, the fds will stick around until we get an error
+ * when trying to log something to them at some later point -- which
+ * eats up fds and might be *much* later for some daemons.
+ */
+ if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets) < 0) {
+ if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sockets) < 0) {
+ zlog_warn("%% could not open socket pair: %m");
+ return;
+ }
+ } else
+ /* SEQPACKET only: try to zap read direction */
+ shutdown(sockets[0], SHUT_RD);
+
+ *other_fd = sockets[1];
+ zlog_live_open_fd(cfg, prio_min, sockets[0]);
+}
+
+void zlog_live_open_fd(struct zlog_live_cfg *cfg, int prio_min, int fd)
+{
+ struct zlt_live *zte;
+ struct zlog_target *zt;
+
+ if (cfg->target)
+ zlog_live_close(cfg);
+
+ zt = zlog_target_clone(MTYPE_LOG_LIVE, NULL, sizeof(*zte));
+ zte = container_of(zt, struct zlt_live, zt);
+ cfg->target = zte;
+
+ set_nonblocking(fd);
+ zte->fd = fd;
+ zte->zt.prio_min = prio_min;
+ zte->zt.logfn = zlog_live;
+ zte->zt.logfn_sigsafe = zlog_live_sigsafe;
+
+ zlog_target_replace(NULL, zt);
+}
+
+void zlog_live_close(struct zlog_live_cfg *cfg)
+{
+ struct zlt_live *zte;
+ int fd;
+
+ if (!cfg->target)
+ return;
+
+ zte = cfg->target;
+ cfg->target = NULL;
+
+ fd = atomic_exchange_explicit(&zte->fd, -1, memory_order_relaxed);
+
+ if (fd >= 0) {
+ rcu_close(&zte->head_close, fd);
+ zlog_target_replace(&zte->zt, NULL);
+ }
+ rcu_free(MTYPE_LOG_LIVE, zte, head_self);
+}
+
+void zlog_live_disown(struct zlog_live_cfg *cfg)
+{
+ struct zlt_live *zte;
+ uint_fast32_t state;
+
+ if (!cfg->target)
+ return;
+
+ zte = cfg->target;
+ cfg->target = NULL;
+
+ state = STATE_NORMAL;
+ atomic_compare_exchange_strong_explicit(
+ &zte->state, &state, STATE_DISOWNED, memory_order_relaxed,
+ memory_order_relaxed);
+ if (state == STATE_FD_DEAD)
+ rcu_free(MTYPE_LOG_LIVE, zte, head_self);
+}
diff --git a/lib/zlog_live.h b/lib/zlog_live.h
new file mode 100644
index 0000000..55e60ae
--- /dev/null
+++ b/lib/zlog_live.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2019-22 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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_ZLOG_LIVE_H
+#define _FRR_ZLOG_LIVE_H
+
+#include "printfrr.h"
+
+struct zlog_live_hdr {
+ /* timestamp (CLOCK_REALTIME) */
+ uint64_t ts_sec;
+ uint32_t ts_nsec;
+
+ /* length of zlog_live_hdr, including variable length bits and
+ * possible future extensions - aka start of text
+ */
+ uint32_t hdrlen;
+
+ /* process & thread ID, meaning depends on OS */
+ int64_t pid;
+ int64_t tid;
+
+ /* number of lost messages due to best-effort non-blocking mode */
+ uint32_t lost_msgs;
+ /* syslog priority value */
+ uint32_t prio;
+ /* flags: currently unused */
+ uint32_t flags;
+ /* length of message text - extra data (e.g. future key/value metadata)
+ * may follow after it
+ */
+ uint32_t textlen;
+ /* length of "[XXXXX-XXXXX][EC 0] " header; consumer may want to skip
+ * over it if using the raw values below. Note that this text may be
+ * absent depending on "log error-category" and "log unique-id"
+ * settings
+ */
+ uint32_t texthdrlen;
+
+ /* xref unique identifier, "XXXXX-XXXXX\0" = 12 bytes */
+ char uid[12];
+ /* EC value */
+ uint32_t ec;
+
+ /* recorded printf formatting argument positions (variable length) */
+ uint32_t n_argpos;
+ struct fmt_outpos argpos[0];
+};
+
+struct zlt_live;
+
+struct zlog_live_cfg {
+ struct zlt_live *target;
+
+ /* nothing else here */
+};
+
+extern void zlog_live_open(struct zlog_live_cfg *cfg, int prio_min,
+ int *other_fd);
+extern void zlog_live_open_fd(struct zlog_live_cfg *cfg, int prio_min, int fd);
+
+static inline bool zlog_live_is_null(struct zlog_live_cfg *cfg)
+{
+ return cfg->target == NULL;
+}
+
+extern void zlog_live_close(struct zlog_live_cfg *cfg);
+extern void zlog_live_disown(struct zlog_live_cfg *cfg);
+
+#endif /* _FRR_ZLOG_5424_H */
diff --git a/lib/zlog_targets.c b/lib/zlog_targets.c
new file mode 100644
index 0000000..31bd8e1
--- /dev/null
+++ b/lib/zlog_targets.c
@@ -0,0 +1,584 @@
+/*
+ * Copyright (c) 2015-19 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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 <sys/un.h>
+#include <syslog.h>
+
+#include "memory.h"
+#include "frrcu.h"
+#include "frr_pthread.h"
+#include "printfrr.h"
+#include "zlog.h"
+#include "zlog_targets.h"
+
+/* these allocations are intentionally left active even when doing full exit
+ * cleanup, in order to keep the logging subsystem fully functional until the
+ * absolute end.
+ */
+
+DEFINE_MGROUP_ACTIVEATEXIT(LOG, "logging subsystem");
+
+DEFINE_MTYPE_STATIC(LOG, LOG_FD, "log file target");
+DEFINE_MTYPE_STATIC(LOG, LOG_FD_NAME, "log file name");
+DEFINE_MTYPE_STATIC(LOG, LOG_FD_ROTATE, "log file rotate helper");
+DEFINE_MTYPE_STATIC(LOG, LOG_SYSL, "syslog target");
+
+struct zlt_fd {
+ struct zlog_target zt;
+
+ atomic_uint_fast32_t fd;
+
+ char ts_subsec;
+ bool record_priority;
+
+ struct rcu_head_close head_close;
+};
+
+static const char * const prionames[] = {
+ [LOG_EMERG] = "emergencies: ",
+ [LOG_ALERT] = "alerts: ",
+ [LOG_CRIT] = "critical: ",
+ [LOG_ERR] = "errors: ",
+ [LOG_WARNING] = "warnings: ",
+ [LOG_NOTICE] = "notifications: ",
+ [LOG_INFO] = "informational: ",
+ [LOG_DEBUG] = "debugging: ",
+};
+
+void zlog_fd(struct zlog_target *zt, struct zlog_msg *msgs[], size_t nmsgs)
+{
+ struct zlt_fd *zte = container_of(zt, struct zlt_fd, zt);
+ int fd;
+ size_t i, textlen, iovpos = 0;
+ size_t niov = MIN(4 * nmsgs + 1, IOV_MAX);
+ struct iovec iov[niov];
+ /* "\nYYYY-MM-DD HH:MM:SS.NNNNNNNNN+ZZ:ZZ " = 37 chars */
+#define TS_LEN 40
+ char ts_buf[TS_LEN * nmsgs], *ts_pos = ts_buf;
+
+ fd = atomic_load_explicit(&zte->fd, memory_order_relaxed);
+
+ for (i = 0; i < nmsgs; i++) {
+ struct zlog_msg *msg = msgs[i];
+ int prio = zlog_msg_prio(msg);
+
+ if (prio <= zt->prio_min) {
+ struct fbuf fbuf = {
+ .buf = ts_buf,
+ .pos = ts_pos,
+ .len = sizeof(ts_buf),
+ };
+
+ iov[iovpos].iov_base = ts_pos;
+ zlog_msg_ts(msg, &fbuf,
+ ZLOG_TS_LEGACY | zte->ts_subsec);
+ ts_pos = fbuf.pos;
+
+ *ts_pos++ = ' ';
+ iov[iovpos].iov_len =
+ ts_pos - (char *)iov[iovpos].iov_base;
+
+ iovpos++;
+
+ if (zte->record_priority) {
+ iov[iovpos].iov_base = (char *)prionames[prio];
+ iov[iovpos].iov_len =
+ strlen(iov[iovpos].iov_base);
+
+ iovpos++;
+ }
+
+ iov[iovpos].iov_base = zlog_prefix;
+ iov[iovpos].iov_len = zlog_prefixsz;
+
+ iovpos++;
+
+ iov[iovpos].iov_base =
+ (char *)zlog_msg_text(msg, &textlen);
+ iov[iovpos].iov_len = textlen + 1;
+
+ iovpos++;
+ }
+
+ /* conditions that trigger writing:
+ * - out of space for more timestamps/headers
+ * - this being the last message in the batch
+ * - not enough remaining iov entries
+ */
+ if (iovpos > 0 && (ts_buf + sizeof(ts_buf) - ts_pos < TS_LEN
+ || i + 1 == nmsgs
+ || array_size(iov) - iovpos < 5)) {
+ writev(fd, iov, iovpos);
+
+ iovpos = 0;
+ ts_pos = ts_buf;
+ }
+ }
+
+ assert(iovpos == 0);
+}
+
+static void zlog_fd_sigsafe(struct zlog_target *zt, const char *text,
+ size_t len)
+{
+ struct zlt_fd *zte = container_of(zt, struct zlt_fd, zt);
+ struct iovec iov[4];
+ int fd;
+
+ iov[0].iov_base = (char *)prionames[LOG_CRIT];
+ iov[0].iov_len = zte->record_priority ? strlen(iov[0].iov_base) : 0;
+
+ iov[1].iov_base = zlog_prefix;
+ iov[1].iov_len = zlog_prefixsz;
+
+ iov[2].iov_base = (char *)text;
+ iov[2].iov_len = len;
+
+ iov[3].iov_base = (char *)"\n";
+ iov[3].iov_len = 1;
+
+ fd = atomic_load_explicit(&zte->fd, memory_order_relaxed);
+
+ writev(fd, iov, array_size(iov));
+}
+
+/*
+ * (re-)configuration
+ */
+
+void zlog_file_init(struct zlog_cfg_file *zcf)
+{
+ memset(zcf, 0, sizeof(*zcf));
+ zcf->prio_min = ZLOG_DISABLED;
+ zcf->fd = -1;
+ pthread_mutex_init(&zcf->cfg_mtx, NULL);
+}
+
+static void zlog_file_target_free(struct zlt_fd *zlt)
+{
+ if (!zlt)
+ return;
+
+ rcu_close(&zlt->head_close, zlt->fd);
+ rcu_free(MTYPE_LOG_FD, zlt, zt.rcu_head);
+}
+
+void zlog_file_fini(struct zlog_cfg_file *zcf)
+{
+ if (zcf->active) {
+ struct zlt_fd *ztf;
+ struct zlog_target *zt;
+
+ zt = zlog_target_replace(&zcf->active->zt, NULL);
+ ztf = container_of(zt, struct zlt_fd, zt);
+ zlog_file_target_free(ztf);
+ }
+ XFREE(MTYPE_LOG_FD_NAME, zcf->filename);
+ pthread_mutex_destroy(&zcf->cfg_mtx);
+}
+
+static bool zlog_file_cycle(struct zlog_cfg_file *zcf)
+{
+ struct zlog_target *zt, *old;
+ struct zlt_fd *zlt = NULL;
+ int fd;
+ bool rv = true;
+
+ do {
+ if (zcf->prio_min == ZLOG_DISABLED)
+ break;
+
+ if (zcf->fd != -1)
+ fd = dup(zcf->fd);
+ else if (zcf->filename)
+ fd = open(zcf->filename,
+ O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC
+ | O_NOCTTY,
+ LOGFILE_MASK);
+ else
+ fd = -1;
+
+ if (fd < 0) {
+ rv = false;
+ break;
+ }
+
+ zt = zlog_target_clone(MTYPE_LOG_FD, &zcf->active->zt,
+ sizeof(*zlt));
+ zlt = container_of(zt, struct zlt_fd, zt);
+
+ zlt->fd = fd;
+ zlt->record_priority = zcf->record_priority;
+ zlt->ts_subsec = zcf->ts_subsec;
+
+ zlt->zt.prio_min = zcf->prio_min;
+ zlt->zt.logfn = zcf->zlog_wrap ? zcf->zlog_wrap : zlog_fd;
+ zlt->zt.logfn_sigsafe = zlog_fd_sigsafe;
+ } while (0);
+
+ old = zlog_target_replace(zcf->active ? &zcf->active->zt : NULL,
+ zlt ? &zlt->zt : NULL);
+ zcf->active = zlt;
+
+ zlog_file_target_free(container_of_null(old, struct zlt_fd, zt));
+
+ return rv;
+}
+
+void zlog_file_set_other(struct zlog_cfg_file *zcf)
+{
+ frr_with_mutex (&zcf->cfg_mtx) {
+ zlog_file_cycle(zcf);
+ }
+}
+
+bool zlog_file_set_filename(struct zlog_cfg_file *zcf, const char *filename)
+{
+ frr_with_mutex (&zcf->cfg_mtx) {
+ XFREE(MTYPE_LOG_FD_NAME, zcf->filename);
+ zcf->filename = XSTRDUP(MTYPE_LOG_FD_NAME, filename);
+ zcf->fd = -1;
+
+ return zlog_file_cycle(zcf);
+ }
+ assert(0);
+}
+
+bool zlog_file_set_fd(struct zlog_cfg_file *zcf, int fd)
+{
+ frr_with_mutex (&zcf->cfg_mtx) {
+ if (zcf->fd == fd)
+ return true;
+
+ XFREE(MTYPE_LOG_FD_NAME, zcf->filename);
+ zcf->fd = fd;
+
+ return zlog_file_cycle(zcf);
+ }
+ assert(0);
+}
+
+struct rcu_close_rotate {
+ struct rcu_head_close head_close;
+ struct rcu_head head_self;
+};
+
+bool zlog_file_rotate(struct zlog_cfg_file *zcf)
+{
+ struct rcu_close_rotate *rcr;
+ int fd;
+
+ frr_with_mutex (&zcf->cfg_mtx) {
+ if (!zcf->active || !zcf->filename)
+ return true;
+
+ fd = open(zcf->filename,
+ O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC | O_NOCTTY,
+ LOGFILE_MASK);
+ if (fd < 0)
+ return false;
+
+ fd = atomic_exchange_explicit(&zcf->active->fd,
+ (uint_fast32_t)fd,
+ memory_order_relaxed);
+ }
+
+ rcr = XCALLOC(MTYPE_LOG_FD_ROTATE, sizeof(*rcr));
+ rcu_close(&rcr->head_close, fd);
+ rcu_free(MTYPE_LOG_FD_ROTATE, rcr, head_self);
+
+ return true;
+}
+
+/* fixed crash logging */
+
+static struct zlt_fd zlog_crashlog;
+
+static void zlog_crashlog_sigsafe(struct zlog_target *zt, const char *text,
+ size_t len)
+{
+ static int crashlog_fd = -1;
+
+ if (crashlog_fd == -1) {
+#ifdef HAVE_OPENAT
+ crashlog_fd = openat(zlog_tmpdirfd, "crashlog",
+ O_WRONLY | O_APPEND | O_CREAT,
+ LOGFILE_MASK);
+#endif
+ if (crashlog_fd < 0)
+ crashlog_fd = -2;
+ }
+
+ if (crashlog_fd == -2)
+ return;
+
+ zlog_crashlog.fd = crashlog_fd;
+ zlog_fd_sigsafe(&zlog_crashlog.zt, text, len);
+}
+
+/* this is used for assert failures (they don't need AS-Safe logging) */
+static void zlog_crashlog_plain(struct zlog_target *zt, struct zlog_msg *msgs[],
+ size_t nmsgs)
+{
+ size_t i, len;
+ const char *text;
+
+ for (i = 0; i < nmsgs; i++) {
+ if (zlog_msg_prio(msgs[i]) > zt->prio_min)
+ continue;
+
+ text = zlog_msg_text(msgs[i], &len);
+ zlog_crashlog_sigsafe(zt, text, len);
+ }
+}
+
+static void zlog_crashlog_init(void)
+{
+ zlog_crashlog.zt.prio_min = LOG_CRIT;
+ zlog_crashlog.zt.logfn = zlog_crashlog_plain;
+ zlog_crashlog.zt.logfn_sigsafe = zlog_crashlog_sigsafe;
+ zlog_crashlog.fd = -1;
+
+ zlog_target_replace(NULL, &zlog_crashlog.zt);
+}
+
+/* fixed logging for test/auxiliary programs */
+
+static struct zlt_fd zlog_aux_stdout;
+static bool zlog_is_aux;
+
+static int zlt_aux_init(const char *prefix, int prio_min)
+{
+ zlog_is_aux = true;
+
+ zlog_aux_stdout.zt.prio_min = prio_min;
+ zlog_aux_stdout.zt.logfn = zlog_fd;
+ zlog_aux_stdout.zt.logfn_sigsafe = zlog_fd_sigsafe;
+ zlog_aux_stdout.fd = STDOUT_FILENO;
+
+ zlog_target_replace(NULL, &zlog_aux_stdout.zt);
+ zlog_startup_end();
+ return 0;
+}
+
+static int zlt_init(const char *progname, const char *protoname,
+ unsigned short instance, uid_t uid, gid_t gid)
+{
+ openlog(progname, LOG_CONS | LOG_NDELAY | LOG_PID, LOG_DAEMON);
+ return 0;
+}
+
+static int zlt_fini(void)
+{
+ closelog();
+ return 0;
+}
+
+/* fixed startup logging to stderr */
+
+static struct zlt_fd zlog_startup_stderr;
+
+__attribute__((_CONSTRUCTOR(450))) static void zlog_startup_init(void)
+{
+ zlog_startup_stderr.zt.prio_min = LOG_WARNING;
+ zlog_startup_stderr.zt.logfn = zlog_fd;
+ zlog_startup_stderr.zt.logfn_sigsafe = zlog_fd_sigsafe;
+ zlog_startup_stderr.fd = STDERR_FILENO;
+
+ zlog_target_replace(NULL, &zlog_startup_stderr.zt);
+
+ hook_register(zlog_aux_init, zlt_aux_init);
+ hook_register(zlog_init, zlt_init);
+ hook_register(zlog_fini, zlt_fini);
+}
+
+void zlog_startup_end(void)
+{
+ static bool startup_ended = false;
+
+ if (startup_ended)
+ return;
+ startup_ended = true;
+
+ zlog_target_replace(&zlog_startup_stderr.zt, NULL);
+
+ if (zlog_is_aux)
+ return;
+
+ /* until here, crashlogs go to stderr */
+ zlog_crashlog_init();
+}
+
+/* syslog */
+
+struct zlt_syslog {
+ struct zlog_target zt;
+
+ int syslog_facility;
+};
+
+static void zlog_syslog(struct zlog_target *zt, struct zlog_msg *msgs[],
+ size_t nmsgs)
+{
+ size_t i;
+ struct zlt_syslog *zte = container_of(zt, struct zlt_syslog, zt);
+ const char *text;
+ size_t text_len;
+
+ for (i = 0; i < nmsgs; i++) {
+ if (zlog_msg_prio(msgs[i]) > zt->prio_min)
+ continue;
+
+ text = zlog_msg_text(msgs[i], &text_len);
+ syslog(zlog_msg_prio(msgs[i]) | zte->syslog_facility, "%.*s",
+ (int)text_len, text);
+ }
+}
+
+#ifndef _PATH_LOG
+#define _PATH_LOG "/dev/log"
+#endif
+
+static void zlog_syslog_sigsafe(struct zlog_target *zt, const char *text,
+ size_t len)
+{
+ static int syslog_fd = -1;
+
+ char hdr[192];
+ size_t hdrlen;
+ struct iovec iov[2];
+
+ if (syslog_fd == -1) {
+ syslog_fd = socket(AF_UNIX, SOCK_DGRAM, 0);
+ if (syslog_fd >= 0) {
+ struct sockaddr_un sa;
+ socklen_t salen = sizeof(sa);
+
+ sa.sun_family = AF_UNIX;
+ strlcpy(sa.sun_path, _PATH_LOG, sizeof(sa.sun_path));
+#ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN
+ salen = sa.sun_len = SUN_LEN(&sa);
+#endif
+ if (connect(syslog_fd, (struct sockaddr *)&sa, salen)) {
+ close(syslog_fd);
+ syslog_fd = -1;
+ }
+ }
+
+ /* /dev/log could be a fifo instead of a socket */
+ if (syslog_fd == -1) {
+ syslog_fd = open(_PATH_LOG, O_WRONLY | O_NOCTTY);
+ if (syslog_fd < 0)
+ /* give up ... */
+ syslog_fd = -2;
+ }
+ }
+
+ if (syslog_fd == -2)
+ return;
+
+ /* note zlog_prefix includes trailing ": ", need to cut off 2 chars */
+ hdrlen = snprintfrr(hdr, sizeof(hdr), "<%d>%.*s[%ld]: ", LOG_CRIT,
+ zlog_prefixsz > 2 ? (int)(zlog_prefixsz - 2) : 0,
+ zlog_prefix, (long)getpid());
+
+ iov[0].iov_base = hdr;
+ iov[0].iov_len = hdrlen;
+
+ iov[1].iov_base = (char *)text;
+ iov[1].iov_len = len;
+
+ writev(syslog_fd, iov, array_size(iov));
+}
+
+
+static pthread_mutex_t syslog_cfg_mutex = PTHREAD_MUTEX_INITIALIZER;
+static struct zlt_syslog *zlt_syslog;
+static int syslog_facility = LOG_DAEMON;
+static int syslog_prio_min = ZLOG_DISABLED;
+
+void zlog_syslog_set_facility(int facility)
+{
+ struct zlog_target *newztc;
+ struct zlt_syslog *newzt;
+
+ frr_with_mutex (&syslog_cfg_mutex) {
+ if (facility == syslog_facility)
+ return;
+ syslog_facility = facility;
+
+ if (syslog_prio_min == ZLOG_DISABLED)
+ return;
+
+ newztc = zlog_target_clone(MTYPE_LOG_SYSL, &zlt_syslog->zt,
+ sizeof(*newzt));
+ newzt = container_of(newztc, struct zlt_syslog, zt);
+ newzt->syslog_facility = syslog_facility;
+
+ zlog_target_free(MTYPE_LOG_SYSL,
+ zlog_target_replace(&zlt_syslog->zt,
+ &newzt->zt));
+
+ zlt_syslog = newzt;
+ }
+}
+
+int zlog_syslog_get_facility(void)
+{
+ frr_with_mutex (&syslog_cfg_mutex) {
+ return syslog_facility;
+ }
+ assert(0);
+}
+
+void zlog_syslog_set_prio_min(int prio_min)
+{
+ struct zlog_target *newztc;
+ struct zlt_syslog *newzt = NULL;
+
+ frr_with_mutex (&syslog_cfg_mutex) {
+ if (prio_min == syslog_prio_min)
+ return;
+ syslog_prio_min = prio_min;
+
+ if (syslog_prio_min != ZLOG_DISABLED) {
+ newztc = zlog_target_clone(MTYPE_LOG_SYSL,
+ &zlt_syslog->zt,
+ sizeof(*newzt));
+ newzt = container_of(newztc, struct zlt_syslog, zt);
+ newzt->zt.prio_min = prio_min;
+ newzt->zt.logfn = zlog_syslog;
+ newzt->zt.logfn_sigsafe = zlog_syslog_sigsafe;
+ newzt->syslog_facility = syslog_facility;
+ }
+
+ zlog_target_free(MTYPE_LOG_SYSL,
+ zlog_target_replace(&zlt_syslog->zt,
+ &newzt->zt));
+
+ zlt_syslog = newzt;
+ }
+}
+
+int zlog_syslog_get_prio_min(void)
+{
+ frr_with_mutex (&syslog_cfg_mutex) {
+ return syslog_prio_min;
+ }
+ assert(0);
+}
diff --git a/lib/zlog_targets.h b/lib/zlog_targets.h
new file mode 100644
index 0000000..6faf722
--- /dev/null
+++ b/lib/zlog_targets.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2015-19 David Lamparter, for NetDEF, Inc.
+ *
+ * Permission to use, copy, modify, and 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_ZLOG_TARGETS_H
+#define _FRR_ZLOG_TARGETS_H
+
+#include <pthread.h>
+
+#include "zlog.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* multiple file log targets can be active */
+
+struct zlt_fd;
+
+struct zlog_cfg_file {
+ struct zlt_fd *active;
+
+ pthread_mutex_t cfg_mtx;
+
+ /* call zlog_file_set_other() to apply these */
+ int prio_min;
+ char ts_subsec;
+ bool record_priority;
+
+ /* call zlog_file_set_filename/fd() to change this */
+ char *filename;
+ int fd;
+
+ void (*zlog_wrap)(struct zlog_target *zt, struct zlog_msg *msgs[],
+ size_t nmsgs);
+};
+
+extern void zlog_file_init(struct zlog_cfg_file *zcf);
+extern void zlog_file_fini(struct zlog_cfg_file *zcf);
+
+extern void zlog_file_set_other(struct zlog_cfg_file *zcf);
+extern bool zlog_file_set_filename(struct zlog_cfg_file *zcf, const char *name);
+extern bool zlog_file_set_fd(struct zlog_cfg_file *zcf, int fd);
+extern bool zlog_file_rotate(struct zlog_cfg_file *zcf);
+
+extern void zlog_fd(struct zlog_target *zt, struct zlog_msg *msgs[],
+ size_t nmsgs);
+
+/* syslog is always limited to one target */
+
+extern void zlog_syslog_set_facility(int facility);
+extern int zlog_syslog_get_facility(void);
+
+/* use ZLOG_DISABLED to disable */
+extern void zlog_syslog_set_prio_min(int prio_min);
+extern int zlog_syslog_get_prio_min(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRR_ZLOG_TARGETS_H */