diff --git a/frr/if_grout.c b/frr/if_grout.c index 4fa7783c8..c0a7615f9 100644 --- a/frr/if_grout.c +++ b/frr/if_grout.c @@ -110,6 +110,8 @@ void grout_link_change(struct gr_iface *gr_if, bool new, bool startup) { dplane_ctx_set_op(ctx, DPLANE_OP_INTF_INSTALL); dplane_ctx_set_status(ctx, ZEBRA_DPLANE_REQUEST_QUEUED); dplane_ctx_set_ifp_mtu(ctx, gr_if->base.mtu); + dplane_ctx_set_ifp_speed_set(ctx, true); + dplane_ctx_set_ifp_speed(ctx, gr_if->base.speed); // no bridge support in grout dplane_ctx_set_ifp_bridge_ifindex(ctx, IFINDEX_INTERNAL); diff --git a/frr/zebra_dplane_grout.c b/frr/zebra_dplane_grout.c index 6d76259aa..6031fe3bf 100644 --- a/frr/zebra_dplane_grout.c +++ b/frr/zebra_dplane_grout.c @@ -607,6 +607,11 @@ static enum zebra_dplane_result zd_grout_process_update(struct zebra_dplane_ctx case DPLANE_OP_SRV6_ENCAP_SRCADDR_SET: return grout_set_sr_tunsrc(ctx); + case DPLANE_OP_INTF_SPEED_GET: + // Workaround: return fail INTF_SPEED_GET to stop zebra from repeatedly polling. + // Speed is already provided via INTF_INSTALL/UPDATE. + return ZEBRA_DPLANE_REQUEST_FAILURE; + case DPLANE_OP_NONE: return ZEBRA_DPLANE_REQUEST_SUCCESS; diff --git a/subprojects/frr.wrap b/subprojects/frr.wrap index e31735931..a09612e77 100644 --- a/subprojects/frr.wrap +++ b/subprojects/frr.wrap @@ -3,7 +3,14 @@ url = https://github.com/FRRouting/frr revision = frr-10.5.0 depth = 1 diff_files = - frr/meson-add-dependency-definition.patch + frr/meson-add-dependency-definition.patch, + frr/0001-zebra-Track-the-number-of-times-that-the-intf-speed-.patch, + frr/0002-zebra-Allow-bonds-to-refigure-speed.patch, + frr/0003-zebra-add-dplane-helpers-provide-interface-speed.patch, + frr/0004-zebra-kernel-get_speed-take-vrf_id-and-ifname-as-par.patch, + frr/0005-zebra-get-interface-speed-from-dataplane-via-ethtool.patch, + frr/0006-zebra-schedule-speed-query-timer-only-when-the-inter.patch, + frr/0007-zebra-skip-kernel-provider-work-when-skip_kernel-is-.patch [provide] dependency_names = frr diff --git a/subprojects/packagefiles/frr/0001-zebra-Track-the-number-of-times-that-the-intf-speed-.patch b/subprojects/packagefiles/frr/0001-zebra-Track-the-number-of-times-that-the-intf-speed-.patch new file mode 100644 index 000000000..22d319cc7 --- /dev/null +++ b/subprojects/packagefiles/frr/0001-zebra-Track-the-number-of-times-that-the-intf-speed-.patch @@ -0,0 +1,60 @@ +From afdf8a3574fac4b3e075d0f1f29b76914562b240 Mon Sep 17 00:00:00 2001 +From: Donald Sharp +Date: Wed, 22 Oct 2025 11:04:43 -0400 +Subject: [PATCH 1/7] zebra: Track the number of times that the intf speed was + checked + +Adding a json value to `show int X json` to allow us to get the +number of times that the speed was checked. + +Signed-off-by: Donald Sharp +--- + zebra/interface.c | 5 +++++ + zebra/interface.h | 1 + + 2 files changed, 6 insertions(+) + +diff --git a/zebra/interface.c b/zebra/interface.c +index 52c2619eed..e1fd092532 100644 +--- a/zebra/interface.c ++++ b/zebra/interface.c +@@ -68,6 +68,8 @@ static void if_zebra_speed_update(struct event *thread) + bool changed = false; + int error = 0; + ++ zif->speed_checked++; ++ + new_speed = kernel_get_speed(ifp, &error); + + /* error may indicate vrf not available or +@@ -164,6 +166,7 @@ static int if_zebra_new_hook(struct interface *ifp) + * down upon startup. + */ + zebra_if->speed_update_count = 0; ++ zebra_if->speed_checked = 0; + event_add_timer(zrouter.master, if_zebra_speed_update, ifp, 15, + &zebra_if->speed_update); + event_ignore_late_timer(zebra_if->speed_update); +@@ -3027,6 +3030,8 @@ static void if_dump_vty_json(struct vty *vty, struct interface *ifp, + json_object_string_add(json_if, "shutdownConfig", if_zebra_data_state(zebra_if->shutdown)); + + json_object_string_add(json_if, "mplsConfig", if_zebra_data_state(zebra_if->mpls_config)); ++ json_object_int_add(json_if, "speedChecked", zebra_if->speed_checked); ++ + + if (ifp->ifindex == IFINDEX_INTERNAL) { + json_object_boolean_add(json_if, "pseudoInterface", true); +diff --git a/zebra/interface.h b/zebra/interface.h +index fca170aee4..98d0dcb325 100644 +--- a/zebra/interface.h ++++ b/zebra/interface.h +@@ -207,6 +207,7 @@ struct zebra_if { + + uint8_t speed_update_count; + struct event *speed_update; ++ uint32_t speed_checked; + + /* + * Does this interface have a v6 to v4 ll neighbor entry +-- +2.43.0 + diff --git a/subprojects/packagefiles/frr/0002-zebra-Allow-bonds-to-refigure-speed.patch b/subprojects/packagefiles/frr/0002-zebra-Allow-bonds-to-refigure-speed.patch new file mode 100644 index 000000000..a0e252daa --- /dev/null +++ b/subprojects/packagefiles/frr/0002-zebra-Allow-bonds-to-refigure-speed.patch @@ -0,0 +1,66 @@ +From 717719db35310c7f8b1398d335a42d76a143f0f1 Mon Sep 17 00:00:00 2001 +From: Donald Sharp +Date: Wed, 22 Oct 2025 13:40:33 -0400 +Subject: [PATCH 2/7] zebra: Allow bonds to refigure speed + +When a member of a bond goes down, notice +that this has happened and schedule a interface +speed update. This will allow the interface +speed to stay accurate to some extent. + +Signed-off-by: Donald Sharp +--- + zebra/interface.c | 23 +++++++++++++++++++++++ + 1 file changed, 23 insertions(+) + +diff --git a/zebra/interface.c b/zebra/interface.c +index e1fd092532..100e6c10fc 100644 +--- a/zebra/interface.c ++++ b/zebra/interface.c +@@ -911,6 +911,25 @@ static void if_down_del_nbr_connected(struct interface *ifp) + } + } + ++static void if_handle_bond_speed_change(struct interface *ifp) ++{ ++ struct zebra_if *zif = ifp->info; ++ struct zebra_l2info_bondslave *part_of_bond; ++ ++ if (!IS_ZEBRA_IF_BOND_SLAVE(ifp)) ++ return; ++ ++ part_of_bond = &zif->bondslave_info; ++ ++ if (part_of_bond->bond_if) { ++ zif = part_of_bond->bond_if->info; ++ ++ if (!event_is_scheduled(zif->speed_update)) ++ event_add_timer(zrouter.master, if_zebra_speed_update, part_of_bond->bond_if, 1, ++ &zif->speed_update); ++ } ++} ++ + /* Interface is up. */ + void if_up(struct interface *ifp, bool install_connected) + { +@@ -975,6 +994,8 @@ void if_up(struct interface *ifp, bool install_connected) + + if_addr_wakeup(ifp); + ++ if_handle_bond_speed_change(ifp); ++ + rib_update_handle_vrf_all(RIB_UPDATE_KERNEL, ZEBRA_ROUTE_KERNEL); + } + +@@ -1027,6 +1048,8 @@ void if_down(struct interface *ifp) + /* Delete all neighbor addresses learnt through IPv6 RA */ + if_down_del_nbr_connected(ifp); + ++ if_handle_bond_speed_change(ifp); ++ + rib_update_handle_vrf_all(RIB_UPDATE_INTERFACE_DOWN, ZEBRA_ROUTE_KERNEL); + } + +-- +2.43.0 + diff --git a/subprojects/packagefiles/frr/0003-zebra-add-dplane-helpers-provide-interface-speed.patch b/subprojects/packagefiles/frr/0003-zebra-add-dplane-helpers-provide-interface-speed.patch new file mode 100644 index 000000000..031e397cf --- /dev/null +++ b/subprojects/packagefiles/frr/0003-zebra-add-dplane-helpers-provide-interface-speed.patch @@ -0,0 +1,125 @@ +From ff6bb34486de63def948902cde22d222eaf88592 Mon Sep 17 00:00:00 2001 +From: Maxime Leroy +Date: Thu, 14 Aug 2025 12:07:19 +0200 +Subject: [PATCH 3/7] zebra: add dplane helpers provide interface speed + +Add dplane ctx helpers to carry interface speed. + +Signed-off-by: Maxime Leroy +--- + zebra/interface.c | 12 +++++++++--- + zebra/zebra_dplane.c | 31 +++++++++++++++++++++++++++++++ + zebra/zebra_dplane.h | 4 ++++ + 3 files changed, 44 insertions(+), 3 deletions(-) + +diff --git a/zebra/interface.c b/zebra/interface.c +index 100e6c10fc..f128b5f422 100644 +--- a/zebra/interface.c ++++ b/zebra/interface.c +@@ -1950,8 +1950,8 @@ static void zebra_if_dplane_ifp_handling(struct zebra_dplane_ctx *ctx) + uint32_t mtu; + ns_id_t link_nsid; + struct zebra_if *zif; +- bool protodown, protodown_set, startup; +- uint32_t rc_bitfield; ++ bool protodown, protodown_set, startup, speed_set; ++ uint32_t rc_bitfield, speed; + uint8_t old_hw_addr[INTERFACE_HWADDR_MAX]; + char *desc; + uint8_t family; +@@ -1976,6 +1976,8 @@ static void zebra_if_dplane_ifp_handling(struct zebra_dplane_ctx *ctx) + startup = dplane_ctx_get_ifp_startup(ctx); + desc = dplane_ctx_get_ifp_desc(ctx); + family = dplane_ctx_get_ifp_family(ctx); ++ speed_set = dplane_ctx_get_ifp_speed_set(ctx); ++ speed = dplane_ctx_get_ifp_speed(ctx); + + #ifndef AF_BRIDGE + /* +@@ -2011,7 +2013,9 @@ static void zebra_if_dplane_ifp_handling(struct zebra_dplane_ctx *ctx) + if_update_state_mtu(ifp, mtu); + if_update_state_mtu6(ifp, mtu); + if_update_state_metric(ifp, 0); +- if_update_state_speed(ifp, kernel_get_speed(ifp, NULL)); ++ if (!speed_set) ++ speed = kernel_get_speed(ifp, NULL); ++ if_update_state_speed(ifp, speed); + ifp->ptm_status = ZEBRA_PTM_STATUS_UNKNOWN; + ifp->txqlen = dplane_ctx_get_intf_txqlen(ctx); + +@@ -2087,6 +2091,8 @@ static void zebra_if_dplane_ifp_handling(struct zebra_dplane_ctx *ctx) + if_update_state_mtu(ifp, mtu); + if_update_state_mtu6(ifp, mtu); + if_update_state_metric(ifp, 0); ++ if (speed_set) ++ if_update_state_speed(ifp, speed); + ifp->txqlen = dplane_ctx_get_intf_txqlen(ctx); + + /* +diff --git a/zebra/zebra_dplane.c b/zebra/zebra_dplane.c +index 06d5d04d7f..40ea590e14 100644 +--- a/zebra/zebra_dplane.c ++++ b/zebra/zebra_dplane.c +@@ -235,6 +235,9 @@ struct dplane_intf_info { + uint32_t metric; + uint32_t flags; + ++ bool speed_set; ++ uint32_t speed; ++ + bool protodown; + bool protodown_set; + bool pd_reason_val; +@@ -1780,6 +1783,34 @@ void dplane_ctx_set_ifp_zif_type(struct zebra_dplane_ctx *ctx, + ctx->u.intf.zif_type = zif_type; + } + ++void dplane_ctx_set_ifp_speed_set(struct zebra_dplane_ctx *ctx, bool set) ++{ ++ DPLANE_CTX_VALID(ctx); ++ ++ ctx->u.intf.speed_set = set; ++} ++ ++bool dplane_ctx_get_ifp_speed_set(const struct zebra_dplane_ctx *ctx) ++{ ++ DPLANE_CTX_VALID(ctx); ++ ++ return ctx->u.intf.speed_set; ++} ++ ++void dplane_ctx_set_ifp_speed(struct zebra_dplane_ctx *ctx, uint32_t speed) ++{ ++ DPLANE_CTX_VALID(ctx); ++ ++ ctx->u.intf.speed = speed; ++} ++ ++uint32_t dplane_ctx_get_ifp_speed(const struct zebra_dplane_ctx *ctx) ++{ ++ DPLANE_CTX_VALID(ctx); ++ ++ return ctx->u.intf.speed; ++} ++ + void dplane_ctx_set_ifname(struct zebra_dplane_ctx *ctx, const char *ifname) + { + DPLANE_CTX_VALID(ctx); +diff --git a/zebra/zebra_dplane.h b/zebra/zebra_dplane.h +index dd8a69e7db..cf1f70843e 100644 +--- a/zebra/zebra_dplane.h ++++ b/zebra/zebra_dplane.h +@@ -435,6 +435,10 @@ uint8_t dplane_ctx_get_ifp_family(const struct zebra_dplane_ctx *ctx); + struct zebra_vxlan_vni_array; + void dplane_ctx_set_ifp_vxlan_vni_array(struct zebra_dplane_ctx *ctx, + struct zebra_vxlan_vni_array *vniarray); ++void dplane_ctx_set_ifp_speed_set(struct zebra_dplane_ctx *ctx, bool set); ++bool dplane_ctx_get_ifp_speed_set(const struct zebra_dplane_ctx *ctx); ++void dplane_ctx_set_ifp_speed(struct zebra_dplane_ctx *ctx, uint32_t speed); ++uint32_t dplane_ctx_get_ifp_speed(const struct zebra_dplane_ctx *ctx); + + /* + * These defines mirror the values for bridge values in linux +-- +2.43.0 + diff --git a/subprojects/packagefiles/frr/0004-zebra-kernel-get_speed-take-vrf_id-and-ifname-as-par.patch b/subprojects/packagefiles/frr/0004-zebra-kernel-get_speed-take-vrf_id-and-ifname-as-par.patch new file mode 100644 index 000000000..3408029a1 --- /dev/null +++ b/subprojects/packagefiles/frr/0004-zebra-kernel-get_speed-take-vrf_id-and-ifname-as-par.patch @@ -0,0 +1,133 @@ +From 3d8d0de8c7b0d479b4f72e446af946ae1fd2ac72 Mon Sep 17 00:00:00 2001 +From: Maxime Leroy +Date: Fri, 5 Sep 2025 09:09:58 +0200 +Subject: [PATCH 4/7] zebra: kernel get_speed take vrf_id and ifname as param + +kernel_get_speed() doesn't need the full interface object; it only needs +the interface name and the VRF id to open the right socket/ioctl. Update +the prototype and callers accordingly. + +This will be used in the next commit. + +Signed-off-by: Maxime Leroy +--- + zebra/if_netlink.c | 14 +++----------- + zebra/interface.c | 4 ++-- + zebra/rt.h | 2 +- + zebra/rt_socket.c | 6 ++++-- + 4 files changed, 10 insertions(+), 16 deletions(-) + +diff --git a/zebra/if_netlink.c b/zebra/if_netlink.c +index a214914171..c1ff967587 100644 +--- a/zebra/if_netlink.c ++++ b/zebra/if_netlink.c +@@ -256,13 +256,12 @@ static void netlink_vrf_change(struct nlmsghdr *h, struct rtattr *tb, + ctx, *(uint32_t *)RTA_DATA(attr[IFLA_VRF_TABLE])); + } + +-static uint32_t get_iflink_speed(struct interface *interface, int *error) ++uint32_t kernel_get_speed(vrf_id_t vrf_id, const char *ifname, int *error) + { + struct ifreq ifdata; + struct ethtool_cmd ecmd; + int sd; + int rc; +- const char *ifname = interface->name; + uint32_t ret; + + if (error) +@@ -280,8 +279,7 @@ static uint32_t get_iflink_speed(struct interface *interface, int *error) + + /* use ioctl to get speed of an interface */ + frr_with_privs(&zserv_privs) { +- sd = vrf_socket(PF_INET, SOCK_DGRAM, IPPROTO_IP, +- interface->vrf->vrf_id, NULL); ++ sd = vrf_socket(PF_INET, SOCK_DGRAM, IPPROTO_IP, vrf_id, NULL); + if (sd < 0) { + if (IS_ZEBRA_DEBUG_KERNEL) + zlog_debug("Failure to read interface %s speed: %d %s", +@@ -292,8 +290,7 @@ static uint32_t get_iflink_speed(struct interface *interface, int *error) + return 0; + } + /* Get the current link state for the interface */ +- rc = vrf_ioctl(interface->vrf->vrf_id, sd, SIOCETHTOOL, +- (char *)&ifdata); ++ rc = vrf_ioctl(vrf_id, sd, SIOCETHTOOL, (char *)&ifdata); + } + if (rc < 0) { + if (errno != EOPNOTSUPP && IS_ZEBRA_DEBUG_KERNEL) +@@ -318,11 +315,6 @@ static uint32_t get_iflink_speed(struct interface *interface, int *error) + return ret; + } + +-uint32_t kernel_get_speed(struct interface *ifp, int *error) +-{ +- return get_iflink_speed(ifp, error); +-} +- + static ssize_t + netlink_gre_set_msg_encoder(struct zebra_dplane_ctx *ctx, void *buf, + size_t buflen) +diff --git a/zebra/interface.c b/zebra/interface.c +index f128b5f422..908dd54215 100644 +--- a/zebra/interface.c ++++ b/zebra/interface.c +@@ -70,7 +70,7 @@ static void if_zebra_speed_update(struct event *thread) + + zif->speed_checked++; + +- new_speed = kernel_get_speed(ifp, &error); ++ new_speed = kernel_get_speed(ifp->vrf->vrf_id, ifp->name, &error); + + /* error may indicate vrf not available or + * interfaces not available. +@@ -2014,7 +2014,7 @@ static void zebra_if_dplane_ifp_handling(struct zebra_dplane_ctx *ctx) + if_update_state_mtu6(ifp, mtu); + if_update_state_metric(ifp, 0); + if (!speed_set) +- speed = kernel_get_speed(ifp, NULL); ++ speed = kernel_get_speed(ifp->vrf->vrf_id, ifp->name, NULL); + if_update_state_speed(ifp, speed); + ifp->ptm_status = ZEBRA_PTM_STATUS_UNKNOWN; + ifp->txqlen = dplane_ctx_get_intf_txqlen(ctx); +diff --git a/zebra/rt.h b/zebra/rt.h +index e5dc26150a..01104b05a1 100644 +--- a/zebra/rt.h ++++ b/zebra/rt.h +@@ -77,7 +77,7 @@ extern int mpls_kernel_init(void); + void kernel_router_init(void); + void kernel_router_terminate(void); + +-extern uint32_t kernel_get_speed(struct interface *ifp, int *error); ++extern uint32_t kernel_get_speed(vrf_id_t vrf_id, const char *ifname, int *error); + extern int kernel_get_ipmr_sg_stats(struct zebra_vrf *zvrf, void *mroute); + + /* +diff --git a/zebra/rt_socket.c b/zebra/rt_socket.c +index de04978600..1e8d3e7eff 100644 +--- a/zebra/rt_socket.c ++++ b/zebra/rt_socket.c +@@ -23,6 +23,7 @@ + #include "lib_errors.h" + + #include "zebra/debug.h" ++#include "zebra/interface.h" + #include "zebra/rib.h" + #include "zebra/rt.h" + #include "zebra/kernel_socket.h" +@@ -382,9 +383,10 @@ extern int kernel_interface_set_master(struct interface *master, + return 0; + } + +-uint32_t kernel_get_speed(struct interface *ifp, int *error) ++uint32_t kernel_get_speed(vrf_id_t vrf_id, const char *ifname, int *error) + { +- return ifp->speed; ++ *error = INTERFACE_SPEED_ERROR_READ; ++ return 0; + } + + int kernel_upd_mac_nh(uint32_t nh_id, struct in_addr vtep_ip) +-- +2.43.0 + diff --git a/subprojects/packagefiles/frr/0005-zebra-get-interface-speed-from-dataplane-via-ethtool.patch b/subprojects/packagefiles/frr/0005-zebra-get-interface-speed-from-dataplane-via-ethtool.patch new file mode 100644 index 000000000..2ad5f5cab --- /dev/null +++ b/subprojects/packagefiles/frr/0005-zebra-get-interface-speed-from-dataplane-via-ethtool.patch @@ -0,0 +1,463 @@ +From 60b6894cb2e4e5d536506ac34da67638ef472a85 Mon Sep 17 00:00:00 2001 +From: Maxime Leroy +Date: Fri, 12 Dec 2025 15:15:48 +0100 +Subject: [PATCH 5/7] zebra: get interface speed from dataplane via ethtool + +This introduces DPLANE_OP_INTF_SPEED_GET so link speed is resolved in the +dataplane via ethtool and reported to zebra. Zebra no longer performs +synchronous speed reads; it simply applies the value provided by the +dataplane. + +If speed is already known during interface creation or modification, it +can be included in INTF_INSTALL/INTF_UPDATE and zebra will use it +directly. If speed is not provided, the zebra main thread +issues a follow-up INTF_SPEED_GET to request the dataplane to fetch the +speed asynchronously. + +For dataplane providers that implement only INTF_INSTALL/INTF_UPDATE and +do not support INTF_SPEED_GET, zebra relies on any speed value provided +by install/update. If speed is missing, zebra attempts a single +INTF_SPEED_GET query and stops if the operation is unsupported or fails. + +Signed-off-by: Maxime Leroy +--- + zebra/dplane_fpm_nl.c | 1 + + zebra/if_netlink.c | 6 +++ + zebra/interface.c | 98 +++++++++++++++++++++++++++++++----------- + zebra/interface.h | 1 + + zebra/kernel_netlink.c | 1 + + zebra/kernel_socket.c | 1 + + zebra/zebra_dplane.c | 67 +++++++++++++++++++++++++++++ + zebra/zebra_dplane.h | 2 + + zebra/zebra_rib.c | 1 + + zebra/zebra_script.c | 1 + + 10 files changed, 155 insertions(+), 24 deletions(-) + +diff --git a/zebra/dplane_fpm_nl.c b/zebra/dplane_fpm_nl.c +index 3bfa308860..e28cb203e4 100644 +--- a/zebra/dplane_fpm_nl.c ++++ b/zebra/dplane_fpm_nl.c +@@ -1102,6 +1102,7 @@ static int fpm_nl_enqueue(struct fpm_nl_ctx *fnc, struct zebra_dplane_ctx *ctx) + case DPLANE_OP_INTF_INSTALL: + case DPLANE_OP_INTF_UPDATE: + case DPLANE_OP_INTF_DELETE: ++ case DPLANE_OP_INTF_SPEED_GET: + case DPLANE_OP_TC_QDISC_INSTALL: + case DPLANE_OP_TC_QDISC_UNINSTALL: + case DPLANE_OP_TC_CLASS_ADD: +diff --git a/zebra/if_netlink.c b/zebra/if_netlink.c +index c1ff967587..c6b73dcd0c 100644 +--- a/zebra/if_netlink.c ++++ b/zebra/if_netlink.c +@@ -1408,6 +1408,12 @@ int netlink_link_change(struct nlmsghdr *h, ns_id_t ns_id, int startup) + } else + zif_slave_type = ZEBRA_IF_SLAVE_OTHER; + } ++ if (startup) { ++ dplane_ctx_set_ifp_speed(ctx, kernel_get_speed(vrf_id, name, NULL)); ++ dplane_ctx_set_ifp_speed_set(ctx, true); ++ } else ++ dplane_ctx_set_ifp_speed_set(ctx, false); ++ + dplane_ctx_set_ifp_zif_slave_type(ctx, zif_slave_type); + dplane_ctx_set_ifp_vrf_id(ctx, vrf_id); + dplane_ctx_set_ifp_master_ifindex(ctx, master_infindex); +diff --git a/zebra/interface.c b/zebra/interface.c +index 908dd54215..4c37e13170 100644 +--- a/zebra/interface.c ++++ b/zebra/interface.c +@@ -63,22 +63,40 @@ static const char *if_zebra_data_state(uint8_t state) + static void if_zebra_speed_update(struct event *thread) + { + struct interface *ifp = EVENT_ARG(thread); +- struct zebra_if *zif = ifp->info; ++ ++ dplane_intf_speed_get(ifp); ++} ++ ++static void zebra_if_schedule_speed_update(struct zebra_if *zif, int timeout) ++{ ++ event_add_timer(zrouter.master, if_zebra_speed_update, zif->ifp, timeout, ++ &zif->speed_update); ++ event_ignore_late_timer(zif->speed_update); ++} ++ ++static void zebra_if_speed_update_ctx(struct zebra_dplane_ctx *ctx, struct interface *ifp) ++{ ++ enum zebra_dplane_result dp_res; ++ bool speed_set, changed = false; ++ struct zebra_if *zif; + uint32_t new_speed; +- bool changed = false; +- int error = 0; + +- zif->speed_checked++; ++ if (!ifp) ++ return; + +- new_speed = kernel_get_speed(ifp->vrf->vrf_id, ifp->name, &error); ++ zif = ifp->info; ++ zif->speed_checked++; + ++ dp_res = dplane_ctx_get_status(ctx); + /* error may indicate vrf not available or + * interfaces not available. + * note that loopback & virtual interfaces can return 0 as speed + */ +- if (error == INTERFACE_SPEED_ERROR_READ) ++ if (dp_res == ZEBRA_DPLANE_REQUEST_FAILURE) + return; + ++ speed_set = dplane_ctx_get_ifp_speed_set(ctx); ++ new_speed = speed_set ? dplane_ctx_get_ifp_speed(ctx) : 0; + if (new_speed != ifp->speed) { + zlog_info("%s: %s old speed: %u new speed: %u", __func__, + ifp->name, ifp->speed, new_speed); +@@ -87,7 +105,7 @@ static void if_zebra_speed_update(struct event *thread) + changed = true; + } + +- if (changed || error == INTERFACE_SPEED_ERROR_UNKNOWN) { ++ if (changed || !speed_set) { + #define SPEED_UPDATE_SLEEP_TIME 5 + #define SPEED_UPDATE_COUNT_MAX (4 * 60 / SPEED_UPDATE_SLEEP_TIME) + /* +@@ -102,17 +120,46 @@ static void if_zebra_speed_update(struct event *thread) + * to not update the system to keep track of that. This + * is far simpler to just stop trying after 4 minutes + */ +- if (error == INTERFACE_SPEED_ERROR_UNKNOWN && +- zif->speed_update_count == SPEED_UPDATE_COUNT_MAX) ++ if (!speed_set && zif->speed_update_count == SPEED_UPDATE_COUNT_MAX) + return; + + zif->speed_update_count++; +- event_add_timer(zrouter.master, if_zebra_speed_update, ifp, +- SPEED_UPDATE_SLEEP_TIME, &zif->speed_update); +- event_ignore_late_timer(zif->speed_update); ++ zebra_if_schedule_speed_update(zif, SPEED_UPDATE_SLEEP_TIME); + } + } + ++void zebra_if_speed_process(struct zebra_dplane_ctx *ctx) ++{ ++ const char *ifname = dplane_ctx_get_ifname(ctx); ++ vrf_id_t vrf_id = dplane_ctx_get_vrf(ctx); ++ uint32_t speed; ++ int error; ++ ++ dplane_ctx_set_status(ctx, ZEBRA_DPLANE_REQUEST_FAILURE); ++ ++ speed = kernel_get_speed(vrf_id, ifname, &error); ++ switch (error) { ++ case 0: ++ dplane_ctx_set_status(ctx, ZEBRA_DPLANE_REQUEST_SUCCESS); ++ dplane_ctx_set_ifp_speed(ctx, speed); ++ dplane_ctx_set_ifp_speed_set(ctx, true); ++ return; ++ case INTERFACE_SPEED_ERROR_UNKNOWN: ++ dplane_ctx_set_status(ctx, ZEBRA_DPLANE_REQUEST_SUCCESS); ++ dplane_ctx_set_ifp_speed_set(ctx, false); ++ return; ++ case INTERFACE_SPEED_ERROR_READ: ++ /* INTERFACE_SPEED_ERROR_READ: means no device, no vrf */ ++ break; ++ default: ++ if (IS_ZEBRA_DEBUG_KERNEL) ++ zlog_debug("kernel_get_speed returns an unkwnown error %u", error); ++ break; ++ } ++ ++ dplane_ctx_set_status(ctx, ZEBRA_DPLANE_REQUEST_FAILURE); ++} ++ + static void zebra_if_node_destroy(route_table_delegate_t *delegate, + struct route_table *table, + struct route_node *node) +@@ -167,9 +214,7 @@ static int if_zebra_new_hook(struct interface *ifp) + */ + zebra_if->speed_update_count = 0; + zebra_if->speed_checked = 0; +- event_add_timer(zrouter.master, if_zebra_speed_update, ifp, 15, +- &zebra_if->speed_update); +- event_ignore_late_timer(zebra_if->speed_update); ++ zebra_if_schedule_speed_update(zebra_if, 15); + + return 0; + } +@@ -924,9 +969,7 @@ static void if_handle_bond_speed_change(struct interface *ifp) + if (part_of_bond->bond_if) { + zif = part_of_bond->bond_if->info; + +- if (!event_is_scheduled(zif->speed_update)) +- event_add_timer(zrouter.master, if_zebra_speed_update, part_of_bond->bond_if, 1, +- &zif->speed_update); ++ zebra_if_schedule_speed_update(zif, 1); + } + } + +@@ -988,9 +1031,8 @@ void if_up(struct interface *ifp, bool install_connected) + if (zif->flags & ZIF_FLAG_EVPN_MH_UPLINK) + zebra_evpn_mh_uplink_oper_update(zif); + +- event_add_timer(zrouter.master, if_zebra_speed_update, ifp, 0, +- &zif->speed_update); +- event_ignore_late_timer(zif->speed_update); ++ event_cancel(&zif->speed_update); ++ dplane_intf_speed_get(ifp); + + if_addr_wakeup(ifp); + +@@ -2013,8 +2055,12 @@ static void zebra_if_dplane_ifp_handling(struct zebra_dplane_ctx *ctx) + if_update_state_mtu(ifp, mtu); + if_update_state_mtu6(ifp, mtu); + if_update_state_metric(ifp, 0); +- if (!speed_set) +- speed = kernel_get_speed(ifp->vrf->vrf_id, ifp->name, NULL); ++ if (!speed_set) { ++ speed = 0; ++ /* Query initial speed if not provided by dplane */ ++ dplane_intf_speed_get(ifp); ++ } else ++ zif->speed_checked++; + if_update_state_speed(ifp, speed); + ifp->ptm_status = ZEBRA_PTM_STATUS_UNKNOWN; + ifp->txqlen = dplane_ctx_get_intf_txqlen(ctx); +@@ -2091,8 +2137,10 @@ static void zebra_if_dplane_ifp_handling(struct zebra_dplane_ctx *ctx) + if_update_state_mtu(ifp, mtu); + if_update_state_mtu6(ifp, mtu); + if_update_state_metric(ifp, 0); +- if (speed_set) ++ if (speed_set) { ++ zif->speed_checked++; + if_update_state_speed(ifp, speed); ++ } + ifp->txqlen = dplane_ctx_get_intf_txqlen(ctx); + + /* +@@ -2271,6 +2319,8 @@ void zebra_if_dplane_result(struct zebra_dplane_ctx *ctx) + zebra_if_update_ctx(ctx, ifp); + } else if (op == DPLANE_OP_INTF_NETCONFIG) { + zebra_if_netconf_update_ctx(ctx, ifp, ifindex); ++ } else if (op == DPLANE_OP_INTF_SPEED_GET) { ++ zebra_if_speed_update_ctx(ctx, ifp); + } + } + +diff --git a/zebra/interface.h b/zebra/interface.h +index 98d0dcb325..fa9dc24e32 100644 +--- a/zebra/interface.h ++++ b/zebra/interface.h +@@ -345,6 +345,7 @@ extern void zebra_l2_unmap_slave_from_bond(struct zebra_if *zif); + extern const char *zebra_protodown_rc_str(uint32_t protodown_rc, char *pd_buf, + uint32_t pd_buf_len); + void zebra_if_dplane_result(struct zebra_dplane_ctx *ctx); ++void zebra_if_speed_process(struct zebra_dplane_ctx *ctx); + + #ifdef HAVE_PROC_NET_DEV + extern void ifstat_update_proc(void); +diff --git a/zebra/kernel_netlink.c b/zebra/kernel_netlink.c +index 6f818c391b..d7e8ca9451 100644 +--- a/zebra/kernel_netlink.c ++++ b/zebra/kernel_netlink.c +@@ -1617,6 +1617,7 @@ static enum netlink_msg_status nl_put_msg(struct nl_batch *bth, + case DPLANE_OP_BR_PORT_UPDATE: + return FRR_NETLINK_SUCCESS; + ++ case DPLANE_OP_INTF_SPEED_GET: + case DPLANE_OP_IPTABLE_ADD: + case DPLANE_OP_IPTABLE_DELETE: + case DPLANE_OP_IPSET_ADD: +diff --git a/zebra/kernel_socket.c b/zebra/kernel_socket.c +index 2b411bb69c..6b3e2f381d 100644 +--- a/zebra/kernel_socket.c ++++ b/zebra/kernel_socket.c +@@ -1624,6 +1624,7 @@ void kernel_update_multi(struct dplane_ctx_list_head *ctx_list) + case DPLANE_OP_GRE_SET: + case DPLANE_OP_INTF_ADDR_ADD: + case DPLANE_OP_INTF_ADDR_DEL: ++ case DPLANE_OP_INTF_SPEED_GET: + case DPLANE_OP_STARTUP_STAGE: + case DPLANE_OP_SRV6_ENCAP_SRCADDR_SET: + case DPLANE_OP_VLAN_INSTALL: +diff --git a/zebra/zebra_dplane.c b/zebra/zebra_dplane.c +index 40ea590e14..48d07bff37 100644 +--- a/zebra/zebra_dplane.c ++++ b/zebra/zebra_dplane.c +@@ -640,6 +640,9 @@ static struct zebra_dplane_globals { + _Atomic uint32_t dg_intfs_in; + _Atomic uint32_t dg_intf_errors; + ++ _Atomic uint32_t dg_intf_speed_get_in; ++ _Atomic uint32_t dg_intf_speed_get_errors; ++ + _Atomic uint32_t dg_tcs_in; + _Atomic uint32_t dg_tcs_errors; + +@@ -913,6 +916,7 @@ static void dplane_ctx_free_internal(struct zebra_dplane_ctx *ctx) + break; + case DPLANE_OP_GRE_SET: + case DPLANE_OP_INTF_NETCONFIG: ++ case DPLANE_OP_INTF_SPEED_GET: + case DPLANE_OP_STARTUP_STAGE: + case DPLANE_OP_SRV6_ENCAP_SRCADDR_SET: + break; +@@ -1179,6 +1183,9 @@ const char *dplane_op2str(enum dplane_op_e op) + case DPLANE_OP_INTF_DELETE: + return "INTF_DELETE"; + ++ case DPLANE_OP_INTF_SPEED_GET: ++ return "INTF_SPEED_GET_GET"; ++ + case DPLANE_OP_TC_QDISC_INSTALL: + return "TC_QDISC_INSTALL"; + case DPLANE_OP_TC_QDISC_UNINSTALL: +@@ -5537,6 +5544,45 @@ enum zebra_dplane_result dplane_intf_update(const struct interface *ifp) + return ret; + } + ++/* ++ * Enqueue a interface speed query for the dataplane. ++ */ ++enum zebra_dplane_result dplane_intf_speed_get(const struct interface *ifp) ++{ ++ enum zebra_dplane_result result = ZEBRA_DPLANE_REQUEST_FAILURE; ++ struct zebra_dplane_ctx *ctx = NULL; ++ struct zebra_ns *zns; ++ int ret; ++ ++ ctx = dplane_ctx_alloc(); ++ ++ ctx->zd_op = DPLANE_OP_INTF_SPEED_GET; ++ ctx->zd_status = ZEBRA_DPLANE_REQUEST_SUCCESS; ++ ctx->zd_vrf_id = ifp->vrf->vrf_id; ++ ++ strlcpy(ctx->zd_ifname, ifp->name, sizeof(ctx->zd_ifname)); ++ ctx->zd_ifindex = ifp->ifindex; ++ ++ zns = zebra_ns_lookup(ifp->vrf->vrf_id); ++ dplane_ctx_ns_init(ctx, zns, false); ++ ++ ret = dplane_update_enqueue(ctx); ++ ++ /* Increment counter */ ++ atomic_fetch_add_explicit(&zdplane_info.dg_intf_speed_get_in, 1, memory_order_relaxed); ++ ++ if (ret == AOK) ++ result = ZEBRA_DPLANE_REQUEST_QUEUED; ++ else { ++ atomic_fetch_add_explicit(&zdplane_info.dg_intf_speed_get_errors, 1, ++ memory_order_relaxed); ++ if (ctx) ++ dplane_ctx_free(&ctx); ++ } ++ ++ return result; ++} ++ + /* + * Enqueue vxlan/evpn mac add (or update). + */ +@@ -6402,6 +6448,11 @@ int dplane_show_helper(struct vty *vty, bool detailed) + vty_out(vty, "Intf change updates: %" PRIu64 "\n", incoming); + vty_out(vty, "Intf change errors: %" PRIu64 "\n", errs); + ++ incoming = atomic_load_explicit(&zdplane_info.dg_intf_speed_get_in, memory_order_relaxed); ++ errs = atomic_load_explicit(&zdplane_info.dg_intf_speed_get_errors, memory_order_relaxed); ++ vty_out(vty, "Intf speed query: %" PRIu64 "\n", incoming); ++ vty_out(vty, "Intf speed errors: %" PRIu64 "\n", errs); ++ + incoming = atomic_load_explicit(&zdplane_info.dg_macs_in, + memory_order_relaxed); + errs = atomic_load_explicit(&zdplane_info.dg_mac_errors, +@@ -7051,6 +7102,12 @@ static void kernel_dplane_log_detail(struct zebra_dplane_ctx *ctx) + dplane_ctx_get_ifindex(ctx), + dplane_ctx_intf_is_protodown(ctx)); + break; ++ case DPLANE_OP_INTF_SPEED_GET: ++ zlog_debug("Dplane intf %s, idx %u, speed %u", ++ dplane_op2str(dplane_ctx_get_op(ctx)), dplane_ctx_get_ifindex(ctx), ++ dplane_ctx_get_ifp_speed(ctx)); ++ break; ++ + + /* TODO: more detailed log */ + case DPLANE_OP_TC_QDISC_INSTALL: +@@ -7247,6 +7304,7 @@ static void kernel_dplane_handle_result(struct zebra_dplane_ctx *ctx) + case DPLANE_OP_INTF_ADDR_ADD: + case DPLANE_OP_INTF_ADDR_DEL: + case DPLANE_OP_INTF_NETCONFIG: ++ case DPLANE_OP_INTF_SPEED_GET: + case DPLANE_OP_VLAN_INSTALL: + break; + +@@ -7288,6 +7346,13 @@ kernel_dplane_process_ipset_entry(struct zebra_dplane_provider *prov, + dplane_provider_enqueue_out_ctx(prov, ctx); + } + ++static void kernel_dplane_process_if_speed(struct zebra_dplane_provider *prov, ++ struct zebra_dplane_ctx *ctx) ++{ ++ zebra_if_speed_process(ctx); ++ dplane_provider_enqueue_out_ctx(prov, ctx); ++} ++ + /* + * Kernel provider callback + */ +@@ -7322,6 +7387,8 @@ static int kernel_dplane_process_func(struct zebra_dplane_provider *prov) + || dplane_ctx_get_op(ctx) + == DPLANE_OP_IPSET_ENTRY_DELETE)) + kernel_dplane_process_ipset_entry(prov, ctx); ++ else if (dplane_ctx_get_op(ctx) == DPLANE_OP_INTF_SPEED_GET) ++ kernel_dplane_process_if_speed(prov, ctx); + else + dplane_ctx_list_add_tail(&work_list, ctx); + } +diff --git a/zebra/zebra_dplane.h b/zebra/zebra_dplane.h +index cf1f70843e..b5e7f0d049 100644 +--- a/zebra/zebra_dplane.h ++++ b/zebra/zebra_dplane.h +@@ -190,6 +190,7 @@ enum dplane_op_e { + + /* Incoming interface config events */ + DPLANE_OP_INTF_NETCONFIG, ++ DPLANE_OP_INTF_SPEED_GET, + + /* Interface update */ + DPLANE_OP_INTF_INSTALL, +@@ -964,6 +965,7 @@ enum zebra_dplane_result dplane_intf_addr_unset(const struct interface *ifp, + */ + enum zebra_dplane_result dplane_intf_add(const struct interface *ifp); + enum zebra_dplane_result dplane_intf_update(const struct interface *ifp); ++enum zebra_dplane_result dplane_intf_speed_get(const struct interface *ifp); + + /* + * Enqueue tc link changes for the dataplane. +diff --git a/zebra/zebra_rib.c b/zebra/zebra_rib.c +index d3f88fd7c1..971e06ecb4 100644 +--- a/zebra/zebra_rib.c ++++ b/zebra/zebra_rib.c +@@ -5271,6 +5271,7 @@ static void rib_process_dplane_results(struct event *thread) + case DPLANE_OP_INTF_UPDATE: + case DPLANE_OP_INTF_DELETE: + case DPLANE_OP_INTF_NETCONFIG: ++ case DPLANE_OP_INTF_SPEED_GET: + zebra_if_dplane_result(ctx); + break; + +diff --git a/zebra/zebra_script.c b/zebra/zebra_script.c +index b6bd2b2a43..7d8e2b632f 100644 +--- a/zebra/zebra_script.c ++++ b/zebra/zebra_script.c +@@ -424,6 +424,7 @@ void lua_pushzebra_dplane_ctx(lua_State *L, const struct zebra_dplane_ctx *ctx) + case DPLANE_OP_TC_FILTER_UPDATE: + /* Not currently handled */ + case DPLANE_OP_INTF_NETCONFIG: /*NYI*/ ++ case DPLANE_OP_INTF_SPEED_GET: + case DPLANE_OP_SRV6_ENCAP_SRCADDR_SET: + case DPLANE_OP_NONE: + case DPLANE_OP_STARTUP_STAGE: +-- +2.43.0 + diff --git a/subprojects/packagefiles/frr/0006-zebra-schedule-speed-query-timer-only-when-the-inter.patch b/subprojects/packagefiles/frr/0006-zebra-schedule-speed-query-timer-only-when-the-inter.patch new file mode 100644 index 000000000..4a7d54352 --- /dev/null +++ b/subprojects/packagefiles/frr/0006-zebra-schedule-speed-query-timer-only-when-the-inter.patch @@ -0,0 +1,79 @@ +From c1a3f47c4014100000e2005dbf2b425d713e8711 Mon Sep 17 00:00:00 2001 +From: Maxime Leroy +Date: Fri, 12 Dec 2025 15:28:23 +0100 +Subject: [PATCH 6/7] zebra: schedule speed-query timer only when the interface + exists + +The 15-second timer used to re-query interface speed is currently +scheduled in if_zebra_new_hook() for every newly created +interface. However, at that point the interface may not yet exist in the +OS, and in some cases it may never be created. + +Because of this, the speed query will usually +fail (e.g. INTERFACE_SPEED_ERROR_READ) since the interface doesn't +exist. There is also a race condition: even if the interface is created, +the timer may run before the RTM_NEWLINK message is processed. + +As a result, ifp->ifp_index can remain IFINDEX_INTERNAL (0). When +if_add_update() calls zebra_ns_link_ifp(), the interface tree is updated +with this incorrect index. If this happens for multiple interfaces, the +tree can end up with duplicate keys, eventually causing a zebra crash. + +A check was added to zebra_ns_link_ifp() to avoid adding an interface +with an IFINDEX_INTERNAL index, but the root cause remained. + +This change fixes the underlying issue by scheduling the speed-update +timer only when a valid RTM_NEWLINK has been received. The scheduling +logic is moved from if_zebra_new_hook() to +zebra_if_dplane_ifp_handling(), and runs only once the interface has a +correct ifindex. + +Fixes: dc7b3ca ("zebra: Add one-shot thread to recheck speed") +Signed-off-by: Maxime Leroy +--- + zebra/interface.c | 22 +++++++++++----------- + 1 file changed, 11 insertions(+), 11 deletions(-) + +diff --git a/zebra/interface.c b/zebra/interface.c +index 4c37e13170..16d522b8c4 100644 +--- a/zebra/interface.c ++++ b/zebra/interface.c +@@ -205,17 +205,6 @@ static int if_zebra_new_hook(struct interface *ifp) + + ifp->info = zebra_if; + +- /* +- * Some platforms are telling us that the interface is +- * up and ready to go. When we check the speed we +- * sometimes get the wrong value. Wait a couple +- * of seconds and ask again. Hopefully it's all settled +- * down upon startup. +- */ +- zebra_if->speed_update_count = 0; +- zebra_if->speed_checked = 0; +- zebra_if_schedule_speed_update(zebra_if, 15); +- + return 0; + } + +@@ -2059,6 +2048,17 @@ static void zebra_if_dplane_ifp_handling(struct zebra_dplane_ctx *ctx) + speed = 0; + /* Query initial speed if not provided by dplane */ + dplane_intf_speed_get(ifp); ++ ++ /* ++ * Some platforms are telling us that the interface is ++ * up and ready to go. When we check the speed we ++ * sometimes get the wrong value. Wait a couple ++ * of seconds and ask again. Hopefully it's all settled ++ * down upon startup. ++ */ ++ zif->speed_update_count = 0; ++ zif->speed_checked = 0; ++ zebra_if_schedule_speed_update(zif, 15); + } else + zif->speed_checked++; + if_update_state_speed(ifp, speed); +-- +2.43.0 + diff --git a/subprojects/packagefiles/frr/0007-zebra-skip-kernel-provider-work-when-skip_kernel-is-.patch b/subprojects/packagefiles/frr/0007-zebra-skip-kernel-provider-work-when-skip_kernel-is-.patch new file mode 100644 index 000000000..4056154eb --- /dev/null +++ b/subprojects/packagefiles/frr/0007-zebra-skip-kernel-provider-work-when-skip_kernel-is-.patch @@ -0,0 +1,88 @@ +From 303e2dfa63f11863c80f9151d3795b34683b3428 Mon Sep 17 00:00:00 2001 +From: Maxime Leroy +Date: Wed, 14 Jan 2026 10:15:30 +0100 +Subject: [PATCH 7/7] zebra: skip kernel provider work when skip_kernel is set + +skip_kernel was only applied in the netlink batch send path. As a result, +operations processed by dedicated handlers (notably DPLANE_OP_INTF_SPEED_GET) +were still executed by the kernel provider even when a previous provider +plugin requested to skip kernel updates. + +Handle skip_kernel early in kernel_dplane_process_func() so it applies to +all kernel provider operations, and remove the scattered checks from the +netlink helpers. + +Signed-off-by: Maxime Leroy +--- + zebra/kernel_netlink.c | 3 --- + zebra/kernel_socket.c | 11 ----------- + zebra/zebra_dplane.c | 10 ++++++++++ + 3 files changed, 10 insertions(+), 14 deletions(-) + +diff --git a/zebra/kernel_netlink.c b/zebra/kernel_netlink.c +index d7e8ca9451..332dea8a92 100644 +--- a/zebra/kernel_netlink.c ++++ b/zebra/kernel_netlink.c +@@ -1562,9 +1562,6 @@ enum netlink_msg_status netlink_batch_add_msg( + static enum netlink_msg_status nl_put_msg(struct nl_batch *bth, + struct zebra_dplane_ctx *ctx) + { +- if (dplane_ctx_is_skip_kernel(ctx)) +- return FRR_NETLINK_SUCCESS; +- + switch (dplane_ctx_get_op(ctx)) { + + case DPLANE_OP_ROUTE_INSTALL: +diff --git a/zebra/kernel_socket.c b/zebra/kernel_socket.c +index 6b3e2f381d..840b33cec9 100644 +--- a/zebra/kernel_socket.c ++++ b/zebra/kernel_socket.c +@@ -1521,16 +1521,6 @@ void kernel_update_multi(struct dplane_ctx_list_head *ctx_list) + ctx = dplane_ctx_dequeue(ctx_list); + if (ctx == NULL) + break; +- +- /* +- * A previous provider plugin may have asked to skip the +- * kernel update. +- */ +- if (dplane_ctx_is_skip_kernel(ctx)) { +- res = ZEBRA_DPLANE_REQUEST_SUCCESS; +- goto skip_one; +- } +- + switch (dplane_ctx_get_op(ctx)) { + + case DPLANE_OP_ROUTE_INSTALL: +@@ -1633,7 +1623,6 @@ void kernel_update_multi(struct dplane_ctx_list_head *ctx_list) + res = ZEBRA_DPLANE_REQUEST_FAILURE; + } + +- skip_one: + dplane_ctx_set_status(ctx, res); + + dplane_ctx_enqueue_tail(&handled_list, ctx); +diff --git a/zebra/zebra_dplane.c b/zebra/zebra_dplane.c +index 48d07bff37..cebb3cbfdf 100644 +--- a/zebra/zebra_dplane.c ++++ b/zebra/zebra_dplane.c +@@ -7377,6 +7377,16 @@ static int kernel_dplane_process_func(struct zebra_dplane_provider *prov) + if (IS_ZEBRA_DEBUG_DPLANE_DETAIL) + kernel_dplane_log_detail(ctx); + ++ /* ++ * A previous provider plugin may have asked to skip the ++ * kernel update. ++ */ ++ if (dplane_ctx_is_skip_kernel(ctx)) { ++ dplane_ctx_set_status(ctx, ZEBRA_DPLANE_REQUEST_SUCCESS); ++ dplane_provider_enqueue_out_ctx(prov, ctx); ++ continue; ++ } ++ + if ((dplane_ctx_get_op(ctx) == DPLANE_OP_IPTABLE_ADD + || dplane_ctx_get_op(ctx) == DPLANE_OP_IPTABLE_DELETE)) + kernel_dplane_process_iptable(prov, ctx); +-- +2.43.0 +