From 29c0610248e2a507fb35225615d54ce61dcde6f9 Mon Sep 17 00:00:00 2001 From: Robin Jarry Date: Thu, 17 Apr 2025 16:33:39 +0200 Subject: [PATCH 01/25] ci: add explicit dependency on gawk The new fedora container images don't have awk installed by default. Install it explicitly and add it to the dev dependencies. Signed-off-by: Robin Jarry --- .github/workflows/check.yml | 2 +- README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 56b68339..89fcc464 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -129,7 +129,7 @@ jobs: runs-on: ubuntu-24.04 container: fedora:latest steps: - - run: dnf install -y make clang-tools-extra git jq curl codespell + - run: dnf install -y gawk make clang-tools-extra git jq curl codespell - uses: actions/checkout@v4 with: persist-credentials: false diff --git a/README.md b/README.md index 0d3bc669..473fe861 100644 --- a/README.md +++ b/README.md @@ -254,13 +254,13 @@ In order to run the `smoke-tests`, `lint`, `check-patches` and `update-graph` targets, you'll need additional packages: ```sh -dnf install clang-tools-extra jq codespell curl traceroute graphviz ndisc6 +dnf install gawk clang-tools-extra jq codespell curl traceroute graphviz ndisc6 ``` or ```sh -apt install clang-format jq codespell curl traceroute graphviz ndisc6 +apt install gawk clang-format jq codespell curl traceroute graphviz ndisc6 ``` ### Build From d7ab951247af6721deb386285361c51cc84375ba Mon Sep 17 00:00:00 2001 From: Robin Jarry Date: Wed, 16 Apr 2025 19:24:10 +0200 Subject: [PATCH 02/25] treewide: help clang-format do a better job Depending on clang-format versions, it may or may not wrap lines as expected. Help it a little with the following changes: * Add function argument names when missing (or add inline comments instead when arguments are unused). * Add missing spaces missed by clang-format-19 and earlier. * Use an intermediate macro to store long literal values. Also, clang-format 20 has a bug when wrapping long lines with type casting. Disable format around these lines until the bug is fixed. Link: https://github.com/llvm/llvm-project/issues/135977 Signed-off-by: Robin Jarry --- modules/infra/control/worker_test.c | 17 +++++++++++++++-- modules/infra/datapath/eth_output.c | 2 +- modules/ip/datapath/gr_ip4_datapath.h | 8 +++++++- modules/ip/datapath/icmp_local_send.c | 2 ++ modules/ip6/control/gr_ip6_control.h | 8 +++++++- modules/ip6/control/icmp6.c | 6 ++++-- modules/ip6/datapath/ip6_error.c | 4 ++++ 7 files changed, 40 insertions(+), 7 deletions(-) diff --git a/modules/infra/control/worker_test.c b/modules/infra/control/worker_test.c index 148c61fc..b920af26 100644 --- a/modules/infra/control/worker_test.c +++ b/modules/infra/control/worker_test.c @@ -77,11 +77,24 @@ mock_func(int, __wrap_rte_eth_dev_get_mtu(uint16_t, uint16_t *)); mock_func(int, __wrap_rte_eth_macaddr_get(uint16_t, struct rte_ether_addr *)); mock_func( int, - __wrap_rte_eth_rx_queue_setup(uint16_t, uint16_t, uint16_t, unsigned int, const struct rte_eth_rxconf *, struct rte_mempool *) + __wrap_rte_eth_rx_queue_setup( + uint16_t, + uint16_t, + uint16_t, + unsigned int, + const struct rte_eth_rxconf *, + struct rte_mempool * /*pool*/ + ) ); mock_func( int, - __wrap_rte_eth_tx_queue_setup(uint16_t, uint16_t, uint16_t, unsigned int, const struct rte_eth_txconf *) + __wrap_rte_eth_tx_queue_setup( + uint16_t, + uint16_t, + uint16_t, + unsigned int, + const struct rte_eth_txconf * /*conf*/ + ) ); mock_func(int, __wrap_numa_available(int)); mock_func(int, __wrap_numa_node_of_cpu(int)); diff --git a/modules/infra/datapath/eth_output.c b/modules/infra/datapath/eth_output.c index e4253a52..babeee1f 100644 --- a/modules/infra/datapath/eth_output.c +++ b/modules/infra/datapath/eth_output.c @@ -85,7 +85,7 @@ eth_output_process(struct rte_graph *graph, struct rte_node *node, void **objs, return nb_objs; } -static struct rte_node_register node= { +static struct rte_node_register node = { .name = "eth_output", .process = eth_output_process, diff --git a/modules/ip/datapath/gr_ip4_datapath.h b/modules/ip/datapath/gr_ip4_datapath.h index 2417eb27..8ff4d627 100644 --- a/modules/ip/datapath/gr_ip4_datapath.h +++ b/modules/ip/datapath/gr_ip4_datapath.h @@ -32,7 +32,13 @@ void ip_input_local_add_proto(uint8_t proto, const char *next_node); void ip_output_register_interface_type(gr_iface_type_t type, const char *next_node); void ip_output_register_nexthop_type(gr_nh_type_t type, const char *next_node); int arp_output_request_solicit(struct nexthop *nh); -void arp_update_nexthop(struct rte_graph *, struct rte_node *, struct nexthop *, const struct iface *, const struct rte_ether_addr *); +void arp_update_nexthop( + struct rte_graph *graph, + struct rte_node *node, + struct nexthop *nh, + const struct iface *iface, + const struct rte_ether_addr *mac +); #define IPV4_VERSION_IHL 0x45 #define IPV4_DEFAULT_TTL 64 diff --git a/modules/ip/datapath/icmp_local_send.c b/modules/ip/datapath/icmp_local_send.c index 399fd7af..c663844d 100644 --- a/modules/ip/datapath/icmp_local_send.c +++ b/modules/ip/datapath/icmp_local_send.c @@ -85,8 +85,10 @@ static uint16_t icmp_local_send_process( for (unsigned i = 0; i < n_objs; i++) { mbuf = objs[i]; msg = control_input_mbuf_data(mbuf)->data; + // clang-format off icmp = (struct rte_icmp_hdr *) rte_pktmbuf_append(mbuf, sizeof(*icmp) + sizeof(clock_t)); + // clang-format on payload = rte_pktmbuf_mtod_offset(mbuf, clock_t *, sizeof(*icmp)); *payload = gr_clock_us(); diff --git a/modules/ip6/control/gr_ip6_control.h b/modules/ip6/control/gr_ip6_control.h index d44b12eb..4b74ae4a 100644 --- a/modules/ip6/control/gr_ip6_control.h +++ b/modules/ip6/control/gr_ip6_control.h @@ -30,7 +30,13 @@ void nh6_unreachable_cb(struct rte_mbuf *m); void ndp_probe_input_cb(struct rte_mbuf *m); void ndp_router_sollicit_input_cb(struct rte_mbuf *m); -int rib6_insert(uint16_t vrf_id, uint16_t iface_id, const struct rte_ipv6_addr *, uint8_t prefixlen, struct nexthop *); +int rib6_insert( + uint16_t vrf_id, + uint16_t iface_id, + const struct rte_ipv6_addr *, + uint8_t prefixlen, + struct nexthop *nh +); int rib6_delete( uint16_t vrf_id, uint16_t iface_id, diff --git a/modules/ip6/control/icmp6.c b/modules/ip6/control/icmp6.c index 2574a6de..aa50ab90 100644 --- a/modules/ip6/control/icmp6.c +++ b/modules/ip6/control/icmp6.c @@ -44,6 +44,9 @@ static void icmp6_input_cb(struct rte_mbuf *m) { STAILQ_INSERT_TAIL(&icmp_queue, i, next); } +#define ICMP6_ERROR_PKT_LEN \ + (GR_ICMP6_HDR_LEN + sizeof(struct rte_ipv6_hdr) + GR_ICMP6_HDR_LEN + sizeof(clock_t)) + static struct rte_mbuf * get_icmp6_echo_reply(uint16_t ident, uint16_t seq_num, struct icmp6 **out_icmp6) { struct icmp_queue_item *i, *tmp; @@ -67,8 +70,7 @@ get_icmp6_echo_reply(uint16_t ident, uint16_t seq_num, struct icmp6 **out_icmp6) ip6 = PAYLOAD(icmp6_echo); icmp6 = PAYLOAD(ip6); icmp6_echo = PAYLOAD(icmp6); - if (rte_pktmbuf_pkt_len(mbuf - ) < GR_ICMP6_HDR_LEN + sizeof(*ip6) + GR_ICMP6_HDR_LEN + sizeof(clock_t) + if (rte_pktmbuf_pkt_len(mbuf) < ICMP6_ERROR_PKT_LEN || ip6->proto != IPPROTO_ICMPV6 || icmp6->type != ICMP6_TYPE_ECHO_REQUEST) goto free_and_skip; diff --git a/modules/ip6/datapath/ip6_error.c b/modules/ip6/datapath/ip6_error.c index d4bd1d8e..3e897c58 100644 --- a/modules/ip6/datapath/ip6_error.c +++ b/modules/ip6/datapath/ip6_error.c @@ -54,16 +54,20 @@ ip6_error_process(struct rte_graph *graph, struct rte_node *node, void **objs, u switch (icmp_type) { case ICMP6_ERR_DEST_UNREACH: + // clang-format off du = (struct icmp6_err_dest_unreach *) rte_pktmbuf_prepend(mbuf, sizeof(*du)); + // clang-format on if (unlikely(du == NULL)) { edge = NO_HEADROOM; goto next; } break; case ICMP6_ERR_TTL_EXCEEDED: + // clang-format off te = (struct icmp6_err_ttl_exceeded *) rte_pktmbuf_prepend(mbuf, sizeof(*du)); + // clang-format on if (unlikely(te == NULL)) { edge = NO_HEADROOM; goto next; From 28ee0be87e3607dedb22ce3dab8b5d0791ef961e Mon Sep 17 00:00:00 2001 From: Robin Jarry Date: Wed, 16 Apr 2025 19:10:36 +0200 Subject: [PATCH 03/25] mk: allow forcing clang-format binary Make it possible to override the clang-format binary name or path. Signed-off-by: Robin Jarry --- GNUmakefile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index f2cf25dc..88956aa4 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -92,6 +92,7 @@ rpm: $$name.$$arch.rpm || exit; \ done +CLANG_FORMAT ?= clang-format c_src = git ls-files '*.[ch]' ':!:subprojects' all_files = git ls-files ':!:subprojects' licensed_files = git ls-files ':!:*.svg' ':!:LICENSE' ':!:*.md' ':!:*.asc' ':!:subprojects' ':!:debian' ':!:.*' @@ -100,7 +101,7 @@ licensed_files = git ls-files ':!:*.svg' ':!:LICENSE' ':!:*.md' ':!:*.asc' ':!:s lint: @echo '[clang-format]' $Q tmp=`mktemp` && trap "rm -f $$tmp" EXIT && $(c_src) > "$$tmp" && \ - clang-format --files="$$tmp" --dry-run --Werror + $(CLANG_FORMAT) --files="$$tmp" --dry-run --Werror @echo '[license-check]' $Q ! $(licensed_files) | while read -r f; do \ if ! grep -qF 'SPDX-License-Identifier: BSD-3-Clause' $$f; then \ @@ -122,7 +123,7 @@ lint: format: @echo '[clang-format]' $Q tmp=`mktemp` && trap "rm -f $$tmp" EXIT && $(c_src) > "$$tmp" && \ - clang-format --files="$$tmp" -i --verbose + $(CLANG_FORMAT) --files="$$tmp" -i --verbose REVISION_RANGE ?= origin/main.. From f16f6fbe23b95d1960d2c2e61f0c427866706e99 Mon Sep 17 00:00:00 2001 From: Robin Jarry Date: Wed, 16 Apr 2025 10:21:33 +0200 Subject: [PATCH 04/25] infra: add route origin constants Reuse the NH_ORIGIN_* from BSD and RTPROT_* constants from Linux to identify what was the cause for every route installation in grout. Add a small function to convert known values to text. Signed-off-by: Robin Jarry --- modules/infra/api/gr_nexthop.h | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/modules/infra/api/gr_nexthop.h b/modules/infra/api/gr_nexthop.h index 62f4de81..a13755da 100644 --- a/modules/infra/api/gr_nexthop.h +++ b/modules/infra/api/gr_nexthop.h @@ -102,6 +102,37 @@ static inline uint8_t nh_af(const struct gr_nexthop *nh) { return 0; } +// Route install origin values shared by IPv4 and IPv6. +// See NH_ORIGIN_* in sys/route/nhop.h (BSD) and RTPROT_* in zebra/rt_netlink.h (FRR). +typedef enum : uint8_t { + GR_RT_ORIGIN_UNSPEC = 0, //!< (NH_ORIGIN_UNSPEC). + GR_RT_ORIGIN_REDIRECT = 1, //!< Installed implicitly by ICMP redirect (NH_ORIGIN_REDIRECT). + GR_RT_ORIGIN_LINK = 2, //!< Installed implicitly for local addresses (NH_ORIGIN_KERNEL). + GR_RT_ORIGIN_BOOT = 3, //!< Installed at boot?? (NH_ORIGIN_BOOT). + GR_RT_ORIGIN_USER = 4, //!< Installed explicitly by user (NH_ORIGIN_STATIC). + // Values 5 to 254 are allowed and are used by routing daemons. + GR_RT_ORIGIN_INTERNAL = 255, //!< Reserved for internal use by grout. +} gr_rt_origin_t; + +static inline const char *gr_rt_origin_name(gr_rt_origin_t origin) { + switch (origin) { + case GR_RT_ORIGIN_UNSPEC: + return "unspec"; + case GR_RT_ORIGIN_REDIRECT: + return "redirect"; + case GR_RT_ORIGIN_LINK: + return "link"; + case GR_RT_ORIGIN_BOOT: + return "boot"; + case GR_RT_ORIGIN_USER: + return "user"; + case GR_RT_ORIGIN_INTERNAL: + return "INTERNAL"; + } + return "?"; +} + +// nexthop config ////////////////////////////////////////////////////////////// struct gr_nexthop_config { //! Maximum number of nexthops for all address families (default: 128K). uint32_t max_count; @@ -117,7 +148,6 @@ struct gr_nexthop_config { uint8_t max_bcast_probes; }; -// nexthop config ////////////////////////////////////////////////////////////// #define GR_INFRA_NH_CONFIG_GET REQUEST_TYPE(GR_INFRA_MODULE, 0x0060) // struct gr_infra_nh_config_get_req { }; From 48069489ef50d225c3f805dd7cfe8f4df61c1430 Mon Sep 17 00:00:00 2001 From: Robin Jarry Date: Wed, 16 Apr 2025 11:48:09 +0200 Subject: [PATCH 05/25] ip: add route origin field Include a new origin field in IPv4 route objects exchanged in the API. Allow users to set an arbitrary value. This is mostly intended for routing daemons to identify if a route was learned from BGP, OSPF, etc. Or if it was configured manually by the user. Store the origin value in the RIB node extension private memory. Display the origin of routes in the CLI. For now, all routes explicitly added by the CLI are GR_RT_ORIGIN_USER. Other API clients can specify any suitable value. GR_RT_ORIGIN_INTERNAL(255) is reserved for dynamically added IPv4 routes when learning new next hops after receiving ARP requests or replies. Hide these routes on purpose when replying to a GR_IP4_ROUTE_LIST request to avoid confusion. Signed-off-by: Robin Jarry --- modules/infra/cli/events.c | 10 ++-- modules/ip/api/gr_ip4.h | 2 + modules/ip/cli/route.c | 4 +- modules/ip/control/address.c | 2 +- modules/ip/control/gr_ip4_control.h | 8 ++- modules/ip/control/nexthop.c | 6 +-- modules/ip/control/route.c | 81 ++++++++++++++++++++++++----- modules/srv6/control_headend.c | 9 +++- 8 files changed, 97 insertions(+), 25 deletions(-) diff --git a/modules/infra/cli/events.c b/modules/infra/cli/events.c index f76e0da5..76e9ea11 100644 --- a/modules/infra/cli/events.c +++ b/modules/infra/cli/events.c @@ -67,18 +67,20 @@ static cmd_status_t events_show(const struct gr_api_client *c, const struct ec_p case IP_EVENT_ROUTE_ADD: assert(e->payload_len == sizeof(*r4)); r4 = PAYLOAD(e); - printf("> route add: %4p/%d via %4p\n", + printf("> route add: %4p/%d via %4p origin %s\n", &r4->dest.ip, r4->dest.prefixlen, - &r4->nh); + &r4->nh, + gr_rt_origin_name(r4->origin)); break; case IP_EVENT_ROUTE_DEL: assert(e->payload_len == sizeof(*r4)); r4 = PAYLOAD(e); - printf("> route del: %4p/%d via %4p\n", + printf("> route del: %4p/%d via %4p origin %s\n", &r4->dest.ip, r4->dest.prefixlen, - &r4->nh); + &r4->nh, + gr_rt_origin_name(r4->origin)); break; case IP6_EVENT_ROUTE_ADD: assert(e->payload_len == sizeof(*r6)); diff --git a/modules/ip/api/gr_ip4.h b/modules/ip/api/gr_ip4.h index fc3c0083..d8a100a2 100644 --- a/modules/ip/api/gr_ip4.h +++ b/modules/ip/api/gr_ip4.h @@ -20,6 +20,7 @@ struct gr_ip4_route { struct ip4_net dest; ip4_addr_t nh; uint16_t vrf_id; + gr_rt_origin_t origin; }; #define GR_IP4_MODULE 0xf00d @@ -64,6 +65,7 @@ struct gr_ip4_route_add_req { uint16_t vrf_id; struct ip4_net dest; ip4_addr_t nh; + gr_rt_origin_t origin; uint8_t exist_ok; }; diff --git a/modules/ip/cli/route.c b/modules/ip/cli/route.c index 98a68ab9..0e46d50c 100644 --- a/modules/ip/cli/route.c +++ b/modules/ip/cli/route.c @@ -16,7 +16,7 @@ #include static cmd_status_t route4_add(const struct gr_api_client *c, const struct ec_pnode *p) { - struct gr_ip4_route_add_req req = {.exist_ok = true}; + struct gr_ip4_route_add_req req = {.exist_ok = true, .origin = GR_RT_ORIGIN_USER}; if (arg_ip4_net(p, "DEST", &req.dest, true) < 0) return CMD_ERROR; @@ -66,6 +66,7 @@ static cmd_status_t route4_list(const struct gr_api_client *c, const struct ec_p scols_table_new_column(table, "VRF", 0, 0); scols_table_new_column(table, "DESTINATION", 0, 0); scols_table_new_column(table, "NEXT_HOP", 0, 0); + scols_table_new_column(table, "ORIGIN", 0, 0); scols_table_set_column_separator(table, " "); for (size_t i = 0; i < resp->n_routes; i++) { @@ -74,6 +75,7 @@ static cmd_status_t route4_list(const struct gr_api_client *c, const struct ec_p scols_line_sprintf(line, 0, "%u", route->vrf_id); scols_line_sprintf(line, 1, IP4_F "/%hhu", &route->dest.ip, route->dest.prefixlen); scols_line_sprintf(line, 2, IP4_F, &route->nh); + scols_line_sprintf(line, 3, "%s", gr_rt_origin_name(route->origin)); } scols_print_table(table); diff --git a/modules/ip/control/address.c b/modules/ip/control/address.c index b73b7362..aaeb2e46 100644 --- a/modules/ip/control/address.c +++ b/modules/ip/control/address.c @@ -92,7 +92,7 @@ static struct api_out addr_add(const void *request, void ** /*response*/) { return api_out(errno, 0); } - if ((ret = rib4_insert(iface->vrf_id, nh->ipv4, nh->prefixlen, nh)) < 0) + if ((ret = rib4_insert(iface->vrf_id, nh->ipv4, nh->prefixlen, GR_RT_ORIGIN_LINK, nh)) < 0) return api_out(-ret, 0); gr_vec_add(ifaddrs->nh, nh); diff --git a/modules/ip/control/gr_ip4_control.h b/modules/ip/control/gr_ip4_control.h index b2003669..96b9f8f3 100644 --- a/modules/ip/control/gr_ip4_control.h +++ b/modules/ip/control/gr_ip4_control.h @@ -29,7 +29,13 @@ void arp_probe_input_cb(struct rte_mbuf *m); struct nexthop *rib4_lookup(uint16_t vrf_id, ip4_addr_t ip); struct nexthop *rib4_lookup_exact(uint16_t vrf_id, ip4_addr_t ip, uint8_t prefixlen); -int rib4_insert(uint16_t vrf_id, ip4_addr_t ip, uint8_t prefixlen, struct nexthop *); +int rib4_insert( + uint16_t vrf_id, + ip4_addr_t ip, + uint8_t prefixlen, + gr_rt_origin_t origin, + struct nexthop *nh +); int rib4_delete(uint16_t vrf_id, ip4_addr_t ip, uint8_t prefixlen); void rib4_cleanup(struct nexthop *); diff --git a/modules/ip/control/nexthop.c b/modules/ip/control/nexthop.c index dc0d4bca..1ed9fabf 100644 --- a/modules/ip/control/nexthop.c +++ b/modules/ip/control/nexthop.c @@ -57,7 +57,7 @@ void nh4_unreachable_cb(struct rte_mbuf *m) { // Create an associated /32 route so that next packets take it // in priority with a single route lookup. - if (rib4_insert(nh->vrf_id, dst, 32, remote) < 0) { + if (rib4_insert(nh->vrf_id, dst, 32, GR_RT_ORIGIN_INTERNAL, remote) < 0) { LOG(ERR, "failed to insert route: %s", strerror(errno)); goto free; } @@ -121,7 +121,7 @@ void arp_probe_input_cb(struct rte_mbuf *m) { goto free; } // Add an internal /32 route to reference the newly created nexthop. - if (rib4_insert(iface->vrf_id, sip, 32, nh) < 0) { + if (rib4_insert(iface->vrf_id, sip, 32, GR_RT_ORIGIN_INTERNAL, nh) < 0) { LOG(ERR, "ip4_nexthop_insert: %s", strerror(errno)); goto free; } @@ -199,7 +199,7 @@ static struct api_out nh4_add(const void *request, void ** /*response*/) { nh->mac = req->nh.mac; nh->flags = GR_NH_F_STATIC | GR_NH_F_REACHABLE; - ret = rib4_insert(nh->vrf_id, nh->ipv4, 32, nh); + ret = rib4_insert(nh->vrf_id, nh->ipv4, 32, GR_RT_ORIGIN_LINK, nh); return api_out(-ret, 0); } diff --git a/modules/ip/control/route.c b/modules/ip/control/route.c index 46a16437..74d420b1 100644 --- a/modules/ip/control/route.c +++ b/modules/ip/control/route.c @@ -29,6 +29,7 @@ static struct rte_rib **vrf_ribs; static struct rte_rib_conf rib_conf = { + .ext_sz = sizeof(gr_rt_origin_t), .max_nodes = IP4_MAX_ROUTES, }; @@ -121,9 +122,16 @@ struct nexthop *rib4_lookup_exact(uint16_t vrf_id, ip4_addr_t ip, uint8_t prefix return nh_id_to_ptr(nh_id); } -int rib4_insert(uint16_t vrf_id, ip4_addr_t ip, uint8_t prefixlen, struct nexthop *nh) { +int rib4_insert( + uint16_t vrf_id, + ip4_addr_t ip, + uint8_t prefixlen, + gr_rt_origin_t origin, + struct nexthop *nh +) { struct rte_rib *rib = get_or_create_rib(vrf_id); struct rte_rib_node *rn; + gr_rt_origin_t *o; int ret; nexthop_incref(nh); @@ -143,10 +151,20 @@ int rib4_insert(uint16_t vrf_id, ip4_addr_t ip, uint8_t prefixlen, struct nextho } rte_rib_set_nh(rn, nh_ptr_to_id(nh)); + o = rte_rib_get_ext(rn); + *o = origin; fib4_insert(vrf_id, ip, prefixlen, nh); - gr_event_push( - IP_EVENT_ROUTE_ADD, &(struct gr_ip4_route) {{ip, prefixlen}, nh->ipv4, nh->vrf_id} - ); + if (origin != GR_RT_ORIGIN_INTERNAL) { + gr_event_push( + IP_EVENT_ROUTE_ADD, + &(struct gr_ip4_route) { + {ip, prefixlen}, + nh->ipv4, + nh->vrf_id, + origin, + } + ); + } return 0; fail: @@ -156,21 +174,37 @@ int rib4_insert(uint16_t vrf_id, ip4_addr_t ip, uint8_t prefixlen, struct nextho int rib4_delete(uint16_t vrf_id, ip4_addr_t ip, uint8_t prefixlen) { struct rte_rib *rib = get_rib(vrf_id); + gr_rt_origin_t *o, origin; + struct rte_rib_node *rn; struct nexthop *nh; + uintptr_t nh_id; if (rib == NULL) return -errno; - nh = rib4_lookup_exact(vrf_id, ip, prefixlen); - if (nh == NULL) + rn = rte_rib_lookup_exact(rib, rte_be_to_cpu_32(ip), prefixlen); + if (rn == NULL) return errno_set(ENOENT); + o = rte_rib_get_ext(rn); + origin = *o; + rte_rib_get_nh(rn, &nh_id); + nh = nh_id_to_ptr(nh_id); + rte_rib_remove(rib, rte_be_to_cpu_32(ip), prefixlen); fib4_remove(vrf_id, ip, prefixlen); - gr_event_push( - IP_EVENT_ROUTE_DEL, &(struct gr_ip4_route) {{ip, prefixlen}, nh->ipv4, nh->vrf_id} - ); + if (origin != GR_RT_ORIGIN_INTERNAL) { + gr_event_push( + IP_EVENT_ROUTE_DEL, + &(struct gr_ip4_route) { + {ip, prefixlen}, + nh->ipv4, + nh->vrf_id, + origin, + } + ); + } nexthop_decref(nh); return 0; @@ -181,6 +215,9 @@ static struct api_out route4_add(const void *request, void ** /*response*/) { struct nexthop *nh; int ret; + if (req->origin == GR_RT_ORIGIN_INTERNAL) + return api_out(EINVAL, 0); + // ensure route gateway is reachable if ((nh = rib4_lookup(req->vrf_id, req->nh)) == NULL) return api_out(EHOSTUNREACH, 0); @@ -194,7 +231,7 @@ static struct api_out route4_add(const void *request, void ** /*response*/) { } // if route insert fails, the created nexthop will be freed - ret = rib4_insert(req->vrf_id, req->dest.ip, req->dest.prefixlen, nh); + ret = rib4_insert(req->vrf_id, req->dest.ip, req->dest.prefixlen, req->origin, nh); if (ret == -EEXIST && req->exist_ok) ret = 0; @@ -240,6 +277,7 @@ static struct api_out route4_get(const void *request, void **response) { static int route4_count(uint16_t vrf_id) { struct rte_rib_node *rn = NULL; + gr_rt_origin_t *origin; struct rte_rib *rib; int num; @@ -248,11 +286,17 @@ static int route4_count(uint16_t vrf_id) { return -errno; num = 0; - while ((rn = rte_rib_get_nxt(rib, 0, 0, rn, RTE_RIB_GET_NXT_ALL)) != NULL) - num++; + while ((rn = rte_rib_get_nxt(rib, 0, 0, rn, RTE_RIB_GET_NXT_ALL)) != NULL) { + origin = rte_rib_get_ext(rn); + if (*origin != GR_RT_ORIGIN_INTERNAL) + num++; + } // FIXME: remove this when rte_rib_get_nxt returns a default route, if any is configured - if (rte_rib_lookup_exact(rib, 0, 0) != NULL) - num++; + if ((rn = rte_rib_lookup_exact(rib, 0, 0)) != NULL) { + origin = rte_rib_get_ext(rn); + if (*origin != GR_RT_ORIGIN_INTERNAL) + num++; + } return num; } @@ -260,6 +304,7 @@ static int route4_count(uint16_t vrf_id) { static void route4_rib_to_api(struct gr_ip4_route_list_resp *resp, uint16_t vrf_id) { struct rte_rib_node *rn = NULL; struct gr_ip4_route *r; + gr_rt_origin_t *origin; struct rte_rib *rib; uintptr_t nh_id; uint32_t ip; @@ -268,6 +313,9 @@ static void route4_rib_to_api(struct gr_ip4_route_list_resp *resp, uint16_t vrf_ assert(rib != NULL); while ((rn = rte_rib_get_nxt(rib, 0, 0, rn, RTE_RIB_GET_NXT_ALL)) != NULL) { + origin = rte_rib_get_ext(rn); + if (*origin == GR_RT_ORIGIN_INTERNAL) + continue; r = &resp->routes[resp->n_routes++]; rte_rib_get_nh(rn, &nh_id); rte_rib_get_ip(rn, &ip); @@ -275,15 +323,20 @@ static void route4_rib_to_api(struct gr_ip4_route_list_resp *resp, uint16_t vrf_ r->dest.ip = htonl(ip); r->nh = nh_id_to_ptr(nh_id)->ipv4; r->vrf_id = vrf_id; + r->origin = *origin; } // FIXME: remove this when rte_rib_get_nxt returns a default route, if any is configured if ((rn = rte_rib_lookup_exact(rib, 0, 0)) != NULL) { + origin = rte_rib_get_ext(rn); + if (*origin == GR_RT_ORIGIN_INTERNAL) + return; r = &resp->routes[resp->n_routes++]; rte_rib_get_nh(rn, &nh_id); r->dest.ip = 0; r->dest.prefixlen = 0; r->nh = nh_id_to_ptr(nh_id)->ipv4; r->vrf_id = vrf_id; + r->origin = *origin; } } diff --git a/modules/srv6/control_headend.c b/modules/srv6/control_headend.c index cd0f34f8..07996815 100644 --- a/modules/srv6/control_headend.c +++ b/modules/srv6/control_headend.c @@ -260,7 +260,14 @@ static struct api_out srv6_steer_add(const void *request, void ** /*response*/) return api_out(errno, 0); nh->prefixlen = req->l3.dest4.prefixlen; - if (rib4_insert(req->l3.vrf_id, req->l3.dest4.ip, req->l3.dest4.prefixlen, nh) < 0) + if (rib4_insert( + req->l3.vrf_id, + req->l3.dest4.ip, + req->l3.dest4.prefixlen, + GR_RT_ORIGIN_LINK, + nh + ) + < 0) return api_out(errno, 0); } nh->flags |= GR_NH_F_GATEWAY | GR_NH_F_STATIC | GR_NH_F_REACHABLE; From 0dba516c2fcc1851e9fc50cd7061bd344c8363cc Mon Sep 17 00:00:00 2001 From: Robin Jarry Date: Wed, 16 Apr 2025 15:09:38 +0200 Subject: [PATCH 06/25] ip6: add route origin field Include a new origin field in IPv6 route objects exchanged in the API. Allow users to set an arbitrary value. This is mostly intended for routing daemons to identify if a route was learned from BGP, OSPF, etc. Or if it was configured manually by the user. Store the origin value in the RIB6 node extension private memory. Display the origin of routes in the CLI. For now, all routes explicitly added by the CLI are GR_RT_ORIGIN_USER. Other API clients can specify any suitable value. GR_RT_ORIGIN_INTERNAL(255) is reserved for dynamically added IPv6 routes when learning new next hops after receiving NDP solicitations or advertisements. Hide these routes on purpose when replying to a GR_IP6_ROUTE_LIST request to avoid confusion. Signed-off-by: Robin Jarry --- modules/infra/cli/events.c | 10 ++-- modules/ip6/api/gr_ip6.h | 2 + modules/ip6/cli/route.c | 4 +- modules/ip6/control/address.c | 3 +- modules/ip6/control/gr_ip6_control.h | 1 + modules/ip6/control/nexthop.c | 25 ++++++-- modules/ip6/control/route.c | 88 ++++++++++++++++++++-------- modules/srv6/control_headend.c | 1 + modules/srv6/control_local.c | 2 +- 9 files changed, 101 insertions(+), 35 deletions(-) diff --git a/modules/infra/cli/events.c b/modules/infra/cli/events.c index 76e9ea11..20c9fd74 100644 --- a/modules/infra/cli/events.c +++ b/modules/infra/cli/events.c @@ -85,18 +85,20 @@ static cmd_status_t events_show(const struct gr_api_client *c, const struct ec_p case IP6_EVENT_ROUTE_ADD: assert(e->payload_len == sizeof(*r6)); r6 = PAYLOAD(e); - printf("> route add: %6p/%d via %6p\n", + printf("> route add: %6p/%d via %6p origin %s\n", &r6->dest.ip, r6->dest.prefixlen, - &r6->nh); + &r6->nh, + gr_rt_origin_name(r6->origin)); break; case IP6_EVENT_ROUTE_DEL: assert(e->payload_len == sizeof(*r6)); r6 = PAYLOAD(e); - printf("> route del: %6p/%d via %6p\n", + printf("> route del: %6p/%d via %6p origin %s\n", &r6->dest.ip, r6->dest.prefixlen, - &r6->nh); + &r6->nh, + gr_rt_origin_name(r6->origin)); break; case NEXTHOP_EVENT_NEW: assert(e->payload_len == sizeof(*nh)); diff --git a/modules/ip6/api/gr_ip6.h b/modules/ip6/api/gr_ip6.h index 4de79f32..030c64ff 100644 --- a/modules/ip6/api/gr_ip6.h +++ b/modules/ip6/api/gr_ip6.h @@ -20,6 +20,7 @@ struct gr_ip6_route { struct ip6_net dest; struct rte_ipv6_addr nh; uint16_t vrf_id; + gr_rt_origin_t origin; }; #define GR_IP6_MODULE 0xfeed @@ -64,6 +65,7 @@ struct gr_ip6_route_add_req { uint16_t vrf_id; struct ip6_net dest; struct rte_ipv6_addr nh; + gr_rt_origin_t origin; uint8_t exist_ok; }; diff --git a/modules/ip6/cli/route.c b/modules/ip6/cli/route.c index 6766aac2..4c41c78d 100644 --- a/modules/ip6/cli/route.c +++ b/modules/ip6/cli/route.c @@ -16,7 +16,7 @@ #include static cmd_status_t route6_add(const struct gr_api_client *c, const struct ec_pnode *p) { - struct gr_ip6_route_add_req req = {.exist_ok = true}; + struct gr_ip6_route_add_req req = {.exist_ok = true, .origin = GR_RT_ORIGIN_USER}; if (arg_ip6_net(p, "DEST", &req.dest, true) < 0) return CMD_ERROR; @@ -66,6 +66,7 @@ static cmd_status_t route6_list(const struct gr_api_client *c, const struct ec_p scols_table_new_column(table, "VRF", 0, 0); scols_table_new_column(table, "DESTINATION", 0, 0); scols_table_new_column(table, "NEXT_HOP", 0, 0); + scols_table_new_column(table, "ORIGIN", 0, 0); scols_table_set_column_separator(table, " "); for (size_t i = 0; i < resp->n_routes; i++) { @@ -74,6 +75,7 @@ static cmd_status_t route6_list(const struct gr_api_client *c, const struct ec_p scols_line_sprintf(line, 0, "%u", route->vrf_id); scols_line_sprintf(line, 1, IP6_F "/%hhu", &route->dest, route->dest.prefixlen); scols_line_sprintf(line, 2, IP6_F, &route->nh); + scols_line_sprintf(line, 3, "%s", gr_rt_origin_name(route->origin)); } scols_print_table(table); diff --git a/modules/ip6/control/address.c b/modules/ip6/control/address.c index d3b22159..1d232438 100644 --- a/modules/ip6/control/address.c +++ b/modules/ip6/control/address.c @@ -175,7 +175,8 @@ iface6_addr_add(const struct iface *iface, const struct rte_ipv6_addr *ip, uint8 return errno_set(-ret); } - if ((ret = rib6_insert(iface->vrf_id, iface->id, ip, nh->prefixlen, nh)) < 0) + ret = rib6_insert(iface->vrf_id, iface->id, ip, nh->prefixlen, GR_RT_ORIGIN_LINK, nh); + if (ret < 0) return errno_set(-ret); gr_vec_add(addrs->nh, nh); diff --git a/modules/ip6/control/gr_ip6_control.h b/modules/ip6/control/gr_ip6_control.h index 4b74ae4a..e2341105 100644 --- a/modules/ip6/control/gr_ip6_control.h +++ b/modules/ip6/control/gr_ip6_control.h @@ -35,6 +35,7 @@ int rib6_insert( uint16_t iface_id, const struct rte_ipv6_addr *, uint8_t prefixlen, + gr_rt_origin_t origin, struct nexthop *nh ); int rib6_delete( diff --git a/modules/ip6/control/nexthop.c b/modules/ip6/control/nexthop.c index e02b8371..dda4c94b 100644 --- a/modules/ip6/control/nexthop.c +++ b/modules/ip6/control/nexthop.c @@ -59,7 +59,15 @@ void nh6_unreachable_cb(struct rte_mbuf *m) { // Create an associated /128 route so that next packets take it // in priority with a single route lookup. - if (rib6_insert(nh->vrf_id, nh->iface_id, dst, RTE_IPV6_MAX_DEPTH, remote) < 0) { + int ret = rib6_insert( + nh->vrf_id, + nh->iface_id, + dst, + RTE_IPV6_MAX_DEPTH, + GR_RT_ORIGIN_INTERNAL, + remote + ); + if (ret < 0) { LOG(ERR, "failed to insert route: %s", strerror(errno)); goto free; } @@ -148,8 +156,15 @@ void ndp_probe_input_cb(struct rte_mbuf *m) { } // Add an internal /128 route to reference the newly created nexthop. - if (rib6_insert(iface->vrf_id, iface->id, remote, RTE_IPV6_MAX_DEPTH, nh) - < 0) { + int ret = rib6_insert( + iface->vrf_id, + iface->id, + remote, + RTE_IPV6_MAX_DEPTH, + GR_RT_ORIGIN_INTERNAL, + nh + ); + if (ret < 0) { LOG(ERR, "ip6_route_insert: %s", strerror(errno)); goto free; } @@ -226,7 +241,9 @@ static struct api_out nh6_add(const void *request, void ** /*response*/) { nh->mac = req->nh.mac; nh->flags = GR_NH_F_STATIC | GR_NH_F_REACHABLE; - ret = rib6_insert(nh->vrf_id, nh->iface_id, &nh->ipv6, RTE_IPV6_MAX_DEPTH, nh); + ret = rib6_insert( + nh->vrf_id, nh->iface_id, &nh->ipv6, RTE_IPV6_MAX_DEPTH, GR_RT_ORIGIN_LINK, nh + ); return api_out(-ret, 0); } diff --git a/modules/ip6/control/route.c b/modules/ip6/control/route.c index db7fade0..0d4057c0 100644 --- a/modules/ip6/control/route.c +++ b/modules/ip6/control/route.c @@ -28,6 +28,7 @@ static struct rte_rib6 **vrf_ribs; static struct rte_rib6_conf rib6_conf = { + .ext_sz = sizeof(gr_rt_origin_t), .max_nodes = IP6_MAX_ROUTES, }; @@ -136,12 +137,14 @@ int rib6_insert( uint16_t iface_id, const struct rte_ipv6_addr *ip, uint8_t prefixlen, + gr_rt_origin_t origin, struct nexthop *nh ) { struct rte_rib6 *rib = get_or_create_rib6(vrf_id); const struct rte_ipv6_addr *scoped_ip; struct rte_ipv6_addr tmp; struct rte_rib6_node *rn; + gr_rt_origin_t *o; int ret; nexthop_incref(nh); @@ -162,15 +165,20 @@ int rib6_insert( } rte_rib6_set_nh(rn, nh_ptr_to_id(nh)); + o = rte_rib6_get_ext(rn); + *o = origin; fib6_insert(vrf_id, iface_id, scoped_ip, prefixlen, nh); - gr_event_push( - IP6_EVENT_ROUTE_ADD, - &(struct gr_ip6_route) { - {*ip, prefixlen}, - nh->ipv6, - vrf_id, - } - ); + if (origin != GR_RT_ORIGIN_INTERNAL) { + gr_event_push( + IP6_EVENT_ROUTE_ADD, + &(struct gr_ip6_route) { + {*ip, prefixlen}, + nh->ipv6, + vrf_id, + origin, + } + ); + } return 0; fail: @@ -186,27 +194,38 @@ int rib6_delete( ) { struct rte_rib6 *rib = get_rib6(vrf_id); const struct rte_ipv6_addr *scoped_ip; + gr_rt_origin_t *o, origin; struct rte_ipv6_addr tmp; + struct rte_rib6_node *rn; struct nexthop *nh; + uintptr_t nh_id; if (rib == NULL) return -errno; - nh = rib6_lookup_exact(vrf_id, iface_id, ip, prefixlen); - if (nh == NULL) + scoped_ip = addr6_linklocal_scope(ip, &tmp, iface_id); + rn = rte_rib6_lookup_exact(rib, scoped_ip, prefixlen); + if (rn == NULL) return errno_set(ENOENT); - scoped_ip = addr6_linklocal_scope(ip, &tmp, iface_id); + o = rte_rib6_get_ext(rn); + origin = *o; + rte_rib6_get_nh(rn, &nh_id); + nh = nh_id_to_ptr(nh_id); + rte_rib6_remove(rib, scoped_ip, prefixlen); - gr_event_push( - IP6_EVENT_ROUTE_DEL, - &(struct gr_ip6_route) { - {*ip, prefixlen}, - nh->ipv6, - vrf_id, - } - ); + if (origin != GR_RT_ORIGIN_INTERNAL) { + gr_event_push( + IP6_EVENT_ROUTE_DEL, + &(struct gr_ip6_route) { + {*ip, prefixlen}, + nh->ipv6, + vrf_id, + origin, + } + ); + } nexthop_decref(nh); return 0; @@ -217,6 +236,9 @@ static struct api_out route6_add(const void *request, void ** /*response*/) { struct nexthop *nh; int ret; + if (req->origin == GR_RT_ORIGIN_INTERNAL) + return api_out(EINVAL, 0); + // ensure route gateway is reachable if ((nh = rib6_lookup(req->vrf_id, GR_IFACE_ID_UNDEF, &req->nh)) == NULL) return api_out(EHOSTUNREACH, 0); @@ -230,7 +252,9 @@ static struct api_out route6_add(const void *request, void ** /*response*/) { } // if route insert fails, the created nexthop will be freed - ret = rib6_insert(req->vrf_id, nh->iface_id, &req->dest.ip, req->dest.prefixlen, nh); + ret = rib6_insert( + req->vrf_id, nh->iface_id, &req->dest.ip, req->dest.prefixlen, req->origin, nh + ); if (ret == -EEXIST && req->exist_ok) ret = 0; @@ -284,6 +308,7 @@ static struct api_out route6_get(const void *request, void **response) { static int route6_count(uint16_t vrf_id) { struct rte_ipv6_addr zero = RTE_IPV6_ADDR_UNSPEC; struct rte_rib6_node *rn = NULL; + gr_rt_origin_t *origin; struct rte_rib6 *rib; int num; @@ -292,11 +317,17 @@ static int route6_count(uint16_t vrf_id) { return -errno; num = 0; - while ((rn = rte_rib6_get_nxt(rib, &zero, 0, rn, RTE_RIB6_GET_NXT_ALL)) != NULL) - num++; + while ((rn = rte_rib6_get_nxt(rib, &zero, 0, rn, RTE_RIB6_GET_NXT_ALL)) != NULL) { + origin = rte_rib6_get_ext(rn); + if (*origin != GR_RT_ORIGIN_INTERNAL) + num++; + } // check if there is a default route configured - if (rte_rib6_lookup_exact(rib, &zero, 0) != NULL) - num++; + if ((rn = rte_rib6_lookup_exact(rib, &zero, 0)) != NULL) { + origin = rte_rib6_get_ext(rn); + if (*origin != GR_RT_ORIGIN_INTERNAL) + num++; + } return num; } @@ -306,6 +337,7 @@ static void route6_rib_to_api(struct gr_ip6_route_list_resp *resp, uint16_t vrf_ struct rte_rib6_node *rn = NULL; struct rte_ipv6_addr tmp; struct gr_ip6_route *r; + gr_rt_origin_t *origin; struct rte_rib6 *rib; uintptr_t nh_id; @@ -313,6 +345,9 @@ static void route6_rib_to_api(struct gr_ip6_route_list_resp *resp, uint16_t vrf_ assert(rib != NULL); while ((rn = rte_rib6_get_nxt(rib, &zero, 0, rn, RTE_RIB6_GET_NXT_ALL)) != NULL) { + origin = rte_rib6_get_ext(rn); + if (*origin == GR_RT_ORIGIN_INTERNAL) + continue; r = &resp->routes[resp->n_routes++]; rte_rib6_get_nh(rn, &nh_id); rte_rib6_get_ip(rn, &r->dest.ip); @@ -320,14 +355,19 @@ static void route6_rib_to_api(struct gr_ip6_route_list_resp *resp, uint16_t vrf_ r->nh = nh_id_to_ptr(nh_id)->ipv6; r->vrf_id = vrf_id; r->dest.ip = *addr6_linklocal_unscope(&r->dest.ip, &tmp); + r->origin = *origin; } // check if there is a default route configured if ((rn = rte_rib6_lookup_exact(rib, &zero, 0)) != NULL) { + origin = rte_rib6_get_ext(rn); + if (*origin == GR_RT_ORIGIN_INTERNAL) + return; r = &resp->routes[resp->n_routes++]; rte_rib6_get_nh(rn, &nh_id); memset(&r->dest, 0, sizeof(r->dest)); r->nh = nh_id_to_ptr(nh_id)->ipv6; r->vrf_id = vrf_id; + r->origin = *origin; } } diff --git a/modules/srv6/control_headend.c b/modules/srv6/control_headend.c index 07996815..788dec31 100644 --- a/modules/srv6/control_headend.c +++ b/modules/srv6/control_headend.c @@ -242,6 +242,7 @@ static struct api_out srv6_steer_add(const void *request, void ** /*response*/) GR_IFACE_ID_UNDEF, &req->l3.dest6.ip, req->l3.dest6.prefixlen, + GR_RT_ORIGIN_LINK, nh ) < 0) diff --git a/modules/srv6/control_local.c b/modules/srv6/control_local.c index 0d11074f..8f334f43 100644 --- a/modules/srv6/control_local.c +++ b/modules/srv6/control_local.c @@ -41,7 +41,7 @@ static struct api_out srv6_localsid_add(const void *request, void ** /*response* return api_out(errno, 0); nh->flags |= GR_NH_F_LOCAL | GR_NH_F_STATIC | GR_NH_F_REACHABLE; - r = rib6_insert(req->l.vrf_id, GR_IFACE_ID_UNDEF, &req->l.lsid, 128, nh); + r = rib6_insert(req->l.vrf_id, GR_IFACE_ID_UNDEF, &req->l.lsid, 128, GR_RT_ORIGIN_LINK, nh); if (r < 0) return api_out(-r, 0); From 651c80b54717569f46d7649a6d7a844cf065fcf2 Mon Sep 17 00:00:00 2001 From: Robin Jarry Date: Thu, 17 Apr 2025 17:08:00 +0200 Subject: [PATCH 07/25] address: always allow deleting addresses If a local address nexthop has routes referencing it (other than the connected route created when the address is configured), it should be allowed to delete the address in any case. Remove the check for the ref_count value. rib4_cleanup and rib6_cleanup will take care of removing any routes that are referencing the nexthop. Signed-off-by: Robin Jarry --- modules/ip/control/address.c | 3 --- modules/ip6/control/address.c | 3 --- 2 files changed, 6 deletions(-) diff --git a/modules/ip/control/address.c b/modules/ip/control/address.c index aaeb2e46..6db04b4f 100644 --- a/modules/ip/control/address.c +++ b/modules/ip/control/address.c @@ -123,9 +123,6 @@ static struct api_out addr_del(const void *request, void ** /*response*/) { return api_out(ENOENT, 0); } - if (nh->ref_count > 1) - return api_out(EBUSY, 0); - rib4_cleanup(nh); gr_vec_del(addrs->nh, i); diff --git a/modules/ip6/control/address.c b/modules/ip6/control/address.c index 1d232438..bb5e206d 100644 --- a/modules/ip6/control/address.c +++ b/modules/ip6/control/address.c @@ -233,9 +233,6 @@ static struct api_out addr6_del(const void *request, void ** /*response*/) { return api_out(ENOENT, 0); } - if (nh->ref_count > 1) - return api_out(EBUSY, 0); - rib6_cleanup(nh); // shift the remaining addresses From a5a14b600725371421b3c7b307ebdd021c036b9f Mon Sep 17 00:00:00 2001 From: Robin Jarry Date: Fri, 18 Apr 2025 12:20:42 +0200 Subject: [PATCH 08/25] nexthop: add more route origin values These values were taken from Linux. We only need them for display purposes in the CLI. Link: https://github.com/torvalds/linux/blob/v6.14/include/uapi/linux/rtnetlink.h#L298-L315 Signed-off-by: Robin Jarry --- modules/infra/api/gr_nexthop.h | 56 +++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/modules/infra/api/gr_nexthop.h b/modules/infra/api/gr_nexthop.h index a13755da..c91df1c5 100644 --- a/modules/infra/api/gr_nexthop.h +++ b/modules/infra/api/gr_nexthop.h @@ -111,13 +111,31 @@ typedef enum : uint8_t { GR_RT_ORIGIN_BOOT = 3, //!< Installed at boot?? (NH_ORIGIN_BOOT). GR_RT_ORIGIN_USER = 4, //!< Installed explicitly by user (NH_ORIGIN_STATIC). // Values 5 to 254 are allowed and are used by routing daemons. + GR_RT_ORIGIN_GATED = 8, // (RTPROT_GATED) + GR_RT_ORIGIN_RA = 9, // (RTPROT_RA) + GR_RT_ORIGIN_MRT = 10, // (RTPROT_MRT) + GR_RT_ORIGIN_ZEBRA = 11, // (RTPROT_ZEBRA) + GR_RT_ORIGIN_BIRD = 12, // (RTPROT_BIRD) + GR_RT_ORIGIN_DNROUTED = 13, // (RTPROT_DNROUTED) + GR_RT_ORIGIN_XORP = 14, // (RTPROT_XORP) + GR_RT_ORIGIN_NTK = 15, // (RTPROT_NTK) + GR_RT_ORIGIN_DHCP = 16, // (RTPROT_DHCP) + GR_RT_ORIGIN_MROUTED = 17, // (RTPROT_MROUTED) + GR_RT_ORIGIN_KEEPALIVED = 18, // (RTPROT_KEEPALIVED) + GR_RT_ORIGIN_BABEL = 42, // (RTPROT_BABEL) + GR_RT_ORIGIN_OPENR = 99, // (RTPROT_OPENR) + GR_RT_ORIGIN_BGP = 186, // (RTPROT_BGP) + GR_RT_ORIGIN_ISIS = 187, // (RTPROT_ISIS) + GR_RT_ORIGIN_OSPF = 188, // (RTPROT_OSPF) + GR_RT_ORIGIN_RIP = 189, // (RTPROT_RIP) + GR_RT_ORIGIN_EIGRP = 192, // (RTPROT_EIGRP) GR_RT_ORIGIN_INTERNAL = 255, //!< Reserved for internal use by grout. } gr_rt_origin_t; static inline const char *gr_rt_origin_name(gr_rt_origin_t origin) { switch (origin) { case GR_RT_ORIGIN_UNSPEC: - return "unspec"; + return ""; case GR_RT_ORIGIN_REDIRECT: return "redirect"; case GR_RT_ORIGIN_LINK: @@ -126,6 +144,42 @@ static inline const char *gr_rt_origin_name(gr_rt_origin_t origin) { return "boot"; case GR_RT_ORIGIN_USER: return "user"; + case GR_RT_ORIGIN_GATED: + return "gated"; + case GR_RT_ORIGIN_RA: + return "ra"; + case GR_RT_ORIGIN_MRT: + return "mrt"; + case GR_RT_ORIGIN_ZEBRA: + return "zebra"; + case GR_RT_ORIGIN_BIRD: + return "bird"; + case GR_RT_ORIGIN_DNROUTED: + return "dnrouted"; + case GR_RT_ORIGIN_XORP: + return "xorp"; + case GR_RT_ORIGIN_NTK: + return "ntk"; + case GR_RT_ORIGIN_DHCP: + return "dhcp"; + case GR_RT_ORIGIN_MROUTED: + return "mrouted"; + case GR_RT_ORIGIN_KEEPALIVED: + return "keepalived"; + case GR_RT_ORIGIN_BABEL: + return "babel"; + case GR_RT_ORIGIN_OPENR: + return "openr"; + case GR_RT_ORIGIN_BGP: + return "bgp"; + case GR_RT_ORIGIN_ISIS: + return "isis"; + case GR_RT_ORIGIN_OSPF: + return "ospf"; + case GR_RT_ORIGIN_RIP: + return "rip"; + case GR_RT_ORIGIN_EIGRP: + return "eigrp"; case GR_RT_ORIGIN_INTERNAL: return "INTERNAL"; } From 741eb867ec2ad62f0be897009968e360deeeccca Mon Sep 17 00:00:00 2001 From: Maxime Leroy Date: Thu, 17 Apr 2025 22:31:18 +0200 Subject: [PATCH 09/25] ip,ip6: fix uninitialized nexthop in "addr del" event When an IP address is removed, the RIB cleanup process (rib4/6_cleanup) resets associated nexthop memory before the addr del event is sent. As a result, the event includes a nexthop structure with all fields set to zero, even though a valid nexthop was previously in use. To reproduce: grcli show events > addr add: iface[1] 9.6.6.7 > route del: 9.6.6.7/24 via 9.6.6.7 origin link > nh del: iface 1 vrf 0 9.6.6.7 d2:f0:0c:bc:a4:10 > addr del: iface[0] 0x5d279f4eb1ac Or from another instance: grout# add ip address 9.6.6.7/24 iface p4 grout# del ip address 9.6.6.7/24 iface p4 Fix this by emitting the addr del event before the nexthop is cleaned up, ensuring the event still carries valid nexthop information. Signed-off-by: Maxime Leroy --- modules/ip/control/address.c | 3 ++- modules/ip6/control/address.c | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/ip/control/address.c b/modules/ip/control/address.c index 6db04b4f..e8cfc0fa 100644 --- a/modules/ip/control/address.c +++ b/modules/ip/control/address.c @@ -123,10 +123,11 @@ static struct api_out addr_del(const void *request, void ** /*response*/) { return api_out(ENOENT, 0); } + gr_event_push(IP_EVENT_ADDR_DEL, nh); + rib4_cleanup(nh); gr_vec_del(addrs->nh, i); - gr_event_push(IP_EVENT_ADDR_DEL, nh); return api_out(0, 0); } diff --git a/modules/ip6/control/address.c b/modules/ip6/control/address.c index bb5e206d..aff792c4 100644 --- a/modules/ip6/control/address.c +++ b/modules/ip6/control/address.c @@ -233,6 +233,8 @@ static struct api_out addr6_del(const void *request, void ** /*response*/) { return api_out(ENOENT, 0); } + gr_event_push(IP6_EVENT_ADDR_DEL, nh); + rib6_cleanup(nh); // shift the remaining addresses @@ -243,8 +245,6 @@ static struct api_out addr6_del(const void *request, void ** /*response*/) { if (mcast6_addr_del(iface_from_id(req->addr.iface_id), &solicited_node) < 0) return api_out(errno, 0); - gr_event_push(IP6_EVENT_ADDR_DEL, nh); - return api_out(0, 0); } From 4f9da2b44e9164c2f43765e67ee995c03fe53935 Mon Sep 17 00:00:00 2001 From: Maxime Leroy Date: Wed, 23 Apr 2025 10:30:26 +0200 Subject: [PATCH 10/25] nexthop: add more route origin values from frr These values were taken from FRR. We only need them for display purposes in the CLI. Link: https://github.com/FRRouting/frr/blob/frr-10.3/zebra/rt_netlink.h#L36 Signed-off-by: Maxime Leroy --- modules/infra/api/gr_nexthop.h | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/modules/infra/api/gr_nexthop.h b/modules/infra/api/gr_nexthop.h index c91df1c5..f679fff7 100644 --- a/modules/infra/api/gr_nexthop.h +++ b/modules/infra/api/gr_nexthop.h @@ -128,7 +128,15 @@ typedef enum : uint8_t { GR_RT_ORIGIN_ISIS = 187, // (RTPROT_ISIS) GR_RT_ORIGIN_OSPF = 188, // (RTPROT_OSPF) GR_RT_ORIGIN_RIP = 189, // (RTPROT_RIP) + GR_RT_ORIGIN_RIPNG = 190, // (RTPROT_RIPNG from zebra) + GR_RT_ORIGIN_NHRP = 191, // (RTPROT_NHRP from zebra) GR_RT_ORIGIN_EIGRP = 192, // (RTPROT_EIGRP) + GR_RT_ORIGIN_LDP = 193, // (RTPROT_LDP from zebra) + GR_RT_ORIGIN_SHARP = 194, // (RTPROT_SHARP from zebra) + GR_RT_ORIGIN_PBR = 195, // (RTPROT_PBR from zebra) + GR_RT_ORIGIN_ZSTATIC = 196, // (RTPROT_ZSTATIC from zebra) + GR_RT_ORIGIN_OPENFABRIC = 197, // (RTPROT_OPENFABIC from zebra) + GR_RT_ORIGIN_SRTE = 198, // (RTPROT_SRTE from zebra) GR_RT_ORIGIN_INTERNAL = 255, //!< Reserved for internal use by grout. } gr_rt_origin_t; @@ -178,8 +186,24 @@ static inline const char *gr_rt_origin_name(gr_rt_origin_t origin) { return "ospf"; case GR_RT_ORIGIN_RIP: return "rip"; + case GR_RT_ORIGIN_RIPNG: + return "ripng"; + case GR_RT_ORIGIN_NHRP: + return "nhrp"; case GR_RT_ORIGIN_EIGRP: return "eigrp"; + case GR_RT_ORIGIN_LDP: + return "ldp"; + case GR_RT_ORIGIN_SHARP: + return "sharp"; + case GR_RT_ORIGIN_PBR: + return "pbr"; + case GR_RT_ORIGIN_ZSTATIC: + return "zebra_static"; + case GR_RT_ORIGIN_OPENFABRIC: + return "openfabric"; + case GR_RT_ORIGIN_SRTE: + return "srte"; case GR_RT_ORIGIN_INTERNAL: return "INTERNAL"; } From 0087baaa1b069976df0ea90a7cc7ad0130a2e7ab Mon Sep 17 00:00:00 2001 From: Maxime Leroy Date: Fri, 25 Apr 2025 16:20:24 +0200 Subject: [PATCH 11/25] infra: send iface down notification before deleting iface To support FRR, interfaces created or deleted in Grout must be synchronized with FRR. FRR listens for interface notifications from Grout to keep its internal state consistent. When FRR receives an IFACE_EVENT_PRE_REMOVE notification, the dplane plugin in FRR attempts to remove the corresponding interface from memory. However, Zebra fails to delete it if the interface is still marked as "up". In the Linux kernel, when an interface is deleted, a netlink message is first sent to indicate that the interface is down. Grout should follow the same behavior. Signed-off-by: Maxime Leroy --- modules/infra/control/iface.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/infra/control/iface.c b/modules/infra/control/iface.c index a5b1e880..5a1fbffb 100644 --- a/modules/infra/control/iface.c +++ b/modules/infra/control/iface.c @@ -244,6 +244,11 @@ int iface_destroy(uint16_t ifid) { if (gr_vec_len(iface->subinterfaces) != 0) return errno_set(EBUSY); + /* interface is still up, send status down */ + if (iface->flags & GR_IFACE_F_UP) { + iface->flags &= ~GR_IFACE_F_UP; + gr_event_push(IFACE_EVENT_STATUS_DOWN, iface); + } gr_event_push(IFACE_EVENT_PRE_REMOVE, iface); ifaces[ifid] = NULL; From da2a0de541320ecf4a76837fbacdf51dd5b4639a Mon Sep 17 00:00:00 2001 From: Maxime Leroy Date: Mon, 28 Apr 2025 11:30:41 +0200 Subject: [PATCH 12/25] smoke: make nexthop ageing test more robust on slow machines The nexthop ageing smoke test could fail intermittently on slow or heavily loaded machines, where state transitions (like nexthop reachability or ageing) take slightly longer than expected. This patch introduces a check_nexthop() helper that polls grcli show ip nexthop for up to 5 seconds, instead of assuming an immediate state change. This ensures that the test is tolerant to minor delays without compromising on correctness. Signed-off-by: Maxime Leroy --- smoke/nexthop_ageing_test.sh | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/smoke/nexthop_ageing_test.sh b/smoke/nexthop_ageing_test.sh index 0386b522..a84b13b9 100755 --- a/smoke/nexthop_ageing_test.sh +++ b/smoke/nexthop_ageing_test.sh @@ -4,6 +4,27 @@ . $(dirname $0)/_init.sh +check_nexthop() { + local ip="$1" + local expect_reacheable="$2" + local timeout=5 + for i in $(seq 1 $timeout); do + grcli show ip nexthop | grep -qE "$ip.+reachable"; + local result=$? + + if [ "$expect_reacheable" = "true" ] && [ "$result" -eq 0 ]; then + return 0 + fi + if [ "$expect_reacheable" = "false" ] && [ "$result" -ne 0 ]; then + return 0 + fi + + sleep 1 + done + + return 1 +} + p0=${run_id}0 p1=${run_id}1 @@ -38,8 +59,9 @@ show ip address EOF # ensure that nexthops are still reachable -grcli show ip nexthop | grep -E '172\.16\.0\.2.+reachable' || fail "nexthop should be reachable" -grcli show ip nexthop | grep -E '172\.16\.1\.2.+reachable' || fail "nexthop should be reachable" +check_nexthop '172\.16\.0\.2' true || fail "nexthop 172.16.0.2 should be reachable" +check_nexthop '172\.16\.1\.2' true || fail "nexthop 172.16.1.2 should be reachable" + # ensure addresses were not destroyed grcli show ip address | grep -E "^$p0[[:space:]]+172\\.16\\.0\\.1/24$" || fail "addresses were destroyed" grcli show ip address | grep -E "^$p1[[:space:]]+172\\.16\\.1\\.1/24$" || fail "addresses were destroyed" @@ -52,8 +74,9 @@ ip -n $p1 link set $p1 down sleep 3 # ensure that nexthops have been aged out and destroyed -! grcli show ip nexthop | grep -q '172\.16\.0\.2.*reachable' || fail "nexthop should be destroyed" -! grcli show ip nexthop | grep -q '172\.16\.1\.2.*reachable' || fail "nexthop should be destroyed" +check_nexthop '172\.16\.0\.2' false || fail "nexthop 172.16.0.2 should be destroyed" +check_nexthop '172\.16\.1\.2' false || fail "nexthop 172.16.1.2 should be destroyed" + # ensure addresses were not destroyed grcli show ip address | grep -E "^$p0[[:space:]]+172\\.16\\.0\\.1/24$" || fail "addresses were destroyed" grcli show ip address | grep -E "^$p1[[:space:]]+172\\.16\\.1\\.1/24$" || fail "addresses were destroyed" From ea44955ecb0e174682940f79638841bcfab1c7f1 Mon Sep 17 00:00:00 2001 From: Maxime Leroy Date: Wed, 30 Apr 2025 09:42:18 +0200 Subject: [PATCH 13/25] treewide: prefix all event constants with GR_EVENT EVENT macro definitions are part of the exposed API. This commit adds a `GR_` prefix to all occurrences of `*_EVENT_` to namespace them properly and avoid potential conflicts. Signed-off-by: Maxime Leroy --- main/api.c | 6 +++--- modules/infra/api/gr_infra.h | 14 ++++++------- modules/infra/api/gr_nexthop.h | 8 ++++---- modules/infra/api/iface.c | 10 ++++----- modules/infra/api/trace.c | 2 +- modules/infra/cli/events.c | 32 ++++++++++++++--------------- modules/infra/control/iface.c | 26 +++++++++++------------ modules/infra/control/nexthop.c | 10 ++++----- modules/infra/control/port.c | 10 ++++----- modules/infra/control/vlan.c | 6 +++--- modules/infra/control/vrf.c | 12 +++++------ modules/infra/control/xconnect.c | 4 ++-- modules/ip/api/gr_ip4.h | 10 ++++----- modules/ip/control/address.c | 8 ++++---- modules/ip/control/nexthop.c | 2 +- modules/ip/control/route.c | 6 +++--- modules/ip6/api/gr_ip6.h | 10 ++++----- modules/ip6/control/address.c | 16 +++++++-------- modules/ip6/control/nexthop.c | 2 +- modules/ip6/control/route.c | 6 +++--- modules/ip6/control/router_advert.c | 6 +++--- modules/ipip/control.c | 2 +- 22 files changed, 104 insertions(+), 104 deletions(-) diff --git a/main/api.c b/main/api.c index 03e70d16..f3c3607b 100644 --- a/main/api.c +++ b/main/api.c @@ -39,13 +39,13 @@ static evutil_socket_t *all_events_subs; // struct module_subscribers // sockets // +--------+ -// IFACE_EVENT_UNKNOWN | 0x0000 | +// GR_EVENT_IFACE_UNKNOWN | 0x0000 | // |--------| -// IFACE_EVENT_POST_ADD | 0x0001 | +// GR_EVENT_IFACE_POST_ADD | 0x0001 | // |--------| // | ...... | // |--------| -// NEXTHOP_EVENT_UPDATE -> | 0x0102 | -> gr_vec of evutil_socket_t +// GR_EVENT_NEXTHOP_UPDATE -> | 0x0102 | -> gr_vec of evutil_socket_t // |--------| // | ...... | // |--------| diff --git a/modules/infra/api/gr_infra.h b/modules/infra/api/gr_infra.h index 661049ed..4a50628d 100644 --- a/modules/infra/api/gr_infra.h +++ b/modules/infra/api/gr_infra.h @@ -137,13 +137,13 @@ struct gr_infra_stat { //! Interface events. typedef enum { - IFACE_EVENT_UNKNOWN = EVENT_TYPE(GR_INFRA_MODULE, 0x0000), - IFACE_EVENT_POST_ADD = EVENT_TYPE(GR_INFRA_MODULE, 0x0001), - IFACE_EVENT_PRE_REMOVE = EVENT_TYPE(GR_INFRA_MODULE, 0x0002), - IFACE_EVENT_POST_RECONFIG = EVENT_TYPE(GR_INFRA_MODULE, 0x0003), - IFACE_EVENT_STATUS_UP = EVENT_TYPE(GR_INFRA_MODULE, 0x0004), - IFACE_EVENT_STATUS_DOWN = EVENT_TYPE(GR_INFRA_MODULE, 0x0005), -} iface_event_t; + GR_EVENT_IFACE_UNKNOWN = EVENT_TYPE(GR_INFRA_MODULE, 0x0000), + GR_EVENT_IFACE_POST_ADD = EVENT_TYPE(GR_INFRA_MODULE, 0x0001), + GR_EVENT_IFACE_PRE_REMOVE = EVENT_TYPE(GR_INFRA_MODULE, 0x0002), + GR_EVENT_IFACE_POST_RECONFIG = EVENT_TYPE(GR_INFRA_MODULE, 0x0003), + GR_EVENT_IFACE_STATUS_UP = EVENT_TYPE(GR_INFRA_MODULE, 0x0004), + GR_EVENT_IFACE_STATUS_DOWN = EVENT_TYPE(GR_INFRA_MODULE, 0x0005), +} gr_event_iface_t; // ifaces /////////////////////////////////////////////////////////////////////// #define GR_INFRA_IFACE_ADD REQUEST_TYPE(GR_INFRA_MODULE, 0x0001) diff --git a/modules/infra/api/gr_nexthop.h b/modules/infra/api/gr_nexthop.h index f679fff7..1f9d5ae8 100644 --- a/modules/infra/api/gr_nexthop.h +++ b/modules/infra/api/gr_nexthop.h @@ -51,10 +51,10 @@ struct gr_nexthop { //! Nexthop events. typedef enum { - NEXTHOP_EVENT_NEW = EVENT_TYPE(GR_INFRA_MODULE, 0x0100), - NEXTHOP_EVENT_DELETE = EVENT_TYPE(GR_INFRA_MODULE, 0x0101), - NEXTHOP_EVENT_UPDATE = EVENT_TYPE(GR_INFRA_MODULE, 0x0102), -} nexthop_event_t; + GR_EVENT_NEXTHOP_NEW = EVENT_TYPE(GR_INFRA_MODULE, 0x0100), + GR_EVENT_NEXTHOP_DELETE = EVENT_TYPE(GR_INFRA_MODULE, 0x0101), + GR_EVENT_NEXTHOP_UPDATE = EVENT_TYPE(GR_INFRA_MODULE, 0x0102), +} gr_event_nexthop_t; #define gr_nh_flags_foreach(f, flags) \ for (gr_nh_flags_t __i = 0, f = GR_BIT16(0); __i < sizeof(gr_nh_flags_t) * CHAR_BIT; \ diff --git a/modules/infra/api/iface.c b/modules/infra/api/iface.c index 022014fb..90a2b4ed 100644 --- a/modules/infra/api/iface.c +++ b/modules/infra/api/iface.c @@ -148,11 +148,11 @@ static struct gr_event_serializer iface_serializer = { .callback = iface_event_serialize, .ev_count = 5, .ev_types = { - IFACE_EVENT_POST_ADD, - IFACE_EVENT_PRE_REMOVE, - IFACE_EVENT_POST_RECONFIG, - IFACE_EVENT_STATUS_UP, - IFACE_EVENT_STATUS_DOWN, + GR_EVENT_IFACE_POST_ADD, + GR_EVENT_IFACE_PRE_REMOVE, + GR_EVENT_IFACE_POST_RECONFIG, + GR_EVENT_IFACE_STATUS_UP, + GR_EVENT_IFACE_STATUS_DOWN, }, }; diff --git a/modules/infra/api/trace.c b/modules/infra/api/trace.c index 4588b1b4..3c269348 100644 --- a/modules/infra/api/trace.c +++ b/modules/infra/api/trace.c @@ -122,7 +122,7 @@ static struct gr_api_handler clear_packet_log_handler = { static struct gr_event_subscription iface_add_sub = { .callback = iface_add_callback, .ev_count = 1, - .ev_types = {IFACE_EVENT_POST_ADD}, + .ev_types = {GR_EVENT_IFACE_POST_ADD}, }; RTE_INIT(trace_init) { diff --git a/modules/infra/cli/events.c b/modules/infra/cli/events.c index 20c9fd74..a852c0cd 100644 --- a/modules/infra/cli/events.c +++ b/modules/infra/cli/events.c @@ -21,33 +21,33 @@ static cmd_status_t events_show(const struct gr_api_client *c, const struct ec_p while (gr_api_client_event_recv(c, &e) == 0) { switch (e->ev_type) { - case IFACE_EVENT_POST_ADD: + case GR_EVENT_IFACE_POST_ADD: assert(e->payload_len == sizeof(*p)); p = PAYLOAD(e); printf("> iface add: %s\n", p->iface.name); break; - case IFACE_EVENT_PRE_REMOVE: + case GR_EVENT_IFACE_PRE_REMOVE: assert(e->payload_len == sizeof(*p)); p = PAYLOAD(e); printf("> iface del: %s\n", p->iface.name); break; - case IFACE_EVENT_STATUS_UP: + case GR_EVENT_IFACE_STATUS_UP: assert(e->payload_len == sizeof(*p)); p = PAYLOAD(e); printf("> iface up: %s\n", p->iface.name); break; - case IFACE_EVENT_STATUS_DOWN: + case GR_EVENT_IFACE_STATUS_DOWN: assert(e->payload_len == sizeof(*p)); p = PAYLOAD(e); printf("> iface down: %s\n", p->iface.name); break; - case IFACE_EVENT_POST_RECONFIG: + case GR_EVENT_IFACE_POST_RECONFIG: assert(e->payload_len == sizeof(*p)); p = PAYLOAD(e); printf("> iface reconf: %s\n", p->iface.name); break; - case IP_EVENT_ADDR_ADD: - case IP6_EVENT_ADDR_ADD: + case GR_EVENT_IP_ADDR_ADD: + case GR_EVENT_IP6_ADDR_ADD: assert(e->payload_len == sizeof(*nh)); nh = PAYLOAD(e); printf("> addr add: iface[%d] " ADDR_F "\n", @@ -55,8 +55,8 @@ static cmd_status_t events_show(const struct gr_api_client *c, const struct ec_p ADDR_W(nh_af(nh)), &nh->addr); break; - case IP_EVENT_ADDR_DEL: - case IP6_EVENT_ADDR_DEL: + case GR_EVENT_IP_ADDR_DEL: + case GR_EVENT_IP6_ADDR_DEL: assert(e->payload_len == sizeof(*nh)); nh = PAYLOAD(e); printf("> addr del: iface[%d] " ADDR_F "\n", @@ -64,7 +64,7 @@ static cmd_status_t events_show(const struct gr_api_client *c, const struct ec_p ADDR_W(nh_af(nh)), &nh->addr); break; - case IP_EVENT_ROUTE_ADD: + case GR_EVENT_IP_ROUTE_ADD: assert(e->payload_len == sizeof(*r4)); r4 = PAYLOAD(e); printf("> route add: %4p/%d via %4p origin %s\n", @@ -73,7 +73,7 @@ static cmd_status_t events_show(const struct gr_api_client *c, const struct ec_p &r4->nh, gr_rt_origin_name(r4->origin)); break; - case IP_EVENT_ROUTE_DEL: + case GR_EVENT_IP_ROUTE_DEL: assert(e->payload_len == sizeof(*r4)); r4 = PAYLOAD(e); printf("> route del: %4p/%d via %4p origin %s\n", @@ -82,7 +82,7 @@ static cmd_status_t events_show(const struct gr_api_client *c, const struct ec_p &r4->nh, gr_rt_origin_name(r4->origin)); break; - case IP6_EVENT_ROUTE_ADD: + case GR_EVENT_IP6_ROUTE_ADD: assert(e->payload_len == sizeof(*r6)); r6 = PAYLOAD(e); printf("> route add: %6p/%d via %6p origin %s\n", @@ -91,7 +91,7 @@ static cmd_status_t events_show(const struct gr_api_client *c, const struct ec_p &r6->nh, gr_rt_origin_name(r6->origin)); break; - case IP6_EVENT_ROUTE_DEL: + case GR_EVENT_IP6_ROUTE_DEL: assert(e->payload_len == sizeof(*r6)); r6 = PAYLOAD(e); printf("> route del: %6p/%d via %6p origin %s\n", @@ -100,7 +100,7 @@ static cmd_status_t events_show(const struct gr_api_client *c, const struct ec_p &r6->nh, gr_rt_origin_name(r6->origin)); break; - case NEXTHOP_EVENT_NEW: + case GR_EVENT_NEXTHOP_NEW: assert(e->payload_len == sizeof(*nh)); nh = PAYLOAD(e); printf("> nh new: iface %d vrf %d " ADDR_F " " ETH_F "\n", @@ -110,7 +110,7 @@ static cmd_status_t events_show(const struct gr_api_client *c, const struct ec_p &nh->addr, &nh->mac); break; - case NEXTHOP_EVENT_DELETE: + case GR_EVENT_NEXTHOP_DELETE: assert(e->payload_len == sizeof(*nh)); nh = PAYLOAD(e); printf("> nh del: iface %d vrf %d " ADDR_F " " ETH_F "\n", @@ -120,7 +120,7 @@ static cmd_status_t events_show(const struct gr_api_client *c, const struct ec_p &nh->addr, &nh->mac); break; - case NEXTHOP_EVENT_UPDATE: + case GR_EVENT_NEXTHOP_UPDATE: assert(e->payload_len == sizeof(*nh)); nh = PAYLOAD(e); printf("> nh update: iface %d vrf %d " ADDR_F " " ETH_F "\n", diff --git a/modules/infra/control/iface.c b/modules/infra/control/iface.c index 5a1fbffb..67ac29d9 100644 --- a/modules/infra/control/iface.c +++ b/modules/infra/control/iface.c @@ -88,7 +88,7 @@ struct iface *iface_create(const struct gr_iface *conf, const void *api_info) { ifaces[ifid] = iface; - gr_event_push(IFACE_EVENT_POST_ADD, iface); + gr_event_push(GR_EVENT_IFACE_POST_ADD, iface); return iface; fail: @@ -247,9 +247,9 @@ int iface_destroy(uint16_t ifid) { /* interface is still up, send status down */ if (iface->flags & GR_IFACE_F_UP) { iface->flags &= ~GR_IFACE_F_UP; - gr_event_push(IFACE_EVENT_STATUS_DOWN, iface); + gr_event_push(GR_EVENT_IFACE_STATUS_DOWN, iface); } - gr_event_push(IFACE_EVENT_PRE_REMOVE, iface); + gr_event_push(GR_EVENT_IFACE_PRE_REMOVE, iface); ifaces[ifid] = NULL; type = iface_type_get(iface->type); @@ -307,19 +307,19 @@ static void iface_event_debug(uint32_t event, const void *obj) { const struct iface *iface = obj; char *str = ""; switch (event) { - case IFACE_EVENT_POST_ADD: + case GR_EVENT_IFACE_POST_ADD: str = "POST_ADD"; break; - case IFACE_EVENT_PRE_REMOVE: + case GR_EVENT_IFACE_PRE_REMOVE: str = "PRE_REMOVE"; break; - case IFACE_EVENT_POST_RECONFIG: + case GR_EVENT_IFACE_POST_RECONFIG: str = "POST_RECONFIG"; break; - case IFACE_EVENT_STATUS_UP: + case GR_EVENT_IFACE_STATUS_UP: str = "STATUS_UP"; break; - case IFACE_EVENT_STATUS_DOWN: + case GR_EVENT_IFACE_STATUS_DOWN: str = "STATUS_DOWN"; break; default: @@ -333,11 +333,11 @@ static struct gr_event_subscription iface_event_debug_handler = { .callback = iface_event_debug, .ev_count = 5, .ev_types = { - IFACE_EVENT_POST_ADD, - IFACE_EVENT_PRE_REMOVE, - IFACE_EVENT_POST_RECONFIG, - IFACE_EVENT_STATUS_UP, - IFACE_EVENT_STATUS_DOWN, + GR_EVENT_IFACE_POST_ADD, + GR_EVENT_IFACE_PRE_REMOVE, + GR_EVENT_IFACE_POST_RECONFIG, + GR_EVENT_IFACE_STATUS_UP, + GR_EVENT_IFACE_STATUS_DOWN, }, }; diff --git a/modules/infra/control/nexthop.c b/modules/infra/control/nexthop.c index e742dbf7..78ab7c79 100644 --- a/modules/infra/control/nexthop.c +++ b/modules/infra/control/nexthop.c @@ -132,7 +132,7 @@ nexthop_new(gr_nh_type_t type, uint16_t vrf_id, uint16_t iface_id, const void *a ABORT("invalid nexthop type %hhu", type); } - gr_event_push(NEXTHOP_EVENT_NEW, nh); + gr_event_push(GR_EVENT_NEXTHOP_NEW, nh); return nh; } @@ -211,7 +211,7 @@ void nexthop_decref(struct nexthop *nh) { rte_pktmbuf_free(m); m = next; } - gr_event_push(NEXTHOP_EVENT_DELETE, nh); + gr_event_push(GR_EVENT_NEXTHOP_DELETE, nh); memset(nh, 0, sizeof(*nh)); rte_mempool_put(pool, nh); } else { @@ -301,9 +301,9 @@ static struct gr_event_serializer nh_serializer = { .size = sizeof(struct gr_nexthop), .ev_count = 3, .ev_types = { - NEXTHOP_EVENT_NEW, - NEXTHOP_EVENT_DELETE, - NEXTHOP_EVENT_UPDATE, + GR_EVENT_NEXTHOP_NEW, + GR_EVENT_NEXTHOP_DELETE, + GR_EVENT_NEXTHOP_UPDATE, }, }; diff --git a/modules/infra/control/port.c b/modules/infra/control/port.c index 55a2d188..da0e4175 100644 --- a/modules/infra/control/port.c +++ b/modules/infra/control/port.c @@ -229,11 +229,11 @@ static int iface_port_reconfig( if (conf->flags & GR_IFACE_F_UP) { ret = rte_eth_dev_set_link_up(p->port_id); iface->flags |= GR_IFACE_F_UP; - gr_event_push(IFACE_EVENT_STATUS_UP, iface); + gr_event_push(GR_EVENT_IFACE_STATUS_UP, iface); } else { ret = rte_eth_dev_set_link_down(p->port_id); iface->flags &= ~GR_IFACE_F_UP; - gr_event_push(IFACE_EVENT_STATUS_DOWN, iface); + gr_event_push(GR_EVENT_IFACE_STATUS_DOWN, iface); } if (ret < 0) errno_log(-ret, "rte_eth_dev_set_link_{up,down}"); @@ -272,7 +272,7 @@ static int iface_port_reconfig( p->started = true; - gr_event_push(IFACE_EVENT_POST_RECONFIG, iface); + gr_event_push(GR_EVENT_IFACE_POST_RECONFIG, iface); return port_plug(p->port_id); } @@ -601,13 +601,13 @@ static void link_event_cb(evutil_socket_t, short /*what*/, void * /*priv*/) { if (!(iface->state & GR_IFACE_S_RUNNING)) { LOG(INFO, "%s: link status up", iface->name); iface->state |= GR_IFACE_S_RUNNING; - gr_event_push(IFACE_EVENT_STATUS_UP, iface); + gr_event_push(GR_EVENT_IFACE_STATUS_UP, iface); } } else { if (iface->state & GR_IFACE_S_RUNNING) { LOG(INFO, "%s: link status down", iface->name); iface->state &= ~GR_IFACE_S_RUNNING; - gr_event_push(IFACE_EVENT_STATUS_DOWN, iface); + gr_event_push(GR_EVENT_IFACE_STATUS_DOWN, iface); } continue; } diff --git a/modules/infra/control/vlan.c b/modules/infra/control/vlan.c index 9f07d5eb..8499c4bd 100644 --- a/modules/infra/control/vlan.c +++ b/modules/infra/control/vlan.c @@ -128,7 +128,7 @@ static int iface_vlan_reconfig( if (set_attrs & GR_IFACE_SET_VRF) iface->vrf_id = conf->vrf_id; - gr_event_push(IFACE_EVENT_POST_RECONFIG, iface); + gr_event_push(GR_EVENT_IFACE_POST_RECONFIG, iface); return 0; } @@ -273,7 +273,7 @@ static void port_event(uint32_t event, const void *obj) { while ((vlan = iface_next(GR_IFACE_TYPE_VLAN, vlan)) != NULL) { info = (struct iface_info_vlan *)vlan->info; if (info->parent_id == iface->id) { - if (event == IFACE_EVENT_STATUS_UP) { + if (event == GR_EVENT_IFACE_STATUS_UP) { vlan->flags |= GR_IFACE_F_UP; vlan->state |= GR_IFACE_S_RUNNING; } else { @@ -288,7 +288,7 @@ static void port_event(uint32_t event, const void *obj) { static struct gr_event_subscription port_event_sub = { .callback = port_event, .ev_count = 2, - .ev_types = {IFACE_EVENT_STATUS_UP, IFACE_EVENT_STATUS_DOWN}, + .ev_types = {GR_EVENT_IFACE_STATUS_UP, GR_EVENT_IFACE_STATUS_DOWN}, }; RTE_INIT(vlan_constructor) { diff --git a/modules/infra/control/vrf.c b/modules/infra/control/vrf.c index a2c4cdc3..79687cb9 100644 --- a/modules/infra/control/vrf.c +++ b/modules/infra/control/vrf.c @@ -29,17 +29,17 @@ static void iface_event_vrf(uint32_t event, const void *obj) { return; switch (event) { - case IFACE_EVENT_POST_ADD: + case GR_EVENT_IFACE_POST_ADD: if (++vrfs[iface->vrf_id].ref_count == 1) vrfs[iface->vrf_id].iface = iface_loopback_create(iface->vrf_id); break; - case IFACE_EVENT_PRE_REMOVE: + case GR_EVENT_IFACE_PRE_REMOVE: if (--vrfs[iface->vrf_id].ref_count == 0) { iface_loopback_delete(iface->vrf_id); vrfs[iface->vrf_id].iface = NULL; } break; - case IFACE_EVENT_POST_RECONFIG: + case GR_EVENT_IFACE_POST_RECONFIG: iface = NULL; while ((iface = iface_next(GR_IFACE_TYPE_UNDEF, iface)) != NULL) { if (iface->type == GR_IFACE_TYPE_LOOPBACK) @@ -68,9 +68,9 @@ static struct gr_event_subscription iface_event_vrf_sub = { .callback = iface_event_vrf, .ev_count = 3, .ev_types = { - IFACE_EVENT_POST_ADD, - IFACE_EVENT_PRE_REMOVE, - IFACE_EVENT_POST_RECONFIG, + GR_EVENT_IFACE_POST_ADD, + GR_EVENT_IFACE_PRE_REMOVE, + GR_EVENT_IFACE_POST_RECONFIG, }, }; diff --git a/modules/infra/control/xconnect.c b/modules/infra/control/xconnect.c index c2c22cc9..2b2f027b 100644 --- a/modules/infra/control/xconnect.c +++ b/modules/infra/control/xconnect.c @@ -18,13 +18,13 @@ static struct api_out l2_mode_set(const void *request, void ** /* response */) { // Clean all L3 related info if (req->mode != GR_IFACE_MODE_L3) - gr_event_push(IFACE_EVENT_STATUS_DOWN, iface); + gr_event_push(GR_EVENT_IFACE_STATUS_DOWN, iface); iface->mode = req->mode; iface->bridge_domain = req->domain_id; if (req->mode == GR_IFACE_MODE_L3) - gr_event_push(IFACE_EVENT_STATUS_UP, iface); + gr_event_push(GR_EVENT_IFACE_STATUS_UP, iface); return api_out(0, 0); } diff --git a/modules/ip/api/gr_ip4.h b/modules/ip/api/gr_ip4.h index d8a100a2..dd1cf621 100644 --- a/modules/ip/api/gr_ip4.h +++ b/modules/ip/api/gr_ip4.h @@ -166,9 +166,9 @@ struct gr_ip4_icmp_recv_resp { }; typedef enum { - IP_EVENT_ADDR_ADD = EVENT_TYPE(GR_IP4_MODULE, 0x0001), - IP_EVENT_ADDR_DEL = EVENT_TYPE(GR_IP4_MODULE, 0x0002), - IP_EVENT_ROUTE_ADD = EVENT_TYPE(GR_IP4_MODULE, 0x0003), - IP_EVENT_ROUTE_DEL = EVENT_TYPE(GR_IP4_MODULE, 0x0004), -} ip_event_t; + GR_EVENT_IP_ADDR_ADD = EVENT_TYPE(GR_IP4_MODULE, 0x0001), + GR_EVENT_IP_ADDR_DEL = EVENT_TYPE(GR_IP4_MODULE, 0x0002), + GR_EVENT_IP_ROUTE_ADD = EVENT_TYPE(GR_IP4_MODULE, 0x0003), + GR_EVENT_IP_ROUTE_DEL = EVENT_TYPE(GR_IP4_MODULE, 0x0004), +} gr_event_ip_t; #endif diff --git a/modules/ip/control/address.c b/modules/ip/control/address.c index e8cfc0fa..5bd0577f 100644 --- a/modules/ip/control/address.c +++ b/modules/ip/control/address.c @@ -96,7 +96,7 @@ static struct api_out addr_add(const void *request, void ** /*response*/) { return api_out(-ret, 0); gr_vec_add(ifaddrs->nh, nh); - gr_event_push(IP_EVENT_ADDR_ADD, nh); + gr_event_push(GR_EVENT_IP_ADDR_ADD, nh); return api_out(0, 0); } @@ -123,7 +123,7 @@ static struct api_out addr_del(const void *request, void ** /*response*/) { return api_out(ENOENT, 0); } - gr_event_push(IP_EVENT_ADDR_DEL, nh); + gr_event_push(GR_EVENT_IP_ADDR_DEL, nh); rib4_cleanup(nh); @@ -223,12 +223,12 @@ static struct gr_module addr_module = { static struct gr_event_subscription iface_pre_rm_subscription = { .callback = iface_pre_remove_cb, .ev_count = 1, - .ev_types = {IFACE_EVENT_PRE_REMOVE}, + .ev_types = {GR_EVENT_IFACE_PRE_REMOVE}, }; static struct gr_event_serializer iface_addr_serializer = { .size = sizeof(struct gr_nexthop), .ev_count = 2, - .ev_types = {IP_EVENT_ADDR_ADD, IP_EVENT_ADDR_DEL}, + .ev_types = {GR_EVENT_IP_ADDR_ADD, GR_EVENT_IP_ADDR_DEL}, }; RTE_INIT(address_constructor) { diff --git a/modules/ip/control/nexthop.c b/modules/ip/control/nexthop.c index 1ed9fabf..7afd4cd6 100644 --- a/modules/ip/control/nexthop.c +++ b/modules/ip/control/nexthop.c @@ -137,7 +137,7 @@ void arp_probe_input_cb(struct rte_mbuf *m) { nh->ucast_probes = 0; nh->bcast_probes = 0; nh->mac = arp->arp_data.arp_sha; - gr_event_push(NEXTHOP_EVENT_UPDATE, nh); + gr_event_push(GR_EVENT_NEXTHOP_UPDATE, nh); } if (arp->arp_opcode == RTE_BE16(RTE_ARP_OP_REQUEST)) { diff --git a/modules/ip/control/route.c b/modules/ip/control/route.c index 74d420b1..78b2715e 100644 --- a/modules/ip/control/route.c +++ b/modules/ip/control/route.c @@ -156,7 +156,7 @@ int rib4_insert( fib4_insert(vrf_id, ip, prefixlen, nh); if (origin != GR_RT_ORIGIN_INTERNAL) { gr_event_push( - IP_EVENT_ROUTE_ADD, + GR_EVENT_IP_ROUTE_ADD, &(struct gr_ip4_route) { {ip, prefixlen}, nh->ipv4, @@ -196,7 +196,7 @@ int rib4_delete(uint16_t vrf_id, ip4_addr_t ip, uint8_t prefixlen) { if (origin != GR_RT_ORIGIN_INTERNAL) { gr_event_push( - IP_EVENT_ROUTE_DEL, + GR_EVENT_IP_ROUTE_DEL, &(struct gr_ip4_route) { {ip, prefixlen}, nh->ipv4, @@ -469,7 +469,7 @@ static struct gr_api_handler route4_list_handler = { static struct gr_event_serializer route_serializer = { .size = sizeof(struct gr_ip4_route), .ev_count = 2, - .ev_types = {IP_EVENT_ROUTE_ADD, IP_EVENT_ROUTE_DEL}, + .ev_types = {GR_EVENT_IP_ROUTE_ADD, GR_EVENT_IP_ROUTE_DEL}, }; static struct gr_module route4_module = { diff --git a/modules/ip6/api/gr_ip6.h b/modules/ip6/api/gr_ip6.h index 030c64ff..b4de3a2c 100644 --- a/modules/ip6/api/gr_ip6.h +++ b/modules/ip6/api/gr_ip6.h @@ -203,10 +203,10 @@ struct gr_ip6_icmp_recv_resp { }; typedef enum { - IP6_EVENT_ADDR_ADD = EVENT_TYPE(GR_IP6_MODULE, 0x0001), - IP6_EVENT_ADDR_DEL = EVENT_TYPE(GR_IP6_MODULE, 0x0002), - IP6_EVENT_ROUTE_ADD = EVENT_TYPE(GR_IP6_MODULE, 0x0003), - IP6_EVENT_ROUTE_DEL = EVENT_TYPE(GR_IP6_MODULE, 0x0004), -} ip6_event_t; + GR_EVENT_IP6_ADDR_ADD = EVENT_TYPE(GR_IP6_MODULE, 0x0001), + GR_EVENT_IP6_ADDR_DEL = EVENT_TYPE(GR_IP6_MODULE, 0x0002), + GR_EVENT_IP6_ROUTE_ADD = EVENT_TYPE(GR_IP6_MODULE, 0x0003), + GR_EVENT_IP6_ROUTE_DEL = EVENT_TYPE(GR_IP6_MODULE, 0x0004), +} gr_event_ip6_t; #endif diff --git a/modules/ip6/control/address.c b/modules/ip6/control/address.c index aff792c4..497f61e6 100644 --- a/modules/ip6/control/address.c +++ b/modules/ip6/control/address.c @@ -180,7 +180,7 @@ iface6_addr_add(const struct iface *iface, const struct rte_ipv6_addr *ip, uint8 return errno_set(-ret); gr_vec_add(addrs->nh, nh); - gr_event_push(IP6_EVENT_ADDR_ADD, nh); + gr_event_push(GR_EVENT_IP6_ADDR_ADD, nh); return 0; } @@ -233,7 +233,7 @@ static struct api_out addr6_del(const void *request, void ** /*response*/) { return api_out(ENOENT, 0); } - gr_event_push(IP6_EVENT_ADDR_DEL, nh); + gr_event_push(GR_EVENT_IP6_ADDR_DEL, nh); rib6_cleanup(nh); @@ -305,7 +305,7 @@ static void ip6_iface_event_handler(uint32_t event, const void *obj) { unsigned i; switch (event) { - case IFACE_EVENT_POST_ADD: + case GR_EVENT_IFACE_POST_ADD: if (iface_get_eth_addr(iface->id, &mac) == 0) { rte_ipv6_llocal_from_ethernet(&link_local, &mac); if (iface6_addr_add(iface, &link_local, 64) < 0) @@ -320,7 +320,7 @@ static void ip6_iface_event_handler(uint32_t event, const void *obj) { LOG(INFO, "%s: mcast_addr_add: %s", iface->name, strerror(errno)); } break; - case IFACE_EVENT_PRE_REMOVE: + case GR_EVENT_IFACE_PRE_REMOVE: struct hoplist *addrs = &iface_addrs[iface->id]; gr_vec_foreach (nh, addrs->nh) @@ -385,16 +385,16 @@ static struct gr_event_subscription iface_event_subscription = { .callback = ip6_iface_event_handler, .ev_count = 2, .ev_types = { - IFACE_EVENT_POST_ADD, - IFACE_EVENT_PRE_REMOVE, + GR_EVENT_IFACE_POST_ADD, + GR_EVENT_IFACE_PRE_REMOVE, }, }; static struct gr_event_serializer iface_addr_serializer = { .size = sizeof(struct gr_nexthop), .ev_count = 2, .ev_types = { - IP6_EVENT_ADDR_ADD, - IP6_EVENT_ADDR_DEL, + GR_EVENT_IP6_ADDR_ADD, + GR_EVENT_IP6_ADDR_DEL, }, }; diff --git a/modules/ip6/control/nexthop.c b/modules/ip6/control/nexthop.c index dda4c94b..5e4f6dbe 100644 --- a/modules/ip6/control/nexthop.c +++ b/modules/ip6/control/nexthop.c @@ -180,7 +180,7 @@ void ndp_probe_input_cb(struct rte_mbuf *m) { nh->ucast_probes = 0; nh->bcast_probes = 0; nh->mac = mac; - gr_event_push(NEXTHOP_EVENT_UPDATE, nh); + gr_event_push(GR_EVENT_NEXTHOP_UPDATE, nh); } if (icmp6->type == ICMP6_TYPE_NEIGH_SOLICIT && local != NULL) { diff --git a/modules/ip6/control/route.c b/modules/ip6/control/route.c index 0d4057c0..5437027c 100644 --- a/modules/ip6/control/route.c +++ b/modules/ip6/control/route.c @@ -170,7 +170,7 @@ int rib6_insert( fib6_insert(vrf_id, iface_id, scoped_ip, prefixlen, nh); if (origin != GR_RT_ORIGIN_INTERNAL) { gr_event_push( - IP6_EVENT_ROUTE_ADD, + GR_EVENT_IP6_ROUTE_ADD, &(struct gr_ip6_route) { {*ip, prefixlen}, nh->ipv6, @@ -217,7 +217,7 @@ int rib6_delete( if (origin != GR_RT_ORIGIN_INTERNAL) { gr_event_push( - IP6_EVENT_ROUTE_DEL, + GR_EVENT_IP6_ROUTE_DEL, &(struct gr_ip6_route) { {*ip, prefixlen}, nh->ipv6, @@ -497,7 +497,7 @@ static struct gr_api_handler route6_list_handler = { static struct gr_event_serializer route6_serializer = { .size = sizeof(struct gr_ip6_route), .ev_count = 2, - .ev_types = {IP6_EVENT_ROUTE_ADD, IP6_EVENT_ROUTE_DEL}, + .ev_types = {GR_EVENT_IP6_ROUTE_ADD, GR_EVENT_IP6_ROUTE_DEL}, }; static struct gr_module route6_module = { diff --git a/modules/ip6/control/router_advert.c b/modules/ip6/control/router_advert.c index 53437dad..419f3937 100644 --- a/modules/ip6/control/router_advert.c +++ b/modules/ip6/control/router_advert.c @@ -237,14 +237,14 @@ static void iface_event_handler(uint32_t event, const void *obj) { const struct iface *iface = obj; switch (event) { - case IFACE_EVENT_POST_ADD: + case GR_EVENT_IFACE_POST_ADD: ra_conf[iface->id].interval = RA_DEFAULT_INTERVAL; ra_conf[iface->id].lifetime = RA_DEFAULT_LIFETIME; ra_conf[iface->id].timer = event_new( ev_base, -1, EV_PERSIST, send_ra_cb, (void *)iface ); break; - case IFACE_EVENT_PRE_REMOVE: + case GR_EVENT_IFACE_PRE_REMOVE: event_free(ra_conf[iface->id].timer); ra_conf[iface->id].timer = NULL; break; @@ -254,7 +254,7 @@ static void iface_event_handler(uint32_t event, const void *obj) { static struct gr_event_subscription iface_event_sub = { .callback = iface_event_handler, .ev_count = 2, - .ev_types = {IFACE_EVENT_POST_ADD, IFACE_EVENT_PRE_REMOVE}, + .ev_types = {GR_EVENT_IFACE_POST_ADD, GR_EVENT_IFACE_PRE_REMOVE}, }; RTE_INIT(router_advertisement_init) { diff --git a/modules/ipip/control.c b/modules/ipip/control.c index 8251d869..77441457 100644 --- a/modules/ipip/control.c +++ b/modules/ipip/control.c @@ -84,7 +84,7 @@ static int iface_ipip_reconfig( if (set_attrs & GR_IFACE_SET_MTU) iface->mtu = conf->mtu; - gr_event_push(IFACE_EVENT_POST_RECONFIG, iface); + gr_event_push(GR_EVENT_IFACE_POST_RECONFIG, iface); return 0; } From 0a339c33e9a76993da86697619e9d937aa82e2e7 Mon Sep 17 00:00:00 2001 From: Robin Jarry Date: Wed, 30 Apr 2025 23:32:21 +0200 Subject: [PATCH 14/25] check-patches: ignore reverts Ignore titles when they look like revert commits. Signed-off-by: Robin Jarry --- devtools/check-patches | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/devtools/check-patches b/devtools/check-patches index 9fa386f2..a17dfc45 100755 --- a/devtools/check-patches +++ b/devtools/check-patches @@ -55,17 +55,19 @@ for rev in $revisions; do title=$(git log --format='%s' -1 "$rev") fail=false - if [ "$(echo "$title" | wc -m)" -gt 72 ]; then - err "title is longer than 72 characters, please make it shorter" - fi - if ! echo "$title" | grep -qE '^[a-z0-9,{}/_-]+: '; then - err "title lacks a lowercase topic prefix (e.g. 'ipv6:')" - fi - if echo "$title" | grep -qE '^[a-z0-9,{}/_-]+: [A-Z][a-z]'; then - err "title starts with an capital letter, please use lower case" - fi - if ! echo "$title" | grep -qE '[A-Za-z0-9]$'; then - err "title ends with punctuation, please remove it" + if ! echo "$title" | grep -qE '^Revert ".+"$'; then + if [ "$(echo "$title" | wc -m)" -gt 72 ]; then + err "title is longer than 72 characters, please make it shorter" + fi + if ! echo "$title" | grep -qE '^[a-z0-9,{}/_-]+: '; then + err "title lacks a lowercase topic prefix (e.g. 'ipv6:')" + fi + if echo "$title" | grep -qE '^[a-z0-9,{}/_-]+: [A-Z][a-z]'; then + err "title starts with an capital letter, please use lower case" + fi + if ! echo "$title" | grep -qE '[A-Za-z0-9]$'; then + err "title ends with punctuation, please remove it" + fi fi author=$(git log --format='%an <%ae>' -1 "$rev") From 1337434d3c71885e9714f388d182dc93409440b4 Mon Sep 17 00:00:00 2001 From: Maxime Leroy Date: Tue, 29 Apr 2025 14:35:31 +0200 Subject: [PATCH 15/25] Revert "api: allow api access with net_admin apability" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 4a685c7da75bc5c7b642878216e69fd5f9580499. The capability library operates per-thread, while SO_PEERCRED returns the PID of the process, which corresponds to the TID of the main thread. When a client connects to Grout, SO_PEERCRED provides the PID of the connecting process—effectively the main thread’s TID. If the connecting thread (not the main thread) holds CAP_NET_ADMIN, cap_get_pid will report that the capability is not set, since it checks capabilities for the main thread only. As a result, the connection is rejected, even though it should be allowed. Since there is no way to obtain the peer TID through getsockopt, this issue cannot be resolved cleanly. Therefore, this commit is being reverted. Signed-off-by: Maxime Leroy --- .github/workflows/check.yml | 5 ++--- .github/workflows/publish.yml | 4 ++-- README.md | 6 ++---- main/api.c | 19 +------------------ meson.build | 3 +-- 5 files changed, 8 insertions(+), 29 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 89fcc464..68b717f8 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -58,8 +58,7 @@ jobs: make gcc ccache ninja-build meson git go-md2man libibverbs-dev \ libasan8 libcmocka-dev libedit-dev libarchive-dev \ libevent-dev libsmartcols-dev libnuma-dev python3-pyelftools \ - socat tcpdump traceroute graphviz iproute2 iputils-ping ndisc6 \ - libcap-dev + socat tcpdump traceroute graphviz iproute2 iputils-ping ndisc6 if printf '%s\n0.63.0\n' "$(meson --version)" | sort -V -C ; then sudo apt install -y python3-pip pip3 install --user -U meson~=0.63.0 @@ -102,7 +101,7 @@ jobs: apt install -qy --no-install-recommends \ make gcc ccache git meson go-md2man python3-pyelftools ca-certificates pkg-config \ crossbuild-essential-arm64 libcmocka-dev:arm64 libedit-dev:arm64 \ - libevent-dev:arm64 libnuma-dev:arm64 libsmartcols-dev:arm64 libcap-dev:arm64 + libevent-dev:arm64 libnuma-dev:arm64 libsmartcols-dev:arm64 - uses: actions/checkout@v4 with: persist-credentials: false diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 4afd38ae..ed84ee64 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -33,7 +33,7 @@ jobs: git build-essential meson ninja-build pkgconf go-md2man python3-pyelftools \ libcmocka-dev libedit-dev libevent-dev libnuma-dev \ libsmartcols-dev libarchive-dev libibverbs-dev \ - bash-completion devscripts debhelper libcap-dev + bash-completion devscripts debhelper - uses: actions/checkout@v4 with: fetch-depth: 0 # force fetch all history @@ -63,7 +63,7 @@ jobs: gcc-toolset-13 scl-utils golang-github-cpuguy83-md2man \ libcmocka-devel libedit-devel libevent-devel numactl-devel \ libsmartcols-devel libarchive-devel rdma-core-devel \ - rpm-build systemd libcap-devel + rpm-build systemd - uses: actions/checkout@v4 with: fetch-depth: 0 # force fetch all history diff --git a/README.md b/README.md index 473fe861..82409e79 100644 --- a/README.md +++ b/README.md @@ -232,8 +232,7 @@ image. dnf install git gcc make meson ninja-build pkgconf \ python3-pyelftools golang-github-cpuguy83-md2man \ libcmocka-devel libedit-devel libevent-devel numactl-devel \ - libsmartcols-devel libarchive-devel rdma-core-devel \ - libcap-devel + libsmartcols-devel libarchive-devel rdma-core-devel ``` or @@ -242,8 +241,7 @@ or apt install git gcc make meson ninja-build pkgconf \ python3-pyelftools go-md2man \ libcmocka-dev libedit-dev libevent-dev libnuma-dev \ - libsmartcols-dev libarchive-dev libibverbs-dev \ - libcap-dev + libsmartcols-dev libarchive-dev libibverbs-dev ``` Important: `grout` requires at least `gcc` 13 or `clang` 15. diff --git a/main/api.c b/main/api.c index f3c3607b..9fad9266 100644 --- a/main/api.c +++ b/main/api.c @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include @@ -172,10 +171,8 @@ static struct api_out unsubscribe(evutil_socket_t sock) { } static int check_permission(int fd) { - cap_flag_value_t has_cap = CAP_CLEAR; struct ucred cred; socklen_t len; - cap_t caps; len = sizeof(cred); if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) == -1) @@ -185,21 +182,7 @@ static int check_permission(int fd) { if (cred.uid == 0 || cred.uid == getuid()) return 0; - // check if peer process has CAP_NET_ADMIN - caps = cap_get_pid(cred.pid); - if (caps == NULL) - return errno_log(errno, "cat_get_pid"); - - if (cap_get_flag(caps, CAP_NET_ADMIN, CAP_EFFECTIVE, &has_cap) == -1) - return errno_log(errno, "cat_get_flags"); - cap_free(caps); - - if (has_cap != CAP_SET) { - LOG(ERR, "unauthorized peer (pid=%d, uid=%d)", cred.pid, cred.uid); - return errno_set(EPERM); - } - - return 0; + return errno_set(EPERM); } static struct api_out hello(const void *request) { diff --git a/meson.build b/meson.build index 774f1d34..8db59e82 100644 --- a/meson.build +++ b/meson.build @@ -91,7 +91,6 @@ ecoli_dep = dependency( ] ) smartcols_dep = dependency('smartcols') -libcap_dep = dependency('libcap') src = [] inc = [] @@ -114,7 +113,7 @@ subdir('cli') grout_exe = executable( 'grout', src, include_directories: inc + api_inc, - dependencies: [dpdk_dep, ev_core_dep, ev_thread_dep, libcap_dep, numa_dep], + dependencies: [dpdk_dep, ev_core_dep, ev_thread_dep, numa_dep], c_args: ['-D__GROUT_MAIN__'], install: true, ) From d9b37858ef27da97e5126d7058d8b3cf1b465073 Mon Sep 17 00:00:00 2001 From: Maxime Leroy Date: Tue, 29 Apr 2025 14:43:33 +0200 Subject: [PATCH 16/25] Revert "deb,rpm: add missing dependency to libcap headers" This reverts commit 95d6ff6fff76b889856013681156d9a06acf5035. Libcap is not needed since the revert of the previous commit. Signed-off-by: Maxime Leroy --- debian/control | 1 - rpm/grout.spec | 1 - 2 files changed, 2 deletions(-) diff --git a/debian/control b/debian/control index 4438bc1d..6a6dd49a 100644 --- a/debian/control +++ b/debian/control @@ -10,7 +10,6 @@ Build-Depends: git, go-md2man, libarchive-dev, - libcap-dev, libcmocka-dev, libedit-dev, libevent-dev, diff --git a/rpm/grout.spec b/rpm/grout.spec index c170c916..6f79d2c0 100644 --- a/rpm/grout.spec +++ b/rpm/grout.spec @@ -25,7 +25,6 @@ BuildRequires: scl-utils %endif BuildRequires: git BuildRequires: libarchive-devel -BuildRequires: libcap-devel BuildRequires: libcmocka-devel BuildRequires: libedit-devel BuildRequires: libevent-devel From e6249eadb90630235bc2aa244d4a8980e8710e4e Mon Sep 17 00:00:00 2001 From: Maxime Leroy Date: Tue, 29 Apr 2025 14:44:30 +0200 Subject: [PATCH 17/25] Revert "api: use abstract socket" This reverts commit ed4b78a49adb8edefde1f0b559c2ec51bd09b1de. We want to allow access to the Grout API across network namespaces, but abstract sockets are scoped to the namespace in which they are created [1]. To support cross-namespace access, we revert to using a Unix socket file. [1] https://elixir.bootlin.com/linux/v6.14.4/source/net/unix/af_unix.c#L1320 Signed-off-by: Maxime Leroy --- GNUmakefile | 9 +++-- api/gr_api.h | 4 +-- api/gr_api_client_impl.h | 10 ++---- cli/complete.c | 6 ++-- cli/ec_node_dyn.c | 14 ++++---- cli/gr_cli.h | 2 +- cli/main.c | 18 +++++----- main/api.c | 77 +++++++++++++--------------------------- main/gr_config.h | 2 +- main/main.c | 16 +++++---- smoke/_init.sh | 6 ++-- 11 files changed, 66 insertions(+), 98 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index 88956aa4..71cf55a6 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -31,12 +31,11 @@ smoke-tests: all .PHONY: update-graph update-graph: all - $Q set -xe; \ - tmp=$(echo $SRANDOM$SRANDOM | base32 -w6 | tr '[:upper:]' '[:lower:]' | head -n1) \ - trap "killall grout; wait" EXIT; \ - export GROUT_SOCK_NAME="$$tmp.sock"; \ + $Q set -xe; tmp=`mktemp -d`; \ + trap "killall grout; wait; rm -rf $$tmp" EXIT; \ + export GROUT_SOCK_PATH="$$tmp/sock"; \ $(BUILDDIR)/grout -t & \ - socat FILE:/dev/null ABSTRACT-CONNECT:$$GROUT_SOCK_NAME,retry=10 && \ + socat FILE:/dev/null UNIX-CONNECT:$$GROUT_SOCK_PATH,retry=10 && \ $(BUILDDIR)/grcli add interface port p0 devargs net_null,no-rx=1 && \ $(BUILDDIR)/grcli show graph brief | dot -Tsvg > docs/graph.svg diff --git a/api/gr_api.h b/api/gr_api.h index a9031ee4..a4e0e9b8 100644 --- a/api/gr_api.h +++ b/api/gr_api.h @@ -25,11 +25,11 @@ struct gr_api_response { #define EVENT_TYPE(module, id) (((uint32_t)(0xffff & module) << 16) | (0xffff & id)) #define EVENT_TYPE_ALL UINT32_C(0xffffffff) -#define GR_DEFAULT_SOCK_NAME "grout.sock" +#define GR_DEFAULT_SOCK_PATH "/run/grout.sock" struct gr_api_client; -struct gr_api_client *gr_api_client_connect(const char *sock_name); +struct gr_api_client *gr_api_client_connect(const char *sock_path); int gr_api_client_disconnect(struct gr_api_client *); diff --git a/api/gr_api_client_impl.h b/api/gr_api_client_impl.h index 418e932c..0f9e0be7 100644 --- a/api/gr_api_client_impl.h +++ b/api/gr_api_client_impl.h @@ -24,7 +24,7 @@ struct gr_api_client { int sock_fd; }; -struct gr_api_client *gr_api_client_connect(const char *sock_name) { +struct gr_api_client *gr_api_client_connect(const char *sock_path) { union { struct sockaddr_un un; struct sockaddr a; @@ -39,13 +39,9 @@ struct gr_api_client *gr_api_client_connect(const char *sock_name) { goto err; addr.un.sun_family = AF_UNIX; - addr.un.sun_path[0] = '\0'; - memccpy(addr.un.sun_path + 1, sock_name, 0, sizeof(addr.un.sun_path) - 2); + memccpy(addr.un.sun_path, sock_path, 0, sizeof(addr.un.sun_path) - 1); - if (connect(client->sock_fd, - &addr.a, - offsetof(struct sockaddr_un, sun_path) + strlen(sock_name) + 1) - < 0) + if (connect(client->sock_fd, &addr.a, sizeof(addr.un)) < 0) goto err; struct gr_hello_req hello = {.version = GROUT_VERSION}; diff --git a/cli/complete.c b/cli/complete.c index b1fea4dd..a81070a9 100644 --- a/cli/complete.c +++ b/cli/complete.c @@ -22,9 +22,9 @@ static struct ec_node *add_flags(struct ec_node *cmdlist) { EC_NODE_SUBSET( EC_NO_ID, FLAG("-h|--help", "Show usage help and exit."), - OPT("-s|--socket " SOCK_NAME_ID, - "Name of the control plane API socket.", - ec_node("file", SOCK_NAME_ID)), + OPT("-s|--socket " SOCK_PATH_ID, + "Path to the control plane API socket.", + ec_node("file", SOCK_PATH_ID)), FLAG("-e|--err-exit", "Abort on first error."), FLAG("-x|--trace-commands", "Print executed commands.") ) diff --git a/cli/ec_node_dyn.c b/cli/ec_node_dyn.c index a526c156..f4e853ec 100644 --- a/cli/ec_node_dyn.c +++ b/cli/ec_node_dyn.c @@ -30,24 +30,24 @@ static void disconnect_client(void *ptr) { static struct gr_api_client *connect_client(struct ec_comp *comp) { const struct ec_pnode *pstate = ec_comp_get_cur_pstate(comp); - const char *sock_name = NULL; + const char *sock_path = NULL; struct gr_api_client *client; // find the root of the parsed tree while (ec_pnode_get_parent(pstate) != NULL) pstate = ec_pnode_get_parent(pstate); - // find a parsed -s or --sock-name argument value - pstate = ec_pnode_find(pstate, SOCK_NAME_ID); + // find a parsed -s or --sock-path argument value + pstate = ec_pnode_find(pstate, SOCK_PATH_ID); if (pstate != NULL) { const struct ec_strvec *vec = ec_pnode_get_strvec(pstate); if (ec_strvec_len(vec) == 1) - sock_name = ec_strvec_val(vec, 0); + sock_path = ec_strvec_val(vec, 0); } - if (sock_name == NULL) - sock_name = GR_DEFAULT_SOCK_NAME; // not specified, use default + if (sock_path == NULL) + sock_path = GR_DEFAULT_SOCK_PATH; // not specified, use default - client = gr_api_client_connect(sock_name); + client = gr_api_client_connect(sock_path); if (client != NULL) { // attach the connected client to the complete tree so that it is // automatically disconnected when the tree is freed. diff --git a/cli/gr_cli.h b/cli/gr_cli.h index 86e6f08d..ceb36d21 100644 --- a/cli/gr_cli.h +++ b/cli/gr_cli.h @@ -111,6 +111,6 @@ typedef int (*ec_node_dyn_comp_t)( struct ec_node *ec_node_dyn(const char *id, ec_node_dyn_comp_t cb, void *cb_arg); #define CLIENT_ATTR "gr_api_client" -#define SOCK_NAME_ID "gr_api_sock_name" +#define SOCK_PATH_ID "gr_api_sock_path" #endif diff --git a/cli/main.c b/cli/main.c index 97b7cdd3..6fe0515f 100644 --- a/cli/main.c +++ b/cli/main.c @@ -37,9 +37,9 @@ static void help(void) { puts(" -e, --err-exit Abort on first error."); puts(" -f PATH, --file PATH Read commands from file instead of stdin."); puts(" -h, --help Show this help message and exit."); - puts(" -s NAME, --socket NAME Name of the control plane API socket."); - puts(" Default: GROUT_SOCK_NAME from env or"); - printf(" %s).\n", GR_DEFAULT_SOCK_NAME); + puts(" -s PATH, --socket PATH Path to the control plane API socket."); + puts(" Default: GROUT_SOCK_PATH from env or"); + printf(" %s).\n", GR_DEFAULT_SOCK_PATH); puts(" -V, --version Print version and exit."); puts(" -x, --trace-commands Print executed commands."); puts(""); @@ -49,7 +49,7 @@ static void help(void) { } struct gr_cli_opts { - const char *sock_name; + const char *sock_path; FILE *cmds_file; bool err_exit; bool trace_commands; @@ -73,7 +73,7 @@ static int parse_args(int argc, char **argv) { opterr = 0; // disable getopt default error reporting - opts.sock_name = getenv("GROUT_SOCK_NAME"); + opts.sock_path = getenv("GROUT_SOCK_PATH"); opts.cmds_file = stdin; while ((c = getopt_long(argc, argv, FLAGS, long_options, NULL)) != -1) { @@ -93,7 +93,7 @@ static int parse_args(int argc, char **argv) { help(); return -1; case 's': - opts.sock_name = optarg; + opts.sock_path = optarg; break; case 'V': printf("grcli %s\n", GROUT_VERSION); @@ -117,8 +117,8 @@ static int parse_args(int argc, char **argv) { } } - if (opts.sock_name == NULL) - opts.sock_name = GR_DEFAULT_SOCK_NAME; + if (opts.sock_path == NULL) + opts.sock_path = GR_DEFAULT_SOCK_PATH; return optind; } @@ -156,7 +156,7 @@ int main(int argc, char **argv) { argc -= c; argv += c; - if ((client = gr_api_client_connect(opts.sock_name)) == NULL) { + if ((client = gr_api_client_connect(opts.sock_path)) == NULL) { errorf("gr_connect: %s", strerror(errno)); goto end; } diff --git a/main/api.c b/main/api.c index 9fad9266..142a12d6 100644 --- a/main/api.c +++ b/main/api.c @@ -58,15 +58,6 @@ struct module_subscribers { }; static struct module_subscribers *mod_subs[UINT16_MAX]; -typedef enum { - CLIENT_CTX_F_NONE = 0, - CLIENT_CTX_PERMISSION_CHECKED, -} client_ctx_flags_t; - -struct client_ctx { - client_ctx_flags_t flags; -}; - static void api_send_notifications(uint32_t ev_type, const void *obj) { struct module_subscribers *subs; evutil_socket_t *socks = NULL; @@ -170,21 +161,6 @@ static struct api_out unsubscribe(evutil_socket_t sock) { return api_out(0, 0); } -static int check_permission(int fd) { - struct ucred cred; - socklen_t len; - - len = sizeof(cred); - if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) == -1) - return errno_log(errno, "getsockopt(SO_PEERCRED)"); - - // peer user is root or with same uid that grout, allowed - if (cred.uid == 0 || cred.uid == getuid()) - return 0; - - return errno_set(EPERM); -} - static struct api_out hello(const void *request) { const struct gr_hello_req *req = request; @@ -194,12 +170,10 @@ static struct api_out hello(const void *request) { return api_out(0, 0); } -static void finalize_fd(struct event *ev, void *ctx) { +static void finalize_fd(struct event *ev, void * /*priv*/) { int fd = event_get_fd(ev); if (fd >= 0) close(fd); - if (ctx) - free(ctx); } static ssize_t send_response(evutil_socket_t sock, struct gr_api_response *resp) { @@ -247,11 +221,10 @@ static void write_cb(evutil_socket_t sock, short /*what*/, void *priv) { event_free(ev); } -static void read_cb(evutil_socket_t sock, short what, void *_ctx) { +static void read_cb(evutil_socket_t sock, short what, void * /*priv*/) { struct event *ev = event_base_get_running_event(ev_base); void *req_payload = NULL, *resp_payload = NULL; struct gr_api_response *resp = NULL; - struct client_ctx *ctx = _ctx; struct gr_api_request req; struct event *write_ev; struct api_out out; @@ -290,15 +263,6 @@ static void read_cb(evutil_socket_t sock, short what, void *_ctx) { } } - if (!(ctx->flags & CLIENT_CTX_PERMISSION_CHECKED)) { - if (check_permission(sock) < 0) { - out = api_out(EPERM, 0); - goto send; - } - - ctx->flags |= CLIENT_CTX_PERMISSION_CHECKED; - } - LOG(DEBUG, "fd=%d id=%u req_type=0x%08x len=%u", sock, req.id, req.type, req.payload_len); switch (req.type) { @@ -366,7 +330,6 @@ static void read_cb(evutil_socket_t sock, short what, void *_ctx) { } static void listen_cb(evutil_socket_t sock, short what, void * /*priv*/) { - struct client_ctx *ctx; struct event *ev; int fd; @@ -383,16 +346,9 @@ static void listen_cb(evutil_socket_t sock, short what, void * /*priv*/) { return; } - ctx = calloc(1, sizeof(struct client_ctx)); - if (ctx == NULL) { - LOG(ERR, "client ctx allocation for new connection failed"); - close(fd); - return; - } - LOG(DEBUG, "new connection fd=%d", fd); - ev = event_new(ev_base, fd, EV_READ | EV_CLOSED | EV_PERSIST | EV_FINALIZE, read_cb, ctx); + ev = event_new(ev_base, fd, EV_READ | EV_CLOSED | EV_PERSIST | EV_FINALIZE, read_cb, NULL); if (ev == NULL || event_add(ev, NULL) < 0) { LOG(ERR, "failed to add event to loop"); if (ev != NULL) @@ -405,7 +361,7 @@ static void listen_cb(evutil_socket_t sock, short what, void * /*priv*/) { static struct event *ev_listen; int api_socket_start(struct event_base *base) { - const char *sock_name = gr_config.api_sock_name; + const char *path = gr_config.api_sock_path; union { struct sockaddr_un un; struct sockaddr a; @@ -418,10 +374,25 @@ int api_socket_start(struct event_base *base) { return errno_log(errno, "socket"); addr.un.sun_family = AF_UNIX; - addr.un.sun_path[0] = '\0'; - memccpy(addr.un.sun_path + 1, sock_name, 0, sizeof(addr.un.sun_path) - 2); - - ret = bind(fd, &addr.a, offsetof(struct sockaddr_un, sun_path) + strlen(sock_name) + 1); + memccpy(addr.un.sun_path, path, 0, sizeof(addr.un.sun_path) - 1); + + ret = bind(fd, &addr.a, sizeof(addr.un)); + if (ret < 0 && errno == EADDRINUSE) { + // unix socket file exists, check if there is a process + // listening on the other side. + ret = connect(fd, &addr.a, sizeof(addr.un)); + if (ret == 0) { + LOG(ERR, "grout already running on API socket %s, exiting", path); + close(fd); + return errno_set(EADDRINUSE); + } + if (ret < 0 && errno != ECONNREFUSED) + return errno_log(errno, "connect"); + // remove socket file, and try to bind again + if (unlink(addr.un.sun_path) < 0) + return errno_log(errno, "unlink"); + ret = bind(fd, &addr.a, sizeof(addr.un)); + } if (ret < 0) { close(fd); return errno_log(errno, "bind"); @@ -442,7 +413,7 @@ int api_socket_start(struct event_base *base) { // keep a reference for callbacks ev_base = base; - LOG(INFO, "listening on API socket %s", sock_name); + LOG(INFO, "listening on API socket %s", path); return 0; } diff --git a/main/gr_config.h b/main/gr_config.h index 8c057e5d..b62ea639 100644 --- a/main/gr_config.h +++ b/main/gr_config.h @@ -8,7 +8,7 @@ #include struct gr_config { - const char *api_sock_name; + const char *api_sock_path; unsigned log_level; bool test_mode; bool poll_mode; diff --git a/main/main.c b/main/main.c index c523637d..e24410be 100644 --- a/main/main.c +++ b/main/main.c @@ -41,9 +41,9 @@ static void usage(const char *prog) { puts(" -L, --log-level : Specify log level for a specific component."); puts(" -p, --poll-mode Disable automatic micro-sleep."); puts(" -S, --syslog Redirect logs to syslog."); - puts(" -s, --socket Name of the control plane API socket."); - puts(" Default: GROUT_SOCK_NAME from env or"); - printf(" %s).\n", GR_DEFAULT_SOCK_NAME); + puts(" -s, --socket Path the control plane API socket."); + puts(" Default: GROUT_SOCK_PATH from env or"); + printf(" %s).\n", GR_DEFAULT_SOCK_PATH); puts(" -t, --test-mode Run in test mode (no hugepages)."); puts(" -T, --trace Enable trace matching the regular expression."); puts(" -B, --trace-bufsz Maximum size of allocated memory for trace output."); @@ -79,9 +79,9 @@ static int parse_args(int argc, char **argv) { opterr = 0; // disable getopt default error reporting - gr_config.api_sock_name = getenv("GROUT_SOCK_NAME"); - if (gr_config.api_sock_name == NULL) - gr_config.api_sock_name = GR_DEFAULT_SOCK_NAME; + gr_config.api_sock_path = getenv("GROUT_SOCK_PATH"); + if (gr_config.api_sock_path == NULL) + gr_config.api_sock_path = GR_DEFAULT_SOCK_PATH; gr_config.log_level = RTE_LOG_NOTICE; gr_config.eal_extra_args = NULL; @@ -101,7 +101,7 @@ static int parse_args(int argc, char **argv) { gr_config.log_syslog = true; break; case 's': - gr_config.api_sock_name = optarg; + gr_config.api_sock_path = optarg; break; case 't': gr_config.test_mode = true; @@ -218,6 +218,8 @@ int main(int argc, char **argv) { modules_fini(ev_base); event_base_free(ev_base); } + if (ret != EXIT_ALREADY_RUNNING) + unlink(gr_config.api_sock_path); libevent_global_shutdown(); dpdk_stop: dpdk_fini(); diff --git a/smoke/_init.sh b/smoke/_init.sh index 2fd6c0c1..bb1d4bd4 100644 --- a/smoke/_init.sh +++ b/smoke/_init.sh @@ -3,7 +3,7 @@ set -e -if [ -n "$GROUT_SOCK_NAME" ]; then +if [ -S "$GROUT_SOCK_PATH" ]; then run_grout=false else run_grout=true @@ -55,7 +55,7 @@ builddir=${1?builddir} run_id=$(echo $SRANDOM$SRANDOM | base32 -w6 | tr '[:upper:]' '[:lower:]' | head -n1) if [ "$run_grout" = true ]; then - export GROUT_SOCK_NAME=$run_id-grout.sock + export GROUT_SOCK_PATH=$tmp/grout.sock fi export PATH=$builddir:$PATH @@ -74,7 +74,7 @@ set -x if [ "$run_grout" = true ]; then taskset -c 0,1 grout -tvx & fi -socat FILE:/dev/null ABSTRACT-CONNECT:$GROUT_SOCK_NAME,retry=10 +socat FILE:/dev/null UNIX-CONNECT:$GROUT_SOCK_PATH,retry=10 case "$(basename $0)" in config_test.sh|graph_svg_test.sh) From f771de5d4635e7a89b5c447e005764ee8bb6ad52 Mon Sep 17 00:00:00 2001 From: Maxime Leroy Date: Tue, 29 Apr 2025 15:07:31 +0200 Subject: [PATCH 18/25] main: add --socket-owner option Add an option to specify the user and group ownership of the API socket file. This allows non-root processes (e.g., FRR) to connect to the socket. Signed-off-by: Maxime Leroy --- docs/grout.8.md | 8 +++++++ main/api.c | 5 ++++ main/gr_config.h | 3 +++ main/main.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 77 insertions(+), 1 deletion(-) diff --git a/docs/grout.8.md b/docs/grout.8.md index b14ed348..4b90d2e4 100644 --- a/docs/grout.8.md +++ b/docs/grout.8.md @@ -17,6 +17,7 @@ Grout is a software router based on DPDK __rte_graph__. [**-h**] [**-L** _TYPE_:_LEVEL_] [**-M** _MODE_] +[**-o** _USER_:_GROUP_] [**-p**] [**-S**] [**-s** _PATH_] @@ -77,6 +78,13 @@ wrapped or discarded when file size reaches its maximum limit. For example: Default mode is _overwrite_ and parameter must be specified once only. +#### **-o**, **--socket-owner** _USER_:_GROUP_ + +Change the owner of the API socket file after creating it. Symbolic names and +numeric IDs are supported. + +Default: current user and current group. + #### **-p**, **--poll-mode** Disable automatic micro-sleep. diff --git a/main/api.c b/main/api.c index 142a12d6..0123e8be 100644 --- a/main/api.c +++ b/main/api.c @@ -398,6 +398,11 @@ int api_socket_start(struct event_base *base) { return errno_log(errno, "bind"); } + if (chown(path, gr_config.api_sock_uid, gr_config.api_sock_gid) < 0) { + close(fd); + return errno_log(errno, "API socket ownership can not be set"); + } + if (listen(fd, SOCKET_LISTEN_BACKLOG) < 0) { close(fd); return errno_log(errno, "listen"); diff --git a/main/gr_config.h b/main/gr_config.h index b62ea639..9a5d274e 100644 --- a/main/gr_config.h +++ b/main/gr_config.h @@ -6,9 +6,12 @@ #include #include +#include struct gr_config { const char *api_sock_path; + uid_t api_sock_uid; + gid_t api_sock_gid; unsigned log_level; bool test_mode; bool poll_mode; diff --git a/main/main.c b/main/main.c index e24410be..d03121b7 100644 --- a/main/main.c +++ b/main/main.c @@ -19,7 +19,9 @@ #include #include +#include #include +#include #include #include #include @@ -39,6 +41,8 @@ static void usage(const char *prog) { puts("options:"); puts(" -h, --help Display this help message and exit."); puts(" -L, --log-level : Specify log level for a specific component."); + puts(" -o, --socket-owner : API socket file ownership"); + puts(" (Default: getuid():getgid())."); puts(" -p, --poll-mode Disable automatic micro-sleep."); puts(" -S, --syslog Redirect logs to syslog."); puts(" -s, --socket Path the control plane API socket."); @@ -56,16 +60,66 @@ static void usage(const char *prog) { struct gr_config gr_config; +static int parse_sock_owner(char *user_group_str) { + char *group_str, *user_str = user_group_str; + struct passwd *pw; + char *colon, *end; + unsigned long val; + struct group *gr; + + colon = strchr(user_group_str, ':'); + if (!colon) { + fprintf(stderr, "error: -%c requires ':'\n", optopt); + return -1; + } + + *colon = '\0'; + group_str = colon + 1; + + pw = getpwnam(user_str); + if (!pw) { + errno = 0; + val = strtoul(user_str, &end, 10); + + if (errno || *end != '\0' || val > (uid_t)-1) { + fprintf(stderr, "error: invalid user '%s'\n", user_str); + return -1; + } + + gr_config.api_sock_uid = (uid_t)val; + } else { + gr_config.api_sock_uid = pw->pw_uid; + } + + gr = getgrnam(group_str); + if (!gr) { + errno = 0; + val = strtoul(group_str, &end, 10); + + if (errno || *end != '\0' || val > (gid_t)-1) { + fprintf(stderr, "error: invalid group %s\n", group_str); + return -1; + } + + gr_config.api_sock_gid = (gid_t)val; + } else { + gr_config.api_sock_gid = gr->gr_gid; + } + + return 0; +} + static int parse_args(int argc, char **argv) { int c; -#define FLAGS ":B:D:L:M:T:VhpSs:tvx" +#define FLAGS ":B:D:L:M:T:Vho:pSs:tvx" static struct option long_options[] = { {"help", no_argument, NULL, 'h'}, {"log-level", required_argument, NULL, 'L'}, {"poll-mode", no_argument, NULL, 'p'}, {"syslog", no_argument, NULL, 'S'}, {"socket", required_argument, NULL, 's'}, + {"socket-owner", required_argument, NULL, 'o'}, {"test-mode", no_argument, NULL, 't'}, {"trace", required_argument, NULL, 'T'}, {"trace-bufsz", required_argument, NULL, 'B'}, @@ -82,6 +136,8 @@ static int parse_args(int argc, char **argv) { gr_config.api_sock_path = getenv("GROUT_SOCK_PATH"); if (gr_config.api_sock_path == NULL) gr_config.api_sock_path = GR_DEFAULT_SOCK_PATH; + gr_config.api_sock_uid = getuid(); + gr_config.api_sock_gid = getgid(); gr_config.log_level = RTE_LOG_NOTICE; gr_config.eal_extra_args = NULL; @@ -94,6 +150,10 @@ static int parse_args(int argc, char **argv) { gr_vec_add(gr_config.eal_extra_args, "--log-level"); gr_vec_add(gr_config.eal_extra_args, optarg); break; + case 'o': + if (parse_sock_owner(optarg) < 0) + return errno_set(EINVAL); + break; case 'p': gr_config.poll_mode = true; break; From e868549237274fc95b833a3f6ca79f0656229abe Mon Sep 17 00:00:00 2001 From: Maxime Leroy Date: Wed, 30 Apr 2025 12:05:37 +0200 Subject: [PATCH 19/25] main: add --socket-mode option Add a new option to specify the permission mode for the API socket file. Signed-off-by: Maxime Leroy --- docs/grout.8.md | 8 ++++++++ main/api.c | 6 ++++++ main/gr_config.h | 2 ++ main/main.c | 26 +++++++++++++++++++++++++- 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/docs/grout.8.md b/docs/grout.8.md index 4b90d2e4..02b844d2 100644 --- a/docs/grout.8.md +++ b/docs/grout.8.md @@ -17,6 +17,7 @@ Grout is a software router based on DPDK __rte_graph__. [**-h**] [**-L** _TYPE_:_LEVEL_] [**-M** _MODE_] +[**-m** _PERMISSIONS_] [**-o** _USER_:_GROUP_] [**-p**] [**-S**] @@ -78,6 +79,13 @@ wrapped or discarded when file size reaches its maximum limit. For example: Default mode is _overwrite_ and parameter must be specified once only. +#### **-m**, **--socket-mode** _PERMISSIONS_ + +Change the API socket file permissions after creating it. Only octal values are +supported. + +Default: _0660_. + #### **-o**, **--socket-owner** _USER_:_GROUP_ Change the owner of the API socket file after creating it. Symbolic names and diff --git a/main/api.c b/main/api.c index 0123e8be..c286868c 100644 --- a/main/api.c +++ b/main/api.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -403,6 +404,11 @@ int api_socket_start(struct event_base *base) { return errno_log(errno, "API socket ownership can not be set"); } + if (chmod(path, gr_config.api_sock_mode) < 0) { + close(fd); + return errno_log(errno, "API socket permission can not be set"); + } + if (listen(fd, SOCKET_LISTEN_BACKLOG) < 0) { close(fd); return errno_log(errno, "listen"); diff --git a/main/gr_config.h b/main/gr_config.h index 9a5d274e..e37aa941 100644 --- a/main/gr_config.h +++ b/main/gr_config.h @@ -6,12 +6,14 @@ #include #include +#include #include struct gr_config { const char *api_sock_path; uid_t api_sock_uid; gid_t api_sock_gid; + mode_t api_sock_mode; unsigned log_level; bool test_mode; bool poll_mode; diff --git a/main/main.c b/main/main.c index d03121b7..eea9791e 100644 --- a/main/main.c +++ b/main/main.c @@ -41,6 +41,7 @@ static void usage(const char *prog) { puts("options:"); puts(" -h, --help Display this help message and exit."); puts(" -L, --log-level : Specify log level for a specific component."); + puts(" -m, --socket-mode API socket file permissions (Default: 0660)."); puts(" -o, --socket-owner : API socket file ownership"); puts(" (Default: getuid():getgid())."); puts(" -p, --poll-mode Disable automatic micro-sleep."); @@ -109,16 +110,34 @@ static int parse_sock_owner(char *user_group_str) { return 0; } +static int parse_sock_mode(const char *perm_str) { + unsigned long val; + char *endptr; + + errno = 0; + val = strtoul(perm_str, &endptr, 8); + + if (errno != 0 || *endptr != '\0' || val > 07777) { + fprintf(stderr, "error: invalid permissions '%s'\n", perm_str); + return -1; + } + + gr_config.api_sock_mode = (mode_t)val; + + return 0; +} + static int parse_args(int argc, char **argv) { int c; -#define FLAGS ":B:D:L:M:T:Vho:pSs:tvx" +#define FLAGS ":B:D:L:M:T:Vhm:o:pSs:tvx" static struct option long_options[] = { {"help", no_argument, NULL, 'h'}, {"log-level", required_argument, NULL, 'L'}, {"poll-mode", no_argument, NULL, 'p'}, {"syslog", no_argument, NULL, 'S'}, {"socket", required_argument, NULL, 's'}, + {"socket-mode", required_argument, NULL, 'm'}, {"socket-owner", required_argument, NULL, 'o'}, {"test-mode", no_argument, NULL, 't'}, {"trace", required_argument, NULL, 'T'}, @@ -138,6 +157,7 @@ static int parse_args(int argc, char **argv) { gr_config.api_sock_path = GR_DEFAULT_SOCK_PATH; gr_config.api_sock_uid = getuid(); gr_config.api_sock_gid = getgid(); + gr_config.api_sock_mode = 0660; gr_config.log_level = RTE_LOG_NOTICE; gr_config.eal_extra_args = NULL; @@ -150,6 +170,10 @@ static int parse_args(int argc, char **argv) { gr_vec_add(gr_config.eal_extra_args, "--log-level"); gr_vec_add(gr_config.eal_extra_args, optarg); break; + case 'm': + if (parse_sock_mode(optarg) < 0) + return errno_set(EINVAL); + break; case 'o': if (parse_sock_owner(optarg) < 0) return errno_set(EINVAL); From 9ca93361aea1f345b83df793e35711ed94fe1b9c Mon Sep 17 00:00:00 2001 From: Robin Jarry Date: Sat, 3 May 2025 11:34:03 +0200 Subject: [PATCH 20/25] port: allow attaching already probed devices At startup, we pass -a 0000:00:00.0 to rte_eal_init() to disable automatic PCI probing. However, this only affects the PCI bus devices. Other buses (e.g. FSMLC) are not affected by this argument and are always probed in rte_eal_init(). When configuring a port with such a device, we get an error from the API saying that the port is already attached to another interface: grout# add interface port dpni2 devargs fslmc:dpni.2 error: File exists Instead of iterating over the DPDK probed devices, look at grout ports to determine whether a port matching this devargs value is already configured. If we cannot find one, check if the device has already been probed and only probe it if it was not. Signed-off-by: Robin Jarry --- modules/infra/control/port.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/modules/infra/control/port.c b/modules/infra/control/port.c index da0e4175..80bea05d 100644 --- a/modules/infra/control/port.c +++ b/modules/infra/control/port.c @@ -326,21 +326,31 @@ static int iface_port_init(struct iface *iface, const void *api_info) { const struct gr_iface_info_port *api = api_info; uint16_t port_id = RTE_MAX_ETHPORTS; struct rte_dev_iterator iterator; + const struct iface *i; struct gr_iface conf; int ret; - RTE_ETH_FOREACH_MATCHING_DEV(port_id, api->devargs, &iterator) { - rte_eth_iterator_cleanup(&iterator); - return errno_set(EEXIST); + i = NULL; + while ((i = iface_next(GR_IFACE_TYPE_PORT, i)) != NULL) { + const struct iface_info_port *p = (const struct iface_info_port *)i->info; + if (strncmp(p->devargs, api->devargs, sizeof(api->devargs)) == 0) + return errno_set(EEXIST); } - if ((ret = rte_dev_probe(api->devargs)) < 0) - return errno_set(-ret); - RTE_ETH_FOREACH_MATCHING_DEV(port_id, api->devargs, &iterator) { rte_eth_iterator_cleanup(&iterator); break; } + + if (!rte_eth_dev_is_valid_port(port_id)) { + if ((ret = rte_dev_probe(api->devargs)) < 0) + return errno_set(-ret); + RTE_ETH_FOREACH_MATCHING_DEV(port_id, api->devargs, &iterator) { + rte_eth_iterator_cleanup(&iterator); + break; + } + } + if (!rte_eth_dev_is_valid_port(port_id)) return errno_set(EIDRM); From ffaa79a7b9eefcd10853dfed163a7288ca95f1bb Mon Sep 17 00:00:00 2001 From: Robin Jarry Date: Mon, 5 May 2025 10:58:24 +0200 Subject: [PATCH 21/25] port: avoid truncating link speed values above 64gb/s Ports with higher speeds (100gb/s and 400gb/s) are not represented in the API. The field is only a 16bit wide integer. These values are truncated. Change the field to use 32 bits as it is in the DPDK API. Fixes: 5a25e184cb1d ("port: expose link_speed to the api") Signed-off-by: Robin Jarry --- modules/infra/api/gr_infra.h | 2 +- modules/infra/cli/port.c | 2 +- modules/infra/control/port.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/infra/api/gr_infra.h b/modules/infra/api/gr_infra.h index 4a50628d..4bb0b55f 100644 --- a/modules/infra/api/gr_infra.h +++ b/modules/infra/api/gr_infra.h @@ -90,7 +90,7 @@ struct __gr_iface_info_port_base { uint16_t n_txq; uint16_t rxq_size; uint16_t txq_size; - uint16_t link_speed; //!< Physical link speed in Megabit/sec. + uint32_t link_speed; //!< Physical link speed in Megabit/sec. struct rte_ether_addr mac; }; diff --git a/modules/infra/cli/port.c b/modules/infra/cli/port.c index 49a94216..b42b7016 100644 --- a/modules/infra/cli/port.c +++ b/modules/infra/cli/port.c @@ -21,7 +21,7 @@ static void port_show(const struct gr_api_client *c, const struct gr_iface *ifac printf("devargs: %s\n", port->devargs); printf("driver: %s\n", port->driver_name); printf("mac: " ETH_F "\n", &port->mac); - if (port->link_speed == UINT16_MAX) + if (port->link_speed == UINT32_MAX) printf("speed: unknown\n"); else printf("speed: %u Mb/s\n", port->link_speed); diff --git a/modules/infra/control/port.c b/modules/infra/control/port.c index 80bea05d..a71160d8 100644 --- a/modules/infra/control/port.c +++ b/modules/infra/control/port.c @@ -605,7 +605,7 @@ static void link_event_cb(evutil_socket_t, short /*what*/, void * /*priv*/) { LOG(WARNING, "rte_eth_link_get_nowait: %s", strerror(rte_errno)); continue; } - port->link_speed = (uint16_t)link.link_speed; + port->link_speed = link.link_speed; if (link.link_status == RTE_ETH_LINK_UP) { if (!(iface->state & GR_IFACE_S_RUNNING)) { From 1e1d65332bf31284990e8d0a14c35b5078626d44 Mon Sep 17 00:00:00 2001 From: Robin Jarry Date: Mon, 5 May 2025 12:31:06 +0200 Subject: [PATCH 22/25] iface: use base structures for copying to api There is no need to copy individual fields. Use the base structures to copy common fields from the internal and public API messages. Fixes: c5f94a9386ae ("iface: reuse public structures") Signed-off-by: Robin Jarry --- modules/infra/control/port.c | 6 +----- modules/infra/control/vlan.c | 4 +--- modules/ipip/control.c | 3 +-- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/modules/infra/control/port.c b/modules/infra/control/port.c index a71160d8..f3708345 100644 --- a/modules/infra/control/port.c +++ b/modules/infra/control/port.c @@ -559,12 +559,8 @@ static void port_to_api(void *info, const struct iface *iface) { struct gr_iface_info_port *api = info; struct rte_eth_dev_info dev_info; + api->base = port->base; memccpy(api->devargs, port->devargs, 0, sizeof(api->devargs)); - api->mac = port->mac; - api->n_rxq = port->n_rxq; - api->n_txq = port->n_txq; - api->rxq_size = port->rxq_size; - api->txq_size = port->txq_size; if (rte_eth_dev_info_get(port->port_id, &dev_info) == 0) { memccpy(api->driver_name, dev_info.driver_name, 0, sizeof(api->driver_name)); diff --git a/modules/infra/control/vlan.c b/modules/infra/control/vlan.c index 8499c4bd..83e5deed 100644 --- a/modules/infra/control/vlan.c +++ b/modules/infra/control/vlan.c @@ -218,9 +218,7 @@ static void vlan_to_api(void *info, const struct iface *iface) { const struct iface_info_vlan *vlan = (const struct iface_info_vlan *)iface->info; struct gr_iface_info_vlan *api = info; - api->parent_id = vlan->parent_id; - api->vlan_id = vlan->vlan_id; - api->mac = vlan->mac; + *api = vlan->base; } static struct iface_type iface_type_vlan = { diff --git a/modules/ipip/control.c b/modules/ipip/control.c index 77441457..29b94aec 100644 --- a/modules/ipip/control.c +++ b/modules/ipip/control.c @@ -120,8 +120,7 @@ static void ipip_to_api(void *info, const struct iface *iface) { const struct iface_info_ipip *ipip = (const struct iface_info_ipip *)iface->info; struct gr_iface_info_ipip *api = info; - api->local = ipip->local; - api->remote = ipip->remote; + *api = ipip->base; } static struct iface_type iface_type_ipip = { From e82a2d448f735fcedb286a30b9e895f31273a466 Mon Sep 17 00:00:00 2001 From: Vincent Jardin Date: Mon, 5 May 2025 23:03:48 +0200 Subject: [PATCH 23/25] dpaa2: do not close devices The dpaa2 devices do not support close/re-open with the current DPDK. Let's work around this limitation for the time being. From the design of the DPDK's fslmc bus, it seems to be the only option for the time being. Fix: grout# add interface port dpni.2 devargs fslmc:dpni.2 up mode l3 Created interface 1 grout# del interface dpni.2 grout# add interface port dpni.2 devargs fslmc:dpni.2 up mode l3 error: command failed: Identifier removed Fixes: 9ca93361aea1 ("port: allow attaching already probed device") Signed-off-by: Vincent Jardin --- modules/infra/control/port.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/infra/control/port.c b/modules/infra/control/port.c index f3708345..24addeed 100644 --- a/modules/infra/control/port.c +++ b/modules/infra/control/port.c @@ -307,10 +307,15 @@ static int iface_port_fini(struct iface *iface) { LOG(ERR, "rte_eth_dev_info_get: %s", rte_strerror(-ret)); if ((ret = rte_eth_dev_stop(port->port_id)) < 0) LOG(ERR, "rte_eth_dev_stop: %s", rte_strerror(-ret)); + // XXX DPDK bus/fslmc VFIO constraint for dpaa2 + if (strcmp(info.driver_name, "net_dpaa2") == 0) + goto fini; if ((ret = rte_eth_dev_close(port->port_id)) < 0) LOG(ERR, "rte_eth_dev_close: %s", rte_strerror(-ret)); if (info.device != NULL && (ret = rte_dev_remove(info.device)) < 0) LOG(ERR, "rte_dev_remove: %s", rte_strerror(-ret)); + +fini: if (port->pool != NULL) { gr_pktmbuf_pool_release(port->pool, port->pool_size); port->pool = NULL; From a96b78eb599be82ebafaf651d955d1d7dbb3f1ab Mon Sep 17 00:00:00 2001 From: Vincent Jardin Date: Mon, 5 May 2025 19:33:33 +0200 Subject: [PATCH 24/25] cli: autocomplete support on fslmc CPUs - QorIQ Add support for parsing and adding to the autocomplete the contents of the fslmc bus (for example for a LX2160 QorIQ CPU). Example: grout# add interface port dpni.2 devargs fslmc:dpni.2 net_null net_tap net_tun net_vhost net_virtio_user grout# add interface port dpni.2 devargs f grout# add interface port dpni.2 devargs fslmc:dpni.2 The bus cases (PCI, fslmc, vdev) have not been redesigned. Signed-off-by: Vincent Jardin --- cli/ec_node_devargs.c | 48 ++++++++++++++++++++++++++++++++++++++++++- docs/grcli.1.md | 11 ++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/cli/ec_node_devargs.c b/cli/ec_node_devargs.c index e1e61987..c47e999b 100644 --- a/cli/ec_node_devargs.c +++ b/cli/ec_node_devargs.c @@ -22,6 +22,7 @@ ec_node_devargs_parse(const struct ec_node *, struct ec_pnode *, const struct ec return 1; } +#define SYS_FSL_MC_DEVICES "/sys/bus/fsl-mc/devices" #define SYS_PCI_DEVICES "/sys/bus/pci/devices" #define PCI_CLASS_ETH "0x020000" static const char *const dpdk_vdevs[] = { @@ -53,7 +54,7 @@ static int ec_node_devargs_complete( dir = opendir(SYS_PCI_DEVICES); if (dir == NULL) - goto out; // sysfs not mounted. + goto skip_pci; while ((de = readdir(dir)) != NULL) { if (fd != -1) @@ -88,7 +89,52 @@ static int ec_node_devargs_complete( if (!ec_comp_add_item(comp, node, EC_COMP_FULL, word, de->d_name)) goto fail; } + closedir(dir); + dir = NULL; +skip_pci: + /* see dpdk fsml_bus.c:rte_fslmc_scan() without rte_ specific code */ + char fslmc_dirpath[PATH_MAX]; + const char *group_name; + + group_name = getenv("DPRC"); + if (!group_name) + goto skip_fslmc; + + snprintf(fslmc_dirpath, sizeof(fslmc_dirpath), "%s/%s", SYS_FSL_MC_DEVICES, group_name); + dir = opendir(fslmc_dirpath); + if (!dir) + goto skip_fslmc; + + while ((de = readdir(dir)) != NULL) { + if (de->d_name[0] == '.' || de->d_type != DT_DIR) + continue; + snprintf(buf2, sizeof(buf2), "fslmc:%s", de->d_name); // devarg = "fslmc:dpni.X" + if (!ec_str_startswith(buf2, word)) + continue; + + /* Parse the device name, ignore ID */ + if (strncmp("dpni.", de->d_name, 5)) + continue; + /* dev_type is DPAA2_ETH, but driver shall be vfio-fsl-mc */ + snprintf(buf, sizeof(buf), "%s/%s", de->d_name, "driver"); + if ((dir_fd = dirfd(dir)) < 0) + continue; + if ((n = readlinkat(dir_fd, buf, buf2, sizeof(buf2))) < 0) + continue; + buf2[n] = '\0'; + driver = basename(buf2); + if (strcmp(driver, "vfio-fsl-mc") != 0) + continue; + + snprintf(buf2, sizeof(buf2), "fslmc:%s", de->d_name); // devarg = "fslmc:dpni.X" + if (!ec_comp_add_item(comp, node, EC_COMP_FULL, word, buf2)) + goto fail; + } + closedir(dir); + dir = NULL; + +skip_fslmc: for (unsigned i = 0; i < ARRAY_DIM(dpdk_vdevs); i++) { if (!ec_str_startswith(dpdk_vdevs[i], word)) continue; diff --git a/docs/grcli.1.md b/docs/grcli.1.md index 7e17a2e9..6323d85d 100644 --- a/docs/grcli.1.md +++ b/docs/grcli.1.md @@ -48,6 +48,17 @@ Print version and exit. Print executed commands. +# ENVIRONMENT + +#### **DPRC** + +Set the DPRC - Datapath Resource Container: This value should match the +one used by DPDK during the scan of the fslmc bus. It is recommended to +set this on any NXP QorIQ targets. This serves as the entry point for +grcli to enable autocompletion of fslmc devices manageable by grout. +While grcli can configure grout without this environment setting, +autocompletion of the devargs will not be available. + # SEE ALSO **grout**(8) From 8511c2721df41135a3c81841b244734d005ec921 Mon Sep 17 00:00:00 2001 From: Robin Jarry Date: Wed, 7 May 2025 12:04:16 +0200 Subject: [PATCH 25/25] grout: release v0.10.0 Author Commits Changed Files Insertions Deletions Robin Jarry 12 39 +365 -110 Maxime Leroy 10 53 +355 -241 Vincent Jardin 2 3 +63 -1 Signed-off-by: Robin Jarry --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 8db59e82..9fc956c0 100644 --- a/meson.build +++ b/meson.build @@ -5,7 +5,7 @@ project( 'grout', 'c', version: run_command( - 'sh', '-c', 'echo ${GROUT_VERSION:-$(git describe --long --abbrev=8 --dirty 2>/dev/null || echo v0.9.1)}', + 'sh', '-c', 'echo ${GROUT_VERSION:-$(git describe --long --abbrev=8 --dirty 2>/dev/null || echo v0.10.0)}', check: false, capture: true, ).stdout().strip(),