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. Do not extend the reachable deadline when the cached MAC is merely used. Only ARP or neighbor discovery learning confirms reachability and renews the deadline. Thus, active tunnel traffic still makes an unconfirmed entry stale and triggers a refresh. 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 active-use aging, 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]> --- v2: - Do not renew reachability merely because a cached MAC was used. - Test that an actively used entry still transitions to stale. - Serialize expired incomplete-entry retransmits. - Snapshot use times before sorting stale entries for eviction. lib/tnl-neigh-cache.c | 251 ++++++++++++++++++++++++----- lib/tnl-neigh-cache.h | 3 +- ofproto/ofproto-dpif-xlate-cache.c | 5 +- ofproto/ofproto-dpif-xlate.c | 60 ++++--- tests/tunnel-push-pop-ipv6.at | 56 ++++++- tests/tunnel-push-pop.at | 53 +++++- 6 files changed, 359 insertions(+), 69 deletions(-) diff --git a/lib/tnl-neigh-cache.c b/lib/tnl-neigh-cache.c index fbefc3d51..9c229d63a 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,82 @@ 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->used, now, + memory_order_release); + res = 0; + } 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 { - res = EINPROGRESS; + long long expires; + long long now = time_msec(); + uint32_t retrans_time = tnl_neigh_get_retrans_time(); + + /* Test and claim the same deadline so that only one thread + * retransmits after it expires. */ + atomic_read_explicit(&neigh->expires, &expires, + memory_order_acquire); + if (expires > now) { + res = EINPROGRESS; + } else if (insert_partial && retrans_time) { + long long next = now + retrans_time; + + 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 +261,78 @@ tnl_neigh_delete(struct tnl_neigh_entry *neigh) ovsrcu_postpone(neigh_entry_free, neigh); } +struct tnl_neigh_entry_ref { + struct tnl_neigh_entry *neigh; + long long int used; +}; + +static int +tnl_neigh_compare_used(const void *a_, const void *b_) +{ + const struct tnl_neigh_entry_ref *a = a_; + const struct tnl_neigh_entry_ref *b = b_; + + 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_ref *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 = neigh; + atomic_read_explicit(&neigh->used, &entries[i].used, + memory_order_acquire); + i++; + } + } + 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].neigh); + } + 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 +345,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 +444,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 +553,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 +599,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 +676,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..bb60ddcc6 100644 --- a/ofproto/ofproto-dpif-xlate-cache.c +++ b/ofproto/ofproto-dpif-xlate-cache.c @@ -150,9 +150,10 @@ xlate_push_stats_entry(struct xc_entry *entry, stats); break; case XC_TNL_NEIGH: - /* Lookup neighbor to avoid timeout. */ + /* Record neighbor use for stale-entry eviction. */ 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..726c71c11 100644 --- a/tests/tunnel-push-pop-ipv6.at +++ b/tests/tunnel-push-pop-ipv6.at @@ -391,11 +391,63 @@ AT_CHECK([ovs-appctl tnl/neigh/show | grep br0 | sort], [0], [dnl 2001:cafe::93 br0 INCOMPLETE ]) -ovs-appctl time/warp 5000 +AT_CHECK([ovs-ofctl add-flow int-br \ + "priority=100,in_port=LOCAL,actions=2"]) + +dnl Use the entry before it expires without confirming reachability. +ovs-appctl time/warp 3000 +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 Check the entry has been removed +ovs-appctl time/warp 2000 + +dnl Check that use did not refresh reachability and the entry is stale. 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-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..9c03e9c4c 100644 --- a/tests/tunnel-push-pop.at +++ b/tests/tunnel-push-pop.at @@ -344,11 +344,60 @@ AT_CHECK([ovs-appctl tnl/neigh/show | grep br0 | sort], [0], [dnl 1.1.2.92 aa:bb:cc:00:00:01 br0 ]) -ovs-appctl time/warp 5000 +AT_CHECK([ovs-ofctl add-flow int-br \ + "priority=100,in_port=LOCAL,actions=2"]) + +dnl Use the entry before it expires without confirming reachability. +ovs-appctl time/warp 3000 +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 Check the entry has been removed +ovs-appctl time/warp 2000 + +dnl Check that use did not refresh reachability and the entry is stale. 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-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
