diff --git a/patches-sonic/0001-vxlan-bridge-Add-NDA_FLAGS_EXT-support-with-NTF_EXT_.patch b/patches-sonic/0001-vxlan-bridge-Add-NDA_FLAGS_EXT-support-with-NTF_EXT_.patch new file mode 100644 index 000000000..d3d42f27f --- /dev/null +++ b/patches-sonic/0001-vxlan-bridge-Add-NDA_FLAGS_EXT-support-with-NTF_EXT_.patch @@ -0,0 +1,632 @@ +From ef2205ef9ff492a8b83dc0ff2727f941c3385e2b Mon Sep 17 00:00:00 2001 +From: "Mike RE Mallin" +Date: Fri, 13 Feb 2026 14:47:17 -0800 +Subject: [PATCH] vxlan/bridge: Add NDA_FLAGS_EXT support with + NTF_EXT_MH_PEER_SYNC + +Add support for NDA_FLAGS_EXT attribute in VXLAN and bridge FDB entries +to support multi-homing peer synchronization. + +This patch adds: +- ext_flags field to vxlan_fdb structure +- NTF_EXT_MH_PEER_SYNC flag and associated infrastructure +- Propagation of ext_flags through vxlan_fdb_create/update/parse +- NEIGH_UPDATE_F_EXT_MH_PEER_SYNC for neighbor updates +--- + drivers/net/vxlan/vxlan_core.c | 140 +++++++++++++++------------- + drivers/net/vxlan/vxlan_private.h | 21 ++--- + drivers/net/vxlan/vxlan_vnifilter.c | 11 +-- + include/net/neighbour.h | 4 +- + include/uapi/linux/neighbour.h | 1 + + net/bridge/br.c | 4 +- + net/bridge/br_fdb.c | 35 +++++-- + net/bridge/br_private.h | 5 +- + net/core/neighbour.c | 13 +++ + 9 files changed, 138 insertions(+), 96 deletions(-) + +diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c +index 1a7077093..de1b3fa96 100644 +--- a/drivers/net/vxlan/vxlan_core.c ++++ b/drivers/net/vxlan/vxlan_core.c +@@ -227,6 +227,9 @@ static int vxlan_fdb_info(struct sk_buff *skb, struct vxlan_dev *vxlan, + be32_to_cpu(fdb->vni))) + goto nla_put_failure; + ++ if (fdb->ext_flags && nla_put_u32(skb, NDA_FLAGS_EXT, fdb->ext_flags)) ++ goto nla_put_failure; ++ + ci.ndm_used = jiffies_to_clock_t(now - READ_ONCE(fdb->used)); + ci.ndm_confirmed = 0; + ci.ndm_updated = jiffies_to_clock_t(now - READ_ONCE(fdb->updated)); +@@ -791,7 +794,7 @@ static int vxlan_gpe_gro_complete(struct sock *sk, struct sk_buff *skb, int nhof + + static struct vxlan_fdb *vxlan_fdb_alloc(struct vxlan_dev *vxlan, const u8 *mac, + __u16 state, __be32 src_vni, +- __u16 ndm_flags) ++ __u16 ndm_flags, __u32 ext_flags) + { + struct vxlan_fdb *f; + +@@ -803,6 +806,7 @@ static struct vxlan_fdb *vxlan_fdb_alloc(struct vxlan_dev *vxlan, const u8 *mac, + f->updated = f->used = jiffies; + f->vni = src_vni; + f->nh = NULL; ++ f->ext_flags = ext_flags; + RCU_INIT_POINTER(f->vdev, vxlan); + INIT_LIST_HEAD(&f->nh_list); + INIT_LIST_HEAD(&f->remotes); +@@ -881,12 +885,11 @@ static int vxlan_fdb_nh_update(struct vxlan_dev *vxlan, struct vxlan_fdb *fdb, + return err; + } + +-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, ++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, +- struct netlink_ext_ack *extack) ++ struct netlink_ext_ack *extack, u32 ext_flags) + { + struct vxlan_rdst *rd = NULL; + struct vxlan_fdb *f; +@@ -897,7 +900,7 @@ int vxlan_fdb_create(struct vxlan_dev *vxlan, + return -ENOSPC; + + netdev_dbg(vxlan->dev, "add %pM -> %pIS\n", mac, ip); +- f = vxlan_fdb_alloc(vxlan, mac, state, src_vni, ndm_flags); ++ f = vxlan_fdb_alloc(vxlan, mac, state, src_vni, ndm_flags, ext_flags); + if (!f) + return -ENOMEM; + +@@ -974,14 +977,12 @@ static void vxlan_dst_free(struct rcu_head *head) + kfree(rd); + } + +-static int vxlan_fdb_update_existing(struct vxlan_dev *vxlan, +- union vxlan_addr *ip, +- __u16 state, __u16 flags, +- __be16 port, __be32 vni, +- __u32 ifindex, __u16 ndm_flags, +- struct vxlan_fdb *f, u32 nhid, +- bool swdev_notify, +- struct netlink_ext_ack *extack) ++static int ++vxlan_fdb_update_existing(struct vxlan_dev *vxlan, union vxlan_addr *ip, ++ __u16 state, __u16 flags, __be16 port, __be32 vni, ++ __u32 ifindex, __u16 ndm_flags, struct vxlan_fdb *f, ++ u32 nhid, bool swdev_notify, ++ struct netlink_ext_ack *extack, u32 ext_flags) + { + __u16 fdb_flags = (ndm_flags & ~NTF_USE); + struct vxlan_rdst *rd = NULL; +@@ -989,6 +990,7 @@ static int vxlan_fdb_update_existing(struct vxlan_dev *vxlan, + int notify = 0; + int rc = 0; + int err; ++ u32 old_ext_flags = f->ext_flags; + + if (nhid && !rcu_access_pointer(f->nh)) { + NL_SET_ERR_MSG(extack, +@@ -1019,6 +1021,14 @@ static int vxlan_fdb_update_existing(struct vxlan_dev *vxlan, + } + } + ++ if ((old_ext_flags ^ ext_flags) & NTF_EXT_MH_PEER_SYNC) { ++ notify = 1; ++ if (ext_flags & NTF_EXT_MH_PEER_SYNC) ++ f->ext_flags |= NTF_EXT_MH_PEER_SYNC; ++ else ++ f->ext_flags &= ~NTF_EXT_MH_PEER_SYNC; ++ } ++ + if ((flags & NLM_F_REPLACE)) { + /* Only change unicasts */ + if (!(is_multicast_ether_addr(f->eth_addr) || +@@ -1074,13 +1084,13 @@ static int vxlan_fdb_update_existing(struct vxlan_dev *vxlan, + return err; + } + +-static int vxlan_fdb_update_create(struct vxlan_dev *vxlan, +- const u8 *mac, union vxlan_addr *ip, +- __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) ++static int vxlan_fdb_update_create(struct vxlan_dev *vxlan, const u8 *mac, ++ union vxlan_addr *ip, __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) + { + __u16 fdb_flags = (ndm_flags & ~NTF_USE); + struct vxlan_fdb *f; +@@ -1092,8 +1102,8 @@ static int vxlan_fdb_update_create(struct vxlan_dev *vxlan, + return -EOPNOTSUPP; + + 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); ++ rc = vxlan_fdb_create(vxlan, mac, ip, state, port, src_vni, vni, ++ ifindex, fdb_flags, nhid, &f, extack, ext_flags); + if (rc < 0) + return rc; + +@@ -1111,13 +1121,11 @@ static int vxlan_fdb_update_create(struct vxlan_dev *vxlan, + } + + /* Add new entry to forwarding table -- assumes lock held */ +-int vxlan_fdb_update(struct vxlan_dev *vxlan, +- const u8 *mac, union vxlan_addr *ip, +- __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) ++int vxlan_fdb_update(struct vxlan_dev *vxlan, const u8 *mac, ++ union vxlan_addr *ip, __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) + { + struct vxlan_fdb *f; + +@@ -1131,7 +1139,8 @@ 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); ++ nhid, swdev_notify, extack, ++ ext_flags); + } else { + if (!(flags & NLM_F_CREATE)) + return -ENOENT; +@@ -1139,7 +1148,7 @@ int vxlan_fdb_update(struct vxlan_dev *vxlan, + return vxlan_fdb_update_create(vxlan, mac, ip, state, flags, + port, src_vni, vni, ifindex, + ndm_flags, nhid, swdev_notify, +- extack); ++ extack, ext_flags); + } + } + +@@ -1154,7 +1163,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, +- struct netlink_ext_ack *extack) ++ struct netlink_ext_ack *extack, u32 *ext_flags) + { + struct net *net = dev_net(vxlan->dev); + int err; +@@ -1237,6 +1246,12 @@ static int vxlan_fdb_parse(struct nlattr *tb[], struct vxlan_dev *vxlan, + else + *nhid = 0; + ++ if (tb[NDA_FLAGS_EXT]) { ++ *ext_flags = nla_get_u32(tb[NDA_FLAGS_EXT]); ++ } else { ++ *ext_flags = 0; ++ } ++ + return 0; + } + +@@ -1253,6 +1268,7 @@ static int vxlan_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], + __be32 src_vni, vni; + u32 ifindex, nhid; + u32 hash_index; ++ u32 ext_flags; + int err; + + if (!(ndm->ndm_state & (NUD_PERMANENT|NUD_REACHABLE))) { +@@ -1265,7 +1281,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); ++ &nhid, extack, &ext_flags); + if (err) + return err; + +@@ -1274,10 +1290,10 @@ static int vxlan_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], + + hash_index = fdb_head_index(vxlan, addr, src_vni); + spin_lock_bh(&vxlan->hash_lock[hash_index]); +- 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); ++ 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); + spin_unlock_bh(&vxlan->hash_lock[hash_index]); + + return err; +@@ -1327,11 +1343,12 @@ static int vxlan_fdb_delete(struct ndmsg *ndm, struct nlattr *tb[], + __be32 src_vni, vni; + u32 ifindex, nhid; + u32 hash_index; ++ u32 ext_flags; + __be16 port; + int err; + + err = vxlan_fdb_parse(tb, vxlan, &ip, &port, &src_vni, &vni, &ifindex, +- &nhid, extack); ++ &nhid, extack, &ext_flags); + if (err) + return err; + +@@ -1487,13 +1504,11 @@ 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); ++ 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); + spin_unlock(&vxlan->hash_lock[hash_index]); + } + +@@ -3941,14 +3956,11 @@ 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); ++ vxlan->cfg.dst_port, dst->remote_vni, ++ dst->remote_vni, dst->remote_ifindex, ++ NTF_SELF, 0, &f, extack, 0); + if (err) + return err; + } +@@ -4379,10 +4391,9 @@ 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); ++ vxlan->cfg.dst_port, conf.vni, ++ conf.vni, conf.remote_ifindex, ++ NTF_SELF, 0, true, extack, 0); + if (err) { + spin_unlock_bh(&vxlan->hash_lock[hash_index]); + netdev_adjacent_change_abort(dst->remote_dev, +@@ -4731,14 +4742,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); ++ 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); + 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 76a351a99..7789af637 100644 +--- a/drivers/net/vxlan/vxlan_private.h ++++ b/drivers/net/vxlan/vxlan_private.h +@@ -35,6 +35,7 @@ struct vxlan_fdb { + u16 state; /* see ndm_state */ + __be32 vni; + u16 flags; /* see ndm_flags and below */ ++ u32 ext_flags; + struct list_head nh_list; + struct nexthop __rcu *nh; + struct vxlan_dev __rcu *vdev; +@@ -175,24 +176,22 @@ vxlan_vnifilter_lookup(struct vxlan_dev *vxlan, __be32 vni) + } + + /* vxlan_core.c */ +-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, ++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, +- struct netlink_ext_ack *extack); ++ struct netlink_ext_ack *extack, u32 ext_flags); + int __vxlan_fdb_delete(struct vxlan_dev *vxlan, + const unsigned char *addr, union vxlan_addr ip, + __be16 port, __be32 src_vni, __be32 vni, + u32 ifindex, bool swdev_notify); + u32 eth_vni_hash(const unsigned char *addr, __be32 vni); + u32 fdb_head_index(struct vxlan_dev *vxlan, const u8 *mac, __be32 vni); +-int vxlan_fdb_update(struct vxlan_dev *vxlan, +- const u8 *mac, union vxlan_addr *ip, +- __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); ++int vxlan_fdb_update(struct vxlan_dev *vxlan, const u8 *mac, ++ union vxlan_addr *ip, __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); + 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 06d19e90e..ad1a46242 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); ++ vxlan->cfg.dst_port, vni, vni, ++ dst->remote_ifindex, NTF_SELF, 0, true, ++ extack, 0); + if (err) { + spin_unlock_bh(&vxlan->hash_lock[hash_index]); + return err; +diff --git a/include/net/neighbour.h b/include/net/neighbour.h +index a44f262a7..5c6f52831 100644 +--- a/include/net/neighbour.h ++++ b/include/net/neighbour.h +@@ -266,13 +266,15 @@ static inline void *neighbour_priv(const struct neighbour *n) + #define NEIGH_UPDATE_F_EXT_LEARNED BIT(5) + #define NEIGH_UPDATE_F_ISROUTER BIT(6) + #define NEIGH_UPDATE_F_ADMIN BIT(7) ++#define NEIGH_UPDATE_F_EXT_MH_PEER_SYNC BIT(8) + + /* 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) ++#define NTF_EXT_MASK (NTF_EXT_MANAGED | NTF_EXT_MH_PEER_SYNC) + + #define NTF_MANAGED (NTF_EXT_MANAGED << NTF_EXT_SHIFT) ++#define NTF_MH_PEER_SYNC (NTF_EXT_MH_PEER_SYNC << 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 5e67a7eaf..dce82ed78 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_MH_PEER_SYNC (1 << 2) + + /* + * Neighbor Cache Entry States. +diff --git a/net/bridge/br.c b/net/bridge/br.c +index 2cab878e0..6b8c782f2 100644 +--- a/net/bridge/br.c ++++ b/net/bridge/br.c +@@ -166,8 +166,8 @@ 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->locked, false); ++ fdb_info->vid, fdb_info->locked, ++ false, 0); + if (err) { + err = notifier_from_errno(err); + break; +diff --git a/net/bridge/br_fdb.c b/net/bridge/br_fdb.c +index 642b8ccaa..202d79183 100644 +--- a/net/bridge/br_fdb.c ++++ b/net/bridge/br_fdb.c +@@ -125,6 +125,8 @@ static int fdb_fill_info(struct sk_buff *skb, const struct net_bridge *br, + ndm->ndm_flags |= NTF_STICKY; + if (test_bit(BR_FDB_LOCKED, &fdb->flags)) + ext_flags |= NTF_EXT_LOCKED; ++ if (test_bit(BR_FDB_REMOTE_SYNC, &fdb->flags)) ++ ext_flags |= NTF_EXT_MH_PEER_SYNC; + + if (nla_put(skb, NDA_LLADDR, ETH_ALEN, &fdb->key.addr)) + goto nla_put_failure; +@@ -920,9 +922,12 @@ void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source, + fdb_modified = true; + /* Take over HW learned entry */ + if (unlikely(test_bit(BR_FDB_ADDED_BY_EXT_LEARN, +- &fdb->flags))) ++ &fdb->flags))) { + clear_bit(BR_FDB_ADDED_BY_EXT_LEARN, + &fdb->flags); ++ clear_bit(BR_FDB_REMOTE_SYNC, ++ &fdb->flags); ++ } + /* Clear locked flag when roaming to an + * unlocked port. + */ +@@ -1159,7 +1164,7 @@ 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[], +- struct netlink_ext_ack *extack) ++ struct netlink_ext_ack *extack, u32 ext_flags) + { + int err = 0; + +@@ -1183,7 +1188,8 @@ 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); ++ err = br_fdb_external_learn_add(br, p, addr, vid, false, true, ++ ext_flags); + } else { + spin_lock_bh(&br->hash_lock); + err = fdb_add_entry(br, p, addr, ndm, nlh_flags, vid, nfea_tb); +@@ -1246,6 +1252,9 @@ int br_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], + return -EINVAL; + } + ++ if (tb[NDA_FLAGS_EXT]) ++ ext_flags = nla_get_u32(tb[NDA_FLAGS_EXT]); ++ + if (tb[NDA_FDB_EXT_ATTRS]) { + attr = tb[NDA_FDB_EXT_ATTRS]; + err = nla_parse_nested(nfea_tb, NFEA_MAX, attr, +@@ -1265,10 +1274,10 @@ 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); ++ extack, ext_flags); + } else { + err = __br_fdb_add(ndm, br, p, addr, nlh_flags, 0, nfea_tb, +- extack); ++ extack, ext_flags); + if (err || !vg || !vg->num_vlans) + goto out; + +@@ -1280,7 +1289,7 @@ 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); ++ nfea_tb, extack, ext_flags); + if (err) + goto out; + } +@@ -1422,7 +1431,7 @@ 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) ++ bool swdev_notify, u32 ext_flags) + { + struct net_bridge_fdb_entry *fdb; + bool modified = false; +@@ -1448,6 +1457,9 @@ int br_fdb_external_learn_add(struct net_bridge *br, struct net_bridge_port *p, + if (locked) + flags |= BIT(BR_FDB_LOCKED); + ++ if (ext_flags & NTF_EXT_MH_PEER_SYNC) ++ flags |= BIT(BR_FDB_REMOTE_SYNC); ++ + fdb = fdb_create(br, p, addr, vid, flags); + if (!fdb) { + err = -ENOMEM; +@@ -1481,6 +1493,15 @@ int br_fdb_external_learn_add(struct net_bridge *br, struct net_bridge_port *p, + modified = true; + } + ++ if (test_bit(BR_FDB_REMOTE_SYNC, &fdb->flags) != ++ !!(ext_flags & NTF_EXT_MH_PEER_SYNC)) { ++ modified = true; ++ if (ext_flags & NTF_EXT_MH_PEER_SYNC) ++ set_bit(BR_FDB_REMOTE_SYNC, &fdb->flags); ++ else ++ clear_bit(BR_FDB_REMOTE_SYNC, &fdb->flags); ++ } ++ + if (swdev_notify) + set_bit(BR_FDB_ADDED_BY_USER, &fdb->flags); + +diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h +index 6a1bce895..2376178ec 100644 +--- a/net/bridge/br_private.h ++++ b/net/bridge/br_private.h +@@ -277,6 +277,7 @@ enum { + BR_FDB_NOTIFY_INACTIVE, + BR_FDB_LOCKED, + BR_FDB_DYNAMIC_LEARNED, ++ BR_FDB_REMOTE_SYNC, + }; + + struct net_bridge_fdb_key { +@@ -868,8 +869,8 @@ int br_fdb_get(struct sk_buff *skb, struct nlattr *tb[], struct net_device *dev, + 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); ++ const unsigned char *addr, u16 vid, 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); +diff --git a/net/core/neighbour.c b/net/core/neighbour.c +index 8082cc6be..019aec2ea 100644 +--- a/net/core/neighbour.c ++++ b/net/core/neighbour.c +@@ -186,6 +186,9 @@ static void neigh_update_flags(struct neighbour *neigh, u32 flags, int *notify, + + ndm_flags = (flags & NEIGH_UPDATE_F_EXT_LEARNED) ? NTF_EXT_LEARNED : 0; + ndm_flags |= (flags & NEIGH_UPDATE_F_MANAGED) ? NTF_MANAGED : 0; ++ ndm_flags |= (flags & NEIGH_UPDATE_F_EXT_MH_PEER_SYNC) ? ++ NTF_MH_PEER_SYNC : ++ 0; + + if ((old_flags ^ ndm_flags) & NTF_EXT_LEARNED) { + if (ndm_flags & NTF_EXT_LEARNED) +@@ -203,6 +206,14 @@ static void neigh_update_flags(struct neighbour *neigh, u32 flags, int *notify, + *notify = 1; + *managed_update = true; + } ++ ++ if ((old_flags ^ ndm_flags) & NTF_MH_PEER_SYNC) { ++ if (ndm_flags & NTF_MH_PEER_SYNC) ++ neigh->flags |= NTF_MH_PEER_SYNC; ++ else ++ neigh->flags &= ~NTF_MH_PEER_SYNC; ++ *notify = 1; ++ } + } + + static bool neigh_del(struct neighbour *n, struct neighbour __rcu **np, +@@ -2104,6 +2115,8 @@ static int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, + flags |= NEIGH_UPDATE_F_MANAGED; + if (ndm_flags & NTF_USE) + flags |= NEIGH_UPDATE_F_USE; ++ if (ndm_flags & NTF_MH_PEER_SYNC) ++ flags |= NEIGH_UPDATE_F_EXT_MH_PEER_SYNC; + + err = __neigh_update(neigh, lladdr, ndm->ndm_state, flags, + NETLINK_CB(skb).portid, extack); +-- +2.34.1 + 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 new file mode 100644 index 000000000..78a4ea363 --- /dev/null +++ b/patches-sonic/0002-net-bridge-vxlan-Protocol-field-in-bridge-fdb.patch @@ -0,0 +1,491 @@ +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 +--- +Index: linux-6.12.41/drivers/net/vxlan/vxlan_core.c +=================================================================== +--- linux-6.12.41.orig/drivers/net/vxlan/vxlan_core.c ++++ linux-6.12.41/drivers/net/vxlan/vxlan_core.c +@@ -200,6 +200,8 @@ static int vxlan_fdb_info(struct sk_buff + 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) { +@@ -888,7 +890,7 @@ err_inval: + 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; +@@ -904,6 +906,7 @@ int vxlan_fdb_create(struct vxlan_dev *v + if (!f) + return -ENOMEM; + ++ f->protocol = protocol; + if (nhid) + rc = vxlan_fdb_nh_update(vxlan, f, nhid, extack); + else +@@ -981,7 +984,7 @@ static int + vxlan_fdb_update_existing(struct vxlan_dev *vxlan, union vxlan_addr *ip, + __u16 state, __u16 flags, __be16 port, __be32 vni, + __u32 ifindex, __u16 ndm_flags, struct vxlan_fdb *f, +- u32 nhid, bool swdev_notify, ++ u32 nhid, u8 protocol, bool swdev_notify, + struct netlink_ext_ack *extack, u32 ext_flags) + { + __u16 fdb_flags = (ndm_flags & ~NTF_USE); +@@ -1019,6 +1022,11 @@ vxlan_fdb_update_existing(struct vxlan_d + WRITE_ONCE(f->updated, jiffies); + notify = 1; + } ++ 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) { +@@ -1088,7 +1096,7 @@ static int vxlan_fdb_update_create(struc + union vxlan_addr *ip, __u16 state, + __u16 flags, __be16 port, __be32 src_vni, + __be32 vni, __u32 ifindex, __u16 ndm_flags, +- u32 nhid, bool swdev_notify, ++ u32 nhid, u8 protocol, bool swdev_notify, + struct netlink_ext_ack *extack, + u32 ext_flags) + { +@@ -1103,7 +1111,8 @@ static int vxlan_fdb_update_create(struc + + 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); ++ ifindex, fdb_flags, nhid, protocol, &f, extack, ++ ext_flags); + if (rc < 0) + return rc; + +@@ -1124,7 +1133,7 @@ err_notify: + int vxlan_fdb_update(struct vxlan_dev *vxlan, const u8 *mac, + union vxlan_addr *ip, __u16 state, __u16 flags, + __be16 port, __be32 src_vni, __be32 vni, __u32 ifindex, +- __u16 ndm_flags, u32 nhid, bool swdev_notify, ++ __u16 ndm_flags, u32 nhid, u8 protocol, bool swdev_notify, + struct netlink_ext_ack *extack, u32 ext_flags) + { + struct vxlan_fdb *f; +@@ -1139,16 +1148,16 @@ int vxlan_fdb_update(struct vxlan_dev *v + + 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); + } + } + +@@ -1162,7 +1171,7 @@ static void vxlan_fdb_dst_destroy(struct + + 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,6 +1261,11 @@ static int vxlan_fdb_parse(struct nlattr + *ext_flags = 0; + } + ++ if (tb[NDA_PROTOCOL]) ++ *protocol = nla_get_u8(tb[NDA_PROTOCOL]); ++ else ++ *protocol = RTPROT_UNSPEC; ++ + return 0; + } + +@@ -1269,6 +1283,7 @@ static int vxlan_fdb_add(struct ndmsg *n + u32 ifindex, nhid; + u32 hash_index; + u32 ext_flags; ++ u8 protocol; + int err; + + if (!(ndm->ndm_state & (NUD_PERMANENT|NUD_REACHABLE))) { +@@ -1281,7 +1296,7 @@ static int vxlan_fdb_add(struct ndmsg *n + 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; + +@@ -1293,7 +1308,7 @@ static int vxlan_fdb_add(struct ndmsg *n + 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); ++ protocol, true, extack, ext_flags); + spin_unlock_bh(&vxlan->hash_lock[hash_index]); + + return err; +@@ -1345,10 +1360,11 @@ static int vxlan_fdb_delete(struct ndmsg + 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, &ext_flags); + if (err) + return err; + +@@ -1508,7 +1524,8 @@ static bool vxlan_snoop(struct net_devic + NLM_F_EXCL | NLM_F_CREATE, + vxlan->cfg.dst_port, vni, + vxlan->default_dst.remote_vni, ifindex, +- NTF_SELF, 0, true, NULL, 0); ++ NTF_SELF, 0, RTPROT_UNSPEC, true, NULL, ++ 0); + spin_unlock(&vxlan->hash_lock[hash_index]); + } + +@@ -3960,7 +3977,8 @@ static int __vxlan_dev_create(struct net + NUD_REACHABLE | NUD_PERMANENT, + vxlan->cfg.dst_port, dst->remote_vni, + dst->remote_vni, dst->remote_ifindex, +- NTF_SELF, 0, &f, extack, 0); ++ NTF_SELF, 0, RTPROT_UNSPEC, &f, extack, ++ 0); + if (err) + return err; + } +@@ -4393,7 +4411,8 @@ static int vxlan_changelink(struct net_d + NLM_F_APPEND | NLM_F_CREATE, + vxlan->cfg.dst_port, conf.vni, + conf.vni, conf.remote_ifindex, +- NTF_SELF, 0, true, extack, 0); ++ 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, +@@ -4735,6 +4754,7 @@ vxlan_fdb_external_learn_add(struct net_ + struct vxlan_dev *vxlan = netdev_priv(dev); + struct netlink_ext_ack *extack; + u32 hash_index; ++ u32 ext_flags; + int err; + + hash_index = fdb_head_index(vxlan, fdb_info->eth_addr, fdb_info->vni); +@@ -4745,8 +4765,8 @@ vxlan_fdb_external_learn_add(struct net_ + 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); ++ NTF_USE | NTF_SELF | NTF_EXT_LEARNED, 0, ++ RTPROT_UNSPEC, false, extack, 0); + spin_unlock_bh(&vxlan->hash_lock[hash_index]); + + return err; +Index: linux-6.12.41/drivers/net/vxlan/vxlan_private.h +=================================================================== +--- linux-6.12.41.orig/drivers/net/vxlan/vxlan_private.h ++++ linux-6.12.41/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; +@@ -179,7 +180,7 @@ vxlan_vnifilter_lookup(struct vxlan_dev + 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, +@@ -190,7 +191,7 @@ u32 fdb_head_index(struct vxlan_dev *vxl + int vxlan_fdb_update(struct vxlan_dev *vxlan, const u8 *mac, + union vxlan_addr *ip, __u16 state, __u16 flags, + __be16 port, __be32 src_vni, __be32 vni, __u32 ifindex, +- __u16 ndm_flags, u32 nhid, bool swdev_notify, ++ __u16 ndm_flags, u32 nhid, 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); +Index: linux-6.12.41/drivers/net/vxlan/vxlan_vnifilter.c +=================================================================== +--- linux-6.12.41.orig/drivers/net/vxlan/vxlan_vnifilter.c ++++ linux-6.12.41/drivers/net/vxlan/vxlan_vnifilter.c +@@ -493,8 +493,8 @@ static int vxlan_update_default_fdb_entr + 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); ++ dst->remote_ifindex, NTF_SELF, 0, ++ RTPROT_UNSPEC, true, extack, 0); + if (err) { + spin_unlock_bh(&vxlan->hash_lock[hash_index]); + return err; +Index: linux-6.12.41/net/bridge/br.c +=================================================================== +--- linux-6.12.41.orig/net/bridge/br.c ++++ linux-6.12.41/net/bridge/br.c +@@ -166,8 +166,8 @@ static int br_switchdev_event(struct not + 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->locked, +- false, 0); ++ fdb_info->vid, RTPROT_UNSPEC, ++ fdb_info->locked, false, 0); + if (err) { + err = notifier_from_errno(err); + break; +Index: linux-6.12.41/net/bridge/br_fdb.c +=================================================================== +--- linux-6.12.41.orig/net/bridge/br_fdb.c ++++ linux-6.12.41/net/bridge/br_fdb.c +@@ -132,6 +132,8 @@ static int fdb_fill_info(struct sk_buff + 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; + +@@ -1163,7 +1165,8 @@ static int fdb_add_entry(struct net_brid + + 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,8 +1191,8 @@ static int __br_fdb_add(struct ndmsg *nd + "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); +@@ -1215,6 +1218,7 @@ int br_fdb_add(struct ndmsg *ndm, struct + 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; + +@@ -1273,11 +1277,11 @@ int br_fdb_add(struct ndmsg *ndm, struct + } + + /* 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 +1293,8 @@ int br_fdb_add(struct ndmsg *ndm, struct + 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 +1306,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 +1314,14 @@ static int fdb_delete_by_addr_and_port(s + if (!fdb || READ_ONCE(fdb->dst) != p) + return -ENOENT; + ++ /* If the delete comes from a different protocol type, ++ * that type is used in the notification as some software ++ * may be expecting multiple deletes (control learned + ++ * hardware datapath learned) ++ */ ++ if (protocol != RTPROT_UNSPEC) ++ fdb->protocol = protocol; ++ + fdb_delete(br, fdb, true); + + return 0; +@@ -1316,12 +1329,12 @@ static int fdb_delete_by_addr_and_port(s + + 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 +1348,14 @@ int br_fdb_delete(struct ndmsg *ndm, str + { + 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 +1377,17 @@ int br_fdb_delete(struct ndmsg *ndm, str + 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 +1447,8 @@ void br_fdb_unsync_static(struct net_bri + } + + 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 +1482,7 @@ int br_fdb_external_learn_add(struct net + err = -ENOMEM; + goto err_unlock; + } ++ fdb->protocol = protocol; + fdb_notify(br, fdb, RTM_NEWNEIGH, swdev_notify); + } else { + if (locked && +@@ -1512,6 +1530,11 @@ int br_fdb_external_learn_add(struct net + test_and_clear_bit(BR_FDB_DYNAMIC_LEARNED, &fdb->flags)) + atomic_dec(&br->fdb_n_learned); + ++ if (fdb->protocol != protocol) { ++ modified = true; ++ fdb->protocol = protocol; ++ } ++ + if (modified) + fdb_notify(br, fdb, RTM_NEWNEIGH, swdev_notify); + } +Index: linux-6.12.41/net/bridge/br_private.h +=================================================================== +--- linux-6.12.41.orig/net/bridge/br_private.h ++++ linux-6.12.41/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; +@@ -869,8 +870,8 @@ int br_fdb_get(struct sk_buff *skb, stru + 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); ++ const unsigned char *addr, u16 vid, 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); 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..ee10320ac --- /dev/null +++ b/patches-sonic/0003-neighbor-Add-NTF_EXT_VALIDATED-flag-for-externally-v.patch @@ -0,0 +1,385 @@ +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 5c6f52831..54c1333a9 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 dce82ed78..2c897c012 100644 +--- a/include/uapi/linux/neighbour.h ++++ b/include/uapi/linux/neighbour.h +@@ -55,6 +55,7 @@ enum { + #define NTF_EXT_MANAGED (1 << 0) + #define NTF_EXT_LOCKED (1 << 1) + #define NTF_EXT_MH_PEER_SYNC (1 << 2) ++#define NTF_EXT_EXT_VALIDATED (1 << 3) + + /* + * Neighbor Cache Entry States. +@@ -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 019aec2ea..888a46faf 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 657538f1e..6969b19ad 100644 --- a/patches-sonic/series +++ b/patches-sonic/series @@ -246,6 +246,11 @@ qsa-2026-apparmor/0008-apparmor-fix-unprivileged-local-user-can-do-privileg.patc qsa-2026-apparmor/0009-apparmor-fix-differential-encoding-verification.patch 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 +# Drop 0003 when kernel >= 6.12 (merged upstream: torvalds/linux 03dc03fa0432) +0003-neighbor-Add-NTF_EXT_VALIDATED-flag-for-externally-v.patch # #