mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-22 01:13:00 +02:00
netd: add vtysh -b backend
This is a simpler approach than both grpc and watchfrr. Since we only really need dynamic handling of static routes (for DHCP), we can track changes made and maintain a "diff engine" for the resulting frr.conf. Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
@@ -1,2 +1,2 @@
|
||||
#set DEBUG=1
|
||||
service [2345] <pid/confd> name:netd netd -- Network route daemon
|
||||
service [2345] <pid/confd,pid/staticd> name:netd netd -- Network route daemon
|
||||
|
||||
+27
-8
@@ -6,28 +6,42 @@ config BR2_PACKAGE_NETD
|
||||
Network route daemon. Manages static routes and RIP routing.
|
||||
Reads configuration from /etc/netd/conf.d/*.conf.
|
||||
|
||||
With FRR: Full routing via gRPC (static routes, RIP, OSPF).
|
||||
With FRR: Full routing via gRPC, vtysh, or frr.conf.
|
||||
Without FRR: Standalone Linux backend via rtnetlink.
|
||||
|
||||
https://github.com/kernelkit/infix
|
||||
|
||||
if BR2_PACKAGE_NETD
|
||||
|
||||
choice
|
||||
prompt "FRR backend"
|
||||
default BR2_PACKAGE_NETD_FRR_VTYSH if BR2_PACKAGE_FRR
|
||||
default BR2_PACKAGE_NETD_LINUX
|
||||
|
||||
config BR2_PACKAGE_NETD_FRR_VTYSH
|
||||
bool "FRR vtysh"
|
||||
depends on BR2_PACKAGE_FRR
|
||||
help
|
||||
Enable FRR integration via vtysh -b.
|
||||
netd generates incremental config diffs and applies
|
||||
them to running FRR daemons via vtysh -b.
|
||||
|
||||
FRR daemons run as standalone finit services (no watchfrr).
|
||||
Provides static routes and RIP support.
|
||||
|
||||
config BR2_PACKAGE_NETD_FRR_CONF
|
||||
bool "FRR frr.conf backend"
|
||||
default y if BR2_PACKAGE_FRR
|
||||
bool "FRR frr.conf"
|
||||
depends on BR2_PACKAGE_FRR
|
||||
help
|
||||
Enable FRR integration via frr.conf file generation.
|
||||
netd assembles a single /etc/frr/frr.conf from routing
|
||||
config and signals FRR to reload via frrinit.sh.
|
||||
config and signals FRR to reload via frrinit.sh/watchfrr.
|
||||
|
||||
Provides full routing support (static routes, RIP, OSPF).
|
||||
|
||||
config BR2_PACKAGE_NETD_FRR
|
||||
bool "FRR gRPC backend"
|
||||
bool "FRR gRPC"
|
||||
depends on BR2_PACKAGE_FRR
|
||||
depends on !BR2_PACKAGE_NETD_FRR_CONF
|
||||
select BR2_PACKAGE_PROTOBUF
|
||||
select BR2_PACKAGE_GRPC
|
||||
select BR2_PACKAGE_HOST_PROTOBUF
|
||||
@@ -36,7 +50,12 @@ config BR2_PACKAGE_NETD_FRR
|
||||
Enable FRR integration via gRPC northbound API.
|
||||
Provides full routing support (static routes, RIP, OSPF).
|
||||
|
||||
If disabled, netd uses Linux kernel backend (rtnetlink)
|
||||
with static routes only.
|
||||
config BR2_PACKAGE_NETD_LINUX
|
||||
bool "Linux kernel"
|
||||
help
|
||||
Use Linux kernel backend (rtnetlink) with static routes
|
||||
only. No FRR dependency.
|
||||
|
||||
endchoice
|
||||
|
||||
endif
|
||||
|
||||
@@ -21,6 +21,9 @@ NETD_CONF_OPTS = --prefix= --disable-silent-rules
|
||||
ifeq ($(BR2_PACKAGE_NETD_FRR_CONF),y)
|
||||
NETD_DEPENDENCIES += frr
|
||||
NETD_CONF_OPTS += --with-frr-conf
|
||||
else ifeq ($(BR2_PACKAGE_NETD_FRR_VTYSH),y)
|
||||
NETD_DEPENDENCIES += frr
|
||||
NETD_CONF_OPTS += --with-frr-vtysh
|
||||
else ifeq ($(BR2_PACKAGE_NETD_FRR),y)
|
||||
NETD_DEPENDENCIES += frr grpc host-grpc protobuf
|
||||
NETD_CONF_ENV += \
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
service <pid/zebra> env:-/etc/default/ripd \
|
||||
[2345] ripd $RIPD_ARGS -- RIP daemon
|
||||
[2345] ripd $RIPD_ARGS
|
||||
-- RIP daemon
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
service log:null <!pid/zebra> \
|
||||
service <!pid/zebra> log:null \
|
||||
[2345] staticd -A 127.0.0.1 -u frr -g frr \
|
||||
-- Static routing daemon
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
service <!pid/netd> pid:!/run/frr/zebra.pid env:-/etc/default/zebra \
|
||||
[2345] zebra $ZEBRA_ARGS -- Zebra routing daemon
|
||||
service <!pid/mgmtd> pid:!/run/frr/zebra.pid env:-/etc/default/zebra \
|
||||
[2345] zebra $ZEBRA_ARGS
|
||||
-- Zebra routing daemon
|
||||
|
||||
@@ -539,7 +539,7 @@ int routing_change(sr_session_ctx_t *session, struct lyd_node *config, struct ly
|
||||
ripd_enabled = fexist(RIPD_SIGNAL_NEXT);
|
||||
|
||||
activate:
|
||||
/* Generate complete /etc/frr/daemons */
|
||||
/* Generate complete /etc/frr/daemons (for watchfrr/frrinit.sh) */
|
||||
frr_daemons_write(ospfd_enabled, ripd_enabled, bfdd_enabled);
|
||||
|
||||
if (bfdd_enabled)
|
||||
@@ -569,6 +569,14 @@ activate:
|
||||
(void)remove(NETD_CONF);
|
||||
}
|
||||
|
||||
/* Enable/disable FRR daemons as standalone finit services.
|
||||
* Harmless no-op when using watchfrr (services not installed). */
|
||||
systemf("initctl -nbq %s ospfd", ospfd_enabled ? "enable" : "disable");
|
||||
if (ospfd_enabled)
|
||||
systemf("initctl -nbq touch ospfd");
|
||||
systemf("initctl -nbq %s ripd", ripd_enabled ? "enable" : "disable");
|
||||
systemf("initctl -nbq %s bfdd", bfdd_enabled ? "enable" : "disable");
|
||||
|
||||
/*
|
||||
* Signal netd to reload - it assembles /etc/frr/frr.conf and
|
||||
* Finit propagates the restart to the frr sysv service
|
||||
|
||||
@@ -33,7 +33,12 @@ if HAVE_FRR_CONF
|
||||
# FRR frr.conf file backend
|
||||
netd_SOURCES += src/frrconf_backend.c src/frrconf_backend.h
|
||||
else
|
||||
if HAVE_FRR_VTYSH
|
||||
# FRR vtysh -b backend
|
||||
netd_SOURCES += src/vtysh_backend.c src/vtysh_backend.h
|
||||
else
|
||||
# Linux kernel backend (no FRR)
|
||||
netd_SOURCES += src/linux_backend.c src/linux_backend.h
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
+17
-1
@@ -24,10 +24,21 @@ AC_ARG_WITH(frr-conf,
|
||||
[with_frr_conf=$withval],
|
||||
[with_frr_conf=no])
|
||||
|
||||
AC_ARG_WITH(frr-vtysh,
|
||||
AS_HELP_STRING([--with-frr-vtysh], [Build with FRR vtysh -b backend (incremental config)]),
|
||||
[with_frr_vtysh=$withval],
|
||||
[with_frr_vtysh=no])
|
||||
|
||||
# Mutual exclusion check
|
||||
AS_IF([test "x$with_frr" = "xyes" -a "x$with_frr_conf" = "xyes"], [
|
||||
AC_MSG_ERROR([--with-frr and --with-frr-conf are mutually exclusive])
|
||||
])
|
||||
AS_IF([test "x$with_frr" = "xyes" -a "x$with_frr_vtysh" = "xyes"], [
|
||||
AC_MSG_ERROR([--with-frr and --with-frr-vtysh are mutually exclusive])
|
||||
])
|
||||
AS_IF([test "x$with_frr_conf" = "xyes" -a "x$with_frr_vtysh" = "xyes"], [
|
||||
AC_MSG_ERROR([--with-frr-conf and --with-frr-vtysh are mutually exclusive])
|
||||
])
|
||||
|
||||
AS_IF([test "x$with_frr" = "xyes"], [
|
||||
# Check for protoc and grpc_cpp_plugin
|
||||
@@ -55,8 +66,13 @@ AS_IF([test "x$with_frr_conf" = "xyes"], [
|
||||
AC_DEFINE(HAVE_FRR_CONF, 1, [Built with FRR frr.conf file backend])
|
||||
])
|
||||
|
||||
AS_IF([test "x$with_frr_vtysh" = "xyes"], [
|
||||
AC_DEFINE(HAVE_FRR_VTYSH, 1, [Built with FRR vtysh -b backend])
|
||||
])
|
||||
|
||||
AM_CONDITIONAL(HAVE_FRR_GRPC, [test "x$with_frr" = "xyes"])
|
||||
AM_CONDITIONAL(HAVE_FRR_CONF, [test "x$with_frr_conf" = "xyes"])
|
||||
AM_CONDITIONAL(HAVE_FRR_VTYSH, [test "x$with_frr_vtysh" = "xyes"])
|
||||
|
||||
# Check for pkg-config first
|
||||
PKG_PROG_PKG_CONFIG
|
||||
@@ -89,7 +105,7 @@ cat <<EOF
|
||||
Prefix................: $prefix
|
||||
Exec prefix...........: $eprefix
|
||||
Sysconfdir............: `eval echo $sysconfdir`
|
||||
Backend...............: $(test "x$with_frr" = "xyes" && echo "FRR gRPC" || (test "x$with_frr_conf" = "xyes" && echo "FRR frr.conf" || echo "Linux kernel"))
|
||||
Backend...............: $(test "x$with_frr" = "xyes" && echo "FRR gRPC" || (test "x$with_frr_conf" = "xyes" && echo "FRR frr.conf" || (test "x$with_frr_vtysh" = "xyes" && echo "FRR vtysh" || echo "Linux kernel")))
|
||||
C Compiler............: $CC $CFLAGS $CPPFLAGS $LDFLAGS $LIBS
|
||||
|
||||
------------- Compiler version --------------
|
||||
|
||||
+15
-8
@@ -7,6 +7,14 @@
|
||||
#include "netd.h"
|
||||
#include "config.h"
|
||||
|
||||
int debug;
|
||||
|
||||
static sig_atomic_t do_reload;
|
||||
static sig_atomic_t do_shutdown;
|
||||
|
||||
static struct route_head active_routes = TAILQ_HEAD_INITIALIZER(active_routes);
|
||||
static struct rip_config active_rip;
|
||||
|
||||
/* Backend selection at compile time */
|
||||
#ifdef HAVE_FRR_GRPC
|
||||
#include "grpc_backend.h"
|
||||
@@ -22,6 +30,13 @@ static void backend_cleanup(void) { frrconf_backend_cleanup(); }
|
||||
static int backend_apply(struct route_head *routes, struct rip_config *rip) {
|
||||
return frrconf_backend_apply(routes, rip);
|
||||
}
|
||||
#elif defined(HAVE_FRR_VTYSH)
|
||||
#include "vtysh_backend.h"
|
||||
static int backend_init(void) { return vtysh_backend_init(); }
|
||||
static void backend_cleanup(void) { vtysh_backend_cleanup(); }
|
||||
static int backend_apply(struct route_head *routes, struct rip_config *rip) {
|
||||
return vtysh_backend_apply(routes, rip);
|
||||
}
|
||||
#else
|
||||
#include "linux_backend.h"
|
||||
static int backend_init(void) { return linux_backend_init(); }
|
||||
@@ -31,14 +46,6 @@ static int backend_apply(struct route_head *routes, struct rip_config *rip) {
|
||||
}
|
||||
#endif
|
||||
|
||||
int debug;
|
||||
|
||||
static volatile sig_atomic_t do_reload;
|
||||
static volatile sig_atomic_t do_shutdown;
|
||||
|
||||
static struct route_head active_routes = TAILQ_HEAD_INITIALIZER(active_routes);
|
||||
static struct rip_config active_rip;
|
||||
|
||||
static void sighup_handler(int sig)
|
||||
{
|
||||
(void)sig;
|
||||
|
||||
@@ -0,0 +1,298 @@
|
||||
/* SPDX-License-Identifier: BSD-3-Clause */
|
||||
|
||||
/*
|
||||
* FRR vtysh backend for netd - applies incremental route changes
|
||||
* by generating a diff (negations of old state + new state) and
|
||||
* feeding it to running FRR daemons via `vtysh -b`.
|
||||
*
|
||||
* Old state is persisted in NETD_CONF so we can recover after a
|
||||
* crash — on startup, any routes/config in that file are negated
|
||||
* before new config is applied.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "netd.h"
|
||||
#include "vtysh_backend.h"
|
||||
|
||||
#define FRR_CONF "/etc/frr/frr.conf"
|
||||
#define FRR_CONF_NEXT FRR_CONF "+"
|
||||
#define NETD_CONF "/etc/frr/netd.conf"
|
||||
#define NETD_CONF_NEXT NETD_CONF "+"
|
||||
#define OSPFD_CONF "/etc/frr/ospfd.conf"
|
||||
|
||||
static const char *frr_header =
|
||||
"! Generated by netd\n"
|
||||
"frr defaults traditional\n"
|
||||
"hostname Router\n"
|
||||
"password zebra\n"
|
||||
"enable password zebra\n"
|
||||
"no log unique-id\n"
|
||||
"log syslog warnings\n"
|
||||
"log facility local2\n"
|
||||
"!\n";
|
||||
|
||||
int vtysh_backend_init(void)
|
||||
{
|
||||
INFO("Using FRR vtysh backend");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void vtysh_backend_cleanup(void)
|
||||
{
|
||||
/* Nothing to clean up */
|
||||
}
|
||||
|
||||
static void write_route(FILE *fp, struct route *r)
|
||||
{
|
||||
char prefix_str[INET6_ADDRSTRLEN];
|
||||
char gw_str[INET6_ADDRSTRLEN];
|
||||
const char *cmd;
|
||||
|
||||
if (r->family == AF_INET) {
|
||||
cmd = "ip";
|
||||
inet_ntop(AF_INET, &r->prefix.ip4, prefix_str, sizeof(prefix_str));
|
||||
} else {
|
||||
cmd = "ipv6";
|
||||
inet_ntop(AF_INET6, &r->prefix.ip6, prefix_str, sizeof(prefix_str));
|
||||
}
|
||||
|
||||
fprintf(fp, "%s route %s/%u ", cmd, prefix_str, r->prefixlen);
|
||||
|
||||
switch (r->nh_type) {
|
||||
case NH_ADDR:
|
||||
if (r->family == AF_INET)
|
||||
inet_ntop(AF_INET, &r->gateway.gw4, gw_str, sizeof(gw_str));
|
||||
else
|
||||
inet_ntop(AF_INET6, &r->gateway.gw6, gw_str, sizeof(gw_str));
|
||||
fputs(gw_str, fp);
|
||||
break;
|
||||
case NH_IFNAME:
|
||||
fputs(r->ifname, fp);
|
||||
break;
|
||||
case NH_BLACKHOLE:
|
||||
switch (r->bh_type) {
|
||||
case BH_NULL:
|
||||
fputs("Null0", fp);
|
||||
break;
|
||||
case BH_REJECT:
|
||||
fputs("reject", fp);
|
||||
break;
|
||||
case BH_DROP:
|
||||
fputs("blackhole", fp);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (r->distance)
|
||||
fprintf(fp, " %u", r->distance);
|
||||
|
||||
fputc('\n', fp);
|
||||
}
|
||||
|
||||
static const char *redist_name(enum rip_redist_type type)
|
||||
{
|
||||
switch (type) {
|
||||
case RIP_REDIST_CONNECTED: return "connected";
|
||||
case RIP_REDIST_STATIC: return "static";
|
||||
case RIP_REDIST_KERNEL: return "kernel";
|
||||
case RIP_REDIST_OSPF: return "ospf";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
static void write_rip_config(FILE *fp, struct rip_config *rip)
|
||||
{
|
||||
struct rip_redistribute *redist;
|
||||
struct rip_neighbor *nbr;
|
||||
struct rip_network *net;
|
||||
|
||||
if (!rip->enabled)
|
||||
return;
|
||||
|
||||
fputs("router rip\n", fp);
|
||||
fprintf(fp, " default-metric %u\n", rip->default_metric);
|
||||
fprintf(fp, " distance %u\n", rip->distance);
|
||||
fprintf(fp, " timers basic %u %u %u\n",
|
||||
rip->timers.update, rip->timers.invalid, rip->timers.flush);
|
||||
|
||||
if (rip->default_route)
|
||||
fputs(" default-information originate\n", fp);
|
||||
|
||||
TAILQ_FOREACH(net, &rip->networks, entries) {
|
||||
fprintf(fp, " network %s\n", net->ifname);
|
||||
if (net->passive)
|
||||
fprintf(fp, " passive-interface %s\n", net->ifname);
|
||||
}
|
||||
|
||||
TAILQ_FOREACH(nbr, &rip->neighbors, entries) {
|
||||
char addr[INET_ADDRSTRLEN];
|
||||
|
||||
inet_ntop(AF_INET, &nbr->addr, addr, sizeof(addr));
|
||||
fprintf(fp, " neighbor %s\n", addr);
|
||||
}
|
||||
|
||||
TAILQ_FOREACH(redist, &rip->redistributes, entries)
|
||||
fprintf(fp, " redistribute %s\n", redist_name(redist->type));
|
||||
|
||||
fputs("!\n", fp);
|
||||
}
|
||||
|
||||
static void append_file(FILE *fp, const char *path)
|
||||
{
|
||||
char buf[1024];
|
||||
FILE *src;
|
||||
size_t n;
|
||||
|
||||
src = fopen(path, "r");
|
||||
if (!src)
|
||||
return;
|
||||
|
||||
while ((n = fread(buf, 1, sizeof(buf), src)) > 0)
|
||||
fwrite(buf, 1, n, fp);
|
||||
|
||||
fclose(src);
|
||||
DEBUG("vtysh: appended %s", path);
|
||||
}
|
||||
|
||||
/*
|
||||
* Negate old state by reading previous netd.conf and prefixing
|
||||
* each config line with "no ". Comment and blank lines are skipped.
|
||||
*
|
||||
* For "router rip" blocks we emit a single "no router rip" instead
|
||||
* of negating each sub-command individually.
|
||||
*/
|
||||
static void negate_old_conf(FILE *fp)
|
||||
{
|
||||
char line[256];
|
||||
int in_rip = 0;
|
||||
int count = 0;
|
||||
FILE *old;
|
||||
|
||||
old = fopen(NETD_CONF, "r");
|
||||
if (!old) {
|
||||
DEBUG("vtysh: no previous %s, first boot", NETD_CONF);
|
||||
return;
|
||||
}
|
||||
|
||||
while (fgets(line, sizeof(line), old)) {
|
||||
size_t len = strlen(line);
|
||||
|
||||
/* Strip trailing newline */
|
||||
if (len > 0 && line[len - 1] == '\n')
|
||||
line[--len] = '\0';
|
||||
|
||||
/* Skip empty lines and comments */
|
||||
if (len == 0 || line[0] == '!' || line[0] == '#')
|
||||
continue;
|
||||
|
||||
/* Track router rip block */
|
||||
if (strcmp(line, "router rip") == 0) {
|
||||
in_rip = 1;
|
||||
fputs("no router rip\n", fp);
|
||||
count++;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Skip sub-commands inside router rip block */
|
||||
if (in_rip) {
|
||||
/* Sub-commands are indented with space */
|
||||
if (line[0] == ' ')
|
||||
continue;
|
||||
in_rip = 0;
|
||||
}
|
||||
|
||||
/* Negate route lines (ip route ..., ipv6 route ...) */
|
||||
fprintf(fp, "no %s\n", line);
|
||||
count++;
|
||||
}
|
||||
|
||||
fclose(old);
|
||||
DEBUG("vtysh: negated %d old config lines", count);
|
||||
}
|
||||
|
||||
/*
|
||||
* Save current config to netd.conf for next reload or crash recovery.
|
||||
* Written atomically via rename.
|
||||
*/
|
||||
static int save_conf(struct route_head *routes, struct rip_config *rip)
|
||||
{
|
||||
struct route *r;
|
||||
FILE *fp;
|
||||
|
||||
fp = fopen(NETD_CONF_NEXT, "w");
|
||||
if (!fp) {
|
||||
ERROR("vtysh: failed to open %s: %s", NETD_CONF_NEXT, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
TAILQ_FOREACH(r, routes, entries)
|
||||
write_route(fp, r);
|
||||
|
||||
write_rip_config(fp, rip);
|
||||
|
||||
fclose(fp);
|
||||
|
||||
if (rename(NETD_CONF_NEXT, NETD_CONF)) {
|
||||
ERROR("vtysh: failed to rename %s to %s: %s",
|
||||
NETD_CONF_NEXT, NETD_CONF, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vtysh_backend_apply(struct route_head *routes, struct rip_config *rip)
|
||||
{
|
||||
struct route *r;
|
||||
int rc, count = 0;
|
||||
FILE *fp;
|
||||
|
||||
fp = fopen(FRR_CONF_NEXT, "w");
|
||||
if (!fp) {
|
||||
ERROR("vtysh: failed to open %s: %s", FRR_CONF_NEXT, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
fputs(frr_header, fp);
|
||||
|
||||
/* Negate old state from previous netd.conf */
|
||||
negate_old_conf(fp);
|
||||
|
||||
/* Write new routes */
|
||||
TAILQ_FOREACH(r, routes, entries) {
|
||||
write_route(fp, r);
|
||||
count++;
|
||||
}
|
||||
DEBUG("vtysh: wrote %d new routes", count);
|
||||
|
||||
/* Write new RIP config */
|
||||
write_rip_config(fp, rip);
|
||||
|
||||
/* Append OSPF config if present (written by confd) */
|
||||
append_file(fp, OSPFD_CONF);
|
||||
|
||||
fclose(fp);
|
||||
|
||||
if (rename(FRR_CONF_NEXT, FRR_CONF)) {
|
||||
ERROR("vtysh: failed to rename %s to %s: %s",
|
||||
FRR_CONF_NEXT, FRR_CONF, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = system("vtysh -b");
|
||||
if (rc) {
|
||||
ERROR("vtysh: vtysh -b failed with status %d", rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Persist new state for next reload / crash recovery */
|
||||
if (save_conf(routes, rip))
|
||||
ERROR("vtysh: failed to save %s, next reload may be inconsistent", NETD_CONF);
|
||||
|
||||
INFO("vtysh: applied config via vtysh -b");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/* SPDX-License-Identifier: BSD-3-Clause */
|
||||
|
||||
#ifndef NETD_VTYSH_BACKEND_H_
|
||||
#define NETD_VTYSH_BACKEND_H_
|
||||
|
||||
#include "netd.h"
|
||||
|
||||
int vtysh_backend_init(void);
|
||||
void vtysh_backend_cleanup(void);
|
||||
int vtysh_backend_apply(struct route_head *routes, struct rip_config *rip);
|
||||
|
||||
#endif /* NETD_VTYSH_BACKEND_H_ */
|
||||
Reference in New Issue
Block a user