[ovs-dev,3/3] xlate: call tnl_neigh_snoop() from terminate_native_tunnel()

Message ID 1512470596-18091-4-git-send-email-zoltan.balogh@ericsson.com
State New
Headers show
Series
  • Fix tunnel neighbor cache population
Related show

Commit Message

Zoltan Balogh Dec. 5, 2017, 10:43 a.m.
Move tunnel neigh snooping from do_xlate_actions() to
terminate_native_tunnel() in order to keep ARP neighbor cache clean.
Furthermore filter ARP reply and Neighbor Advertisement messages
addressing tunnel endpoint.

Signed-off-by: Zoltan Balogh <zoltan.balogh@ericsson.com>
---
 include/sparse/netinet/in.h   |  10 +++
 lib/tnl-neigh-cache.c         |   1 +
 ofproto/ofproto-dpif-xlate.c  | 143 +++++++++++++++++++++++++++++++++++++++---
 tests/tunnel-push-pop-ipv6.at |  68 +++++++++++++++++++-
 tests/tunnel-push-pop.at      |  67 +++++++++++++++++++-
 5 files changed, 279 insertions(+), 10 deletions(-)

Patch

diff --git a/include/sparse/netinet/in.h b/include/sparse/netinet/in.h
index c28158ca0..c90a9dcef 100644
--- a/include/sparse/netinet/in.h
+++ b/include/sparse/netinet/in.h
@@ -117,6 +117,16 @@  struct sockaddr_in6 {
      (X)->s6_addr[10] == 0xff &&                \
      (X)->s6_addr[11] == 0xff)
 
+#define IN6_IS_ADDR_MC_LINKLOCAL(a)                 \
+    (((const uint8_t *) (a))[0] == 0xff &&          \
+     (((const uint8_t *) (a))[1] & 0xf) == 0x2)
+
+# define IN6_ARE_ADDR_EQUAL(a,b)                                          \
+    ((((const uint32_t *) (a))[0] == ((const uint32_t *) (b))[0]) &&      \
+     (((const uint32_t *) (a))[1] == ((const uint32_t *) (b))[1]) &&      \
+     (((const uint32_t *) (a))[2] == ((const uint32_t *) (b))[2]) &&      \
+     (((const uint32_t *) (a))[3] == ((const uint32_t *) (b))[3]))
+
 #define INET_ADDRSTRLEN 16
 #define INET6_ADDRSTRLEN 46
 
diff --git a/lib/tnl-neigh-cache.c b/lib/tnl-neigh-cache.c
index 5ddd53976..5b32efb6b 100644
--- a/lib/tnl-neigh-cache.c
+++ b/lib/tnl-neigh-cache.c
@@ -42,6 +42,7 @@ 
 #include "unixctl.h"
 #include "util.h"
 #include "openvswitch/vlog.h"
+#include "odp-util.h"
 
 
 /* In seconds */
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index 19343aca3..c139b9e62 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -89,6 +89,12 @@  VLOG_DEFINE_THIS_MODULE(ofproto_dpif_xlate);
  * recursive or not. */
 #define MAX_RESUBMITS (MAX_DEPTH * MAX_DEPTH)
 
+struct xbridge_addr {
+    struct in6_addr *addr;        /* Array of IP addresses of xbridge. */
+    int n_addr;                   /* Number of IP addresses. */
+    struct ovs_refcount ref_cnt;
+};
+
 struct xbridge {
     struct hmap_node hmap_node;   /* Node in global 'xbridges' map. */
     struct ofproto_dpif *ofproto; /* Key in global 'xbridges' map. */
@@ -112,6 +118,8 @@  struct xbridge {
 
     /* Datapath feature support. */
     struct dpif_backer_support support;
+
+    struct xbridge_addr *addr;
 };
 
 struct xbundle {
@@ -575,7 +583,8 @@  static void xlate_xbridge_set(struct xbridge *, struct dpif *,
                               const struct dpif_ipfix *,
                               const struct netflow *,
                               bool forward_bpdu, bool has_in_band,
-                              const struct dpif_backer_support *);
+                              const struct dpif_backer_support *,
+                              const struct xbridge_addr *);
 static void xlate_xbundle_set(struct xbundle *xbundle,
                               enum port_vlan_mode vlan_mode,
                               uint16_t qinq_ethtype, int vlan,
@@ -825,6 +834,56 @@  xlate_xport_init(struct xlate_cfg *xcfg, struct xport *xport)
                 hash_ofp_port(xport->ofp_port));
 }
 
+static struct xbridge_addr *
+xbridge_addr_create(struct xbridge *xbridge)
+{
+    struct xbridge_addr *xbridge_addr = xbridge->addr;
+    struct in6_addr *addr = NULL, *mask = NULL;
+    struct netdev *dev;
+    int err, n_addr = 0;
+
+    err = netdev_open(xbridge->name, NULL, &dev);
+    if (!err) {
+        err = netdev_get_addr_list(dev, &addr, &mask, &n_addr);
+        if (!err) {
+            if (!xbridge->addr ||
+                n_addr != xbridge->addr->n_addr ||
+                (xbridge->addr->addr && memcmp(addr, xbridge->addr->addr,
+                                               sizeof(*addr) * n_addr))) {
+                xbridge_addr = xzalloc(sizeof *xbridge_addr);
+                xbridge_addr->addr = addr;
+                xbridge_addr->n_addr = n_addr;
+                ovs_refcount_init(&xbridge_addr->ref_cnt);
+            } else {
+                free(addr);
+            }
+            free(mask);
+        }
+        netdev_close(dev);
+    }
+
+    return xbridge_addr;
+}
+
+static struct xbridge_addr *
+xbridge_addr_ref(const struct xbridge_addr *addr_)
+{
+    struct xbridge_addr *addr = CONST_CAST(struct xbridge_addr *, addr_);
+    if (addr) {
+        ovs_refcount_ref(&addr->ref_cnt);
+    }
+    return addr;
+}
+
+static void
+xbridge_addr_unref(struct xbridge_addr *addr)
+{
+    if (addr && ovs_refcount_unref_relaxed(&addr->ref_cnt) == 1) {
+        free(addr->addr);
+        free(addr);
+    }
+}
+
 static void
 xlate_xbridge_set(struct xbridge *xbridge,
                   struct dpif *dpif,
@@ -835,7 +894,8 @@  xlate_xbridge_set(struct xbridge *xbridge,
                   const struct dpif_ipfix *ipfix,
                   const struct netflow *netflow,
                   bool forward_bpdu, bool has_in_band,
-                  const struct dpif_backer_support *support)
+                  const struct dpif_backer_support *support,
+                  const struct xbridge_addr *addr)
 {
     if (xbridge->ml != ml) {
         mac_learning_unref(xbridge->ml);
@@ -877,6 +937,11 @@  xlate_xbridge_set(struct xbridge *xbridge,
         xbridge->netflow = netflow_ref(netflow);
     }
 
+    if (xbridge->addr != addr) {
+        xbridge_addr_unref(xbridge->addr);
+        xbridge->addr = xbridge_addr_ref(addr);
+    }
+
     xbridge->dpif = dpif;
     xbridge->forward_bpdu = forward_bpdu;
     xbridge->has_in_band = has_in_band;
@@ -970,7 +1035,7 @@  xlate_xbridge_copy(struct xbridge *xbridge)
                       xbridge->rstp, xbridge->ms, xbridge->mbridge,
                       xbridge->sflow, xbridge->ipfix, xbridge->netflow,
                       xbridge->forward_bpdu, xbridge->has_in_band,
-                      &xbridge->support);
+                      &xbridge->support, xbridge->addr);
     LIST_FOR_EACH (xbundle, list_node, &xbridge->xbundles) {
         xlate_xbundle_copy(new_xbridge, xbundle);
     }
@@ -1125,6 +1190,7 @@  xlate_ofproto_set(struct ofproto_dpif *ofproto, const char *name,
                   const struct dpif_backer_support *support)
 {
     struct xbridge *xbridge;
+    struct xbridge_addr *xbridge_addr, *old_addr;
 
     ovs_assert(new_xcfg);
 
@@ -1139,8 +1205,16 @@  xlate_ofproto_set(struct ofproto_dpif *ofproto, const char *name,
     free(xbridge->name);
     xbridge->name = xstrdup(name);
 
+    xbridge_addr = xbridge_addr_create(xbridge);
+    old_addr = xbridge->addr;
+
     xlate_xbridge_set(xbridge, dpif, ml, stp, rstp, ms, mbridge, sflow, ipfix,
-                      netflow, forward_bpdu, has_in_band, support);
+                      netflow, forward_bpdu, has_in_band, support,
+                      xbridge_addr);
+
+    if (xbridge_addr != old_addr) {
+        xbridge_addr_unref(xbridge_addr);
+    }
 }
 
 static void
@@ -1170,6 +1244,7 @@  xlate_xbridge_remove(struct xlate_cfg *xcfg, struct xbridge *xbridge)
     netflow_unref(xbridge->netflow);
     stp_unref(xbridge->stp);
     rstp_unref(xbridge->rstp);
+    xbridge_addr_unref(xbridge->addr);
     hmap_destroy(&xbridge->xports);
     free(xbridge->name);
     free(xbridge);
@@ -3654,6 +3729,54 @@  check_output_prerequisites(struct xlate_ctx *ctx,
     return true;
 }
 
+/* Function verifies if destination address of received Neighbor Advertisement
+ * message stored in 'flow' is correct. It should be either FF02::1:FFXX:XXXX
+ * where XX:XXXX stands for the last 24 bits of 'ipv6_addr' or it should match
+ * 'ipv6_addr'. */
+static bool
+is_nd_dst_correct(const struct flow *flow, const struct in6_addr *ipv6_addr)
+{
+    const uint8_t *flow_ipv6_addr = (uint8_t *) & flow->ipv6_dst;
+    const uint8_t *addr = (uint8_t *) ipv6_addr;
+
+    return (IN6_IS_ADDR_MC_LINKLOCAL(flow_ipv6_addr) &&
+            flow_ipv6_addr[11] == 0x01 &&
+            flow_ipv6_addr[12] == 0xff &&
+            flow_ipv6_addr[13] == addr[13] &&
+            flow_ipv6_addr[14] == addr[14] &&
+            flow_ipv6_addr[15] == addr[15]) ||
+            IN6_ARE_ADDR_EQUAL(flow_ipv6_addr, addr);
+}
+
+/* Function verifies if the ARP reply or Neighbor Advertisement represented by
+ * 'flow' addresses the 'xbridge' of 'ctx'. Returns true if the ARP TA or
+ * neighbor discovery destination is in the list of configured IP addresses of
+ * the bridge. Otherwise, it returns false. */
+static bool
+is_neighbor_reply_correct(const struct xlate_ctx *ctx, const struct flow *flow)
+{
+    bool ret = false;
+    int i;
+    struct xbridge_addr *xbridge_addr = xbridge_addr_ref(ctx->xbridge->addr);
+
+    /* Verify if 'nw_dst' of ARP or 'ipv6_dst' of ICMPV6 is in the list. */
+    for (i = 0; xbridge_addr && i < xbridge_addr->n_addr; i++) {
+        struct in6_addr *ip_addr = &xbridge_addr->addr[i];
+        if ((IN6_IS_ADDR_V4MAPPED(ip_addr) &&
+             flow->dl_type == htons(ETH_TYPE_ARP) &&
+             in6_addr_get_mapped_ipv4(ip_addr) == flow->nw_dst) ||
+            (!IN6_IS_ADDR_V4MAPPED(ip_addr) &&
+              is_nd_dst_correct(flow, ip_addr))) {
+            /* Found a match. */
+            ret = true;
+            break;
+        }
+    }
+
+    xbridge_addr_unref(xbridge_addr);
+    return ret;
+}
+
 static bool
 terminate_native_tunnel(struct xlate_ctx *ctx, ofp_port_t ofp_port,
                         struct flow *flow, struct flow_wildcards *wc,
@@ -3666,6 +3789,15 @@  terminate_native_tunnel(struct xlate_ctx *ctx, ofp_port_t ofp_port,
     if (ofp_port == OFPP_LOCAL &&
         ovs_native_tunneling_is_on(ctx->xbridge->ofproto)) {
         *tnl_port = tnl_port_map_lookup(flow, wc);
+
+        /* If no tunnel port was found and it's about an ARP or ICMPv6 packet,
+         * do tunnel neighbor snooping. */
+        if (*tnl_port == ODPP_NONE &&
+            (flow->dl_type == htons(ETH_TYPE_ARP) ||
+             flow->nw_proto == IPPROTO_ICMPV6) &&
+             is_neighbor_reply_correct(ctx, flow)) {
+            tnl_neigh_snoop(flow, wc, ctx->xbridge->name);
+        }
     }
 
     return *tnl_port != ODPP_NONE;
@@ -6165,9 +6297,6 @@  do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
     struct flow *flow = &ctx->xin->flow;
     const struct ofpact *a;
 
-    if (ovs_native_tunneling_is_on(ctx->xbridge->ofproto)) {
-        tnl_neigh_snoop(flow, wc, ctx->xbridge->name);
-    }
     /* dl_type already in the mask, not set below. */
 
     if (!ofpacts_len) {
diff --git a/tests/tunnel-push-pop-ipv6.at b/tests/tunnel-push-pop-ipv6.at
index 0b9d436db..b0f4a7e91 100644
--- a/tests/tunnel-push-pop-ipv6.at
+++ b/tests/tunnel-push-pop-ipv6.at
@@ -55,9 +55,73 @@  AT_CHECK([cat p0.pcap.txt | grep 93aa55aa55000086dd6000000000203aff2001cafe | un
 ])
 
 dnl Check ARP Snoop
-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::94,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:c8,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:c8)'])
 
-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::94,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:c8   br0
+])
+
+dnl Receiving Neighbor Advertisement with incorrect 'nw_dst' should not alter tunnel neighbor cache
+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::99,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)'])
+
+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:c8   br0
+])
+
+dnl Receiving Neighbot Advertisement with incorrect VLAN id should not alter tunnel neighbor cache
+AT_CHECK([ovs-vsctl set port br0 tag=10])
+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(0x8100),vlan(vid=99,pcp=7),encap(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))'])
+
+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:c8   br0
+])
+
+dnl Receiving Neighbor Advertisement with correct VLAN id should alter tunnel neighbor cache
+AT_CHECK([ovs-vsctl set port br0 tag=10])
+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(0x8100),vlan(vid=10,pcp=7),encap(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))'])
+
+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
+])
+
+dnl Receiving Neighbor Advertisement in overlay bridge should not alter tunnel neighbor cache
+AT_CHECK([ovs-vsctl add-port int-br p1 -- set interface p1 type=dummy ofport_request=200 other-config:hwaddr=aa:55:aa:55:00:99])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(200),eth(src=f8:bc:12:44:34:c8,dst=aa:55:aa:55:00:00),eth_type(0x86dd),ipv6(src=2001:cafe::92,dst=2001:cafe::99,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:c8)'])
+
+ovs-appctl time/warp 1000
+ovs-appctl time/warp 1000
+
+AT_CHECK([ovs-appctl tnl/neigh/show | grep br | sort], [0], [dnl
+2001:cafe::92                                 f8:bc:12:44:34:b6   br0
+])
+
+dnl Receive Neighbor Advertisement without VLAN header
+AT_CHECK([ovs-vsctl set port br0 tag=0])
+AT_CHECK([ovs-appctl tnl/neigh/flush], [0], [OK
+])
+
+ovs-appctl time/warp 1000
+ovs-appctl time/warp 1000
+
+
+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=ff02::1:ff00:0088,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
diff --git a/tests/tunnel-push-pop.at b/tests/tunnel-push-pop.at
index b168f5f2f..4e259730c 100644
--- a/tests/tunnel-push-pop.at
+++ b/tests/tunnel-push-pop.at
@@ -70,9 +70,71 @@  ffffffffffffaa55aa55000008060001080006040001aa55aa550000010102580000000000000101
 ])
 
 dnl Check ARP Snoop
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 'recirc_id(0),in_port(1),eth(src=f8:bc:12:44:34:c8,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:c8,tha=00:00:00:00:00:00)'])
+
+ovs-appctl time/warp 1000
+ovs-appctl time/warp 1000
+
+AT_CHECK([ovs-appctl tnl/neigh/show | grep br0 | sort], [0], [dnl
+1.1.2.92                                      f8:bc:12:44:34:c8   br0
+])
+
+dnl Receiving ARP reply with incorrect 'tip' should not alter tunnel neighbor cache
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 'recirc_id(0),in_port(1),eth(src=f8:bc:12:44:34:b8,dst=ff:ff:ff:ff:ff:ff),eth_type(0x0806),arp(sip=1.1.2.92,tip=1.1.2.90,op=2,sha=f8:bc:12:44:34:b8,tha=00:00:00:00:00:00)'])
+
+ovs-appctl time/warp 1000
+ovs-appctl time/warp 1000
+
+AT_CHECK([ovs-appctl tnl/neigh/show | grep br0 | sort], [0], [dnl
+1.1.2.92                                      f8:bc:12:44:34:c8   br0
+])
+
+dnl Receiving ARP reply with incorrect VLAN id should not alter tunnel neighbor cache
+AT_CHECK([ovs-vsctl set port br0 tag=10])
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 'recirc_id(0),in_port(1),eth(src=f8:bc:12:44:34:b6,dst=ff:ff:ff:ff:ff:ff),eth_type(0x8100),vlan(vid=99,pcp=7),encap(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))'])
+
+ovs-appctl time/warp 1000
+ovs-appctl time/warp 1000
+
+AT_CHECK([ovs-appctl tnl/neigh/show | grep br0 | sort], [0], [dnl
+1.1.2.92                                      f8:bc:12:44:34:c8   br0
+])
+
+dnl Receiving ARP reply with correct VLAN id should alter tunnel neighbor cache
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 'recirc_id(0),in_port(1),eth(src=f8:bc:12:44:34:b6,dst=ff:ff:ff:ff:ff:ff),eth_type(0x8100),vlan(vid=10,pcp=7),encap(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))'])
+
+ovs-appctl time/warp 1000
+ovs-appctl time/warp 1000
+
+AT_CHECK([ovs-appctl tnl/neigh/show | grep br0 | sort], [0], [dnl
+1.1.2.92                                      f8:bc:12:44:34:b6   br0
+])
+
+dnl Receiving ARP reply in overlay bridge should not alter tunnel neighbor cache
+AT_CHECK([ovs-vsctl add-port int-br p1 -- set interface p1 type=dummy ofport_request=200 other-config:hwaddr=aa:55:aa:55:00:99])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 'recirc_id(0),in_port(200),eth(src=f8:bc:12:44:34:c8,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:c8,tha=00:00:00:00:00:00)'])
+
+ovs-appctl time/warp 1000
+ovs-appctl time/warp 1000
+
+AT_CHECK([ovs-appctl tnl/neigh/show | grep br | sort], [0], [dnl
+1.1.2.92                                      f8:bc:12:44:34:b6   br0
+])
+
+dnl Receive ARP reply without VLAN header
+AT_CHECK([ovs-vsctl set port br0 tag=0])
+AT_CHECK([ovs-appctl tnl/neigh/flush], [0], [OK
+])
+
+ovs-appctl time/warp 1000
+ovs-appctl time/warp 1000
+
 AT_CHECK([ovs-appctl netdev-dummy/receive p0 'recirc_id(0),in_port(1),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(1),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
@@ -190,9 +252,12 @@  AT_CHECK([ovs-ofctl dump-ports int-br | grep 'port  7'], [0], [dnl
 dnl Check GREL3 only accepts non-fragmented packets?
 AT_CHECK([ovs-appctl netdev-dummy/receive p0 'aa55aa550000001b213cab6408004500007e79464000402fba550101025c0101025820000800000001c8fe71d883724fbeb6f4e1494a080045000054ba200000400184861e0000011e00000200004227e75400030af3195500000000f265010000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637'])
 
+ovs-appctl time/warp 1000
+ovs-appctl time/warp 1000
+
 AT_CHECK([ovs-ofctl dump-ports int-br | grep 'port  [[37]]' | sort], [0], [dnl
   port  3: rx pkts=3, bytes=294, drop=?, errs=?, frame=?, over=?, crc=?
-  port  7: rx pkts=3, bytes=252, drop=?, errs=?, frame=?, over=?, crc=?
+  port  7: rx pkts=4, bytes=350, drop=?, errs=?, frame=?, over=?, crc=?
 ])
 
 dnl Check decapsulation of Geneve packet with options