From d8fa3d59274d0c2ba38e969b67ae991a8bb67303 Mon Sep 17 00:00:00 2001 From: Volodymyr Huti Date: Wed, 4 Oct 2023 15:52:14 +0300 Subject: [PATCH 1/6] lib, pbrd: Move DSCP logic into a library module Pull out all DSCP related code from PBR into a separate library module. Relevant for route-maps used by the QPPB plugin. Signed-off-by: Volodymyr Huti --- lib/dscp.c | 82 +++++++++++++++++++++++++++++++++++++++++++ lib/dscp.h | 28 +++++++++++++++ lib/pbr.h | 3 -- lib/subdir.am | 2 ++ lib/zclient.c | 15 ++++---- pbrd/pbr_map.c | 53 ---------------------------- pbrd/pbr_map.h | 2 -- pbrd/pbr_vty.c | 38 ++++++++++---------- tests/lib/cxxcompat.c | 1 + zebra/rule_netlink.c | 3 +- zebra/zebra_pbr.c | 5 +-- 11 files changed, 145 insertions(+), 87 deletions(-) create mode 100644 lib/dscp.c create mode 100644 lib/dscp.h diff --git a/lib/dscp.c b/lib/dscp.c new file mode 100644 index 000000000000..8c516bc089f1 --- /dev/null +++ b/lib/dscp.c @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * DSCP manipulation routines + * Copyright (C) 2023 VyOS Inc. + * Volodymyr Huti + */ + +#include "dscp.h" + +enum dscp_val { + DSCP_CS0 = 0x00, + DSCP_CS1 = 0x08, + DSCP_CS2 = 0x10, + DSCP_CS3 = 0x18, + DSCP_CS4 = 0x20, + DSCP_CS5 = 0x28, + DSCP_CS6 = 0x30, + DSCP_CS7 = 0x38, + DSCP_AF11 = 0x0A, + DSCP_AF12 = 0x0C, + DSCP_AF13 = 0x0E, + DSCP_AF21 = 0x12, + DSCP_AF22 = 0x14, + DSCP_AF23 = 0x16, + DSCP_AF31 = 0x1A, + DSCP_AF32 = 0x1C, + DSCP_AF33 = 0x1E, + DSCP_AF41 = 0x22, + DSCP_AF42 = 0x34, + DSCP_AF43 = 0x26, + DSCP_EF = 0x2E, + DSCP_VOICE = 0x2C, + DSCP_MAX = DSCP_CS7 + 1, + DSCP_ERR +}; + +static const struct { + const char *name; + int val; +} dscp_enum_int_map[] = { + {"cs0", DSCP_CS0}, {"cs1", DSCP_CS1}, {"cs2", DSCP_CS2}, + {"cs3", DSCP_CS3}, {"cs4", DSCP_CS4}, {"cs5", DSCP_CS5}, + {"cs6", DSCP_CS6}, {"cs7", DSCP_CS7}, {"af11", DSCP_AF11}, + {"af12", DSCP_AF12}, {"af13", DSCP_AF13}, {"af21", DSCP_AF21}, + {"af22", DSCP_AF22}, {"af23", DSCP_AF23}, {"af31", DSCP_AF31}, + {"af32", DSCP_AF32}, {"af33", DSCP_AF33}, {"af41", DSCP_AF41}, + {"af42", DSCP_AF42}, {"af43", DSCP_AF43}, {"ef", DSCP_EF}, + {"voice-admit", DSCP_VOICE}}; + +static const char *dscp_int_enum_map[DSCP_MAX] = { + [DSCP_CS0] = "cs0", [DSCP_CS1] = "cs1", [DSCP_CS2] = "cs2", + [DSCP_CS3] = "cs3", [DSCP_CS4] = "cs4", [DSCP_CS5] = "cs5", + [DSCP_CS6] = "cs6", [DSCP_CS7] = "cs7", [DSCP_AF11] = "af11", + [DSCP_AF12] = "af12", [DSCP_AF13] = "af13", [DSCP_AF21] = "af21", + [DSCP_AF22] = "af22", [DSCP_AF23] = "af23", [DSCP_AF31] = "af31", + [DSCP_AF32] = "af32", [DSCP_AF33] = "af33", [DSCP_AF41] = "af41", + [DSCP_AF42] = "af42", [DSCP_AF43] = "af43", [DSCP_EF] = "ef", + [DSCP_VOICE] = "voice-admit"}; + +static const int dscp_msize = + (sizeof(dscp_enum_int_map) / sizeof(*dscp_enum_int_map)); + +/* Decodes a standardized DSCP into its representative value */ +uint8_t dscp_decode_enum(const char *name) +{ + int dscp_val = -1; + + for (int i = 0; i < dscp_msize; ++i) { + if (!strcmp(dscp_enum_int_map[i].name, name)) { + dscp_val = dscp_enum_int_map[i].val; + break; + } + } + + return dscp_val; +} + +const char *dscp_enum_str(int dscp) +{ + return (dscp < DSCP_MAX) ? dscp_int_enum_map[dscp] : NULL; +} + diff --git a/lib/dscp.h b/lib/dscp.h new file mode 100644 index 000000000000..a69e7783bb4c --- /dev/null +++ b/lib/dscp.h @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * DSCP manipulation routines header + * Copyright (C) 2023 VyOS Inc. + * Volodymyr Huti + */ + +#ifndef FRR_DSCP_H +#define FRR_DSCP_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include "vty.h" + +#define DSFIELD_DSCP (0xfc) /* Upper 6 bits of DS field: DSCP */ +#define DSFIELD_ECN (0x03) /* Lower 2 bits of DS field: BCN */ + +extern uint8_t dscp_decode_enum(const char *dscp); +extern const char *dscp_enum_str(int dscp); + +#ifdef __cplusplus +} +#endif + +#endif /* FRR_DSCP_H */ diff --git a/lib/pbr.h b/lib/pbr.h index fe2d32a44a17..07b8d94c4022 100644 --- a/lib/pbr.h +++ b/lib/pbr.h @@ -43,9 +43,6 @@ struct pbr_filter { #define PBR_FILTER_VLAN_FLAGS (1 << 11) #define PBR_FILTER_VLAN_ID (1 << 12) -#define PBR_DSFIELD_DSCP (0xfc) /* Upper 6 bits of DS field: DSCP */ -#define PBR_DSFIELD_ECN (0x03) /* Lower 2 bits of DS field: BCN */ - #define PBR_PCP (0x07) /* 3-bit value 0..7 for prioritization*/ #define PBR_VLAN_FLAGS_NO_WILD 0 diff --git a/lib/subdir.am b/lib/subdir.am index 5ec6adf4c0a0..2454662533c7 100644 --- a/lib/subdir.am +++ b/lib/subdir.am @@ -138,6 +138,7 @@ lib_libfrr_la_SOURCES = \ lib/printf/glue.c \ lib/routing_nb.c \ lib/routing_nb_config.c \ + lib/dscp.c \ lib/tc.c \ # end @@ -333,6 +334,7 @@ pkginclude_HEADERS += \ lib/zlog_recirculate.h \ lib/zlog_targets.h \ lib/pbr.h \ + lib/dscp.h \ lib/tc.h \ lib/routing_nb.h \ \ diff --git a/lib/zclient.c b/lib/zclient.c index 4cbd04c11693..286439182282 100644 --- a/lib/zclient.c +++ b/lib/zclient.c @@ -28,6 +28,7 @@ #include "srte.h" #include "printfrr.h" #include "srv6.h" +#include "dscp.h" DEFINE_MTYPE_STATIC(LIB, ZCLIENT, "Zclient"); DEFINE_MTYPE_STATIC(LIB, REDIST_INST, "Redistribution instance IDs"); @@ -1708,9 +1709,9 @@ static void zapi_pbr_rule_filter_encode(struct stream *s, struct pbr_filter *f) stream_putw(s, f->dst_port); if (CHECK_FLAG(f->filter_bm, PBR_FILTER_DSCP)) - stream_putc(s, f->dsfield & PBR_DSFIELD_DSCP); + stream_putc(s, f->dsfield & DSFIELD_DSCP); if (CHECK_FLAG(f->filter_bm, PBR_FILTER_ECN)) - stream_putc(s, f->dsfield & PBR_DSFIELD_ECN); + stream_putc(s, f->dsfield & DSFIELD_ECN); /* vlan */ if (CHECK_FLAG(f->filter_bm, PBR_FILTER_PCP)) @@ -1751,7 +1752,7 @@ static bool zapi_pbr_rule_filter_decode(struct stream *s, struct pbr_filter *f) STREAM_GETC(s, dscp); if (CHECK_FLAG(f->filter_bm, PBR_FILTER_ECN)) STREAM_GETC(s, ecn); - f->dsfield = (dscp & PBR_DSFIELD_DSCP) | (ecn & PBR_DSFIELD_ECN); + f->dsfield = (dscp & DSFIELD_DSCP) | (ecn & DSFIELD_ECN); /* vlan */ if (CHECK_FLAG(f->filter_bm, PBR_FILTER_PCP)) @@ -1790,9 +1791,9 @@ static void zapi_pbr_rule_action_encode(struct stream *s, struct pbr_action *a) stream_putw(s, a->dst_port); if (CHECK_FLAG(a->flags, PBR_ACTION_DSCP)) - stream_putc(s, a->dscp & PBR_DSFIELD_DSCP); + stream_putc(s, a->dscp & DSFIELD_DSCP); if (CHECK_FLAG(a->flags, PBR_ACTION_ECN)) - stream_putc(s, a->ecn & PBR_DSFIELD_ECN); + stream_putc(s, a->ecn & DSFIELD_ECN); /* L2 */ if (CHECK_FLAG(a->flags, PBR_ACTION_PCP)) @@ -1826,11 +1827,11 @@ static bool zapi_pbr_rule_action_decode(struct stream *s, struct pbr_action *a) if (CHECK_FLAG(a->flags, PBR_ACTION_DSCP)) { STREAM_GETC(s, a->dscp); - a->dscp &= PBR_DSFIELD_DSCP; + a->dscp &= DSFIELD_DSCP; } if (CHECK_FLAG(a->flags, PBR_ACTION_ECN)) { STREAM_GETC(s, a->ecn); - a->ecn &= PBR_DSFIELD_ECN; + a->ecn &= DSFIELD_ECN; } /* L2 */ diff --git a/pbrd/pbr_map.c b/pbrd/pbr_map.c index 8f7a46377c86..0acaed8dafbc 100644 --- a/pbrd/pbr_map.c +++ b/pbrd/pbr_map.c @@ -440,59 +440,6 @@ static void pbr_map_add_interfaces(struct pbr_map *pbrm) } } -/* Decodes a standardized DSCP into its representative value */ -uint8_t pbr_map_decode_dscp_enum(const char *name) -{ - /* Standard Differentiated Services Field Codepoints */ - if (!strcmp(name, "cs0")) - return 0; - if (!strcmp(name, "cs1")) - return 8; - if (!strcmp(name, "cs2")) - return 16; - if (!strcmp(name, "cs3")) - return 24; - if (!strcmp(name, "cs4")) - return 32; - if (!strcmp(name, "cs5")) - return 40; - if (!strcmp(name, "cs6")) - return 48; - if (!strcmp(name, "cs7")) - return 56; - if (!strcmp(name, "af11")) - return 10; - if (!strcmp(name, "af12")) - return 12; - if (!strcmp(name, "af13")) - return 14; - if (!strcmp(name, "af21")) - return 18; - if (!strcmp(name, "af22")) - return 20; - if (!strcmp(name, "af23")) - return 22; - if (!strcmp(name, "af31")) - return 26; - if (!strcmp(name, "af32")) - return 28; - if (!strcmp(name, "af33")) - return 30; - if (!strcmp(name, "af41")) - return 34; - if (!strcmp(name, "af42")) - return 36; - if (!strcmp(name, "af43")) - return 38; - if (!strcmp(name, "ef")) - return 46; - if (!strcmp(name, "voice-admit")) - return 44; - - /* No match? Error out */ - return -1; -} - struct pbr_map_sequence *pbrms_get(const char *name, uint32_t seqno) { struct pbr_map *pbrm = NULL; diff --git a/pbrd/pbr_map.h b/pbrd/pbr_map.h index 9fb674bd6eb8..37ea73a78288 100644 --- a/pbrd/pbr_map.h +++ b/pbrd/pbr_map.h @@ -214,8 +214,6 @@ extern void pbr_map_add_interface(struct pbr_map *pbrm, struct interface *ifp); extern void pbr_map_interface_delete(struct pbr_map *pbrm, struct interface *ifp); -extern uint8_t pbr_map_decode_dscp_enum(const char *name); - /* Update maps installed on interface */ extern void pbr_map_policy_interface_update(const struct interface *ifp, bool state_up); diff --git a/pbrd/pbr_vty.c b/pbrd/pbr_vty.c index 64d88847c8a5..d91eaee0eadc 100644 --- a/pbrd/pbr_vty.c +++ b/pbrd/pbr_vty.c @@ -20,6 +20,7 @@ #include "json.h" #include "debug.h" #include "pbr.h" +#include "dscp.h" #include "pbrd/pbr_nht.h" #include "pbrd/pbr_map.h" @@ -437,7 +438,7 @@ DEFPY (pbr_map_match_dscp, if (!CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DSCP)) return CMD_SUCCESS; UNSET_FLAG(pbrms->filter_bm, PBR_FILTER_DSCP); - pbrms->dsfield &= ~PBR_DSFIELD_DSCP; + pbrms->dsfield &= ~DSFIELD_DSCP; goto check; } @@ -448,23 +449,23 @@ DEFPY (pbr_map_match_dscp, assert(dscp); ul_dscp = strtoul(dscp, &pend, 0); if (pend && *pend) - ul_dscp = pbr_map_decode_dscp_enum(dscp); + ul_dscp = dscp_decode_enum(dscp); - if (ul_dscp > (PBR_DSFIELD_DSCP >> 2)) { + if (ul_dscp > (DSFIELD_DSCP >> 2)) { vty_out(vty, "Invalid dscp value: %s%s\n", dscp, ((pend && *pend) ? "" : " (numeric value must be in range 0-63)")); return CMD_WARNING_CONFIG_FAILED; } - shifted_dscp = (ul_dscp << 2) & PBR_DSFIELD_DSCP; + shifted_dscp = (ul_dscp << 2) & DSFIELD_DSCP; if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DSCP) && - ((pbrms->dsfield & PBR_DSFIELD_DSCP) == shifted_dscp)) { + ((pbrms->dsfield & DSFIELD_DSCP) == shifted_dscp)) { return CMD_SUCCESS; } /* Set the DSCP bits of the DSField */ - pbrms->dsfield = (pbrms->dsfield & ~PBR_DSFIELD_DSCP) | shifted_dscp; + pbrms->dsfield = (pbrms->dsfield & ~DSFIELD_DSCP) | shifted_dscp; SET_FLAG(pbrms->filter_bm, PBR_FILTER_DSCP); check: @@ -491,17 +492,17 @@ DEFPY (pbr_map_match_ecn, if (!CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_ECN)) return CMD_SUCCESS; UNSET_FLAG(pbrms->filter_bm, PBR_FILTER_ECN); - pbrms->dsfield &= ~PBR_DSFIELD_ECN; + pbrms->dsfield &= ~DSFIELD_ECN; goto check; } if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_ECN) && - ((pbrms->dsfield & PBR_DSFIELD_ECN) == ecn)) { + ((pbrms->dsfield & DSFIELD_ECN) == ecn)) { return CMD_SUCCESS; } /* Set the ECN bits of the DSField */ - pbrms->dsfield = (pbrms->dsfield & ~PBR_DSFIELD_ECN) | ecn; + pbrms->dsfield = (pbrms->dsfield & ~DSFIELD_ECN) | ecn; SET_FLAG(pbrms->filter_bm, PBR_FILTER_ECN); check: @@ -876,15 +877,15 @@ DEFPY (pbr_map_action_dscp, assert(dscp); ul_dscp = strtoul(dscp, &pend, 0); if (pend && *pend) - ul_dscp = pbr_map_decode_dscp_enum(dscp); + ul_dscp = dscp_decode_enum(dscp); - if (ul_dscp > (PBR_DSFIELD_DSCP >> 2)) { + if (ul_dscp > (DSFIELD_DSCP >> 2)) { vty_out(vty, "Invalid dscp value: %s%s\n", dscp, ((pend && *pend) ? "" : " (numeric value must be in range 0-63)")); return CMD_WARNING_CONFIG_FAILED; } - shifted_dscp = (ul_dscp << 2) & PBR_DSFIELD_DSCP; + shifted_dscp = (ul_dscp << 2) & DSFIELD_DSCP; if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_DSCP) && (pbrms->action_dscp == shifted_dscp)) { @@ -1549,10 +1550,10 @@ static void vty_show_pbrms(struct vty *vty, if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DSCP)) vty_out(vty, " DSCP Match: %u\n", - (pbrms->dsfield & PBR_DSFIELD_DSCP) >> 2); + (pbrms->dsfield & DSFIELD_DSCP) >> 2); if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_ECN)) vty_out(vty, " ECN Match: %u\n", - pbrms->dsfield & PBR_DSFIELD_ECN); + pbrms->dsfield & DSFIELD_ECN); if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_FWMARK)) vty_out(vty, " MARK Match: %u\n", pbrms->mark); @@ -1716,11 +1717,10 @@ static void vty_json_pbrms(json_object *j, struct vty *vty, if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DSCP)) json_object_int_add(jpbrm, "matchDscp", - (pbrms->dsfield & PBR_DSFIELD_DSCP) >> 2); + (pbrms->dsfield & DSFIELD_DSCP) >> 2); if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_ECN)) json_object_int_add(jpbrm, "matchEcn", - pbrms->dsfield & PBR_DSFIELD_ECN); - + pbrms->dsfield & DSFIELD_ECN); /* L2 headers */ if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_PCP)) json_object_int_add(jpbrm, "matchPcp", pbrms->match_pcp); @@ -2066,11 +2066,11 @@ static int pbr_vty_map_config_write_sequence(struct vty *vty, if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DSCP)) vty_out(vty, " match dscp %u\n", - (pbrms->dsfield & PBR_DSFIELD_DSCP) >> 2); + (pbrms->dsfield & DSFIELD_DSCP) >> 2); if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_ECN)) vty_out(vty, " match ecn %u\n", - pbrms->dsfield & PBR_DSFIELD_ECN); + pbrms->dsfield & DSFIELD_ECN); if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_PCP)) vty_out(vty, " match pcp %d\n", pbrms->match_pcp); diff --git a/tests/lib/cxxcompat.c b/tests/lib/cxxcompat.c index 4ad41fca425e..b4ca01f33392 100644 --- a/tests/lib/cxxcompat.c +++ b/tests/lib/cxxcompat.c @@ -32,6 +32,7 @@ #include "lib/if.h" #include "lib/if_rmap.h" #include "lib/imsg.h" +#include "lib/dscp.h" #include "lib/ipaddr.h" #include "lib/jhash.h" #include "lib/json.h" diff --git a/zebra/rule_netlink.c b/zebra/rule_netlink.c index 05282793d72d..6fd643414884 100644 --- a/zebra/rule_netlink.c +++ b/zebra/rule_netlink.c @@ -15,6 +15,7 @@ #include "if.h" #include "prefix.h" #include "vrf.h" +#include "dscp.h" #include #include "zebra/zserv.h" @@ -121,7 +122,7 @@ static ssize_t netlink_rule_msg_encode( /* dsfield, if specified; mask off the ECN bits */ if (filter_bm & PBR_FILTER_DSCP) - req->frh.tos = dsfield & PBR_DSFIELD_DSCP; + req->frh.tos = dsfield & DSFIELD_DSCP; /* protocol to match on */ if (filter_bm & PBR_FILTER_IP_PROTOCOL) diff --git a/zebra/zebra_pbr.c b/zebra/zebra_pbr.c index 7f3635702f82..99b2d5cb18e8 100644 --- a/zebra/zebra_pbr.c +++ b/zebra/zebra_pbr.c @@ -12,6 +12,7 @@ #include #include #include +#include #include "zebra/zebra_router.h" #include "zebra/zebra_pbr.h" @@ -533,10 +534,10 @@ void zebra_pbr_show_rule_unit(struct zebra_pbr_rule *rule, struct vty *vty) if (prule->filter.filter_bm & PBR_FILTER_DSCP) vty_out(vty, " DSCP Match: %u\n", - (prule->filter.dsfield & PBR_DSFIELD_DSCP) >> 2); + (prule->filter.dsfield & DSFIELD_DSCP) >> 2); if (prule->filter.filter_bm & PBR_FILTER_ECN) vty_out(vty, " ECN Match: %u\n", - prule->filter.dsfield & PBR_DSFIELD_ECN); + prule->filter.dsfield & DSFIELD_ECN); if (prule->filter.filter_bm & PBR_FILTER_FWMARK) vty_out(vty, " MARK Match: %u\n", prule->filter.fwmark); From 2bb38d2e6515bf6db2215ace494a0ededc5c239a Mon Sep 17 00:00:00 2001 From: Volodymyr Huti Date: Wed, 4 Oct 2023 16:09:34 +0300 Subject: [PATCH 2/6] bgp, lib, yang: Extend route-map to support the DSCP tag Signed-off-by: Volodymyr Huti --- bgpd/bgp_attr.c | 1 + bgpd/bgp_attr.h | 3 +++ bgpd/bgp_route.c | 15 ++++++++++- bgpd/bgp_routemap.c | 29 +++++++++++++++++++++ bgpd/bgp_zebra.c | 16 +++++++++--- lib/routemap.c | 46 +++++++++++++++++++++++++++++++++ lib/routemap.h | 23 +++++++++++++++++ lib/routemap_cli.c | 39 ++++++++++++++++++++++++++++ lib/routemap_northbound.c | 53 +++++++++++++++++++++++++++++++++++++++ yang/frr-route-map.yang | 15 +++++++++++ 10 files changed, 235 insertions(+), 5 deletions(-) diff --git a/bgpd/bgp_attr.c b/bgpd/bgp_attr.c index 5320a5f57e7d..98f6c954c315 100644 --- a/bgpd/bgp_attr.c +++ b/bgpd/bgp_attr.c @@ -1076,6 +1076,7 @@ struct attr *bgp_attr_default_set(struct attr *attr, struct bgp *bgp, attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_AS_PATH); attr->weight = BGP_ATTR_DEFAULT_WEIGHT; attr->tag = 0; + attr->dscp = 0; attr->label_index = BGP_INVALID_LABEL_INDEX; attr->label = MPLS_INVALID_LABEL; attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP); diff --git a/bgpd/bgp_attr.h b/bgpd/bgp_attr.h index 5386f24a0bac..45e569a68c0c 100644 --- a/bgpd/bgp_attr.h +++ b/bgpd/bgp_attr.h @@ -309,6 +309,9 @@ struct attr { /* SR-TE Color */ uint32_t srte_color; + /* DSCP tag, used by the QPPB plugin */ + uint8_t dscp; + /* Nexthop type */ enum nexthop_types_t nh_type; diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c index 88baa535100d..34cffc1fadf4 100644 --- a/bgpd/bgp_route.c +++ b/bgpd/bgp_route.c @@ -16,6 +16,7 @@ #include "stream.h" #include "filter.h" #include "log.h" +#include "dscp.h" #include "routemap.h" #include "buffer.h" #include "sockunion.h" @@ -10156,7 +10157,7 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, json_object *json_peer = NULL; json_object *json_string = NULL; json_object *json_adv_to = NULL; - int first = 0; + int first = 0, dscp = attr->dscp >> 2; struct listnode *node, *nnode; struct peer *peer; bool addpath_capable; @@ -10838,6 +10839,18 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, } } + /* XXX: on Cisco + * R1# sh ip cef 5.5.0.0 de + * 5.5.0.0/24, epoch 0 + * QOS: Precedence critical (5) + * recursive via ... + * nexthop ... iface + */ + if (dscp) { + vty_out(vty, " QOS: Precedence %s (%d)\n", + dscp_enum_str(dscp), dscp); + } + /* Line 5 display Extended-community */ if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES)) { if (json_paths) { diff --git a/bgpd/bgp_routemap.c b/bgpd/bgp_routemap.c index 15828b659465..38ff5d75d62b 100644 --- a/bgpd/bgp_routemap.c +++ b/bgpd/bgp_routemap.c @@ -107,6 +107,7 @@ o Cisco route-map metric-type : Not yet origin : Done tag : Done + dscp : Done weight : Done table : Done @@ -3567,6 +3568,30 @@ static const struct route_map_rule_cmd route_set_tag_cmd = { route_map_rule_tag_free, }; +/* Set dscp to object. object must be pointer to struct bgp_path_info */ +static enum route_map_cmd_result_t +route_set_dscp(void *rule, const struct prefix *prefix, void *object) +{ + struct bgp_path_info *path; + uint8_t *rawDscp; + + rawDscp = rule; + path = object; + + /* Set dscp value */ + path->attr->dscp = *rawDscp; + + return RMAP_OKAY; +} + +/* Route map commands for dscp set. */ +static const struct route_map_rule_cmd route_set_dscp_cmd = { + "dscp", + route_set_dscp, + route_map_rule_dscp_compile, + route_map_rule_dscp_free, +}; + /* Set label-index to object. object must be pointer to struct bgp_path_info */ static enum route_map_cmd_result_t route_set_label_index(void *rule, const struct prefix *prefix, void *object) @@ -7840,6 +7865,9 @@ void bgp_route_map_init(void) route_map_set_tag_hook(generic_set_add); route_map_no_set_tag_hook(generic_set_delete); + route_map_set_dscp_hook(generic_set_add); + route_map_no_set_dscp_hook(generic_set_delete); + route_map_install_match(&route_match_peer_cmd); route_map_install_match(&route_match_alias_cmd); route_map_install_match(&route_match_local_pref_cmd); @@ -7903,6 +7931,7 @@ void bgp_route_map_init(void) route_map_install_set(&route_set_ecommunity_color_cmd); route_map_install_set(&route_set_ecommunity_none_cmd); route_map_install_set(&route_set_tag_cmd); + route_map_install_set(&route_set_dscp_cmd); route_map_install_set(&route_set_label_index_cmd); route_map_install_set(&route_set_l3vpn_nexthop_encapsulation_cmd); diff --git a/bgpd/bgp_zebra.c b/bgpd/bgp_zebra.c index 04d520a9233e..c94c79b094b6 100644 --- a/bgpd/bgp_zebra.c +++ b/bgpd/bgp_zebra.c @@ -1233,7 +1233,7 @@ static void bgp_zebra_announce_parse_nexthop( struct bgp_path_info *info, const struct prefix *p, struct bgp *bgp, struct zapi_route *api, unsigned int *valid_nh_count, afi_t afi, safi_t safi, uint32_t *nhg_id, uint32_t *metric, route_tag_t *tag, - bool *allow_recursion) + uint8_t *dscp, bool *allow_recursion) { struct zapi_nexthop *api_nh; int nh_family; @@ -1334,13 +1334,21 @@ static void bgp_zebra_announce_parse_nexthop( p, mpinfo_cp)) continue; - /* metric/tag is only allowed to be + /* metric/tag/dscp is only allowed to be * overridden on 1st nexthop */ if (mpinfo == info) { if (metric) *metric = mpinfo_cp->attr->med; if (tag) *tag = mpinfo_cp->attr->tag; + if (dscp) { + /* used by hook from the QPPB plugin */ + *dscp = mpinfo_cp->attr->dscp; + /* expose value back to CLI `show bgp ipv4 nh` + * displayed by route_vty_out_detail()` + */ + mpinfo->attr->dscp = *dscp; + } } } @@ -1532,7 +1540,7 @@ bgp_zebra_announce_actual(struct bgp_dest *dest, struct bgp_path_info *info, struct zapi_route api = { 0 }; unsigned int valid_nh_count = 0; bool allow_recursion = false; - uint8_t distance; + uint8_t distance, dscp = 0; struct peer *peer; uint32_t metric; route_tag_t tag; @@ -1589,7 +1597,7 @@ bgp_zebra_announce_actual(struct bgp_dest *dest, struct bgp_path_info *info, bgp_zebra_announce_parse_nexthop(info, p, bgp, &api, &valid_nh_count, table->afi, table->safi, &nhg_id, - &metric, &tag, &allow_recursion); + &metric, &tag, &dscp, &allow_recursion); is_add = (valid_nh_count || nhg_id) ? true : false; diff --git a/lib/routemap.c b/lib/routemap.c index ea917ebd8c73..7af60c8d1ce0 100644 --- a/lib/routemap.c +++ b/lib/routemap.c @@ -20,6 +20,7 @@ #include "table.h" #include "json.h" #include "jhash.h" +#include "dscp.h" #include "lib/routemap_clippy.c" @@ -446,6 +447,24 @@ void route_map_no_set_tag_hook(int (*func)(struct route_map_index *index, rmap_match_set_hook.no_set_tag = func; } +/* set dscp */ +void route_map_set_dscp_hook(int (*func)(struct route_map_index *index, + const char *command, const char *arg, + char *errmsg, size_t errmsg_len)) +{ + + rmap_match_set_hook.set_dscp = func; +} + +/* no set dscp */ +void route_map_no_set_dscp_hook(int (*func)(struct route_map_index *index, + const char *command, + const char *arg, char *errmsg, + size_t errmsg_len)) +{ + rmap_match_set_hook.no_set_dscp = func; +} + int generic_match_add(struct route_map_index *index, const char *command, const char *arg, route_map_event_t type, @@ -3228,6 +3247,33 @@ void *route_map_rule_tag_compile(const char *arg) return tag; } +void *route_map_rule_dscp_compile(const char *dscp) +{ + unsigned long ul_dscp; + char *pend = NULL; + uint8_t *shifted_dscp; + + assert(dscp); + ul_dscp = strtoul(dscp, &pend, 0); + if (pend && *pend) + ul_dscp = dscp_decode_enum(dscp); + + if (ul_dscp > (DSFIELD_DSCP >> 2)) { + zlog_err("Invalid dscp value: %s%s", dscp, + ((pend && *pend) ? "" : " (numeric value must be in range 0-63)")); + return NULL; + } + + shifted_dscp = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(*shifted_dscp)); + *shifted_dscp = (ul_dscp << 2) & DSFIELD_DSCP; + return shifted_dscp; +} + +void route_map_rule_dscp_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + void route_map_rule_tag_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); diff --git a/lib/routemap.h b/lib/routemap.h index dfb84ced5bae..3069523470ea 100644 --- a/lib/routemap.h +++ b/lib/routemap.h @@ -317,6 +317,7 @@ DECLARE_QOBJ_TYPE(route_map); #define IS_SET_MIN_METRIC(A) (strmatch(A, "frr-route-map:set-min-metric")) #define IS_SET_MAX_METRIC(A) (strmatch(A, "frr-route-map:set-max-metric")) #define IS_SET_TAG(A) (strmatch(A, "frr-route-map:set-tag")) +#define IS_SET_DSCP(A) (strmatch(A, "frr-route-map:set-dscp")) #define IS_SET_SR_TE_COLOR(A) \ (strmatch(A, "frr-route-map:set-sr-te-color")) /* Zebra route-map set actions */ @@ -725,8 +726,22 @@ extern void route_map_no_set_tag_hook(int (*func)(struct route_map_index *index, char *errmsg, size_t errmsg_len)); +/* set dscp */ +extern void route_map_set_dscp_hook(int (*func)(struct route_map_index *index, + const char *command, + const char *arg, char *errmsg, + size_t errmsg_len)); +/* no set dscp */ +extern void route_map_no_set_dscp_hook( + int (*func)(struct route_map_index *index, const char *command, + const char *arg, char *errmsg, size_t errmsg_len)); + extern void *route_map_rule_tag_compile(const char *arg); extern void route_map_rule_tag_free(void *rule); +extern uint8_t route_map_decode_dscp_enum(const char *name); +extern const char *route_map_dscp_enum_str(int dscp); +extern void *route_map_rule_dscp_compile(const char *dscp); +extern void route_map_rule_dscp_free(void *dscp); /* Increment the route-map used counter */ extern void route_map_counter_increment(struct route_map *map); @@ -977,6 +992,14 @@ struct route_map_match_set_hooks { int (*no_set_tag)(struct route_map_index *index, const char *command, const char *arg, char *errmsg, size_t errmsg_len); + + /* set dscp */ + int (*set_dscp)(struct route_map_index *index, const char *command, + const char *arg, char *errmsg, size_t errmsg_len); + + /* no set dscp */ + int (*no_set_dscp)(struct route_map_index *index, const char *command, + const char *arg, char *errmsg, size_t errmsg_len); }; extern struct route_map_match_set_hooks rmap_match_set_hook; diff --git a/lib/routemap_cli.c b/lib/routemap_cli.c index 88b341cac080..2f42dc77b5ba 100644 --- a/lib/routemap_cli.c +++ b/lib/routemap_cli.c @@ -1004,6 +1004,39 @@ DEFPY_YANG( return nb_cli_apply_changes(vty, NULL); } +DEFPY_YANG( + set_dscp, set_dscp_cmd, + "set dscp DSCP$dscp", + SET_STR + "DSCP value for routing protocol\n" + "DSCP value\n") +{ + const char *xpath = "./set-action[action='frr-route-map:set-dscp']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), "%s/rmap-set-action/dscp", + xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, dscp); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_set_dscp, no_set_dscp_cmd, + "no set dscp [OPTVAL]", + NO_STR + SET_STR + "DSCP value for routing protocol\n" + "DSCP value\n") +{ + const char *xpath = "./set-action[action='frr-route-map:set-dscp']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + DEFUN_YANG (set_srte_color, set_srte_color_cmd, "set sr-te color (1-4294967295)", @@ -1103,6 +1136,9 @@ void route_map_action_show(struct vty *vty, const struct lyd_node *dnode, } else if (IS_SET_TAG(action)) { vty_out(vty, " set tag %s\n", yang_dnode_get_string(dnode, "rmap-set-action/tag")); + } else if (IS_SET_DSCP(action)) { + vty_out(vty, " set dscp %s\n", + yang_dnode_get_string(dnode, "./rmap-set-action/dscp")); } else if (IS_SET_SR_TE_COLOR(action)) { vty_out(vty, " set sr-te color %s\n", yang_dnode_get_string(dnode, @@ -1669,6 +1705,9 @@ void route_map_cli_init(void) install_element(RMAP_NODE, &set_tag_cmd); install_element(RMAP_NODE, &no_set_tag_cmd); + install_element(RMAP_NODE, &set_dscp_cmd); + install_element(RMAP_NODE, &no_set_dscp_cmd); + install_element(RMAP_NODE, &set_srte_color_cmd); install_element(RMAP_NODE, &no_set_srte_color_cmd); } diff --git a/lib/routemap_northbound.c b/lib/routemap_northbound.c index 1bba4dad47a6..ee1013cdd46e 100644 --- a/lib/routemap_northbound.c +++ b/lib/routemap_northbound.c @@ -1292,6 +1292,52 @@ lib_route_map_entry_set_action_tag_destroy(struct nb_cb_destroy_args *args) return lib_route_map_entry_set_destroy(args); } +/* + * XPath: /frr-route-map:lib/route-map/entry/set-action/dscp + */ +static int +lib_route_map_entry_set_action_dscp_modify(struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *dscp; + int rv; + + /* + * NOTE: validate if 'action' is 'dscp', currently it is not + * necessary because this is the only implemented action. Other + * actions might have different validations. + */ + if (args->event != NB_EV_APPLY) + return NB_OK; + + /* Check for hook function. */ + if (rmap_match_set_hook.set_dscp == NULL) + return NB_OK; + + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + dscp = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_shook = rmap_match_set_hook.no_set_dscp; + rhc->rhc_rule = "dscp"; + + rv = rmap_match_set_hook.set_dscp(rhc->rhc_rmi, "dscp", dscp, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + + return NB_OK; +} + +static int +lib_route_map_entry_set_action_dscp_destroy(struct nb_cb_destroy_args *args) +{ + return lib_route_map_entry_set_destroy(args); +} + /* * XPath: /frr-route-map:lib/route-map/entry/set-action/policy */ @@ -1537,6 +1583,13 @@ const struct frr_yang_module_info frr_route_map_info = { .destroy = lib_route_map_entry_set_action_tag_destroy, } }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/dscp", + .cbs = { + .modify = lib_route_map_entry_set_action_dscp_modify, + .destroy = lib_route_map_entry_set_action_dscp_destroy, + } + }, { .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/policy", .cbs = { diff --git a/yang/frr-route-map.yang b/yang/frr-route-map.yang index 7cb13b60f2fc..db325e8011b1 100644 --- a/yang/frr-route-map.yang +++ b/yang/frr-route-map.yang @@ -178,6 +178,12 @@ module frr-route-map { "Set tag"; } + identity set-dscp { + base rmap-set-type; + description + "Set dscp"; + } + identity set-sr-te-color { base rmap-set-type; description @@ -402,6 +408,15 @@ module frr-route-map { } } + case set-dscp { + when "derived-from-or-self(../action, 'set-dscp')"; + leaf dscp { + type string; + description + "Dscp value"; + } + } + case set-sr-te-color { when "derived-from-or-self(../action, 'set-sr-te-color')"; leaf policy { From 6122f6692be5ee7938d47ed357b5c69a06bc9387 Mon Sep 17 00:00:00 2001 From: Volodymyr Huti Date: Sat, 18 Feb 2023 17:11:43 +0200 Subject: [PATCH 3/6] bgpd: Initial QPPB plugin for interacting with BPF mappings Check the documentation for more details: https://phabricator.vyos.net/T4180 Signed-off-by: Volodymyr Huti --- bgpd/bgp_qppb_private.c | 85 ++++++++++++++ bgpd/bgp_xdp_qppb.c | 244 ++++++++++++++++++++++++++++++++++++++++ bgpd/bgp_zebra.c | 3 + bgpd/bgpd.h | 2 + bgpd/subdir.am | 7 ++ configure.ac | 15 +++ 6 files changed, 356 insertions(+) create mode 100644 bgpd/bgp_qppb_private.c create mode 100644 bgpd/bgp_xdp_qppb.c diff --git a/bgpd/bgp_qppb_private.c b/bgpd/bgp_qppb_private.c new file mode 100644 index 000000000000..50b96b90868f --- /dev/null +++ b/bgpd/bgp_qppb_private.c @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BGP QPPB support + * Copyright (C) 2023 VyOS Inc. + * Volodymyr Huti + */ + +#include +#include +#include +#include "bgpd/bgpd.h" +#include "log.h" +#include "prefix.h" +#include "privs.h" + +#define BPF_DSCP_MAP "dscp_map" +#define BPF_PIN_DIR "/sys/fs/bpf/" +/* + * struct bpf_lpm_trie_key { + * __u32 prefixlen; + * __u8 data[0]; + * }; + * data[0] - stretchy buf, sizeof() doesn`t count for it + */ +#define BPF_LPM_KEY_SIZE (sizeof(struct bpf_lpm_trie_key) + sizeof(__u32)) + +extern struct zebra_privs_t bgpd_privs; +static int dscp_map_fd; + +static int open_bpf_map_file(const char *pin_dir, const char *mapname) +{ + char filename[PATH_MAX]; + int len, fd; + + len = snprintf(filename, PATH_MAX, "%s/%s", pin_dir, mapname); + if (len < 0) { + zlog_err("Failed constructing BPF map path"); + return -1; + } + + fd = bpf_obj_get(filename); + if (fd < 0) + zlog_err("Failed to open bpf map file [%s - err(%d):%s]", + filename, errno, strerror(errno)); + return fd; +} + +static void bgp_qppb_map_init(void) +{ + const char *pin_dir = THIS_MODULE->load_args ?: BPF_PIN_DIR; + + dscp_map_fd = open_bpf_map_file(pin_dir, BPF_DSCP_MAP); +} + +static int bgp_qppb_mark_prefix(const struct prefix *p, uint8_t dscp, bool add) +{ + struct bpf_lpm_trie_key *key_ipv4; + int err = 0; + + if (dscp_map_fd < 0) + return err; + + key_ipv4 = alloca(BPF_LPM_KEY_SIZE); + key_ipv4->prefixlen = p->prefixlen; + memcpy(key_ipv4->data, &p->u.prefix4, sizeof(struct in_addr)); + + frr_with_privs (&bgpd_privs) { + err = add ? bpf_map_update_elem(dscp_map_fd, key_ipv4, &dscp, 0) + : bpf_map_delete_elem(dscp_map_fd, key_ipv4); + } + zlog_info("QPPB %s prefix [%pFX| dscp %d, err %d]", + add ? "mark" : "unmark", p, dscp, err); + return err; +} + +static int bgp_qppb_module_init(void) +{ + bgp_qppb_map_init(); + hook_register(bgp_qppb_mark_prefix, bgp_qppb_mark_prefix); + return 0; +} + +FRR_MODULE_SETUP(.name = "bgp_vyos_qppb", .version = "0.0.1", + .description = "bgp QPPB implementation for VyOS", + .init = bgp_qppb_module_init); diff --git a/bgpd/bgp_xdp_qppb.c b/bgpd/bgp_xdp_qppb.c new file mode 100644 index 000000000000..abd30a271574 --- /dev/null +++ b/bgpd/bgp_xdp_qppb.c @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * xdp_qppb.c + * XDP handlers to mark/classify traffic by QPPB plugin + * + * Copyright (C) 2023 VyOS Inc. + * Volodymyr Huti + */ + +#include +#include +#include +#include +#include + +/* REFERENCES: + * linux/samples/bpf/xdp_fwd_kernel.c + * linux/samples/bpf/xdp_router_ipv4.bpf.c + * linux/samples/bpf/xdp2skb_meta_kern.c + * xdp-tutorial/packet-solutions/xdp_prog_kern_03.c + * bcc/examples/networking/xdp_drop_count.py + * bcc/examples/networking/tc_perf_event.py + * xdp-cpumap-tc/src/tc_classify_kern.c + */ + +#if (!defined(MARK_SKB) && !defined(MARK_META)) +#error Specify marking mode to be used +#elif (defined(MARK_SKB) && defined(MARK_META)) +#error Specify single mode only +#elif (!defined(MODE_STR)) +#warn XXX : Poor config +#endif + +struct datarec { + __u64 rx_packets; + __u64 rx_bytes; +}; + +struct lpm_key4 { + __u32 prefixlen; + __u32 src; +}; + +union lpm_key4_u { + __u32 b32[2]; + __u8 b8[8]; +}; + +#if !defined(XDP_ACTION_MAX) +#define XDP_ACTION_MAX (XDP_REDIRECT + 1) +#endif +#if !defined(BPF_PIN_DIR) +#define BPF_PIN_DIR "/sys/fs/bpf" +#endif + +#define DSCP_PIN BPF_PIN_DIR "/dscp_map" +#define QPPB_PIN BPF_PIN_DIR "/qppb_mode_map" +#if !defined(IFACE) +#define STAT_PIN BPF_PIN_DIR "/xdp_stats_map" +#else +#define STAT_PIN BPF_PIN_DIR "/" IFACE "/xdp_stats_map" +#endif +// type : key : leaf : name : size : pin_dir : flags +BPF_TABLE_PINNED("percpu_array", u32, struct datarec, xdp_stats_map, + XDP_ACTION_MAX, STAT_PIN); +BPF_TABLE_PINNED("lpm_trie", struct lpm_key4, u8, dscp_map, 10240, DSCP_PIN, + BPF_F_NO_PREALLOC); +BPF_TABLE_PINNED("array", u32 /*iface_id*/, u32 /*qppb_bgp_policy*/, + qppb_mode_map, 64, QPPB_PIN); +// XXX: choose table size limits (read them from sysctl?) + +enum qppb_bgp_policy { + BGP_POLICY_NONE = 0, + BGP_POLICY_DST = 1, + BGP_POLICY_SRC = 2, + BGP_POLICY_MAX +}; + +static __always_inline __u32 xdp_stats_record_action(struct xdp_md *ctx, + u32 action) +{ + if (action >= XDP_ACTION_MAX) + return XDP_ABORTED; + + struct datarec *rec = xdp_stats_map.lookup(&action); + + if (!rec) + return XDP_ABORTED; + rec->rx_packets++; + rec->rx_bytes += (ctx->data_end - ctx->data); + return action; +} + +/* Taken from include/net/dsfield.h */ +static __always_inline void ipv4_change_dsfield(struct iphdr *iph, __u8 mask, + __u8 value) +{ + __u32 check = bpf_ntohs((__be16)iph->check); + __u8 dsfield; + + dsfield = (iph->tos & mask) | value; + check += iph->tos; + if ((check + 1) >> 16) + check = (check + 1) & 0xffff; + check -= dsfield; + check += check >> 16; /* adjust carry */ + iph->check = (__sum16)bpf_htons(check); + iph->tos = dsfield; +} + +struct meta_info { + __u8 mark; +} __attribute__((aligned(4))); + +int xdp_qppb(struct xdp_md *ctx) +{ + int rc, action = XDP_PASS; +#if defined(MARK_META) + struct meta_info *meta; + + rc = bpf_xdp_adjust_meta(ctx, -(int)sizeof(*meta)); + if (rc < 0) + goto aborted; +#endif + + void *data = (void *)(long)ctx->data; + void *data_end = (void *)(long)ctx->data_end; + struct iphdr *iph = data + sizeof(struct ethhdr); + __u64 nh_off = sizeof(struct ethhdr); + __u32 ifindex = ctx->ingress_ifindex; + union lpm_key4_u key4; + __u8 *mark, qppb_mode; + __u32 *qppb_mkey; + __be16 h_proto; + + if (data + nh_off > data_end) + goto drop; + if ((void *)(iph + 1) > data_end) + goto drop; +#if defined(MARK_META) + meta = (void *)(long)ctx->data_meta; + if ((void *)(meta + 1) > data) + goto aborted; +#endif + qppb_mkey = qppb_mode_map.lookup(&ifindex); + qppb_mode = qppb_mkey ? *qppb_mkey : BGP_POLICY_NONE; + // skip if bgp mode was not configured + if (qppb_mode == BGP_POLICY_NONE) + goto skip; + + h_proto = ((struct ethhdr *)data)->h_proto; + if (h_proto != bpf_htons(ETH_P_IP) || iph->ttl <= 1) + goto skip; +#if defined(RESPECT_TOS) + if (iph->tos) { +#if defined(MARK_META) + meta->mark = iph->tos; +#if defined(LOG_QPPB) + bpf_trace_printk("XDP ignore marked packet [%d|%d]", iph->tos, + meta->mark); +#endif +#endif + goto skip; + } +#endif + key4.b32[0] = 32; + switch (qppb_mode) { + case BGP_POLICY_DST: + key4.b8[4] = iph->daddr & 0xff; + key4.b8[5] = (iph->daddr >> 8) & 0xff; + key4.b8[6] = (iph->daddr >> 16) & 0xff; + key4.b8[7] = (iph->daddr >> 24) & 0xff; + break; + case BGP_POLICY_SRC: + key4.b8[4] = iph->saddr & 0xff; + key4.b8[5] = (iph->saddr >> 8) & 0xff; + key4.b8[6] = (iph->saddr >> 16) & 0xff; + key4.b8[7] = (iph->saddr >> 24) & 0xff; + break; + default: + goto out; + } + + mark = dscp_map.lookup((struct lpm_key4 *)&key4); + if (!mark) + goto out; +#if defined(MARK_SKB) + ipv4_change_dsfield(iph, 0, *mark); +#elif defined(MARK_META) + meta->mark = *mark; +#endif +#if defined(LOG_QPPB) + bpf_trace_printk("XDP Mark detected [%d]\n", *mark); +#endif +out: + return xdp_stats_record_action(ctx, action); +drop: + return xdp_stats_record_action(ctx, XDP_DROP); +aborted: + return xdp_stats_record_action(ctx, XDP_ABORTED); // packet is dropped +skip: + return action; +} + +int xdp_tc_mark(struct __sk_buff *skb) +{ + void *data = (void *)(long)skb->data; + void *data_meta = (void *)(long)skb->data_meta; + struct meta_info *meta = data_meta; + + // Default priority + skb->tc_classid = 0x50; + // Check XDP gave us some data_meta + if ((void *)(meta + 1) > data) + return TC_ACT_OK; + if (!meta->mark) + return TC_ACT_OK; + + /* skb->mark = meta->mark; // Firewall fw mark */ + /* skb->priority = meta->mark; */ + switch (meta->mark >> 2) { + case 10: + skb->tc_classid = 0x10; + break; + case 20: + skb->tc_classid = 0x20; + break; + case 30: + skb->tc_classid = 0x30; + break; + case 40: + skb->tc_classid = 0x40; + break; +defaut: + break; + } + +#if defined(LOG_TC) + bpf_trace_printk("TC Mark detected [%d|%d|%d]", meta->mark, + meta->mark >> 2, skb->tc_classid); +#endif + return TC_ACT_OK; +} diff --git a/bgpd/bgp_zebra.c b/bgpd/bgp_zebra.c index c94c79b094b6..24ac1fac6ea0 100644 --- a/bgpd/bgp_zebra.c +++ b/bgpd/bgp_zebra.c @@ -63,6 +63,8 @@ static bool bgp_zebra_label_manager_connect(void); /* hook to indicate vrf status change for SNMP */ DEFINE_HOOK(bgp_vrf_status_changed, (struct bgp *bgp, struct interface *ifp), (bgp, ifp)); +DEFINE_HOOK(bgp_qppb_mark_prefix, + (const struct prefix *p, uint8_t dscp, bool add), (p, dscp, add)); DEFINE_MTYPE_STATIC(BGPD, BGP_IF_INFO, "BGP interface context"); @@ -1670,6 +1672,7 @@ bgp_zebra_announce_actual(struct bgp_dest *dest, struct bgp_path_info *info, zlog_debug("%s: %pFX: announcing to zebra (recursion %sset)", __func__, p, (recursion_flag ? "" : "NOT ")); } + hook_call(bgp_qppb_mark_prefix, p, dscp, is_add); return zclient_route_send(is_add ? ZEBRA_ROUTE_ADD : ZEBRA_ROUTE_DELETE, zclient, &api); } diff --git a/bgpd/bgpd.h b/bgpd/bgpd.h index c0fefd53baa7..859073aac4e6 100644 --- a/bgpd/bgpd.h +++ b/bgpd/bgpd.h @@ -2753,6 +2753,8 @@ DECLARE_HOOK(bgp_rpki_prefix_status, (struct peer * peer, struct attr *attr, const struct prefix *prefix), (peer, attr, prefix)); +DECLARE_HOOK(bgp_qppb_mark_prefix, + (const struct prefix *p, uint8_t dscp, bool add), (p, dscp, add)); void peer_nsf_stop(struct peer *peer); diff --git a/bgpd/subdir.am b/bgpd/subdir.am index 6d6fad00745f..e55c2d52372e 100644 --- a/bgpd/subdir.am +++ b/bgpd/subdir.am @@ -9,6 +9,9 @@ noinst_PROGRAMS += bgpd/bgp_btoa vtysh_daemons += bgpd +if QPPB +module_LTLIBRARIES += bgpd/bgpd_qppb.la +endif if SNMP module_LTLIBRARIES += bgpd/bgpd_snmp.la endif @@ -206,6 +209,10 @@ bgpd_bgpd_bmp_la_SOURCES = bgpd/bgp_bmp.c bgpd_bgpd_bmp_la_LIBADD = lib/libfrrcares.la bgpd_bgpd_bmp_la_LDFLAGS = $(MODULE_LDFLAGS) +bgpd_bgpd_qppb_la_SOURCES = bgpd/bgp_qppb_private.c +bgpd_bgpd_qppb_la_LIBADD = lib/libfrrcares.la +bgpd_bgpd_qppb_la_LDFLAGS = $(MODULE_LDFLAGS) -lbpf + clippy_scan += \ bgpd/bgp_bmp.c \ bgpd/bgp_debug.c \ diff --git a/configure.ac b/configure.ac index f11b345cf6ef..3e8a9827b8ed 100644 --- a/configure.ac +++ b/configure.ac @@ -749,6 +749,8 @@ AC_ARG_ENABLE([bgp-bmp], AS_HELP_STRING([--disable-bgp-bmp],[turn off BGP BMP support])) AC_ARG_ENABLE([snmp], AS_HELP_STRING([--enable-snmp], [enable SNMP support for agentx])) +AC_ARG_ENABLE([qppb], + AS_HELP_STRING([--enable-qppb], [enable QPPB support for BGP (requires libbpf)])) AC_ARG_ENABLE([config_rollbacks], AS_HELP_STRING([--enable-config-rollbacks], [enable configuration rollbacks (requires sqlite3)])) AC_ARG_ENABLE([sysrepo], @@ -2079,6 +2081,18 @@ if test "$enable_config_rollbacks" = "yes"; then ]) fi +dnl --------------- +dnl configuration QPPB +dnl --------------- +QPPB=false +if test "$enable_qppb" = "yes"; then + PKG_CHECK_MODULES([LIBBPF], [libbpf], [ + QPPB=true + ], [ + AC_MSG_ERROR([--enable-qppb given but libbpf was not found on your system.]) + ]) +fi + dnl --------------- dnl sysrepo dnl --------------- @@ -2748,6 +2762,7 @@ AM_CONDITIONAL([ENABLE_BGP_VNC], [test "$enable_bgp_vnc" != "no"]) AM_CONDITIONAL([BGP_BMP], [$bgpd_bmp]) dnl northbound AM_CONDITIONAL([SQLITE3], [$SQLITE3]) +AM_CONDITIONAL([QPPB], [$QPPB]) AM_CONDITIONAL([SYSREPO], [test "$enable_sysrepo" = "yes"]) AM_CONDITIONAL([GRPC], [test "$enable_grpc" = "yes"]) AM_CONDITIONAL([ZEROMQ], [test "$ZEROMQ" = "true"]) From c42708e50b2f7f5ffea30a27fe013a4ca41289e8 Mon Sep 17 00:00:00 2001 From: Volodymyr Huti Date: Wed, 4 Oct 2023 18:13:40 +0300 Subject: [PATCH 4/6] tests: Rework IPerf/Tcpdump host helpers Convert the iperf/tcpdump helper functions into HostApplicationHelper interface. Signed-off-by: Volodymyr Huti --- tests/topotests/lib/common_config.py | 241 +++++++++++++++------------ 1 file changed, 134 insertions(+), 107 deletions(-) diff --git a/tests/topotests/lib/common_config.py b/tests/topotests/lib/common_config.py index 598db84e636b..202872586c86 100644 --- a/tests/topotests/lib/common_config.py +++ b/tests/topotests/lib/common_config.py @@ -132,10 +132,6 @@ ], } -g_iperf_client_procs = {} -g_iperf_server_procs = {} - - def is_string(value): try: return isinstance(value, basestring) @@ -4725,127 +4721,158 @@ def check_procs(self): class IPerfHelper(HostApplicationHelper): def __str__(self): - return "IPerfHelper()" + return "IPerfHelper({})".format(self.iperf_path) - def run_join( + def __init__(self, tgen=None): + super(IPerfHelper, self).__init__(tgen) + self.iperf_path = tgen.net.get_exec_path("iperf3") + logger.debug("IPerfHelper initialized...") + + def iperf( self, host, - join_addr, - l4Type="UDP", - join_interval=1, - join_intf=None, - join_towards=None, + length=0, + timeout=0, + dst=None, + port=None, + dscp=None, + json=False, + server=False, + oneoff=False, + bind_addr=None, + background=False, ): """ - Use iperf to send IGMP join and listen to traffic - - Parameters: - ----------- - * `host`: iperf host from where IGMP join would be sent - * `l4Type`: string, one of [ TCP, UDP ] - * `join_addr`: multicast address (or addresses) to join to - * `join_interval`: seconds between periodic bandwidth reports - * `join_intf`: the interface to bind the join to - * `join_towards`: router whos interface to bind the join to - - returns: Success (bool) + Run iperf ... + Return: Success ;) """ + log_name = "iperf_{}_{}_".format(host.name, dst if dst else "") + log_name += "server.log" if server else "client.json" + log_file = "{}/{}".format(self.tgen.logdir, log_name) + + iperf_args = [] + if background: + iperf_args.append("nohup &>" + log_file) + if timeout > 0: + iperf_args.append("timeout {}".format(timeout)) + + iperf_args.append(self.iperf_path) + if server: + iperf_args.append("-s") + else: + iperf_args.append("-c") + iperf_args.append(dst) + + if length and not timeout: + iperf_args.append("-t") + iperf_args.append(str(length)) + if port: + iperf_args.append("-p") + iperf_args.append(str(port)) + if oneoff: + iperf_args.append("-1") + if json: + iperf_args.append("-J") + + if bind_addr: + iperf_args.append("-B") + iperf_args.append(bind_addr) + if dscp: + iperf_args.append("--dscp") + iperf_args.append(str(dscp)) + if background: + iperf_args.append("&") - iperf_path = self.tgen.net.get_exec_path("iperf") - - assert join_addr - if not isinstance(join_addr, list) and not isinstance(join_addr, tuple): - join_addr = [ipaddress.IPv4Address(frr_unicode(join_addr))] - - for bindTo in join_addr: - iperf_args = [iperf_path, "-s"] - - if l4Type == "UDP": - iperf_args.append("-u") + # XXX: installing `bash -c` as base_cmd doesn't work + cmd = ["bash", "-c", " ".join(iperf_args)] + return self.run(host.name, cmd) - iperf_args.append("-B") - if join_towards: - to_intf = frr_unicode( - self.tgen.json_topo["routers"][host]["links"][join_towards][ - "interface" - ] - ) - iperf_args.append("{}%{}".format(str(bindTo), to_intf)) - elif join_intf: - iperf_args.append("{}%{}".format(str(bindTo), join_intf)) - else: - iperf_args.append(str(bindTo)) - if join_interval: - iperf_args.append("-i") - iperf_args.append(str(join_interval)) +class TcpDumpHelper(HostApplicationHelper): + def __str__(self): + return "TcpdumpHelper({},{},{})".format( + self.protocol, self.options, self.cap_file + ) - p = self.run(host, iperf_args) - if p.poll() is not None: - logger.error("IGMP join failed on %s: %s", bindTo, comm_error(p)) - return False - return True + def __init__( + self, + tgen=None, + protocol=None, + options="-A -vv", + cap_file="ping_test.txt", + ): + super(TcpDumpHelper, self).__init__(tgen) + self.protocol = protocol + self.options = options + self.cap_file = cap_file + logger.debug("TcpdumpHelper initialized ...") - def run_traffic( - self, host, sentToAddress, ttl, time=0, l4Type="UDP", bind_towards=None + def capture_start( + self, + host, + intf, + timeout=0, + background=False, ): """ - Run iperf to send IGMP join and traffic - - Parameters: - ----------- - * `host`: iperf host to send traffic from - * `l4Type`: string, one of [ TCP, UDP ] - * `sentToAddress`: multicast address to send traffic to - * `ttl`: time to live - * `time`: time in seconds to transmit for - * `bind_towards`: Router who's interface the source ip address is got from - - returns: Success (bool) + API to capture network packets using tcp dump. """ + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + cmd = ["bash", "-c"] + host = host.name + cmdargs = [] + + tcpdump_path = self.tgen.net.get_exec_path("tcpdump") + log_file = os.path.join(self.tgen.logdir, host, self.cap_file) + if timeout > 0: + cmdargs.append("timeout {}".format(timeout)) + + cmdargs.append(tcpdump_path) + if intf: + cmdargs.append("-i {}".format(intf)) + if self.protocol: + cmdargs.append("'%s'" % self.protocol) + if self.options: + cmdargs.append("-s 0 {}".format(self.options)) + + if not background: + cmd.append(cmdargs) + else: + tcpdump_args = ["nohup &>" + log_file, *cmdargs, "&"] + cmd.append(" ".join(tcpdump_args)) - iperf_path = self.tgen.net.get_exec_path("iperf") - - if sentToAddress and not isinstance(sentToAddress, list): - sentToAddress = [ipaddress.IPv4Address(frr_unicode(sentToAddress))] - - for sendTo in sentToAddress: - iperf_args = [iperf_path, "-c", sendTo] + logger.info("Running tcpdump command: " + str(cmd)) + return self.run(host, cmd) - # Bind to Interface IP - if bind_towards: - ifaddr = frr_unicode( - self.tgen.json_topo["routers"][host]["links"][bind_towards]["ipv4"] - ) - ipaddr = ipaddress.IPv4Interface(ifaddr).ip - iperf_args.append("-B") - iperf_args.append(str(ipaddr)) - - # UDP/TCP - if l4Type == "UDP": - iperf_args.append("-u") - iperf_args.append("-b") - iperf_args.append("0.012m") - - # TTL - if ttl: - iperf_args.append("-T") - iperf_args.append(str(ttl)) - - # Time - if time: - iperf_args.append("-t") - iperf_args.append(str(time)) - - p = self.run(host, iperf_args) - if p.poll() is not None: - logger.error( - "mcast traffic send failed for %s: %s", sendTo, comm_error(p) + def find_msg(self, host, message, count=0): + """ + API to find messages in tcpdump capture file + """ + matches = 0 + host = host.name + filepath = os.path.join(self.tgen.logdir, host, self.cap_file) + with open(filepath) as f: + matches = len(re.findall("{}".format(message), f.read())) + if count and matches < count: + errormsg = ( + "[DUT: %s]: Verify Message: %s in tcpdump " + "[%s!=%s FAILED!!]" + % ( + host, + count, + matches, + message, + ) ) - return False - - return True + return errormsg, matches + logger.info( + "[DUT: %s]: Found message: %s in tcpdump " " count: %s [PASSED!!]", + host, + message, + matches, + ) + return matches != 0, matches def verify_ip_nht(tgen, input_dict): """ From 23c3a4daf9b76daa7136903a62b1be4060f38714 Mon Sep 17 00:00:00 2001 From: Volodymyr Huti Date: Fri, 20 Jan 2023 02:03:43 +0200 Subject: [PATCH 5/6] tests: Implement test suit with common setups, based on BCC Check the documentation for more details: https://phabricator.vyos.net/T4180 Signed-off-by: Volodymyr Huti --- tests/topotests/bgp_qppb_flow/__init__.py | 201 ++++++ tests/topotests/bgp_qppb_flow/bgp_ipv4_nh.ref | 10 + tests/topotests/bgp_qppb_flow/bgp_xdp_qppb.c | 1 + .../topotests/bgp_qppb_flow/test_bgp_qppb.py | 580 ++++++++++++++++++ tests/topotests/bgp_qppb_flow/topo_cisco.json | 383 ++++++++++++ tests/topotests/lib/common_config.py | 18 +- tests/topotests/lib/topogen.py | 10 +- tests/topotests/lib/topotest.py | 21 +- 8 files changed, 1217 insertions(+), 7 deletions(-) create mode 100755 tests/topotests/bgp_qppb_flow/__init__.py create mode 100644 tests/topotests/bgp_qppb_flow/bgp_ipv4_nh.ref create mode 120000 tests/topotests/bgp_qppb_flow/bgp_xdp_qppb.c create mode 100644 tests/topotests/bgp_qppb_flow/test_bgp_qppb.py create mode 100644 tests/topotests/bgp_qppb_flow/topo_cisco.json diff --git a/tests/topotests/bgp_qppb_flow/__init__.py b/tests/topotests/bgp_qppb_flow/__init__.py new file mode 100755 index 000000000000..3b9ac16575ca --- /dev/null +++ b/tests/topotests/bgp_qppb_flow/__init__.py @@ -0,0 +1,201 @@ +#!/usr/bin/env python +# +# SPDX-License-Identifier: ISC +# Copyright (c) 2023 VyOS Inc. +# Volodymyr Huti +# + +import os +import sys +import json +import pytest + +from lib.topolog import logger +from lib.common_config import ( + start_router_daemons, + kill_router_daemons, +) + +from bcc import BPF, DEBUG_PREPROCESSOR, DEBUG_SOURCE, DEBUG_BPF, DEBUG_BTF +from pyroute2.netns import pushns, popns +from pyroute2 import IPRoute +from ctypes import Structure, c_int, c_uint, c_ubyte +from enum import Enum + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# os.environ["PYTHONBREAKPOINT"] = "pudb.set_trace" +DEV_DEBUG = False + + +class BgpPolicy(Enum): + NONE = c_int(0) + Dst = c_int(1) + Src = c_int(2) + + +class XdpMode(str, Enum): + META = "MARK_META" + SKB = "MARK_SKB" + + +class KeyV4(Structure): + _fields_ = [("prefixlen", c_uint), ("data", c_ubyte * 4)] + + +def router_attach_xdp(rnode, iface): + """ + - swap netns to rnode, + - attach `xdp_qppb` to `iface` + - switch back to root ns + """ + ns = "/proc/%d/ns/net" % rnode.net.pid + qppb_fn = rnode.bpf.funcs[b"xdp_qppb"] + + pushns(ns) + logger.debug("Attach XDP handler '{}'\nNetNS --> {})".format(iface, ns)) + rnode.bpf.attach_xdp(iface, qppb_fn, BPF.XDP_FLAGS_DRV_MODE) + popns() + + +def router_remove_xdp(rnode, iface): + pushns("/proc/%d/ns/net" % rnode.net.pid) + logger.debug("Removing XDP handler for {}:{}".format(rnode.name, iface)) + rnode.bpf.remove_xdp(iface) + popns() + + +def load_qppb_plugin(tgen, rnode, mode=XdpMode.SKB, debug_on=DEV_DEBUG): + """ + Initialize rnode XDP hooks and BPF mapping handlers + - compile xdp handlers from `xdp_qppb.c` in specified `mode` + - load `xdp_qppb` and `xdp_tc_mark` hooks + - restart router with QPPB plugin + + Parameters + ---------- + * `tgen`: topogen object + * `rnode`: router object + * `mode`: xdp processing mode required + * `debug_on`: enable debug logs for bpf compilation / xdp handlers + + Usage + --------- + load_qppb_plugin(tgen, r1, mode=XdpMode.META) + Returns -> None (XXX) + """ + debug_flags = DEBUG_BPF | DEBUG_PREPROCESSOR | DEBUG_SOURCE | DEBUG_BTF + debug = debug_flags if debug_on else 0 + src_file = CWD + "/bgp_xdp_qppb.c" + bpf_flags = [ + '-DMODE_STR="{}"'.format(mode), + "-D{}".format(mode.value), + "-DRESPECT_TOS", + "-w", + ] + if debug_on: + bpf_flags.append("-DLOG_QPPB") + bpf_flags.append("-DLOG_TC") + + try: + logger.info("Preparing the XDP src: " + src_file) + b = BPF(src_file=src_file.encode(), cflags=bpf_flags, debug=debug) + + logger.info("Loading XDP hooks -- xdp_qppb, xdp_tc_mark") + b.load_func(b"xdp_qppb", BPF.XDP) + b.load_func(b"xdp_tc_mark", BPF.SCHED_CLS) + rnode.bpf = b + except Exception as e: + pytest.skip("Failed to configure XDP environment -- \n" + str(e)) + + qppb_module = "-M vyos_qppb" + logger.info( + "Restart {}, XDP hooks loading...\nPlugin :: {}".format(rnode.name, qppb_module) + ) + kill_router_daemons(tgen, rnode.name, ["bgpd"]) + start_router_daemons(tgen, rnode.name, ["bgpd"], {"bgpd": qppb_module}) + + +def tc_bpf_filter(rnode, ifid): + "Attach tc bpf filter, depends on pyroute2 package" + tc_fn = rnode.bpf.funcs[b"xdp_tc_mark"] + rnode_ns = "/proc/{}/ns/net".format(rnode.net.pid) + + logger.debug("Attach TC-BPF handler '{}'\nNetNS --> {})".format(ifid, rnode_ns)) + # ip.tc("add", "clsact", ifid, "1:") + pushns(rnode_ns) + ip = IPRoute() + ip.tc( + "add-filter", + "bpf", + ifid, + 20, # XXX: + fd=tc_fn.fd, + name=tc_fn.name, + parent=0x10000, + classid=0x10030, # XXX: should be default? default is taken from htb + direct_action=True, + ) + popns() + + +def assert_bw(out, bw_target, tolerance, time=10): + "Assert that connection matches BW in Mbits +- %tolerance" + _min = bw_target * (1 - tolerance) + _max = bw_target * (1 + tolerance) + half_samples = time / 2 + data = json.loads(out) + bws = [] + + for sample in data["intervals"]: + bits = int(sample["sum"]["bits_per_second"]) + mbits = bits / 1024 / 1024 + bw = mbits / 8 + logger.debug("BW sample [{} <= {} <= {}]".format(_min, bw, _max)) + if _min <= bw <= _max: + bws.append(bw) + + _len = len(bws) + assert ( + _len >= half_samples + ), "Only {} samples are within targeted BW [{}:{}%]".format( + _len, bw_target, tolerance * 100 + ) + + +def bpf_print_trace(b): + "XXX: Call this from debugger, to avoid blocking / IO collissions" + logger.info("=" * 40) + logger.debug("Dump bpf log buffer:\n") + line = b.trace_readline(nonblocking=True) + while line: + logger.debug(line) + line = b.trace_readline(nonblocking=True) + + +def tc(rnode, cmd): + logger.debug("TC cmd: " + cmd) + return rnode.cmd_raises("tc " + cmd) + + +def tc_check(host, cmds): + tcoutputs = [tc(host, cmd) for cmd in cmds] + for output in tcoutputs: + if output != "": + logger.debug("TC: " + output) + + +def tc_log_stats(host, iface): + if not DEV_DEBUG: + return + tc_flags = "-g -s -d -p -col" + tc_check( + host, + [ + tc_flags + " filter ls dev " + iface, + tc_flags + " class ls dev " + iface, + tc_flags + " qdisc ls ", + ], + ) diff --git a/tests/topotests/bgp_qppb_flow/bgp_ipv4_nh.ref b/tests/topotests/bgp_qppb_flow/bgp_ipv4_nh.ref new file mode 100644 index 000000000000..4db9698d2168 --- /dev/null +++ b/tests/topotests/bgp_qppb_flow/bgp_ipv4_nh.ref @@ -0,0 +1,10 @@ +BGP routing table entry for 10.61.0.1/32, version XX +Paths: (1 available, best #1, table default) + Advertised to non peer-group peers: + 10.0.0.2 + 10 60 + 10.0.0.2 from 10.0.0.2 (1.0.2.17) + Origin incomplete, valid, external, best (First path received) + Community: 60:1 + QOS: Precedence af11 (10) + Last update: XXXX diff --git a/tests/topotests/bgp_qppb_flow/bgp_xdp_qppb.c b/tests/topotests/bgp_qppb_flow/bgp_xdp_qppb.c new file mode 120000 index 000000000000..b12e30f0f728 --- /dev/null +++ b/tests/topotests/bgp_qppb_flow/bgp_xdp_qppb.c @@ -0,0 +1 @@ +../../../bgpd/bgp_xdp_qppb.c \ No newline at end of file diff --git a/tests/topotests/bgp_qppb_flow/test_bgp_qppb.py b/tests/topotests/bgp_qppb_flow/test_bgp_qppb.py new file mode 100644 index 000000000000..ff55a18cb591 --- /dev/null +++ b/tests/topotests/bgp_qppb_flow/test_bgp_qppb.py @@ -0,0 +1,580 @@ +#!/usr/bin/env python +# +# SPDX-License-Identifier: ISC +# +# test_bgp_qppb.py +# +# Copyright (c) 2023 VyOS Inc. +# Volodymyr Huti +# + +""" +Test QPPB plugin functionality: +- verify bpf map manipulations affect xdp processing properly +- dscp tag is displayed for nexthop entry +- QOS setup balances the traffic throughput via plugin +- LPM overlapping setup + +TODO: +- redirection to different l3 iface based on configured marking +- layer 3 devices are functional +- fragmentation/scalability +""" + +import pytest + +pytestmark = [pytest.mark.bgpd] + +""" +test_bgp_qppb.py: + + 20...1 + +------+ 20...2 + | h1 |----------+ AS30 AS10 AS10 + +------+ ++------+ +------+ +------+ + | | | | | | + +------+ 20..1.2 | R1 | | R2 | | R3 | + | h2 |-----------| |------| |------| | + +------+ +------+ +------+ +------+ + 20..1.1 lo:1.0.1.17 lo:1.0.2.17 | lo:1.0.3.17 + QPPB Router | + | + lo: +------+ + | 1.0.4.17 | | + | 10.61.0.1 | R4 | + | ***** | | + | 10.66.0.1 +------+ + AS60 +""" + +import os +import re +import sys +import time +import functools +import subprocess + +from lib import topotest +from lib.topolog import logger +from lib.topojson import build_config_from_json +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.bgp import verify_bgp_convergence +from lib.common_config import ( + create_debug_log_config, + apply_raw_config, + start_topology, + TcpDumpHelper, + IPerfHelper, + step, +) + +from bgp_qppb_flow import * +from lib.topotest import version_cmp, interface_to_ifindex +import ctypes + + +xdp_ifindex = lambda host, iface: c_uint(interface_to_ifindex(host, iface)) +af21_tag = c_ubyte(0x12) +af12_tag = c_ubyte(0x0C) +zero_tag = c_ubyte(0) + + +# Helpers +# ------------------------------------------------------- +def setup_test_hosts(tgen, router): + """ + Setup client hosts to test traffic forwarding + NOTE, networks are overlaping for the purpose of lpm_overlap TC + privateDirs empty, so you can str(host) + """ + h1 = tgen.add_host("h1", "20.0.0.1", "dev h1-eth0", private_mounts="") + h2 = tgen.add_host("h2", "20.0.1.1", "dev h2-eth0", private_mounts="") + router.add_link(h1) + router.add_link(h2) + tgen.net.configure_hosts() + + ip_cmd = "ip addr add {} {}" + # XXX: will be used for overlap testing + router.cmd_raises(ip_cmd.format("20.0.0.2/16", "dev " + router.name + "-eth0")) + router.cmd_raises(ip_cmd.format("20.0.1.2/24", "dev " + router.name + "-eth1")) + # XXX: do we really need this? + router.cmd_raises("sysctl -w net.ipv4.conf.all.proxy_arp=1") + + +def check_ping4(rnode, dst, connected=True, src=None, tos=None, count=10, timeout=0): + ping = "" + if timeout: + ping = "timeout {} ".format(timeout) + ping += "ping {} -c{}".format(dst, count) + if src: + ping = "{} -I{}".format(ping, src) + if tos: + ping = "{} -Q{}".format(ping, src) + + match = ", {} packet loss".format("100%" if connected else "0%") + logger.info( + "[+] {} ping -> {}, connection expected -> {}".format( + rnode, dst, "up" if connected else "down" + ) + ) + logger.debug("Executing the ping -> {}".format(ping)) + + def _match_missing(rnode, dst, match): + output = rnode.run(ping) + logger.info(output) + return match not in output + + func = functools.partial(_match_missing, rnode, dst, match) + success, result = topotest.run_and_expect(func, True, count, wait=1) + assert result is True + + +# Module +# ------------------------------------------------------- +def teardown_module(_mod): + "Teardown the pytest environment" + tgen = get_topogen() + tgen.stop_topology() + # iperf_helper.cleanup() + # tcpdumpf_helper.cleanup() + + +def setup_module(mod): + # XXX: write down [ requirement:verion, ... ] + # result |= required_linux_kernel_version("5+") + # result |= required_linux_kernel_features("BPF") + # result |= required_package_version(bcc, dev) + # ... + # if result is not True: + # pytest.skip("Kernel requirements are not met") + # XXX(?): verify that user XPD env doesn't overlap with test + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + json_file = f"{CWD}/topo_cisco.json" + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + + start_topology(tgen) + build_config_from_json(tgen, topo) + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + global BGP_CONVERGENCE + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + # Extra setup steps + # ----------------------------------------------------------------------- + r4 = tgen.gears["r4"] + r1 = tgen.gears["r1"] + + debug_rmap_dict = {"r1": {"raw_config": ["end", "debug route-map"]}} + debug_config_dict = { + "r1": {"debug": {"log_file": "debug.log", "enable": ["bgpd", "zebra"]}} + } + if DEV_DEBUG: + create_debug_log_config(tgen, debug_config_dict) + apply_raw_config(tgen, debug_rmap_dict) + + setup_test_hosts(tgen, r1) + r1.vtysh_cmd( + """ + configure + router bgp 30 + table-map QPPB + """ + ) + # each address will receive different marking (tier of preference) + lo_ip_add = "ip address add dev lo 10.6{0}.0.1/32" + [r4.cmd_raises(lo_ip_add.format(n)) for n in range(1, 7)] + + # Initializing BPF objects + # ----------------------------------------------------------------------- + # NOTE: we need to switch mnt namespace to instantiate BPF mappings + # XXX: python3.12 introduces os.setns, for now use libc directly + ns = "/proc/%d/ns/mnt" % r1.net.pid + nsfd = os.open(ns, os.O_RDONLY) + + libc = ctypes.CDLL("libc.so.6", use_errno=True) + libc.setns(nsfd, 0) + + r1.cmd_raises( + """ + mkdir -p /sys/fs/bpf + mount -t bpf bpf /sys/fs/bpf + """ + ) + load_qppb_plugin(tgen, r1) + + +# Test Cases +# ------------------------------------------------------- +# @pytest.mark.skip +def test_xdp_lpm(tgen): + """ + Manually setup the XDP mappings, without route destribution + Assume that H1 is pinging the managment interface on R4 [lo(1.0.4.17)] + The R1 is marking/forwarding based on QPPB mappings: + qppb_router + h1 -> [ R1 ] -> .... -> R4 + eth0 r1-r2-eth0 + The packet marking happens as follows: + ----------------------------------------- + xdp_qppb(xdp_md *skb): + switch qppb_map[iif]: // idx for eth0 + BgpPolicy.Src: mark = dscp_map[(skb.src, 32)] + BgpPolicy.Dst: mark = dscp_map[(skb.dst, 32)] + NONE: return pass + + if MARK_SKB: skb->tos = mark + if MARK_META: skb->classid = mark + return pass + ----------------------------------------- + """ + h1 = tgen.gears["h1"] + r1 = tgen.gears["r1"] + r4 = tgen.gears["r4"] + + qppb_map = r1.bpf[b"qppb_mode_map"] + dscp_map = r1.bpf[b"dscp_map"] + + r1_eth0_idx = xdp_ifindex(r1, "r1-eth0") + qppb_map[r1_eth0_idx] = BgpPolicy.Dst.value + router_attach_xdp(r1, b"r1-eth0") + + # -------------------------------------------------------------------------------- + tcpdump = TcpDumpHelper(tgen, "icmp[0] == 8") # ICMP Echo requst + r4_lo_key = KeyV4(32, (1, 0, 4, 17)) + h1_key = KeyV4(32, (20, 0, 0, 1)) + r4_lo_ip = "1.0.4.17" + PINGS = 10 + + def _check(sender, receiver, dst, cap_iface, tos, src=None, ping_tos=None): + p1 = tcpdump.capture_start(receiver, cap_iface, background=True, timeout=PINGS) + assert p1, "Failed to run tcpdump on {}:\n{}".format(sender.name, p1) + + check_ping4(sender, dst, src=src, count=PINGS, timeout=PINGS, tos=ping_tos) + time.sleep(1.5) + return tcpdump.find_msg(receiver, "tos 0x%x" % tos.value) + + check_connection = functools.partial(_check, h1, r4, r4_lo_ip, "r4-r3-eth0") + h1.run("ping -c 3 -w 3 " + r4_lo_ip) # refresh arp cache, etc ... + time.sleep(2) + # -------------------------------------------------------------------------------- + dscp_map[r4_lo_key] = af21_tag + found, matches = check_connection(af21_tag) + assert found and matches >= ( + PINGS - 1 # XXX: first packet is not tagged - caching issues? + ), "LPM doesn't work as expected, mark detected only {} times ".format(matches) + + # -------------------------------------------------------------------------------- + dscp_map[r4_lo_key] = af12_tag + found, matches = check_connection(af12_tag) + assert found and matches >= ( + PINGS - 1 + ), "LPM doesn't work as expected, mark detected only {} times ".format(matches) + + # --------------------------------------------------------------------------------- + dscp_map[h1_key] = af12_tag + dscp_map[r4_lo_key] = zero_tag + qppb_map[r1_eth0_idx] = BgpPolicy.Src.value + found, matches = check_connection(af12_tag) + assert found and matches >= ( + PINGS - 1 + ), "LPM doesn't work as expected, mark detected only {} times ".format(matches) + + # -------------------------------------------------------------------------------- + # XXX: Run some flows into opposite directions + # XXX: Use ping with custom tos ... + # XXX: Try using invalid values, i.e. tos > 64 + # ... + # -------------------------------------------------------------------------------- + qppb_map.clear() + dscp_map[h1_key] = af21_tag + dscp_map[r4_lo_key] = af12_tag + found, _ = check_connection(af12_tag) + assert not found, "LPM misbehaviour, markings not expected after clearing dscp map" + + # cleanup used resources + router_remove_xdp(r1, b"r1-eth0") + dscp_map.pop(h1_key) + dscp_map.pop(r4_lo_key) + # XXX dscp_map.clear() - clears the initial config, used by the following test + # bpf_print_trace(bpf) + # breakpoint() + # -------------------------------------------------------------------------------- + + +def test_nh_dscp_displayed(tgen): + """ + Verify that QoS group is displayed for the marked prefix + """ + nhFile = "{}/bgp_ipv4_nh.ref".format(CWD) + expected = open(nhFile).read().rstrip() + expected = ("\n".join(expected.splitlines()) + "\n").rstrip() + + def check_dscp_displayed(): + r1 = tgen.gears["r1"] + actual = r1.vtysh_cmd("show bgp ipv4 10.61.0.1") + actual = ("\n".join(actual.splitlines()) + "\n").rstrip() + actual = re.sub(r" version [0-9]+", " version XX", actual) + actual = re.sub(r"Last update: .*", "Last update: XXXX", actual) + return topotest.get_textdiff( + actual, expected, title1="Actual bgp nh show", title2="Expected bgp nh show" + ) + + ok, result = topotest.run_and_expect(check_dscp_displayed, "", count=5, wait=1) + assert ok, result + + +def test_qos_topo(tgen): + """ + Setup QOS topology and verify traffic prioritization works as expected + + Steps: + --------------------------------------------- + - setup tc on qppb router (r1) + * 10Mbit htb queue + * bandwidth classes + - setup iperf servers + - choose processing mode SKB / META + * attach tc filters + - for SKB, use tc binary + - for META, use pyroute tc func + * run traffic in Dst mode + * run traffic with custom tos + verify, it is respected + * run traffic in Src mode + * flood link with different prio traffic + verify rebalancing works + + dscp | bw Mbytes + ------+------------ + 10 | 7.5 + 20 | 5.0 + 30 | 2.5 + 40 | 2.5 + * | 1 + --------------------------------------------- + Refences: + - ipmininet/tests/test_tc.py + - mininet/examples/test/test_simpleperf.py + - mininet/examples/test/test_intfoptions.py + - mininet/examples/test/test_walkthrough.py + - mininet/mininet/link.py -> class TCIntf + - http://luxik.cdi.cz/~devik/qos/htb/manual/userg.htm + --------------------------------------------- + """ + xdp_dscp = lambda x: c_ubyte(dscp_tos(x)) + dscp_tos = lambda x: x << 2 + h1 = tgen.gears["h1"] + r1 = tgen.gears["r1"] + r4 = tgen.gears["r4"] + + tc_egress_idx = interface_to_ifindex(r1, "r1-r2-eth0") + r1_eth0_idx = xdp_ifindex(r1, "r1-eth0") + qppb_map = r1.bpf[b"qppb_mode_map"] + dscp_map = r1.bpf[b"dscp_map"] + h1_key = KeyV4(24, (20, 0, 0, 1)) + R4_L0_61 = "10.61.0.1" + tolerance = 0.20 # 20% slippage, for short lived connection + TIME_OUT = 8 + bw = 7.5 + + # TC setup + # --------------------------------------------------------------- + _class = "class add dev r1-r2-eth0 parent 1:1 " + _filter = "filter add dev r1-r2-eth0 parent 1:0 " + u32_fmt = "prio %d protocol ip u32 match ip tos %d 0xff classid %s" + tc_setup = [ + "qdisc replace dev r1-r2-eth0 root handle 1:0 htb default 50", + "class add dev r1-r2-eth0 parent 1:0 classid 1:1 htb rate 10Mbps", + _class + "classid 1:10 htb rate 7.5Mbps", + _class + "classid 1:20 htb rate 5.0Mbps", + _class + "classid 1:30 htb rate 2.5Mbps", + _class + "classid 1:40 htb rate 0.5Mbps", + _class + "classid 1:50 htb rate 100kbps", + ] + tc_filters = [ + _filter + u32_fmt % (1, dscp_tos(10), "1:10"), + _filter + u32_fmt % (2, dscp_tos(20), "1:20"), + _filter + u32_fmt % (3, dscp_tos(30), "1:30"), + _filter + u32_fmt % (4, dscp_tos(40), "1:40"), + ] + + # Setup iperf server/client helpers + # --------------------------------------------------------------- + servers = clients = [] + iph = IPerfHelper(tgen) + start_client = functools.partial(iph.iperf, json=True, length=TIME_OUT) + start_server = functools.partial(iph.iperf, server=True, background=True) + for i in range(1, 7): + server = start_server(r4, bind_addr="10.6%d.0.1" % i, port=5200 + i) + servers.append(server) + + tc_check(r1, tc_setup) + for mode in [XdpMode.META, XdpMode.SKB]: + # reset tc filters/ xdp handlers + r1.run("tc filter del dev r1-r2-eth0") + router_remove_xdp(r1, b"r1-eth0") + load_qppb_plugin(tgen, r1, mode=mode) + router_attach_xdp(r1, b"r1-eth0") + + if mode == XdpMode.SKB: + tc_check(r1, tc_filters) + elif mode == XdpMode.META: + tc_bpf_filter(r1, tc_egress_idx) + # refresh arp cache, etc ... + h1.run("ping -c 3 " + R4_L0_61) + time.sleep(1) + # breakpoint() + # TC1: BGP_POLICT_DST + # ----------------------------------------------------------------- + tc_log_stats(r1, "r1-r2-eth0") + qppb_map[r1_eth0_idx] = BgpPolicy.Dst.value + client = start_client(h1, dst=R4_L0_61) + tc_log_stats(r1, "r1-r2-eth0") + + out, err = client.communicate() + assert_bw(out, bw, tolerance, time=TIME_OUT) + + # TC2: Respect TOS + # ----------------------------------------------------------------- + tc_log_stats(r1, "r1-r2-eth0") + client = start_client(h1, dst=R4_L0_61, dscp=20) + tc_log_stats(r1, "r1-r2-eth0") + # bpf_print_trace(r1.bpf) + + bw = 5 + out, err = client.communicate() + assert_bw(out, bw, tolerance, time=TIME_OUT) + + # TC3: BGP_POLICT_SRC, swap host roles + # ----------------------------------------------------------------- + qppb_map[r1_eth0_idx] = BgpPolicy.Src.value + dscp_map[h1_key] = xdp_dscp(10) + tc_log_stats(r1, "r1-r2-eth0") + client = start_client(h1, dst=R4_L0_61) + tc_log_stats(r1, "r1-r2-eth0") + + bw = 7.5 + out, err = client.communicate() + assert_bw(out, bw, tolerance, time=TIME_OUT) + dscp_map[h1_key] = zero_tag + + # TC4: verify bw rebalancing + # - setup all without max prio (10) + # - start max prio + # - verify BW is realocated -> max prio takes full link + # - verify lowest prio gets no BW + # ----------------------------------------------------------------- + # breakpoint() + qppb_map[r1_eth0_idx] = BgpPolicy.Dst.value + for i in range(2, 7): + client = start_client( + h1, dst="10.6%d.0.1" % i, port=5200 + i, timeout=10, background=True + ) + clients.append(client) + + # kill the second highest prio + high_prio = clients[0] + high_prio.kill() + + # max prio flow, should receive the best treatment + client = start_client(h1, dst=R4_L0_61) + out, err = client.communicate() + assert_bw(out, bw, tolerance, time=TIME_OUT) + + time.sleep(5) # XXX: + # iph.stop_host("h1") # does not work with background processes + iperf_json = "%s/iperf_h1_10.66.0.1_client.json" % tgen.logdir + with open(iperf_json) as file: + out = file.read() + # the lowest prio should be ~0.5mbps + assert_bw(out, 0.25, 1, time=TIME_OUT) + + # XXX: swap priority on the fly + # + swap filter priorty + # + swap lpm entries + # + swap direction + # .... + + +@pytest.mark.skip +def test_xdp_network_overlap(tgen): + """ + The feature configuration involves many steps making it quite easy to messup + and accidentally leak traffic, apply wrong preference, etc .. + I`m assuming the following scenarios that may require special handling on xdp side + * configuration mistakes, network loops + * delays during (re)convergance (I guess?) + * using overlaping network ranges (router cascading ?) + * external events (malformed update packets / fail in processing ...?) + * sidefects of admin involvment ... + # XXX(?): how this would work with different kinds of NAT + # XXX: overlaping vs router cascading scenario + Topo: + 20.0.0.0/16 h1-eth1, learned (dscp af22) + h1 <-----++ + r2 <-- r3 <-- r4 + h2 <-----++ + 20.0.1.0/24 h2-eth2, installed by admin + + Admin should be aware that subnetwork 20..1. will receive the QOS treatment from 20.../16, + even though it was not explicitly configured for the new network segment + """ + # XXX,TBD: not sure how common/critical such issues would be + # XXX: likely, there will be many ways to acidentally leak traffic (; + # any tools to detect leaks? i.e. some packet processing stats to look at + r1 = tgen.gears["r1"] + qppb_map = r1.bpf[b"qppb_mode_map"] + dscp_map = r1.bpf[b"dscp_map"] + + r1_eth0_idx = xdp_ifindex(r1, "r1-r2-eth0") + qppb_map[r1_eth0_idx] = BgpPolicy.Dst.value + h1_key = KeyV4(16, (20, 0, 0, 0)) + dscp_map[h1_key] = af21_tag + router_attach_xdp(r1, b"r1-r2-eth0") + + r4 = tgen.gears["r4"] + r4.run("ping -c 3 20.0.0.1") # is tagged with 0x12 + r4.run("ping -c 3 20.0.1.1") # is tagged as well + + # Admin can dissable marking/processing by manually inserting prefix without policy + # The LPM will lookup the more specific prefix and ignore it + h2_key = KeyV4(24, (20, 0, 1, 0)) + dscp_map[h2_key] = zero_tag + r4.run("ping -c 3 20.0.1.1") # should not be tagged any more + + # ::TBD:: + breakpoint() + + +@pytest.mark.skip +def test_get_version(tgen): + "Sanity testing, triggers breapoint" + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + r3 = tgen.gears["r3"] + r4 = tgen.gears["r4"] + version = r1.vtysh_cmd("show version") + logger.info("FRR version is: " + version) + + for host in ["h1", "r1", "r4"]: + tgen.net.hosts[host].run_in_window("bash") + + bpf_print_trace(r1.bpf) + breakpoint() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_qppb_flow/topo_cisco.json b/tests/topotests/bgp_qppb_flow/topo_cisco.json new file mode 100644 index 000000000000..c9bbd506441c --- /dev/null +++ b/tests/topotests/bgp_qppb_flow/topo_cisco.json @@ -0,0 +1,383 @@ +{ + "address_types": ["ipv4"], + "ipv4base": "10.0.0.0", + "ipv4mask": 30, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.0.0.0", + "v4mask": 30, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:DB8:F::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "30", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + { "redist_type": "static" }, + { "redist_type": "connected" } + ], + "neighbor": { + "r2": { + "dest_link": { + "r1": {} + } + } + } + } + } + } + }, + "bgp_community_lists": [ + { + "community_type": "standard", + "action": "permit", + "name": "comm_list_1", + "value": "60:1" + }, + { + "community_type": "standard", + "action": "permit", + "name": "comm_list_2", + "value": "60:2" + }, + { + "community_type": "standard", + "action": "permit", + "name": "comm_list_3", + "value": "60:3" + }, + { + "community_type": "standard", + "action": "permit", + "name": "comm_list_4", + "value": "60:4" + }, + { + "community_type": "standard", + "action": "permit", + "name": "comm_list_5", + "value": "60:5" + }, + { + "community_type": "standard", + "action": "permit", + "name": "comm_list_6", + "value": "60:6" + }, + { + "community_type": "standard", + "action": "permit", + "name": "comm_list_7", + "value": "60:7" + } + ], + "route_maps": { + "QPPB": [ + { + "action": "permit", + "match": { "community_list": { "id": "comm_list_1" } }, + "set": { "dscp": "10" } + }, + { + "action": "permit", + "match": { "community_list": { "id": "comm_list_2" } }, + "set": { "dscp": "20" } + }, + { + "action": "permit", + "match": { "community_list": { "id": "comm_list_3" } }, + "set": { "dscp": "30" } + }, + { + "action": "permit", + "match": { "community_list": { "id": "comm_list_4" } }, + "set": { "dscp": "40" } + }, + { + "action": "permit", + "match": { "community_list": { "id": "comm_list_5" } }, + "set": { "dscp": "50" } + }, + { + "action": "permit", + "match": { "community_list": { "id": "comm_list_6" } }, + "set": { "dscp": "15" } + }, + { + "action": "permit", + "match": { "community_list": { "id": "comm_list_7" } }, + "set": { "dscp": "25" } + }, + { + "action": "permit", + "match": { "ipv4": {"prefix_lists": "pf_list_1"} }, + "set": { "dscp": "63" } + }, + { "action": "permit" } + ] + }, + "prefix_lists": { + "ipv4": { + "pf_list_1": [ + { + "seqid": "69", + "network": "10.69.0.0/30", + "action": "permit" + } + ] + } + } + }, + "r2": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "10", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + { "redist_type": "static" }, + { "redist_type": "connected" } + ], + "neighbor": { + "r1": { + "dest_link": { + "r2": { + "route_maps": [{ + "name": "send_community", + "direction": "out" + }] + } + } + }, + "r3": { + "dest_link": { + "r2": {} + } + } + } + } + } + } + }, + "route_maps": { + "send_community": [ + { + "action": "permit", + "match": { "ipv4": {"prefix_lists": "pf_list_1"} }, + "set": { + "community": { + "num": "60:1", + "action": "additive" + } + } + }, + { + "action": "permit", + "match": { "ipv4": {"prefix_lists": "pf_list_2"} }, + "set": { + "community": { + "num": "60:2", + "action": "additive" + } + } + }, + { + "action": "permit", + "match": { "ipv4": {"prefix_lists": "pf_list_3"} }, + "set": { + "community": { + "num": "60:3", + "action": "additive" + } + } + }, + { + "action": "permit", + "match": { "ipv4": {"prefix_lists": "pf_list_4"} }, + "set": { + "community": { + "num": "60:4", + "action": "additive" + } + } + }, + { + "action": "permit", + "match": { "ipv4": {"prefix_lists": "pf_list_5"} }, + "set": { + "community": { + "num": "60:5", + "action": "additive" + } + } + }, + { + "action": "permit", + "match": { "ipv4": {"prefix_lists": "pf_list_6"} }, + "set": { + "community": { + "num": "60:6", + "action": "additive" + } + } + }, + { + "action": "permit", + "match": { "ipv4": {"prefix_lists": "pf_list_7"} }, + "set": { + "community": { + "num": "60:7", + "action": "additive" + } + } + }, + { "action": "permit" } + ] + }, + "prefix_lists": { + "ipv4": { + "pf_list_1": [{ + "network": "10.61.0.1/32", + "action": "permit" + }], + "pf_list_2": [{ + "network": "10.62.0.1/32", + "action": "permit" + }], + "pf_list_3": [{ + "network": "10.63.0.1/32", + "action": "permit" + }], + "pf_list_4": [{ + "network": "10.64.0.1/32", + "action": "permit" + }], + "pf_list_5": [{ + "network": "10.65.0.1/32", + "action": "permit" + }], + "pf_list_6": [{ + "network": "10.66.0.1/32", + "action": "permit" + }], + "pf_list_7": [{ + "network": "10.67.0.1/32", + "action": "permit" + }] + } + } + }, + "r3": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "10", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + { "redist_type": "static" }, + { "redist_type": "connected" } + ], + "neighbor": { + "r2": { + "dest_link": { + "r3": {} + } + }, + "r4": { + "dest_link": { + "r3": {} + } + } + } + } + } + } + } + }, + "r4": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "60", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + { "redist_type": "static" }, + { "redist_type": "connected" } + ], + "neighbor": { + "r3": { + "dest_link": { + "r4": {} + } + } + } + } + } + } + } + } + } +} diff --git a/tests/topotests/lib/common_config.py b/tests/topotests/lib/common_config.py index 202872586c86..39948cd4bc5e 100644 --- a/tests/topotests/lib/common_config.py +++ b/tests/topotests/lib/common_config.py @@ -9,6 +9,7 @@ import ipaddress import json import os +import re import platform import socket import subprocess @@ -132,6 +133,7 @@ ], } + def is_string(value): try: return isinstance(value, basestring) @@ -403,7 +405,7 @@ def kill_router_daemons(tgen, router, daemons, save_config=True): return errormsg -def start_router_daemons(tgen, router, daemons): +def start_router_daemons(tgen, router, daemons, plugins=None): """ Daemons defined by user would be started * `tgen` : topogen object @@ -417,7 +419,7 @@ def start_router_daemons(tgen, router, daemons): router_list = tgen.routers() # Start daemons - res = router_list[router].startDaemons(daemons) + res = router_list[router].startDaemons(daemons, plugins) except Exception as e: errormsg = traceback.format_exc() @@ -2317,6 +2319,7 @@ def create_route_maps(tgen, input_dict, build=False): # med: metric value advertised for AS # aspath: set AS path value # weight: weight for the route + # dscp: dscp for the routed traffic, int or code point name # community: standard community value to be attached # large_community: large community value to be attached # community_additive: if set to "additive", adds community/large-community @@ -2349,6 +2352,7 @@ def create_route_maps(tgen, input_dict, build=False): "set": { "locPrf": 150, "metric": 30, + "dscp": "af21", "path": { "num": 20000, "action": "prepend", @@ -2447,6 +2451,7 @@ def create_route_maps(tgen, input_dict, build=False): ipv6_data = set_data.setdefault("ipv6", {}) local_preference = set_data.setdefault("locPrf", None) metric = set_data.setdefault("metric", None) + dscp = set_data.setdefault("dscp", None) metric_type = set_data.setdefault("metric-type", None) as_path = set_data.setdefault("path", {}) weight = set_data.setdefault("weight", None) @@ -2477,6 +2482,14 @@ def create_route_maps(tgen, input_dict, build=False): else: rmap_data.append("set metric {}".format(metric)) + # Dscp + if dscp: + del_comm = set_data.setdefault("delete", None) + if del_comm: + rmap_data.append("no set dscp {}".format(dscp)) + else: + rmap_data.append("set dscp {}".format(dscp)) + # Origin if origin: rmap_data.append("set origin {} \n".format(origin)) @@ -4874,6 +4887,7 @@ def find_msg(self, host, message, count=0): ) return matches != 0, matches + def verify_ip_nht(tgen, input_dict): """ Running "show ip nht" command and verifying given nexthop resolution diff --git a/tests/topotests/lib/topogen.py b/tests/topotests/lib/topogen.py index f49e30ea5f9c..dfb3d6711917 100644 --- a/tests/topotests/lib/topogen.py +++ b/tests/topotests/lib/topogen.py @@ -349,7 +349,7 @@ def add_exabgp_peer(self, name, ip, defaultRoute): self.peern += 1 return self.gears[name] - def add_host(self, name, ip, defaultRoute): + def add_host(self, name, ip, defaultRoute, **kwargs): """ Adds a new host to the topology. This function has the following parameters: @@ -361,7 +361,9 @@ def add_host(self, name, ip, defaultRoute): if name in self.gears: raise KeyError("host already exists") - self.gears[name] = TopoHost(self, name, ip=ip, defaultRoute=defaultRoute) + self.gears[name] = TopoHost( + self, name, ip=ip, defaultRoute=defaultRoute, **kwargs + ) self.peern += 1 return self.gears[name] @@ -926,7 +928,7 @@ def stop(self): self.logger.debug("stopping (no assert)") return self.net.stopRouter(False) - def startDaemons(self, daemons): + def startDaemons(self, daemons, plugins=None): """ Start Daemons: to start specific daemon(user defined daemon only) * Start daemons (e.g. FRR) @@ -934,7 +936,7 @@ def startDaemons(self, daemons): """ self.logger.debug("starting") nrouter = self.net - result = nrouter.startRouterDaemons(daemons) + result = nrouter.startRouterDaemons(daemons, plugins=plugins) if daemons is None: daemons = nrouter.daemons.keys() diff --git a/tests/topotests/lib/topotest.py b/tests/topotests/lib/topotest.py index 2f69f7364b21..bfdf0be43732 100644 --- a/tests/topotests/lib/topotest.py +++ b/tests/topotests/lib/topotest.py @@ -735,6 +735,18 @@ def version_cmp(v1, v2): return 0 +def interface_to_ifindex(node, iface): + """ + Gets the interface index using its name. Returns None on failure. + """ + interfaces = json.loads(node.cmd_raises("ip -j link show")) + for interface in interfaces: + if interface["ifname"] == iface: + return int(interface["ifindex"]) + + return None + + def interface_set_status(node, ifacename, ifaceaction=False, vrf_name=None): if ifaceaction: str_ifaceaction = "no shutdown" @@ -1797,7 +1809,7 @@ def getLog(self, log, daemon): log = file.read() return log - def startRouterDaemons(self, daemons=None, tgen=None): + def startRouterDaemons(self, daemons=None, tgen=None, plugins=None): "Starts FRR daemons for this router." asan_abort = bool(g_pytest_config.option.asan_abort) @@ -2197,6 +2209,13 @@ def emacs_gdb_ready(): while "mgmtd" in daemons_list: daemons_list.remove("mgmtd") + # XXX: handle plugins properly - per daemon + bgpd_plugins = plugins.get("bgpd") if plugins else None + if "bgpd" in daemons_list and bgpd_plugins: + start_daemon("bgpd", bgpd_plugins) + while "bgpd" in daemons_list: + daemons_list.remove("bgpd") + # Start Zebra after mgmtd if "zebra" in daemons_list: start_daemon("zebra", "-s 90000000") From 1f18c93ce4c2a30522162dff7d5c5ccce722eff5 Mon Sep 17 00:00:00 2001 From: Volodymyr Huti Date: Tue, 12 Dec 2023 23:45:15 +0200 Subject: [PATCH 6/6] bgpd, zebra, tests: move plugin down to zebra Move plugin invocation lower in the demon stack. This way, dscp tag is exposed for usage to the rest of demons. Signed-off-by: Volodymyr Huti --- bgpd/bgp_zebra.c | 8 ++-- bgpd/bgpd.h | 2 - bgpd/subdir.am | 7 ---- lib/zclient.c | 4 ++ lib/zclient.h | 3 ++ tests/topotests/bgp_qppb_flow/__init__.py | 9 +--- tests/topotests/bgp_qppb_flow/bgp_xdp_qppb.c | 1 - .../topotests/bgp_qppb_flow/test_bgp_qppb.py | 41 ++++++++++--------- .../topotests/bgp_qppb_flow/zebra_xdp_qppb.c | 1 + tests/topotests/lib/topogen.py | 1 + tests/topotests/lib/topotest.py | 13 +++--- zebra/rib.h | 3 ++ zebra/subdir.am | 7 ++++ zebra/zapi_msg.c | 12 ++++++ .../zebra_qppb_private.c | 28 +++++++------ bgpd/bgp_xdp_qppb.c => zebra/zebra_xdp_qppb.c | 0 16 files changed, 78 insertions(+), 62 deletions(-) delete mode 120000 tests/topotests/bgp_qppb_flow/bgp_xdp_qppb.c create mode 120000 tests/topotests/bgp_qppb_flow/zebra_xdp_qppb.c rename bgpd/bgp_qppb_private.c => zebra/zebra_qppb_private.c (70%) rename bgpd/bgp_xdp_qppb.c => zebra/zebra_xdp_qppb.c (100%) diff --git a/bgpd/bgp_zebra.c b/bgpd/bgp_zebra.c index 24ac1fac6ea0..3ab5c9c50338 100644 --- a/bgpd/bgp_zebra.c +++ b/bgpd/bgp_zebra.c @@ -63,8 +63,6 @@ static bool bgp_zebra_label_manager_connect(void); /* hook to indicate vrf status change for SNMP */ DEFINE_HOOK(bgp_vrf_status_changed, (struct bgp *bgp, struct interface *ifp), (bgp, ifp)); -DEFINE_HOOK(bgp_qppb_mark_prefix, - (const struct prefix *p, uint8_t dscp, bool add), (p, dscp, add)); DEFINE_MTYPE_STATIC(BGPD, BGP_IF_INFO, "BGP interface context"); @@ -1647,6 +1645,11 @@ bgp_zebra_announce_actual(struct bgp_dest *dest, struct bgp_path_info *info, SET_FLAG(api.message, ZAPI_MESSAGE_METRIC); api.metric = metric; + if (dscp) { + SET_FLAG(api.message, ZAPI_MESSAGE_DSCP); + api.dscp = dscp; + } + if (tag) { SET_FLAG(api.message, ZAPI_MESSAGE_TAG); api.tag = tag; @@ -1672,7 +1675,6 @@ bgp_zebra_announce_actual(struct bgp_dest *dest, struct bgp_path_info *info, zlog_debug("%s: %pFX: announcing to zebra (recursion %sset)", __func__, p, (recursion_flag ? "" : "NOT ")); } - hook_call(bgp_qppb_mark_prefix, p, dscp, is_add); return zclient_route_send(is_add ? ZEBRA_ROUTE_ADD : ZEBRA_ROUTE_DELETE, zclient, &api); } diff --git a/bgpd/bgpd.h b/bgpd/bgpd.h index 859073aac4e6..c0fefd53baa7 100644 --- a/bgpd/bgpd.h +++ b/bgpd/bgpd.h @@ -2753,8 +2753,6 @@ DECLARE_HOOK(bgp_rpki_prefix_status, (struct peer * peer, struct attr *attr, const struct prefix *prefix), (peer, attr, prefix)); -DECLARE_HOOK(bgp_qppb_mark_prefix, - (const struct prefix *p, uint8_t dscp, bool add), (p, dscp, add)); void peer_nsf_stop(struct peer *peer); diff --git a/bgpd/subdir.am b/bgpd/subdir.am index e55c2d52372e..6d6fad00745f 100644 --- a/bgpd/subdir.am +++ b/bgpd/subdir.am @@ -9,9 +9,6 @@ noinst_PROGRAMS += bgpd/bgp_btoa vtysh_daemons += bgpd -if QPPB -module_LTLIBRARIES += bgpd/bgpd_qppb.la -endif if SNMP module_LTLIBRARIES += bgpd/bgpd_snmp.la endif @@ -209,10 +206,6 @@ bgpd_bgpd_bmp_la_SOURCES = bgpd/bgp_bmp.c bgpd_bgpd_bmp_la_LIBADD = lib/libfrrcares.la bgpd_bgpd_bmp_la_LDFLAGS = $(MODULE_LDFLAGS) -bgpd_bgpd_qppb_la_SOURCES = bgpd/bgp_qppb_private.c -bgpd_bgpd_qppb_la_LIBADD = lib/libfrrcares.la -bgpd_bgpd_qppb_la_LDFLAGS = $(MODULE_LDFLAGS) -lbpf - clippy_scan += \ bgpd/bgp_bmp.c \ bgpd/bgp_debug.c \ diff --git a/lib/zclient.c b/lib/zclient.c index 286439182282..a8d947b9fa85 100644 --- a/lib/zclient.c +++ b/lib/zclient.c @@ -1337,6 +1337,8 @@ int zapi_route_encode(uint8_t cmd, struct stream *s, struct zapi_route *api) stream_putc(s, api->distance); if (CHECK_FLAG(api->message, ZAPI_MESSAGE_METRIC)) stream_putl(s, api->metric); + if (CHECK_FLAG(api->message, ZAPI_MESSAGE_DSCP)) + stream_putl(s, api->dscp); if (CHECK_FLAG(api->message, ZAPI_MESSAGE_TAG)) stream_putl(s, api->tag); if (CHECK_FLAG(api->message, ZAPI_MESSAGE_MTU)) @@ -1590,6 +1592,8 @@ int zapi_route_decode(struct stream *s, struct zapi_route *api) STREAM_GETC(s, api->distance); if (CHECK_FLAG(api->message, ZAPI_MESSAGE_METRIC)) STREAM_GETL(s, api->metric); + if (CHECK_FLAG(api->message, ZAPI_MESSAGE_DSCP)) + STREAM_GETL(s, api->dscp); if (CHECK_FLAG(api->message, ZAPI_MESSAGE_TAG)) STREAM_GETL(s, api->tag); if (CHECK_FLAG(api->message, ZAPI_MESSAGE_MTU)) diff --git a/lib/zclient.h b/lib/zclient.h index 1bf91064e2d0..141b9f1254fa 100644 --- a/lib/zclient.h +++ b/lib/zclient.h @@ -406,6 +406,7 @@ extern int zclient_bfd_session_update(ZAPI_CALLBACK_ARGS); #define ZAPI_MESSAGE_TABLEID 0x0100 #define ZAPI_MESSAGE_SRTE 0x0200 #define ZAPI_MESSAGE_OPAQUE 0x0400 +#define ZAPI_MESSAGE_DSCP 0x0800 #define ZSERV_VERSION 6 /* Zserv protocol message header */ @@ -591,6 +592,8 @@ struct zapi_route { uint32_t metric; + uint32_t dscp; + route_tag_t tag; uint32_t mtu; diff --git a/tests/topotests/bgp_qppb_flow/__init__.py b/tests/topotests/bgp_qppb_flow/__init__.py index 3b9ac16575ca..c11320e9d5de 100755 --- a/tests/topotests/bgp_qppb_flow/__init__.py +++ b/tests/topotests/bgp_qppb_flow/__init__.py @@ -88,7 +88,7 @@ def load_qppb_plugin(tgen, rnode, mode=XdpMode.SKB, debug_on=DEV_DEBUG): """ debug_flags = DEBUG_BPF | DEBUG_PREPROCESSOR | DEBUG_SOURCE | DEBUG_BTF debug = debug_flags if debug_on else 0 - src_file = CWD + "/bgp_xdp_qppb.c" + src_file = CWD + "/zebra_xdp_qppb.c" bpf_flags = [ '-DMODE_STR="{}"'.format(mode), "-D{}".format(mode.value), @@ -110,13 +110,6 @@ def load_qppb_plugin(tgen, rnode, mode=XdpMode.SKB, debug_on=DEV_DEBUG): except Exception as e: pytest.skip("Failed to configure XDP environment -- \n" + str(e)) - qppb_module = "-M vyos_qppb" - logger.info( - "Restart {}, XDP hooks loading...\nPlugin :: {}".format(rnode.name, qppb_module) - ) - kill_router_daemons(tgen, rnode.name, ["bgpd"]) - start_router_daemons(tgen, rnode.name, ["bgpd"], {"bgpd": qppb_module}) - def tc_bpf_filter(rnode, ifid): "Attach tc bpf filter, depends on pyroute2 package" diff --git a/tests/topotests/bgp_qppb_flow/bgp_xdp_qppb.c b/tests/topotests/bgp_qppb_flow/bgp_xdp_qppb.c deleted file mode 120000 index b12e30f0f728..000000000000 --- a/tests/topotests/bgp_qppb_flow/bgp_xdp_qppb.c +++ /dev/null @@ -1 +0,0 @@ -../../../bgpd/bgp_xdp_qppb.c \ No newline at end of file diff --git a/tests/topotests/bgp_qppb_flow/test_bgp_qppb.py b/tests/topotests/bgp_qppb_flow/test_bgp_qppb.py index ff55a18cb591..a72fc061b711 100644 --- a/tests/topotests/bgp_qppb_flow/test_bgp_qppb.py +++ b/tests/topotests/bgp_qppb_flow/test_bgp_qppb.py @@ -160,6 +160,27 @@ def setup_module(mod): global topo topo = tgen.json_topo + r1 = tgen.gears["r1"] + r4 = tgen.gears["r4"] + # Initializing BPF objects + # ----------------------------------------------------------------------- + # NOTE: we need to switch mnt namespace to instantiate BPF mappings + # XXX: python3.12 introduces os.setns, for now use libc directly + ns = "/proc/%d/ns/mnt" % r1.net.pid + nsfd = os.open(ns, os.O_RDONLY) + + libc = ctypes.CDLL("libc.so.6", use_errno=True) + libc.setns(nsfd, 0) + + tgen.qppb_nodes.append("r1") + r1.cmd_raises( + """ + mkdir -p /sys/fs/bpf + mount -t bpf bpf /sys/fs/bpf + """ + ) + load_qppb_plugin(tgen, r1) + start_topology(tgen) build_config_from_json(tgen, topo) if tgen.routers_have_failure(): @@ -173,9 +194,6 @@ def setup_module(mod): # Extra setup steps # ----------------------------------------------------------------------- - r4 = tgen.gears["r4"] - r1 = tgen.gears["r1"] - debug_rmap_dict = {"r1": {"raw_config": ["end", "debug route-map"]}} debug_config_dict = { "r1": {"debug": {"log_file": "debug.log", "enable": ["bgpd", "zebra"]}} @@ -196,23 +214,6 @@ def setup_module(mod): lo_ip_add = "ip address add dev lo 10.6{0}.0.1/32" [r4.cmd_raises(lo_ip_add.format(n)) for n in range(1, 7)] - # Initializing BPF objects - # ----------------------------------------------------------------------- - # NOTE: we need to switch mnt namespace to instantiate BPF mappings - # XXX: python3.12 introduces os.setns, for now use libc directly - ns = "/proc/%d/ns/mnt" % r1.net.pid - nsfd = os.open(ns, os.O_RDONLY) - - libc = ctypes.CDLL("libc.so.6", use_errno=True) - libc.setns(nsfd, 0) - - r1.cmd_raises( - """ - mkdir -p /sys/fs/bpf - mount -t bpf bpf /sys/fs/bpf - """ - ) - load_qppb_plugin(tgen, r1) # Test Cases diff --git a/tests/topotests/bgp_qppb_flow/zebra_xdp_qppb.c b/tests/topotests/bgp_qppb_flow/zebra_xdp_qppb.c new file mode 120000 index 000000000000..95af94733108 --- /dev/null +++ b/tests/topotests/bgp_qppb_flow/zebra_xdp_qppb.c @@ -0,0 +1 @@ +../../../zebra/zebra_xdp_qppb.c \ No newline at end of file diff --git a/tests/topotests/lib/topogen.py b/tests/topotests/lib/topogen.py index dfb3d6711917..55ebe0fac72a 100644 --- a/tests/topotests/lib/topogen.py +++ b/tests/topotests/lib/topogen.py @@ -154,6 +154,7 @@ def __init__(self, topodef, modname="unnamed"): self.peern = 1 self.cfg_gen = 0 self.exabgp_cmd = None + self.qppb_nodes = [] self._init_topo(topodef) logger.info("loading topology: {}".format(self.modname)) diff --git a/tests/topotests/lib/topotest.py b/tests/topotests/lib/topotest.py index bfdf0be43732..ea873544d263 100644 --- a/tests/topotests/lib/topotest.py +++ b/tests/topotests/lib/topotest.py @@ -2209,19 +2209,16 @@ def emacs_gdb_ready(): while "mgmtd" in daemons_list: daemons_list.remove("mgmtd") - # XXX: handle plugins properly - per daemon - bgpd_plugins = plugins.get("bgpd") if plugins else None - if "bgpd" in daemons_list and bgpd_plugins: - start_daemon("bgpd", bgpd_plugins) - while "bgpd" in daemons_list: - daemons_list.remove("bgpd") - # Start Zebra after mgmtd + zebra_plugins = "" + if tgen and self.name in tgen.qppb_nodes: + zebra_plugins = "-M zebra_qppb" if "zebra" in daemons_list: - start_daemon("zebra", "-s 90000000") + start_daemon("zebra", "-s 90000000 " + zebra_plugins) while "zebra" in daemons_list: daemons_list.remove("zebra") + # Start staticd next if required if "staticd" in daemons_list: start_daemon("staticd") diff --git a/zebra/rib.h b/zebra/rib.h index a721f4bac456..6acbe265e29f 100644 --- a/zebra/rib.h +++ b/zebra/rib.h @@ -103,6 +103,9 @@ struct route_entry { /* Metric */ uint32_t metric; + /* DSCP */ + uint32_t dscp; + /* MTU */ uint32_t mtu; uint32_t nexthop_mtu; diff --git a/zebra/subdir.am b/zebra/subdir.am index f7674473661c..88d9237c1580 100644 --- a/zebra/subdir.am +++ b/zebra/subdir.am @@ -18,6 +18,9 @@ endif if LINUX module_LTLIBRARIES += zebra/zebra_cumulus_mlag.la endif +if QPPB +module_LTLIBRARIES += zebra/zebra_qppb.la +endif #if FPM_LISTENER sbin_PROGRAMS += zebra/fpm_listener @@ -204,6 +207,10 @@ noinst_HEADERS += \ zebra/dpdk/zebra_dplane_dpdk_private.h \ # end +zebra_zebra_qppb_la_SOURCES = zebra/zebra_qppb_private.c +zebra_zebra_qppb_la_LIBADD = lib/libfrrcares.la +zebra_zebra_qppb_la_LDFLAGS = $(MODULE_LDFLAGS) -lbpf + zebra_zebra_irdp_la_SOURCES = \ zebra/irdp_interface.c \ zebra/irdp_main.c \ diff --git a/zebra/zapi_msg.c b/zebra/zapi_msg.c index 76cabd1bf09b..1060c6716bc5 100644 --- a/zebra/zapi_msg.c +++ b/zebra/zapi_msg.c @@ -55,6 +55,10 @@ DEFINE_MTYPE_STATIC(ZEBRA, RE_OPAQUE, "Route Opaque Data"); +DEFINE_HOOK(zebra_qppb_mark_prefix, + (const struct prefix *p, uint8_t dscp, bool add), (p, dscp, add)); + + static int zapi_nhg_decode(struct stream *s, int cmd, struct zapi_nhg *api_nhg); /* Encoding helpers -------------------------------------------------------- */ @@ -604,6 +608,10 @@ int zsend_redistribute_route(int cmd, struct zserv *client, api.distance = re->distance; SET_FLAG(api.message, ZAPI_MESSAGE_METRIC); api.metric = re->metric; + if (re->dscp) { + SET_FLAG(api.message, ZAPI_MESSAGE_DSCP); + api.dscp = re->dscp; + } if (re->tag) { SET_FLAG(api.message, ZAPI_MESSAGE_TAG); api.tag = re->tag; @@ -2182,6 +2190,8 @@ static void zread_route_add(ZAPI_HANDLER_ARGS) XFREE(MTYPE_RE, re); } + hook_call(zebra_qppb_mark_prefix, &api.prefix, api.dscp, true); + /* At this point, these allocations are not needed: 're' has been * retained or freed, and if 're' still exists, it is using * a reference to a shared group object. @@ -2248,6 +2258,8 @@ static void zread_route_del(ZAPI_HANDLER_ARGS) api.flags, &api.prefix, src_p, NULL, 0, table_id, api.metric, api.distance, false); + hook_call(zebra_qppb_mark_prefix, &api.prefix, api.dscp, false); + /* Stats */ switch (api.prefix.family) { case AF_INET: diff --git a/bgpd/bgp_qppb_private.c b/zebra/zebra_qppb_private.c similarity index 70% rename from bgpd/bgp_qppb_private.c rename to zebra/zebra_qppb_private.c index 50b96b90868f..c7017ed12e95 100644 --- a/bgpd/bgp_qppb_private.c +++ b/zebra/zebra_qppb_private.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * BGP QPPB support + * zebra QPPB support * Copyright (C) 2023 VyOS Inc. * Volodymyr Huti */ @@ -8,11 +8,12 @@ #include #include #include -#include "bgpd/bgpd.h" #include "log.h" #include "prefix.h" #include "privs.h" +extern struct zebra_privs_t zserv_privs; + #define BPF_DSCP_MAP "dscp_map" #define BPF_PIN_DIR "/sys/fs/bpf/" /* @@ -24,7 +25,8 @@ */ #define BPF_LPM_KEY_SIZE (sizeof(struct bpf_lpm_trie_key) + sizeof(__u32)) -extern struct zebra_privs_t bgpd_privs; +DECLARE_HOOK(zebra_qppb_mark_prefix, + (const struct prefix *p, uint8_t dscp, bool add), (p, dscp, add)); static int dscp_map_fd; static int open_bpf_map_file(const char *pin_dir, const char *mapname) @@ -45,26 +47,26 @@ static int open_bpf_map_file(const char *pin_dir, const char *mapname) return fd; } -static void bgp_qppb_map_init(void) +static void zebra_qppb_map_init(void) { const char *pin_dir = THIS_MODULE->load_args ?: BPF_PIN_DIR; dscp_map_fd = open_bpf_map_file(pin_dir, BPF_DSCP_MAP); } -static int bgp_qppb_mark_prefix(const struct prefix *p, uint8_t dscp, bool add) +static int zebra_qppb_mark_prefix(const struct prefix *p, uint8_t dscp, bool add) { struct bpf_lpm_trie_key *key_ipv4; int err = 0; - if (dscp_map_fd < 0) + if (dscp_map_fd < 0 || !dscp) return err; key_ipv4 = alloca(BPF_LPM_KEY_SIZE); key_ipv4->prefixlen = p->prefixlen; memcpy(key_ipv4->data, &p->u.prefix4, sizeof(struct in_addr)); - frr_with_privs (&bgpd_privs) { + frr_with_privs (&zserv_privs) { err = add ? bpf_map_update_elem(dscp_map_fd, key_ipv4, &dscp, 0) : bpf_map_delete_elem(dscp_map_fd, key_ipv4); } @@ -73,13 +75,13 @@ static int bgp_qppb_mark_prefix(const struct prefix *p, uint8_t dscp, bool add) return err; } -static int bgp_qppb_module_init(void) +static int zebra_qppb_module_init(void) { - bgp_qppb_map_init(); - hook_register(bgp_qppb_mark_prefix, bgp_qppb_mark_prefix); + zebra_qppb_map_init(); + hook_register(zebra_qppb_mark_prefix, zebra_qppb_mark_prefix); return 0; } -FRR_MODULE_SETUP(.name = "bgp_vyos_qppb", .version = "0.0.1", - .description = "bgp QPPB implementation for VyOS", - .init = bgp_qppb_module_init); +FRR_MODULE_SETUP(.name = "zebra_vyos_qppb", .version = "0.0.1", + .description = "zebra QPPB plugin for VyOS", + .init = zebra_qppb_module_init); diff --git a/bgpd/bgp_xdp_qppb.c b/zebra/zebra_xdp_qppb.c similarity index 100% rename from bgpd/bgp_xdp_qppb.c rename to zebra/zebra_xdp_qppb.c