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]> --- 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); + 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
