From 5bac510525977cd8ee460c5ec6421d74e90c99ac Mon Sep 17 00:00:00 2001 From: Maxime Leroy Date: Tue, 27 Jan 2026 10:05:26 +0100 Subject: [PATCH 1/3] zebra: do not rely on dplane table_id for VRF delete zebra_if_dplane_ifp_handling() was reading dplane_ctx_get_ifp_table_id() for VRF events. In the netlink dplane backend, ifp_table_id is only set via netlink_vrf_change(), which is invoked from netlink_link_change() for RTM_NEWLINK VRF events. It is not set for RTM_DELLINK. This causes interface_vrf_change() to receive table_id as 0 on VRF deletes. For delete operations, save the VRF pointer before if_delete_update() and retrieve table_id from vrf->data.l.table_id. For add/update operations, continue using the dplane-provided table_id. Signed-off-by: Maxime Leroy --- zebra/interface.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/zebra/interface.c b/zebra/interface.c index fb8388513d24..bcc8b4345605 100644 --- a/zebra/interface.c +++ b/zebra/interface.c @@ -1976,7 +1976,6 @@ static void zebra_if_dplane_ifp_handling(struct zebra_dplane_ctx *ctx) ns_id_t ns_id = dplane_ctx_get_ns_id(ctx); ifindex_t ifindex = dplane_ctx_get_ifindex(ctx); ifindex_t bond_ifindex = dplane_ctx_get_ifp_bond_ifindex(ctx); - uint32_t tableid = dplane_ctx_get_ifp_table_id(ctx); enum zebra_iftype zif_type = dplane_ctx_get_ifp_zif_type(ctx); struct interface *ifp; struct zebra_ns *zns; @@ -1992,6 +1991,8 @@ static void zebra_if_dplane_ifp_handling(struct zebra_dplane_ctx *ctx) ifp = if_lookup_by_name_per_ns(zns, name); if (op == DPLANE_OP_INTF_DELETE) { + struct vrf *vrf = NULL; + /* Delete interface notification from kernel */ if (ifp == NULL) { if (IS_ZEBRA_DEBUG_EVENT) @@ -2013,10 +2014,13 @@ static void zebra_if_dplane_ifp_handling(struct zebra_dplane_ctx *ctx) else if (IS_ZEBRA_IF_VXLAN(ifp)) zebra_l2_vxlanif_del(ifp); + if (zif_type == ZEBRA_IF_VRF && !vrf_is_backend_netns()) + vrf = ifp->vrf; + if_delete_update(&ifp); - if (zif_type == ZEBRA_IF_VRF && !vrf_is_backend_netns()) - interface_vrf_change(op, ifindex, name, tableid, ns_id); + if (vrf) + interface_vrf_change(op, ifindex, name, vrf->data.l.table_id, ns_id); } else { ifindex_t master_ifindex, bridge_ifindex, link_ifindex; enum zebra_slave_iftype zif_slave_type; @@ -2034,8 +2038,11 @@ static void zebra_if_dplane_ifp_handling(struct zebra_dplane_ctx *ctx) uint64_t change_flags; /* If VRF, create or update the VRF structure itself. */ - if (zif_type == ZEBRA_IF_VRF && !vrf_is_backend_netns()) + if (zif_type == ZEBRA_IF_VRF && !vrf_is_backend_netns()) { + uint32_t tableid = dplane_ctx_get_ifp_table_id(ctx); + interface_vrf_change(op, ifindex, name, tableid, ns_id); + } master_ifindex = dplane_ctx_get_ifp_master_ifindex(ctx); zif_slave_type = dplane_ctx_get_ifp_zif_slave_type(ctx); From def349c96545364175800fb50e49d8def579b7d8 Mon Sep 17 00:00:00 2001 From: Maxime Leroy Date: Tue, 19 May 2026 11:06:27 +0200 Subject: [PATCH 2/3] zebra: use dplane provided vrf_id instead of casting ifindex interface_vrf_change() was implicitly assuming that the VRF netdevice ifindex could be used as zebra's vrf_id. This assumption holds true for the Linux kernel dataplane, where the VRF ID is defined as the ifindex of the VRF interface, so this change does not alter kernel behavior. However, the dataplane API already exposes both concepts explicitly via dplane_ctx_get_ifindex() and dplane_ctx_get_ifp_vrf_id(ctx). Using the proper accessor avoids casting an ifindex to vrf_id_t and better respects the dataplane abstraction. On interface updates, the vrf_id provided by the dataplane is now used directly. On interface deletion (DELLINK), where the dataplane context may no longer carry vrf information, zebra relies on the existing ifp state (ifp->vrf->vrf_id) before if_delete_update() is called. The table_id is also retrieved from ifp->vrf->data.l.table_id since dplane_ctx_get_ifp_table_id() is not set for RTM_DELLINK events. The if_vrf_change LTTng tracepoint is moved out of interface_vrf_change() and into the caller zebra_if_dplane_ifp_handling(), where the original ifindex from the dplane context is still available. This preserves the existing tracepoint ABI (field name "ifindex", type ifindex_t) so that existing trace consumers are not affected. This is required for non-kernel dataplanes such as Grout (DPDK), where the VRF ID is not equal to the VRF interface ifindex. In that case, using the correct vrf_id fixes VRF handling between zebra and the Grout dataplane. Signed-off-by: Maxime Leroy --- zebra/interface.c | 51 ++++++++++++++++++++--------------------------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/zebra/interface.c b/zebra/interface.c index bcc8b4345605..b51989b8e80f 100644 --- a/zebra/interface.c +++ b/zebra/interface.c @@ -1503,32 +1503,27 @@ static void zebra_if_netconf_update_ctx(struct zebra_dplane_ctx *ctx, (*linkdown_set ? "ON" : "OFF")); } -static void interface_vrf_change(enum dplane_op_e op, ifindex_t ifindex, - const char *name, uint32_t tableid, - ns_id_t ns_id) +static void interface_vrf_change(enum dplane_op_e op, vrf_id_t vrf_id, const char *name, + uint32_t tableid, ns_id_t ns_id) { struct vrf *vrf; struct zebra_vrf *zvrf = NULL; if (op == DPLANE_OP_INTF_DELETE) { if (IS_ZEBRA_DEBUG_DPLANE) - zlog_debug("DPLANE_OP_INTF_DELETE for VRF %s(%u)", name, - ifindex); + zlog_debug("DPLANE_OP_INTF_DELETE for VRF %s(%u)", name, vrf_id); - vrf = vrf_lookup_by_id((vrf_id_t)ifindex); + vrf = vrf_lookup_by_id(vrf_id); if (!vrf) { - flog_warn(EC_ZEBRA_VRF_NOT_FOUND, - "%s(%u): vrf not found", name, ifindex); + flog_warn(EC_ZEBRA_VRF_NOT_FOUND, "%s(%u): vrf not found", name, vrf_id); return; } - frrtrace(4, frr_zebra, if_vrf_change, ifindex, name, tableid, 0); vrf_delete(vrf); } else { if (IS_ZEBRA_DEBUG_DPLANE) - zlog_debug( - "DPLANE_OP_INTF_UPDATE for VRF %s(%u) table %u", - name, ifindex, tableid); + zlog_debug("DPLANE_OP_INTF_UPDATE for VRF %s(%u) table %u", name, vrf_id, + tableid); /* * For a given tableid, if there already exists a vrf and it @@ -1540,26 +1535,23 @@ static void interface_vrf_change(enum dplane_op_e op, ifindex_t ifindex, if (exist_id != VRF_DEFAULT || strmatch(name, VRF_DEFAULT_NAME)) { vrf = vrf_lookup_by_id(exist_id); - if (!vrf_lookup_by_id((vrf_id_t)ifindex) && !vrf) { - flog_err(EC_ZEBRA_VRF_NOT_FOUND, - "VRF %s id %u does not exist", name, - ifindex); + if (!vrf_lookup_by_id(vrf_id) && !vrf) { + flog_err(EC_ZEBRA_VRF_NOT_FOUND, "VRF %s id %u does not exist", + name, vrf_id); frr_exit_with_buffer_flush(-1); } if (vrf && strcmp(name, vrf->name)) { flog_err(EC_ZEBRA_VRF_MISCONFIGURED, "VRF %s id %u table id overlaps existing vrf %s(%d), misconfiguration exiting", - name, ifindex, vrf->name, vrf->vrf_id); + name, vrf_id, vrf->name, vrf->vrf_id); frr_exit_with_buffer_flush(-1); } } - frrtrace(4, frr_zebra, if_vrf_change, ifindex, name, tableid, 1); - vrf = vrf_update((vrf_id_t)ifindex, name); + vrf = vrf_update(vrf_id, name); if (!vrf) { - flog_err(EC_LIB_INTERFACE, "VRF %s id %u not created", - name, ifindex); + flog_err(EC_LIB_INTERFACE, "VRF %s id %u not created", name, vrf_id); return; } @@ -1580,9 +1572,7 @@ static void interface_vrf_change(enum dplane_op_e op, ifindex_t ifindex, /* Enable the created VRF. */ if (!vrf_enable(vrf)) { - flog_err(EC_LIB_INTERFACE, - "Failed to enable VRF %s id %u", name, - ifindex); + flog_err(EC_LIB_INTERFACE, "Failed to enable VRF %s id %u", name, vrf_id); return; } } @@ -2019,14 +2009,17 @@ static void zebra_if_dplane_ifp_handling(struct zebra_dplane_ctx *ctx) if_delete_update(&ifp); - if (vrf) - interface_vrf_change(op, ifindex, name, vrf->data.l.table_id, ns_id); + if (vrf) { + frrtrace(4, frr_zebra, if_vrf_change, ifindex, name, vrf->data.l.table_id, + 0); + interface_vrf_change(op, vrf->vrf_id, name, vrf->data.l.table_id, ns_id); + } } else { ifindex_t master_ifindex, bridge_ifindex, link_ifindex; + vrf_id_t vrf_id = dplane_ctx_get_ifp_vrf_id(ctx); enum zebra_slave_iftype zif_slave_type; uint8_t bypass; uint64_t flags; - vrf_id_t vrf_id; uint32_t mtu; ns_id_t link_nsid; struct zebra_if *zif; @@ -2041,7 +2034,8 @@ static void zebra_if_dplane_ifp_handling(struct zebra_dplane_ctx *ctx) if (zif_type == ZEBRA_IF_VRF && !vrf_is_backend_netns()) { uint32_t tableid = dplane_ctx_get_ifp_table_id(ctx); - interface_vrf_change(op, ifindex, name, tableid, ns_id); + frrtrace(4, frr_zebra, if_vrf_change, ifindex, name, tableid, 1); + interface_vrf_change(op, vrf_id, name, tableid, ns_id); } master_ifindex = dplane_ctx_get_ifp_master_ifindex(ctx); @@ -2050,7 +2044,6 @@ static void zebra_if_dplane_ifp_handling(struct zebra_dplane_ctx *ctx) bond_ifindex = dplane_ctx_get_ifp_bond_ifindex(ctx); bypass = dplane_ctx_get_ifp_bypass(ctx); flags = dplane_ctx_get_ifp_flags(ctx); - vrf_id = dplane_ctx_get_ifp_vrf_id(ctx); mtu = dplane_ctx_get_ifp_mtu(ctx); link_ifindex = dplane_ctx_get_ifp_link_ifindex(ctx); link_nsid = dplane_ctx_get_ifp_link_nsid(ctx); From 3c06982df17330566d15108ed3e7c2eda2a25011 Mon Sep 17 00:00:00 2001 From: Maxime Leroy Date: Tue, 27 Jan 2026 12:04:48 +0100 Subject: [PATCH 3/3] zebra: pass vrf pointer to interface_vrf_change for delete In the delete path, we already hold a reference to the vrf structure before calling if_delete_update(). Passing this pointer directly to interface_vrf_change() avoids a redundant vrf_lookup_by_id() call and allows accessing vrf->name, vrf->vrf_id, and vrf->data.l.table_id directly. For update operations, the vrf pointer is passed as NULL and the function continues to use the vrf_id, name, and tableid parameters from the dataplane context. Signed-off-by: Maxime Leroy --- zebra/interface.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/zebra/interface.c b/zebra/interface.c index b51989b8e80f..1c278457eed2 100644 --- a/zebra/interface.c +++ b/zebra/interface.c @@ -1503,21 +1503,14 @@ static void zebra_if_netconf_update_ctx(struct zebra_dplane_ctx *ctx, (*linkdown_set ? "ON" : "OFF")); } -static void interface_vrf_change(enum dplane_op_e op, vrf_id_t vrf_id, const char *name, - uint32_t tableid, ns_id_t ns_id) +static void interface_vrf_change(enum dplane_op_e op, struct vrf *vrf, vrf_id_t vrf_id, + const char *name, uint32_t tableid, ns_id_t ns_id) { - struct vrf *vrf; struct zebra_vrf *zvrf = NULL; if (op == DPLANE_OP_INTF_DELETE) { if (IS_ZEBRA_DEBUG_DPLANE) - zlog_debug("DPLANE_OP_INTF_DELETE for VRF %s(%u)", name, vrf_id); - - vrf = vrf_lookup_by_id(vrf_id); - if (!vrf) { - flog_warn(EC_ZEBRA_VRF_NOT_FOUND, "%s(%u): vrf not found", name, vrf_id); - return; - } + zlog_debug("DPLANE_OP_INTF_DELETE for VRF %s(%u)", vrf->name, vrf->vrf_id); vrf_delete(vrf); } else { @@ -2012,7 +2005,7 @@ static void zebra_if_dplane_ifp_handling(struct zebra_dplane_ctx *ctx) if (vrf) { frrtrace(4, frr_zebra, if_vrf_change, ifindex, name, vrf->data.l.table_id, 0); - interface_vrf_change(op, vrf->vrf_id, name, vrf->data.l.table_id, ns_id); + interface_vrf_change(op, vrf, 0, NULL, 0, ns_id); } } else { ifindex_t master_ifindex, bridge_ifindex, link_ifindex; @@ -2035,7 +2028,7 @@ static void zebra_if_dplane_ifp_handling(struct zebra_dplane_ctx *ctx) uint32_t tableid = dplane_ctx_get_ifp_table_id(ctx); frrtrace(4, frr_zebra, if_vrf_change, ifindex, name, tableid, 1); - interface_vrf_change(op, vrf_id, name, tableid, ns_id); + interface_vrf_change(op, NULL, vrf_id, name, tableid, ns_id); } master_ifindex = dplane_ctx_get_ifp_master_ifindex(ctx);