The native userspace tunnel neighbor cache extends a complete entry's lifetime whenever it is used. An idle entry is instead removed when its aging timer expires. The first packet after removal is dropped while OVS resolves the neighbor again.
Refresh complete entries before they expire while keeping the cached MAC usable. Start up to three retransmission intervals before expiration, capped at half the aging interval, and retry at the retransmission interval while the entry remains valid. The cap prevents immediate repeated probes when aging and retransmission intervals are equal. Unanswered entries still expire at their original deadline. Track the next cache event and queue due refreshes. Normal main-loop iterations only compare the cached deadline; scan the table when an event is due. Send ARP or Neighbor Discovery requests through the bridge pipeline outside the neighbor mutex. Retain the IP interface on which the neighbor was learned so refresh preserves policy-routing context. For manually inserted entries, select a suitable IP interface on their bridge. Renew the aging deadline on neighbor learning, rather than cache lookup. Remove the now-unused XC_TNL_NEIGH statistics bookkeeping. Cold entries, flushed entries, and entries lost on restart still require resolution and can drop their triggering packet. Add IPv4 and IPv6 tests covering proactive refresh, retries, actual tunnel forwarding while refresh is pending and after renewal, and expiration without a reply. Also cover equal timers, source-based routing with two VTEPs, and learned and manual entries on a non-local tunnel endpoint. Verify that a changed neighbor MAC invalidates existing datapath actions and that unanswered expiration invalidates them even with ongoing traffic. Assisted-by: GPT-6, OpenAI Codex Signed-off-by: Tim Rozet <[email protected]> --- v3: - Replace stale-entry retention with proactive ARP/ND refresh. - Schedule up to three probes before expiration, retaining the cached MAC while awaiting a reply. - Preserve the learned interface for policy-routed tunnels. - Remove obsolete neighbor lookup statistics bookkeeping. - Test cached datapath forwarding after MAC changes and invalidate cached actions on unanswered expiration, addressing Eelco's concern. - Freeze test clocks and avoid immediate wakeups for queued probes. Previous discussion: https://mail.openvswitch.org/pipermail/ovs-dev/2026-September/436265.html lib/dpif-netdev.c | 1 + lib/tnl-neigh-cache.c | 191 ++++++++++++++++++++++++----- lib/tnl-neigh-cache.h | 6 +- ofproto/ofproto-dpif-xlate-cache.c | 10 -- ofproto/ofproto-dpif-xlate-cache.h | 5 - ofproto/ofproto-dpif-xlate.c | 80 ++++++++++-- ofproto/ofproto-dpif-xlate.h | 1 + ofproto/ofproto-dpif.c | 2 +- tests/tunnel-push-pop-ipv6.at | 111 ++++++++++++++++- tests/tunnel-push-pop.at | 164 ++++++++++++++++++++++++- 10 files changed, 510 insertions(+), 61 deletions(-) diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c index 4151ea056..7d397887a 100644 --- a/lib/dpif-netdev.c +++ b/lib/dpif-netdev.c @@ -5946,6 +5946,7 @@ dpif_netdev_wait(struct dpif *dpif) } ovs_rwlock_unlock(&dp->port_rwlock); ovs_mutex_unlock(&dp_netdev_mutex); + tnl_neigh_cache_wait(); seq_wait(tnl_conf_seq, dp->last_tnl_conf_seq); } diff --git a/lib/tnl-neigh-cache.c b/lib/tnl-neigh-cache.c index fbefc3d51..5d46b3135 100644 --- a/lib/tnl-neigh-cache.c +++ b/lib/tnl-neigh-cache.c @@ -33,6 +33,7 @@ #include "flow.h" #include "netdev.h" #include "ovs-atomic.h" +#include "openvswitch/list.h" #include "ovs-thread.h" #include "packets.h" #include "openvswitch/poll-loop.h" @@ -51,17 +52,23 @@ struct tnl_neigh_entry { struct cmap_node cmap_node; + struct ovs_list refresh_node; struct in6_addr ip; struct eth_addr mac; atomic_llong expires; /* Expiration time in ms. */ char br_name[IFNAMSIZ]; atomic_bool complete; + char dev_name[IFNAMSIZ]; /* IP interface used to learn the neighbor. */ + long long refresh_at; /* Next probe, protected by mutex. */ + bool refresh_pending; /* Queued for transmission. */ }; static struct cmap table = CMAP_INITIALIZER; +static struct ovs_list refresh_list = OVS_LIST_INITIALIZER(&refresh_list); static struct ovs_mutex mutex = OVS_MUTEX_INITIALIZER; static atomic_uint32_t neigh_aging; static atomic_uint32_t neigh_retrans_time; +static atomic_llong next_event; static uint32_t tnl_neigh_hash(const struct in6_addr *ip) @@ -98,6 +105,29 @@ tnl_neigh_get_retrans_time(void) return retrans_time; } +static void +tnl_neigh_schedule(long long deadline) + OVS_REQUIRES(mutex) +{ + long long next; + + atomic_read_explicit(&next_event, &next, memory_order_acquire); + if (deadline < next) { + atomic_store_explicit(&next_event, deadline, memory_order_release); + } +} + +static long long +tnl_neigh_refresh_deadline(long long expires) +{ + uint32_t refresh_time = tnl_neigh_get_retrans_time(); + + /* Leave time for two retries when the intervals permit, but always give + * a learned entry at least half its lifetime before probing again, + * including when aging == retrans. */ + return expires - MIN(3 * refresh_time, tnl_neigh_get_aging() / 2); +} + static bool tnl_neigh_is_complete(struct tnl_neigh_entry *neigh) { @@ -120,12 +150,6 @@ tnl_neigh_lookup__(const char br_name[IFNAMSIZ], const struct in6_addr *dst) return NULL; } - if (tnl_neigh_is_complete(neigh)) { - atomic_store_explicit(&neigh->expires, - time_msec() + tnl_neigh_get_aging(), - memory_order_release); - } - return neigh; } } @@ -135,6 +159,8 @@ tnl_neigh_lookup__(const char br_name[IFNAMSIZ], const struct in6_addr *dst) static void tnl_neigh_set_partial(const char name[IFNAMSIZ], const struct in6_addr *dst) { + long long expires; + ovs_mutex_lock(&mutex); struct tnl_neigh_entry *neigh = tnl_neigh_lookup__(name, dst); if (neigh) { @@ -145,12 +171,16 @@ tnl_neigh_set_partial(const char name[IFNAMSIZ], const struct in6_addr *dst) } neigh = xmalloc(sizeof *neigh); + ovs_list_init(&neigh->refresh_node); neigh->ip = *dst; atomic_store_relaxed(&neigh->complete, false); - atomic_store_relaxed(&neigh->expires, - time_msec() + tnl_neigh_get_retrans_time()); + expires = time_msec() + tnl_neigh_get_retrans_time(); + atomic_store_relaxed(&neigh->expires, expires); + neigh->refresh_at = LLONG_MAX; + neigh->refresh_pending = false; ovs_strlcpy(neigh->br_name, name, sizeof neigh->br_name); cmap_insert(&table, &neigh->cmap_node, tnl_neigh_hash(&neigh->ip)); + tnl_neigh_schedule(expires); ovs_mutex_unlock(&mutex); seq_change(tnl_conf_seq); @@ -185,18 +215,31 @@ neigh_entry_free(struct tnl_neigh_entry *neigh) free(neigh); } +static void +tnl_neigh_cancel_refresh(struct tnl_neigh_entry *neigh) +{ + if (neigh->refresh_pending) { + ovs_list_remove(&neigh->refresh_node); + neigh->refresh_pending = false; + } +} + static void tnl_neigh_delete(struct tnl_neigh_entry *neigh) { uint32_t hash = tnl_neigh_hash(&neigh->ip); + + tnl_neigh_cancel_refresh(neigh); cmap_remove(&table, &neigh->cmap_node, hash); ovsrcu_postpone(neigh_entry_free, neigh); } void tnl_neigh_set(const char name[IFNAMSIZ], const struct in6_addr *dst, - const struct eth_addr mac) + const struct eth_addr mac, const char *dev_name) { + long long expires; + ovs_mutex_lock(&mutex); struct tnl_neigh_entry *neigh = tnl_neigh_lookup__(name, dst); bool insert = true; @@ -205,8 +248,14 @@ tnl_neigh_set(const char name[IFNAMSIZ], const struct in6_addr *dst, if (!tnl_neigh_is_complete(neigh)) { insert = false; } else if (eth_addr_equals(neigh->mac, mac)) { - atomic_store_relaxed(&neigh->expires, - time_msec() + tnl_neigh_get_aging()); + expires = time_msec() + tnl_neigh_get_aging(); + atomic_store_relaxed(&neigh->expires, expires); + tnl_neigh_cancel_refresh(neigh); + if (dev_name[0]) { + ovs_strlcpy(neigh->dev_name, dev_name, sizeof neigh->dev_name); + } + neigh->refresh_at = tnl_neigh_refresh_deadline(expires); + tnl_neigh_schedule(neigh->refresh_at); ovs_mutex_unlock(&mutex); return; } else { @@ -217,19 +266,25 @@ tnl_neigh_set(const char name[IFNAMSIZ], const struct in6_addr *dst, if (insert) { neigh = xmalloc(sizeof *neigh); + ovs_list_init(&neigh->refresh_node); neigh->ip = *dst; + neigh->refresh_pending = false; ovs_strlcpy(neigh->br_name, name, sizeof neigh->br_name); } + expires = time_msec() + tnl_neigh_get_aging(); neigh->mac = mac; - atomic_store_explicit(&neigh->expires, - time_msec() + tnl_neigh_get_aging(), + tnl_neigh_cancel_refresh(neigh); + ovs_strlcpy(neigh->dev_name, dev_name, sizeof neigh->dev_name); + neigh->refresh_at = tnl_neigh_refresh_deadline(expires); + atomic_store_explicit(&neigh->expires, expires, 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)); } + tnl_neigh_schedule(neigh->refresh_at); ovs_mutex_unlock(&mutex); seq_change(tnl_conf_seq); @@ -237,15 +292,16 @@ tnl_neigh_set(const char name[IFNAMSIZ], const struct in6_addr *dst, static void tnl_arp_set(const char name[IFNAMSIZ], ovs_be32 dst, - const struct eth_addr mac) + const struct eth_addr mac, const char *dev_name) { struct in6_addr dst6 = in6_addr_mapped_ipv4(dst); - tnl_neigh_set(name, &dst6, mac); + tnl_neigh_set(name, &dst6, mac, dev_name); } static int tnl_arp_snoop(const struct flow *flow, struct flow_wildcards *wc, - const char name[IFNAMSIZ], bool allow_update) + const char name[IFNAMSIZ], const char dev_name[IFNAMSIZ], + bool allow_update) { /* Snoop normal ARP replies and gratuitous ARP requests/replies only */ if (!is_arp(flow) @@ -258,14 +314,15 @@ tnl_arp_snoop(const struct flow *flow, struct flow_wildcards *wc, memset(&wc->masks.nw_src, 0xff, sizeof wc->masks.nw_src); if (allow_update) { - tnl_arp_set(name, flow->nw_src, flow->arp_sha); + tnl_arp_set(name, flow->nw_src, flow->arp_sha, dev_name); } return 0; } static int tnl_nd_snoop(const struct flow *flow, struct flow_wildcards *wc, - const char name[IFNAMSIZ], bool allow_update) + const char name[IFNAMSIZ], const char dev_name[IFNAMSIZ], + bool allow_update) { if (!is_nd(flow, wc) || flow->tp_src != htons(ND_NEIGHBOR_ADVERT)) { return EINVAL; @@ -285,36 +342,66 @@ tnl_nd_snoop(const struct flow *flow, struct flow_wildcards *wc, memset(&wc->masks.nd_target, 0xff, sizeof wc->masks.nd_target); if (allow_update) { - tnl_neigh_set(name, &flow->nd_target, flow->arp_tha); + tnl_neigh_set(name, &flow->nd_target, flow->arp_tha, dev_name); } return 0; } int tnl_neigh_snoop(const struct flow *flow, struct flow_wildcards *wc, - const char name[IFNAMSIZ], bool allow_update) + const char name[IFNAMSIZ], const char dev_name[IFNAMSIZ], + bool allow_update) { int res; - res = tnl_arp_snoop(flow, wc, name, allow_update); + res = tnl_arp_snoop(flow, wc, name, dev_name, allow_update); if (res != EINVAL) { return res; } - return tnl_nd_snoop(flow, wc, name, allow_update); + return tnl_nd_snoop(flow, wc, name, dev_name, allow_update); } void tnl_neigh_cache_run(void) { struct tnl_neigh_entry *neigh; + long long wakeup = LLONG_MAX; + long long now = time_msec(); + long long scheduled; + uint32_t refresh_time; bool changed = false; + atomic_read_explicit(&next_event, &scheduled, memory_order_acquire); + if (scheduled > now) { + return; + } + + refresh_time = tnl_neigh_get_retrans_time(); ovs_mutex_lock(&mutex); CMAP_FOR_EACH(neigh, cmap_node, &table) { - if (tnl_neigh_expired(neigh)) { + long long deadline; + long long expires; + + atomic_read_explicit(&neigh->expires, &expires, + memory_order_acquire); + if (expires <= now) { tnl_neigh_delete(neigh); changed = true; + continue; } + + deadline = expires; + if (tnl_neigh_is_complete(neigh) && refresh_time + && !neigh->refresh_pending) { + if (neigh->refresh_at <= now) { + neigh->refresh_pending = true; + ovs_list_push_back(&refresh_list, &neigh->refresh_node); + } else { + deadline = MIN(expires, neigh->refresh_at); + } + } + wakeup = MIN(wakeup, deadline); } + atomic_store_explicit(&next_event, wakeup, memory_order_release); ovs_mutex_unlock(&mutex); if (changed) { @@ -322,6 +409,46 @@ tnl_neigh_cache_run(void) } } +/* Removes one pending refresh and copies its bridge, IP interface and + * destination. Returns false if no refresh is pending. */ +bool +tnl_neigh_get_refresh(char br_name[IFNAMSIZ], char dev_name[IFNAMSIZ], + struct in6_addr *dst) +{ + struct tnl_neigh_entry *neigh; + bool found = false; + + ovs_mutex_lock(&mutex); + if (!ovs_list_is_empty(&refresh_list)) { + neigh = CONTAINER_OF(ovs_list_pop_front(&refresh_list), + struct tnl_neigh_entry, refresh_node); + neigh->refresh_pending = false; + /* A queued request may wait for the translation configuration. Arm + * the retry only when dequeuing it, rather than repeatedly waking for + * an overdue request that is still pending. */ + neigh->refresh_at = time_msec() + tnl_neigh_get_retrans_time(); + tnl_neigh_schedule(neigh->refresh_at); + ovs_strlcpy(br_name, neigh->br_name, IFNAMSIZ); + ovs_strlcpy(dev_name, neigh->dev_name, IFNAMSIZ); + *dst = neigh->ip; + found = true; + } + ovs_mutex_unlock(&mutex); + + return found; +} + +void +tnl_neigh_cache_wait(void) +{ + long long wakeup; + + atomic_read_explicit(&next_event, &wakeup, memory_order_acquire); + if (wakeup != LLONG_MAX) { + poll_timer_wait_until(wakeup); + } +} + void tnl_neigh_flush(const char br_name[IFNAMSIZ]) { @@ -395,14 +522,19 @@ tnl_neigh_cache_aging(struct unixctl_conn *conn, int argc, atomic_store_explicit(&neigh_aging, aging, memory_order_release); new_exp = time_msec() + aging; + ovs_mutex_lock(&mutex); CMAP_FOR_EACH (neigh, cmap_node, &table) { atomic_read_explicit(&neigh->expires, &curr_exp, memory_order_acquire); if (new_exp < curr_exp) { atomic_store_explicit(&neigh->expires, new_exp, memory_order_release); + curr_exp = new_exp; } + neigh->refresh_at = tnl_neigh_refresh_deadline(curr_exp); } + tnl_neigh_schedule(time_msec()); + ovs_mutex_unlock(&mutex); unixctl_command_reply(conn, "OK"); } @@ -438,17 +570,19 @@ tnl_neigh_cache_retrans_time(struct unixctl_conn *conn, int argc, memory_order_release); new_exp = time_msec() + retrans_time; + ovs_mutex_lock(&mutex); CMAP_FOR_EACH (neigh, cmap_node, &table) { - if (tnl_neigh_is_complete(neigh)) { - continue; - } atomic_read_explicit(&neigh->expires, &curr_exp, memory_order_acquire); - if (new_exp < curr_exp) { + if (!tnl_neigh_is_complete(neigh) && new_exp < curr_exp) { atomic_store_explicit(&neigh->expires, new_exp, memory_order_release); + curr_exp = new_exp; } + neigh->refresh_at = tnl_neigh_refresh_deadline(curr_exp); } + tnl_neigh_schedule(time_msec()); + ovs_mutex_unlock(&mutex); unixctl_command_reply(conn, "OK"); } @@ -488,7 +622,7 @@ tnl_neigh_cache_add(struct unixctl_conn *conn, int argc OVS_UNUSED, return; } - tnl_neigh_set(br_name, &ip6, mac); + tnl_neigh_set(br_name, &ip6, mac, ""); unixctl_command_reply(conn, "OK"); } @@ -534,6 +668,7 @@ tnl_neigh_cache_init(void) { atomic_init(&neigh_aging, NEIGH_ENTRY_DEFAULT_IDLE_TIME_MS); atomic_init(&neigh_retrans_time, NEIGH_ENTRY_LOOKUP_RETRANS_TIME); + atomic_init(&next_event, LLONG_MAX); unixctl_command_register("tnl/arp/show", "", 0, 0, tnl_neigh_cache_show, NULL); unixctl_command_register("tnl/arp/set", "BRIDGE IP MAC", 3, 3, diff --git a/lib/tnl-neigh-cache.h b/lib/tnl-neigh-cache.h index e16155b4d..a4e27294a 100644 --- a/lib/tnl-neigh-cache.h +++ b/lib/tnl-neigh-cache.h @@ -32,13 +32,17 @@ #include "util.h" int tnl_neigh_snoop(const struct flow *flow, struct flow_wildcards *wc, + const char br_name[IFNAMSIZ], const char dev_name[IFNAMSIZ], bool allow_update); void tnl_neigh_set(const char name[IFNAMSIZ], const struct in6_addr *dst, - const struct eth_addr mac); + const struct eth_addr mac, const char *dev_name); int tnl_neigh_lookup(const char dev_name[IFNAMSIZ], const struct in6_addr *dst, struct eth_addr *mac, bool insert_partial); void tnl_neigh_cache_init(void); void tnl_neigh_cache_run(void); +bool tnl_neigh_get_refresh(char br_name[IFNAMSIZ], char dev_name[IFNAMSIZ], + struct in6_addr *dst); +void tnl_neigh_cache_wait(void); void tnl_neigh_flush(const char dev_name[IFNAMSIZ]); #endif diff --git a/ofproto/ofproto-dpif-xlate-cache.c b/ofproto/ofproto-dpif-xlate-cache.c index cb37e2462..9b06fdf6b 100644 --- a/ofproto/ofproto-dpif-xlate-cache.c +++ b/ofproto/ofproto-dpif-xlate-cache.c @@ -43,7 +43,6 @@ #include "openvswitch/vlog.h" #include "ovs-router.h" #include "packets.h" -#include "tnl-neigh-cache.h" #include "util.h" VLOG_DEFINE_THIS_MODULE(ofproto_xlate_cache); @@ -92,7 +91,6 @@ void xlate_push_stats_entry(struct xc_entry *entry, struct dpif_flow_stats *stats, bool offloaded) { - struct eth_addr dmac; switch (entry->type) { case XC_TABLE: @@ -149,11 +147,6 @@ xlate_push_stats_entry(struct xc_entry *entry, group_dpif_credit_stats(entry->group.group, entry->group.bucket, stats); break; - 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); - break; case XC_TUNNEL_HEADER: if (entry->tunnel_hdr.operation == ADD) { stats->n_bytes += stats->n_packets * entry->tunnel_hdr.hdr_size; @@ -241,8 +234,6 @@ xlate_cache_clear_entry(struct xc_entry *entry) case XC_GROUP: ofproto_group_unref(&entry->group.group->up); break; - case XC_TNL_NEIGH: - break; case XC_TUNNEL_HEADER: break; default: @@ -348,7 +339,6 @@ xlate_xcache_format(struct ds *s, const struct xlate_cache *xcache) case XC_LEARN: case XC_NORMAL: case XC_FIN_TIMEOUT: - case XC_TNL_NEIGH: case XC_TUNNEL_HEADER: break; } diff --git a/ofproto/ofproto-dpif-xlate-cache.h b/ofproto/ofproto-dpif-xlate-cache.h index e701734d7..adcd6856f 100644 --- a/ofproto/ofproto-dpif-xlate-cache.h +++ b/ofproto/ofproto-dpif-xlate-cache.h @@ -51,7 +51,6 @@ enum xc_type { XC_NORMAL, XC_FIN_TIMEOUT, /* Calls back to ofproto. */ XC_GROUP, - XC_TNL_NEIGH, XC_TUNNEL_HEADER, }; @@ -111,10 +110,6 @@ struct xc_entry { struct group_dpif *group; struct ofputil_bucket *bucket; } group; - struct { - char br_name[IFNAMSIZ]; - struct in6_addr d_ipv6; - } tnl_neigh_cache; struct { struct ofproto_dpif *ofproto; struct ofproto_async_msg *am; diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c index 4e7d6fb40..d31667ccc 100644 --- a/ofproto/ofproto-dpif-xlate.c +++ b/ofproto/ofproto-dpif-xlate.c @@ -3767,7 +3767,7 @@ compose_table_xlate(struct xlate_ctx *ctx, const struct xport *out_dev, struct ofpact_output output; struct flow flow; - if (!xlate_resubmit_resource_check(ctx)) { + if (ctx && !xlate_resubmit_resource_check(ctx)) { return 0; } @@ -3779,7 +3779,8 @@ compose_table_xlate(struct xlate_ctx *ctx, const struct xport *out_dev, return ofproto_dpif_execute_actions__(xbridge->ofproto, version, &flow, NULL, &output.ofpact, sizeof output, - ctx->depth + 1, ctx->resubmits, + ctx ? ctx->depth + 1 : 0, + ctx ? ctx->resubmits : 0, packet); } @@ -3811,6 +3812,68 @@ tnl_send_arp_request(struct xlate_ctx *ctx, const struct xport *out_dev, dp_packet_uninit(&packet); } +/* Sends all tunnel neighbor refresh requests queued by the cache. */ +void +xlate_tnl_neigh_refresh(void) +{ + struct xlate_cfg *xcfg = ovsrcu_get(struct xlate_cfg *, &xcfgp); + char br_name[IFNAMSIZ], out_dev_name[IFNAMSIZ]; + struct in6_addr dst; + + if (!xcfg) { + return; + } + + while (tnl_neigh_get_refresh(br_name, out_dev_name, &dst)) { + char dst_s[INET6_ADDRSTRLEN]; + struct in6_addr src; + struct eth_addr smac; + struct xport *out_dev = NULL; + struct xbridge *xbridge = NULL; + struct xbridge *iter; + struct xport *port; + + HMAP_FOR_EACH (iter, hmap_node, &xcfg->xbridges) { + if (!strcmp(iter->name, br_name)) { + xbridge = iter; + break; + } + } + if (!xbridge) { + continue; + } + + ipv6_string_mapped(dst_s, &dst); + VLOG_DBG("refreshing tunnel neighbor %s on bridge %s", + dst_s, xbridge->name); + + /* Manually inserted entries have no learned IP interface. Select + * an interface on their bridge that can address this neighbor. */ + HMAP_FOR_EACH (port, ofp_node, &xbridge->xports) { + const char *name = netdev_get_name(port->netdev); + + if ((!out_dev_name[0] || !strncmp(name, out_dev_name, IFNAMSIZ)) + && !ovs_router_get_netdev_source_address(&dst, name, &src)) { + out_dev = port; + break; + } + } + if (!out_dev || netdev_get_etheraddr(out_dev->netdev, &smac)) { + VLOG_DBG("no output port for tunnel neighbor %s", dst_s); + continue; + } + + COVERAGE_INC(xlate_actions_neigh_sent); + if (IN6_IS_ADDR_V4MAPPED(&dst)) { + tnl_send_arp_request(NULL, out_dev, smac, + in6_addr_get_mapped_ipv4(&src), + in6_addr_get_mapped_ipv4(&dst)); + } else { + tnl_send_nd_request(NULL, out_dev, smac, &src, &dst); + } + } +} + static void propagate_tunnel_data_to_flow__(struct flow *dst_flow, const struct flow *src_flow, @@ -4001,15 +4064,6 @@ native_tunnel_output(struct xlate_ctx *ctx, const struct xport *xport, return err; } - if (ctx->xin->xcache) { - struct xc_entry *entry; - - entry = xlate_cache_add_entry(ctx->xin->xcache, XC_TNL_NEIGH); - ovs_strlcpy(entry->tnl_neigh_cache.br_name, out_dev->xbridge->name, - sizeof entry->tnl_neigh_cache.br_name); - entry->tnl_neigh_cache.d_ipv6 = d_ip6; - } - xlate_report(ctx, OFT_DETAIL, "tunneling from "ETH_ADDR_FMT" %s" " to "ETH_ADDR_FMT" %s", ETH_ADDR_ARGS(smac), ipv6_string_mapped(buf_sip6, &s_ip6), @@ -4476,6 +4530,7 @@ terminate_native_tunnel(struct xlate_ctx *ctx, const struct xport *xport, if (*tnl_port == ODPP_NONE && (check_neighbor_reply(ctx, flow) || is_garp(flow, wc))) { tnl_neigh_snoop(flow, wc, ctx->xbridge->name, + netdev_get_name(xport->netdev), ctx->xin->allow_side_effects); } else if (*tnl_port != ODPP_NONE && ctx->xin->allow_side_effects && @@ -4489,7 +4544,8 @@ terminate_native_tunnel(struct xlate_ctx *ctx, const struct xport *xport, s_ip6 = flow->ipv6_src; } - tnl_neigh_set(ctx->xbridge->name, &s_ip6, mac); + tnl_neigh_set(ctx->xbridge->name, &s_ip6, mac, + netdev_get_name(xport->netdev)); } } diff --git a/ofproto/ofproto-dpif-xlate.h b/ofproto/ofproto-dpif-xlate.h index d973a634a..7b87ed73c 100644 --- a/ofproto/ofproto-dpif-xlate.h +++ b/ofproto/ofproto-dpif-xlate.h @@ -231,6 +231,7 @@ enum ofperr xlate_resume(struct ofproto_dpif *, struct ofpbuf *odp_actions, enum slow_path_reason *, struct flow *, struct xlate_cache *); int xlate_send_packet(const struct ofport_dpif *, bool oam, struct dp_packet *); +void xlate_tnl_neigh_refresh(void); void xlate_mac_learning_update(const struct ofproto_dpif *ofproto, ofp_port_t in_port, struct eth_addr dl_src, diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c index 0f0f71d14..8ade83656 100644 --- a/ofproto/ofproto-dpif.c +++ b/ofproto/ofproto-dpif.c @@ -372,6 +372,7 @@ type_run(const char *type) if (dpif_run(backer->dpif)) { backer->need_revalidate = REV_RECONFIGURE; } + xlate_tnl_neigh_refresh(); udpif_run(backer->udpif); @@ -5172,7 +5173,6 @@ ofproto_dpif_xcache_execute(struct ofproto_dpif *ofproto, case XC_MIRROR: case XC_NORMAL: case XC_GROUP: - case XC_TNL_NEIGH: case XC_TUNNEL_HEADER: xlate_push_stats_entry(entry, stats, false); break; diff --git a/tests/tunnel-push-pop-ipv6.at b/tests/tunnel-push-pop-ipv6.at index ec757f7d7..4ef445a4c 100644 --- a/tests/tunnel-push-pop-ipv6.at +++ b/tests/tunnel-push-pop-ipv6.at @@ -373,7 +373,7 @@ AT_CHECK([cat p0.pcap.txt | grep 93aa55aa55000086dd6000000000203aff2001cafe | un ]) dnl Set the aging time to 5 seconds -AT_CHECK([ovs-appctl tnl/neigh/retrans_time 5000], [0], [OK +AT_CHECK([ovs-appctl tnl/neigh/retrans_time 1000], [0], [OK ]) AT_CHECK([ovs-appctl tnl/neigh/aging 5], [0], [OK ]) @@ -382,6 +382,9 @@ dnl Read the current aging time AT_CHECK([ovs-appctl tnl/neigh/aging], [0], [5 ]) +dnl Freeze time so deadlines depend only on explicit time/warp calls. +AT_CHECK([ovs-appctl time/stop]) + dnl Add an entry AT_CHECK([ovs-appctl tnl/neigh/set br0 2001:cafe::92 aa:bb:cc:00:00:01], [0], [OK ]) @@ -391,7 +394,50 @@ AT_CHECK([ovs-appctl tnl/neigh/show | grep br0 | sort], [0], [dnl 2001:cafe::93 br0 INCOMPLETE ]) -ovs-appctl time/warp 5000 +dnl Refresh early enough to retry twice before expiration. +AT_CHECK([ovs-pcap p0.pcap | grep 92aa55aa55000086dd | wc -l > nd_count]) +ovs-appctl time/warp 2500 +OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | grep 92aa55aa55000086dd | wc -l` \ + -gt `cat nd_count`]) + +dnl The cached MAC remains usable while the refresh is outstanding. +AT_CHECK([ovs-appctl tnl/neigh/show | grep br0 | sort], [0], [dnl +2001:cafe::92 aa:bb:cc:00:00:01 br0 +]) + +dnl Forward a packet through the tunnel while the refresh is unanswered. +AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out int-br CONTROLLER "output:2" '50540000000a5054000000091235']) +OVS_WAIT_UNTIL([ovs-pcap p0.pcap | grep '^aabbcc000001.*50540000000a5054000000091235' > /dev/null]) + +dnl A lost first probe is retried before the original expiration. +AT_CHECK([ovs-pcap p0.pcap | grep 92aa55aa55000086dd | wc -l > nd_count]) +ovs-appctl time/warp 1000 +OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | grep 92aa55aa55000086dd | wc -l` \ + -gt `cat nd_count`]) + +dnl A second lost probe gets one more attempt before expiration. +AT_CHECK([ovs-pcap p0.pcap | grep 92aa55aa55000086dd | wc -l > nd_count]) +ovs-appctl time/warp 1000 +OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | grep 92aa55aa55000086dd | wc -l` \ + -gt `cat nd_count`]) + +dnl A reply renews the entry and allows another refresh cycle. +AT_CHECK([ovs-appctl netdev-dummy/receive p0 'in_port(1),eth(src=aa:bb:cc:00:00:01,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=aa:bb:cc:00:00:01)']) +AT_CHECK([ovs-pcap p0.pcap | grep 92aa55aa55000086dd | wc -l > nd_count]) +ovs-appctl time/warp 4000 +OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | grep 92aa55aa55000086dd | wc -l` \ + -gt `cat nd_count`]) + +AT_CHECK([ovs-appctl tnl/neigh/show | grep br0 | sort], [0], [dnl +2001:cafe::92 aa:bb:cc:00:00:01 br0 +]) + +dnl Forward beyond the original deadline after the reply renewed the entry. +AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out int-br CONTROLLER "output:2" '50540000000a5054000000091236']) +OVS_WAIT_UNTIL([ovs-pcap p0.pcap | grep '^aabbcc000001.*50540000000a5054000000091236' > /dev/null]) + +dnl An unanswered refresh does not keep the entry past its expiration. +ovs-appctl time/warp 1000 dnl Check the entry has been removed AT_CHECK([ovs-appctl tnl/neigh/show | grep br0 | sort], [0], [dnl @@ -957,3 +1003,64 @@ AT_CHECK([grep -q "GENEVE_ACT" stdout]) OVS_VSWITCHD_STOP AT_CLEANUP + +AT_SETUP([tunnel_push_pop_ipv6 - neighbor changes revalidate cached flows]) + +OVS_VSWITCHD_START([add-port br0 p0 \ + -- set Interface p0 type=dummy ofport_request=1]) +AT_CHECK([ovs-vsctl add-br int-br -- set bridge int-br datapath_type=dummy \ + -- add-port int-br t1 -- set Interface t1 type=vxlan \ + options:remote_ip=2001:cafe::92 ofport_request=2]) +AT_CHECK([ovs-appctl netdev-dummy/ip6addr br0 2001:cafe::88/64], [0], [OK +]) +dnl Avoid NORMAL learning so it cannot independently trigger revalidation. +AT_CHECK([ovs-ofctl add-flow br0 'in_port=LOCAL,actions=1']) +AT_CHECK([ovs-ofctl add-flow br0 'in_port=1,actions=LOCAL']) +AT_CHECK([ovs-ofctl add-flow int-br 'in_port=LOCAL,actions=2']) +AT_CHECK([ovs-vsctl set Interface p0 options:tx_pcap=p0.pcap]) +AT_CHECK([ovs-appctl time/stop]) +AT_CHECK([ovs-appctl tnl/neigh/aging 5], [0], [OK +]) +AT_CHECK([ovs-appctl tnl/neigh/set br0 2001:cafe::92 aa:bb:cc:00:00:01], [0], [OK +]) + +packet=50540000000a5054000000091235 +dnl Install a datapath flow and exercise its cached action. +AT_CHECK([ovs-appctl netdev-dummy/receive int-br "$packet"]) +OVS_WAIT_UNTIL([ovs-appctl dpctl/dump-flows | grep 'tnl_push.*dst=aa:bb:cc:00:00:01' > /dev/null]) +AT_CHECK([ovs-appctl netdev-dummy/receive int-br "$packet"]) +OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | grep -c "^aabbcc000001.*$packet"` -eq 2]) +OVS_WAIT_UNTIL([ovs-appctl dpctl/dump-flows | grep 'packets:1,.*tnl_push.*dst=aa:bb:cc:00:00:01' > /dev/null]) + +AT_CHECK([ovs-appctl time/warp 2500], [0], [ignore]) +dnl The existing flow still forwards while the proactive probe is unanswered. +AT_CHECK([ovs-appctl netdev-dummy/receive int-br "$packet"]) +OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | grep -c "^aabbcc000001.*$packet"` -eq 3]) +AT_CHECK([ovs-appctl dpctl/dump-flows | grep -q 'tnl_push.*dst=aa:bb:cc:00:00:01']) + +dnl A reply with a changed MAC must invalidate the already-cached action. +AT_CHECK([ovs-appctl netdev-dummy/receive p0 'eth(src=aa:bb:cc:00:00:02,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=aa:bb:cc:00:00:02)']) +OVS_WAIT_UNTIL([test `ovs-appctl dpctl/dump-flows | grep -c 'tnl_push.*dst=aa:bb:cc:00:00:01'` -eq 0]) +AT_CHECK([ovs-appctl netdev-dummy/receive int-br "$packet"]) +OVS_WAIT_UNTIL([ovs-appctl dpctl/dump-flows | grep 'tnl_push.*dst=aa:bb:cc:00:00:02' > /dev/null]) +AT_CHECK([ovs-appctl netdev-dummy/receive int-br "$packet"]) +OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | grep -c "^aabbcc000002.*$packet"` -eq 2]) + +dnl Keep hitting the flow throughout the next three unanswered probes. +dnl Transmitting packets must not extend the neighbor's learned lifetime. +AT_CHECK([ovs-appctl time/warp 2500], [0], [ignore]) +AT_CHECK([ovs-appctl netdev-dummy/receive int-br "$packet"]) +AT_CHECK([ovs-appctl time/warp 1000], [0], [ignore]) +AT_CHECK([ovs-appctl netdev-dummy/receive int-br "$packet"]) +AT_CHECK([ovs-appctl time/warp 1000], [0], [ignore]) +AT_CHECK([ovs-appctl netdev-dummy/receive int-br "$packet"]) +OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | grep -c "^aabbcc000002.*$packet"` -eq 5]) +AT_CHECK([ovs-appctl dpctl/dump-flows | grep -q 'tnl_push.*dst=aa:bb:cc:00:00:02']) +AT_CHECK([ovs-appctl time/warp 500], [0], [ignore]) +OVS_WAIT_UNTIL([test `ovs-appctl dpctl/dump-flows | grep -c 'tnl_push.*dst=aa:bb:cc:00:00:02'` -eq 0]) +AT_CHECK([ovs-appctl netdev-dummy/receive int-br "$packet"]) +AT_CHECK([ovs-pcap p0.pcap | grep -c "^aabbcc000002.*$packet"], [0], [5 +]) + +OVS_VSWITCHD_STOP +AT_CLEANUP diff --git a/tests/tunnel-push-pop.at b/tests/tunnel-push-pop.at index ab393cfc7..d18c6aa0a 100644 --- a/tests/tunnel-push-pop.at +++ b/tests/tunnel-push-pop.at @@ -325,6 +325,9 @@ aging value cannot be less than retrans_time ovs-appctl: ovs-vswitchd: server returned an error ]) +AT_CHECK([ovs-appctl tnl/neigh/retrans_time 1000], [0], [OK +]) + dnl Set the aging time to 5 seconds AT_CHECK([ovs-appctl tnl/neigh/aging 5], [0], [OK ]) @@ -333,9 +336,12 @@ dnl Read the current aging time AT_CHECK([ovs-appctl tnl/neigh/aging], [0], [5 ]) -AT_CHECK([ovs-appctl tnl/neigh/retrans_time], [0], [5000 +AT_CHECK([ovs-appctl tnl/neigh/retrans_time], [0], [1000 ]) +dnl Freeze time so deadlines depend only on explicit time/warp calls. +AT_CHECK([ovs-appctl time/stop]) + dnl Add an entry AT_CHECK([ovs-appctl tnl/neigh/set br0 1.1.2.92 aa:bb:cc:00:00:01], [0], [OK ]) @@ -344,7 +350,50 @@ 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 +dnl Refresh early enough to retry twice before expiration. +AT_CHECK([ovs-pcap p0.pcap | grep 101025c | wc -l > arp_count]) +ovs-appctl time/warp 2500 +OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | grep 101025c | wc -l` \ + -gt `cat arp_count`]) + +dnl The cached MAC remains usable while the refresh is outstanding. +AT_CHECK([ovs-appctl tnl/neigh/show | grep br0 | sort], [0], [dnl +1.1.2.92 aa:bb:cc:00:00:01 br0 +]) + +dnl Forward a packet through the tunnel while the refresh is unanswered. +AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out int-br CONTROLLER "output:2" '50540000000a5054000000091235']) +OVS_WAIT_UNTIL([ovs-pcap p0.pcap | grep '^aabbcc000001.*50540000000a5054000000091235' > /dev/null]) + +dnl A lost first probe is retried before the original expiration. +AT_CHECK([ovs-pcap p0.pcap | grep 101025c | wc -l > arp_count]) +ovs-appctl time/warp 1000 +OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | grep 101025c | wc -l` \ + -gt `cat arp_count`]) + +dnl A second lost probe gets one more attempt before expiration. +AT_CHECK([ovs-pcap p0.pcap | grep 101025c | wc -l > arp_count]) +ovs-appctl time/warp 1000 +OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | grep 101025c | wc -l` \ + -gt `cat arp_count`]) + +dnl A reply renews the entry and allows another refresh cycle. +AT_CHECK([ovs-appctl netdev-dummy/receive p0 'recirc_id(0),in_port(1),eth(src=aa:bb:cc:00:00:01,dst=aa:55:aa:55:00:00),eth_type(0x0806),arp(sip=1.1.2.92,tip=1.1.2.88,op=2,sha=aa:bb:cc:00:00:01,tha=aa:55:aa:55:00:00)']) +AT_CHECK([ovs-pcap p0.pcap | grep 101025c | wc -l > arp_count]) +ovs-appctl time/warp 4000 +OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | grep 101025c | wc -l` \ + -gt `cat arp_count`]) + +AT_CHECK([ovs-appctl tnl/neigh/show | grep br0 | sort], [0], [dnl +1.1.2.92 aa:bb:cc:00:00:01 br0 +]) + +dnl Forward beyond the original deadline after the reply renewed the entry. +AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out int-br CONTROLLER "output:2" '50540000000a5054000000091236']) +OVS_WAIT_UNTIL([ovs-pcap p0.pcap | grep '^aabbcc000001.*50540000000a5054000000091236' > /dev/null]) + +dnl An unanswered refresh does not keep the entry past its expiration. +ovs-appctl time/warp 1000 dnl Check the entry has been removed AT_CHECK([ovs-appctl tnl/neigh/show | grep br0 | sort], [0], [dnl @@ -1304,6 +1353,26 @@ ipv4(src=1.1.2.88,dst=1.1.2.92,proto=47,tos=0,ttl=64,frag=0x4000),dnl gre((flags=0x0,proto=0x6558))),out_port(2)),1 ]) +dnl Refresh uses the non-local IP interface, including for manual entries. +AT_CHECK([ovs-appctl time/stop]) +AT_CHECK([ovs-appctl tnl/neigh/aging 5], [0], [OK +]) +AT_CHECK([ovs-vsctl set Interface p0 options:tx_pcap=refresh.pcap]) +AT_CHECK([ovs-appctl time/warp 3000], [0], [ignore]) +OVS_WAIT_UNTIL([test `ovs-pcap refresh.pcap | wc -l` -eq 1]) +AT_CHECK([ovs-pcap refresh.pcap], [0], [dnl +ffffffffffffaa55aa55000308060001080006040001aa55aa550003010102580000000000000101025c +]) +AT_CHECK([ovs-appctl tnl/neigh/flush], [0], [OK +]) +AT_CHECK([ovs-appctl tnl/neigh/set br0 1.1.2.92 f8:bc:12:44:34:b6], [0], [OK +]) +AT_CHECK([ovs-appctl time/warp 3000], [0], [ignore]) +OVS_WAIT_UNTIL([test `ovs-pcap refresh.pcap | wc -l` -eq 2]) +AT_CHECK([ovs-pcap refresh.pcap | sort -u], [0], [dnl +ffffffffffffaa55aa55000308060001080006040001aa55aa550003010102580000000000000101025c +]) + OVS_VSWITCHD_STOP AT_CLEANUP @@ -1614,5 +1683,96 @@ AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(int-br),dnl -> tunneling to 1.1.2.91 via br1 ]) +dnl Refresh must retain each learned interface despite the source-based routes. +dnl Equal aging/retrans timers must leave a quiet period after learning. +AT_CHECK([ovs-appctl time/stop]) +AT_CHECK([ovs-appctl tnl/neigh/aging 2], [0], [OK +]) +AT_CHECK([ovs-appctl tnl/neigh/retrans_time 2000], [0], [OK +]) +AT_CHECK([ovs-vsctl set Interface p0 options:tx_pcap=refresh0.pcap \ + -- set Interface p1 options:tx_pcap=refresh1.pcap]) +AT_CHECK([ovs-appctl time/warp 1], [0], [ignore]) +AT_CHECK([ovs-pcap refresh0.pcap | wc -l], [0], [0 +]) +AT_CHECK([ovs-pcap refresh1.pcap | wc -l], [0], [0 +]) +AT_CHECK([ovs-appctl time/warp 1000], [0], [ignore]) +OVS_WAIT_UNTIL([test `ovs-pcap refresh0.pcap | wc -l` -eq 1]) +OVS_WAIT_UNTIL([test `ovs-pcap refresh1.pcap | wc -l` -eq 1]) +AT_CHECK([ovs-pcap refresh0.pcap], [0], [dnl +ffffffffffffaa55aa55000008060001080006040001aa55aa550000010102500000000000000101025a +]) +AT_CHECK([ovs-pcap refresh1.pcap | grep -q '010102510000000000000101025b$']) +dnl Renew the same MAC without causing an immediate new probe. +AT_CHECK([ovs-appctl tnl/neigh/set br0 1.1.2.90 f8:bc:12:44:34:b0], [0], [OK +]) +AT_CHECK([ovs-appctl time/warp 1], [0], [ignore]) +AT_CHECK([ovs-pcap refresh0.pcap | wc -l], [0], [1 +]) +AT_CHECK([ovs-appctl time/warp 1000], [0], [ignore]) +OVS_WAIT_UNTIL([test `ovs-pcap refresh0.pcap | wc -l` -eq 2]) + +OVS_VSWITCHD_STOP +AT_CLEANUP + +AT_SETUP([tunnel_push_pop - neighbor changes revalidate cached flows]) + +OVS_VSWITCHD_START([add-port br0 p0 \ + -- set Interface p0 type=dummy ofport_request=1]) +AT_CHECK([ovs-vsctl add-br int-br -- set bridge int-br datapath_type=dummy \ + -- add-port int-br t1 -- set Interface t1 type=vxlan \ + options:remote_ip=1.1.2.92 ofport_request=2]) +AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK +]) +dnl Avoid NORMAL learning so it cannot independently trigger revalidation. +AT_CHECK([ovs-ofctl add-flow br0 'in_port=LOCAL,actions=1']) +AT_CHECK([ovs-ofctl add-flow br0 'in_port=1,actions=LOCAL']) +AT_CHECK([ovs-ofctl add-flow int-br 'in_port=LOCAL,actions=2']) +AT_CHECK([ovs-vsctl set Interface p0 options:tx_pcap=p0.pcap]) +AT_CHECK([ovs-appctl time/stop]) +AT_CHECK([ovs-appctl tnl/neigh/aging 5], [0], [OK +]) +AT_CHECK([ovs-appctl tnl/neigh/set br0 1.1.2.92 aa:bb:cc:00:00:01], [0], [OK +]) + +packet=50540000000a5054000000091235 +dnl Install a datapath flow and exercise its cached action. +AT_CHECK([ovs-appctl netdev-dummy/receive int-br "$packet"]) +OVS_WAIT_UNTIL([ovs-appctl dpctl/dump-flows | grep 'tnl_push.*dst=aa:bb:cc:00:00:01' > /dev/null]) +AT_CHECK([ovs-appctl netdev-dummy/receive int-br "$packet"]) +OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | grep -c "^aabbcc000001.*$packet"` -eq 2]) +OVS_WAIT_UNTIL([ovs-appctl dpctl/dump-flows | grep 'packets:1,.*tnl_push.*dst=aa:bb:cc:00:00:01' > /dev/null]) + +AT_CHECK([ovs-appctl time/warp 2500], [0], [ignore]) +dnl The existing flow still forwards while the proactive probe is unanswered. +AT_CHECK([ovs-appctl netdev-dummy/receive int-br "$packet"]) +OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | grep -c "^aabbcc000001.*$packet"` -eq 3]) +AT_CHECK([ovs-appctl dpctl/dump-flows | grep -q 'tnl_push.*dst=aa:bb:cc:00:00:01']) + +dnl A reply with a changed MAC must invalidate the already-cached action. +AT_CHECK([ovs-appctl netdev-dummy/receive p0 'eth(src=aa:bb:cc:00:00:02,dst=aa:55:aa:55:00:00),eth_type(0x0806),arp(sip=1.1.2.92,tip=1.1.2.88,op=2,sha=aa:bb:cc:00:00:02,tha=aa:55:aa:55:00:00)']) +OVS_WAIT_UNTIL([test `ovs-appctl dpctl/dump-flows | grep -c 'tnl_push.*dst=aa:bb:cc:00:00:01'` -eq 0]) +AT_CHECK([ovs-appctl netdev-dummy/receive int-br "$packet"]) +OVS_WAIT_UNTIL([ovs-appctl dpctl/dump-flows | grep 'tnl_push.*dst=aa:bb:cc:00:00:02' > /dev/null]) +AT_CHECK([ovs-appctl netdev-dummy/receive int-br "$packet"]) +OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | grep -c "^aabbcc000002.*$packet"` -eq 2]) + +dnl Keep hitting the flow throughout the next three unanswered probes. +dnl Transmitting packets must not extend the neighbor's learned lifetime. +AT_CHECK([ovs-appctl time/warp 2500], [0], [ignore]) +AT_CHECK([ovs-appctl netdev-dummy/receive int-br "$packet"]) +AT_CHECK([ovs-appctl time/warp 1000], [0], [ignore]) +AT_CHECK([ovs-appctl netdev-dummy/receive int-br "$packet"]) +AT_CHECK([ovs-appctl time/warp 1000], [0], [ignore]) +AT_CHECK([ovs-appctl netdev-dummy/receive int-br "$packet"]) +OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | grep -c "^aabbcc000002.*$packet"` -eq 5]) +AT_CHECK([ovs-appctl dpctl/dump-flows | grep -q 'tnl_push.*dst=aa:bb:cc:00:00:02']) +AT_CHECK([ovs-appctl time/warp 500], [0], [ignore]) +OVS_WAIT_UNTIL([test `ovs-appctl dpctl/dump-flows | grep -c 'tnl_push.*dst=aa:bb:cc:00:00:02'` -eq 0]) +AT_CHECK([ovs-appctl netdev-dummy/receive int-br "$packet"]) +AT_CHECK([ovs-pcap p0.pcap | grep -c "^aabbcc000002.*$packet"], [0], [5 +]) + OVS_VSWITCHD_STOP AT_CLEANUP -- 2.55.0 _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
