diff --git a/modules/ip6/datapath/gr_ip6_datapath.h b/modules/ip6/datapath/gr_ip6_datapath.h index 5ee9bb12..5bf4a4c3 100644 --- a/modules/ip6/datapath/gr_ip6_datapath.h +++ b/modules/ip6/datapath/gr_ip6_datapath.h @@ -17,6 +17,37 @@ #include -GR_MBUF_PRIV_DATA_TYPE(ip6_output_mbuf_data, { struct nexthop6 *nh; }); +GR_MBUF_PRIV_DATA_TYPE(ip6_output_mbuf_data, { + const struct iface *input_iface; + struct nexthop6 *nh; +}); + +GR_MBUF_PRIV_DATA_TYPE(ip6_local_mbuf_data, { + struct rte_ipv6_addr src; + struct rte_ipv6_addr dst; + uint16_t len; + uint8_t hop_limit; + uint8_t proto; + const struct iface *input_iface; +}); + +void ip6_input_local_add_proto(uint8_t proto, const char *next_node); + +#define IP6_DEFAULT_HOP_LIMIT 255 + +static inline void ip6_set_fields( + struct rte_ipv6_hdr *ip, + uint16_t len, + uint8_t proto, + struct rte_ipv6_addr *src, + struct rte_ipv6_addr *dst +) { + ip->vtc_flow = RTE_IPV6_VTC_FLOW_VERSION; + ip->payload_len = rte_cpu_to_be_16(len); + ip->proto = proto; + ip->hop_limits = IP6_DEFAULT_HOP_LIMIT; + rte_ipv6_addr_cpy(&ip->src_addr, src); + rte_ipv6_addr_cpy(&ip->dst_addr, dst); +} #endif diff --git a/modules/ip6/datapath/icmp6_input.c b/modules/ip6/datapath/icmp6_input.c new file mode 100644 index 00000000..84d42c22 --- /dev/null +++ b/modules/ip6/datapath/icmp6_input.c @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2024 Robin Jarry + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +enum { + ICMP6_OUTPUT = 0, + BAD_CHECKSUM, + INVALID, + UNSUPPORTED, + EDGE_COUNT, +}; + +static uint16_t +icmp6_input_process(struct rte_graph *graph, struct rte_node *node, void **objs, uint16_t nb_objs) { + struct ip6_local_mbuf_data *d; + struct icmp6 *icmp6; + struct rte_ipv6_addr tmp_ip; + struct rte_mbuf *mbuf; + rte_edge_t next; + + for (uint16_t i = 0; i < nb_objs; i++) { + mbuf = objs[i]; + icmp6 = rte_pktmbuf_mtod(mbuf, struct icmp6 *); + d = ip6_local_mbuf_data(mbuf); + + switch (icmp6->type) { + case ICMP6_TYPE_ECHO_REQUEST: + if (icmp6->code != 0) { + next = INVALID; + goto next; + } + icmp6->type = ICMP6_TYPE_ECHO_REPLY; + // swap source/destination addresses + rte_ipv6_addr_cpy(&tmp_ip, &d->dst); + rte_ipv6_addr_cpy(&d->dst, &d->src); + rte_ipv6_addr_cpy(&d->src, &tmp_ip); + next = ICMP6_OUTPUT; + break; + default: + next = UNSUPPORTED; + } +next: + rte_node_enqueue_x1(graph, node, next, mbuf); + } + + return nb_objs; +} + +static void icmp6_input_register(void) { + ip6_input_local_add_proto(IPPROTO_ICMPV6, "icmp6_input"); +} + +static struct rte_node_register icmp6_input_node = { + .name = "icmp6_input", + + .process = icmp6_input_process, + + .nb_edges = EDGE_COUNT, + .next_nodes = { + [ICMP6_OUTPUT] = "icmp6_output", + [BAD_CHECKSUM] = "icmp6_input_bad_checksum", + [INVALID] = "icmp6_input_invalid", + [UNSUPPORTED] = "icmp6_input_unsupported", + }, +}; + +static struct gr_node_info icmp6_input_info = { + .node = &icmp6_input_node, + .register_callback = icmp6_input_register, +}; + +GR_NODE_REGISTER(icmp6_input_info); + +GR_DROP_REGISTER(icmp6_input_bad_checksum); +GR_DROP_REGISTER(icmp6_input_invalid); +GR_DROP_REGISTER(icmp6_input_unsupported); diff --git a/modules/ip6/datapath/icmp6_output.c b/modules/ip6/datapath/icmp6_output.c new file mode 100644 index 00000000..82f65f89 --- /dev/null +++ b/modules/ip6/datapath/icmp6_output.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2024 Robin Jarry + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +enum { + OUTPUT = 0, + NO_HEADROOM, + NO_ROUTE, + EDGE_COUNT, +}; + +static uint16_t icmp6_output_process( + struct rte_graph *graph, + struct rte_node *node, + void **objs, + uint16_t nb_objs +) { + struct ip6_output_mbuf_data *o; + struct ip6_local_mbuf_data *d; + const struct iface *iface; + struct rte_ipv6_hdr *ip; + struct rte_mbuf *mbuf; + struct nexthop6 *nh; + struct icmp6 *icmp6; + rte_edge_t edge; + + 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 *); + ip = (struct rte_ipv6_hdr *)rte_pktmbuf_prepend(mbuf, sizeof(*ip)); + if (unlikely(ip == NULL)) { + edge = NO_HEADROOM; + goto next; + } + ip6_set_fields(ip, d->len, IPPROTO_ICMPV6, &d->src, &d->dst); + // Compute ICMP6 checksum with pseudo header + icmp6->cksum = 0; + icmp6->cksum = rte_ipv6_udptcp_cksum(ip, icmp6); + + if ((nh = ip6_route_lookup(d->input_iface->vrf_id, &d->dst)) == NULL) { + edge = NO_ROUTE; + goto next; + } + o = ip6_output_mbuf_data(mbuf); + iface = d->input_iface; + o->nh = nh; + o->input_iface = iface; + edge = OUTPUT; +next: + rte_node_enqueue_x1(graph, node, edge, mbuf); + } + + return nb_objs; +} + +static struct rte_node_register icmp6_output_node = { + .name = "icmp6_output", + + .process = icmp6_output_process, + + .nb_edges = EDGE_COUNT, + .next_nodes = { + [OUTPUT] = "ip6_output", + [NO_HEADROOM] = "error_no_headroom", + [NO_ROUTE] = "icmp6_output_no_route", + }, +}; + +static struct gr_node_info icmp6_output_info = { + .node = &icmp6_output_node, +}; + +GR_NODE_REGISTER(icmp6_output_info); + +GR_DROP_REGISTER(icmp6_output_no_route) diff --git a/modules/ip6/datapath/ip6_input.c b/modules/ip6/datapath/ip6_input.c index 60af580d..b26a1d11 100644 --- a/modules/ip6/datapath/ip6_input.c +++ b/modules/ip6/datapath/ip6_input.c @@ -123,7 +123,6 @@ static struct gr_node_info info = { GR_NODE_REGISTER(info); -GR_DROP_REGISTER(ip6_input_local); GR_DROP_REGISTER(ip6_input_no_route); GR_DROP_REGISTER(ip6_input_other_host); GR_DROP_REGISTER(ip6_input_bad_version); diff --git a/modules/ip6/datapath/ip6_local.c b/modules/ip6/datapath/ip6_local.c new file mode 100644 index 00000000..4f7cab53 --- /dev/null +++ b/modules/ip6/datapath/ip6_local.c @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2024 Robin Jarry + +#include +#include +#include +#include +#include + +#include +#include +#include + +enum { + UNKNOWN_PROTO = 0, + BAD_CHECKSUM, + EDGE_COUNT, +}; +static rte_edge_t edges[256] = {UNKNOWN_PROTO}; + +void ip6_input_local_add_proto(uint8_t proto, const char *next_node) { + LOG(DEBUG, "ip6_input_local: proto=%hhu -> %s", proto, next_node); + if (edges[proto] != UNKNOWN_PROTO) + ABORT("next node already registered for proto=%hhu", proto); + edges[proto] = gr_node_attach_parent("ip6_input_local", next_node); +} + +static uint16_t ip6_input_local_process( + struct rte_graph *graph, + struct rte_node *node, + void **objs, + uint16_t nb_objs +) { + struct ip6_local_mbuf_data *d; + const struct iface *iface; + struct rte_ipv6_hdr *ip; + struct rte_mbuf *m; + rte_edge_t edge; + uint16_t i; + + for (i = 0; i < nb_objs; i++) { + m = objs[i]; + ip = rte_pktmbuf_mtod(m, struct rte_ipv6_hdr *); + + edge = edges[ip->proto]; + if (edge == UNKNOWN_PROTO) + goto next; + + // prepare ip local data + iface = ip6_output_mbuf_data(m)->input_iface; + d = ip6_local_mbuf_data(m); + rte_ipv6_addr_cpy(&d->src, &ip->src_addr); + rte_ipv6_addr_cpy(&d->dst, &ip->dst_addr); + d->len = rte_be_to_cpu_16(ip->payload_len); + d->hop_limit = ip->hop_limits; + d->proto = ip->proto; + d->input_iface = iface; + rte_pktmbuf_adj(m, sizeof(*ip)); + + // strip IPv6 extension headers + while (rte_pktmbuf_pkt_len(m) > 0) { + size_t ext_size = 0; + int next_proto = rte_ipv6_get_next_ext( + rte_pktmbuf_mtod(m, uint8_t *), d->proto, &ext_size + ); + if (next_proto < 0) + break; + rte_pktmbuf_adj(m, ext_size); + d->proto = next_proto; + }; + + // verify checksum if not already checked by hardware + switch (m->ol_flags & RTE_MBUF_F_RX_L4_CKSUM_MASK) { + case RTE_MBUF_F_RX_L4_CKSUM_NONE: + case RTE_MBUF_F_RX_L4_CKSUM_UNKNOWN: + if (rte_ipv6_udptcp_cksum_verify(ip, rte_pktmbuf_mtod(m, void *))) + edge = BAD_CHECKSUM; + break; + case RTE_MBUF_F_RX_L4_CKSUM_BAD: + edge = BAD_CHECKSUM; + break; + } +next: + rte_node_enqueue_x1(graph, node, edge, m); + } + + return nb_objs; +} + +static struct rte_node_register input_node = { + .name = "ip6_input_local", + .process = ip6_input_local_process, + .nb_edges = EDGE_COUNT, + .next_nodes = { + [UNKNOWN_PROTO] = "ip6_input_local_unknown_proto", + [BAD_CHECKSUM] = "ip6_input_local_bad_checksum", + }, +}; + +static struct gr_node_info info = { + .node = &input_node, +}; + +GR_NODE_REGISTER(info); + +GR_DROP_REGISTER(ip6_input_local_unknown_proto); +GR_DROP_REGISTER(ip6_input_local_bad_checksum); diff --git a/modules/ip6/datapath/meson.build b/modules/ip6/datapath/meson.build index a16c0491..de31c23a 100644 --- a/modules/ip6/datapath/meson.build +++ b/modules/ip6/datapath/meson.build @@ -2,8 +2,11 @@ # Copyright (c) 2024 Robin Jarry src += files( + 'icmp6_input.c', + 'icmp6_output.c', 'ip6_forward.c', 'ip6_input.c', + 'ip6_local.c', 'ip6_output.c', ) inc += include_directories('.') diff --git a/smoke/ip6_forward_test.sh b/smoke/ip6_forward_test.sh index 662327ec..e232cea9 100755 --- a/smoke/ip6_forward_test.sh +++ b/smoke/ip6_forward_test.sh @@ -31,3 +31,5 @@ sleep 3 # wait for DAD 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