Add MPLS support#641
Conversation
Introduce the basic infrastructure types needed for MPLS support. Add GR_NH_T_MPLS to the nexthop type enum for MPLS label imposition and swap operations. Add GR_AF_MPLS (AF_MPLS = 28) to the address family enum so that MPLS can register per-AF operations such as nexthop resolution callbacks and per-VRF FIB lifecycle management. Reserve a fib_mpls slot in the private VRF struct for the per-VRF label forwarding table that will be populated by the MPLS module. Extend nexthop_af_ops_from_mbuf() to recognize MPLS packets held for ARP resolution. Packets marked with RTE_PTYPE_TUNNEL_MPLS_IN_GRE are dispatched to the MPLS AF ops so that they get resubmitted to the correct datapath node after the gateway nexthop becomes reachable. Signed-off-by: Matej Muzila <mmuzila@redhat.com>
The nexthop info struct carries the output label stack, initial TTL, and a gateway address for ARP/NDP resolution. Each MPLS nexthop stores its own via address rather than referencing an existing IP route. MPLS nexthops are managed through the generic GR_NH_ADD/DEL API, same as SRv6. Label routes map an incoming label to a nexthop in the per-VRF LFIB. The API provides add, delete, get, and streaming list operations with the corresponding event types for route change notifications. Signed-off-by: Matej Muzila <mmuzila@redhat.com>
Implement the MPLS nexthop type operations, the per-VRF label forwarding information base, and the API request handlers. The nexthop type ops handle creation, update, and teardown of MPLS nexthops. Each MPLS nexthop holds a reference to an L3 nexthop for gateway resolution. The LFIB is a flat array of nexthop pointers indexed directly by the 20-bit label value, allocated per VRF via the vrf_fib_ops mechanism. Signed-off-by: Matej Muzila <mmuzila@redhat.com>
Implement the three MPLS graph nodes (input, output, push) and the control plane callbacks for held packet resolution. The input node decodes the MPLS label stack in a while loop since rte_graph forbids self-loop edges. It handles label swap (overwrite and forward), pop with TTL propagation (RFC 1624 incremental checksum update for IPv4), reserved labels 0/2/3, and stacked label processing with a depth limit. Payload type after pop is detected from the IP version nibble. The output node resolves the gateway MAC from the via L3 nexthop. For unresolved gateways, the MPLS nexthop pointer is stashed in the mbuf priv data overlay before entering ip_hold, and restored on resubmit via the MPLS AF ops. The ether type is selected from the packet type bits so that popped IP packets get the correct L3 ether type while swapped packets keep the MPLS ether type. The push node is dispatched from ip_output and ip6_output for MPLS nexthops. It checks MTU accounting for the label overhead, reads the payload TTL, and prepends the label stack. Signed-off-by: Matej Mužila <mmuzila@redhat.com>
Register grcli commands under "mpls route" for managing the MPLS label forwarding table. The commands use a separate CLI context since label routes are keyed by numeric labels, not IP prefixes, and cannot reuse the existing cli_route_ops dispatch. The list command streams label routes as a table with VRF, label, nexthop ID, and origin columns. Event printers for label route add/del notifications are also registered. Signed-off-by: Matej Muzila <mmuzila@redhat.com>
Cover the main MPLS datapath operations. A separate CLI test exercises nexthop and label route CRUD through grcli. Tests that send MPLS-encapsulated traffic from Linux namespaces pre-resolve ARP with a plain IP ping before the MPLS test to avoid a race between the iptunnel encap path and neighbor resolution. Signed-off-by: Matej Muzila <mmuzila@redhat.com>
|
Please, can you explain how you would integrate it with frrouting ? |
AFAIK the FRR zebra dplane plugin already handles IP routes, nexthops, and SRv6 bidirectionally. FRR -> grout : FRR sends DPLANE_OP_LSP_INSTALL/UPDATE/DELETE when LDP, OSPF-SR, grout -> FRR: Subscribe to GR_EVENT_MPLS_ROUTE_ADD/DEL in the zebra notification Startup sync: After the existing nexthop/route sync passes, add a pass that streams The main complexity is that FRR's LSP NHLFEs don't carry nexthop IDs, so the plugin needs a |
| // Reserved MPLS label values (RFC 3032). | ||
| #define GR_MPLS_LABEL_IPV4_EXPLICIT_NULL 0 | ||
| #define GR_MPLS_LABEL_ROUTER_ALERT 1 | ||
| #define GR_MPLS_LABEL_IPV6_EXPLICIT_NULL 2 | ||
| #define GR_MPLS_LABEL_IMPLICIT_NULL 3 | ||
| #define GR_MPLS_LABEL_FIRST_UNRESERVED 16 |
There was a problem hiding this comment.
Does it make sense to store these in an enum?
| addr_family_t via_af; // Gateway address family (GR_AF_IP4 or GR_AF_IP6). | ||
| uint8_t _pad; | ||
| union { | ||
| ip4_addr_t ipv4; | ||
| struct rte_ipv6_addr ipv6; | ||
| } via; // Gateway address for ARP/NDP resolution. |
There was a problem hiding this comment.
You could replace all of this with:
struct l3_addr via;| typedef int (*mpls_rib_iter_cb)(uint16_t, uint32_t, const struct nexthop *, void *); | ||
| int mpls_rib_iter(uint16_t vrf_id, mpls_rib_iter_cb cb, void *priv); | ||
|
|
||
| extern control_input_t mpls_output_node; |
There was a problem hiding this comment.
This internal identifier probably shouldn't be exposed in a header. See below...
| control_input_t mpls_output_node; | ||
|
|
||
| static int mpls_resubmit_cb(struct rte_mbuf *m, struct nexthop *) { | ||
| l3_mbuf_data(m)->nh = mpls_hold_mbuf_data(m)->mpls_nh; | ||
| mbuf_data(m)->iface = NULL; | ||
| if (post_to_stack(mpls_output_node, m) < 0) { | ||
| LOG(ERR, "post_to_stack: %s", strerror(errno)); | ||
| return -errno; | ||
| } | ||
| return 0; | ||
| } |
There was a problem hiding this comment.
Could you move this to datapath/mpls_output.c and keep the control_input_t value static? See ndp_na_output for reference:
grout/modules/ip6/datapath/ndp_na_output.c
Lines 24 to 40 in 94d1367
| static void mpls_resolve_init(struct event_base *) { | ||
| mpls_output_node = gr_control_input_register_handler("mpls_output", true); | ||
| } |
There was a problem hiding this comment.
This should be moved as a .register_callback in datapath/mpls_output.c as it is done in ndp_na_output:
grout/modules/ip6/datapath/ndp_na_output.c
Lines 114 to 116 in 94d1367
| if (label < GR_MPLS_LABEL_FIRST_UNRESERVED) { | ||
| rte_pktmbuf_adj(mbuf, sizeof(*mpls)); | ||
|
|
||
| if (label == GR_MPLS_LABEL_IPV4_EXPLICIT_NULL) { | ||
| mbuf->packet_type = RTE_PTYPE_L3_IPV4; | ||
| edge = IP_INPUT; | ||
| } else if (label == GR_MPLS_LABEL_IPV6_EXPLICIT_NULL) { | ||
| mbuf->packet_type = RTE_PTYPE_L3_IPV6; | ||
| edge = IP6_INPUT; | ||
| } else if (label == GR_MPLS_LABEL_IMPLICIT_NULL) { | ||
| uint8_t ver = *rte_pktmbuf_mtod(mbuf, uint8_t *) >> 4; | ||
| if (ver == 4) { | ||
| mbuf->packet_type = RTE_PTYPE_L3_IPV4; | ||
| edge = IP_INPUT; | ||
| } else if (ver == 6) { | ||
| mbuf->packet_type = RTE_PTYPE_L3_IPV6; | ||
| edge = IP6_INPUT; | ||
| } else { | ||
| edge = BAD_LABEL; | ||
| } | ||
| } else { | ||
| edge = BAD_LABEL; | ||
| } | ||
| break; | ||
| } |
There was a problem hiding this comment.
This looks like it could be rewritten with a switch case block.
|
|
||
| struct rte_mpls_hdr trace_hdr = *rte_pktmbuf_mtod(mbuf, struct rte_mpls_hdr *); | ||
|
|
||
| while (true) { |
There was a problem hiding this comment.
I am not fond of while (true) blocks in datapath nodes. Since we know there is a limit of GR_MPLS_MAX_LABELS, why not clamp the loop to this value so that the compiler has a chance to unroll the loop.
|
|
||
| if (bos) { | ||
| rte_pktmbuf_adj(mbuf, sizeof(*mpls)); | ||
| uint8_t ver = *rte_pktmbuf_mtod(mbuf, uint8_t *) >> 4; |
There was a problem hiding this comment.
This is fragile. The MPLS FIB should contain the necessary info about what payload is next. Maybe info->via.af?
I don't know how other stacks do this but it seems to me we shouldn't try to guess what is next.
| csum = (csum >> 16) + (csum & 0xffff); | ||
| csum += csum >> 16; | ||
| ip->hdr_checksum = ~(uint16_t)csum; | ||
| mbuf->packet_type = RTE_PTYPE_L3_IPV4; |
There was a problem hiding this comment.
Why do you need this convoluted casting? Maybe this could be simplified by reusing the existing fixup_checksum_* functions from nat_datapath.h (which should be moved into a more generic place first):
struct rte_ipv4_hdr *ip;
ip = rte_pktmbuf_mtod(mbuf, struct rte_ipv4_hdr *);
ip->hdr_checksum = fixup_checksum_16(
ip->hdr_checksum,
rte_cpu_to_be_16(ip->time_to_live << 8),
rte_cpu_to_be_16(ttl << 8)
);
ip->time_to_live = ttl;
No description provided.