From ee2b79f4355f584ca322a83a02750f5de4725f14 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 92c5de72f5119de4b6e5c5a00411a9366b700d2b Mon Sep 17 00:00:00 2001 From: Maxime Leroy Date: Tue, 27 Jan 2026 12:03:11 +0100 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. 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 | 47 ++++++++++++++++++--------------------------- zebra/zebra_trace.h | 4 ++-- 2 files changed, 21 insertions(+), 30 deletions(-) diff --git a/zebra/interface.c b/zebra/interface.c index bcc8b4345605..8d7bd35d92bf 100644 --- a/zebra/interface.c +++ b/zebra/interface.c @@ -1503,32 +1503,28 @@ 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); + frrtrace(4, frr_zebra, if_vrf_change, vrf_id, 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 +1536,24 @@ 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); + frrtrace(4, frr_zebra, if_vrf_change, vrf_id, name, tableid, 1); + 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 +1574,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; } } @@ -2020,13 +2012,13 @@ 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); + 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 +2033,7 @@ 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); + interface_vrf_change(op, vrf_id, name, tableid, ns_id); } master_ifindex = dplane_ctx_get_ifp_master_ifindex(ctx); @@ -2050,7 +2042,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); diff --git a/zebra/zebra_trace.h b/zebra/zebra_trace.h index 868839c37204..30e8adc71343 100644 --- a/zebra/zebra_trace.h +++ b/zebra/zebra_trace.h @@ -91,9 +91,9 @@ TRACEPOINT_LOGLEVEL(frr_zebra, if_upd_ctx_dplane_result, TRACE_INFO) TRACEPOINT_EVENT( frr_zebra, if_vrf_change, - TP_ARGS(ifindex_t, ifindex, const char *, name, uint32_t, tableid, uint8_t, loc), + TP_ARGS(vrf_id_t, vrf_id, const char *, name, uint32_t, tableid, uint8_t, loc), TP_FIELDS( - ctf_integer(ifindex_t, ifindex, ifindex) + ctf_integer(vrf_id_t, vrf_id, vrf_id) ctf_string(vrf_name, name) ctf_integer(uint32_t, tableid, tableid) ctf_integer(uint8_t, location, loc) From e3b212d14ee42a09d64bc93d214139e254260c3c 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 | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/zebra/interface.c b/zebra/interface.c index 8d7bd35d92bf..941118976303 100644 --- a/zebra/interface.c +++ b/zebra/interface.c @@ -1503,23 +1503,17 @@ 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); - frrtrace(4, frr_zebra, if_vrf_change, vrf_id, name, tableid, 0); + frrtrace(4, frr_zebra, if_vrf_change, vrf->vrf_id, vrf->name, vrf->data.l.table_id, + 0); vrf_delete(vrf); } else { if (IS_ZEBRA_DEBUG_DPLANE) @@ -2012,7 +2006,7 @@ static void zebra_if_dplane_ifp_handling(struct zebra_dplane_ctx *ctx) if_delete_update(&ifp); if (vrf) - 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; vrf_id_t vrf_id = dplane_ctx_get_ifp_vrf_id(ctx); @@ -2033,7 +2027,7 @@ 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, 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);