On 26 Aug 2026, at 15:26, Tim Rozet via dev wrote:
> The native userspace tunnel neighbor cache removes a complete entry > as soon as its aging timer expires. The next packet is treated as a > cache miss and dropped while OVS sends an ARP or neighbor discovery > request. > > A kernel neighbor entry can instead enter the stale state while still > retaining a usable link-layer address. Traffic continues using that > address while neighbor reachability is refreshed. > > Add reachable, stale, and incomplete states to the userspace cache. > When a reachable entry expires, retain its MAC indefinitely and send > traffic while issuing rate-limited refresh probes. Avoid caching > translations made with a stale entry so that OVS can retry probes and > promptly use a refreshed binding. > > Remove a stale entry after three unanswered probes and one retransmit > interval. Limit retained stale entries to 4096 and evict the > oldest-used entries when the limit is exceeded. > > This fixes the case where OVS previously learned the neighbor MAC but > the entry became stale during an idle period. > > A genuinely cold entry—restart, flush, eviction, or never learned— > still drops its triggering packet. That remains the future buffering > case. > > Add IPv4 and IPv6 tests covering long-idle retention, stale MAC > forwarding, rate-limited refresh probes, and removal after three > unanswered probes. > > Assisted-by: GPT-5, OpenAI Codex > Signed-off-by: Tim Rozet <[email protected]> Hi Tim, I just started looking at this patch, but got stuck on the point below. Before I continue, could you take a look at my comment below? Thanks, Eelco > --- > lib/tnl-neigh-cache.c | 245 ++++++++++++++++++++++++----- > lib/tnl-neigh-cache.h | 3 +- > ofproto/ofproto-dpif-xlate-cache.c | 3 +- > ofproto/ofproto-dpif-xlate.c | 60 ++++--- > tests/tunnel-push-pop-ipv6.at | 44 +++++- > tests/tunnel-push-pop.at | 41 ++++- > 6 files changed, 330 insertions(+), 66 deletions(-) > > diff --git a/lib/tnl-neigh-cache.c b/lib/tnl-neigh-cache.c > index fbefc3d51..2c791af12 100644 > --- a/lib/tnl-neigh-cache.c > +++ b/lib/tnl-neigh-cache.c > @@ -48,14 +48,25 @@ > #define NEIGH_ENTRY_DEFAULT_IDLE_TIME_MS (15 * 60 * 1000) > #define NEIGH_ENTRY_MAX_AGING_TIME_S 3600 > #define NEIGH_ENTRY_LOOKUP_RETRANS_TIME 1000 > +#define NEIGH_ENTRY_MAX_PROBES 3 > +#define NEIGH_ENTRY_MAX_STALE 4096 > + > +enum tnl_neigh_state { > + TNL_NEIGH_INCOMPLETE, > + TNL_NEIGH_REACHABLE, > + TNL_NEIGH_STALE, > +}; > > struct tnl_neigh_entry { > struct cmap_node cmap_node; > struct in6_addr ip; > struct eth_addr mac; > - atomic_llong expires; /* Expiration time in ms. */ > + atomic_llong expires; /* Reachable/incomplete deadline in ms. */ > + atomic_llong used; /* Last successful lookup in ms. */ > + atomic_llong probe_expires; /* Next allowed stale probe in ms. */ > char br_name[IFNAMSIZ]; > - atomic_bool complete; > + atomic_uint n_probes; /* Unanswered probes while stale. */ > + atomic_uint state; /* enum tnl_neigh_state. */ > }; > > static struct cmap table = CMAP_INITIALIZER; > @@ -70,15 +81,24 @@ tnl_neigh_hash(const struct in6_addr *ip) > } > > static bool > -tnl_neigh_expired(struct tnl_neigh_entry *neigh) > +tnl_neigh_deadline_expired(atomic_llong *deadline) > { > long long expires; > > - atomic_read_explicit(&neigh->expires, &expires, memory_order_acquire); > + atomic_read_explicit(deadline, &expires, memory_order_acquire); > > return expires <= time_msec(); > } > > +static enum tnl_neigh_state > +tnl_neigh_get_state(struct tnl_neigh_entry *neigh) > +{ > + unsigned int state; > + > + atomic_read_explicit(&neigh->state, &state, memory_order_acquire); > + return state; > +} > + > static uint32_t > tnl_neigh_get_aging(void) > { > @@ -98,15 +118,6 @@ tnl_neigh_get_retrans_time(void) > return retrans_time; > } > > -static bool > -tnl_neigh_is_complete(struct tnl_neigh_entry *neigh) > -{ > - bool complete; > - > - atomic_read_explicit(&neigh->complete, &complete, memory_order_acquire); > - return complete; > -} > - > static struct tnl_neigh_entry * > tnl_neigh_lookup__(const char br_name[IFNAMSIZ], const struct in6_addr *dst) > { > @@ -116,16 +127,6 @@ tnl_neigh_lookup__(const char br_name[IFNAMSIZ], const > struct in6_addr *dst) > hash = tnl_neigh_hash(dst); > CMAP_FOR_EACH_WITH_HASH (neigh, cmap_node, hash, &table) { > if (ipv6_addr_equals(&neigh->ip, dst) && !strcmp(neigh->br_name, > br_name)) { > - if (tnl_neigh_expired(neigh)) { > - return NULL; > - } > - > - if (tnl_neigh_is_complete(neigh)) { > - atomic_store_explicit(&neigh->expires, > - time_msec() + tnl_neigh_get_aging(), > - memory_order_release); > - } > - > return neigh; > } > } > @@ -146,9 +147,12 @@ tnl_neigh_set_partial(const char name[IFNAMSIZ], const > struct in6_addr *dst) > neigh = xmalloc(sizeof *neigh); > > neigh->ip = *dst; > - atomic_store_relaxed(&neigh->complete, false); > atomic_store_relaxed(&neigh->expires, > time_msec() + tnl_neigh_get_retrans_time()); > + atomic_store_relaxed(&neigh->used, 0); > + atomic_store_relaxed(&neigh->probe_expires, 0); > + atomic_store_relaxed(&neigh->n_probes, 0); > + atomic_store_relaxed(&neigh->state, TNL_NEIGH_INCOMPLETE); > ovs_strlcpy(neigh->br_name, name, sizeof neigh->br_name); > cmap_insert(&table, &neigh->cmap_node, tnl_neigh_hash(&neigh->ip)); > > @@ -158,18 +162,79 @@ tnl_neigh_set_partial(const char name[IFNAMSIZ], const > struct in6_addr *dst) > > int > tnl_neigh_lookup(const char br_name[IFNAMSIZ], const struct in6_addr *dst, > - struct eth_addr *mac, bool insert_partial) > + struct eth_addr *mac, bool insert_partial, bool *stale, > + bool *probe) > { > struct tnl_neigh_entry *neigh; > + enum tnl_neigh_state state; > int res = ENOENT; > > + if (stale) { > + *stale = false; > + } > + if (probe) { > + *probe = false; > + } > + > neigh = tnl_neigh_lookup__(br_name, dst); > if (neigh) { > - if (tnl_neigh_is_complete(neigh)) { > + state = tnl_neigh_get_state(neigh); > + if (state == TNL_NEIGH_REACHABLE) { > + long long now = time_msec(); > + > *mac = neigh->mac; > + atomic_store_explicit(&neigh->expires, > + now + tnl_neigh_get_aging(), > + memory_order_release); If we reset the expires timer each time we use the MAC, and if this happens more frequently than every 15 min, the entry will never expire. If on top of this the MAC changes, we will black hole traffic for at least 15 min. And the tunnel flow might just be re-added every time it expires (30 seconds), so we might black hole forever. I think the kernel only updates this timer when there is actual reachability confirmation from the peer. > + atomic_store_explicit(&neigh->used, now, > + memory_order_release); > res = 0; > - } else { > + } else if (state == TNL_NEIGH_STALE) { > + long long expires; > + long long now = time_msec(); > + uint32_t retrans_time = tnl_neigh_get_retrans_time(); > + unsigned int n_probes; > + > + *mac = neigh->mac; > + if (stale) { > + *stale = true; > + } > + atomic_store_explicit(&neigh->used, now, > + memory_order_release); > + atomic_read_explicit(&neigh->n_probes, &n_probes, > + memory_order_acquire); > + atomic_read_explicit(&neigh->probe_expires, &expires, > + memory_order_acquire); > + if (probe && n_probes < NEIGH_ENTRY_MAX_PROBES && > + (!retrans_time || expires <= now)) { > + long long next = now + retrans_time; > + > + if (!retrans_time || > + atomic_compare_exchange_strong_explicit( > + &neigh->probe_expires, &expires, next, > + memory_order_acq_rel, memory_order_acquire)) { > + atomic_add_explicit(&neigh->n_probes, 1, &n_probes, > + memory_order_acq_rel); > + if (n_probes < NEIGH_ENTRY_MAX_PROBES) { > + *probe = true; > + } > + } > + } > + res = 0; > + } else if (!tnl_neigh_deadline_expired(&neigh->expires)) { > res = EINPROGRESS; > + } else if (insert_partial && tnl_neigh_get_retrans_time()) { > + long long expires; > + long long now = time_msec(); > + long long next = now + tnl_neigh_get_retrans_time(); > + > + atomic_read_explicit(&neigh->expires, &expires, > + memory_order_acquire); > + if (!atomic_compare_exchange_strong_explicit( > + &neigh->expires, &expires, next, > + memory_order_acq_rel, memory_order_acquire)) { > + res = EINPROGRESS; > + } > } > } else if (insert_partial && tnl_neigh_get_retrans_time()) { > /* Insert a partial entry only if there is a retransmit timer set. */ > @@ -193,22 +258,75 @@ tnl_neigh_delete(struct tnl_neigh_entry *neigh) > ovsrcu_postpone(neigh_entry_free, neigh); > } > > +static int > +tnl_neigh_compare_used(const void *a_, const void *b_) > +{ > + struct tnl_neigh_entry *const *a = a_; > + struct tnl_neigh_entry *const *b = b_; > + long long int a_used; > + long long int b_used; > + > + atomic_read_explicit(&(*a)->used, &a_used, memory_order_acquire); > + atomic_read_explicit(&(*b)->used, &b_used, memory_order_acquire); > + > + return a_used > b_used ? 1 : a_used < b_used ? -1 : 0; > +} > + > +/* Retain stale entries so that they remain usable after long idle periods, > + * but limit how much memory they can consume. */ > +static bool > +tnl_neigh_evict_stale(void) > +{ > + struct tnl_neigh_entry **entries; > + struct tnl_neigh_entry *neigh; > + size_t n_stale = 0; > + size_t i = 0; > + > + CMAP_FOR_EACH (neigh, cmap_node, &table) { > + if (tnl_neigh_get_state(neigh) == TNL_NEIGH_STALE) { > + n_stale++; > + } > + } > + if (n_stale <= NEIGH_ENTRY_MAX_STALE) { > + return false; > + } > + > + entries = xmalloc(n_stale * sizeof *entries); > + CMAP_FOR_EACH (neigh, cmap_node, &table) { > + if (tnl_neigh_get_state(neigh) == TNL_NEIGH_STALE) { > + entries[i++] = neigh; > + } > + } > + ovs_assert(i == n_stale); > + qsort(entries, n_stale, sizeof *entries, tnl_neigh_compare_used); > + > + for (i = 0; i < n_stale - NEIGH_ENTRY_MAX_STALE; i++) { > + tnl_neigh_delete(entries[i]); > + } > + free(entries); > + > + return true; > +} > + > void > tnl_neigh_set(const char name[IFNAMSIZ], const struct in6_addr *dst, > const struct eth_addr mac) > { > ovs_mutex_lock(&mutex); > struct tnl_neigh_entry *neigh = tnl_neigh_lookup__(name, dst); > + enum tnl_neigh_state state; > bool insert = true; > + bool changed = true; > + bool update_mac = true; > > if (neigh) { > - if (!tnl_neigh_is_complete(neigh)) { > + state = tnl_neigh_get_state(neigh); > + if (state == TNL_NEIGH_INCOMPLETE) { > insert = false; > } else if (eth_addr_equals(neigh->mac, mac)) { > - atomic_store_relaxed(&neigh->expires, > - time_msec() + tnl_neigh_get_aging()); > - ovs_mutex_unlock(&mutex); > - return; > + insert = false; > + update_mac = false; > + changed = state != TNL_NEIGH_REACHABLE; > } else { > tnl_neigh_delete(neigh); > } > @@ -221,18 +339,28 @@ tnl_neigh_set(const char name[IFNAMSIZ], const struct > in6_addr *dst, > ovs_strlcpy(neigh->br_name, name, sizeof neigh->br_name); > } > > - neigh->mac = mac; > + if (update_mac) { > + neigh->mac = mac; > + } > + long long now = time_msec(); > + > atomic_store_explicit(&neigh->expires, > - time_msec() + tnl_neigh_get_aging(), > + now + tnl_neigh_get_aging(), > + memory_order_release); > + atomic_store_explicit(&neigh->used, now, memory_order_release); > + atomic_store_relaxed(&neigh->probe_expires, 0); > + atomic_store_relaxed(&neigh->n_probes, 0); > + atomic_store_explicit(&neigh->state, TNL_NEIGH_REACHABLE, > memory_order_release); > - atomic_store_explicit(&neigh->complete, true, memory_order_release); > > if (insert) { > cmap_insert(&table, &neigh->cmap_node, tnl_neigh_hash(&neigh->ip)); > } > > ovs_mutex_unlock(&mutex); > - seq_change(tnl_conf_seq); > + if (changed) { > + seq_change(tnl_conf_seq); > + } > } > > static void > @@ -310,11 +438,34 @@ tnl_neigh_cache_run(void) > > ovs_mutex_lock(&mutex); > CMAP_FOR_EACH(neigh, cmap_node, &table) { > - if (tnl_neigh_expired(neigh)) { > + enum tnl_neigh_state state = tnl_neigh_get_state(neigh); > + > + if (state == TNL_NEIGH_REACHABLE && > + tnl_neigh_deadline_expired(&neigh->expires)) { > + long long now = time_msec(); > + > + atomic_store_relaxed(&neigh->probe_expires, now); > + atomic_store_relaxed(&neigh->n_probes, 0); > + atomic_store_explicit(&neigh->state, TNL_NEIGH_STALE, > + memory_order_release); > + changed = true; > + } else if (state == TNL_NEIGH_INCOMPLETE && > + tnl_neigh_deadline_expired(&neigh->expires)) { > tnl_neigh_delete(neigh); > changed = true; > + } else if (state == TNL_NEIGH_STALE) { > + unsigned int n_probes; > + > + atomic_read_explicit(&neigh->n_probes, &n_probes, > + memory_order_acquire); > + if (n_probes >= NEIGH_ENTRY_MAX_PROBES && > + tnl_neigh_deadline_expired(&neigh->probe_expires)) { > + tnl_neigh_delete(neigh); > + changed = true; > + } > } > } > + changed |= tnl_neigh_evict_stale(); > ovs_mutex_unlock(&mutex); > > if (changed) { > @@ -396,6 +547,9 @@ tnl_neigh_cache_aging(struct unixctl_conn *conn, int argc, > new_exp = time_msec() + aging; > > CMAP_FOR_EACH (neigh, cmap_node, &table) { > + if (tnl_neigh_get_state(neigh) != TNL_NEIGH_REACHABLE) { > + continue; > + } > atomic_read_explicit(&neigh->expires, &curr_exp, > memory_order_acquire); > if (new_exp < curr_exp) { > @@ -439,13 +593,18 @@ tnl_neigh_cache_retrans_time(struct unixctl_conn *conn, > int argc, > new_exp = time_msec() + retrans_time; > > CMAP_FOR_EACH (neigh, cmap_node, &table) { > - if (tnl_neigh_is_complete(neigh)) { > + atomic_llong *deadline; > + enum tnl_neigh_state state = tnl_neigh_get_state(neigh); > + > + if (state == TNL_NEIGH_REACHABLE) { > continue; > } > - atomic_read_explicit(&neigh->expires, &curr_exp, > + deadline = state == TNL_NEIGH_STALE > + ? &neigh->probe_expires : &neigh->expires; > + atomic_read_explicit(deadline, &curr_exp, > memory_order_acquire); > if (new_exp < curr_exp) { > - atomic_store_explicit(&neigh->expires, new_exp, > + atomic_store_explicit(deadline, new_exp, > memory_order_release); > } > } > @@ -511,14 +670,16 @@ tnl_neigh_cache_show(struct unixctl_conn *conn, int > argc OVS_UNUSED, > need_ws = INET6_ADDRSTRLEN - (ds.length - start_len); > ds_put_char_multiple(&ds, ' ', need_ws); > > - if (tnl_neigh_is_complete(neigh)) { > + enum tnl_neigh_state state = tnl_neigh_get_state(neigh); > + > + if (state != TNL_NEIGH_INCOMPLETE) { > ds_put_format(&ds, ETH_ADDR_FMT" %s", > ETH_ADDR_ARGS(neigh->mac), neigh->br_name); > } else { > ds_put_format(&ds, " %s INCOMPLETE", > neigh->br_name); > } > - if (tnl_neigh_expired(neigh)) { > + if (state == TNL_NEIGH_STALE) { > ds_put_format(&ds, " STALE"); > } > ds_put_char(&ds, '\n'); > diff --git a/lib/tnl-neigh-cache.h b/lib/tnl-neigh-cache.h > index e16155b4d..f98707964 100644 > --- a/lib/tnl-neigh-cache.h > +++ b/lib/tnl-neigh-cache.h > @@ -36,7 +36,8 @@ int tnl_neigh_snoop(const struct flow *flow, struct > flow_wildcards *wc, > void tnl_neigh_set(const char name[IFNAMSIZ], const struct in6_addr *dst, > const struct eth_addr mac); > int tnl_neigh_lookup(const char dev_name[IFNAMSIZ], const struct in6_addr > *dst, > - struct eth_addr *mac, bool insert_partial); > + struct eth_addr *mac, bool insert_partial, bool *stale, > + bool *probe); > void tnl_neigh_cache_init(void); > void tnl_neigh_cache_run(void); > void tnl_neigh_flush(const char dev_name[IFNAMSIZ]); > diff --git a/ofproto/ofproto-dpif-xlate-cache.c > b/ofproto/ofproto-dpif-xlate-cache.c > index cb37e2462..54e9e7bd1 100644 > --- a/ofproto/ofproto-dpif-xlate-cache.c > +++ b/ofproto/ofproto-dpif-xlate-cache.c > @@ -152,7 +152,8 @@ xlate_push_stats_entry(struct xc_entry *entry, > case XC_TNL_NEIGH: > /* Lookup neighbor to avoid timeout. */ > tnl_neigh_lookup(entry->tnl_neigh_cache.br_name, > - &entry->tnl_neigh_cache.d_ipv6, &dmac, false); > + &entry->tnl_neigh_cache.d_ipv6, &dmac, false, > + NULL, NULL); > break; > case XC_TUNNEL_HEADER: > if (entry->tunnel_hdr.operation == ADD) { > diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c > index 764dbd662..b3a65f676 100644 > --- a/ofproto/ofproto-dpif-xlate.c > +++ b/ofproto/ofproto-dpif-xlate.c > @@ -3792,6 +3792,30 @@ tnl_send_arp_request(struct xlate_ctx *ctx, const > struct xport *out_dev, > dp_packet_uninit(&packet); > } > > +static void > +tnl_send_neigh_request(struct xlate_ctx *ctx, const struct xport *out_dev, > + const struct eth_addr eth_src, > + const struct in6_addr *ip_src, > + const struct in6_addr *ip_dst) > +{ > + struct in6_addr nh_src = in6addr_any; > + struct in6_addr nh_dst = *ip_dst; > + ovs_be32 ip4_dst = in6_addr_get_mapped_ipv4(ip_dst); > + > + COVERAGE_INC(xlate_actions_neigh_sent); > + if (ovs_router_get_netdev_source_address( > + ip_dst, netdev_get_name(out_dev->netdev), &nh_src)) { > + nh_src = *ip_src; > + } > + > + if (ip4_dst) { > + tnl_send_arp_request(ctx, out_dev, eth_src, > + in6_addr_get_mapped_ipv4(&nh_src), ip4_dst); > + } else { > + tnl_send_nd_request(ctx, out_dev, eth_src, &nh_src, &nh_dst); > + } > +} > + > static void > propagate_tunnel_data_to_flow__(struct flow *dst_flow, > const struct flow *src_flow, > @@ -3894,6 +3918,8 @@ native_tunnel_output(struct xlate_ctx *ctx, const > struct xport *xport, > struct in6_addr d_ip6 = in6addr_any; > struct eth_addr smac; > struct eth_addr dmac; > + bool stale; > + bool probe; > int err; > char buf_sip6[INET6_ADDRSTRLEN]; > char buf_dip6[INET6_ADDRSTRLEN]; > @@ -3943,10 +3969,9 @@ native_tunnel_output(struct xlate_ctx *ctx, const > struct xport *xport, > s_ip = in6_addr_get_mapped_ipv4(&s_ip6); > } > > - err = tnl_neigh_lookup(out_dev->xbridge->name, &d_ip6, &dmac, true); > + err = tnl_neigh_lookup(out_dev->xbridge->name, &d_ip6, &dmac, true, > + &stale, &probe); > if (err) { > - struct in6_addr nh_s_ip6 = in6addr_any; > - > put_cloned_drop_action(ctx->xbridge->ofproto, ctx->odp_actions, > XLATE_TUNNEL_NEIGH_CACHE_MISS, > !is_last_action); > @@ -3962,26 +3987,21 @@ native_tunnel_output(struct xlate_ctx *ctx, const > struct xport *xport, > "neighbor cache miss for %s on bridge %s, " > "sending %s request", > buf_dip6, out_dev->xbridge->name, d_ip ? "ARP" : "ND"); > - COVERAGE_INC(xlate_actions_neigh_sent); > - > - err = ovs_router_get_netdev_source_address( > - &d_ip6, netdev_get_name(out_dev->netdev), &nh_s_ip6); > - > - if (err) { > - nh_s_ip6 = s_ip6; > - } > - > - if (d_ip) { > - ovs_be32 nh_s_ip; > - > - nh_s_ip = in6_addr_get_mapped_ipv4(&nh_s_ip6); > - tnl_send_arp_request(ctx, out_dev, smac, nh_s_ip, d_ip); > - } else { > - tnl_send_nd_request(ctx, out_dev, smac, &nh_s_ip6, &d_ip6); > - } > + tnl_send_neigh_request(ctx, out_dev, smac, &s_ip6, &d_ip6); > return err; > } > > + if (stale) { > + ctx->xout->avoid_caching = true; > + } > + if (probe) { > + xlate_report(ctx, OFT_DETAIL, > + "neighbor cache stale for %s on bridge %s, " > + "sending %s request", > + buf_dip6, out_dev->xbridge->name, d_ip ? "ARP" : "ND"); > + tnl_send_neigh_request(ctx, out_dev, smac, &s_ip6, &d_ip6); > + } > + > if (ctx->xin->xcache) { > struct xc_entry *entry; > > diff --git a/tests/tunnel-push-pop-ipv6.at b/tests/tunnel-push-pop-ipv6.at > index ec757f7d7..65a66745e 100644 > --- a/tests/tunnel-push-pop-ipv6.at > +++ b/tests/tunnel-push-pop-ipv6.at > @@ -393,9 +393,51 @@ AT_CHECK([ovs-appctl tnl/neigh/show | grep br0 | sort], > [0], [dnl > > ovs-appctl time/warp 5000 > > -dnl Check the entry has been removed > +dnl Check that an idle stale entry is retained indefinitely. > AT_CHECK([ovs-appctl tnl/neigh/show | grep br0 | sort], [0], [dnl > +2001:cafe::92 aa:bb:cc:00:00:01 br0 STALE > ]) > +ovs-appctl time/warp 15000 > +AT_CHECK([ovs-appctl tnl/neigh/show | grep br0 | sort], [0], [dnl > +2001:cafe::92 aa:bb:cc:00:00:01 br0 STALE > +]) > + > +dnl Check that the MAC remains usable while OVS refreshes the stale entry. > +AT_CHECK([ovs-ofctl add-flow int-br \ > + "priority=100,in_port=LOCAL,actions=2"]) > +AT_CHECK([ovs-appctl ofproto/trace ovs-dummy "in_port(int-br),]dnl > + [eth(src=aa:55:aa:55:00:00,dst=f8:bc:12:ff:ff:ff),eth_type(0x0800),]dnl > + [ipv4(src=1.1.3.92,dst=1.1.3.88,proto=1,tos=0,ttl=64,frag=no),]dnl > + [icmp(type=0,code=0)"], [0], [stdout]) > +AT_CHECK([tail -1 stdout | grep -q '^Datapath actions: tnl_push'], [0]) > +AT_CHECK([grep -q 'aa:bb:cc:00:00:01' stdout], [0]) > + > +dnl A second lookup uses the stale MAC without sending another ND request. > +AT_CHECK([ovs-appctl ofproto/trace ovs-dummy "in_port(int-br),]dnl > + [eth(src=aa:55:aa:55:00:00,dst=f8:bc:12:ff:ff:ff),eth_type(0x0800),]dnl > + [ipv4(src=1.1.3.92,dst=1.1.3.88,proto=1,tos=0,ttl=64,frag=no),]dnl > + [icmp(type=0,code=0)"], [0], [ignore]) > +OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | grep -c \ > + 92aa55aa55000086dd6000000000203aff2001cafe` -ge 2]) > +AT_CHECK([ovs-pcap p0.pcap | grep -c \ > + 92aa55aa55000086dd6000000000203aff2001cafe], [0], [2 > +]) > + > +dnl Remove the stale entry after three unanswered ND probes. > +ovs-appctl time/warp 5000 > +AT_CHECK([ovs-appctl ofproto/trace ovs-dummy "in_port(int-br),]dnl > + [eth(src=aa:55:aa:55:00:00,dst=f8:bc:12:ff:ff:ff),eth_type(0x0800),]dnl > + [ipv4(src=1.1.3.92,dst=1.1.3.88,proto=1,tos=0,ttl=64,frag=no),]dnl > + [icmp(type=0,code=0)"], [0], [ignore]) > +ovs-appctl time/warp 5000 > +AT_CHECK([ovs-appctl ofproto/trace ovs-dummy "in_port(int-br),]dnl > + [eth(src=aa:55:aa:55:00:00,dst=f8:bc:12:ff:ff:ff),eth_type(0x0800),]dnl > + [ipv4(src=1.1.3.92,dst=1.1.3.88,proto=1,tos=0,ttl=64,frag=no),]dnl > + [icmp(type=0,code=0)"], [0], [ignore]) > +OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | grep -c \ > + 92aa55aa55000086dd6000000000203aff2001cafe` -ge 4]) > +ovs-appctl time/warp 5000 > +AT_CHECK([ovs-appctl tnl/neigh/show | grep 2001:cafe::92], [1], []) > > dnl Restore the aging time to 900s (default) > AT_CHECK([ovs-appctl tnl/neigh/aging 900], [0], [OK > diff --git a/tests/tunnel-push-pop.at b/tests/tunnel-push-pop.at > index ab393cfc7..56d2cc324 100644 > --- a/tests/tunnel-push-pop.at > +++ b/tests/tunnel-push-pop.at > @@ -346,9 +346,48 @@ AT_CHECK([ovs-appctl tnl/neigh/show | grep br0 | sort], > [0], [dnl > > ovs-appctl time/warp 5000 > > -dnl Check the entry has been removed > +dnl Check that an idle stale entry is retained indefinitely. > AT_CHECK([ovs-appctl tnl/neigh/show | grep br0 | sort], [0], [dnl > +1.1.2.92 aa:bb:cc:00:00:01 br0 STALE > ]) > +ovs-appctl time/warp 15000 > +AT_CHECK([ovs-appctl tnl/neigh/show | grep br0 | sort], [0], [dnl > +1.1.2.92 aa:bb:cc:00:00:01 br0 STALE > +]) > + > +dnl Check that the MAC remains usable while OVS refreshes the stale entry. > +AT_CHECK([ovs-ofctl add-flow int-br \ > + "priority=100,in_port=LOCAL,actions=2"]) > +AT_CHECK([ovs-appctl ofproto/trace ovs-dummy "in_port(int-br),]dnl > + [eth(src=aa:55:aa:55:00:00,dst=f8:bc:12:ff:ff:ff),eth_type(0x0800),]dnl > + [ipv4(src=1.1.3.92,dst=1.1.3.88,proto=1,tos=0,ttl=64,frag=no),]dnl > + [icmp(type=0,code=0)"], [0], [stdout]) > +AT_CHECK([tail -1 stdout | grep -q '^Datapath actions: tnl_push'], [0]) > +AT_CHECK([grep -q 'aa:bb:cc:00:00:01' stdout], [0]) > + > +dnl A second lookup uses the stale MAC without sending another ARP request. > +AT_CHECK([ovs-appctl ofproto/trace ovs-dummy "in_port(int-br),]dnl > + [eth(src=aa:55:aa:55:00:00,dst=f8:bc:12:ff:ff:ff),eth_type(0x0800),]dnl > + [ipv4(src=1.1.3.92,dst=1.1.3.88,proto=1,tos=0,ttl=64,frag=no),]dnl > + [icmp(type=0,code=0)"], [0], [ignore]) > +OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | grep -c 101025c` -ge 2]) > +AT_CHECK([ovs-pcap p0.pcap | grep -c 101025c], [0], [2 > +]) > + > +dnl Remove the stale entry after three unanswered ARP probes. > +ovs-appctl time/warp 5000 > +AT_CHECK([ovs-appctl ofproto/trace ovs-dummy "in_port(int-br),]dnl > + [eth(src=aa:55:aa:55:00:00,dst=f8:bc:12:ff:ff:ff),eth_type(0x0800),]dnl > + [ipv4(src=1.1.3.92,dst=1.1.3.88,proto=1,tos=0,ttl=64,frag=no),]dnl > + [icmp(type=0,code=0)"], [0], [ignore]) > +ovs-appctl time/warp 5000 > +AT_CHECK([ovs-appctl ofproto/trace ovs-dummy "in_port(int-br),]dnl > + [eth(src=aa:55:aa:55:00:00,dst=f8:bc:12:ff:ff:ff),eth_type(0x0800),]dnl > + [ipv4(src=1.1.3.92,dst=1.1.3.88,proto=1,tos=0,ttl=64,frag=no),]dnl > + [icmp(type=0,code=0)"], [0], [ignore]) > +OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | grep -c 101025c` -ge 4]) > +ovs-appctl time/warp 5000 > +AT_CHECK([ovs-appctl tnl/neigh/show | grep br0], [1], []) > > dnl Restore the aging time to 900s (default) > AT_CHECK([ovs-appctl tnl/neigh/aging 900], [0], [OK > -- > 2.55.0 > > _______________________________________________ > dev mailing list > [email protected] > https://mail.openvswitch.org/mailman/listinfo/ovs-dev _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
