Introduce the capability to actively refresh mac_binding entries available
in OFTABLE_MAC_BINDING table when they are inactive for more than the
configured threshold. Add the OFTABLE_MAC_BINDING table stats
monitoring. This feature avoids possible unnecessary mac_entry removals
if the destination is still alive but inactive for more than the configured
value.

Acked-by: Mark Michelson <mmich...@redhat.com>
Reported-at: https://issues.redhat.com/browse/FDP-1135
Signed-off-by: Lorenzo Bianconi <lorenzo.bianc...@redhat.com>
---
 controller/mac-cache.c      | 111 +++++++++++++++++-
 controller/mac-cache.h      |  26 ++++-
 controller/ovn-controller.c |  40 +++++--
 controller/pinctrl.c        |   8 +-
 controller/pinctrl.h        |   7 ++
 controller/statctrl.c       |  25 +++-
 tests/ovn.at                | 221 ++++++++++++++++++++++++++++++++++++
 tests/system-ovn.at         | 108 ++++++++++++++++++
 8 files changed, 521 insertions(+), 25 deletions(-)

diff --git a/controller/mac-cache.c b/controller/mac-cache.c
index 9d05c1950..5c4e10810 100644
--- a/controller/mac-cache.c
+++ b/controller/mac-cache.c
@@ -16,6 +16,7 @@
 #include <config.h>
 #include <stdbool.h>
 
+#include "lflow.h"
 #include "lib/mac-binding-index.h"
 #include "local_data.h"
 #include "lport.h"
@@ -24,6 +25,7 @@
 #include "openvswitch/vlog.h"
 #include "ovn/logical-fields.h"
 #include "ovn-sb-idl.h"
+#include "pinctrl.h"
 
 VLOG_DEFINE_THIS_MODULE(mac_cache);
 
@@ -160,17 +162,25 @@ mac_cache_thresholds_clear(struct mac_cache_data *data)
 /* MAC binding. */
 void
 mac_binding_add(struct hmap *map, struct mac_binding_data mb_data,
-                const struct sbrec_mac_binding *smb, long long timestamp)
+                const struct sbrec_mac_binding *smb,
+                const struct sbrec_port_binding *pb,
+                long long timestamp)
 {
     struct mac_binding *mb = mac_binding_find(map, &mb_data);
     if (!mb) {
-        mb = xmalloc(sizeof *mb);
+        mb = xzalloc(sizeof *mb);
         hmap_insert(map, &mb->hmap_node, mac_binding_data_hash(&mb_data));
     }
 
     mb->data = mb_data;
     mb->sbrec = smb;
     mb->timestamp = timestamp;
+    if (pb) {
+        destroy_lport_addresses(&mb->laddr);
+        if (extract_lsp_addresses(pb->mac[0], &mb->laddr)) {
+            mb->tunnel_key = pb->tunnel_key;
+        }
+    }
     mac_binding_update_log("Added", &mb_data, false, NULL, 0, 0);
 }
 
@@ -179,6 +189,7 @@ mac_binding_remove(struct hmap *map, struct mac_binding *mb)
 {
     mac_binding_update_log("Removed", &mb->data, false, NULL, 0, 0);
     hmap_remove(map, &mb->hmap_node);
+    destroy_lport_addresses(&mb->laddr);
     free(mb);
 }
 
@@ -237,6 +248,7 @@ mac_bindings_clear(struct hmap *map)
 {
     struct mac_binding *mb;
     HMAP_FOR_EACH_POP (mb, hmap_node, map) {
+        destroy_lport_addresses(&mb->laddr);
         free(mb);
     }
 }
@@ -399,7 +411,8 @@ mac_binding_update_log(const char *action,
 }
 
 void
-mac_binding_stats_run(struct ovs_list *stats_list, uint64_t *req_delay,
+mac_binding_stats_run(struct rconn *swconn OVS_UNUSED,
+                      struct ovs_list *stats_list, uint64_t *req_delay,
                       void *data)
 {
     struct mac_cache_data *cache_data = data;
@@ -495,8 +508,9 @@ fdb_update_log(const char *action,
 }
 
 void
-fdb_stats_run(struct ovs_list *stats_list, uint64_t *req_delay,
-              void *data)
+fdb_stats_run(struct rconn *swconn OVS_UNUSED,
+              struct ovs_list *stats_list,
+              uint64_t *req_delay, void *data)
 {
     struct mac_cache_data *cache_data = data;
     long long timewall_now = time_wall_msec();
@@ -847,3 +861,90 @@ buffered_packets_db_lookup(struct buffered_packets *bp, 
struct ds *ip,
 
     eth_addr_from_string(smb->mac, mac);
 }
+
+void
+mac_binding_probe_stats_process_flow_stats(
+        struct ovs_list *stats_list,
+        struct ofputil_flow_stats *ofp_stats)
+{
+    struct mac_cache_stats *stats = xmalloc(sizeof *stats);
+
+    stats->idle_age_ms = ofp_stats->idle_age * 1000;
+    stats->data.mb = (struct mac_binding_data) {
+        .cookie = ntohll(ofp_stats->cookie),
+        /* The port_key must be zero to match mac_binding_data_from_sbrec. */
+        .port_key = 0,
+        .dp_key = ntohll(ofp_stats->match.flow.metadata),
+    };
+
+    if (ofp_stats->match.flow.regs[0]) {
+        stats->data.mb.ip =
+            in6_addr_mapped_ipv4(htonl(ofp_stats->match.flow.regs[0]));
+    } else {
+        ovs_be128 ip6 = hton128(flow_get_xxreg(&ofp_stats->match.flow, 1));
+        memcpy(&stats->data.mb.ip, &ip6, sizeof stats->data.mb.ip);
+    }
+
+    ovs_list_push_back(stats_list, &stats->list_node);
+}
+
+void
+mac_binding_probe_stats_run(
+        struct rconn *swconn,
+        struct ovs_list *stats_list,
+        uint64_t *req_delay, void *data)
+{
+    long long timewall_now = time_wall_msec();
+    struct mac_cache_data *cache_data = data;
+
+    struct mac_cache_stats *stats;
+    LIST_FOR_EACH_POP (stats, list_node, stats_list) {
+        struct mac_binding *mb = mac_binding_find(&cache_data->mac_bindings,
+                                                  &stats->data.mb);
+        if (!mb) {
+            mac_binding_update_log("Probe: not found in the cache:",
+                                   &stats->data.mb, false, NULL, 0, 0);
+            free(stats);
+            continue;
+        }
+
+        struct mac_cache_threshold *threshold =
+                mac_cache_threshold_find(cache_data, mb->data.dp_key);
+        uint64_t since_updated_ms = timewall_now - mb->sbrec->timestamp;
+        const struct sbrec_mac_binding *sbrec = mb->sbrec;
+        mac_binding_update_log("Probe: trying to refresh", &mb->data, true,
+                               threshold, stats->idle_age_ms,
+                               since_updated_ms);
+
+        if (stats->idle_age_ms > threshold->value) {
+            free(stats);
+            continue;
+        }
+
+        if (since_updated_ms < threshold->value / 2) {
+            free(stats);
+            continue;
+        }
+
+        if (!mb->laddr.n_ipv4_addrs && !mb->laddr.n_ipv6_addrs) {
+            free(stats);
+            continue;
+        }
+
+        struct in6_addr local = mb->laddr.n_ipv4_addrs
+            ? in6_addr_mapped_ipv4(mb->laddr.ipv4_addrs[0].addr)
+            : mb->laddr.ipv6_addrs[0].addr;
+        send_self_originated_neigh_packet(swconn,
+                                          sbrec->datapath->tunnel_key,
+                                          mb->tunnel_key, mb->laddr.ea,
+                                          &local, &mb->data.ip,
+                                          OFTABLE_LOCAL_OUTPUT);
+        free(stats);
+    }
+
+    mac_cache_update_req_delay(&cache_data->thresholds, req_delay);
+    *req_delay = *req_delay / 2;
+    if (*req_delay) {
+        VLOG_DBG("MAC probe binding statistics delay: %"PRIu64, *req_delay);
+    }
+}
diff --git a/controller/mac-cache.h b/controller/mac-cache.h
index 40d7b9c25..eb2ad7b55 100644
--- a/controller/mac-cache.h
+++ b/controller/mac-cache.h
@@ -70,6 +70,10 @@ struct mac_binding {
     const struct sbrec_mac_binding *sbrec;
     /* User specified timestamp (in ms) */
     long long timestamp;
+    /* SB MAC binding logical port address. */
+    struct lport_addresses laddr;
+    /* SB Port binding tunnel key. */
+    int64_t tunnel_key;
 };
 
 struct fdb_data {
@@ -147,7 +151,9 @@ mac_binding_data_init(struct mac_binding_data *data,
 }
 
 void mac_binding_add(struct hmap *map, struct mac_binding_data mb_data,
-                     const struct sbrec_mac_binding *, long long timestamp);
+                     const struct sbrec_mac_binding *smb,
+                     const struct sbrec_port_binding *pb,
+                     long long timestamp);
 
 void mac_binding_remove(struct hmap *map, struct mac_binding *mb);
 
@@ -180,15 +186,17 @@ void
 mac_binding_stats_process_flow_stats(struct ovs_list *stats_list,
                                      struct ofputil_flow_stats *ofp_stats);
 
-void mac_binding_stats_run(struct ovs_list *stats_list, uint64_t *req_delay,
-                           void *data);
+void mac_binding_stats_run(struct rconn *swconn OVS_UNUSED,
+                           struct ovs_list *stats_list,
+                           uint64_t *req_delay, void *data);
 
 /* FDB stat processing. */
 void fdb_stats_process_flow_stats(struct ovs_list *stats_list,
                                   struct ofputil_flow_stats *ofp_stats);
 
-void fdb_stats_run(struct ovs_list *stats_list, uint64_t *req_delay,
-                   void *data);
+void fdb_stats_run(struct rconn *swconn OVS_UNUSED,
+                   struct ovs_list *stats_list,
+                   uint64_t *req_delay, void *data);
 
 void mac_cache_stats_destroy(struct ovs_list *stats_list);
 
@@ -221,4 +229,12 @@ bool buffered_packets_ctx_is_ready_to_send(struct 
buffered_packets_ctx *ctx);
 
 bool buffered_packets_ctx_has_packets(struct buffered_packets_ctx *ctx);
 
+void mac_binding_probe_stats_process_flow_stats(
+        struct ovs_list *stats_list,
+        struct ofputil_flow_stats *ofp_stats);
+
+void mac_binding_probe_stats_run(struct rconn *swconn,
+                                 struct ovs_list *stats_list,
+                                 uint64_t *req_delay, void *data);
+
 #endif /* controller/mac-cache.h */
diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
index 081411cba..5749f316b 100644
--- a/controller/ovn-controller.c
+++ b/controller/ovn-controller.c
@@ -3054,14 +3054,17 @@ en_lb_data_cleanup(void *data)
 
 static void
 mac_binding_add_sb(struct mac_cache_data *data,
-                   const struct sbrec_mac_binding *smb)
+                   const struct sbrec_mac_binding *smb,
+                   struct ovsdb_idl_index *sbrec_port_binding_by_name)
 {
     struct mac_binding_data mb_data;
     if (!mac_binding_data_from_sbrec(&mb_data, smb)) {
         return;
     }
 
-    mac_binding_add(&data->mac_bindings, mb_data, smb, 0);
+    const struct sbrec_port_binding *pb = lport_lookup_by_name(
+            sbrec_port_binding_by_name, smb->logical_port);
+    mac_binding_add(&data->mac_bindings, mb_data, smb, pb, 0);
 }
 
 static void
@@ -3113,7 +3116,8 @@ fdb_remove_sb(struct mac_cache_data *data, const struct 
sbrec_fdb *sfdb)
 static void
 mac_cache_mb_handle_for_datapath(struct mac_cache_data *data,
                                  const struct sbrec_datapath_binding *dp,
-                                 struct ovsdb_idl_index *sbrec_mb_by_dp)
+                                 struct ovsdb_idl_index *sbrec_mb_by_dp,
+                                 struct ovsdb_idl_index *sbrec_pb_by_name)
 {
     bool has_threshold = mac_cache_threshold_find(data, dp->tunnel_key);
 
@@ -3124,7 +3128,7 @@ mac_cache_mb_handle_for_datapath(struct mac_cache_data 
*data,
     const struct sbrec_mac_binding *mb;
     SBREC_MAC_BINDING_FOR_EACH_EQUAL (mb, mb_index_row, sbrec_mb_by_dp) {
         if (has_threshold) {
-            mac_binding_add_sb(data, mb);
+            mac_binding_add_sb(data, mb, sbrec_pb_by_name);
         } else {
             mac_binding_remove_sb(data, mb);
         }
@@ -3185,6 +3189,10 @@ en_mac_cache_run(struct engine_node *node, void *data)
             engine_ovsdb_node_get_index(
                     engine_get_input("SB_fdb", node),
                     "dp_key");
+    struct ovsdb_idl_index *sbrec_port_binding_by_name =
+            engine_ovsdb_node_get_index(
+                    engine_get_input("SB_port_binding", node),
+                    "name");
 
     mac_cache_thresholds_clear(cache_data);
     mac_bindings_clear(&cache_data->mac_bindings);
@@ -3199,7 +3207,8 @@ en_mac_cache_run(struct engine_node *node, void *data)
 
         mac_cache_threshold_add(cache_data, sbrec_dp);
         mac_cache_mb_handle_for_datapath(cache_data, sbrec_dp,
-                                         sbrec_mb_by_dp);
+                                         sbrec_mb_by_dp,
+                                         sbrec_port_binding_by_name);
         mac_cache_fdb_handle_for_datapath(cache_data, sbrec_dp,
                                           sbrec_fdb_by_dp_key);
     }
@@ -3216,6 +3225,10 @@ mac_cache_sb_mac_binding_handler(struct engine_node 
*node, void *data)
     const struct sbrec_mac_binding_table *mb_table =
             EN_OVSDB_GET(engine_get_input("SB_mac_binding", node));
     size_t previous_size = hmap_count(&cache_data->mac_bindings);
+    struct ovsdb_idl_index *sbrec_port_binding_by_name =
+            engine_ovsdb_node_get_index(
+                    engine_get_input("SB_port_binding", node),
+                    "name");
 
     const struct sbrec_mac_binding *sbrec_mb;
     SBREC_MAC_BINDING_TABLE_FOR_EACH_TRACKED (sbrec_mb, mb_table) {
@@ -3231,7 +3244,8 @@ mac_cache_sb_mac_binding_handler(struct engine_node 
*node, void *data)
 
         if (mac_cache_threshold_find(cache_data,
                                      sbrec_mb->datapath->tunnel_key)) {
-            mac_binding_add_sb(cache_data, sbrec_mb);
+            mac_binding_add_sb(cache_data, sbrec_mb,
+                               sbrec_port_binding_by_name);
         }
     }
 
@@ -3292,6 +3306,10 @@ mac_cache_runtime_data_handler(struct engine_node *node, 
void *data OVS_UNUSED)
             engine_ovsdb_node_get_index(
                     engine_get_input("SB_fdb", node),
                     "dp_key");
+    struct ovsdb_idl_index *sbrec_port_binding_by_name =
+            engine_ovsdb_node_get_index(
+                    engine_get_input("SB_port_binding", node),
+                    "name");
 
     /* There are no tracked data. Fall back to full recompute. */
     if (!rt_data->tracked) {
@@ -3312,7 +3330,8 @@ mac_cache_runtime_data_handler(struct engine_node *node, 
void *data OVS_UNUSED)
 
     HMAP_FOR_EACH (tdp, node, &rt_data->tracked_dp_bindings) {
         mac_cache_mb_handle_for_datapath(cache_data, tdp->dp,
-                                         sbrec_mb_by_dp);
+                                         sbrec_mb_by_dp,
+                                         sbrec_port_binding_by_name);
 
         mac_cache_fdb_handle_for_datapath(cache_data, tdp->dp,
                                           sbrec_fdb_by_dp_key);
@@ -3342,6 +3361,10 @@ mac_cache_sb_datapath_binding_handler(struct engine_node 
*node, void *data)
             engine_ovsdb_node_get_index(
                     engine_get_input("SB_fdb", node),
                     "dp_key");
+    struct ovsdb_idl_index *sbrec_port_binding_by_name =
+            engine_ovsdb_node_get_index(
+                    engine_get_input("SB_port_binding", node),
+                    "name");
 
     size_t previous_mb_size = hmap_count(&cache_data->mac_bindings);
     size_t previous_fdb_size = hmap_count(&cache_data->fdbs);
@@ -3365,7 +3388,8 @@ mac_cache_sb_datapath_binding_handler(struct engine_node 
*node, void *data)
 
     SBREC_DATAPATH_BINDING_TABLE_FOR_EACH_TRACKED (sbrec_dp, dp_table) {
         mac_cache_mb_handle_for_datapath(cache_data, sbrec_dp,
-                                         sbrec_mb_by_dp);
+                                         sbrec_mb_by_dp,
+                                         sbrec_port_binding_by_name);
 
         mac_cache_fdb_handle_for_datapath(cache_data, sbrec_dp,
                                           sbrec_fdb_by_dp_key);
diff --git a/controller/pinctrl.c b/controller/pinctrl.c
index 47c4bf78b..1957ff771 100644
--- a/controller/pinctrl.c
+++ b/controller/pinctrl.c
@@ -4718,7 +4718,7 @@ pinctrl_handle_put_mac_binding(const struct flow *md,
                      ? random_range(MAX_MAC_BINDING_DELAY_MSEC) + 1
                      : 0;
     long long timestamp = time_msec() + delay;
-    mac_binding_add(&put_mac_bindings, mb_data, NULL, timestamp);
+    mac_binding_add(&put_mac_bindings, mb_data, NULL, NULL, timestamp);
 
     /* We can send the buffered packet once the main ovn-controller
      * thread calls pinctrl_run() and it writes the mac_bindings stored
@@ -4924,7 +4924,7 @@ run_buffered_binding(const struct sbrec_mac_binding_table 
*mac_binding_table,
             continue;
         }
 
-        mac_binding_add(&recent_mbs, mb_data, smb, 0);
+        mac_binding_add(&recent_mbs, mb_data, smb, pb, 0);
 
         const char *redirect_port =
             smap_get(&pb->options, "chassis-redirect-port");
@@ -4941,7 +4941,7 @@ run_buffered_binding(const struct sbrec_mac_binding_table 
*mac_binding_table,
         /* Add the same entry also for chassisredirect port as the buffered
          * traffic might be buffered on the cr port. */
         mb_data.port_key = pb->tunnel_key;
-        mac_binding_add(&recent_mbs, mb_data, smb, 0);
+        mac_binding_add(&recent_mbs, mb_data, smb, NULL, 0);
     }
 
     buffered_packets_ctx_run(&buffered_packets_ctx, &recent_mbs,
@@ -5303,7 +5303,7 @@ send_garp_rarp_delete(const char *lport)
     notify_pinctrl_handler();
 }
 
-static void
+void
 send_self_originated_neigh_packet(struct rconn *swconn,
                                   uint32_t dp_key, uint32_t port_key,
                                   struct eth_addr eth,
diff --git a/controller/pinctrl.h b/controller/pinctrl.h
index 8459f4f53..e33d7cbf5 100644
--- a/controller/pinctrl.h
+++ b/controller/pinctrl.h
@@ -31,6 +31,7 @@ struct ovsdb_idl_index;
 struct ovsdb_idl_txn;
 struct ovsrec_bridge;
 struct ovsrec_open_vswitch_table;
+struct rconn;
 struct sbrec_chassis;
 struct sbrec_dns_table;
 struct sbrec_controller_event_table;
@@ -77,4 +78,10 @@ struct activated_port {
 void tag_port_as_activated_in_engine(struct activated_port *ap);
 struct ovs_list *get_ports_to_activate_in_engine(void);
 bool pinctrl_is_port_activated(int64_t dp_key, int64_t port_key);
+void send_self_originated_neigh_packet(struct rconn *swconn,
+                                       uint32_t dp_key, uint32_t port_key,
+                                       struct eth_addr eth,
+                                       struct in6_addr *local,
+                                       struct in6_addr *target,
+                                       uint8_t table_id);
 #endif /* controller/pinctrl.h */
diff --git a/controller/statctrl.c b/controller/statctrl.c
index d3c70ccba..6052d2884 100644
--- a/controller/statctrl.c
+++ b/controller/statctrl.c
@@ -39,6 +39,7 @@ VLOG_DEFINE_THIS_MODULE(statctrl);
 enum stat_type {
     STATS_MAC_BINDING = 0,
     STATS_FDB,
+    STATS_MAC_BINDING_PROBE,
     STATS_MAX,
 };
 
@@ -62,7 +63,8 @@ struct stats_node {
                                struct ofputil_flow_stats *ofp_stats);
     /* Function to process the parsed stats.
      * This function runs in main thread locked behind mutex. */
-    void (*run)(struct ovs_list *stats_list, uint64_t *req_delay, void *data);
+    void (*run)(struct rconn *swconn, struct ovs_list *stats_list,
+                uint64_t *req_delay, void *data);
 };
 
 #define STATS_NODE(NAME, REQUEST, DESTROY, PROCESS, RUN)                   \
@@ -145,6 +147,18 @@ statctrl_init(void)
     STATS_NODE(FDB, fdb_request, mac_cache_stats_destroy,
                fdb_stats_process_flow_stats, fdb_stats_run);
 
+    struct ofputil_flow_stats_request mac_binding_probe_request = {
+            .cookie = htonll(0),
+            .cookie_mask = htonll(0),
+            .out_port = OFPP_ANY,
+            .out_group = OFPG_ANY,
+            .table_id = OFTABLE_MAC_BINDING,
+    };
+    STATS_NODE(MAC_BINDING_PROBE, mac_binding_probe_request,
+               mac_cache_stats_destroy,
+               mac_binding_probe_stats_process_flow_stats,
+               mac_binding_probe_stats_run);
+
     statctrl_ctx.thread = ovs_thread_create("ovn_statctrl",
                                             statctrl_thread_handler,
                                             &statctrl_ctx);
@@ -158,7 +172,11 @@ statctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
         return;
     }
 
-    void *node_data[STATS_MAX] = {mac_cache_data, mac_cache_data};
+    void *node_data[STATS_MAX] = {
+        mac_cache_data,
+        mac_cache_data,
+        mac_cache_data
+    };
 
     bool schedule_updated = false;
     long long now = time_msec();
@@ -168,7 +186,8 @@ statctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
         struct stats_node *node = &statctrl_ctx.nodes[i];
         uint64_t prev_delay = node->request_delay;
 
-        node->run(&node->stats_list, &node->request_delay, node_data[i]);
+        node->run(statctrl_ctx.swconn, &node->stats_list,
+                  &node->request_delay, node_data[i]);
 
         schedule_updated |=
                 statctrl_update_next_request_timestamp(node, now, prev_delay);
diff --git a/tests/ovn.at b/tests/ovn.at
index 509a0c6ce..5d9bc9310 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -36079,6 +36079,227 @@ OVN_CLEANUP([hv1])
 AT_CLEANUP
 ])
 
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([MAC binding aging - probing])
+AT_SKIP_IF([test $HAVE_SCAPY = no])
+ovn_start
+
+send_udp() {
+    local hv=$1 dev=$2 hdst=$3 hsrc=$4 idst=$5 isrc=$6
+    local packet=$(fmt_pkt "Ether(dst='${hdst}', src='${hsrc}')/ \
+                            IP(dst='${idst}', src='${isrc}')/UDP()")
+    as $hv ovs-appctl netdev-dummy/receive $dev $packet
+}
+
+dump_arp() {
+    local op=$1 eth_src=$2 eth_dst=$3 spa=$4 tpa=$5 hwdst=$6
+
+    local packet=$(fmt_pkt "Ether(dst='${eth_dst}', src='${eth_src}')/ \
+                      ARP(op=$op, hwsrc='${eth_src}', hwdst='${hwdst}', \
+                      psrc='${spa}', pdst='${tpa}')")
+    echo $packet
+}
+
+aging_th=5
+net_add n1
+sim_add hv1
+as hv1
+check ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+ovn-appctl -t ovn-controller vlog/set mac_cache:file:dbg pinctrl:file:dbg
+
+check ovn-nbctl                                                             \
+    -- ls-add ls1                                                           \
+    -- ls-add ls2                                                           \
+    -- lr-add lr                                                            \
+    -- set logical_router lr options:mac_binding_age_threshold=$aging_th    \
+    -- lrp-add lr lr-ls1 00:00:00:00:10:00 192.168.10.1/24                  \
+    -- lrp-add lr lr-ls2 00:00:00:00:20:00 192.168.20.1/24                  \
+    -- lsp-add ls1 ls1-lr                                                   \
+    -- lsp-set-type ls1-lr router                                           \
+    -- lsp-set-addresses ls1-lr router                                      \
+    -- lsp-set-options ls1-lr router-port=lr-ls1                            \
+    -- lsp-add ls1 vif1                                                     \
+    -- lsp-set-addresses vif1 "unknown"                                     \
+    -- lsp-add ls2 ls2-lr                                                   \
+    -- lsp-set-type ls2-lr router                                           \
+    -- lsp-set-addresses ls2-lr router                                      \
+    -- lsp-set-options ls2-lr router-port=lr-ls2                            \
+    -- lsp-add ls2 vif2                                                     \
+    -- lsp-set-addresses vif2 "unknown"
+
+check ovs-vsctl                                                             \
+    -- add-port br-int vif1                                                 \
+    -- set interface vif1 external-ids:iface-id=vif1                        \
+    options:tx_pcap=hv1/vif1-tx.pcap options:rxq_pcap=hv1/vif1-rx.pcap      \
+    -- add-port br-int vif2                                                 \
+    -- set interface vif2 external-ids:iface-id=vif2                        \
+    options:tx_pcap=hv1/vif2-tx.pcap options:rxq_pcap=hv1/vif2-rx.pcap
+
+OVN_POPULATE_ARP
+wait_for_ports_up
+check ovn-nbctl --wait=hv sync
+
+# Wait for pinctrl thread to be connected.
+OVS_WAIT_UNTIL([grep pinctrl hv1/ovn-controller.log | grep -q connected])
+
+send_garp hv1 vif1 2 00:00:00:00:10:1a ff:ff:ff:ff:ff:ff 192.168.10.100 
192.168.10.100
+wait_row_count mac_binding 1 ip="192.168.10.100" logical_port="lr-ls1"
+
+send_garp hv1 vif2 2 00:00:00:00:10:1b ff:ff:ff:ff:ff:ff 192.168.20.100 
192.168.20.100
+wait_row_count mac_binding 1 ip="192.168.20.100" logical_port="lr-ls2"
+
+# Send UDP traffic to create entries in OFTABLE_MAC_BINDING.
+# Please note we are using a different src mac address with respect to the GARP
+# one.
+send_udp hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:2a 192.168.20.100 
192.168.10.100
+send_udp hv1 vif2 00:00:00:00:20:00 00:00:00:00:10:2b 192.168.10.100 
192.168.20.100
+
+OVS_WAIT_UNTIL([$(ovs-ofctl dump-flows br-int table=OFTABLE_MAC_BINDING | \
+                  sed 
's/reg15=0x.,metadata=0x./reg15=<cleared>,metadata=<cleared>/g' | \
+                  grep -q "reg0=0xc0a80a64,reg15=<cleared>,metadata=<cleared> 
actions=mod_dl_dst:00:00:00:00:10:1a")])
+OVS_WAIT_UNTIL([$(ovs-ofctl dump-flows br-int table=OFTABLE_MAC_BINDING | \
+                  sed 
's/reg15=0x.,metadata=0x./reg15=<cleared>,metadata=<cleared>/g' | \
+                  grep -q "reg0=0xc0a81464,reg15=<cleared>,metadata=<cleared> 
actions=mod_dl_dst:00:00:00:00:10:1b")])
+
+ts0=$(fetch_column Mac_Binding timestamp ip=192.168.10.100)
+# Wait until entries in OFTABLE_MAC_CACHE_USE are stale.
+OVS_WAIT_UNTIL([test $(ovs-ofctl dump-flows br-int table=OFTABLE_MAC_CACHE_USE 
| \
+                       awk '/nw_src=192.168.10.100/{print substr($6,10,1)}') 
-ge ${aging_th}])
+
+# Send UDP traffic to refresh entries in OFTABLE_MAC_BINDING.
+send_udp hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:2a 192.168.20.100 
192.168.10.100
+send_udp hv1 vif2 00:00:00:00:20:00 00:00:00:00:10:2b 192.168.10.100 
192.168.20.100
+# Wait for ARP requests to be generated.
+dump_arp 1 00:00:00:00:20:00 ff:ff:ff:ff:ff:ff 192.168.20.1 192.168.20.100 
00:00:00:00:00:00 > expected1
+dump_arp 1 00:00:00:00:10:00 ff:ff:ff:ff:ff:ff 192.168.10.1 192.168.10.100 
00:00:00:00:00:00 > expected2
+
+OVN_CHECK_PACKETS_CONTAIN([hv1/vif1-tx.pcap], [expected2])
+# Check MAC_Binding timestamp is updated receiving the ARP replay.
+send_garp hv1 vif1 2 00:00:00:00:10:1a 00:00:00:00:10:00 192.168.10.100 
192.168.10.1
+OVS_WAIT_UNTIL([test $(fetch_column Mac_Binding timestamp ip=192.168.10.100) 
-gt $ts0])
+
+# Refresh OFTABLE_MAC_BINDING entires.
+send_udp hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:2a 192.168.20.100 
192.168.10.100
+send_udp hv1 vif2 00:00:00:00:20:00 00:00:00:00:10:2b 192.168.10.100 
192.168.20.100
+# Check OVN tries to refresh MAC Binding entries.
+dump_arp 1 00:00:00:00:10:00 ff:ff:ff:ff:ff:ff 192.168.10.1 192.168.10.100 
00:00:00:00:00:00 >> expected2
+wait_row_count mac_binding 0 ip="192.168.10.100" logical_port="lr-ls1"
+OVN_CHECK_PACKETS_CONTAIN([hv1/vif1-tx.pcap], [expected2])
+
+dump_arp 1 00:00:00:00:20:00 ff:ff:ff:ff:ff:ff 192.168.20.1 192.168.20.100 
00:00:00:00:00:00 >> expected1
+wait_row_count mac_binding 0 ip="192.168.20.100" logical_port="lr-ls2"
+OVN_CHECK_PACKETS_CONTAIN([hv1/vif2-tx.pcap], [expected1])
+
+OVN_CLEANUP([hv1])
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([MAC binding aging - probing GW router Dynamic Neigh])
+AT_SKIP_IF([test $HAVE_SCAPY = no])
+ovn_start
+
+send_imcp_echo_req() {
+    local hv=$1 dev=$2 hdst=$3 hsrc=$4 idst=$5 isrc=$6
+    local packet=$(fmt_pkt "Ether(dst='${hdst}', src='${hsrc}')/ \
+                            IP(dst='${idst}', src='${isrc}')/ICMP()")
+    as $hv ovs-appctl netdev-dummy/receive $dev $packet
+}
+
+dump_icmp() {
+    local hdst=$1 hsrc=$2 idst=$3 isrc=$4 ttl=$5 type=$6 chksum=$7
+    local packet=$(fmt_pkt "Ether(dst='${hdst}', src='${hsrc}')/ \
+                            IP(dst='${idst}', src='${isrc}', ttl=${ttl})/\
+                            ICMP(type=${type}, chksum=${chksum})")
+
+    echo $packet
+}
+
+aging_th=5
+net_add n1
+sim_add hv1
+as hv1
+check ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+ovn-appctl -t ovn-controller vlog/set mac_cache:file:dbg pinctrl:file:dbg
+
+check ovn-nbctl                                                             \
+    -- ls-add public                                                        \
+    -- ls-add join                                                          \
+    -- lr-add lr                                                            \
+    -- lr-add gw                                                            \
+    -- set Logical_Router gw options:chassis="hv1"                          \
+    -- set logical_router gw options:mac_binding_age_threshold=$aging_th    \
+    -- set logical_router gw options:dynamic_neigh_routers=true             \
+    -- lrp-add gw gw-public 00:00:00:00:10:00 192.168.10.1/24               \
+    -- lrp-add gw gw-join 00:00:00:00:20:00 192.168.20.1/24                 \
+    -- lsp-add public public-gw                                             \
+    -- lsp-set-type public-gw router                                        \
+    -- lsp-set-addresses public-gw router                                   \
+    -- lsp-set-options public-gw router-port=gw-public                      \
+    -- lsp-add public public                                                \
+    -- lsp-set-addresses public "unknown"                                   \
+    -- lsp-add join join-gw                                                 \
+    -- lsp-set-type join-gw router                                          \
+    -- lsp-set-addresses join-gw router                                     \
+    -- lsp-set-options join-gw router-port=gw-join                          \
+    -- lrp-add lr lr-join 00:00:00:00:30:00 192.168.20.2/24                 \
+    -- lsp-add join join-lr                                                 \
+    -- lsp-set-type join-lr router                                          \
+    -- lsp-set-addresses join-lr router                                     \
+    -- lsp-set-options join-lr router-port=lr-join                          \
+    -- lr-route-add lr 0.0.0.0/0 192.168.20.1
+
+check ovs-vsctl                                                             \
+    -- add-port br-int public                                               \
+    -- set interface public external-ids:iface-id=public                    \
+    options:tx_pcap=hv1/public-tx.pcap options:rxq_pcap=hv1/public-rx.pcap
+
+OVN_POPULATE_ARP
+wait_for_ports_up
+check ovn-nbctl --wait=hv sync
+
+dnl Wait for pinctrl thread to be connected.
+OVS_WAIT_UNTIL([grep pinctrl hv1/ovn-controller.log | grep -q connected])
+
+# Send a GARP for an external device to populate MAC Binding table.
+send_garp hv1 public 2 00:00:00:00:10:1a ff:ff:ff:ff:ff:ff 192.168.10.100 
192.168.10.100
+wait_row_count mac_binding 1 ip="192.168.10.100" logical_port="gw-public"
+
+# Send ICMP echo request to create entries in OFTABLE_MAC_BINDING.
+send_imcp_echo_req hv1 public 00:00:00:00:10:00 00:00:00:00:10:1a 192.168.20.2 
192.168.10.100
+dump_icmp 00:00:00:00:10:1a 00:00:00:00:10:00 192.168.10.100 192.168.20.2 253 
0 0 > expected
+wait_row_count mac_binding 1 ip="192.168.20.2" logical_port="gw-join"
+ts0=$(fetch_column Mac_Binding timestamp ip=192.168.20.2)
+OVN_CHECK_PACKETS_CONTAIN([hv1/public-tx.pcap], [expected])
+n_arp=$(ovs-ofctl dump-flows br-int table=OFTABLE_MAC_CACHE_USE |awk 
'/arp_spa=192.168.20.2/{print substr($4,11,1)}')
+
+OVS_WAIT_UNTIL([$(ovs-ofctl dump-flows br-int table=OFTABLE_MAC_BINDING | \
+                  sed 
's/reg15=0x.,metadata=0x./reg15=<cleared>,metadata=<cleared>/g' | \
+                  grep -q "reg0=0xc0a80a64,reg15=<cleared>,metadata=<cleared> 
actions=mod_dl_dst:00:00:00:00:10:1a")])
+OVS_WAIT_UNTIL([$(ovs-ofctl dump-flows br-int table=OFTABLE_MAC_BINDING | \
+                  sed 
's/reg15=0x.,metadata=0x./reg15=<cleared>,metadata=<cleared>/g' | \
+                  grep -q "reg0=0xc0a81402,reg15=<cleared>,metadata=<cleared> 
actions=mod_dl_dst:00:00:00:00:30:00")])
+
+# Check GW router does not send any ARP request in this case.
+wait_row_count mac_binding 1 ip="192.168.20.2" logical_port="gw-join"
+AT_CHECK([test "$(ovs-ofctl dump-flows br-int table=OFTABLE_MAC_CACHE_USE |awk 
'/arp_spa=192.168.20.2/{print substr($4,11,1)}')" = "$n_arp"])
+
+# Now drop ICMP echo reply in order to force OVN to arp the mac binding entry
+# for the distributed router lr.
+# Wait until entries in OFTABLE_MAC_CACHE_USE are stale.
+OVS_WAIT_UNTIL([test $(ovs-ofctl dump-flows br-int table=OFTABLE_MAC_CACHE_USE 
| \
+                       awk '/nw_src=192.168.10.100/{print substr($6,10,1)}') 
-ge ${aging_th}])
+n_arp=$(ovs-ofctl dump-flows br-int table=OFTABLE_MAC_CACHE_USE |awk 
'/arp_spa=192.168.20.2/{print substr($4,11,1)}')
+check ovn-nbctl --wait=hv acl-add join from-lport 1000 'inport == "join-lr" && 
icmp' drop
+send_imcp_echo_req hv1 public 00:00:00:00:10:00 00:00:00:00:10:1a 192.168.20.2 
192.168.10.100
+OVS_WAIT_UNTIL([test "$(ovs-ofctl dump-flows br-int 
table=OFTABLE_MAC_CACHE_USE |awk '/arp_spa=192.168.20.2/{print 
substr($4,11,1)}')" = "$((n_arp+2))"])
+
+OVN_CLEANUP([hv1])
+AT_CLEANUP
+])
+
 OVN_FOR_EACH_NORTHD([
 AT_SETUP([router port type update and then remove])
 ovn_start
diff --git a/tests/system-ovn.at b/tests/system-ovn.at
index 9982da7fe..d5858bec7 100644
--- a/tests/system-ovn.at
+++ b/tests/system-ovn.at
@@ -17240,3 +17240,111 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port 
patch-.*/d
 /connection dropped.*/d"])
 AT_CLEANUP
 ])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([Mac binding aging - Probing])
+AT_KEYWORDS([mac_binding_probing])
+ovn_start
+OVS_TRAFFIC_VSWITCHD_START()
+
+ADD_BR([br-int])
+ADD_BR([br-ext])
+ADD_BR([br-gw])
+
+check ovs-ofctl add-flow br-ext action=normal
+check ovs-ofctl add-flow br-gw action=normal
+# Set external-ids in br-int needed for ovn-controller
+check ovs-vsctl \
+        -- set Open_vSwitch . external-ids:system-id=hv1 \
+        -- set Open_vSwitch . 
external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
+        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
+        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
+        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
+
+# Start ovn-controller
+start_daemon ovn-controller
+
+check ovn-appctl -t ovn-controller vlog/set mac_cache:file:dbg pinctrl:file:dbg
+check ovn-nbctl lr-add lr
+check ovn-nbctl set logical_router lr options:chassis=hv1
+check ovn-nbctl set logical_router lr options:mac_binding_age_threshold=10
+
+check ovn-nbctl ls-add sw
+check ovn-nbctl ls-add public
+
+check ovn-nbctl lrp-add lr rp-sw 00:00:01:01:02:03 192.168.1.1/24
+check ovn-nbctl lrp-add lr rp-public 00:00:02:01:02:03 172.16.1.1/24
+
+check ovn-nbctl lsp-add sw sw-rp -- set Logical_Switch_Port sw-rp \
+    type=router options:router-port=rp-sw \
+    -- lsp-set-addresses sw-rp router
+
+check ovn-nbctl lsp-add public public-rp -- set Logical_Switch_Port public-rp \
+    type=router options:router-port=rp-public \
+    -- lsp-set-addresses public-rp router
+
+ADD_NAMESPACES(alice)
+ADD_VETH(alice, alice, br-int, "192.168.1.2/24", "f0:00:00:01:02:03", 
"192.168.1.1")
+check ovn-nbctl lsp-add sw alice -- lsp-set-addresses alice "f0:00:00:01:02:03 
192.168.1.2"
+
+check ovs-vsctl set Open_vSwitch . 
external-ids:ovn-bridge-mappings=phynet:br-ext
+check ovn-nbctl lsp-add public public \
+        -- lsp-set-addresses public unknown \
+        -- lsp-set-type public localnet \
+        -- lsp-set-options public network_name=phynet
+
+ADD_NAMESPACES(gw)
+ADD_VETH(gw0, gw, br-ext, "172.16.1.2/24", "f0:00:00:01:02:04", "172.16.1.1")
+ADD_VETH(gw1, gw, br-gw, "172.16.2.2/24", "f0:00:00:01:03:04")
+
+NS_CHECK_EXEC([gw], [sysctl -w net.ipv4.conf.all.forwarding=1],[0], [dnl
+net.ipv4.conf.all.forwarding = 1
+])
+
+ADD_NAMESPACES(bob)
+ADD_VETH(bob, bob, br-gw, "172.16.2.10/24", "f0:00:00:01:02:06", "172.16.2.2")
+
+check ovn-nbctl lr-route-add lr 0.0.0.0/0 172.16.1.2
+check ovn-nbctl --wait=hv sync
+
+NS_CHECK_EXEC([alice], [ping -q -c 3 -i 0.3 -w 2 172.16.2.10 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+check_row_count mac_binding 1 mac=\"f0:00:00:01:02:04\"
+mac_binding_uuid=$(fetch_column mac_binding _uuid logical_port=rp-public 
ip=172.16.1.2)
+
+NETNS_START_TCPDUMP([gw], [-n -c 2 -i gw0 arp[[6:2]] == 0x1 and arp[[24:4]] == 
0xac100102], [gw])
+NS_CHECK_EXEC([alice], [ping -q -c 20 -i 0.5 172.16.2.10 | FORMAT_PING], \
+[0], [dnl
+20 packets transmitted, 20 received, 0% packet loss, time 0ms
+])
+
+# Check mac binding entry is still active.
+check_row_count mac_binding 1 mac=\"f0:00:00:01:02:04\"
+OVS_WAIT_UNTIL([
+    n_arp=$(cat gw.tcpdump | wc -l)
+    test ${n_arp} -ge 2
+])
+
+AT_CHECK([test "$(fetch_column mac_binding _uuid logical_port=rp-public 
ip=172.16.1.2)" = "$mac_binding_uuid"])
+
+# Wait for the mac binding entry to expire.
+wait_row_count MAC_Binding 0
+
+OVS_APP_EXIT_AND_WAIT([ovn-controller])
+
+as ovn-sb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as ovn-nb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as northd
+OVS_APP_EXIT_AND_WAIT([ovn-northd])
+
+as
+OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d
+/.*terminating with signal 15.*/d"])
+AT_CLEANUP
+])
-- 
2.48.1

_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to