mirror of
https://git.proxmox.com/git/mirror_frr
synced 2025-08-04 13:31:48 +00:00
Merge pull request #15369 from donaldsharp/fpm_stub_addition
Add ability to test dplane_fpm_nl.c file
This commit is contained in:
commit
a44918640f
@ -703,6 +703,8 @@ AC_ARG_ENABLE([mgmtd_local_validations],
|
||||
AS_HELP_STRING([--enable-mgmtd-local-validations], [dev: unimplemented local validation]))
|
||||
AC_ARG_ENABLE([mgmtd_test_be_client],
|
||||
AS_HELP_STRING([--enable-mgmtd-test-be-client], [build test backend client]))
|
||||
AC_ARG_ENABLE([fpm_listener],
|
||||
AS_HELP_STRING([--enable-fpm-listener], [build fpm listener test program]))
|
||||
AC_ARG_ENABLE([ripd],
|
||||
AS_HELP_STRING([--disable-ripd], [do not build ripd]))
|
||||
AC_ARG_ENABLE([ripngd],
|
||||
@ -1811,6 +1813,10 @@ AS_IF([test "$enable_mgmtd_test_be_client" = "yes"], [
|
||||
AC_DEFINE([HAVE_MGMTD_TESTC], [1], [mgmtd_testc])
|
||||
])
|
||||
|
||||
AS_IF([test "$enable_fpm_listener" = "yes"], [
|
||||
AC_DEFINE([HAVE_FPM_LISTENER], [1], [fpm_listener])
|
||||
])
|
||||
|
||||
AS_IF([test "$enable_ripd" != "no"], [
|
||||
AC_DEFINE([HAVE_RIPD], [1], [ripd])
|
||||
])
|
||||
@ -2773,6 +2779,7 @@ AM_CONDITIONAL([ZEBRA], [test "$enable_zebra" != "no"])
|
||||
AM_CONDITIONAL([BGPD], [test "$enable_bgpd" != "no"])
|
||||
AM_CONDITIONAL([MGMTD], [test "$enable_mgmtd" != "no"])
|
||||
AM_CONDITIONAL([MGMTD_TESTC], [test "$enable_mgmtd_test_be_client" = "yes"])
|
||||
AM_CONDITIONAL([FPM_LISTENER], [test "enable_fpm_listener" = "yes"])
|
||||
AM_CONDITIONAL([RIPD], [test "$enable_ripd" != "no"])
|
||||
AM_CONDITIONAL([OSPFD], [test "$enable_ospfd" != "no"])
|
||||
AM_CONDITIONAL([LDPD], [test "$enable_ldpd" != "no"])
|
||||
|
8
debian/control
vendored
8
debian/control
vendored
@ -102,6 +102,14 @@ Description: FRRouting suite - BGP RPKI support (rtrlib)
|
||||
number.
|
||||
Build-Profiles: <!pkg.frr.nortrlib>
|
||||
|
||||
Package: frr-test-tools
|
||||
Architecture: linux-any
|
||||
Depends: frr (= ${binary:Version}),
|
||||
${misc:Depends},
|
||||
${shlibs:Depends}
|
||||
Description: FRRouting suite - Testing Tools
|
||||
Adds FRR test tools, used in testing FRR.
|
||||
|
||||
Package: frr-doc
|
||||
Section: doc
|
||||
Architecture: all
|
||||
|
1
debian/frr-test-tools.install
vendored
Normal file
1
debian/frr-test-tools.install
vendored
Normal file
@ -0,0 +1 @@
|
||||
usr/lib/frr/fpm_listener
|
@ -761,15 +761,6 @@ Indentation and Line Breaks
|
||||
Macros, Attributes and Symbols
|
||||
------------------------------
|
||||
|
||||
**ARRAY_SIZE**
|
||||
The ARRAY_SIZE(foo) macro should be preferred over
|
||||
sizeof(foo)/sizeof(foo[0]) for finding number of elements in an
|
||||
array.
|
||||
|
||||
The macro is defined in include/linux/kernel.h::
|
||||
|
||||
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
|
||||
|
||||
**AVOID_EXTERNS**
|
||||
Function prototypes don't need to be declared extern in .h
|
||||
files. It's assumed by the compiler and is unnecessary.
|
||||
|
@ -274,6 +274,10 @@ options from the list below.
|
||||
|
||||
Build with FPM module support.
|
||||
|
||||
.. option:: --enable-fpm-listener
|
||||
|
||||
Build a small fpm listener for testing.
|
||||
|
||||
.. option:: --with-service-timeout=X
|
||||
|
||||
Set timeout value for FRR service. The time of restarting or reloading FRR
|
||||
|
@ -1356,6 +1356,9 @@ FPM Commands
|
||||
User FPM configurations: 1
|
||||
User FPM disable requests: 0
|
||||
|
||||
.. clicmd:: show fpm status [json]
|
||||
|
||||
Show the FPM status.
|
||||
|
||||
.. clicmd:: clear fpm counters
|
||||
|
||||
|
@ -677,6 +677,7 @@ fi
|
||||
%{_sbindir}/mgmtd_testc
|
||||
%endif
|
||||
%exclude %{_sbindir}/ssd
|
||||
%exclude %{_sbindir}/fpm_listener
|
||||
%if %{with_watchfrr}
|
||||
%{_sbindir}/watchfrr
|
||||
%endif
|
||||
|
8
tests/topotests/fpm_testing_topo1/r1/fpm_counters.json
Normal file
8
tests/topotests/fpm_testing_topo1/r1/fpm_counters.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"connected":true,
|
||||
"useNHG":true,
|
||||
"useRouteReplace":true,
|
||||
"disabled":false,
|
||||
"address":"127.0.0.1",
|
||||
"port":2620
|
||||
}
|
0
tests/topotests/fpm_testing_topo1/r1/fpm_stub.conf
Normal file
0
tests/topotests/fpm_testing_topo1/r1/fpm_stub.conf
Normal file
27
tests/topotests/fpm_testing_topo1/r1/routes_summ.json
Normal file
27
tests/topotests/fpm_testing_topo1/r1/routes_summ.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"routes":[
|
||||
{
|
||||
"fib":1,
|
||||
"rib":1,
|
||||
"fibOffLoaded":0,
|
||||
"fibTrapped":0,
|
||||
"type":"connected"
|
||||
},
|
||||
{
|
||||
"fib":1,
|
||||
"rib":1,
|
||||
"fibOffLoaded":0,
|
||||
"fibTrapped":0,
|
||||
"type":"local"
|
||||
},
|
||||
{
|
||||
"fib":10000,
|
||||
"rib":10000,
|
||||
"fibOffLoaded":0,
|
||||
"fibTrapped":0,
|
||||
"type":"sharp"
|
||||
}
|
||||
],
|
||||
"routesTotal":10002,
|
||||
"routesTotalFib":10002
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
{
|
||||
"routes":[
|
||||
{
|
||||
"fib":1,
|
||||
"rib":1,
|
||||
"fibOffLoaded":0,
|
||||
"fibTrapped":0,
|
||||
"type":"connected"
|
||||
},
|
||||
{
|
||||
"fib":1,
|
||||
"rib":1,
|
||||
"fibOffLoaded":0,
|
||||
"fibTrapped":0,
|
||||
"type":"local"
|
||||
}
|
||||
],
|
||||
"routesTotal":2,
|
||||
"routesTotalFib":2
|
||||
}
|
0
tests/topotests/fpm_testing_topo1/r1/sharpd.conf
Normal file
0
tests/topotests/fpm_testing_topo1/r1/sharpd.conf
Normal file
5
tests/topotests/fpm_testing_topo1/r1/zebra.conf
Normal file
5
tests/topotests/fpm_testing_topo1/r1/zebra.conf
Normal file
@ -0,0 +1,5 @@
|
||||
fpm address 127.0.0.1
|
||||
|
||||
interface r1-eth0
|
||||
ip address 192.168.44.1/24
|
||||
!
|
134
tests/topotests/fpm_testing_topo1/test_fpm_topo1.py
Normal file
134
tests/topotests/fpm_testing_topo1/test_fpm_topo1.py
Normal file
@ -0,0 +1,134 @@
|
||||
#!/usr/bin/env python
|
||||
# SPDX-License-Identifier: ISC
|
||||
|
||||
#
|
||||
# test_route_scale1.py
|
||||
#
|
||||
# Copyright (c) 2024 by
|
||||
# Nvidia, Inc.
|
||||
# Donald Sharp
|
||||
#
|
||||
|
||||
"""
|
||||
test_fpm_topo1.py: Testing FPM module
|
||||
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import pytest
|
||||
import json
|
||||
from functools import partial
|
||||
|
||||
# Save the Current Working Directory to find configuration files.
|
||||
CWD = os.path.dirname(os.path.realpath(__file__))
|
||||
sys.path.append(os.path.join(CWD, "../"))
|
||||
|
||||
# pylint: disable=C0413
|
||||
# Import topogen and topotest helpers
|
||||
from lib import topotest
|
||||
from lib.topogen import Topogen, TopoRouter, get_topogen
|
||||
from lib.topolog import logger
|
||||
|
||||
|
||||
pytestmark = [pytest.mark.fpm, pytest.mark.sharpd]
|
||||
|
||||
|
||||
def build_topo(tgen):
|
||||
"Build function"
|
||||
|
||||
# Populate routers
|
||||
tgen.add_router("r1")
|
||||
|
||||
switch = tgen.add_switch("sw1")
|
||||
switch.add_link(tgen.gears["r1"])
|
||||
|
||||
|
||||
def setup_module(module):
|
||||
"Setup topology"
|
||||
|
||||
# fpm_stub = os.system("which fpm-stub")
|
||||
# if fpm-stub:
|
||||
# pytest.skip("")
|
||||
|
||||
tgen = Topogen(build_topo, module.__name__)
|
||||
tgen.start_topology()
|
||||
|
||||
router_list = tgen.routers()
|
||||
for rname, router in router_list.items():
|
||||
router.load_config(
|
||||
TopoRouter.RD_ZEBRA,
|
||||
os.path.join(CWD, "{}/zebra.conf".format(rname)),
|
||||
"-M dplane_fpm_nl",
|
||||
)
|
||||
router.load_config(
|
||||
TopoRouter.RD_SHARP, os.path.join(CWD, "{}/sharpd.conf".format(rname))
|
||||
)
|
||||
router.load_config(
|
||||
TopoRouter.RD_FPM_LISTENER, os.path.join(CWD, "{}/fpm_stub.conf".format(rname))
|
||||
)
|
||||
|
||||
tgen.start_router()
|
||||
|
||||
|
||||
def teardown_module(_mod):
|
||||
"Teardown the pytest environment"
|
||||
|
||||
tgen = get_topogen()
|
||||
|
||||
# This function tears down the whole topology.
|
||||
tgen.stop_topology()
|
||||
|
||||
|
||||
def test_fpm_connection_made():
|
||||
"Test that the fpm starts up and a connection is made"
|
||||
|
||||
tgen = get_topogen()
|
||||
router = tgen.gears["r1"]
|
||||
|
||||
fpm_counters = "{}/r1/fpm_counters.json".format(CWD)
|
||||
expected = json.loads(open(fpm_counters).read())
|
||||
|
||||
test_func = partial(
|
||||
topotest.router_json_cmp, router, "show fpm status json", expected
|
||||
)
|
||||
|
||||
success, result = topotest.run_and_expect(test_func, None, 30, 1)
|
||||
assert success, "Unable to connect to the fpm:\n{}".format(result)
|
||||
|
||||
|
||||
def test_fpm_install_routes():
|
||||
"Test that simple routes installed appears to work"
|
||||
|
||||
tgen = get_topogen()
|
||||
router = tgen.gears["r1"]
|
||||
|
||||
# Let's install 10000 routes
|
||||
router.vtysh_cmd("sharp install routes 10.0.0.0 nexthop 192.168.44.33 10000")
|
||||
routes_file = "{}/r1/routes_summ.json".format(CWD)
|
||||
expected = json.loads(open(routes_file).read())
|
||||
|
||||
test_func = partial(
|
||||
topotest.router_json_cmp, router, "show ip route summ json", expected
|
||||
)
|
||||
|
||||
success, result = topotest.run_and_expect(test_func, None, 60, 1)
|
||||
assert success, "Unable to successfully install 10000 routes: {}".format(result)
|
||||
|
||||
# Let's remove 10000 routes
|
||||
router.vtysh_cmd("sharp remove routes 10.0.0.0 10000")
|
||||
|
||||
routes_file_removed = "{}/r1/routes_summ_removed.json".format(CWD)
|
||||
expected = json.loads(open(routes_file_removed).read())
|
||||
|
||||
test_func = partial(
|
||||
topotest.router_json_cmp, router, "show ip route summ json", expected
|
||||
)
|
||||
|
||||
success, result = topotest.run_and_expect(test_func, None, 60, 1)
|
||||
assert success, "Unable to remove 10000 routes: {}".format(result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = ["-s"] + sys.argv[1:]
|
||||
sys.exit(pytest.main(args))
|
@ -748,6 +748,7 @@ class TopoRouter(TopoGear):
|
||||
RD_PIM6 = 19
|
||||
RD_MGMTD = 20
|
||||
RD_TRAP = 21
|
||||
RD_FPM_LISTENER = 22
|
||||
RD = {
|
||||
RD_FRR: "frr",
|
||||
RD_ZEBRA: "zebra",
|
||||
@ -771,6 +772,7 @@ class TopoRouter(TopoGear):
|
||||
RD_SNMP: "snmpd",
|
||||
RD_MGMTD: "mgmtd",
|
||||
RD_TRAP: "snmptrapd",
|
||||
RD_FPM_LISTENER: "fpm_listener",
|
||||
}
|
||||
|
||||
def __init__(self, tgen, cls, name, **params):
|
||||
@ -847,7 +849,8 @@ class TopoRouter(TopoGear):
|
||||
TopoRouter.RD_RIPNG, TopoRouter.RD_OSPF, TopoRouter.RD_OSPF6,
|
||||
TopoRouter.RD_ISIS, TopoRouter.RD_BGP, TopoRouter.RD_LDP,
|
||||
TopoRouter.RD_PIM, TopoRouter.RD_PIM6, TopoRouter.RD_PBR,
|
||||
TopoRouter.RD_SNMP, TopoRouter.RD_MGMTD, TopoRouter.RD_TRAP.
|
||||
TopoRouter.RD_SNMP, TopoRouter.RD_MGMTD, TopoRouter.RD_TRAP,
|
||||
TopoRouter.RD_FPM_LISTENER.
|
||||
|
||||
Possible `source` values are `None` for an empty config file, a path name which is
|
||||
used directly, or a file name with no path components which is first looked for
|
||||
@ -885,7 +888,12 @@ class TopoRouter(TopoGear):
|
||||
# Enable all daemon command logging, logging files
|
||||
# and set them to the start dir.
|
||||
for daemon, enabled in nrouter.daemons.items():
|
||||
if enabled and daemon != "snmpd" and daemon != "snmptrapd":
|
||||
if (
|
||||
enabled
|
||||
and daemon != "snmpd"
|
||||
and daemon != "snmptrapd"
|
||||
and daemon != "fpm_listener"
|
||||
):
|
||||
self.vtysh_cmd(
|
||||
"\n".join(
|
||||
[
|
||||
@ -935,7 +943,7 @@ class TopoRouter(TopoGear):
|
||||
# and set them to the start dir.
|
||||
for daemon in daemons:
|
||||
enabled = nrouter.daemons[daemon]
|
||||
if enabled and daemon != "snmpd":
|
||||
if enabled and daemon != "snmpd" and daemon != "fpm_listener":
|
||||
self.vtysh_cmd(
|
||||
"\n".join(
|
||||
[
|
||||
|
@ -1262,8 +1262,8 @@ def rlimit_atleast(rname, min_value, raises=False):
|
||||
|
||||
def fix_netns_limits(ns):
|
||||
# Maximum read and write socket buffer sizes
|
||||
sysctl_atleast(ns, "net.ipv4.tcp_rmem", [10 * 1024, 87380, 16 * 2**20])
|
||||
sysctl_atleast(ns, "net.ipv4.tcp_wmem", [10 * 1024, 87380, 16 * 2**20])
|
||||
sysctl_atleast(ns, "net.ipv4.tcp_rmem", [10 * 1024, 87380, 16 * 2 ** 20])
|
||||
sysctl_atleast(ns, "net.ipv4.tcp_wmem", [10 * 1024, 87380, 16 * 2 ** 20])
|
||||
|
||||
sysctl_assure(ns, "net.ipv4.conf.all.rp_filter", 0)
|
||||
sysctl_assure(ns, "net.ipv4.conf.default.rp_filter", 0)
|
||||
@ -1322,8 +1322,8 @@ def fix_host_limits():
|
||||
sysctl_atleast(None, "net.core.netdev_max_backlog", 4 * 1024)
|
||||
|
||||
# Maximum read and write socket buffer sizes
|
||||
sysctl_atleast(None, "net.core.rmem_max", 16 * 2**20)
|
||||
sysctl_atleast(None, "net.core.wmem_max", 16 * 2**20)
|
||||
sysctl_atleast(None, "net.core.rmem_max", 16 * 2 ** 20)
|
||||
sysctl_atleast(None, "net.core.wmem_max", 16 * 2 ** 20)
|
||||
|
||||
# Garbage Collection Settings for ARP and Neighbors
|
||||
sysctl_atleast(None, "net.ipv4.neigh.default.gc_thresh2", 4 * 1024)
|
||||
@ -1426,6 +1426,7 @@ class Router(Node):
|
||||
"snmpd": 0,
|
||||
"mgmtd": 0,
|
||||
"snmptrapd": 0,
|
||||
"fpm_listener": 0,
|
||||
}
|
||||
self.daemons_options = {"zebra": ""}
|
||||
self.reportCores = True
|
||||
@ -1896,7 +1897,11 @@ class Router(Node):
|
||||
)
|
||||
|
||||
rediropt = " > {0}.out 2> {0}.err".format(daemon)
|
||||
if daemon == "snmpd":
|
||||
if daemon == "fpm_listener":
|
||||
binary = "/usr/lib/frr/fpm_listener"
|
||||
cmdenv = ""
|
||||
cmdopt = "-d {}".format(daemon_opts)
|
||||
elif daemon == "snmpd":
|
||||
binary = "/usr/sbin/snmpd"
|
||||
cmdenv = ""
|
||||
cmdopt = "{} -C -c /etc/frr/snmpd.conf -p ".format(
|
||||
@ -2162,7 +2167,11 @@ class Router(Node):
|
||||
"%s: %s %s started with rr", self, self.routertype, daemon
|
||||
)
|
||||
else:
|
||||
if daemon != "snmpd" and daemon != "snmptrapd":
|
||||
if (
|
||||
daemon != "snmpd"
|
||||
and daemon != "snmptrapd"
|
||||
and daemon != "fpm_listener"
|
||||
):
|
||||
cmdopt += " -d "
|
||||
cmdopt += rediropt
|
||||
|
||||
@ -2212,6 +2221,11 @@ class Router(Node):
|
||||
while "snmpd" in daemons_list:
|
||||
daemons_list.remove("snmpd")
|
||||
|
||||
if "fpm_listener" in daemons_list:
|
||||
start_daemon("fpm_listener")
|
||||
while "fpm_listener" in daemons_list:
|
||||
daemons_list.remove("fpm_listener")
|
||||
|
||||
# Now start all the other daemons
|
||||
for daemon in daemons_list:
|
||||
if self.daemons[daemon] == 0:
|
||||
@ -2407,6 +2421,8 @@ class Router(Node):
|
||||
continue
|
||||
if daemon == "snmptrapd":
|
||||
continue
|
||||
if daemon == "fpm_listener":
|
||||
continue
|
||||
if (self.daemons[daemon] == 1) and not (daemon in daemonsRunning):
|
||||
sys.stderr.write("%s: Daemon %s not running\n" % (self.name, daemon))
|
||||
if daemon == "staticd":
|
||||
|
@ -43,6 +43,7 @@ markers =
|
||||
bfdd: Tests that run against BFDD
|
||||
bgpd: Tests that run against BGPD
|
||||
eigrpd: Tests that run against EIGRPD
|
||||
fpm: Tests that run against the FPM
|
||||
isisd: Tests that run against ISISD
|
||||
ldpd: Tests that run against LDPD
|
||||
mgmtd: Tests that run against MGMTD
|
||||
|
@ -4656,19 +4656,6 @@ sub process {
|
||||
$herecurr);
|
||||
}
|
||||
|
||||
# check for sizeof(foo)/sizeof(foo[0]) that could be ARRAY_SIZE(foo)
|
||||
if ($line =~ m@\bsizeof\s*\(\s*($Lval)\s*\)@) {
|
||||
my $array = $1;
|
||||
if ($line =~ m@\b(sizeof\s*\(\s*\Q$array\E\s*\)\s*/\s*sizeof\s*\(\s*\Q$array\E\s*\[\s*0\s*\]\s*\))@) {
|
||||
my $array_div = $1;
|
||||
if (WARN("ARRAY_SIZE",
|
||||
"Prefer ARRAY_SIZE($array)\n" . $herecurr) &&
|
||||
$fix) {
|
||||
$fixed[$fixlinenr] =~ s/\Q$array_div\E/ARRAY_SIZE($array)/;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# check for function declarations without arguments like "int foo()"
|
||||
if ($line =~ /(\b$Type\s*$Ident)\s*\(\s*\)/) {
|
||||
if (ERROR("FUNCTION_WITHOUT_ARGS",
|
||||
|
1
zebra/.gitignore
vendored
1
zebra/.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
zebra
|
||||
zebra.conf
|
||||
client
|
||||
fpm_listener
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include "lib/network.h"
|
||||
#include "lib/ns.h"
|
||||
#include "lib/frr_pthread.h"
|
||||
#include "lib/termtable.h"
|
||||
#include "zebra/debug.h"
|
||||
#include "zebra/interface.h"
|
||||
#include "zebra/zebra_dplane.h"
|
||||
@ -44,6 +45,8 @@
|
||||
#include "zebra/debug.h"
|
||||
#include "fpm/fpm.h"
|
||||
|
||||
#include "zebra/dplane_fpm_nl_clippy.c"
|
||||
|
||||
#define SOUTHBOUND_DEFAULT_ADDR INADDR_LOOPBACK
|
||||
|
||||
/*
|
||||
@ -322,6 +325,74 @@ DEFUN(fpm_reset_counters, fpm_reset_counters_cmd,
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFPY(fpm_show_status,
|
||||
fpm_show_status_cmd,
|
||||
"show fpm status [json]$json",
|
||||
SHOW_STR FPM_STR "FPM status\n" JSON_STR)
|
||||
{
|
||||
struct json_object *j;
|
||||
bool connected;
|
||||
uint16_t port;
|
||||
struct sockaddr_in *sin;
|
||||
struct sockaddr_in6 *sin6;
|
||||
char buf[BUFSIZ];
|
||||
|
||||
connected = gfnc->socket > 0 ? true : false;
|
||||
|
||||
switch (gfnc->addr.ss_family) {
|
||||
case AF_INET:
|
||||
sin = (struct sockaddr_in *)&gfnc->addr;
|
||||
snprintfrr(buf, sizeof(buf), "%pI4", &sin->sin_addr);
|
||||
port = ntohs(sin->sin_port);
|
||||
break;
|
||||
case AF_INET6:
|
||||
sin6 = (struct sockaddr_in6 *)&gfnc->addr;
|
||||
snprintfrr(buf, sizeof(buf), "%pI6", &sin6->sin6_addr);
|
||||
port = ntohs(sin6->sin6_port);
|
||||
break;
|
||||
default:
|
||||
strlcpy(buf, "Unknown", sizeof(buf));
|
||||
port = FPM_DEFAULT_PORT;
|
||||
break;
|
||||
}
|
||||
|
||||
if (json) {
|
||||
j = json_object_new_object();
|
||||
|
||||
json_object_boolean_add(j, "connected", connected);
|
||||
json_object_boolean_add(j, "useNHG", gfnc->use_nhg);
|
||||
json_object_boolean_add(j, "useRouteReplace",
|
||||
gfnc->use_route_replace);
|
||||
json_object_boolean_add(j, "disabled", gfnc->disabled);
|
||||
json_object_string_add(j, "address", buf);
|
||||
json_object_int_add(j, "port", port);
|
||||
|
||||
vty_json(vty, j);
|
||||
} else {
|
||||
struct ttable *table = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
|
||||
char *out;
|
||||
|
||||
ttable_rowseps(table, 0, BOTTOM, true, '-');
|
||||
ttable_add_row(table, "Address to connect to|%s", buf);
|
||||
ttable_add_row(table, "Port|%u", port);
|
||||
ttable_add_row(table, "Connected|%s", connected ? "Yes" : "No");
|
||||
ttable_add_row(table, "Use Nexthop Groups|%s",
|
||||
gfnc->use_nhg ? "Yes" : "No");
|
||||
ttable_add_row(table, "Use Route Replace Semantics|%s",
|
||||
gfnc->use_route_replace ? "Yes" : "No");
|
||||
ttable_add_row(table, "Disabled|%s",
|
||||
gfnc->disabled ? "Yes" : "No");
|
||||
|
||||
out = ttable_dump(table, "\n");
|
||||
vty_out(vty, "%s\n", out);
|
||||
XFREE(MTYPE_TMP, out);
|
||||
|
||||
ttable_del(table);
|
||||
}
|
||||
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(fpm_show_counters, fpm_show_counters_cmd,
|
||||
"show fpm counters",
|
||||
SHOW_STR
|
||||
@ -1665,6 +1736,7 @@ static int fpm_nl_new(struct event_loop *tm)
|
||||
zlog_debug("%s register status: %d", prov_name, rv);
|
||||
|
||||
install_node(&fpm_node);
|
||||
install_element(ENABLE_NODE, &fpm_show_status_cmd);
|
||||
install_element(ENABLE_NODE, &fpm_show_counters_cmd);
|
||||
install_element(ENABLE_NODE, &fpm_show_counters_json_cmd);
|
||||
install_element(ENABLE_NODE, &fpm_reset_counters_cmd);
|
||||
|
632
zebra/fpm_listener.c
Normal file
632
zebra/fpm_listener.c
Normal file
@ -0,0 +1,632 @@
|
||||
// SPDX-License-Identifier: ISC
|
||||
/*
|
||||
* Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
|
||||
*
|
||||
* 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 ISC DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL ISC 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifdef GNU_LINUX
|
||||
#include <stdint.h>
|
||||
#include <memory.h>
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
#include <err.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <linux/netlink.h>
|
||||
#include <linux/rtnetlink.h>
|
||||
|
||||
#include "fpm/fpm.h"
|
||||
#include "lib/libfrr.h"
|
||||
|
||||
struct glob {
|
||||
int server_sock;
|
||||
int sock;
|
||||
};
|
||||
|
||||
struct glob glob_space;
|
||||
struct glob *glob = &glob_space;
|
||||
|
||||
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
|
||||
|
||||
/*
|
||||
* get_print_buf
|
||||
*/
|
||||
static char *
|
||||
get_print_buf(size_t *buf_len)
|
||||
{
|
||||
static char print_bufs[16][128];
|
||||
static int counter;
|
||||
|
||||
counter++;
|
||||
if (counter >= 16)
|
||||
counter = 0;
|
||||
|
||||
*buf_len = 128;
|
||||
return &print_bufs[counter][0];
|
||||
}
|
||||
|
||||
/*
|
||||
* create_listen_sock
|
||||
*/
|
||||
static int create_listen_sock(int port, int *sock_p)
|
||||
{
|
||||
int sock;
|
||||
struct sockaddr_in addr;
|
||||
int reuse;
|
||||
|
||||
sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (sock < 0) {
|
||||
fprintf(stderr, "Failed to create socket: %s\n", strerror(errno));
|
||||
return 0;
|
||||
}
|
||||
|
||||
reuse = 1;
|
||||
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) <
|
||||
0) {
|
||||
fprintf(stderr, "Failed to set reuse addr option: %s\n",
|
||||
strerror(errno));
|
||||
}
|
||||
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
addr.sin_port = htons(port);
|
||||
|
||||
if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||
fprintf(stderr, "Failed to bind to port %d: %s\n", port, strerror(errno));
|
||||
close(sock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (listen(sock, 5)) {
|
||||
fprintf(stderr, "Failed to listen on socket: %s\n", strerror(errno));
|
||||
close(sock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
*sock_p = sock;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* accept_conn
|
||||
*/
|
||||
static int accept_conn(int listen_sock)
|
||||
{
|
||||
int sock;
|
||||
struct sockaddr_in client_addr = { 0 };
|
||||
unsigned int client_len;
|
||||
|
||||
while (1) {
|
||||
char buf[120];
|
||||
|
||||
fprintf(stdout, "Waiting for client connection...\n");
|
||||
client_len = sizeof(client_addr);
|
||||
sock = accept(listen_sock, (struct sockaddr *)&client_addr,
|
||||
&client_len);
|
||||
|
||||
if (sock >= 0) {
|
||||
fprintf(stdout, "Accepted client %s\n",
|
||||
inet_ntop(AF_INET, &client_addr.sin_addr, buf, sizeof(buf)));
|
||||
return sock;
|
||||
}
|
||||
fprintf(stderr, "Failed to accept socket: %s\n", strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* read_fpm_msg
|
||||
*/
|
||||
static fpm_msg_hdr_t *
|
||||
read_fpm_msg(char *buf, size_t buf_len)
|
||||
{
|
||||
char *cur, *end;
|
||||
long need_len, bytes_read, have_len;
|
||||
fpm_msg_hdr_t *hdr;
|
||||
int reading_full_msg;
|
||||
|
||||
end = buf + buf_len;
|
||||
cur = buf;
|
||||
hdr = (fpm_msg_hdr_t *)buf;
|
||||
|
||||
while (1) {
|
||||
reading_full_msg = 0;
|
||||
|
||||
have_len = cur - buf;
|
||||
|
||||
if (have_len < (long)FPM_MSG_HDR_LEN) {
|
||||
need_len = FPM_MSG_HDR_LEN - have_len;
|
||||
} else {
|
||||
need_len = fpm_msg_len(hdr) - have_len;
|
||||
assert(need_len >= 0 && need_len <= (end - cur));
|
||||
|
||||
if (!need_len)
|
||||
return hdr;
|
||||
|
||||
reading_full_msg = 1;
|
||||
}
|
||||
|
||||
bytes_read = read(glob->sock, cur, need_len);
|
||||
|
||||
if (bytes_read == 0) {
|
||||
fprintf(stdout,
|
||||
"Socket closed as that read returned 0\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (bytes_read < 0) {
|
||||
fprintf(stderr, "Error reading from socket: %s\n",
|
||||
strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cur += bytes_read;
|
||||
|
||||
if (bytes_read < need_len) {
|
||||
fprintf(stderr,
|
||||
"Read %lu bytes but expected to read %lu bytes instead\n",
|
||||
bytes_read, need_len);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (reading_full_msg)
|
||||
return hdr;
|
||||
|
||||
if (!fpm_msg_ok(hdr, buf_len)) {
|
||||
assert(0);
|
||||
fprintf(stderr, "Malformed fpm message\n");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* netlink_msg_type_to_s
|
||||
*/
|
||||
static const char *
|
||||
netlink_msg_type_to_s(uint16_t type)
|
||||
{
|
||||
switch (type) {
|
||||
|
||||
case RTM_NEWROUTE:
|
||||
return "New route";
|
||||
|
||||
case RTM_DELROUTE:
|
||||
return "Del route";
|
||||
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* netlink_prot_to_s
|
||||
*/
|
||||
static const char *
|
||||
netlink_prot_to_s(unsigned char prot)
|
||||
{
|
||||
switch (prot) {
|
||||
|
||||
case RTPROT_KERNEL:
|
||||
return "Kernel";
|
||||
|
||||
case RTPROT_BOOT:
|
||||
return "Boot";
|
||||
|
||||
case RTPROT_STATIC:
|
||||
return "Static";
|
||||
|
||||
case RTPROT_ZEBRA:
|
||||
return "Zebra";
|
||||
|
||||
case RTPROT_DHCP:
|
||||
return "Dhcp";
|
||||
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
#define MAX_NHS 16
|
||||
|
||||
struct netlink_nh {
|
||||
struct rtattr *gateway;
|
||||
int if_index;
|
||||
};
|
||||
|
||||
struct netlink_msg_ctx {
|
||||
struct nlmsghdr *hdr;
|
||||
|
||||
/*
|
||||
* Stuff pertaining to route messages.
|
||||
*/
|
||||
struct rtmsg *rtmsg;
|
||||
struct rtattr *rtattrs[RTA_MAX + 1];
|
||||
|
||||
/*
|
||||
* Nexthops.
|
||||
*/
|
||||
struct netlink_nh nhs[MAX_NHS];
|
||||
unsigned long num_nhs;
|
||||
|
||||
struct rtattr *dest;
|
||||
struct rtattr *src;
|
||||
int *metric;
|
||||
|
||||
const char *err_msg;
|
||||
};
|
||||
|
||||
/*
|
||||
* netlink_msg_ctx_init
|
||||
*/
|
||||
static inline void netlink_msg_ctx_init(struct netlink_msg_ctx *ctx)
|
||||
{
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
}
|
||||
|
||||
/*
|
||||
* netlink_msg_ctx_set_err
|
||||
*/
|
||||
static inline void netlink_msg_ctx_set_err(struct netlink_msg_ctx *ctx,
|
||||
const char *err_msg)
|
||||
{
|
||||
if (ctx->err_msg)
|
||||
return;
|
||||
|
||||
ctx->err_msg = err_msg;
|
||||
}
|
||||
|
||||
/*
|
||||
* parse_rtattrs_
|
||||
*/
|
||||
static int parse_rtattrs_(struct rtattr *rta, size_t len, struct rtattr **rtas,
|
||||
int num_rtas, const char **err_msg)
|
||||
{
|
||||
memset(rtas, 0, num_rtas * sizeof(rtas[0]));
|
||||
|
||||
for (; len > 0; rta = RTA_NEXT(rta, len)) {
|
||||
if (!RTA_OK(rta, len)) {
|
||||
*err_msg = "Malformed rta";
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (rta->rta_type >= num_rtas) {
|
||||
warn("Unknown rtattr type %d", rta->rta_type);
|
||||
continue;
|
||||
}
|
||||
|
||||
rtas[rta->rta_type] = rta;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* parse_rtattrs
|
||||
*/
|
||||
static int parse_rtattrs(struct netlink_msg_ctx *ctx, struct rtattr *rta,
|
||||
size_t len)
|
||||
{
|
||||
const char *err_msg;
|
||||
|
||||
err_msg = NULL;
|
||||
|
||||
if (!parse_rtattrs_(rta, len, ctx->rtattrs, ARRAY_SIZE(ctx->rtattrs),
|
||||
&err_msg)) {
|
||||
netlink_msg_ctx_set_err(ctx, err_msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* netlink_msg_ctx_add_nh
|
||||
*/
|
||||
static int netlink_msg_ctx_add_nh(struct netlink_msg_ctx *ctx, int if_index,
|
||||
struct rtattr *gateway)
|
||||
{
|
||||
struct netlink_nh *nh;
|
||||
|
||||
if (ctx->num_nhs + 1 >= ARRAY_SIZE(ctx->nhs)) {
|
||||
warn("Too many next hops");
|
||||
return 0;
|
||||
}
|
||||
nh = &ctx->nhs[ctx->num_nhs];
|
||||
ctx->num_nhs++;
|
||||
|
||||
nh->gateway = gateway;
|
||||
nh->if_index = if_index;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* parse_multipath_attr
|
||||
*/
|
||||
static int parse_multipath_attr(struct netlink_msg_ctx *ctx,
|
||||
struct rtattr *mpath_rtattr)
|
||||
{
|
||||
size_t len;
|
||||
struct rtnexthop *rtnh;
|
||||
struct rtattr *rtattrs[RTA_MAX + 1];
|
||||
struct rtattr *gateway;
|
||||
const char *err_msg;
|
||||
|
||||
rtnh = RTA_DATA(mpath_rtattr);
|
||||
len = RTA_PAYLOAD(mpath_rtattr);
|
||||
|
||||
for (; len > 0;
|
||||
len -= NLMSG_ALIGN(rtnh->rtnh_len), rtnh = RTNH_NEXT(rtnh)) {
|
||||
|
||||
if (!RTNH_OK(rtnh, len)) {
|
||||
netlink_msg_ctx_set_err(ctx, "Malformed nh");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (rtnh->rtnh_len <= sizeof(*rtnh)) {
|
||||
netlink_msg_ctx_set_err(ctx, "NH len too small");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse attributes included in the nexthop.
|
||||
*/
|
||||
err_msg = NULL;
|
||||
if (!parse_rtattrs_(RTNH_DATA(rtnh),
|
||||
rtnh->rtnh_len - sizeof(*rtnh), rtattrs,
|
||||
ARRAY_SIZE(rtattrs), &err_msg)) {
|
||||
netlink_msg_ctx_set_err(ctx, err_msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
gateway = rtattrs[RTA_GATEWAY];
|
||||
netlink_msg_ctx_add_nh(ctx, rtnh->rtnh_ifindex, gateway);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* parse_route_msg
|
||||
*/
|
||||
static int parse_route_msg(struct netlink_msg_ctx *ctx)
|
||||
{
|
||||
int len;
|
||||
struct rtattr **rtattrs, *rtattr, *gateway, *oif;
|
||||
int if_index;
|
||||
|
||||
ctx->rtmsg = NLMSG_DATA(ctx->hdr);
|
||||
|
||||
len = ctx->hdr->nlmsg_len - NLMSG_LENGTH(sizeof(struct rtmsg));
|
||||
if (len < 0) {
|
||||
netlink_msg_ctx_set_err(ctx, "Bad message length");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!parse_rtattrs(ctx, RTM_RTA(ctx->rtmsg), len))
|
||||
return 0;
|
||||
|
||||
rtattrs = ctx->rtattrs;
|
||||
|
||||
ctx->dest = rtattrs[RTA_DST];
|
||||
ctx->src = rtattrs[RTA_PREFSRC];
|
||||
|
||||
rtattr = rtattrs[RTA_PRIORITY];
|
||||
if (rtattr)
|
||||
ctx->metric = (int *)RTA_DATA(rtattr);
|
||||
|
||||
gateway = rtattrs[RTA_GATEWAY];
|
||||
oif = rtattrs[RTA_OIF];
|
||||
if (gateway || oif) {
|
||||
if_index = 0;
|
||||
if (oif)
|
||||
if_index = *((int *)RTA_DATA(oif));
|
||||
|
||||
netlink_msg_ctx_add_nh(ctx, if_index, gateway);
|
||||
}
|
||||
|
||||
rtattr = rtattrs[RTA_MULTIPATH];
|
||||
if (rtattr)
|
||||
parse_multipath_attr(ctx, rtattr);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* addr_to_s
|
||||
*/
|
||||
static const char *
|
||||
addr_to_s(unsigned char family, void *addr)
|
||||
{
|
||||
size_t buf_len;
|
||||
char *buf;
|
||||
|
||||
buf = get_print_buf(&buf_len);
|
||||
|
||||
return inet_ntop(family, addr, buf, buf_len);
|
||||
}
|
||||
|
||||
/*
|
||||
* netlink_msg_ctx_print
|
||||
*/
|
||||
static int netlink_msg_ctx_snprint(struct netlink_msg_ctx *ctx, char *buf,
|
||||
size_t buf_len)
|
||||
{
|
||||
struct nlmsghdr *hdr;
|
||||
struct rtmsg *rtmsg;
|
||||
struct netlink_nh *nh;
|
||||
char *cur, *end;
|
||||
unsigned long i;
|
||||
|
||||
hdr = ctx->hdr;
|
||||
rtmsg = ctx->rtmsg;
|
||||
|
||||
cur = buf;
|
||||
end = buf + buf_len;
|
||||
|
||||
cur += snprintf(cur, end - cur, "%s %s/%d, Prot: %s",
|
||||
netlink_msg_type_to_s(hdr->nlmsg_type),
|
||||
addr_to_s(rtmsg->rtm_family, RTA_DATA(ctx->dest)),
|
||||
rtmsg->rtm_dst_len,
|
||||
netlink_prot_to_s(rtmsg->rtm_protocol));
|
||||
|
||||
if (ctx->metric)
|
||||
cur += snprintf(cur, end - cur, ", Metric: %d", *ctx->metric);
|
||||
|
||||
for (i = 0; i < ctx->num_nhs; i++) {
|
||||
cur += snprintf(cur, end - cur, "\n ");
|
||||
nh = &ctx->nhs[i];
|
||||
|
||||
if (nh->gateway) {
|
||||
cur += snprintf(cur, end - cur, " %s",
|
||||
addr_to_s(rtmsg->rtm_family,
|
||||
RTA_DATA(nh->gateway)));
|
||||
}
|
||||
|
||||
if (nh->if_index) {
|
||||
cur += snprintf(cur, end - cur, " via interface %d",
|
||||
nh->if_index);
|
||||
}
|
||||
}
|
||||
|
||||
return cur - buf;
|
||||
}
|
||||
|
||||
/*
|
||||
* print_netlink_msg_ctx
|
||||
*/
|
||||
static void print_netlink_msg_ctx(struct netlink_msg_ctx *ctx)
|
||||
{
|
||||
char buf[1024];
|
||||
|
||||
netlink_msg_ctx_snprint(ctx, buf, sizeof(buf));
|
||||
printf("%s\n", buf);
|
||||
}
|
||||
|
||||
/*
|
||||
* parse_netlink_msg
|
||||
*/
|
||||
static void
|
||||
parse_netlink_msg(char *buf, size_t buf_len)
|
||||
{
|
||||
struct netlink_msg_ctx ctx_space, *ctx;
|
||||
struct nlmsghdr *hdr;
|
||||
unsigned int len;
|
||||
|
||||
ctx = &ctx_space;
|
||||
|
||||
hdr = (struct nlmsghdr *)buf;
|
||||
len = buf_len;
|
||||
for (; NLMSG_OK(hdr, len); hdr = NLMSG_NEXT(hdr, len)) {
|
||||
|
||||
netlink_msg_ctx_init(ctx);
|
||||
ctx->hdr = (struct nlmsghdr *)buf;
|
||||
|
||||
switch (hdr->nlmsg_type) {
|
||||
|
||||
case RTM_DELROUTE:
|
||||
case RTM_NEWROUTE:
|
||||
|
||||
parse_route_msg(ctx);
|
||||
if (ctx->err_msg) {
|
||||
fprintf(stderr,
|
||||
"Error parsing route message: %s\n",
|
||||
ctx->err_msg);
|
||||
}
|
||||
|
||||
print_netlink_msg_ctx(ctx);
|
||||
break;
|
||||
|
||||
default:
|
||||
fprintf(stdout, "Ignoring unknown netlink message - Type: %d\n",
|
||||
hdr->nlmsg_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* process_fpm_msg
|
||||
*/
|
||||
static void process_fpm_msg(fpm_msg_hdr_t *hdr)
|
||||
{
|
||||
fprintf(stdout, "FPM message - Type: %d, Length %d\n", hdr->msg_type,
|
||||
ntohs(hdr->msg_len));
|
||||
|
||||
if (hdr->msg_type != FPM_MSG_TYPE_NETLINK) {
|
||||
fprintf(stderr, "Unknown fpm message type %u\n", hdr->msg_type);
|
||||
return;
|
||||
}
|
||||
|
||||
parse_netlink_msg(fpm_msg_data(hdr), fpm_msg_data_len(hdr));
|
||||
}
|
||||
|
||||
/*
|
||||
* fpm_serve
|
||||
*/
|
||||
static void fpm_serve(void)
|
||||
{
|
||||
char buf[FPM_MAX_MSG_LEN * 4];
|
||||
fpm_msg_hdr_t *hdr;
|
||||
|
||||
while (1) {
|
||||
|
||||
hdr = read_fpm_msg(buf, sizeof(buf));
|
||||
if (!hdr)
|
||||
return;
|
||||
|
||||
process_fpm_msg(hdr);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
pid_t daemon;
|
||||
int d;
|
||||
|
||||
d = getopt(argc, argv, "d");
|
||||
if (d == 'd') {
|
||||
daemon = fork();
|
||||
|
||||
if (daemon)
|
||||
exit(0);
|
||||
}
|
||||
|
||||
memset(glob, 0, sizeof(*glob));
|
||||
|
||||
if (!create_listen_sock(FPM_DEFAULT_PORT, &glob->server_sock))
|
||||
exit(1);
|
||||
|
||||
/*
|
||||
* Server forever.
|
||||
*/
|
||||
while (1) {
|
||||
glob->sock = accept_conn(glob->server_sock);
|
||||
fpm_serve();
|
||||
fprintf(stdout, "Done serving client");
|
||||
}
|
||||
}
|
||||
#else
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
fprintf(stderr, "This program only works on linux");
|
||||
exit(-1);
|
||||
}
|
||||
#endif
|
@ -19,6 +19,12 @@ if LINUX
|
||||
module_LTLIBRARIES += zebra/zebra_cumulus_mlag.la
|
||||
endif
|
||||
|
||||
#if FPM_LISTENER
|
||||
sbin_PROGRAMS += zebra/fpm_listener
|
||||
zebra_fpm_listener_SOURCES = zebra/fpm_listener.c
|
||||
zebra_fpm_listener_LDADD = lib/libfrr.la
|
||||
#endf
|
||||
|
||||
# Dataplane sample plugin
|
||||
if DEV_BUILD
|
||||
module_LTLIBRARIES += zebra/dplane_sample_plugin.la
|
||||
@ -116,6 +122,7 @@ zebra_zebra_SOURCES = \
|
||||
|
||||
clippy_scan += \
|
||||
zebra/debug.c \
|
||||
zebra/dplane_fpm_nl.c \
|
||||
zebra/interface.c \
|
||||
zebra/rtadv.c \
|
||||
zebra/zebra_mlag_vty.c \
|
||||
|
Loading…
Reference in New Issue
Block a user