From 852a74807fe1af51d69fd327156084b3e5d68fe4 Mon Sep 17 00:00:00 2001 From: Donald Sharp Date: Thu, 15 Feb 2024 10:19:36 -0500 Subject: [PATCH 1/8] doc, tools: Remove ARRAY_SIZE check checkpatch.pl wants you to use ARRAY_SIZE in a kernel header file. We don't have access to this kernel header file for normal compilation. I'm just going to remove it. Signed-off-by: Donald Sharp --- doc/developer/checkpatch.rst | 9 --------- tools/checkpatch.pl | 13 ------------- 2 files changed, 22 deletions(-) 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/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", From fef93e6149f088f5f717a37f36b4f35f8beb5690 Mon Sep 17 00:00:00 2001 From: Donald Sharp Date: Wed, 14 Feb 2024 16:07:45 -0500 Subject: [PATCH 2/8] zebra: Add fpm_listener Add a tiny fpm_listener program to allow FRR to test the fpm. Signed-off-by: Donald Sharp --- configure.ac | 7 + debian/not-installed | 1 + doc/user/installation.rst | 4 + redhat/frr.spec.in | 1 + zebra/.gitignore | 1 + zebra/fpm_listener.c | 632 ++++++++++++++++++++++++++++++++++++++ zebra/subdir.am | 6 + 7 files changed, 652 insertions(+) create mode 100644 zebra/fpm_listener.c 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/not-installed b/debian/not-installed index 8999dd948b..c7766ac257 100644 --- a/debian/not-installed +++ b/debian/not-installed @@ -1,4 +1,5 @@ usr/include usr/lib/frr/ospfclient +usr/lib/frr/fpm_listener usr/lib/frr/rfptest usr/lib/*/frr/modules/dplane_sample_plugin.so 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/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/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/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..41088e2c4c 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 From 0a9e8ef491e94a62d60fc2bfb0a151cef79961ea Mon Sep 17 00:00:00 2001 From: Donald Sharp Date: Wed, 14 Feb 2024 12:15:57 -0500 Subject: [PATCH 3/8] zebra: Add `show fpm status [json]` command Add a command to get the status of the dplane_fpm_nl module. Signed-off-by: Donald Sharp --- zebra/dplane_fpm_nl.c | 72 +++++++++++++++++++++++++++++++++++++++++++ zebra/subdir.am | 1 + 2 files changed, 73 insertions(+) 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/subdir.am b/zebra/subdir.am index 41088e2c4c..f767447366 100644 --- a/zebra/subdir.am +++ b/zebra/subdir.am @@ -122,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 \ From a0c4fe2ca65acd95f8fd2856aceda0a8af02e01e Mon Sep 17 00:00:00 2001 From: Donald Sharp Date: Wed, 14 Feb 2024 12:17:56 -0500 Subject: [PATCH 4/8] doc: Add `show fpm status [json]` command to documentation Signed-off-by: Donald Sharp --- doc/user/zebra.rst | 3 +++ 1 file changed, 3 insertions(+) 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 From 841d50e82f8586d2590c014d79620ef90756c590 Mon Sep 17 00:00:00 2001 From: Donald Sharp Date: Wed, 14 Feb 2024 12:41:49 -0500 Subject: [PATCH 5/8] tests: Add ability to run a fpm listener Add the ability to run a fpm listener to the testing system. This is nothing more just allowing the test system to bring it up. Signed-off-by: Donald Sharp --- tests/topotests/lib/topogen.py | 14 +++++++++++--- tests/topotests/lib/topotest.py | 28 ++++++++++++++++++++++------ 2 files changed, 33 insertions(+), 9 deletions(-) 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": From 5ca133e070277d7a317968e79f4e94881b87b7a1 Mon Sep 17 00:00:00 2001 From: Donald Sharp Date: Wed, 14 Feb 2024 12:45:16 -0500 Subject: [PATCH 6/8] tests: Add a very simple test for the dplane_fpm_nl module Ensure that the fpm module connects to the specified listener and then ensure that 10k routes from sharpd are installed into the system and then are removed. Signed-off-by: Donald Sharp --- .../fpm_testing_topo1/r1/fpm_counters.json | 8 ++ .../fpm_testing_topo1/r1/fpm_stub.conf | 0 .../fpm_testing_topo1/r1/routes_summ.json | 27 ++++ .../r1/routes_summ_removed.json | 20 +++ .../fpm_testing_topo1/r1/sharpd.conf | 0 .../topotests/fpm_testing_topo1/r1/zebra.conf | 5 + .../fpm_testing_topo1/test_fpm_topo1.py | 134 ++++++++++++++++++ 7 files changed, 194 insertions(+) create mode 100644 tests/topotests/fpm_testing_topo1/r1/fpm_counters.json create mode 100644 tests/topotests/fpm_testing_topo1/r1/fpm_stub.conf create mode 100644 tests/topotests/fpm_testing_topo1/r1/routes_summ.json create mode 100644 tests/topotests/fpm_testing_topo1/r1/routes_summ_removed.json create mode 100644 tests/topotests/fpm_testing_topo1/r1/sharpd.conf create mode 100644 tests/topotests/fpm_testing_topo1/r1/zebra.conf create mode 100644 tests/topotests/fpm_testing_topo1/test_fpm_topo1.py 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..557e551480 --- /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.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)) From c12f88cf6630624682aaab94218ccb3b9a3ef457 Mon Sep 17 00:00:00 2001 From: Donald Sharp Date: Sat, 17 Feb 2024 17:16:29 -0500 Subject: [PATCH 7/8] tests: Add a pytest marker for the fpm Signed-off-by: Donald Sharp --- tests/topotests/fpm_testing_topo1/test_fpm_topo1.py | 2 +- tests/topotests/pytest.ini | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/topotests/fpm_testing_topo1/test_fpm_topo1.py b/tests/topotests/fpm_testing_topo1/test_fpm_topo1.py index 557e551480..bb4d02d342 100644 --- a/tests/topotests/fpm_testing_topo1/test_fpm_topo1.py +++ b/tests/topotests/fpm_testing_topo1/test_fpm_topo1.py @@ -31,7 +31,7 @@ from lib.topogen import Topogen, TopoRouter, get_topogen from lib.topolog import logger -pytestmark = [pytest.mark.sharpd] +pytestmark = [pytest.mark.fpm, pytest.mark.sharpd] def build_topo(tgen): 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 From 0beabd76e59dda1aa4cdf991e87a183540835bf5 Mon Sep 17 00:00:00 2001 From: Donald Sharp Date: Wed, 21 Feb 2024 12:50:13 -0500 Subject: [PATCH 8/8] debian: Add a frr-test-tools debian package This package will hold test tools that are built and useful for developers of FRR but not useful for everyday usage of FRR. This is separted out because these are useful enough to have in their own package. Signed-off-by: Donald Sharp --- debian/control | 8 ++++++++ debian/frr-test-tools.install | 1 + debian/not-installed | 1 - 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 debian/frr-test-tools.install 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/debian/not-installed b/debian/not-installed index c7766ac257..8999dd948b 100644 --- a/debian/not-installed +++ b/debian/not-installed @@ -1,5 +1,4 @@ usr/include usr/lib/frr/ospfclient -usr/lib/frr/fpm_listener usr/lib/frr/rfptest usr/lib/*/frr/modules/dplane_sample_plugin.so