Native tunnels snoop the outer Ethernet source MAC of every IPv4/IPv6
data packet in terminate_native_tunnel() and call tnl_neigh_set().
That lets BFD recover when the remote VTEP NIC MAC changes, so the
default stays enabled.

The outer source MAC is the last-hop MAC, not necessarily the remote
VTEP MAC.  Under ECMP or MLAG, packets from one peer can flap last-hop
MACs.  Each mismatch deletes the tnl-neigh entry and seq_change()s
tnl_conf_seq, which revalidates every datapath flow.

Add Interface option neigh_snoop (default true).  When false on the
OpenFlow tunnel port that receive matching selects for the packet,
skip the data-packet tnl_neigh_set() path.  At terminate time the
packet is still on the physical port, so build receive tunnel metadata
from the outer IP and the encapsulation key (parsed from the native
vport type: vxlan, geneve, or gtpu) and reuse tnl_find() the same way
tnl_port_receive() does.  ARP/ND and GARP snooping via tnl_neigh_snoop()
is unchanged.

This option is userspace native tunneling only.

Signed-off-by: Girish Kumar <[email protected]>
---
v2: Resend via git send-email.  v1 failed git am (Gmail linkified
    tests/tunnel-push-pop*.at paths).

 NEWS                          |   6 ++
 lib/netdev-vport.c            |  10 +++
 lib/netdev.h                  |   1 +
 lib/tnl-ports.c               |  27 +++++++-
 lib/tnl-ports.h               |   1 +
 ofproto/ofproto-dpif-xlate.c  | 116 +++++++++++++++++++++++++++++++++-
 ofproto/tunnel.c              |  36 +++++++++++
 ofproto/tunnel.h              |   3 +
 tests/tunnel-push-pop-ipv6.at |  48 ++++++++++++++
 tests/tunnel-push-pop.at      |  48 ++++++++++++++
 vswitchd/vswitch.xml          |  13 ++++
 11 files changed, 306 insertions(+), 3 deletions(-)

diff --git a/NEWS b/NEWS
index de1a030..a57914f 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,11 @@
 Post-v4.0.0
 --------------------
+   - Userspace datapath:
+     * Native tunnels accept a per-tunnel Interface 'neigh_snoop' option
+       (default true).  Setting 'options:neigh_snoop=false' skips updating
+       the tunnel neighbor cache from the outer source MAC of encapsulated
+       data packets on that Interface.  Userspace native tunneling only.
+       ARP/ND snooping is unchanged.
 
 
 v4.0.0 - 17 Aug 2026
diff --git a/lib/netdev-vport.c b/lib/netdev-vport.c
index d11269d..a28af1c 100644
--- a/lib/netdev-vport.c
+++ b/lib/netdev-vport.c
@@ -228,6 +228,7 @@ netdev_vport_construct(struct netdev *netdev_)
     }
 
     tnl_cfg->dont_fragment = true;
+    tnl_cfg->neigh_snoop = true;
     tnl_cfg->ttl = DEFAULT_TTL;
 
     ovsrcu_set(&dev->tnl_cfg, tnl_cfg);
@@ -638,6 +639,7 @@ set_tunnel_config(struct netdev *dev_, const struct smap 
*args, char **errp)
 
     needs_dst_port = netdev_vport_needs_dst_port(dev_);
     tnl_cfg.dont_fragment = true;
+    tnl_cfg.neigh_snoop = true;
 
     SMAP_FOR_EACH (node, args) {
         if (!strcmp(node->key, "remote_ip")) {
@@ -697,6 +699,10 @@ set_tunnel_config(struct netdev *dev_, const struct smap 
*args, char **errp)
             if (!strcmp(node->value, "false")) {
                 tnl_cfg.dont_fragment = false;
             }
+        } else if (!strcmp(node->key, "neigh_snoop")) {
+            if (!strcmp(node->value, "false")) {
+                tnl_cfg.neigh_snoop = false;
+            }
         } else if (!strcmp(node->key, "key") ||
                    !strcmp(node->key, "in_key") ||
                    !strcmp(node->key, "out_key") ||
@@ -1045,6 +1051,10 @@ get_tunnel_config(const struct netdev *dev, struct smap 
*args)
         smap_add(args, "df_default", "false");
     }
 
+    if (!tnl_cfg->neigh_snoop) {
+        smap_add(args, "neigh_snoop", "false");
+    }
+
     if (tnl_cfg->set_egress_pkt_mark) {
         smap_add_format(args, "egress_pkt_mark",
                         "%"PRIu32, tnl_cfg->egress_pkt_mark);
diff --git a/lib/netdev.h b/lib/netdev.h
index 5e093b0..42c82bd 100644
--- a/lib/netdev.h
+++ b/lib/netdev.h
@@ -159,6 +159,7 @@ struct netdev_tunnel_config {
 
     enum netdev_tnl_csum csum;
     bool dont_fragment;
+    bool neigh_snoop;
     enum netdev_pt_mode pt_mode;
 
     bool set_seq;
diff --git a/lib/tnl-ports.c b/lib/tnl-ports.c
index 56119b3..2e9030d 100644
--- a/lib/tnl-ports.c
+++ b/lib/tnl-ports.c
@@ -53,6 +53,7 @@ struct tnl_port {
     struct ovs_refcount ref_cnt;
     ovs_be16 tp_port;
     uint8_t nw_proto;
+    const char *type;
     char dev_name[IFNAMSIZ];
     struct ovs_list node;
 };
@@ -180,7 +181,8 @@ tnl_type_to_nw_proto(const char type[], uint8_t 
nw_protos[2])
 
 static void
 tnl_port_map_insert__(odp_port_t port, ovs_be16 tp_port,
-                      const char dev_name[], uint8_t nw_proto)
+                      const char dev_name[], uint8_t nw_proto,
+                      const char type[])
 {
     struct tnl_port *p;
     struct ip_device *ip_dev;
@@ -197,6 +199,7 @@ tnl_port_map_insert__(odp_port_t port, ovs_be16 tp_port,
     p->port = port;
     p->tp_port = tp_port;
     p->nw_proto = nw_proto;
+    p->type = type;
     ovs_strlcpy(p->dev_name, dev_name, sizeof p->dev_name);
     ovs_refcount_init(&p->ref_cnt);
     ovs_list_insert(&port_list, &p->node);
@@ -220,7 +223,8 @@ tnl_port_map_insert(odp_port_t port, ovs_be16 tp_port,
 
     for (i = 0; i < 2; i++) {
         if (nw_protos[i]) {
-            tnl_port_map_insert__(port, tp_port, dev_name, nw_protos[i]);
+            tnl_port_map_insert__(port, tp_port, dev_name, nw_protos[i],
+                                  type);
         }
     }
 }
@@ -308,6 +312,25 @@ tnl_port_map_lookup(struct flow *flow, struct 
flow_wildcards *wc)
     return (cr) ? tnl_port_cast(cr)->portno : ODPP_NONE;
 }
 
+/* Returns the native vport type ("vxlan", "geneve", "gtpu", ...) for
+ * 'port', or NULL if 'port' is not in the map. */
+const char *
+tnl_port_map_get_type(odp_port_t port)
+{
+    const char *type = NULL;
+    struct tnl_port *p;
+
+    ovs_mutex_lock(&mutex);
+    LIST_FOR_EACH (p, node, &port_list) {
+        if (p->port == port) {
+            type = p->type;
+            break;
+        }
+    }
+    ovs_mutex_unlock(&mutex);
+    return type;
+}
+
 static void
 tnl_port_show_v(struct ds *ds)
 {
diff --git a/lib/tnl-ports.h b/lib/tnl-ports.h
index 61ca0f8..284b117 100644
--- a/lib/tnl-ports.h
+++ b/lib/tnl-ports.h
@@ -25,6 +25,7 @@
 #include "util.h"
 
 odp_port_t tnl_port_map_lookup(struct flow *flow, struct flow_wildcards *wc);
+const char *tnl_port_map_get_type(odp_port_t);
 
 void tnl_port_map_insert(odp_port_t, ovs_be16 tp_port,
                          const char dev_name[], const char type[]);
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index 764dbd6..11de7a3 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -4435,6 +4435,119 @@ static bool check_neighbor_reply(struct xlate_ctx *ctx, 
struct flow *flow)
     return false;
 }
 
+/* Encapsulation key from a native-tunnel packet still on the phy port.
+ * 'type' is the native vport type for tnl_odp_port (vxlan, geneve, gtpu).
+ * Geneve stores the VNI in the same 4 bytes as VXLAN; GTP does not.
+ * GRE uses the optional key.  Returns 0 if the type is unknown or the
+ * key is absent. */
+static ovs_be64
+native_tnl_recv_key(const struct dp_packet *packet, const struct flow *flow,
+                    const char *type)
+{
+    const void *l4;
+    size_t l4_size;
+
+    if (!packet) {
+        return 0;
+    }
+
+    l4 = dp_packet_l4(packet);
+    if (!l4) {
+        return 0;
+    }
+    l4_size = dp_packet_l4_size(packet);
+
+    if (flow->nw_proto == IPPROTO_UDP) {
+        if (!type) {
+            return 0;
+        }
+
+        if (!strcmp(type, "vxlan")) {
+            const struct vxlanhdr *vxh;
+
+            if (l4_size < UDP_HEADER_LEN + sizeof *vxh) {
+                return 0;
+            }
+            vxh = ALIGNED_CAST(const struct vxlanhdr *,
+                               (const char *) l4 + UDP_HEADER_LEN);
+            return htonll(ntohl(get_16aligned_be32(&vxh->vx_vni)) >> 8);
+        }
+
+        if (!strcmp(type, "geneve")) {
+            const struct genevehdr *gnh;
+
+            if (l4_size < UDP_HEADER_LEN + sizeof *gnh) {
+                return 0;
+            }
+            gnh = ALIGNED_CAST(const struct genevehdr *,
+                               (const char *) l4 + UDP_HEADER_LEN);
+            return htonll(ntohl(get_16aligned_be32(&gnh->vni)) >> 8);
+        }
+
+        if (!strcmp(type, "gtpu")) {
+            const struct gtpuhdr *gtph;
+
+            if (l4_size < UDP_HEADER_LEN + sizeof *gtph) {
+                return 0;
+            }
+            gtph = ALIGNED_CAST(const struct gtpuhdr *,
+                                (const char *) l4 + UDP_HEADER_LEN);
+            return be32_to_be64(get_16aligned_be32(&gtph->teid));
+        }
+
+        return 0;
+    }
+
+    if (flow->nw_proto == IPPROTO_GRE) {
+        const struct gre_base_hdr *greh = l4;
+        const ovs_16aligned_be32 *options;
+        size_t hlen = sizeof *greh;
+
+        if (l4_size < hlen) {
+            return 0;
+        }
+        if (greh->flags & htons(GRE_CSUM)) {
+            hlen += 4;
+        }
+        if (!(greh->flags & htons(GRE_KEY))) {
+            return 0;
+        }
+        hlen += 4;
+        if (l4_size < hlen) {
+            return 0;
+        }
+        options = ALIGNED_CAST(const ovs_16aligned_be32 *,
+                               (const char *) greh + hlen - 4);
+        return be32_to_be64(get_16aligned_be32(options));
+    }
+
+    return 0;
+}
+
+/* options:neigh_snoop from the OpenFlow tunnel port that receive matching
+ * would select for this packet.  Default on. */
+static bool
+native_tnl_neigh_snoop(const struct xlate_ctx *ctx, const struct flow *flow,
+                       odp_port_t tnl_odp_port)
+{
+    const struct netdev_tunnel_config *cfg;
+    const struct ofport_dpif *ofport;
+    const struct xport *xport;
+    const char *type;
+    ovs_be64 tun_id;
+
+    type = tnl_port_map_get_type(tnl_odp_port);
+    tun_id = native_tnl_recv_key(ctx->xin->packet, flow, type);
+    ofport = tnl_port_receive_native(flow, tnl_odp_port, tun_id);
+    xport = xport_lookup(ctx->xcfg, ofport);
+    if (!xport || !xport->netdev) {
+        return true;
+    }
+
+    cfg = netdev_get_tunnel_config(xport->netdev);
+    return !cfg || cfg->neigh_snoop;
+}
+
 static bool
 terminate_native_tunnel(struct xlate_ctx *ctx, const struct xport *xport,
                         struct flow *flow, struct flow_wildcards *wc,
@@ -4460,7 +4573,8 @@ terminate_native_tunnel(struct xlate_ctx *ctx, const 
struct xport *xport,
                             ctx->xin->allow_side_effects);
         } else if (*tnl_port != ODPP_NONE &&
                    ctx->xin->allow_side_effects &&
-                   dl_type_is_ip_any(flow->dl_type)) {
+                   dl_type_is_ip_any(flow->dl_type) &&
+                   native_tnl_neigh_snoop(ctx, flow, *tnl_port)) {
             struct eth_addr mac = flow->dl_src;
             struct in6_addr s_ip6;
 
diff --git a/ofproto/tunnel.c b/ofproto/tunnel.c
index d8a2895..6001d50 100644
--- a/ofproto/tunnel.c
+++ b/ofproto/tunnel.c
@@ -335,6 +335,42 @@ out:
     return ofport;
 }
 
+/* Like tnl_port_receive(), but 'flow' still has the outer headers from the
+ * physical port.  'tnl_odp_port' is the shared native vport from
+ * tnl_port_map_lookup() and 'tun_id' is the encapsulation key. */
+const struct ofport_dpif *
+tnl_port_receive_native(const struct flow *flow, odp_port_t tnl_odp_port,
+                        ovs_be64 tun_id) OVS_EXCLUDED(rwlock)
+{
+    const struct ofport_dpif *ofport = NULL;
+    struct tnl_port *tnl_port;
+    struct flow recv_flow;
+
+    memset(&recv_flow, 0, sizeof recv_flow);
+    recv_flow.packet_type = flow->packet_type;
+    recv_flow.in_port.odp_port = tnl_odp_port;
+    recv_flow.tunnel.tun_id = tun_id;
+    recv_flow.tunnel.flags = FLOW_TNL_F_KEY;
+
+    if (flow->dl_type == htons(ETH_TYPE_IP)) {
+        recv_flow.tunnel.ip_src = flow->nw_src;
+        recv_flow.tunnel.ip_dst = flow->nw_dst;
+    } else if (flow->dl_type == htons(ETH_TYPE_IPV6)) {
+        recv_flow.tunnel.ipv6_src = flow->ipv6_src;
+        recv_flow.tunnel.ipv6_dst = flow->ipv6_dst;
+    } else {
+        return NULL;
+    }
+
+    fat_rwlock_rdlock(&rwlock);
+    tnl_port = tnl_find(&recv_flow);
+    if (tnl_port) {
+        ofport = tnl_port->ofport;
+    }
+    fat_rwlock_unlock(&rwlock);
+    return ofport;
+}
+
 /* Should be called at the beginning of action translation to initialize
  * wildcards and perform any actions based on receiving on tunnel port.
  *
diff --git a/ofproto/tunnel.h b/ofproto/tunnel.h
index 323f3fa..c70e899 100644
--- a/ofproto/tunnel.h
+++ b/ofproto/tunnel.h
@@ -40,6 +40,9 @@ int tnl_port_add(const struct ofport_dpif *, const struct 
netdev *,
 void tnl_port_del(const struct ofport_dpif *, odp_port_t);
 
 const struct ofport_dpif *tnl_port_receive(const struct flow *);
+const struct ofport_dpif *tnl_port_receive_native(const struct flow *,
+                                                  odp_port_t tnl_odp_port,
+                                                  ovs_be64 tun_id);
 void tnl_wc_init(struct flow *, struct flow_wildcards *);
 bool tnl_process_ecn(struct flow *);
 odp_port_t tnl_port_send(const struct ofport_dpif *, struct flow *,
diff --git a/tests/tunnel-push-pop-ipv6.at b/tests/tunnel-push-pop-ipv6.at
index ec757f7..38dd077 100644
--- a/tests/tunnel-push-pop-ipv6.at
+++ b/tests/tunnel-push-pop-ipv6.at
@@ -656,6 +656,54 @@ AT_CHECK([ovs-appctl tnl/arp/show | tail -n+3 | sort], 
[0], [dnl
 2001:cafe::93                                 f8:bc:12:44:34:b7   br0
 ])
 
+dnl Disable data-packet neigh snoop on t2 only.  A different outer MAC
+dnl on that Interface must not replace the complete tnl-neigh entry.
+AT_CHECK([ovs-vsctl set Interface t2 options:neigh_snoop=false])
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 
'aa55aa550000f8bc1244cafe86dd60000000003a11402001cafe0000000000000000000000922001cafe000000000000000000000088c85312b5003abc700c00000300007b00ffffffffffff00000000000008004500001c0001000040117cce7f0000017f0000010035003500080172'])
+
+ovs-appctl time/warp 1000
+ovs-appctl time/warp 1000
+
+AT_CHECK([ovs-appctl tnl/arp/show | tail -n+3 | sort], [0], [dnl
+2001:cafe::92                                 f8:bc:12:44:34:b6   br0
+2001:cafe::93                                 f8:bc:12:44:34:b7   br0
+])
+
+dnl t3 stays default-on.  A data packet for 2001:cafe::93 must still update.
+dnl Use a different outer MAC so this misses the t2 megaflow and re-xlates.
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 
'aa55aa550000f8bc1244caff86dd60000000003a11402001cafe0000000000000000000000932001cafe000000000000000000000088c85312b5003abc700c00000300000000ffffffffffff00000000000008004500001c0001000040117cce7f0000017f0000010035003500080172'])
+
+ovs-appctl time/warp 1000
+ovs-appctl time/warp 1000
+
+AT_CHECK([ovs-appctl tnl/arp/show | tail -n+3 | sort], [0], [dnl
+2001:cafe::92                                 f8:bc:12:44:34:b6   br0
+2001:cafe::93                                 f8:bc:12:44:ca:ff   br0
+])
+
+dnl ND snooping still updates the cache when neigh_snoop is false.
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 
'in_port(1),eth(src=f8:bc:12:44:ca:fe,dst=aa:55:aa:55:00:00),eth_type(0x86dd),ipv6(src=2001:cafe::92,dst=2001:cafe::88,label=0,proto=58,tclass=0,hlimit=255,frag=no),icmpv6(type=136,code=0),nd(target=2001:cafe::92,sll=00:00:00:00:00:00,tll=f8:bc:12:44:ca:fe)'])
+
+ovs-appctl time/warp 1000
+ovs-appctl time/warp 1000
+
+AT_CHECK([ovs-appctl tnl/arp/show | tail -n+3 | sort], [0], [dnl
+2001:cafe::92                                 f8:bc:12:44:ca:fe   br0
+2001:cafe::93                                 f8:bc:12:44:ca:ff   br0
+])
+
+dnl Restore the complete entries used by later push checks.
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 
'in_port(1),eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:00),eth_type(0x86dd),ipv6(src=2001:cafe::92,dst=2001:cafe::88,label=0,proto=58,tclass=0,hlimit=255,frag=no),icmpv6(type=136,code=0),nd(target=2001:cafe::92,sll=00:00:00:00:00:00,tll=f8:bc:12:44:34:b6)'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 
'in_port(1),eth(src=f8:bc:12:44:34:b7,dst=aa:55:aa:55:00:00),eth_type(0x86dd),ipv6(src=2001:cafe::93,dst=2001:cafe::88,label=0,proto=58,tclass=0,hlimit=255,frag=no),icmpv6(type=136,code=0),nd(target=2001:cafe::93,sll=00:00:00:00:00:00,tll=f8:bc:12:44:34:b7)'])
+
+ovs-appctl time/warp 1000
+ovs-appctl time/warp 1000
+
+AT_CHECK([ovs-appctl tnl/arp/show | tail -n+3 | sort], [0], [dnl
+2001:cafe::92                                 f8:bc:12:44:34:b6   br0
+2001:cafe::93                                 f8:bc:12:44:34:b7   br0
+])
+
 dnl Disable checksum from VXLAN port.
 AT_CHECK([ovs-vsctl set Interface t3 options:csum=false])
 AT_CHECK([ovs-ofctl del-flows int-br])
diff --git a/tests/tunnel-push-pop.at b/tests/tunnel-push-pop.at
index ab393cf..79cec86 100644
--- a/tests/tunnel-push-pop.at
+++ b/tests/tunnel-push-pop.at
@@ -759,6 +759,54 @@ AT_CHECK([ovs-appctl tnl/neigh/show | tail -n+3 | sort], 
[0], [dnl
 1.1.2.93                                      f8:bc:12:44:34:b7   br0
 ])
 
+dnl Disable data-packet neigh snoop on t2 only.  A different outer MAC
+dnl on that Interface must not replace the complete tnl-neigh entry.
+AT_CHECK([ovs-vsctl set Interface t2 options:neigh_snoop=false])
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 
'aa55aa550000f8bc1244cafe08004500004e00010000401173e90101025c01010258c85312b5003a8cd40c00000300007b00ffffffffffff00000000000008004500001c0001000040117cce7f0000017f0000010035003500080172'])
+
+ovs-appctl time/warp 1000
+ovs-appctl time/warp 1000
+
+AT_CHECK([ovs-appctl tnl/neigh/show | tail -n+3 | sort], [0], [dnl
+1.1.2.92                                      f8:bc:12:44:34:b6   br0
+1.1.2.93                                      f8:bc:12:44:34:b7   br0
+])
+
+dnl t3 stays default-on.  A data packet for 1.1.2.93 must still update.
+dnl Use a different outer MAC so this misses the t2 megaflow and re-xlates.
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 
'aa55aa550000f8bc1244caff08004500004e00010000401173e80101025d01010258c85312b5003a00000c00000300000000ffffffffffff00000000000008004500001c0001000040117cce7f0000017f0000010035003500080172'])
+
+ovs-appctl time/warp 1000
+ovs-appctl time/warp 1000
+
+AT_CHECK([ovs-appctl tnl/neigh/show | tail -n+3 | sort], [0], [dnl
+1.1.2.92                                      f8:bc:12:44:34:b6   br0
+1.1.2.93                                      f8:bc:12:44:ca:ff   br0
+])
+
+dnl ARP snooping still updates the cache when neigh_snoop is false.
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 
'recirc_id(0),in_port(100),eth(src=f8:bc:12:44:ca:fe,dst=ff:ff:ff:ff:ff:ff),eth_type(0x0806),arp(sip=1.1.2.92,tip=1.1.2.88,op=2,sha=f8:bc:12:44:ca:fe,tha=00:00:00:00:00:00)'])
+
+ovs-appctl time/warp 1000
+ovs-appctl time/warp 1000
+
+AT_CHECK([ovs-appctl tnl/neigh/show | tail -n+3 | sort], [0], [dnl
+1.1.2.92                                      f8:bc:12:44:ca:fe   br0
+1.1.2.93                                      f8:bc:12:44:ca:ff   br0
+])
+
+dnl Restore the complete entries used by later push checks.
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 
'recirc_id(0),in_port(100),eth(src=f8:bc:12:44:34:b6,dst=ff:ff:ff:ff:ff:ff),eth_type(0x0806),arp(sip=1.1.2.92,tip=1.1.2.88,op=2,sha=f8:bc:12:44:34:b6,tha=00:00:00:00:00:00)'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 
'recirc_id(0),in_port(100),eth(src=f8:bc:12:44:34:b7,dst=ff:ff:ff:ff:ff:ff),eth_type(0x0806),arp(sip=1.1.2.93,tip=1.1.2.88,op=2,sha=f8:bc:12:44:34:b7,tha=00:00:00:00:00:00)'])
+
+ovs-appctl time/warp 1000
+ovs-appctl time/warp 1000
+
+AT_CHECK([ovs-appctl tnl/neigh/show | tail -n+3 | sort], [0], [dnl
+1.1.2.92                                      f8:bc:12:44:34:b6   br0
+1.1.2.93                                      f8:bc:12:44:34:b7   br0
+])
+
 ovs-appctl time/warp 10000
 
 AT_CHECK([ovs-vsctl del-port int-br t3 \
diff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml
index 4eec70f..acd1bd0 100644
--- a/vswitchd/vswitch.xml
+++ b/vswitchd/vswitch.xml
@@ -3102,6 +3102,19 @@
         to <code>false</code> to disable.
       </column>
 
+      <column name="options" key="neigh_snoop"
+              type='{"type": "boolean"}'>
+        Optional.  Userspace native tunneling only.  If enabled (the
+        default), the userspace datapath updates the native-tunnel neighbor
+        cache from the outer Ethernet source MAC of encapsulated IPv4 and
+        IPv6 data packets received on this tunnel Interface.  That lets
+        BFD recover after the remote VTEP NIC MAC changes.  Set to
+        <code>false</code> to skip that data-packet snooping, which can
+        otherwise flap the neighbor entry when ECMP or MLAG changes the
+        last-hop MAC.  ARP, GARP, and Neighbor Discovery snooping is not
+        affected.
+      </column>
+
       <column name="options" key="egress_pkt_mark">
         Optional.  The pkt_mark to be set on the encapsulating packet.  This
         option sets packet mark for the tunnel endpoint for all tunnel packets
-- 
2.47.3

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to