Merge pull request #15369 from donaldsharp/fpm_stub_addition

Add ability to test dplane_fpm_nl.c file
This commit is contained in:
Martin Winter 2024-03-05 13:06:42 +01:00 committed by GitHub
commit a44918640f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 964 additions and 31 deletions

View File

@ -703,6 +703,8 @@ AC_ARG_ENABLE([mgmtd_local_validations],
AS_HELP_STRING([--enable-mgmtd-local-validations], [dev: unimplemented local validation])) AS_HELP_STRING([--enable-mgmtd-local-validations], [dev: unimplemented local validation]))
AC_ARG_ENABLE([mgmtd_test_be_client], AC_ARG_ENABLE([mgmtd_test_be_client],
AS_HELP_STRING([--enable-mgmtd-test-be-client], [build test backend 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], AC_ARG_ENABLE([ripd],
AS_HELP_STRING([--disable-ripd], [do not build ripd])) AS_HELP_STRING([--disable-ripd], [do not build ripd]))
AC_ARG_ENABLE([ripngd], 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]) 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"], [ AS_IF([test "$enable_ripd" != "no"], [
AC_DEFINE([HAVE_RIPD], [1], [ripd]) 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([BGPD], [test "$enable_bgpd" != "no"])
AM_CONDITIONAL([MGMTD], [test "$enable_mgmtd" != "no"]) AM_CONDITIONAL([MGMTD], [test "$enable_mgmtd" != "no"])
AM_CONDITIONAL([MGMTD_TESTC], [test "$enable_mgmtd_test_be_client" = "yes"]) 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([RIPD], [test "$enable_ripd" != "no"])
AM_CONDITIONAL([OSPFD], [test "$enable_ospfd" != "no"]) AM_CONDITIONAL([OSPFD], [test "$enable_ospfd" != "no"])
AM_CONDITIONAL([LDPD], [test "$enable_ldpd" != "no"]) AM_CONDITIONAL([LDPD], [test "$enable_ldpd" != "no"])

8
debian/control vendored
View File

@ -102,6 +102,14 @@ Description: FRRouting suite - BGP RPKI support (rtrlib)
number. number.
Build-Profiles: <!pkg.frr.nortrlib> 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 Package: frr-doc
Section: doc Section: doc
Architecture: all Architecture: all

1
debian/frr-test-tools.install vendored Normal file
View File

@ -0,0 +1 @@
usr/lib/frr/fpm_listener

View File

@ -761,15 +761,6 @@ Indentation and Line Breaks
Macros, Attributes and Symbols 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** **AVOID_EXTERNS**
Function prototypes don't need to be declared extern in .h Function prototypes don't need to be declared extern in .h
files. It's assumed by the compiler and is unnecessary. files. It's assumed by the compiler and is unnecessary.

View File

@ -274,6 +274,10 @@ options from the list below.
Build with FPM module support. Build with FPM module support.
.. option:: --enable-fpm-listener
Build a small fpm listener for testing.
.. option:: --with-service-timeout=X .. option:: --with-service-timeout=X
Set timeout value for FRR service. The time of restarting or reloading FRR Set timeout value for FRR service. The time of restarting or reloading FRR

View File

@ -1356,6 +1356,9 @@ FPM Commands
User FPM configurations: 1 User FPM configurations: 1
User FPM disable requests: 0 User FPM disable requests: 0
.. clicmd:: show fpm status [json]
Show the FPM status.
.. clicmd:: clear fpm counters .. clicmd:: clear fpm counters

View File

@ -677,6 +677,7 @@ fi
%{_sbindir}/mgmtd_testc %{_sbindir}/mgmtd_testc
%endif %endif
%exclude %{_sbindir}/ssd %exclude %{_sbindir}/ssd
%exclude %{_sbindir}/fpm_listener
%if %{with_watchfrr} %if %{with_watchfrr}
%{_sbindir}/watchfrr %{_sbindir}/watchfrr
%endif %endif

View File

@ -0,0 +1,8 @@
{
"connected":true,
"useNHG":true,
"useRouteReplace":true,
"disabled":false,
"address":"127.0.0.1",
"port":2620
}

View 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
}

View File

@ -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
}

View File

@ -0,0 +1,5 @@
fpm address 127.0.0.1
interface r1-eth0
ip address 192.168.44.1/24
!

View 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))

View File

@ -748,6 +748,7 @@ class TopoRouter(TopoGear):
RD_PIM6 = 19 RD_PIM6 = 19
RD_MGMTD = 20 RD_MGMTD = 20
RD_TRAP = 21 RD_TRAP = 21
RD_FPM_LISTENER = 22
RD = { RD = {
RD_FRR: "frr", RD_FRR: "frr",
RD_ZEBRA: "zebra", RD_ZEBRA: "zebra",
@ -771,6 +772,7 @@ class TopoRouter(TopoGear):
RD_SNMP: "snmpd", RD_SNMP: "snmpd",
RD_MGMTD: "mgmtd", RD_MGMTD: "mgmtd",
RD_TRAP: "snmptrapd", RD_TRAP: "snmptrapd",
RD_FPM_LISTENER: "fpm_listener",
} }
def __init__(self, tgen, cls, name, **params): 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_RIPNG, TopoRouter.RD_OSPF, TopoRouter.RD_OSPF6,
TopoRouter.RD_ISIS, TopoRouter.RD_BGP, TopoRouter.RD_LDP, TopoRouter.RD_ISIS, TopoRouter.RD_BGP, TopoRouter.RD_LDP,
TopoRouter.RD_PIM, TopoRouter.RD_PIM6, TopoRouter.RD_PBR, 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 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 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 # Enable all daemon command logging, logging files
# and set them to the start dir. # and set them to the start dir.
for daemon, enabled in nrouter.daemons.items(): 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( self.vtysh_cmd(
"\n".join( "\n".join(
[ [
@ -935,7 +943,7 @@ class TopoRouter(TopoGear):
# and set them to the start dir. # and set them to the start dir.
for daemon in daemons: for daemon in daemons:
enabled = nrouter.daemons[daemon] enabled = nrouter.daemons[daemon]
if enabled and daemon != "snmpd": if enabled and daemon != "snmpd" and daemon != "fpm_listener":
self.vtysh_cmd( self.vtysh_cmd(
"\n".join( "\n".join(
[ [

View File

@ -1262,8 +1262,8 @@ def rlimit_atleast(rname, min_value, raises=False):
def fix_netns_limits(ns): def fix_netns_limits(ns):
# Maximum read and write socket buffer sizes # 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_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_wmem", [10 * 1024, 87380, 16 * 2 ** 20])
sysctl_assure(ns, "net.ipv4.conf.all.rp_filter", 0) sysctl_assure(ns, "net.ipv4.conf.all.rp_filter", 0)
sysctl_assure(ns, "net.ipv4.conf.default.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) sysctl_atleast(None, "net.core.netdev_max_backlog", 4 * 1024)
# Maximum read and write socket buffer sizes # Maximum read and write socket buffer sizes
sysctl_atleast(None, "net.core.rmem_max", 16 * 2**20) 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.wmem_max", 16 * 2 ** 20)
# Garbage Collection Settings for ARP and Neighbors # Garbage Collection Settings for ARP and Neighbors
sysctl_atleast(None, "net.ipv4.neigh.default.gc_thresh2", 4 * 1024) sysctl_atleast(None, "net.ipv4.neigh.default.gc_thresh2", 4 * 1024)
@ -1426,6 +1426,7 @@ class Router(Node):
"snmpd": 0, "snmpd": 0,
"mgmtd": 0, "mgmtd": 0,
"snmptrapd": 0, "snmptrapd": 0,
"fpm_listener": 0,
} }
self.daemons_options = {"zebra": ""} self.daemons_options = {"zebra": ""}
self.reportCores = True self.reportCores = True
@ -1896,7 +1897,11 @@ class Router(Node):
) )
rediropt = " > {0}.out 2> {0}.err".format(daemon) 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" binary = "/usr/sbin/snmpd"
cmdenv = "" cmdenv = ""
cmdopt = "{} -C -c /etc/frr/snmpd.conf -p ".format( 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 "%s: %s %s started with rr", self, self.routertype, daemon
) )
else: else:
if daemon != "snmpd" and daemon != "snmptrapd": if (
daemon != "snmpd"
and daemon != "snmptrapd"
and daemon != "fpm_listener"
):
cmdopt += " -d " cmdopt += " -d "
cmdopt += rediropt cmdopt += rediropt
@ -2212,6 +2221,11 @@ class Router(Node):
while "snmpd" in daemons_list: while "snmpd" in daemons_list:
daemons_list.remove("snmpd") 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 # Now start all the other daemons
for daemon in daemons_list: for daemon in daemons_list:
if self.daemons[daemon] == 0: if self.daemons[daemon] == 0:
@ -2407,6 +2421,8 @@ class Router(Node):
continue continue
if daemon == "snmptrapd": if daemon == "snmptrapd":
continue continue
if daemon == "fpm_listener":
continue
if (self.daemons[daemon] == 1) and not (daemon in daemonsRunning): if (self.daemons[daemon] == 1) and not (daemon in daemonsRunning):
sys.stderr.write("%s: Daemon %s not running\n" % (self.name, daemon)) sys.stderr.write("%s: Daemon %s not running\n" % (self.name, daemon))
if daemon == "staticd": if daemon == "staticd":

View File

@ -43,6 +43,7 @@ markers =
bfdd: Tests that run against BFDD bfdd: Tests that run against BFDD
bgpd: Tests that run against BGPD bgpd: Tests that run against BGPD
eigrpd: Tests that run against EIGRPD eigrpd: Tests that run against EIGRPD
fpm: Tests that run against the FPM
isisd: Tests that run against ISISD isisd: Tests that run against ISISD
ldpd: Tests that run against LDPD ldpd: Tests that run against LDPD
mgmtd: Tests that run against MGMTD mgmtd: Tests that run against MGMTD

View File

@ -4656,19 +4656,6 @@ sub process {
$herecurr); $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()" # check for function declarations without arguments like "int foo()"
if ($line =~ /(\b$Type\s*$Ident)\s*\(\s*\)/) { if ($line =~ /(\b$Type\s*$Ident)\s*\(\s*\)/) {
if (ERROR("FUNCTION_WITHOUT_ARGS", if (ERROR("FUNCTION_WITHOUT_ARGS",

1
zebra/.gitignore vendored
View File

@ -1,3 +1,4 @@
zebra zebra
zebra.conf zebra.conf
client client
fpm_listener

View File

@ -30,6 +30,7 @@
#include "lib/network.h" #include "lib/network.h"
#include "lib/ns.h" #include "lib/ns.h"
#include "lib/frr_pthread.h" #include "lib/frr_pthread.h"
#include "lib/termtable.h"
#include "zebra/debug.h" #include "zebra/debug.h"
#include "zebra/interface.h" #include "zebra/interface.h"
#include "zebra/zebra_dplane.h" #include "zebra/zebra_dplane.h"
@ -44,6 +45,8 @@
#include "zebra/debug.h" #include "zebra/debug.h"
#include "fpm/fpm.h" #include "fpm/fpm.h"
#include "zebra/dplane_fpm_nl_clippy.c"
#define SOUTHBOUND_DEFAULT_ADDR INADDR_LOOPBACK #define SOUTHBOUND_DEFAULT_ADDR INADDR_LOOPBACK
/* /*
@ -322,6 +325,74 @@ DEFUN(fpm_reset_counters, fpm_reset_counters_cmd,
return CMD_SUCCESS; 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, DEFUN(fpm_show_counters, fpm_show_counters_cmd,
"show fpm counters", "show fpm counters",
SHOW_STR SHOW_STR
@ -1665,6 +1736,7 @@ static int fpm_nl_new(struct event_loop *tm)
zlog_debug("%s register status: %d", prov_name, rv); zlog_debug("%s register status: %d", prov_name, rv);
install_node(&fpm_node); 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_cmd);
install_element(ENABLE_NODE, &fpm_show_counters_json_cmd); install_element(ENABLE_NODE, &fpm_show_counters_json_cmd);
install_element(ENABLE_NODE, &fpm_reset_counters_cmd); install_element(ENABLE_NODE, &fpm_reset_counters_cmd);

632
zebra/fpm_listener.c Normal file
View 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

View File

@ -19,6 +19,12 @@ if LINUX
module_LTLIBRARIES += zebra/zebra_cumulus_mlag.la module_LTLIBRARIES += zebra/zebra_cumulus_mlag.la
endif 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 # Dataplane sample plugin
if DEV_BUILD if DEV_BUILD
module_LTLIBRARIES += zebra/dplane_sample_plugin.la module_LTLIBRARIES += zebra/dplane_sample_plugin.la
@ -116,6 +122,7 @@ zebra_zebra_SOURCES = \
clippy_scan += \ clippy_scan += \
zebra/debug.c \ zebra/debug.c \
zebra/dplane_fpm_nl.c \
zebra/interface.c \ zebra/interface.c \
zebra/rtadv.c \ zebra/rtadv.c \
zebra/zebra_mlag_vty.c \ zebra/zebra_mlag_vty.c \