diff --git a/configure.ac b/configure.ac index c73131751f..ba11e8996e 100644 --- a/configure.ac +++ b/configure.ac @@ -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"]) diff --git a/debian/control b/debian/control index b3c14f06f3..fb8c2162da 100644 --- a/debian/control +++ b/debian/control @@ -102,6 +102,14 @@ Description: FRRouting suite - BGP RPKI support (rtrlib) number. Build-Profiles: +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 diff --git a/debian/frr-test-tools.install b/debian/frr-test-tools.install new file mode 100644 index 0000000000..a8ad18f2c6 --- /dev/null +++ b/debian/frr-test-tools.install @@ -0,0 +1 @@ +usr/lib/frr/fpm_listener diff --git a/doc/developer/checkpatch.rst b/doc/developer/checkpatch.rst index d8fe007c31..4ef261ba22 100644 --- a/doc/developer/checkpatch.rst +++ b/doc/developer/checkpatch.rst @@ -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. diff --git a/doc/user/installation.rst b/doc/user/installation.rst index 70f82353b7..f07bade52c 100644 --- a/doc/user/installation.rst +++ b/doc/user/installation.rst @@ -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 diff --git a/doc/user/zebra.rst b/doc/user/zebra.rst index 30b0204254..72b4f20418 100644 --- a/doc/user/zebra.rst +++ b/doc/user/zebra.rst @@ -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 diff --git a/redhat/frr.spec.in b/redhat/frr.spec.in index 3371a3ed4d..433aeacebb 100644 --- a/redhat/frr.spec.in +++ b/redhat/frr.spec.in @@ -677,6 +677,7 @@ fi %{_sbindir}/mgmtd_testc %endif %exclude %{_sbindir}/ssd +%exclude %{_sbindir}/fpm_listener %if %{with_watchfrr} %{_sbindir}/watchfrr %endif diff --git a/tests/topotests/fpm_testing_topo1/r1/fpm_counters.json b/tests/topotests/fpm_testing_topo1/r1/fpm_counters.json new file mode 100644 index 0000000000..05a6731e13 --- /dev/null +++ b/tests/topotests/fpm_testing_topo1/r1/fpm_counters.json @@ -0,0 +1,8 @@ +{ + "connected":true, + "useNHG":true, + "useRouteReplace":true, + "disabled":false, + "address":"127.0.0.1", + "port":2620 +} diff --git a/tests/topotests/fpm_testing_topo1/r1/fpm_stub.conf b/tests/topotests/fpm_testing_topo1/r1/fpm_stub.conf new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/topotests/fpm_testing_topo1/r1/routes_summ.json b/tests/topotests/fpm_testing_topo1/r1/routes_summ.json new file mode 100644 index 0000000000..e9157bc664 --- /dev/null +++ b/tests/topotests/fpm_testing_topo1/r1/routes_summ.json @@ -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 +} diff --git a/tests/topotests/fpm_testing_topo1/r1/routes_summ_removed.json b/tests/topotests/fpm_testing_topo1/r1/routes_summ_removed.json new file mode 100644 index 0000000000..8585b2bb6b --- /dev/null +++ b/tests/topotests/fpm_testing_topo1/r1/routes_summ_removed.json @@ -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 +} diff --git a/tests/topotests/fpm_testing_topo1/r1/sharpd.conf b/tests/topotests/fpm_testing_topo1/r1/sharpd.conf new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/topotests/fpm_testing_topo1/r1/zebra.conf b/tests/topotests/fpm_testing_topo1/r1/zebra.conf new file mode 100644 index 0000000000..c7b1646dda --- /dev/null +++ b/tests/topotests/fpm_testing_topo1/r1/zebra.conf @@ -0,0 +1,5 @@ +fpm address 127.0.0.1 + +interface r1-eth0 + ip address 192.168.44.1/24 +! diff --git a/tests/topotests/fpm_testing_topo1/test_fpm_topo1.py b/tests/topotests/fpm_testing_topo1/test_fpm_topo1.py new file mode 100644 index 0000000000..bb4d02d342 --- /dev/null +++ b/tests/topotests/fpm_testing_topo1/test_fpm_topo1.py @@ -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)) diff --git a/tests/topotests/lib/topogen.py b/tests/topotests/lib/topogen.py index 78d1b1c42c..f49e30ea5f 100644 --- a/tests/topotests/lib/topogen.py +++ b/tests/topotests/lib/topogen.py @@ -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( [ diff --git a/tests/topotests/lib/topotest.py b/tests/topotests/lib/topotest.py index 2bb892355e..0a5be970b8 100644 --- a/tests/topotests/lib/topotest.py +++ b/tests/topotests/lib/topotest.py @@ -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": diff --git a/tests/topotests/pytest.ini b/tests/topotests/pytest.ini index 98fcfbc848..db806fed39 100644 --- a/tests/topotests/pytest.ini +++ b/tests/topotests/pytest.ini @@ -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 diff --git a/tools/checkpatch.pl b/tools/checkpatch.pl index ecae0e92a1..f52f1a1616 100755 --- a/tools/checkpatch.pl +++ b/tools/checkpatch.pl @@ -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", diff --git a/zebra/.gitignore b/zebra/.gitignore index 41a86e7d75..f10240db43 100644 --- a/zebra/.gitignore +++ b/zebra/.gitignore @@ -1,3 +1,4 @@ zebra zebra.conf client +fpm_listener diff --git a/zebra/dplane_fpm_nl.c b/zebra/dplane_fpm_nl.c index 7ae1b2a090..3e7e84edd2 100644 --- a/zebra/dplane_fpm_nl.c +++ b/zebra/dplane_fpm_nl.c @@ -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); diff --git a/zebra/fpm_listener.c b/zebra/fpm_listener.c new file mode 100644 index 0000000000..d50e40e9d8 --- /dev/null +++ b/zebra/fpm_listener.c @@ -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 +#include +#include + +#ifdef GNU_LINUX +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 diff --git a/zebra/subdir.am b/zebra/subdir.am index d9c8d9045e..f767447366 100644 --- a/zebra/subdir.am +++ b/zebra/subdir.am @@ -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 \