From 2ff74294aac326dec97b9b261c685b6247768e24 Mon Sep 17 00:00:00 2001 From: Tamer Ahmed Date: Thu, 20 Aug 2026 16:33:21 -0700 Subject: [PATCH 1/3] [patches-sonic]: Drop the unused NTF_EXT_VALIDATED neighbour patch Nothing consumes the flag. It exists for EVPN multihoming neighbour proxy advertisement, and FRR implements that in user space through its own ZEBRA_NEIGH_ES_PEER_PROXY state rather than asking the kernel, so neither FRR nor any SONiC daemon references NTF_EXT_VALIDATED or extern_valid. The series note claimed the flag was already upstream at 6.12, but it is absent from the 6.12.41 tree we build, so that was not the reason to drop it either way. The EVPN MH patches that remain are load bearing: NDA_FLAGS_EXT carries NTF_E_MH_PEER_SYNC, and the bridge FDB protocol field is what records whether a MAC was learned by the hardware or by zebra. Signed-off-by: Tamer Ahmed --- ..._EXT_VALIDATED-flag-for-externally-v.patch | 384 ------------------ patches-sonic/series | 2 - 2 files changed, 386 deletions(-) delete mode 100644 patches-sonic/0003-neighbor-Add-NTF_EXT_VALIDATED-flag-for-externally-v.patch diff --git a/patches-sonic/0003-neighbor-Add-NTF_EXT_VALIDATED-flag-for-externally-v.patch b/patches-sonic/0003-neighbor-Add-NTF_EXT_VALIDATED-flag-for-externally-v.patch deleted file mode 100644 index 7b67d2fa9..000000000 --- a/patches-sonic/0003-neighbor-Add-NTF_EXT_VALIDATED-flag-for-externally-v.patch +++ /dev/null @@ -1,384 +0,0 @@ -From fe8cb206bf32a211cdf0675a1a7d3bf484ab660e Mon Sep 17 00:00:00 2001 -From: "Ido Schimmel" -Date: Fri, 13 Feb 2026 19:51:25 -0800 -Subject: [PATCH] neighbor: Add NTF_EXT_VALIDATED flag for externally validated - entries - -tl;dr -===== - -Add a new neighbor flag ("extern_valid") that can be used to indicate to -the kernel that a neighbor entry was learned and determined to be valid -externally. The kernel will not try to remove or invalidate such an -entry, leaving these decisions to the user space control plane. This is -needed for EVPN multi-homing where a neighbor entry for a multi-homed -host needs to be synced across all the VTEPs among which the host is -multi-homed. - -Background -========== - -In a typical EVPN multi-homing setup each host is multi-homed using a -set of links called ES (Ethernet Segment, i.e., LAG) to multiple leaf -switches (VTEPs). VTEPs that are connected to the same ES are called ES -peers. - -When a neighbor entry is learned on a VTEP, it is distributed to both ES -peers and remote VTEPs using EVPN MAC/IP advertisement routes. ES peers -use the neighbor entry when routing traffic towards the multi-homed host -and remote VTEPs use it for ARP/NS suppression. - -Motivation -========== - -If the ES link between a host and the VTEP on which the neighbor entry -was locally learned goes down, the EVPN MAC/IP advertisement route will -be withdrawn and the neighbor entries will be removed from both ES peers -and remote VTEPs. Routing towards the multi-homed host and ARP/NS -suppression can fail until another ES peer locally learns the neighbor -entry and distributes it via an EVPN MAC/IP advertisement route. - -"draft-rbickhart-evpn-ip-mac-proxy-adv-03" [1] suggests avoiding these -intermittent failures by having the ES peers install the neighbor -entries as before, but also injecting EVPN MAC/IP advertisement routes -with a proxy indication. When the previously mentioned ES link goes down -and the original EVPN MAC/IP advertisement route is withdrawn, the ES -peers will not withdraw their neighbor entries, but instead start aging -timers for the proxy indication. - -If an ES peer locally learns the neighbor entry (i.e., it becomes -"reachable"), it will restart its aging timer for the entry and emit an -EVPN MAC/IP advertisement route without a proxy indication. An ES peer -will stop its aging timer for the proxy indication if it observes the -removal of the proxy indication from at least one of the ES peers -advertising the entry. - -In the event that the aging timer for the proxy indication expired, an -ES peer will withdraw its EVPN MAC/IP advertisement route. If the timer -expired on all ES peers and they all withdrew their proxy -advertisements, the neighbor entry will be completely removed from the -EVPN fabric. - -Implementation -============== - -In the above scheme, when the control plane (e.g., FRR) advertises a -neighbor entry with a proxy indication, it expects the corresponding -entry in the data plane (i.e., the kernel) to remain valid and not be -removed due to garbage collection or loss of carrier. The control plane -also expects the kernel to notify it if the entry was learned locally -(i.e., became "reachable") so that it will remove the proxy indication -from the EVPN MAC/IP advertisement route. That is why these entries -cannot be programmed with dummy states such as "permanent" or "noarp". - -Instead, add a new neighbor flag ("extern_valid") which indicates that -the entry was learned and determined to be valid externally and should -not be removed or invalidated by the kernel. The kernel can probe the -entry and notify user space when it becomes "reachable" (it is initially -installed as "stale"). However, if the kernel does not receive a -confirmation, have it return the entry to the "stale" state instead of -the "failed" state. - -In other words, an entry marked with the "extern_valid" flag behaves -like any other dynamically learned entry other than the fact that the -kernel cannot remove or invalidate it. - -One can argue that the "extern_valid" flag should not prevent garbage -collection and that instead a neighbor entry should be programmed with -both the "extern_valid" and "extern_learn" flags. There are two reasons -for not doing that: - -1. Unclear why a control plane would like to program an entry that the - kernel cannot invalidate but can completely remove. - -2. The "extern_learn" flag is used by FRR for neighbor entries learned - on remote VTEPs (for ARP/NS suppression) whereas here we are - concerned with local entries. This distinction is currently irrelevant - for the kernel, but might be relevant in the future. - -Given that the flag only makes sense when the neighbor has a valid -state, reject attempts to add a neighbor with an invalid state and with -this flag set. For example: - - # ip neigh add 192.0.2.1 nud none dev br0.10 extern_valid - Error: Cannot create externally validated neighbor with an invalid state. - # ip neigh add 192.0.2.1 lladdr 00:11:22:33:44:55 nud stale dev br0.10 extern_valid - # ip neigh replace 192.0.2.1 nud failed dev br0.10 extern_valid - Error: Cannot mark neighbor as externally validated with an invalid state. - -The above means that a neighbor cannot be created with the -"extern_valid" flag and flags such as "use" or "managed" as they result -in a neighbor being created with an invalid state ("none") and -immediately getting probed: - - # ip neigh add 192.0.2.1 lladdr 00:11:22:33:44:55 nud stale dev br0.10 extern_valid use - Error: Cannot create externally validated neighbor with an invalid state. - -However, these flags can be used together with "extern_valid" after the -neighbor was created with a valid state: - - # ip neigh add 192.0.2.1 lladdr 00:11:22:33:44:55 nud stale dev br0.10 extern_valid - # ip neigh replace 192.0.2.1 lladdr 00:11:22:33:44:55 nud stale dev br0.10 extern_valid use - -One consequence of preventing the kernel from invalidating a neighbor -entry is that by default it will only try to determine reachability -using unicast probes. This can be changed using the "mcast_resolicit" -sysctl: - - # sysctl net.ipv4.neigh.br0/10.mcast_resolicit - 0 - # tcpdump -nn -e -i br0.10 -Q out arp & - # ip neigh replace 192.0.2.1 lladdr 00:11:22:33:44:55 nud stale dev br0.10 extern_valid use - 62:50:1d:11:93:6f > 00:11:22:33:44:55, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 - 62:50:1d:11:93:6f > 00:11:22:33:44:55, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 - 62:50:1d:11:93:6f > 00:11:22:33:44:55, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 - # sysctl -wq net.ipv4.neigh.br0/10.mcast_resolicit=3 - # ip neigh replace 192.0.2.1 lladdr 00:11:22:33:44:55 nud stale dev br0.10 extern_valid use - 62:50:1d:11:93:6f > 00:11:22:33:44:55, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 - 62:50:1d:11:93:6f > 00:11:22:33:44:55, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 - 62:50:1d:11:93:6f > 00:11:22:33:44:55, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 - 62:50:1d:11:93:6f > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 - 62:50:1d:11:93:6f > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 - 62:50:1d:11:93:6f > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 - -iproute2 patches can be found here [2]. - -[1] https://datatracker.ietf.org/doc/html/draft-rbickhart-evpn-ip-mac-proxy-adv-03 -[2] https://github.com/idosch/iproute2/tree/submit/extern_valid_v1 - -Signed-off-by: Ido Schimmel -Acked-by: Daniel Borkmann -Link: https://patch.msgid.link/20250626073111.244534-2-idosch@nvidia.com -Signed-off-by: Jakub Kicinski ---- - include/net/neighbour.h | 5 ++- - include/uapi/linux/neighbour.h | 5 +++ - net/core/neighbour.c | 84 ++++++++++++++++++++++++++++++++++++------ - 3 files changed, 82 insertions(+), 12 deletions(-) - -diff --git a/include/net/neighbour.h b/include/net/neighbour.h -index 5804014..2082bd3 100644 ---- a/include/net/neighbour.h -+++ b/include/net/neighbour.h -@@ -267,14 +267,17 @@ static inline void *neighbour_priv(const struct neighbour *n) - #define NEIGH_UPDATE_F_ISROUTER BIT(6) - #define NEIGH_UPDATE_F_ADMIN BIT(7) - #define NEIGH_UPDATE_F_EXT_MH_PEER_SYNC BIT(8) -+#define NEIGH_UPDATE_F_EXT_VALIDATED BIT(9) - - /* In-kernel representation for NDA_FLAGS_EXT flags: */ - #define NTF_OLD_MASK 0xff - #define NTF_EXT_SHIFT 8 --#define NTF_EXT_MASK (NTF_EXT_MANAGED | NTF_EXT_MH_PEER_SYNC) -+#define NTF_EXT_MASK \ -+ (NTF_EXT_MANAGED | NTF_EXT_MH_PEER_SYNC | NTF_EXT_EXT_VALIDATED) - - #define NTF_MANAGED (NTF_EXT_MANAGED << NTF_EXT_SHIFT) - #define NTF_MH_PEER_SYNC (NTF_EXT_MH_PEER_SYNC << NTF_EXT_SHIFT) -+#define NTF_EXT_VALIDATED (NTF_EXT_EXT_VALIDATED << NTF_EXT_SHIFT) - - extern const struct nla_policy nda_policy[]; - -diff --git a/include/uapi/linux/neighbour.h b/include/uapi/linux/neighbour.h -index 12412eb..afaece4 100644 ---- a/include/uapi/linux/neighbour.h -+++ b/include/uapi/linux/neighbour.h -@@ -54,6 +54,7 @@ enum { - /* Extended flags under NDA_FLAGS_EXT: */ - #define NTF_EXT_MANAGED (1 << 0) - #define NTF_EXT_LOCKED (1 << 1) -+#define NTF_EXT_EXT_VALIDATED (1 << 2) - #define NTF_EXT_MH_PEER_SYNC (1 << 3) - - /* -@@ -93,6 +94,10 @@ enum { - * bridge in response to a host trying to communicate via a locked bridge port - * with MAB enabled. Their purpose is to notify user space that a host requires - * authentication. -+ * -+ * NTF_EXT_EXT_VALIDATED flagged neighbor entries were externally validated by -+ * a user space control plane. The kernel will not remove or invalidate them, -+ * but it can probe them and notify user space when they become reachable. - */ - - struct nda_cacheinfo { -diff --git a/net/core/neighbour.c b/net/core/neighbour.c -index 019aec2..888a46f 100644 ---- a/net/core/neighbour.c -+++ b/net/core/neighbour.c -@@ -135,11 +135,12 @@ static void neigh_update_gc_list(struct neighbour *n) - if (n->dead) - goto out; - -- /* remove from the gc list if new state is permanent or if neighbor -- * is externally learned; otherwise entry should be on the gc list -+ /* remove from the gc list if new state is permanent or if neighbor is -+ * externally learned / validated; otherwise entry should be on the gc -+ * list - */ - exempt_from_gc = n->nud_state & NUD_PERMANENT || -- n->flags & NTF_EXT_LEARNED; -+ n->flags & (NTF_EXT_LEARNED | NTF_EXT_VALIDATED); - on_gc_list = !list_empty(&n->gc_list); - - if (exempt_from_gc && on_gc_list) { -@@ -189,6 +190,8 @@ static void neigh_update_flags(struct neighbour *neigh, u32 flags, int *notify, - ndm_flags |= (flags & NEIGH_UPDATE_F_EXT_MH_PEER_SYNC) ? - NTF_MH_PEER_SYNC : - 0; -+ ndm_flags |= -+ (flags & NEIGH_UPDATE_F_EXT_VALIDATED) ? NTF_EXT_VALIDATED : 0; - - if ((old_flags ^ ndm_flags) & NTF_EXT_LEARNED) { - if (ndm_flags & NTF_EXT_LEARNED) -@@ -214,6 +217,14 @@ static void neigh_update_flags(struct neighbour *neigh, u32 flags, int *notify, - neigh->flags &= ~NTF_MH_PEER_SYNC; - *notify = 1; - } -+ if ((old_flags ^ ndm_flags) & NTF_EXT_VALIDATED) { -+ if (ndm_flags & NTF_EXT_VALIDATED) -+ neigh->flags |= NTF_EXT_VALIDATED; -+ else -+ neigh->flags &= ~NTF_EXT_VALIDATED; -+ *notify = 1; -+ *gc_update = true; -+ } - } - - static bool neigh_del(struct neighbour *n, struct neighbour __rcu **np, -@@ -407,7 +418,8 @@ static void neigh_flush_dev(struct neigh_table *tbl, struct net_device *dev, - np = &n->next; - continue; - } -- if (skip_perm && n->nud_state & NUD_PERMANENT) { -+ if (skip_perm && (n->nud_state & NUD_PERMANENT || -+ n->flags & NTF_EXT_VALIDATED)) { - np = &n->next; - continue; - } -@@ -997,7 +1009,8 @@ static void neigh_periodic_work(struct work_struct *work) - - state = n->nud_state; - if ((state & (NUD_PERMANENT | NUD_IN_TIMER)) || -- (n->flags & NTF_EXT_LEARNED)) { -+ (n->flags & -+ (NTF_EXT_LEARNED | NTF_EXT_VALIDATED))) { - write_unlock(&n->lock); - goto next_elt; - } -@@ -1154,9 +1167,15 @@ static void neigh_timer_handler(struct timer_list *t) - - if ((neigh->nud_state & (NUD_INCOMPLETE | NUD_PROBE)) && - atomic_read(&neigh->probes) >= neigh_max_probes(neigh)) { -- WRITE_ONCE(neigh->nud_state, NUD_FAILED); -+ if (neigh->nud_state == NUD_PROBE && -+ neigh->flags & NTF_EXT_VALIDATED) { -+ WRITE_ONCE(neigh->nud_state, NUD_STALE); -+ neigh->updated = jiffies; -+ } else { -+ WRITE_ONCE(neigh->nud_state, NUD_FAILED); -+ neigh_invalidate(neigh); -+ } - notify = 1; -- neigh_invalidate(neigh); - goto out; - } - -@@ -1304,6 +1323,8 @@ static void neigh_update_hhs(struct neighbour *neigh) - NTF_ROUTER flag. - NEIGH_UPDATE_F_ISROUTER indicates if the neighbour is known as - a router. -+ NEIGH_UPDATE_F_EXT_VALIDATED means that the entry will not be removed -+ or invalidated. - - Caller MUST hold reference count on the entry. - */ -@@ -2043,7 +2064,7 @@ static int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, - if (ndm_flags & NTF_PROXY) { - struct pneigh_entry *pn; - -- if (ndm_flags & NTF_MANAGED) { -+ if (ndm_flags & (NTF_MANAGED | NTF_EXT_VALIDATED)) { - NL_SET_ERR_MSG(extack, "Invalid NTF_* flag combination"); - goto out; - } -@@ -2072,8 +2093,9 @@ static int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, - neigh = neigh_lookup(tbl, dst, dev); - if (neigh == NULL) { - bool ndm_permanent = ndm->ndm_state & NUD_PERMANENT; -- bool exempt_from_gc = ndm_permanent || -- ndm_flags & NTF_EXT_LEARNED; -+ bool exempt_from_gc = -+ ndm_permanent || -+ ndm_flags & (NTF_EXT_LEARNED | NTF_EXT_VALIDATED); - - if (!(nlh->nlmsg_flags & NLM_F_CREATE)) { - err = -ENOENT; -@@ -2084,10 +2106,28 @@ static int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, - err = -EINVAL; - goto out; - } -+ if (ndm_flags & NTF_EXT_VALIDATED) { -+ u8 state = ndm->ndm_state; -+ -+ /* NTF_USE and NTF_MANAGED will result in the neighbor -+ * being created with an invalid state (NUD_NONE). -+ */ -+ if (ndm_flags & (NTF_USE | NTF_MANAGED)) -+ state = NUD_NONE; -+ -+ if (!(state & NUD_VALID)) { -+ NL_SET_ERR_MSG( -+ extack, -+ "Cannot create externally validated neighbor with an invalid state"); -+ err = -EINVAL; -+ goto out; -+ } -+ } - - neigh = ___neigh_create(tbl, dst, dev, - ndm_flags & -- (NTF_EXT_LEARNED | NTF_MANAGED), -+ (NTF_EXT_LEARNED | NTF_MANAGED | -+ NTF_EXT_VALIDATED), - exempt_from_gc, true); - if (IS_ERR(neigh)) { - err = PTR_ERR(neigh); -@@ -2099,6 +2139,26 @@ static int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, - neigh_release(neigh); - goto out; - } -+ if (ndm_flags & NTF_EXT_VALIDATED) { -+ u8 state = ndm->ndm_state; -+ -+ /* NTF_USE and NTF_MANAGED do not update the existing -+ * state other than clearing it if it was -+ * NUD_PERMANENT. -+ */ -+ if (ndm_flags & (NTF_USE | NTF_MANAGED)) -+ state = READ_ONCE(neigh->nud_state) & -+ ~NUD_PERMANENT; -+ -+ if (!(state & NUD_VALID)) { -+ NL_SET_ERR_MSG( -+ extack, -+ "Cannot mark neighbor as externally validated with an invalid state"); -+ err = -EINVAL; -+ neigh_release(neigh); -+ goto out; -+ } -+ } - - if (!(nlh->nlmsg_flags & NLM_F_REPLACE)) - flags &= ~(NEIGH_UPDATE_F_OVERRIDE | -@@ -2117,6 +2177,8 @@ static int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, - flags |= NEIGH_UPDATE_F_USE; - if (ndm_flags & NTF_MH_PEER_SYNC) - flags |= NEIGH_UPDATE_F_EXT_MH_PEER_SYNC; -+ if (ndm_flags & NTF_EXT_VALIDATED) -+ flags |= NEIGH_UPDATE_F_EXT_VALIDATED; - - err = __neigh_update(neigh, lladdr, ndm->ndm_state, flags, - NETLINK_CB(skb).portid, extack); --- -2.34.1 diff --git a/patches-sonic/series b/patches-sonic/series index 5d45cd015..f7e1d4259 100644 --- a/patches-sonic/series +++ b/patches-sonic/series @@ -280,8 +280,6 @@ qsa-2026-apparmor/0011-apparmor-fix-race-between-freeing-data-and-fs-access.patc # Patches for EVPN MH 0001-vxlan-bridge-Add-NDA_FLAGS_EXT-support-with-NTF_EXT_.patch 0002-net-bridge-vxlan-Protocol-field-in-bridge-fdb.patch -# Drop 0003 when kernel >= 6.12 (merged upstream: torvalds/linux 03dc03fa0432) -0003-neighbor-Add-NTF_EXT_VALIDATED-flag-for-externally-v.patch ############################################################ # From a575e64094127ae195e7a872ae5c6e446d4f283e Mon Sep 17 00:00:00 2001 From: Tamer Ahmed Date: Wed, 26 Aug 2026 18:21:05 -0700 Subject: [PATCH 2/3] [patches-sonic]: Drop the bridge FDB protocol field patch NDA_PROTOCOL on a bridge FDB entry is not upstream, and nothing needs it any more. Where a MAC came from now travels over the FPM channel, so zebra no longer has to read an origin tag back off the kernel entry, and it no longer writes one. fdbsyncd still spells "proto hw" on its kernel path, but it probes for support at startup and drops the tag when the kernel does not accept it, so removing this patch degrades that path rather than breaking it. The NDA_FLAGS_EXT patch stays: it carries NTF_EXT_MH_PEER_SYNC for EVPN multihoming, which is a separate feature and is not upstream either. Signed-off-by: Tamer Ahmed --- ...e-vxlan-Protocol-field-in-bridge-fdb.patch | 618 ------------------ patches-sonic/series | 1 - 2 files changed, 619 deletions(-) delete mode 100644 patches-sonic/0002-net-bridge-vxlan-Protocol-field-in-bridge-fdb.patch diff --git a/patches-sonic/0002-net-bridge-vxlan-Protocol-field-in-bridge-fdb.patch b/patches-sonic/0002-net-bridge-vxlan-Protocol-field-in-bridge-fdb.patch deleted file mode 100644 index 22bfe9744..000000000 --- a/patches-sonic/0002-net-bridge-vxlan-Protocol-field-in-bridge-fdb.patch +++ /dev/null @@ -1,618 +0,0 @@ -From: Mrinmoy Ghosh -Date: Sat, 16 Aug 2025 03:11:45 +0000 -Subject: [PATCH 2/3] net: bridge: vxlan: Protocol field in bridge fdb - -Add an optional "protocol" field for bridge FDB and VXLAN FDB entries to -distinguish between control plane and data plane learned MAC addresses. - -In EVPN Multihoming, MAC addresses can be learned via: -- Control plane (ZEBRA protocol): Static MACs distributed by FRR/BGP -- Data plane (HW/KERNEL protocol): Dynamic MACs learned from traffic - -Implementation: -- New field: protocol in net_bridge_fdb_entry and vxlan_fdb structures -- Protocol values: standard routing protocol values (RTPROT_*) -- NDA_PROTOCOL attribute encoded in netlink messages for FDB entries -- br_fdb_add: parse NDA_PROTOCOL from netlink add path -- fdb_add_entry: propagate protocol for non-extern_learn path -- vxlan_fdb_update_existing: guard against RTPROT_UNSPEC overwriting - previously-set protocol (prevents vxlan_snoop from clearing - control-plane-set protocol) -- br_switchdev_event: correct argument order for protocol parameter - -Signed-off-by: Mrinmoy Ghosh -Signed-off-by: Tamer Ahmed ---- - drivers/net/vxlan/vxlan_core.c | 108 +++++++++++++++++++++--------------- - drivers/net/vxlan/vxlan_private.h | 7 ++- - drivers/net/vxlan/vxlan_vnifilter.c | 11 ++-- - net/bridge/br.c | 2 +- - net/bridge/br_fdb.c | 70 +++++++++++++++++------ - net/bridge/br_private.h | 4 +- - 6 files changed, 128 insertions(+), 74 deletions(-) - -diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c -index ffb0b61..7e08ae3 100644 ---- a/drivers/net/vxlan/vxlan_core.c -+++ b/drivers/net/vxlan/vxlan_core.c -@@ -200,6 +200,8 @@ static int vxlan_fdb_info(struct sk_buff *skb, struct vxlan_dev *vxlan, - peernet2id(dev_net(vxlan->dev), vxlan->net))) - goto nla_put_failure; - -+ if (nla_put_u8(skb, NDA_PROTOCOL, fdb->protocol)) -+ goto nla_put_failure; - if (send_eth && nla_put(skb, NDA_LLADDR, ETH_ALEN, &fdb->eth_addr)) - goto nla_put_failure; - if (nh) { -@@ -250,6 +252,7 @@ static inline size_t vxlan_nlmsg_size(void) - { - return NLMSG_ALIGN(sizeof(struct ndmsg)) - + nla_total_size(ETH_ALEN) /* NDA_LLADDR */ -+ + nla_total_size(sizeof(__u8)) /* NDA_PROTOCOL */ - + nla_total_size(sizeof(struct in6_addr)) /* NDA_DST */ - + nla_total_size(sizeof(__be16)) /* NDA_PORT */ - + nla_total_size(sizeof(__be32)) /* NDA_VNI */ -@@ -890,7 +893,7 @@ int vxlan_fdb_create(struct vxlan_dev *vxlan, - const u8 *mac, union vxlan_addr *ip, - __u16 state, __be16 port, __be32 src_vni, - __be32 vni, __u32 ifindex, __u16 ndm_flags, -- u32 nhid, struct vxlan_fdb **fdb, -+ u32 nhid, u8 protocol, struct vxlan_fdb **fdb, - struct netlink_ext_ack *extack, u32 ext_flags) - { - struct vxlan_rdst *rd = NULL; -@@ -906,6 +909,7 @@ int vxlan_fdb_create(struct vxlan_dev *vxlan, - if (!f) - return -ENOMEM; - -+ f->protocol = protocol; - if (nhid) - rc = vxlan_fdb_nh_update(vxlan, f, nhid, extack); - else -@@ -985,7 +989,7 @@ static int vxlan_fdb_update_existing(struct vxlan_dev *vxlan, - __be16 port, __be32 vni, - __u32 ifindex, __u16 ndm_flags, - struct vxlan_fdb *f, u32 nhid, -- bool swdev_notify, -+ u8 protocol, bool swdev_notify, - struct netlink_ext_ack *extack, u32 ext_flags) - { - __u16 fdb_flags = (ndm_flags & ~NTF_USE); -@@ -1025,6 +1029,12 @@ static int vxlan_fdb_update_existing(struct vxlan_dev *vxlan, - } - } - -+ if (protocol != RTPROT_UNSPEC && f->protocol != protocol) { -+ f->protocol = protocol; -+ WRITE_ONCE(f->updated, jiffies); -+ notify = 1; -+ } -+ - if ((old_ext_flags ^ ext_flags) & NTF_EXT_MH_PEER_SYNC) { - notify = 1; - if (ext_flags & NTF_EXT_MH_PEER_SYNC) -@@ -1093,7 +1103,7 @@ static int vxlan_fdb_update_create(struct vxlan_dev *vxlan, - __u16 state, __u16 flags, - __be16 port, __be32 src_vni, __be32 vni, - __u32 ifindex, __u16 ndm_flags, u32 nhid, -- bool swdev_notify, -+ u8 protocol, bool swdev_notify, - struct netlink_ext_ack *extack, u32 ext_flags) - { - __u16 fdb_flags = (ndm_flags & ~NTF_USE); -@@ -1107,8 +1117,8 @@ static int vxlan_fdb_update_create(struct vxlan_dev *vxlan, - - netdev_dbg(vxlan->dev, "add %pM -> %pIS\n", mac, ip); - rc = vxlan_fdb_create(vxlan, mac, ip, state, port, src_vni, -- vni, ifindex, fdb_flags, nhid, &f, extack, -- ext_flags); -+ vni, ifindex, fdb_flags, nhid, protocol, &f, -+ extack, ext_flags); - if (rc < 0) - return rc; - -@@ -1131,8 +1141,8 @@ int vxlan_fdb_update(struct vxlan_dev *vxlan, - __u16 state, __u16 flags, - __be16 port, __be32 src_vni, __be32 vni, - __u32 ifindex, __u16 ndm_flags, u32 nhid, -- bool swdev_notify, struct netlink_ext_ack *extack, -- u32 ext_flags) -+ u8 protocol, bool swdev_notify, -+ struct netlink_ext_ack *extack, u32 ext_flags) - { - struct vxlan_fdb *f; - -@@ -1146,15 +1156,16 @@ int vxlan_fdb_update(struct vxlan_dev *vxlan, - - return vxlan_fdb_update_existing(vxlan, ip, state, flags, port, - vni, ifindex, ndm_flags, f, -- nhid, swdev_notify, extack, ext_flags); -+ nhid, protocol, swdev_notify, -+ extack, ext_flags); - } else { - if (!(flags & NLM_F_CREATE)) - return -ENOENT; - - return vxlan_fdb_update_create(vxlan, mac, ip, state, flags, - port, src_vni, vni, ifindex, -- ndm_flags, nhid, swdev_notify, -- extack, ext_flags); -+ ndm_flags, nhid, protocol, -+ swdev_notify, extack, ext_flags); - } - } - -@@ -1168,7 +1179,7 @@ static void vxlan_fdb_dst_destroy(struct vxlan_dev *vxlan, struct vxlan_fdb *f, - - static int vxlan_fdb_parse(struct nlattr *tb[], struct vxlan_dev *vxlan, - union vxlan_addr *ip, __be16 *port, __be32 *src_vni, -- __be32 *vni, u32 *ifindex, u32 *nhid, -+ __be32 *vni, u32 *ifindex, u32 *nhid, u8 *protocol, - struct netlink_ext_ack *extack, u32 *ext_flags) - { - struct net *net = dev_net(vxlan->dev); -@@ -1252,11 +1263,15 @@ static int vxlan_fdb_parse(struct nlattr *tb[], struct vxlan_dev *vxlan, - else - *nhid = 0; - -- if (tb[NDA_FLAGS_EXT]) { -+ if (tb[NDA_FLAGS_EXT] && ext_flags) - *ext_flags = nla_get_u32(tb[NDA_FLAGS_EXT]); -- } else { -+ else if (ext_flags) - *ext_flags = 0; -- } -+ -+ if (tb[NDA_PROTOCOL]) -+ *protocol = nla_get_u8(tb[NDA_PROTOCOL]); -+ else -+ *protocol = RTPROT_UNSPEC; - - return 0; - } -@@ -1275,6 +1290,7 @@ static int vxlan_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], - u32 ifindex, nhid; - u32 hash_index; - u32 ext_flags; -+ u8 protocol; - int err; - - if (!(ndm->ndm_state & (NUD_PERMANENT|NUD_REACHABLE))) { -@@ -1287,7 +1303,7 @@ static int vxlan_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], - return -EINVAL; - - err = vxlan_fdb_parse(tb, vxlan, &ip, &port, &src_vni, &vni, &ifindex, -- &nhid, extack, &ext_flags); -+ &nhid, &protocol, extack, &ext_flags); - if (err) - return err; - -@@ -1299,7 +1315,7 @@ static int vxlan_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], - err = vxlan_fdb_update(vxlan, addr, &ip, ndm->ndm_state, flags, - port, src_vni, vni, ifindex, - ndm->ndm_flags | NTF_VXLAN_ADDED_BY_USER, -- nhid, true, extack, ext_flags); -+ nhid, protocol, true, extack, ext_flags); - spin_unlock_bh(&vxlan->hash_lock[hash_index]); - - return err; -@@ -1347,21 +1363,30 @@ static int vxlan_fdb_delete(struct ndmsg *ndm, struct nlattr *tb[], - struct vxlan_dev *vxlan = netdev_priv(dev); - union vxlan_addr ip; - __be32 src_vni, vni; -+ struct vxlan_fdb *f; - u32 ifindex, nhid; - u32 hash_index; -- u32 ext_flags; - __be16 port; -+ u8 protocol; - int err; - - err = vxlan_fdb_parse(tb, vxlan, &ip, &port, &src_vni, &vni, &ifindex, -- &nhid, extack, &ext_flags); -+ &nhid, &protocol, extack, NULL); - if (err) - return err; - - hash_index = fdb_head_index(vxlan, addr, src_vni); - spin_lock_bh(&vxlan->hash_lock[hash_index]); -+ if (protocol != RTPROT_UNSPEC) { -+ f = vxlan_find_mac(vxlan, addr, src_vni); -+ if (!f || f->protocol != protocol) { -+ err = -ENOENT; -+ goto out; -+ } -+ } - err = __vxlan_fdb_delete(vxlan, addr, ip, port, src_vni, vni, ifindex, - true); -+out: - spin_unlock_bh(&vxlan->hash_lock[hash_index]); - - return err; -@@ -1510,13 +1535,12 @@ static bool vxlan_snoop(struct net_device *dev, - - /* close off race between vxlan_flush and incoming packets */ - if (netif_running(dev)) -- vxlan_fdb_update(vxlan, src_mac, src_ip, -- NUD_REACHABLE, -- NLM_F_EXCL|NLM_F_CREATE, -- vxlan->cfg.dst_port, -- vni, -- vxlan->default_dst.remote_vni, -- ifindex, NTF_SELF, 0, true, NULL, 0); -+ vxlan_fdb_update(vxlan, src_mac, src_ip, NUD_REACHABLE, -+ NLM_F_EXCL | NLM_F_CREATE, -+ vxlan->cfg.dst_port, vni, -+ vxlan->default_dst.remote_vni, ifindex, -+ NTF_SELF, 0, RTPROT_UNSPEC, true, NULL, -+ 0); - spin_unlock(&vxlan->hash_lock[hash_index]); - } - -@@ -3964,14 +3988,12 @@ static int __vxlan_dev_create(struct net *net, struct net_device *dev, - - /* create an fdb entry for a valid default destination */ - if (!vxlan_addr_any(&dst->remote_ip)) { -- err = vxlan_fdb_create(vxlan, all_zeros_mac, -- &dst->remote_ip, -+ err = vxlan_fdb_create(vxlan, all_zeros_mac, &dst->remote_ip, - NUD_REACHABLE | NUD_PERMANENT, -- vxlan->cfg.dst_port, -- dst->remote_vni, -- dst->remote_vni, -- dst->remote_ifindex, -- NTF_SELF, 0, &f, extack, 0); -+ vxlan->cfg.dst_port, dst->remote_vni, -+ dst->remote_vni, dst->remote_ifindex, -+ NTF_SELF, 0, RTPROT_UNSPEC, &f, extack, -+ 0); - if (err) - return err; - } -@@ -4402,10 +4424,10 @@ static int vxlan_changelink(struct net_device *dev, struct nlattr *tb[], - &conf.remote_ip, - NUD_REACHABLE | NUD_PERMANENT, - NLM_F_APPEND | NLM_F_CREATE, -- vxlan->cfg.dst_port, -- conf.vni, conf.vni, -- conf.remote_ifindex, -- NTF_SELF, 0, true, extack, 0); -+ vxlan->cfg.dst_port, conf.vni, -+ conf.vni, conf.remote_ifindex, -+ NTF_SELF, 0, RTPROT_UNSPEC, true, -+ extack, 0); - if (err) { - spin_unlock_bh(&vxlan->hash_lock[hash_index]); - netdev_adjacent_change_abort(dst->remote_dev, -@@ -4754,14 +4777,11 @@ vxlan_fdb_external_learn_add(struct net_device *dev, - - spin_lock_bh(&vxlan->hash_lock[hash_index]); - err = vxlan_fdb_update(vxlan, fdb_info->eth_addr, &fdb_info->remote_ip, -- NUD_REACHABLE, -- NLM_F_CREATE | NLM_F_REPLACE, -- fdb_info->remote_port, -- fdb_info->vni, -- fdb_info->remote_vni, -- fdb_info->remote_ifindex, -- NTF_USE | NTF_SELF | NTF_EXT_LEARNED, -- 0, false, extack, 0); -+ NUD_REACHABLE, NLM_F_CREATE | NLM_F_REPLACE, -+ fdb_info->remote_port, fdb_info->vni, -+ fdb_info->remote_vni, fdb_info->remote_ifindex, -+ NTF_USE | NTF_SELF | NTF_EXT_LEARNED, 0, -+ RTPROT_UNSPEC, false, extack, 0); - spin_unlock_bh(&vxlan->hash_lock[hash_index]); - - return err; -diff --git a/drivers/net/vxlan/vxlan_private.h b/drivers/net/vxlan/vxlan_private.h -index e0878e0..c3cc3fa 100644 ---- a/drivers/net/vxlan/vxlan_private.h -+++ b/drivers/net/vxlan/vxlan_private.h -@@ -36,6 +36,7 @@ struct vxlan_fdb { - __be32 vni; - u16 flags; /* see ndm_flags and below */ - u32 ext_flags; -+ u8 protocol; - struct list_head nh_list; - struct nexthop __rcu *nh; - struct vxlan_dev __rcu *vdev; -@@ -180,7 +181,7 @@ int vxlan_fdb_create(struct vxlan_dev *vxlan, - const u8 *mac, union vxlan_addr *ip, - __u16 state, __be16 port, __be32 src_vni, - __be32 vni, __u32 ifindex, __u16 ndm_flags, -- u32 nhid, struct vxlan_fdb **fdb, -+ u32 nhid, u8 protocol, struct vxlan_fdb **fdb, - struct netlink_ext_ack *extack, u32 ext_flags); - int __vxlan_fdb_delete(struct vxlan_dev *vxlan, - const unsigned char *addr, union vxlan_addr ip, -@@ -193,8 +194,8 @@ int vxlan_fdb_update(struct vxlan_dev *vxlan, - __u16 state, __u16 flags, - __be16 port, __be32 src_vni, __be32 vni, - __u32 ifindex, __u16 ndm_flags, u32 nhid, -- bool swdev_notify, struct netlink_ext_ack *extack, -- u32 ext_flags); -+ u8 protocol, bool swdev_notify, -+ struct netlink_ext_ack *extack, u32 ext_flags); - void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev, - __be32 default_vni, struct vxlan_rdst *rdst, bool did_rsc); - int vxlan_vni_in_use(struct net *src_net, struct vxlan_dev *vxlan, -diff --git a/drivers/net/vxlan/vxlan_vnifilter.c b/drivers/net/vxlan/vxlan_vnifilter.c -index af38b5b..f8396be 100644 ---- a/drivers/net/vxlan/vxlan_vnifilter.c -+++ b/drivers/net/vxlan/vxlan_vnifilter.c -@@ -489,15 +489,12 @@ static int vxlan_update_default_fdb_entry(struct vxlan_dev *vxlan, __be32 vni, - hash_index = fdb_head_index(vxlan, all_zeros_mac, vni); - spin_lock_bh(&vxlan->hash_lock[hash_index]); - if (remote_ip && !vxlan_addr_any(remote_ip)) { -- err = vxlan_fdb_update(vxlan, all_zeros_mac, -- remote_ip, -+ err = vxlan_fdb_update(vxlan, all_zeros_mac, remote_ip, - NUD_REACHABLE | NUD_PERMANENT, - NLM_F_APPEND | NLM_F_CREATE, -- vxlan->cfg.dst_port, -- vni, -- vni, -- dst->remote_ifindex, -- NTF_SELF, 0, true, extack, 0); -+ vxlan->cfg.dst_port, vni, vni, -+ dst->remote_ifindex, NTF_SELF, 0, -+ RTPROT_UNSPEC, true, extack, 0); - if (err) { - spin_unlock_bh(&vxlan->hash_lock[hash_index]); - return err; -diff --git a/net/bridge/br.c b/net/bridge/br.c -index 72b3b54..94ea448 100644 ---- a/net/bridge/br.c -+++ b/net/bridge/br.c -@@ -166,7 +166,7 @@ static int br_switchdev_event(struct notifier_block *unused, - case SWITCHDEV_FDB_ADD_TO_BRIDGE: - fdb_info = ptr; - err = br_fdb_external_learn_add(br, p, fdb_info->addr, -- fdb_info->vid, -+ fdb_info->vid, RTPROT_UNSPEC, - fdb_info->locked, false, 0); - if (err) { - err = notifier_from_errno(err); -diff --git a/net/bridge/br_fdb.c b/net/bridge/br_fdb.c -index 202d791..71c1ada 100644 ---- a/net/bridge/br_fdb.c -+++ b/net/bridge/br_fdb.c -@@ -132,6 +132,8 @@ static int fdb_fill_info(struct sk_buff *skb, const struct net_bridge *br, - goto nla_put_failure; - if (nla_put_u32(skb, NDA_MASTER, br->dev->ifindex)) - goto nla_put_failure; -+ if (nla_put_u8(skb, NDA_PROTOCOL, fdb->protocol)) -+ goto nla_put_failure; - if (nla_put_u32(skb, NDA_FLAGS_EXT, ext_flags)) - goto nla_put_failure; - -@@ -176,6 +178,7 @@ static inline size_t fdb_nlmsg_size(void) - return NLMSG_ALIGN(sizeof(struct ndmsg)) - + nla_total_size(ETH_ALEN) /* NDA_LLADDR */ - + nla_total_size(sizeof(u32)) /* NDA_MASTER */ -+ + nla_total_size(sizeof(u8)) /* NDA_PROTOCOL */ - + nla_total_size(sizeof(u32)) /* NDA_FLAGS_EXT */ - + nla_total_size(sizeof(u16)) /* NDA_VLAN */ - + nla_total_size(sizeof(struct nda_cacheinfo)) -@@ -1066,7 +1069,7 @@ static bool fdb_handle_notify(struct net_bridge_fdb_entry *fdb, u8 notify) - /* Update (create or replace) forwarding database entry */ - static int fdb_add_entry(struct net_bridge *br, struct net_bridge_port *source, - const u8 *addr, struct ndmsg *ndm, u16 flags, u16 vid, -- struct nlattr *nfea_tb[]) -+ u8 protocol, struct nlattr *nfea_tb[]) - { - bool is_sticky = !!(ndm->ndm_flags & NTF_STICKY); - bool refresh = !nfea_tb[NFEA_DONT_REFRESH]; -@@ -1122,6 +1125,11 @@ static int fdb_add_entry(struct net_bridge *br, struct net_bridge_port *source, - atomic_dec(&br->fdb_n_learned); - } - -+ if (fdb->protocol != protocol) { -+ fdb->protocol = protocol; -+ modified = true; -+ } -+ - if (fdb_to_nud(br, fdb) != state) { - if (state & NUD_PERMANENT) { - set_bit(BR_FDB_LOCAL, &fdb->flags); -@@ -1163,7 +1171,8 @@ static int fdb_add_entry(struct net_bridge *br, struct net_bridge_port *source, - - static int __br_fdb_add(struct ndmsg *ndm, struct net_bridge *br, - struct net_bridge_port *p, const unsigned char *addr, -- u16 nlh_flags, u16 vid, struct nlattr *nfea_tb[], -+ u16 nlh_flags, u16 vid, u8 protocol, -+ struct nlattr *nfea_tb[], - struct netlink_ext_ack *extack, u32 ext_flags) - { - int err = 0; -@@ -1188,11 +1197,12 @@ static int __br_fdb_add(struct ndmsg *ndm, struct net_bridge *br, - "FDB entry towards bridge must be permanent"); - return -EINVAL; - } -- err = br_fdb_external_learn_add(br, p, addr, vid, false, true, -- ext_flags); -+ err = br_fdb_external_learn_add(br, p, addr, vid, protocol, -+ false, true, ext_flags); - } else { - spin_lock_bh(&br->hash_lock); -- err = fdb_add_entry(br, p, addr, ndm, nlh_flags, vid, nfea_tb); -+ err = fdb_add_entry(br, p, addr, ndm, nlh_flags, vid, -+ protocol, nfea_tb); - spin_unlock_bh(&br->hash_lock); - } - -@@ -1215,6 +1225,7 @@ int br_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], - struct net_bridge_port *p = NULL; - struct net_bridge_vlan *v; - struct net_bridge *br = NULL; -+ u8 protocol = RTPROT_UNSPEC; - u32 ext_flags = 0; - int err = 0; - -@@ -1247,6 +1258,9 @@ int br_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], - if (tb[NDA_FLAGS_EXT]) - ext_flags = nla_get_u32(tb[NDA_FLAGS_EXT]); - -+ if (tb[NDA_PROTOCOL]) -+ protocol = nla_get_u8(tb[NDA_PROTOCOL]); -+ - if (ext_flags & NTF_EXT_LOCKED) { - NL_SET_ERR_MSG_MOD(extack, "Cannot add FDB entry with \"locked\" flag set"); - return -EINVAL; -@@ -1273,11 +1287,11 @@ int br_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], - } - - /* VID was specified, so use it. */ -- err = __br_fdb_add(ndm, br, p, addr, nlh_flags, vid, nfea_tb, -- extack, ext_flags); -+ err = __br_fdb_add(ndm, br, p, addr, nlh_flags, vid, protocol, -+ nfea_tb, extack, ext_flags); - } else { -- err = __br_fdb_add(ndm, br, p, addr, nlh_flags, 0, nfea_tb, -- extack, ext_flags); -+ err = __br_fdb_add(ndm, br, p, addr, nlh_flags, 0, protocol, -+ nfea_tb, extack, ext_flags); - if (err || !vg || !vg->num_vlans) - goto out; - -@@ -1289,7 +1303,8 @@ int br_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], - if (!br_vlan_should_use(v)) - continue; - err = __br_fdb_add(ndm, br, p, addr, nlh_flags, v->vid, -- nfea_tb, extack, ext_flags); -+ protocol, nfea_tb, extack, -+ ext_flags); - if (err) - goto out; - } -@@ -1301,7 +1316,7 @@ out: - - static int fdb_delete_by_addr_and_port(struct net_bridge *br, - const struct net_bridge_port *p, -- const u8 *addr, u16 vlan) -+ const u8 *addr, u16 vlan, u8 protocol) - { - struct net_bridge_fdb_entry *fdb; - -@@ -1309,6 +1324,14 @@ static int fdb_delete_by_addr_and_port(struct net_bridge *br, - if (!fdb || READ_ONCE(fdb->dst) != p) - return -ENOENT; - -+ /* If a protocol was provided, delete only matching entries so -+ * independent control-plane and data-plane FDB entries can -+ * coexist without a delete for one protocol removing the other. -+ */ -+ if (protocol != RTPROT_UNSPEC && -+ fdb->protocol != protocol) -+ return -ENOENT; -+ - fdb_delete(br, fdb, true); - - return 0; -@@ -1316,12 +1339,12 @@ static int fdb_delete_by_addr_and_port(struct net_bridge *br, - - static int __br_fdb_delete(struct net_bridge *br, - const struct net_bridge_port *p, -- const unsigned char *addr, u16 vid) -+ const unsigned char *addr, u16 vid, u8 protocol) - { - int err; - - spin_lock_bh(&br->hash_lock); -- err = fdb_delete_by_addr_and_port(br, p, addr, vid); -+ err = fdb_delete_by_addr_and_port(br, p, addr, vid, protocol); - spin_unlock_bh(&br->hash_lock); - - return err; -@@ -1335,10 +1358,14 @@ int br_fdb_delete(struct ndmsg *ndm, struct nlattr *tb[], - { - struct net_bridge_vlan_group *vg; - struct net_bridge_port *p = NULL; -+ u8 protocol = RTPROT_UNSPEC; - struct net_bridge_vlan *v; - struct net_bridge *br; - int err; - -+ if (tb[NDA_PROTOCOL]) -+ protocol = nla_get_u8(tb[NDA_PROTOCOL]); -+ - if (netif_is_bridge_master(dev)) { - br = netdev_priv(dev); - vg = br_vlan_group(br); -@@ -1360,17 +1387,17 @@ int br_fdb_delete(struct ndmsg *ndm, struct nlattr *tb[], - return -EINVAL; - } - -- err = __br_fdb_delete(br, p, addr, vid); -+ err = __br_fdb_delete(br, p, addr, vid, protocol); - } else { - err = -ENOENT; -- err &= __br_fdb_delete(br, p, addr, 0); -+ err &= __br_fdb_delete(br, p, addr, 0, protocol); - if (!vg || !vg->num_vlans) - return err; - - list_for_each_entry(v, &vg->vlan_list, vlist) { - if (!br_vlan_should_use(v)) - continue; -- err &= __br_fdb_delete(br, p, addr, v->vid); -+ err &= __br_fdb_delete(br, p, addr, v->vid, protocol); - } - } - -@@ -1430,8 +1457,8 @@ void br_fdb_unsync_static(struct net_bridge *br, struct net_bridge_port *p) - } - - int br_fdb_external_learn_add(struct net_bridge *br, struct net_bridge_port *p, -- const unsigned char *addr, u16 vid, bool locked, -- bool swdev_notify, u32 ext_flags) -+ const unsigned char *addr, u16 vid, u8 protocol, -+ bool locked, bool swdev_notify, u32 ext_flags) - { - struct net_bridge_fdb_entry *fdb; - bool modified = false; -@@ -1465,6 +1492,7 @@ int br_fdb_external_learn_add(struct net_bridge *br, struct net_bridge_port *p, - err = -ENOMEM; - goto err_unlock; - } -+ fdb->protocol = protocol; - fdb_notify(br, fdb, RTM_NEWNEIGH, swdev_notify); - } else { - if (locked && -@@ -1512,6 +1540,12 @@ int br_fdb_external_learn_add(struct net_bridge *br, struct net_bridge_port *p, - test_and_clear_bit(BR_FDB_DYNAMIC_LEARNED, &fdb->flags)) - atomic_dec(&br->fdb_n_learned); - -+ if (protocol != RTPROT_UNSPEC && -+ fdb->protocol != protocol) { -+ modified = true; -+ fdb->protocol = protocol; -+ } -+ - if (modified) - fdb_notify(br, fdb, RTM_NEWNEIGH, swdev_notify); - } -diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h -index bb431db..022b7b4 100644 ---- a/net/bridge/br_private.h -+++ b/net/bridge/br_private.h -@@ -292,6 +292,7 @@ struct net_bridge_fdb_entry { - struct net_bridge_fdb_key key; - struct hlist_node fdb_node; - unsigned long flags; -+ u8 protocol; - - /* write-heavy members should not affect lookups */ - unsigned long updated ____cacheline_aligned_in_smp; -@@ -870,7 +871,8 @@ int br_fdb_sync_static(struct net_bridge *br, struct net_bridge_port *p); - void br_fdb_unsync_static(struct net_bridge *br, struct net_bridge_port *p); - int br_fdb_external_learn_add(struct net_bridge *br, struct net_bridge_port *p, - const unsigned char *addr, u16 vid, -- bool locked, bool swdev_notify, u32 ext_flags); -+ u8 protocol, bool locked, bool swdev_notify, -+ u32 ext_flags); - int br_fdb_external_learn_del(struct net_bridge *br, struct net_bridge_port *p, - const unsigned char *addr, u16 vid, - bool swdev_notify); --- -2.34.1 diff --git a/patches-sonic/series b/patches-sonic/series index f7e1d4259..eacc71e6d 100644 --- a/patches-sonic/series +++ b/patches-sonic/series @@ -279,7 +279,6 @@ qsa-2026-apparmor/0010-apparmor-fix-race-on-rawdata-dereference.patch qsa-2026-apparmor/0011-apparmor-fix-race-between-freeing-data-and-fs-access.patch # Patches for EVPN MH 0001-vxlan-bridge-Add-NDA_FLAGS_EXT-support-with-NTF_EXT_.patch -0002-net-bridge-vxlan-Protocol-field-in-bridge-fdb.patch ############################################################ # From a5adc528388d5aa2d85282e73a6c4a364bd59459 Mon Sep 17 00:00:00 2001 From: Tamer Ahmed Date: Mon, 14 Sep 2026 09:23:05 -0700 Subject: [PATCH 3/3] Revert "[patches-sonic]: Drop the unused NTF_EXT_VALIDATED neighbour patch" This reverts commit 2ff74294aac326dec97b9b261c685b6247768e24. The patch was not unused. The original assessment searched for the symbol NTF_EXT_VALIDATED, found no references in FRR, and concluded the patch was dead. These flags couple by bit value inside a netlink policy mask, not by symbol name. zebra sends NTF_E_MH_PEER_SYNC, which FRR defines as 0x04. In the kernel uapi that bit is NTF_EXT_EXT_VALIDATED (1 << 2); NTF_EXT_MH_PEER_SYNC is (1 << 3). Removing this patch narrowed NTF_EXT_MASK from 0x0D to 0x09, and nda_policy[NDA_FLAGS_EXT] is NLA_POLICY_MASK(NLA_U32, NTF_EXT_MASK), which rejects out-of-mask bits with -EINVAL. Every RTM_NEWNEIGH zebra sends for a peer-synced neighbour is therefore refused, the entry is never refreshed and ages to NUD_FAILED. The visible effect is that EVPN multi-homing failover breaks: when an Ethernet Segment link goes down, traffic does not move to the peer leaf. Reported by Cisco against test_portchannel_shut_noshut. Signed-off-by: Tamer Ahmed --- ..._EXT_VALIDATED-flag-for-externally-v.patch | 384 ++++++++++++++++++ patches-sonic/series | 4 + 2 files changed, 388 insertions(+) create mode 100644 patches-sonic/0003-neighbor-Add-NTF_EXT_VALIDATED-flag-for-externally-v.patch diff --git a/patches-sonic/0003-neighbor-Add-NTF_EXT_VALIDATED-flag-for-externally-v.patch b/patches-sonic/0003-neighbor-Add-NTF_EXT_VALIDATED-flag-for-externally-v.patch new file mode 100644 index 000000000..7b67d2fa9 --- /dev/null +++ b/patches-sonic/0003-neighbor-Add-NTF_EXT_VALIDATED-flag-for-externally-v.patch @@ -0,0 +1,384 @@ +From fe8cb206bf32a211cdf0675a1a7d3bf484ab660e Mon Sep 17 00:00:00 2001 +From: "Ido Schimmel" +Date: Fri, 13 Feb 2026 19:51:25 -0800 +Subject: [PATCH] neighbor: Add NTF_EXT_VALIDATED flag for externally validated + entries + +tl;dr +===== + +Add a new neighbor flag ("extern_valid") that can be used to indicate to +the kernel that a neighbor entry was learned and determined to be valid +externally. The kernel will not try to remove or invalidate such an +entry, leaving these decisions to the user space control plane. This is +needed for EVPN multi-homing where a neighbor entry for a multi-homed +host needs to be synced across all the VTEPs among which the host is +multi-homed. + +Background +========== + +In a typical EVPN multi-homing setup each host is multi-homed using a +set of links called ES (Ethernet Segment, i.e., LAG) to multiple leaf +switches (VTEPs). VTEPs that are connected to the same ES are called ES +peers. + +When a neighbor entry is learned on a VTEP, it is distributed to both ES +peers and remote VTEPs using EVPN MAC/IP advertisement routes. ES peers +use the neighbor entry when routing traffic towards the multi-homed host +and remote VTEPs use it for ARP/NS suppression. + +Motivation +========== + +If the ES link between a host and the VTEP on which the neighbor entry +was locally learned goes down, the EVPN MAC/IP advertisement route will +be withdrawn and the neighbor entries will be removed from both ES peers +and remote VTEPs. Routing towards the multi-homed host and ARP/NS +suppression can fail until another ES peer locally learns the neighbor +entry and distributes it via an EVPN MAC/IP advertisement route. + +"draft-rbickhart-evpn-ip-mac-proxy-adv-03" [1] suggests avoiding these +intermittent failures by having the ES peers install the neighbor +entries as before, but also injecting EVPN MAC/IP advertisement routes +with a proxy indication. When the previously mentioned ES link goes down +and the original EVPN MAC/IP advertisement route is withdrawn, the ES +peers will not withdraw their neighbor entries, but instead start aging +timers for the proxy indication. + +If an ES peer locally learns the neighbor entry (i.e., it becomes +"reachable"), it will restart its aging timer for the entry and emit an +EVPN MAC/IP advertisement route without a proxy indication. An ES peer +will stop its aging timer for the proxy indication if it observes the +removal of the proxy indication from at least one of the ES peers +advertising the entry. + +In the event that the aging timer for the proxy indication expired, an +ES peer will withdraw its EVPN MAC/IP advertisement route. If the timer +expired on all ES peers and they all withdrew their proxy +advertisements, the neighbor entry will be completely removed from the +EVPN fabric. + +Implementation +============== + +In the above scheme, when the control plane (e.g., FRR) advertises a +neighbor entry with a proxy indication, it expects the corresponding +entry in the data plane (i.e., the kernel) to remain valid and not be +removed due to garbage collection or loss of carrier. The control plane +also expects the kernel to notify it if the entry was learned locally +(i.e., became "reachable") so that it will remove the proxy indication +from the EVPN MAC/IP advertisement route. That is why these entries +cannot be programmed with dummy states such as "permanent" or "noarp". + +Instead, add a new neighbor flag ("extern_valid") which indicates that +the entry was learned and determined to be valid externally and should +not be removed or invalidated by the kernel. The kernel can probe the +entry and notify user space when it becomes "reachable" (it is initially +installed as "stale"). However, if the kernel does not receive a +confirmation, have it return the entry to the "stale" state instead of +the "failed" state. + +In other words, an entry marked with the "extern_valid" flag behaves +like any other dynamically learned entry other than the fact that the +kernel cannot remove or invalidate it. + +One can argue that the "extern_valid" flag should not prevent garbage +collection and that instead a neighbor entry should be programmed with +both the "extern_valid" and "extern_learn" flags. There are two reasons +for not doing that: + +1. Unclear why a control plane would like to program an entry that the + kernel cannot invalidate but can completely remove. + +2. The "extern_learn" flag is used by FRR for neighbor entries learned + on remote VTEPs (for ARP/NS suppression) whereas here we are + concerned with local entries. This distinction is currently irrelevant + for the kernel, but might be relevant in the future. + +Given that the flag only makes sense when the neighbor has a valid +state, reject attempts to add a neighbor with an invalid state and with +this flag set. For example: + + # ip neigh add 192.0.2.1 nud none dev br0.10 extern_valid + Error: Cannot create externally validated neighbor with an invalid state. + # ip neigh add 192.0.2.1 lladdr 00:11:22:33:44:55 nud stale dev br0.10 extern_valid + # ip neigh replace 192.0.2.1 nud failed dev br0.10 extern_valid + Error: Cannot mark neighbor as externally validated with an invalid state. + +The above means that a neighbor cannot be created with the +"extern_valid" flag and flags such as "use" or "managed" as they result +in a neighbor being created with an invalid state ("none") and +immediately getting probed: + + # ip neigh add 192.0.2.1 lladdr 00:11:22:33:44:55 nud stale dev br0.10 extern_valid use + Error: Cannot create externally validated neighbor with an invalid state. + +However, these flags can be used together with "extern_valid" after the +neighbor was created with a valid state: + + # ip neigh add 192.0.2.1 lladdr 00:11:22:33:44:55 nud stale dev br0.10 extern_valid + # ip neigh replace 192.0.2.1 lladdr 00:11:22:33:44:55 nud stale dev br0.10 extern_valid use + +One consequence of preventing the kernel from invalidating a neighbor +entry is that by default it will only try to determine reachability +using unicast probes. This can be changed using the "mcast_resolicit" +sysctl: + + # sysctl net.ipv4.neigh.br0/10.mcast_resolicit + 0 + # tcpdump -nn -e -i br0.10 -Q out arp & + # ip neigh replace 192.0.2.1 lladdr 00:11:22:33:44:55 nud stale dev br0.10 extern_valid use + 62:50:1d:11:93:6f > 00:11:22:33:44:55, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 + 62:50:1d:11:93:6f > 00:11:22:33:44:55, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 + 62:50:1d:11:93:6f > 00:11:22:33:44:55, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 + # sysctl -wq net.ipv4.neigh.br0/10.mcast_resolicit=3 + # ip neigh replace 192.0.2.1 lladdr 00:11:22:33:44:55 nud stale dev br0.10 extern_valid use + 62:50:1d:11:93:6f > 00:11:22:33:44:55, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 + 62:50:1d:11:93:6f > 00:11:22:33:44:55, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 + 62:50:1d:11:93:6f > 00:11:22:33:44:55, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 + 62:50:1d:11:93:6f > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 + 62:50:1d:11:93:6f > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 + 62:50:1d:11:93:6f > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.1 tell 192.0.2.2, length 28 + +iproute2 patches can be found here [2]. + +[1] https://datatracker.ietf.org/doc/html/draft-rbickhart-evpn-ip-mac-proxy-adv-03 +[2] https://github.com/idosch/iproute2/tree/submit/extern_valid_v1 + +Signed-off-by: Ido Schimmel +Acked-by: Daniel Borkmann +Link: https://patch.msgid.link/20250626073111.244534-2-idosch@nvidia.com +Signed-off-by: Jakub Kicinski +--- + include/net/neighbour.h | 5 ++- + include/uapi/linux/neighbour.h | 5 +++ + net/core/neighbour.c | 84 ++++++++++++++++++++++++++++++++++++------ + 3 files changed, 82 insertions(+), 12 deletions(-) + +diff --git a/include/net/neighbour.h b/include/net/neighbour.h +index 5804014..2082bd3 100644 +--- a/include/net/neighbour.h ++++ b/include/net/neighbour.h +@@ -267,14 +267,17 @@ static inline void *neighbour_priv(const struct neighbour *n) + #define NEIGH_UPDATE_F_ISROUTER BIT(6) + #define NEIGH_UPDATE_F_ADMIN BIT(7) + #define NEIGH_UPDATE_F_EXT_MH_PEER_SYNC BIT(8) ++#define NEIGH_UPDATE_F_EXT_VALIDATED BIT(9) + + /* In-kernel representation for NDA_FLAGS_EXT flags: */ + #define NTF_OLD_MASK 0xff + #define NTF_EXT_SHIFT 8 +-#define NTF_EXT_MASK (NTF_EXT_MANAGED | NTF_EXT_MH_PEER_SYNC) ++#define NTF_EXT_MASK \ ++ (NTF_EXT_MANAGED | NTF_EXT_MH_PEER_SYNC | NTF_EXT_EXT_VALIDATED) + + #define NTF_MANAGED (NTF_EXT_MANAGED << NTF_EXT_SHIFT) + #define NTF_MH_PEER_SYNC (NTF_EXT_MH_PEER_SYNC << NTF_EXT_SHIFT) ++#define NTF_EXT_VALIDATED (NTF_EXT_EXT_VALIDATED << NTF_EXT_SHIFT) + + extern const struct nla_policy nda_policy[]; + +diff --git a/include/uapi/linux/neighbour.h b/include/uapi/linux/neighbour.h +index 12412eb..afaece4 100644 +--- a/include/uapi/linux/neighbour.h ++++ b/include/uapi/linux/neighbour.h +@@ -54,6 +54,7 @@ enum { + /* Extended flags under NDA_FLAGS_EXT: */ + #define NTF_EXT_MANAGED (1 << 0) + #define NTF_EXT_LOCKED (1 << 1) ++#define NTF_EXT_EXT_VALIDATED (1 << 2) + #define NTF_EXT_MH_PEER_SYNC (1 << 3) + + /* +@@ -93,6 +94,10 @@ enum { + * bridge in response to a host trying to communicate via a locked bridge port + * with MAB enabled. Their purpose is to notify user space that a host requires + * authentication. ++ * ++ * NTF_EXT_EXT_VALIDATED flagged neighbor entries were externally validated by ++ * a user space control plane. The kernel will not remove or invalidate them, ++ * but it can probe them and notify user space when they become reachable. + */ + + struct nda_cacheinfo { +diff --git a/net/core/neighbour.c b/net/core/neighbour.c +index 019aec2..888a46f 100644 +--- a/net/core/neighbour.c ++++ b/net/core/neighbour.c +@@ -135,11 +135,12 @@ static void neigh_update_gc_list(struct neighbour *n) + if (n->dead) + goto out; + +- /* remove from the gc list if new state is permanent or if neighbor +- * is externally learned; otherwise entry should be on the gc list ++ /* remove from the gc list if new state is permanent or if neighbor is ++ * externally learned / validated; otherwise entry should be on the gc ++ * list + */ + exempt_from_gc = n->nud_state & NUD_PERMANENT || +- n->flags & NTF_EXT_LEARNED; ++ n->flags & (NTF_EXT_LEARNED | NTF_EXT_VALIDATED); + on_gc_list = !list_empty(&n->gc_list); + + if (exempt_from_gc && on_gc_list) { +@@ -189,6 +190,8 @@ static void neigh_update_flags(struct neighbour *neigh, u32 flags, int *notify, + ndm_flags |= (flags & NEIGH_UPDATE_F_EXT_MH_PEER_SYNC) ? + NTF_MH_PEER_SYNC : + 0; ++ ndm_flags |= ++ (flags & NEIGH_UPDATE_F_EXT_VALIDATED) ? NTF_EXT_VALIDATED : 0; + + if ((old_flags ^ ndm_flags) & NTF_EXT_LEARNED) { + if (ndm_flags & NTF_EXT_LEARNED) +@@ -214,6 +217,14 @@ static void neigh_update_flags(struct neighbour *neigh, u32 flags, int *notify, + neigh->flags &= ~NTF_MH_PEER_SYNC; + *notify = 1; + } ++ if ((old_flags ^ ndm_flags) & NTF_EXT_VALIDATED) { ++ if (ndm_flags & NTF_EXT_VALIDATED) ++ neigh->flags |= NTF_EXT_VALIDATED; ++ else ++ neigh->flags &= ~NTF_EXT_VALIDATED; ++ *notify = 1; ++ *gc_update = true; ++ } + } + + static bool neigh_del(struct neighbour *n, struct neighbour __rcu **np, +@@ -407,7 +418,8 @@ static void neigh_flush_dev(struct neigh_table *tbl, struct net_device *dev, + np = &n->next; + continue; + } +- if (skip_perm && n->nud_state & NUD_PERMANENT) { ++ if (skip_perm && (n->nud_state & NUD_PERMANENT || ++ n->flags & NTF_EXT_VALIDATED)) { + np = &n->next; + continue; + } +@@ -997,7 +1009,8 @@ static void neigh_periodic_work(struct work_struct *work) + + state = n->nud_state; + if ((state & (NUD_PERMANENT | NUD_IN_TIMER)) || +- (n->flags & NTF_EXT_LEARNED)) { ++ (n->flags & ++ (NTF_EXT_LEARNED | NTF_EXT_VALIDATED))) { + write_unlock(&n->lock); + goto next_elt; + } +@@ -1154,9 +1167,15 @@ static void neigh_timer_handler(struct timer_list *t) + + if ((neigh->nud_state & (NUD_INCOMPLETE | NUD_PROBE)) && + atomic_read(&neigh->probes) >= neigh_max_probes(neigh)) { +- WRITE_ONCE(neigh->nud_state, NUD_FAILED); ++ if (neigh->nud_state == NUD_PROBE && ++ neigh->flags & NTF_EXT_VALIDATED) { ++ WRITE_ONCE(neigh->nud_state, NUD_STALE); ++ neigh->updated = jiffies; ++ } else { ++ WRITE_ONCE(neigh->nud_state, NUD_FAILED); ++ neigh_invalidate(neigh); ++ } + notify = 1; +- neigh_invalidate(neigh); + goto out; + } + +@@ -1304,6 +1323,8 @@ static void neigh_update_hhs(struct neighbour *neigh) + NTF_ROUTER flag. + NEIGH_UPDATE_F_ISROUTER indicates if the neighbour is known as + a router. ++ NEIGH_UPDATE_F_EXT_VALIDATED means that the entry will not be removed ++ or invalidated. + + Caller MUST hold reference count on the entry. + */ +@@ -2043,7 +2064,7 @@ static int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, + if (ndm_flags & NTF_PROXY) { + struct pneigh_entry *pn; + +- if (ndm_flags & NTF_MANAGED) { ++ if (ndm_flags & (NTF_MANAGED | NTF_EXT_VALIDATED)) { + NL_SET_ERR_MSG(extack, "Invalid NTF_* flag combination"); + goto out; + } +@@ -2072,8 +2093,9 @@ static int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, + neigh = neigh_lookup(tbl, dst, dev); + if (neigh == NULL) { + bool ndm_permanent = ndm->ndm_state & NUD_PERMANENT; +- bool exempt_from_gc = ndm_permanent || +- ndm_flags & NTF_EXT_LEARNED; ++ bool exempt_from_gc = ++ ndm_permanent || ++ ndm_flags & (NTF_EXT_LEARNED | NTF_EXT_VALIDATED); + + if (!(nlh->nlmsg_flags & NLM_F_CREATE)) { + err = -ENOENT; +@@ -2084,10 +2106,28 @@ static int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, + err = -EINVAL; + goto out; + } ++ if (ndm_flags & NTF_EXT_VALIDATED) { ++ u8 state = ndm->ndm_state; ++ ++ /* NTF_USE and NTF_MANAGED will result in the neighbor ++ * being created with an invalid state (NUD_NONE). ++ */ ++ if (ndm_flags & (NTF_USE | NTF_MANAGED)) ++ state = NUD_NONE; ++ ++ if (!(state & NUD_VALID)) { ++ NL_SET_ERR_MSG( ++ extack, ++ "Cannot create externally validated neighbor with an invalid state"); ++ err = -EINVAL; ++ goto out; ++ } ++ } + + neigh = ___neigh_create(tbl, dst, dev, + ndm_flags & +- (NTF_EXT_LEARNED | NTF_MANAGED), ++ (NTF_EXT_LEARNED | NTF_MANAGED | ++ NTF_EXT_VALIDATED), + exempt_from_gc, true); + if (IS_ERR(neigh)) { + err = PTR_ERR(neigh); +@@ -2099,6 +2139,26 @@ static int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, + neigh_release(neigh); + goto out; + } ++ if (ndm_flags & NTF_EXT_VALIDATED) { ++ u8 state = ndm->ndm_state; ++ ++ /* NTF_USE and NTF_MANAGED do not update the existing ++ * state other than clearing it if it was ++ * NUD_PERMANENT. ++ */ ++ if (ndm_flags & (NTF_USE | NTF_MANAGED)) ++ state = READ_ONCE(neigh->nud_state) & ++ ~NUD_PERMANENT; ++ ++ if (!(state & NUD_VALID)) { ++ NL_SET_ERR_MSG( ++ extack, ++ "Cannot mark neighbor as externally validated with an invalid state"); ++ err = -EINVAL; ++ neigh_release(neigh); ++ goto out; ++ } ++ } + + if (!(nlh->nlmsg_flags & NLM_F_REPLACE)) + flags &= ~(NEIGH_UPDATE_F_OVERRIDE | +@@ -2117,6 +2177,8 @@ static int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, + flags |= NEIGH_UPDATE_F_USE; + if (ndm_flags & NTF_MH_PEER_SYNC) + flags |= NEIGH_UPDATE_F_EXT_MH_PEER_SYNC; ++ if (ndm_flags & NTF_EXT_VALIDATED) ++ flags |= NEIGH_UPDATE_F_EXT_VALIDATED; + + err = __neigh_update(neigh, lladdr, ndm->ndm_state, flags, + NETLINK_CB(skb).portid, extack); +-- +2.34.1 diff --git a/patches-sonic/series b/patches-sonic/series index eacc71e6d..4448ed659 100644 --- a/patches-sonic/series +++ b/patches-sonic/series @@ -279,6 +279,10 @@ qsa-2026-apparmor/0010-apparmor-fix-race-on-rawdata-dereference.patch qsa-2026-apparmor/0011-apparmor-fix-race-between-freeing-data-and-fs-access.patch # Patches for EVPN MH 0001-vxlan-bridge-Add-NDA_FLAGS_EXT-support-with-NTF_EXT_.patch +# Do NOT drop 0003 while on 6.12: NTF_EXT_EXT_VALIDATED is absent from the +# 6.12.41 uapi, and zebra sends that bit as NTF_E_MH_PEER_SYNC (0x04). Without it +# NTF_EXT_MASK excludes 0x04 and nda_policy rejects RTM_NEWNEIGH with -EINVAL. +0003-neighbor-Add-NTF_EXT_VALIDATED-flag-for-externally-v.patch ############################################################ #