From 7927bd340976cd170829a9b07412d1fc121f7ba8 Mon Sep 17 00:00:00 2001 From: Christophe Fontaine Date: Thu, 16 Jan 2025 08:30:32 +0000 Subject: [PATCH 1/6] ip6: always register to multicast addresses As the loopback interface is not ethernet based, well known multicast addresses were not registered: grout rejects the neighbor solicitation and router solication messages. Register to the well known multicast addresses even if there is no ethernet addresses configured on the interface. Signed-off-by: Christophe Fontaine --- modules/ip6/control/address.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/modules/ip6/control/address.c b/modules/ip6/control/address.c index 1fa8befe..0a9fb8ec 100644 --- a/modules/ip6/control/address.c +++ b/modules/ip6/control/address.c @@ -296,14 +296,10 @@ static void ip6_iface_event_handler(iface_event_t event, struct iface *iface) { rte_ipv6_solnode_from_addr(&solicited_node, &link_local); if (ip6_mcast_addr_add(iface, &solicited_node) < 0) LOG(INFO, "%s: mcast_addr_add: %s", iface->name, strerror(errno)); - - for (i = 0; i < ARRAY_DIM(well_known_mcast_addrs); i++) { - if (ip6_mcast_addr_add(iface, &well_known_mcast_addrs[i]) < 0) - LOG(INFO, - "%s: mcast_addr_add: %s", - iface->name, - strerror(errno)); - } + } + for (i = 0; i < ARRAY_DIM(well_known_mcast_addrs); i++) { + if (ip6_mcast_addr_add(iface, &well_known_mcast_addrs[i]) < 0) + LOG(INFO, "%s: mcast_addr_add: %s", iface->name, strerror(errno)); } break; case IFACE_EVENT_PRE_REMOVE: From aa7f895ae6f5e659c52ac69abc05deeccd10062d Mon Sep 17 00:00:00 2001 From: Christophe Fontaine Date: Sun, 15 Dec 2024 17:08:55 +0000 Subject: [PATCH 2/6] ip6: send icmp router advertisement BGP unnumbered relies on RA message to discover the local routers. Broadcast the simplest router advertisement message on ipv6 enabled interfaces. Additional options (DNS, ... ) may be added in the future. Signed-off-by: Christophe Fontaine --- docs/graph.svg | 388 ++++++++++++++-------------- modules/ip6/api/gr_ip6.h | 11 + modules/ip6/control/meson.build | 1 + modules/ip6/control/router_advert.c | 162 ++++++++++++ 4 files changed, 368 insertions(+), 194 deletions(-) create mode 100644 modules/ip6/control/router_advert.c diff --git a/docs/graph.svg b/docs/graph.svg index 31004b98..22003d18 100644 --- a/docs/graph.svg +++ b/docs/graph.svg @@ -1,577 +1,577 @@ - - - + + -gr-0003 - +gr-0005 + control_input - -control_input + +control_input loopback_input - -loopback_input + +loopback_input control_input->loopback_input - - + + arp_output_request - -arp_output_request + +arp_output_request control_input->arp_output_request - - + + icmp_local_send - -icmp_local_send + +icmp_local_send control_input->icmp_local_send - - + + ndp_ns_output - -ndp_ns_output + +ndp_ns_output control_input->ndp_ns_output - - + + ip_output - -ip_output + +ip_output control_input->ip_output - - + + ip6_output - -ip6_output + +ip6_output control_input->ip6_output - - + + ip_input - -ip_input + +ip_input loopback_input->ip_input - - + + ip6_input - -ip6_input + +ip6_input loopback_input->ip6_input - - + + eth_output - -eth_output + +eth_output arp_output_request->eth_output - - + + icmp_output - -icmp_output + +icmp_output icmp_local_send->icmp_output - - + + ndp_ns_output->ip6_output - - + + ip_output->eth_output - - + + loopback_output - -loopback_output + +loopback_output ip_output->loopback_output - - + + ip_hold - -ip_hold + +ip_hold ip_output->ip_hold - - + + ipip_output - -ipip_output + +ipip_output ip_output->ipip_output - - + + ip6_output->eth_output - - + + ip6_output->loopback_output - - + + ip6_hold - -ip6_hold + +ip6_hold ip6_output->ip6_hold - - + + control_output - -control_output + +control_output eth_input - -eth_input + +eth_input arp_input - -arp_input + +arp_input eth_input->arp_input - - + + eth_input->ip_input - - + + eth_input->ip6_input - - + + arp_input_request - -arp_input_request + +arp_input_request arp_input->arp_input_request - - + + arp_input_reply - -arp_input_reply + +arp_input_reply arp_input->arp_input_reply - - + + ip_input->ip_output - - + + ip_forward - -ip_forward + +ip_forward ip_input->ip_forward - - + + ip_input_local - -ip_input_local + +ip_input_local ip_input->ip_input_local - - + + ip6_input->ip6_output - - + + ip6_forward - -ip6_forward + +ip6_forward ip6_input->ip6_forward - - + + ip6_input_local - -ip6_input_local + +ip6_input_local ip6_input->ip6_input_local - - + + port_tx - -port_tx + +port_tx eth_output->port_tx - - + + loopback_output->control_output - - + + port_rx - -port_rx + +port_rx port_rx->eth_input - - + + arp_input_request->control_output - - + + arp_output_reply - -arp_output_reply + +arp_output_reply arp_input_request->arp_output_reply - - + + arp_input_reply->control_output - - + + arp_output_reply->eth_output - - + + icmp_input - -icmp_input + +icmp_input icmp_input->control_output - - + + icmp_input->icmp_output - - + + icmp_output->ip_output - - + + ip_forward->ip_output - - + + ip_hold->control_output - - + + ip_input_local->icmp_input - - + + ipip_input - -ipip_input + +ipip_input ip_input_local->ipip_input - - + + l4_input_local - -l4_input_local + +l4_input_local ip_input_local->l4_input_local - - + + ipip_input->ip_input - - + + l4_loopback_output - -l4_loopback_output + +l4_loopback_output l4_input_local->l4_loopback_output - - + + ipip_output->ip_output - - + + icmp6_input - -icmp6_input + +icmp6_input icmp6_output - -icmp6_output + +icmp6_output icmp6_input->icmp6_output - - + + ndp_ns_input - -ndp_ns_input + +ndp_ns_input icmp6_input->ndp_ns_input - - + + ndp_na_input - -ndp_na_input + +ndp_na_input icmp6_input->ndp_na_input - - + + icmp6_output->ip6_output - - + + ndp_ns_input->ip6_output - - + + ndp_ns_input->control_output - - + + ndp_na_input->control_output - - + + ip6_forward->ip6_output - - + + ip6_hold->control_output - - + + ip6_input_local->l4_input_local - - + + ip6_input_local->icmp6_input - - + + l4_loopback_output->loopback_output - - + + diff --git a/modules/ip6/api/gr_ip6.h b/modules/ip6/api/gr_ip6.h index 8abf3cbe..01137176 100644 --- a/modules/ip6/api/gr_ip6.h +++ b/modules/ip6/api/gr_ip6.h @@ -132,4 +132,15 @@ struct gr_ip6_addr_list_resp { struct gr_ip6_ifaddr addrs[/* n_addrs */]; }; +#define GR_IP6_IFACE_RA_SET REQUEST_TYPE(GR_IP6_MODULE, 0x0030) +struct gr_ip6_ra_set_req { + uint16_t iface_id; +}; +// struct gr_ip6_ra_set_resp { }; + +#define GR_IP6_IFACE_RA_CLEAR REQUEST_TYPE(GR_IP6_MODULE, 0x0031) +struct gr_ip6_ra_clear_req { + uint16_t iface_id; +}; +// struct gr_ip6_ra_clear_resp { }; #endif diff --git a/modules/ip6/control/meson.build b/modules/ip6/control/meson.build index 0fbd8ec2..248f09f9 100644 --- a/modules/ip6/control/meson.build +++ b/modules/ip6/control/meson.build @@ -5,5 +5,6 @@ src += files( 'address.c', 'nexthop.c', 'route.c', + 'router_advert.c', ) inc += include_directories('.') diff --git a/modules/ip6/control/router_advert.c b/modules/ip6/control/router_advert.c new file mode 100644 index 00000000..7636da62 --- /dev/null +++ b/modules/ip6/control/router_advert.c @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2025 Christophe Fontaine + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +struct ra_ctx { + struct rte_mempool *mp; + struct event *timer; +}; + +static struct ra_ctx ra_ctx; +static control_input_t ra_output; + +static struct api_out iface_ra_set(const void *request, void ** /*response*/) { + const struct gr_ip6_ra_set_req *req = request; + if (iface_from_id(req->iface_id) == NULL) + return api_out(errno, 0); + + return api_out(0, 0); +} + +static struct api_out iface_ra_clear(const void *request, void ** /*response*/) { + const struct gr_ip6_ra_clear_req *req = request; + if (iface_from_id(req->iface_id) == NULL) + return api_out(errno, 0); + + return api_out(0, 0); +} + +static void build_ra_packet(struct rte_mbuf *m, struct rte_ipv6_addr *srcv6) { + struct rte_ipv6_addr dst = RTE_IPV6_ADDR_ALLNODES_LINK_LOCAL; + struct rte_ipv6_addr src = *srcv6; + struct icmp6_opt_lladdr *lladdr; + struct icmp6_router_advert *ra; + struct rte_ether_addr mac; + struct rte_ipv6_hdr *ip; + struct icmp6_opt *opt; + uint16_t payload_len; + struct icmp6 *icmp6; + uint16_t iface_id; + uint16_t vrf_id; + + vrf_id = mbuf_data(m)->iface->vrf_id; + iface_id = mbuf_data(m)->iface->id; + ip6_output_mbuf_data(m)->nh = ip6_nexthop_lookup(vrf_id, iface_id, &dst); + ip = (struct rte_ipv6_hdr *)rte_pktmbuf_append(m, sizeof(*ip)); + icmp6 = (struct icmp6 *)rte_pktmbuf_append(m, sizeof(*icmp6)); + icmp6->type = ICMP6_TYPE_ROUTER_ADVERT; + icmp6->code = 0; + ra = (struct icmp6_router_advert *)rte_pktmbuf_append(m, sizeof(*ra)); + ra->cur_hoplim = IP6_DEFAULT_HOP_LIMIT; // Default TTL for this network + ra->managed_addr = 0; // DHCPv6 is available + ra->other_config = 0; // DNS available, ... + ra->lifetime = RTE_BE16(0); // Not a default router + ra->reachable_time = RTE_BE16(0); + ra->retrans_timer = RTE_BE16(0); + + payload_len = sizeof(*icmp6) + sizeof(*ra); + + if (iface_get_eth_addr(mbuf_data(m)->iface->id, &mac) == 0) { + opt = (struct icmp6_opt *)rte_pktmbuf_append(m, sizeof(*opt)); + opt->type = ICMP6_OPT_SRC_LLADDR; + opt->len = ICMP6_OPT_LEN(sizeof(*opt) + sizeof(*lladdr)); + lladdr = (struct icmp6_opt_lladdr *)rte_pktmbuf_append(m, sizeof(*lladdr)); + lladdr->mac = mac; + payload_len += sizeof(*opt) + sizeof(*lladdr); + } + + ip6_set_fields(ip, payload_len, IPPROTO_ICMPV6, &src, &dst); + icmp6->cksum = 0; + icmp6->cksum = rte_ipv6_udptcp_cksum(ip, icmp6); +} + +static void send_ra_cb(evutil_socket_t, short /*what*/, void * /*priv*/) { + struct iface *iface = NULL; + struct rte_mbuf *m; + + while ((iface = iface_next(GR_IFACE_TYPE_UNDEF, iface)) != NULL) { + struct hoplist *hl = ip6_addr_get_all(iface->id); + if (hl == NULL) + continue; + for (unsigned i = 0; i < gr_vec_len(hl); i++) { + struct nexthop *nh = hl->nh[i]; + struct rte_ipv6_addr ip = nh->ipv6; + if (nh->family != AF_INET6) + continue; + if (!rte_ipv6_addr_is_linklocal(&ip)) + continue; + + if ((m = rte_pktmbuf_alloc(ra_ctx.mp)) == NULL) { + LOG(ERR, "rte_pktmbuf_alloc"); + return; + } + mbuf_data(m)->iface = iface; + build_ra_packet(m, &ip); + post_to_stack(ra_output, m); + } + } +} + +static void ra_init(struct event_base *ev_base) { + ra_output = gr_control_input_register_handler("ip6_output", true); + ra_ctx.mp = gr_pktmbuf_pool_get(SOCKET_ID_ANY, 512); + if (ra_ctx.mp == NULL) { + ABORT("gr_pktmbuf_pool_get ENOMEM"); + } + + ra_ctx.timer = event_new(ev_base, -1, EV_PERSIST, send_ra_cb, NULL); + if (ra_ctx.timer == NULL) { + ABORT("event_new() failed"); + } + + if (event_add(ra_ctx.timer, &(struct timeval) {.tv_sec = 600}) < 0) { + ABORT("event_add() failed"); + } +} + +static void ra_fini(struct event_base * /*ev_base*/) { + gr_pktmbuf_pool_release(ra_ctx.mp, 512); + event_free(ra_ctx.timer); +} + +static struct gr_module ra_module = { + .name = "ipv6 router advertisement", + .init = ra_init, + .fini = ra_fini, + .fini_prio = 20000, +}; + +static struct gr_api_handler ra_set_handler = { + .name = "set interface ra", + .request_type = GR_IP6_IFACE_RA_SET, + .callback = iface_ra_set, +}; + +static struct gr_api_handler ra_clear_handler = { + .name = "clear interface ra", + .request_type = GR_IP6_IFACE_RA_CLEAR, + .callback = iface_ra_clear, +}; + +RTE_INIT(router_advertisement_init) { + gr_register_module(&ra_module); + gr_register_api_handler(&ra_set_handler); + gr_register_api_handler(&ra_clear_handler); +} From f2f2e8162261e30abe5c278b177dc4fd746896c0 Mon Sep 17 00:00:00 2001 From: Christophe Fontaine Date: Fri, 20 Dec 2024 10:53:57 +0000 Subject: [PATCH 3/6] ip6: fix iface output for multicast destination The returned nexthop for a multicast route lookup doesn't have a defined iface. Yet, this nexthop is valid, and we can rely on the iface defined by the parent node to select the output iface. Signed-off-by: Christophe Fontaine --- modules/ip6/datapath/ip6_output.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/ip6/datapath/ip6_output.c b/modules/ip6/datapath/ip6_output.c index 9362c50a..3ab0f404 100644 --- a/modules/ip6/datapath/ip6_output.c +++ b/modules/ip6/datapath/ip6_output.c @@ -57,7 +57,12 @@ ip6_output_process(struct rte_graph *graph, struct rte_node *node, void **objs, edge = DEST_UNREACH; goto next; } - iface = iface_from_id(nh->iface_id); + + // For multicast destination, nh->iface will be NULL + if (rte_ipv6_addr_is_mcast(&ip->dst_addr)) + iface = mbuf_data(mbuf)->iface; + else + iface = iface_from_id(nh->iface_id); if (iface == NULL) { edge = ERROR; goto next; From 73cf73452632923c5a0a6b26a21beca1c78e7ddb Mon Sep 17 00:00:00 2001 From: Christophe Fontaine Date: Fri, 17 Jan 2025 11:51:26 +0000 Subject: [PATCH 4/6] ip6: tests - use traceroute Use traceroute instead of traceroute6, to use the same tool as ip4_forward_test.sh Signed-off-by: Christophe Fontaine --- smoke/ip6_forward_test.sh | 4 ++-- smoke/ip6_same_peer_test.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/smoke/ip6_forward_test.sh b/smoke/ip6_forward_test.sh index 43fad0bf..02ce3886 100755 --- a/smoke/ip6_forward_test.sh +++ b/smoke/ip6_forward_test.sh @@ -32,5 +32,5 @@ ip netns exec $p1 ping6 -i0.01 -c3 fd00:ba4:2::2 ip netns exec $p2 ping6 -i0.01 -c3 fd00:ba4:1::2 ip netns exec $p1 ping6 -i0.01 -c3 fd00:ba4:1::1 ip netns exec $p2 ping6 -i0.01 -c3 fd00:ba4:2::1 -ip netns exec $p1 traceroute6 -N1 fd00:ba4:2::2 -ip netns exec $p2 traceroute6 -N1 fd00:ba4:1::2 +ip netns exec $p1 traceroute -N1 fd00:ba4:2::2 +ip netns exec $p2 traceroute -N1 fd00:ba4:1::2 diff --git a/smoke/ip6_same_peer_test.sh b/smoke/ip6_same_peer_test.sh index 4192d569..92c5341f 100755 --- a/smoke/ip6_same_peer_test.sh +++ b/smoke/ip6_same_peer_test.sh @@ -35,5 +35,5 @@ ip netns exec $p1 ping6 -i0.01 -c3 fd00:ba4:2::2 ip netns exec $p2 ping6 -i0.01 -c3 fd00:ba4:1::2 ip netns exec $p1 ping6 -i0.01 -c3 fd00:ba4:1::1 ip netns exec $p2 ping6 -i0.01 -c3 fd00:ba4:2::1 -ip netns exec $p1 traceroute6 -N1 fd00:ba4:2::2 -ip netns exec $p2 traceroute6 -N1 fd00:ba4:1::2 +ip netns exec $p1 traceroute -N1 fd00:ba4:2::2 +ip netns exec $p2 traceroute -N1 fd00:ba4:1::2 From 711a2f3a3d347c00fb7fae37b0331626846dfa07 Mon Sep 17 00:00:00 2001 From: Christophe Fontaine Date: Thu, 19 Dec 2024 13:33:18 +0000 Subject: [PATCH 5/6] ip6: answer to router sollicit message Upon reception of a router sollicit message, trigger the existing timer to send immediately a router advertismement packet. Add relevant test: as the same mac address is defined for 2 interfaces in the 2 namespaces, the same link local ip address will be generated and used by linux. Update github workflow and doc to reflect the addition of 'ndisc6' as a test tool. Signed-off-by: Christophe Fontaine --- .github/workflows/check.yml | 3 +- README.md | 19 +- docs/graph.svg | 352 ++++++++++++++------------- modules/ip6/control/gr_ip6_control.h | 1 + modules/ip6/control/router_advert.c | 5 + modules/ip6/datapath/icmp6_input.c | 5 + modules/ip6/datapath/meson.build | 1 + modules/ip6/datapath/ndp_rs_input.c | 92 +++++++ smoke/ip6_rs_ra_test.sh | 25 ++ 9 files changed, 331 insertions(+), 172 deletions(-) create mode 100644 modules/ip6/datapath/ndp_rs_input.c create mode 100755 smoke/ip6_rs_ra_test.sh diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 11de4fe1..29e1f1dc 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -66,7 +66,8 @@ jobs: sudo NEEDRESTART_MODE=l apt-get install -qy --no-install-recommends \ git socat tcpdump traceroute graphviz \ iproute2 iputils-ping libasan8 libedit2 \ - libevent-2.1-7t64 libsmartcols1 libnuma1 + libevent-2.1-7t64 libsmartcols1 libnuma1 \ + ndisc6 - uses: actions/checkout@v4 with: persist-credentials: false diff --git a/README.md b/README.md index 8a30130d..0d17dc77 100644 --- a/README.md +++ b/README.md @@ -237,8 +237,7 @@ image. ```sh dnf install gcc git make meson ninja-build pkgconf scdoc python3-pyelftools \ libcmocka-devel libedit-devel libevent-devel numactl-devel \ - libsmartcols-devel libarchive-devel rdma-core-devel \ - clang-tools-extra jq curl traceroute graphviz + libsmartcols-devel libarchive-devel rdma-core-devel ``` or @@ -246,8 +245,20 @@ or ```sh apt install git gcc make meson ninja-build pkgconf scdoc python3-pyelftools \ libcmocka-dev libedit-dev libevent-dev libnuma-dev \ - libsmartcols-dev libarchive-dev libibverbs-dev \ - clang-format jq curl traceroute graphviz + libsmartcols-dev libarchive-dev libibverbs-dev +``` + +### Install optional dependencies +In order to run "make smoke-test" and "make update-graph", you'll need additional packages: + +```sh +dnf install clang-tools-extra jq curl traceroute graphviz ndisc6 +``` + +or + +```sh +apt install clang-format jq curl traceroute graphviz ndisc6 ``` ### Build diff --git a/docs/graph.svg b/docs/graph.svg index 22003d18..13465fa5 100644 --- a/docs/graph.svg +++ b/docs/graph.svg @@ -4,64 +4,64 @@ - - + + gr-0005 - + control_input - -control_input + +control_input loopback_input - -loopback_input + +loopback_input control_input->loopback_input - - + + arp_output_request - -arp_output_request + +arp_output_request control_input->arp_output_request - - + + icmp_local_send - -icmp_local_send + +icmp_local_send control_input->icmp_local_send - - + + ndp_ns_output - -ndp_ns_output + +ndp_ns_output control_input->ndp_ns_output - - + + @@ -72,20 +72,20 @@ control_input->ip_output - + ip6_output - -ip6_output + +ip6_output control_input->ip6_output - - + + @@ -96,32 +96,32 @@ loopback_input->ip_input - - + + ip6_input - -ip6_input + +ip6_input loopback_input->ip6_input - - + + eth_output - -eth_output + +eth_output arp_output_request->eth_output - - + + @@ -132,32 +132,32 @@ icmp_local_send->icmp_output - + - + ndp_ns_output->ip6_output - - + + ip_output->eth_output - - + + loopback_output - -loopback_output + +loopback_output ip_output->loopback_output - - + + @@ -184,88 +184,88 @@ - + ip6_output->eth_output - - + + - + ip6_output->loopback_output - - + + - + ip6_hold - -ip6_hold + +ip6_hold - + ip6_output->ip6_hold - - + + control_output - -control_output + +control_output eth_input - -eth_input + +eth_input arp_input - -arp_input + +arp_input eth_input->arp_input - - + + eth_input->ip_input - - + + eth_input->ip6_input - - + + arp_input_request - -arp_input_request + +arp_input_request arp_input->arp_input_request - - + + arp_input_reply - -arp_input_reply + +arp_input_reply arp_input->arp_input_reply - - + + @@ -298,94 +298,94 @@ - + ip6_input->ip6_output - - + + - + ip6_forward - -ip6_forward + +ip6_forward - + ip6_input->ip6_forward - - + + - + ip6_input_local - -ip6_input_local + +ip6_input_local - + ip6_input->ip6_input_local - - + + port_tx - -port_tx + +port_tx eth_output->port_tx - - + + loopback_output->control_output - - + + port_rx - -port_rx + +port_rx port_rx->eth_input - - + + arp_input_request->control_output - - + + arp_output_reply - -arp_output_reply + +arp_output_reply arp_input_request->arp_output_reply - - + + arp_input_reply->control_output - - + + arp_output_reply->eth_output - - + + @@ -396,8 +396,8 @@ icmp_input->control_output - - + + @@ -420,8 +420,8 @@ ip_hold->control_output - - + + @@ -454,25 +454,25 @@ - + ipip_input->ip_input - + l4_loopback_output - -l4_loopback_output + +l4_loopback_output - + l4_input_local->l4_loopback_output - - + + - + ipip_output->ip_output @@ -480,98 +480,116 @@ icmp6_input - -icmp6_input + +icmp6_input icmp6_output - -icmp6_output + +icmp6_output icmp6_input->icmp6_output - - + + ndp_ns_input - -ndp_ns_input + +ndp_ns_input icmp6_input->ndp_ns_input - - + + ndp_na_input - -ndp_na_input + +ndp_na_input icmp6_input->ndp_na_input - - + + - + + +ndp_rs_input + +ndp_rs_input + + +icmp6_input->ndp_rs_input + + + + + icmp6_output->ip6_output - - + + - + ndp_ns_input->ip6_output - - + + - + ndp_ns_input->control_output - - + + - + ndp_na_input->control_output - - + + + + + +ndp_rs_input->control_output + + - + ip6_forward->ip6_output - - + + - + ip6_hold->control_output - - + + - + ip6_input_local->l4_input_local - - + + - + ip6_input_local->icmp6_input - - + + - + l4_loopback_output->loopback_output - - + + diff --git a/modules/ip6/control/gr_ip6_control.h b/modules/ip6/control/gr_ip6_control.h index 55ac7f69..02086956 100644 --- a/modules/ip6/control/gr_ip6_control.h +++ b/modules/ip6/control/gr_ip6_control.h @@ -26,6 +26,7 @@ struct nexthop *ip6_nexthop_new(uint16_t vrf_id, uint16_t iface_id, const struct void ip6_nexthop_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 ip6_route_insert(uint16_t vrf_id, uint16_t iface_id, const struct rte_ipv6_addr *, uint8_t prefixlen, struct nexthop *); int ip6_route_delete( diff --git a/modules/ip6/control/router_advert.c b/modules/ip6/control/router_advert.c index 7636da62..db7f0885 100644 --- a/modules/ip6/control/router_advert.c +++ b/modules/ip6/control/router_advert.c @@ -43,6 +43,11 @@ static struct api_out iface_ra_clear(const void *request, void ** /*response*/) return api_out(0, 0); } +void ndp_router_sollicit_input_cb(struct rte_mbuf *m) { + rte_pktmbuf_free(m); + event_active(ra_ctx.timer, 0, 0); +} + static void build_ra_packet(struct rte_mbuf *m, struct rte_ipv6_addr *srcv6) { struct rte_ipv6_addr dst = RTE_IPV6_ADDR_ALLNODES_LINK_LOCAL; struct rte_ipv6_addr src = *srcv6; diff --git a/modules/ip6/datapath/icmp6_input.c b/modules/ip6/datapath/icmp6_input.c index 493ae99c..c650d9b8 100644 --- a/modules/ip6/datapath/icmp6_input.c +++ b/modules/ip6/datapath/icmp6_input.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2024 Robin Jarry +#include #include #include #include @@ -19,6 +20,7 @@ enum { ICMP6_OUTPUT = 0, NEIGH_SOLICIT, NEIGH_ADVERT, + ROUTER_SOLICIT, BAD_CHECKSUM, INVALID, UNSUPPORTED, @@ -64,6 +66,8 @@ icmp6_input_process(struct rte_graph *graph, struct rte_node *node, void **objs, next = NEIGH_ADVERT; break; case ICMP6_TYPE_ROUTER_SOLICIT: + next = ROUTER_SOLICIT; + break; case ICMP6_TYPE_ROUTER_ADVERT: default: next = UNSUPPORTED; @@ -89,6 +93,7 @@ static struct rte_node_register icmp6_input_node = { [ICMP6_OUTPUT] = "icmp6_output", [NEIGH_SOLICIT] = "ndp_ns_input", [NEIGH_ADVERT] = "ndp_na_input", + [ROUTER_SOLICIT] = "ndp_rs_input", [BAD_CHECKSUM] = "icmp6_input_bad_checksum", [INVALID] = "icmp6_input_invalid", [UNSUPPORTED] = "icmp6_input_unsupported", diff --git a/modules/ip6/datapath/meson.build b/modules/ip6/datapath/meson.build index a8f14ca3..729a613f 100644 --- a/modules/ip6/datapath/meson.build +++ b/modules/ip6/datapath/meson.build @@ -13,5 +13,6 @@ src += files( 'ndp_na_input.c', 'ndp_ns_input.c', 'ndp_ns_output.c', + 'ndp_rs_input.c', ) inc += include_directories('.') diff --git a/modules/ip6/datapath/ndp_rs_input.c b/modules/ip6/datapath/ndp_rs_input.c new file mode 100644 index 00000000..1724757c --- /dev/null +++ b/modules/ip6/datapath/ndp_rs_input.c @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2025 Christophe Fontaine + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +enum { + CONTROL, + INVAL, + EDGE_COUNT, +}; + +static uint16_t ndp_rs_input_process( + struct rte_graph *graph, + struct rte_node *node, + void **objs, + uint16_t nb_objs +) { + struct control_output_mbuf_data *co; + struct ip6_local_mbuf_data *d; + struct rte_mbuf *mbuf; + struct icmp6 *icmp6; + rte_edge_t next; + +#define ASSERT_NDP(condition) \ + do { \ + if (!(condition)) { \ + next = INVAL; \ + goto next; \ + } \ + } while (0) + + for (uint16_t i = 0; i < nb_objs; i++) { + mbuf = objs[i]; + + d = ip6_local_mbuf_data(mbuf); + icmp6 = rte_pktmbuf_mtod(mbuf, struct icmp6 *); + + // Validation of Router Solicitations + // https://www.rfc-editor.org/rfc/rfc4861#section-6.1.1 + // + // - The IP Hop Limit field has a value of 255, i.e., the packet + // could not possibly have been forwarded by a router. + ASSERT_NDP(d->hop_limit == 255); + // - ICMP Checksum is valid. (already checked in icmp6_input) + // + // - ICMP Code is 0. + ASSERT_NDP(icmp6->code == 0); + // - ICMP length (derived from the IP length) is 8 or more octets. + ASSERT_NDP(d->len >= 8); + + next = CONTROL; + co = control_output_mbuf_data(mbuf); + co->callback = ndp_router_sollicit_input_cb; +next: + if (gr_mbuf_is_traced(mbuf)) + gr_mbuf_trace_add(mbuf, node, 0); + rte_node_enqueue_x1(graph, node, next, mbuf); + } + + return nb_objs; +} + +static struct rte_node_register node = { + .name = "ndp_rs_input", + .process = ndp_rs_input_process, + .nb_edges = EDGE_COUNT, + .next_nodes = { + [CONTROL] = "control_output", + [INVAL] = "ndp_rs_input_inval", + }, +}; + +static struct gr_node_info info = { + .node = &node, + .trace_format = (gr_trace_format_cb_t)trace_icmp6_format, +}; + +GR_NODE_REGISTER(info); + +GR_DROP_REGISTER(ndp_rs_input_inval); diff --git a/smoke/ip6_rs_ra_test.sh b/smoke/ip6_rs_ra_test.sh new file mode 100755 index 00000000..05551c12 --- /dev/null +++ b/smoke/ip6_rs_ra_test.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2025 Christophe Fontaine + +. $(dirname $0)/_init.sh + +p1=${run_id}1 + +grcli add interface port $p1 devargs net_tap0,iface=$p1 mac d2:f0:0c:ba:a4:11 +grcli add ip6 address fd00:ba4:1::1/64 iface $p1 + +for n in 1; do + p=$run_id$n + ip netns add $p + echo ip netns del $p >> $tmp/cleanup + ip link set $p netns $p + ip -n $p link set $p address d2:ad:ca:ca:a4:1$n + ip -n $p link set $p up + ip -n $p addr add fd00:ba4:$n::2/64 dev $p + ip -n $p addr show +done + +sleep 3 # wait for DAD + +ip netns exec $p1 rdisc6 $p1 From ebcf795e39243ee5848fdec5b58cf979ba6a0f50 Mon Sep 17 00:00:00 2001 From: Christophe Fontaine Date: Fri, 20 Dec 2024 07:38:35 +0000 Subject: [PATCH 6/6] ip6: router advertisement configuration Add configuration knobs for RA messages. Instead of a hardcoded periodic message, allow a per-interface configuration. Signed-off-by: Christophe Fontaine --- modules/ip6/api/gr_ip6.h | 23 ++++ modules/ip6/cli/ip.h | 3 + modules/ip6/cli/meson.build | 1 + modules/ip6/cli/router_advert.c | 134 +++++++++++++++++++++++ modules/ip6/control/router_advert.c | 160 ++++++++++++++++++++++------ 5 files changed, 286 insertions(+), 35 deletions(-) create mode 100644 modules/ip6/cli/router_advert.c diff --git a/modules/ip6/api/gr_ip6.h b/modules/ip6/api/gr_ip6.h index 01137176..c34b1d1b 100644 --- a/modules/ip6/api/gr_ip6.h +++ b/modules/ip6/api/gr_ip6.h @@ -132,9 +132,15 @@ struct gr_ip6_addr_list_resp { struct gr_ip6_ifaddr addrs[/* n_addrs */]; }; +// router advertisement //////////////////////////////////////////////////////// #define GR_IP6_IFACE_RA_SET REQUEST_TYPE(GR_IP6_MODULE, 0x0030) struct gr_ip6_ra_set_req { uint16_t iface_id; + uint16_t set_interval : 1; + uint16_t set_lifetime : 1; + + uint16_t interval; + uint16_t lifetime; }; // struct gr_ip6_ra_set_resp { }; @@ -143,4 +149,21 @@ struct gr_ip6_ra_clear_req { uint16_t iface_id; }; // struct gr_ip6_ra_clear_resp { }; + +#define GR_IP6_IFACE_RA_SHOW REQUEST_TYPE(GR_IP6_MODULE, 0x0032) +struct gr_ip6_ra_show_req { + uint16_t iface_id; +}; + +struct gr_ip6_ra_conf { + bool enabled; + uint16_t iface_id; + uint16_t interval; + uint16_t lifetime; +}; + +struct gr_ip6_ra_show_resp { + uint16_t n_ras; + struct gr_ip6_ra_conf ras[]; +}; #endif diff --git a/modules/ip6/cli/ip.h b/modules/ip6/cli/ip.h index e99433ec..7cc04474 100644 --- a/modules/ip6/cli/ip.h +++ b/modules/ip6/cli/ip.h @@ -9,5 +9,8 @@ #define IP6_ADD_CTX(root) CLI_CONTEXT(root, CTX_ADD, CTX_ARG("ip6", "Create IPv6 stack elements.")) #define IP6_DEL_CTX(root) CLI_CONTEXT(root, CTX_DEL, CTX_ARG("ip6", "Delete IPv6 stack elements.")) #define IP6_SHOW_CTX(root) CLI_CONTEXT(root, CTX_SHOW, CTX_ARG("ip6", "Show IPv6 stack details.")) +#define IP6_SET_CTX(root) CLI_CONTEXT(root, CTX_SET, CTX_ARG("ip6", "Set IPv6 stack elements.")) +#define IP6_CLEAR_CTX(root) \ + CLI_CONTEXT(root, CTX_CLEAR, CTX_ARG("ip6", "Clear IPv6 stack elements.")) #endif diff --git a/modules/ip6/cli/meson.build b/modules/ip6/cli/meson.build index 69c41354..806d7726 100644 --- a/modules/ip6/cli/meson.build +++ b/modules/ip6/cli/meson.build @@ -5,4 +5,5 @@ cli_src += files( 'address.c', 'nexthop.c', 'route.c', + 'router_advert.c', ) diff --git a/modules/ip6/cli/router_advert.c b/modules/ip6/cli/router_advert.c new file mode 100644 index 00000000..447877de --- /dev/null +++ b/modules/ip6/cli/router_advert.c @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2025 Christophe Fontaine + +#include "ip.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +static cmd_status_t ra_show(const struct gr_api_client *c, const struct ec_pnode *p) { + struct libscols_table *table = scols_new_table(); + struct gr_ip6_ra_show_resp *resp; + struct gr_ip6_ra_show_req req; + struct gr_iface iface; + void *resp_ptr = NULL; + + if (!iface_from_name(c, arg_str(p, "IFACE"), &iface)) + req.iface_id = iface.id; + else + req.iface_id = 0; + + if (gr_api_client_send_recv(c, GR_IP6_IFACE_RA_SHOW, sizeof(req), &req, &resp_ptr) < 0) + return CMD_ERROR; + resp = resp_ptr; + + scols_table_new_column(table, "IFACE", 0, 0); + scols_table_new_column(table, "RA", 0, 0); + scols_table_new_column(table, "interval", 0, 0); + scols_table_new_column(table, "lifetime", 0, 0); + scols_table_set_column_separator(table, " "); + + for (uint16_t i = 0; i < resp->n_ras; i++) { + struct libscols_line *line = scols_table_new_line(table, NULL); + if (iface_from_id(c, resp->ras[i].iface_id, &iface) == 0) + scols_line_sprintf(line, 0, "%s", iface.name); + else + scols_line_sprintf(line, 0, "%u", resp->ras[i].iface_id); + scols_line_sprintf(line, 1, "%u", resp->ras[i].enabled); + scols_line_sprintf(line, 2, "%u", resp->ras[i].interval); + scols_line_sprintf(line, 3, "%u", resp->ras[i].lifetime); + } + + scols_print_table(table); + scols_unref_table(table); + free(resp_ptr); + return CMD_SUCCESS; +} + +static cmd_status_t ra_set(const struct gr_api_client *c, const struct ec_pnode *p) { + struct gr_ip6_ra_set_req req = {0}; + struct gr_iface iface; + + if (iface_from_name(c, arg_str(p, "IFACE"), &iface) < 0) + return CMD_ERROR; + + req.iface_id = iface.id; + if (!arg_u16(p, "IT", &req.interval)) + req.set_interval = 1; + + if (!arg_u16(p, "LT", &req.lifetime)) + req.set_lifetime = 1; + + if (gr_api_client_send_recv(c, GR_IP6_IFACE_RA_SET, sizeof(req), &req, NULL) < 0) + return CMD_ERROR; + return CMD_SUCCESS; +} + +static cmd_status_t ra_clear(const struct gr_api_client *c, const struct ec_pnode *p) { + struct gr_ip6_ra_clear_req req; + struct gr_iface iface; + + if (iface_from_name(c, arg_str(p, "IFACE"), &iface) < 0) + return CMD_ERROR; + + req.iface_id = iface.id; + if (gr_api_client_send_recv(c, GR_IP6_IFACE_RA_CLEAR, sizeof(req), &req, NULL) < 0) + return CMD_ERROR; + return CMD_SUCCESS; +} + +static int ctx_init(struct ec_node *root) { + int ret; + + ret = CLI_COMMAND( + IP6_SHOW_CTX(root), + "router-advert [IFACE]", + ra_show, + "Show router advertisement configuration", + with_help("Interface name.", ec_node_dyn("IFACE", complete_iface_names, NULL)) + ); + if (ret < 0) + return ret; + + ret = CLI_COMMAND( + IP6_SET_CTX(root), + "router-advert IFACE [interval IT] [lifetime LT]", + ra_set, + "Set router advertisement parameters", + with_help("Interface name.", ec_node_dyn("IFACE", complete_iface_names, NULL)), + with_help("Interval", ec_node_uint("IT", 0, UINT16_MAX - 1, 10)), + with_help("Life time", ec_node_uint("LT", 0, UINT16_MAX - 1, 10)) + ); + if (ret < 0) + return ret; + + ret = CLI_COMMAND( + IP6_CLEAR_CTX(root), + "router-advert IFACE", + ra_clear, + "Disable router advertisement and reset parameters", + with_help("Interface name.", ec_node_dyn("IFACE", complete_iface_names, NULL)) + ); + if (ret < 0) + return ret; + + return 0; +} + +static struct gr_cli_context ctx = { + .name = "ipv6 router-advert", + .init = ctx_init, +}; + +static void __attribute__((constructor, used)) init(void) { + register_context(&ctx); +} diff --git a/modules/ip6/control/router_advert.c b/modules/ip6/control/router_advert.c index db7f0885..de30b658 100644 --- a/modules/ip6/control/router_advert.c +++ b/modules/ip6/control/router_advert.c @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include @@ -19,33 +18,111 @@ #include #include +#define RA_DEFAULT_INTERVAL 600 +#define RA_DEFAULT_LIFETIME 1800 + struct ra_ctx { struct rte_mempool *mp; - struct event *timer; }; static struct ra_ctx ra_ctx; static control_input_t ra_output; +static struct event_base *ev_base; + +struct ra_iface_conf { + struct event *timer; + bool enabled; + uint16_t interval; + uint16_t lifetime; +}; + +static struct ra_iface_conf ra_conf[MAX_IFACES]; static struct api_out iface_ra_set(const void *request, void ** /*response*/) { const struct gr_ip6_ra_set_req *req = request; + if (iface_from_id(req->iface_id) == NULL) return api_out(errno, 0); + if (req->set_interval) + ra_conf[req->iface_id].interval = req->interval; + if (req->set_lifetime) + ra_conf[req->iface_id].lifetime = req->lifetime; + + event_add( + ra_conf[req->iface_id].timer, + &(struct timeval) {.tv_sec = ra_conf[req->iface_id].interval} + ); + event_active(ra_conf[req->iface_id].timer, 0, 0); return api_out(0, 0); } static struct api_out iface_ra_clear(const void *request, void ** /*response*/) { const struct gr_ip6_ra_clear_req *req = request; + if (iface_from_id(req->iface_id) == NULL) return api_out(errno, 0); + event_del(ra_conf[req->iface_id].timer); + ra_conf[req->iface_id].interval = RA_DEFAULT_INTERVAL; + ra_conf[req->iface_id].lifetime = RA_DEFAULT_LIFETIME; + return api_out(0, 0); } +static struct api_out iface_ra_show(const void *request, void **response) { + const struct gr_ip6_ra_show_req *req = request; + struct gr_ip6_ra_show_resp *resp; + uint16_t iface_id, n_ras; + struct hoplist *addrs; + bool show_all = false; + size_t len; + + if (req->iface_id == 0) + show_all = true; + else if (iface_from_id(req->iface_id) == NULL) + return api_out(errno, 0); + + n_ras = 0; + for (iface_id = 0; iface_id < MAX_IFACES; iface_id++) { + addrs = ip6_addr_get_all(iface_id); + if (addrs == NULL || gr_vec_len(addrs) == 0) + continue; + if (show_all == false && iface_id != req->iface_id) + continue; + n_ras++; + } + + len = sizeof(*resp) + n_ras * sizeof(struct gr_ip6_ra_conf); + resp = calloc(1, sizeof(*resp) + n_ras * sizeof(struct gr_ip6_ra_conf)); + if (!resp) + return api_out(ENOMEM, 0); + resp->n_ras = n_ras; + n_ras = 0; + for (uint16_t iface_id = 0; iface_id < MAX_IFACES; iface_id++) { + addrs = ip6_addr_get_all(iface_id); + if (addrs == NULL || gr_vec_len(addrs) == 0) + continue; + if (show_all == false && iface_id != req->iface_id) + continue; + + resp->ras[n_ras].iface_id = iface_id; + resp->ras[n_ras].enabled = event_pending( + ra_conf[iface_id].timer, EV_TIMEOUT | EV_READ | EV_WRITE | EV_SIGNAL, 0 + ); + resp->ras[n_ras].interval = ra_conf[iface_id].interval; + resp->ras[n_ras].lifetime = ra_conf[iface_id].lifetime; + n_ras++; + } + + *response = resp; + return api_out(0, len); +} + void ndp_router_sollicit_input_cb(struct rte_mbuf *m) { + uint16_t iface_id = mbuf_data(m)->iface->id; rte_pktmbuf_free(m); - event_active(ra_ctx.timer, 0, 0); + event_active(ra_conf[iface_id].timer, 0, 0); } static void build_ra_packet(struct rte_mbuf *m, struct rte_ipv6_addr *srcv6) { @@ -72,7 +149,7 @@ static void build_ra_packet(struct rte_mbuf *m, struct rte_ipv6_addr *srcv6) { ra->cur_hoplim = IP6_DEFAULT_HOP_LIMIT; // Default TTL for this network ra->managed_addr = 0; // DHCPv6 is available ra->other_config = 0; // DNS available, ... - ra->lifetime = RTE_BE16(0); // Not a default router + ra->lifetime = rte_cpu_to_be_16(ra_conf[iface_id].lifetime); ra->reachable_time = RTE_BE16(0); ra->retrans_timer = RTE_BE16(0); @@ -92,53 +169,42 @@ static void build_ra_packet(struct rte_mbuf *m, struct rte_ipv6_addr *srcv6) { icmp6->cksum = rte_ipv6_udptcp_cksum(ip, icmp6); } -static void send_ra_cb(evutil_socket_t, short /*what*/, void * /*priv*/) { - struct iface *iface = NULL; +static void send_ra_cb(evutil_socket_t, short /*what*/, void *priv) { + struct iface *iface = priv; + struct hoplist *hl; + struct nexthop *nh; struct rte_mbuf *m; - while ((iface = iface_next(GR_IFACE_TYPE_UNDEF, iface)) != NULL) { - struct hoplist *hl = ip6_addr_get_all(iface->id); - if (hl == NULL) + if ((hl = ip6_addr_get_all(iface->id)) == NULL) + return; + + gr_vec_foreach (nh, hl->nh) { + struct rte_ipv6_addr ip = nh->ipv6; + if (nh->family != AF_INET6) + continue; + if (!rte_ipv6_addr_is_linklocal(&ip)) continue; - for (unsigned i = 0; i < gr_vec_len(hl); i++) { - struct nexthop *nh = hl->nh[i]; - struct rte_ipv6_addr ip = nh->ipv6; - if (nh->family != AF_INET6) - continue; - if (!rte_ipv6_addr_is_linklocal(&ip)) - continue; - - if ((m = rte_pktmbuf_alloc(ra_ctx.mp)) == NULL) { - LOG(ERR, "rte_pktmbuf_alloc"); - return; - } - mbuf_data(m)->iface = iface; - build_ra_packet(m, &ip); - post_to_stack(ra_output, m); + if ((m = rte_pktmbuf_alloc(ra_ctx.mp)) == NULL) { + LOG(ERR, "rte_pktmbuf_alloc"); + return; } + mbuf_data(m)->iface = iface; + build_ra_packet(m, &ip); + post_to_stack(ra_output, m); } } -static void ra_init(struct event_base *ev_base) { +static void ra_init(struct event_base *base) { + ev_base = base; ra_output = gr_control_input_register_handler("ip6_output", true); ra_ctx.mp = gr_pktmbuf_pool_get(SOCKET_ID_ANY, 512); if (ra_ctx.mp == NULL) { ABORT("gr_pktmbuf_pool_get ENOMEM"); } - - ra_ctx.timer = event_new(ev_base, -1, EV_PERSIST, send_ra_cb, NULL); - if (ra_ctx.timer == NULL) { - ABORT("event_new() failed"); - } - - if (event_add(ra_ctx.timer, &(struct timeval) {.tv_sec = 600}) < 0) { - ABORT("event_add() failed"); - } } static void ra_fini(struct event_base * /*ev_base*/) { gr_pktmbuf_pool_release(ra_ctx.mp, 512); - event_free(ra_ctx.timer); } static struct gr_module ra_module = { @@ -160,8 +226,32 @@ static struct gr_api_handler ra_clear_handler = { .callback = iface_ra_clear, }; +static struct gr_api_handler ra_show_handler = { + .name = "show interface ra", + .request_type = GR_IP6_IFACE_RA_SHOW, + .callback = iface_ra_show, +}; + +static void iface_event_handler(iface_event_t event, struct iface *iface) { + if (event == IFACE_EVENT_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, iface); + } else if (event == IFACE_EVENT_PRE_REMOVE) { + event_free(ra_conf[iface->id].timer); + ra_conf[iface->id].timer = NULL; + } +} + +static struct iface_event_handler iface_event_address_handler = { + .callback = iface_event_handler, +}; + RTE_INIT(router_advertisement_init) { gr_register_module(&ra_module); gr_register_api_handler(&ra_set_handler); gr_register_api_handler(&ra_clear_handler); + gr_register_api_handler(&ra_show_handler); + iface_event_register_handler(&iface_event_address_handler); }