Hello Tiago Lam

I'm so sorry. I didn't find you until after completing my patch. Their function is the same.

https://patchwork.ozlabs.org/project/openvswitch/list/?series=33916

I can not cherry pick this patch to the master, so I just looked simple. I found our thinking is very similar.


Currently, any binding that is inserted in the SB DB MAC_Binding table
(dynamically discovered), is not renewed nor disposed of, if stalled. This
commit adds initial support for ARP renewal and expiration, cleaning the
binds if stalled.

The logic implemented is similar to how gARP and Ipv6 periodic router
advertisements are currently working, where, in the ovn-controller (more
specifically, pinctrl.c), a map is maintained to keep track of the ARP
state of each local logical port scanned from the SB DB which has a MAC-
Binding entry associated with it. For each of those entries:
   a. If the 'timestamp' of the entry has expired (taking into account the
   configured 'timeout'), send a Unicast ARP;
I think timestamp may not be suitable in the database. When lport 'schassis switch, I am not sure the time is consistent.
   b. If entry is still not updated within the 'timeout', send a broadcast
   ARP;
   c. If that hasn't resulted in an update either, the entry is disposed of.

A change in the SB DB schema was also required (a new 'timestamp' column in
the MAC_Binding table); to keep track of when each MAC-IP bind entry has
been updated.

By default the timeout for each logical port is 60 seconds. In order to
configure one can use (in this case to set it to 30s):

     ovn-nbctl set Logical_Router_Port ro-sw arp_configs:timeout=30

Finally. the test "ARP recheck and expiration" has also been written to
test the case where a logical router port receives an ARP reply and thus
needs to revalidate and expire/delete that MAC-IP bind (taking into account
the "timeout" configuration of the port). The test verifies that an ARP
unicast is sent, to revalidate the bind, followed by an ARP broadcast.

Signed-off-by: Tiago Lam <tiago...@gmail.com>
---
Note: This patch depends on the series at [1], and can't be directly applied to 
master.

This presents a first approach for tackling the "ARP renewal and expiration", 
any feedback / suggestions is welcome. There's also room for improvement, I think; With 
regards to gARP, since some work can be combined in the future; Or even on a more fancier 
logic between sending the Unicast and the Broadcast ARPs (sending the Unicast ARP 
multiple times before sending the Broadcast ARP, for example).

[1] https://patchwork.ozlabs.org/cover/837323/

---
  lib/packets.h            |   2 +
  ovn/controller/pinctrl.c | 312 +++++++++++++++++++++++++++++++++++++++++++++++
  ovn/northd/ovn-northd.c  |  27 ++++
  ovn/ovn-nb.ovsschema     |   7 +-
  ovn/ovn-nb.xml           |   9 ++
  ovn/ovn-sb.ovsschema     |   5 +-
  ovn/ovn-sb.xml           |   5 +
  tests/ovn.at             | 166 +++++++++++++++++++++++++
  8 files changed, 529 insertions(+), 4 deletions(-)

diff --git a/lib/packets.h b/lib/packets.h
index f545455..f13e903 100644
--- a/lib/packets.h
+++ b/lib/packets.h
@@ -1022,6 +1022,8 @@ BUILD_ASSERT_DECL(RA_MSG_LEN == sizeof(struct 
ovs_ra_msg));
  #define ND_RA_MAX_INTERVAL_DEFAULT 600
  #define ND_RA_MIN_INTERVAL_DEFAULT(max) ((max) >= 9 ? (max) / 3 : (max) * 3 / 
4)
+#define ND_ARP_DEFAULT_TIMEOUT 60
arp_configs is a good idea, but the default 1 minute may be short.
+
  /*
   * Use the same struct for MLD and MLD2, naming members as the defined fields 
in
   * in the corresponding version of the protocol, though they are reserved in 
the
diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c
index 7f6f55b..e5789be 100644
--- a/ovn/controller/pinctrl.c
+++ b/ovn/controller/pinctrl.c
@@ -95,6 +95,12 @@ static void ipv6_ra_wait(void);
  static void send_ipv6_ras(const struct controller_ctx *ctx,
      struct hmap *local_datapaths);
+static void init_arp_states(void);
+static void destroy_arp_states(void);
+static void send_arp(const struct controller_ctx *ctx,
+                     const struct sbrec_chassis *chassis,
+                     struct hmap *local_datapaths);
+
  COVERAGE_DEFINE(pinctrl_drop_put_mac_binding);
void
@@ -105,6 +111,7 @@ pinctrl_init(void)
      init_put_mac_bindings();
      init_send_garps();
      init_ipv6_ras();
+    init_arp_states();
  }
static ovs_be32
@@ -1091,6 +1098,7 @@ pinctrl_run(struct controller_ctx *ctx,
      send_garp_run(ctx, br_int, chassis, chassis_index, local_datapaths,
                    active_tunnels);
      send_ipv6_ras(ctx, local_datapaths);
+    send_arp(ctx, chassis, local_datapaths);
  }
/* Table of ipv6_ra_state structures, keyed on logical port name */
@@ -1425,6 +1433,304 @@ send_ipv6_ras(const struct controller_ctx *ctx 
OVS_UNUSED,
      }
  }
+/* Table of `arp_state` structures, keyed on logical port + IP of the mapped
+ * entry (concatenatedd), so to allow us to have all data in one flat structure
+ * (`arp_state`) */
+static struct shash arp_states;
+
+/* Next ARP in miliseconds. */
+static long long int send_arp_time;
+
+struct arp_config {
+    time_t timeout;
+    struct eth_addr eth_src;
+    struct eth_addr eth_dst;
+    ovs_be32 ipv4_src;
+    ovs_be32 ipv4_dst;
+};
+
+struct arp_state {
+    struct arp_config *config;
+    int64_t port_key;
+    int64_t metadata;
+    bool delete_me;
+    /* Tracking flags, used to differentiate between unicast and broadcast */
+    bool unicast;
+    bool broadcast;
+};
+
+static void
+init_arp_states(void)
+{
+    shash_init(&arp_states);
+    send_arp_time = LLONG_MAX;
+}
+
+static void arp_config_delete(struct arp_config *config)
+{
+    free(config);
+}
+
+static void
+arp_state_delete(struct arp_state *arp)
+{
+    if (arp) {
+        arp_config_delete(arp->config);
+    }
+    free(arp);
+}
+
+static void
+destroy_arp_states(void)
+{
+    struct shash_node *iter, *next;
+    SHASH_FOR_EACH_SAFE (iter, next, &arp_states) {
+        struct arp_state *arp = iter->data;
+        arp_state_delete(arp);
+        shash_delete(&arp_states, iter);
+    }
+    shash_destroy(&arp_states);
+}
+
+static struct arp_config *
+arp_update_config(const struct sbrec_port_binding *pb)
+{
+    struct arp_config *config;
+
+    config = xzalloc(sizeof *config);
+
+    config->timeout = smap_get_int(&pb->options, "arp_timeout",
+            ND_ARP_DEFAULT_TIMEOUT);
+
+    const char *eth_addr = smap_get(&pb->options, "arp_src_eth");
+    if (!eth_addr || !eth_addr_from_string(eth_addr, &config->eth_src)) {
+        VLOG_WARN("Invalid ARP ethernet source %s", eth_addr);
+        goto fail;
+    }
+    const char *ip_addr = smap_get(&pb->options, "arp_ip_src_addr");
+    if (!ip_addr || !ip_parse(ip_addr, &config->ipv4_src)) {
+        VLOG_WARN("Invalid ARP IP source %s", ip_addr);
+        goto fail;
+    }
+
+    return config;
+
+fail:
+    arp_config_delete(config);
+    return NULL;
+}
+
+static void
+arp_wait(void)
+{
+    poll_timer_wait_until(send_arp_time);
+}
+
+static void
+arp_send(struct arp_state *arp)
+{
+    /* Compose an ARP request packet. */
+    uint64_t packet_stub[128 / 8];
+    struct dp_packet packet;
+    /* Update packet with correct ARP dst + src IPs and MACs */
+    dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
+    compose_arp(&packet, ARP_OP_REQUEST, arp->config->eth_src,
+                arp->config->eth_dst, false, arp->config->ipv4_src,
+                arp->config->ipv4_dst);
+
+    /* Compose actions. */
+    uint64_t ofpacts_stub[4096 / 8];
+    struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub);
+    enum ofp_version version = rconn_get_version(swconn);
+
+    /* Set MFF_LOG_DATAPATH and MFF_LOG_INPORT. */
+    uint32_t dp_key = arp->metadata;
+    uint32_t port_key = arp->port_key;
I can't determine which is better to send to the logical-switch-port or to the localnet port.
+    put_load(dp_key, MFF_LOG_DATAPATH, 0, 64, &ofpacts);
+    put_load(port_key, MFF_LOG_INPORT, 0, 32, &ofpacts);
+    struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&ofpacts);
+    resubmit->in_port = OFPP_CONTROLLER;
+    resubmit->table_id = OFTABLE_LOG_INGRESS_PIPELINE;
+
+    struct ofputil_packet_out po = {
+        .packet = dp_packet_data(&packet),
+        .packet_len = dp_packet_size(&packet),
+        .buffer_id = UINT32_MAX,
+        .ofpacts = ofpacts.data,
+        .ofpacts_len = ofpacts.size,
+    };
+
+    match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER);
+    enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
+    queue_msg(ofputil_encode_packet_out(&po, proto));
+    dp_packet_uninit(&packet);
+    ofpbuf_uninit(&ofpacts);
+}
+
+/* This function is two-fold. 1. Keeps the `arp_states` map up to date with the
+ * SB DB, and 2. revalidates / deletes any MAC-IP bindings in the MAC_Binding
+ * table of the SB DB.
+ *
+ * During the revalidation of each entry, the following logic is performed:
+ * 1. Check if the 'timestamp' of the current entry has expired. If not,
+ * update the time of the next wake up of the loop. If so, continue with
+ * revalidating the entry:
+ *   a. If first time trying to revalidate, first send an Unicast ARP to the
+ *   dst MAC-IP mapped in the MAC_Binding table;
+ *   b. If Unicast has been sent, send a Broadcast ARP, in the hopes someone
+ *   else has the mapped IP;
+ *   c. Finally, if both Unicast and Broadcast ARPs have been sent (and the
+ *   'timestamp' of the entry still hasn't been updated), delete the entry
+ *   from the SB DB and the `arp_states` map. */
+static void
+send_arp(const struct controller_ctx *ctx OVS_UNUSED,
+         const struct sbrec_chassis *chassis,
+    struct hmap *local_datapaths)
+{
+    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+    struct shash_node *iter, *iter_next;
+
+    send_arp_time = LLONG_MAX;
+
+    SHASH_FOR_EACH (iter, &arp_states) {
+        struct arp_state *arp = iter->data;
+        arp->delete_me = true;
+    }
+
+    const struct local_datapath *ld;
+
+    HMAP_FOR_EACH (ld, hmap_node, local_datapaths) {
+        struct sbrec_port_binding *lpval;
+        const struct sbrec_port_binding *pb;
+        struct ovsdb_idl_index_cursor cursor;
+
+        lpval = sbrec_port_binding_index_init_row(ctx->ovnsb_idl,
+                                                  &sbrec_table_port_binding);
+        sbrec_port_binding_index_set_datapath(lpval, ld->datapath);
+        ovsdb_idl_initialize_cursor(ctx->ovnsb_idl, &sbrec_table_port_binding,
+                                    "lport-by-datapath", &cursor);
+
+        SBREC_PORT_BINDING_FOR_EACH_EQUAL (pb, &cursor, lpval) {
+            /* Check if port is bound to the current chassis */
+            if (!pb->chassis || strcmp(pb->chassis->name, chassis->name)) {
+                continue;
+            }
+
+            const struct sbrec_mac_binding *mb;
+            /* For each existing port, revalidate the existing MAC-IP
+             * mappings present in MAC_Binding.
+             *
+             * XXX Traverving all entries, not very efficient. */
+            SBREC_MAC_BINDING_FOR_EACH (mb, ctx->ovnsb_idl) {
+                if (strcmp(mb->logical_port, pb->logical_port)) {
+                    continue;
+                }
+
+                const char *peer_s;
+                peer_s = smap_get(&pb->options, "peer");
+                if (!peer_s) {
+                    continue;
+                }
+
+                const struct sbrec_port_binding *peer;
+                peer = lport_lookup_by_name(ctx->ovnsb_idl, peer_s);
+                if (!peer) {
+                    continue;
+                }
+
+                struct arp_config *config;
+                config = arp_update_config(pb);
+                if (!config) {
+                    continue;
+                }
+
+                /* Concatenate the logical port with the IP, to get the index
+                 * for `arp_states`. */
+                char *id = xasprintf("%s-%s", pb->logical_port, mb->ip);
+
+                struct arp_state *arp;
+                arp = shash_find_data(&arp_states, id);
+                if (!arp) {
+                    arp = xzalloc(sizeof *arp);
+                    arp->config = config;
+                    shash_add(&arp_states, id, arp);
+                } else {
+                    arp_config_delete(arp->config);
+                    arp->config = config;
+                }
+
+                /* Peer is the logical switch port that the logical router
+                 * port is connected to. The ARP is injected into that logical
+                 * switch port. */
+                arp->port_key = peer->tunnel_key;
+                arp->metadata = peer->datapath->tunnel_key;
+                arp->delete_me = false;
+
+                long long int curr_time = time_msec();
+                long timeout_ms = arp->config->timeout * 1000;
+                long next_time = mb->timestamp + timeout_ms;
+                if (curr_time <= next_time) {
+                    /* Entry hasn't expired yet */
+                    if (next_time < send_arp_time) {
+                        send_arp_time = next_time;
+                    }
+
+                    /* Reset tracking flags */
+                    arp->unicast = false;
+                    arp->broadcast = false;
+                    continue;
+                }
+
+                /* At this point the entry has expired. Thus:
+                 * 1. If Unicast hasn't been sent, send an Unicast ARP first;
+                 * 2. If Unicast has been sent, send a broadcast ARP;
+                 * 3. Otherwise, delete it as the bind is no longer valid. */
+                if (!arp->delete_me) {
+                    /* Update next time to perform the revalidation */
+                    next_time = curr_time + timeout_ms;
+                    if (!arp->unicast) {
+                        /* Send Unicat ARP for revalidation */
+                        eth_addr_from_string(mb->mac, &arp->config->eth_dst);
+                        ip_parse(mb->ip, &arp->config->ipv4_dst);
+                        arp->unicast = true;
+                        if (next_time < send_arp_time) {
+                            send_arp_time = next_time;
+                        }
+                        arp_send(arp);
+                    } else if (!arp->broadcast) {
+                        /* Send a Broadcast ARP in the hopes someone else has
+                         * it */
+                        eth_addr_from_string("ff:ff:ff:ff:ff:ff",
+                                             &arp->config->eth_dst);
+                        ip_parse(mb->ip, &arp->config->ipv4_dst);
+                        arp->broadcast = true;
+                        if (next_time < send_arp_time) {
+                            send_arp_time = next_time;
+                        }
+                        arp_send(arp);
+                    } else {
+                        /* We've tried both Unicast and Broadcast, no one
+                         * knows */
+                        VLOG_INFO_RL(&rl, "Dropping mapping '%s'->'%s' for"
+                                     "port '%s'", mb->ip, mb->mac,
+                                     pb->logical_port);
+                        sbrec_mac_binding_delete(mb);
+                        arp->delete_me = true;
+                    }
+                }
+            }
+        }
+    }
+
+    /* Remove those that are no longer in the SB database */
+    SHASH_FOR_EACH_SAFE (iter, iter_next, &arp_states) {
+        struct arp_state *arp = iter->data;
+        if (arp->delete_me) {
+            shash_delete(&arp_states, iter);
+            arp_state_delete(arp);
+        }
+    }
+}
void
  pinctrl_wait(struct controller_ctx *ctx)
@@ -1434,6 +1740,7 @@ pinctrl_wait(struct controller_ctx *ctx)
      rconn_recv_wait(swconn);
      send_garp_wait();
      ipv6_ra_wait();
+    arp_wait();
  }
void
@@ -1443,6 +1750,7 @@ pinctrl_destroy(void)
      destroy_put_mac_bindings();
      destroy_send_garps();
      destroy_ipv6_ras();
+    destroy_arp_states();
  }
  
  /* Implementation of the "put_arp" and "put_nd" OVN actions.  These
@@ -1570,6 +1878,9 @@ run_put_mac_binding(struct controller_ctx *ctx,
              && !strcmp(b->ip, pmb->ip_s)) {
              if (strcmp(b->mac, mac_string)) {
                  sbrec_mac_binding_set_mac(b, mac_string);
+                sbrec_mac_binding_set_timestamp(b, pmb->timestamp);
+            } else {
+                sbrec_mac_binding_set_timestamp(b, pmb->timestamp);
              }
              return;
          }
@@ -1581,6 +1892,7 @@ run_put_mac_binding(struct controller_ctx *ctx,
      sbrec_mac_binding_set_ip(b, pmb->ip_s);
      sbrec_mac_binding_set_mac(b, mac_string);
      sbrec_mac_binding_set_datapath(b, pb->datapath);
+    sbrec_mac_binding_set_timestamp(b, pmb->timestamp);
  }
static void
diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
index a77b7bc..8e7f728 100644
--- a/ovn/northd/ovn-northd.c
+++ b/ovn/northd/ovn-northd.c
@@ -4515,6 +4515,30 @@ copy_ra_to_sb(struct ovn_port *op, const char 
*address_mode)
  }
static void
+copy_arp_to_sb(struct ovn_port *op)
+{
+    struct smap options;
+    smap_clone(&options, &op->sb->options);
+
+    int timeout = smap_get_int(&op->nbrp->arp_configs,
+            "timeout", ND_ARP_DEFAULT_TIMEOUT);
+    if (timeout > ND_ARP_DEFAULT_TIMEOUT) {
+        timeout = ND_ARP_DEFAULT_TIMEOUT;
+    }
+    smap_add_format(&options, "arp_timeout", "%d", timeout);
+
+    for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; ++i) {
+        struct ipv4_netaddr *addrs = &op->lrp_networks.ipv4_addrs[i];
+        smap_add(&options, "arp_ip_src_addr", addrs->addr_s);
+    }
+
+    smap_add(&options, "arp_src_eth", op->lrp_networks.ea_s);
+

arp_ip_src_addr/arp_src_eth may in Port_binding's mac Columns.

+    sbrec_port_binding_set_options(op->sb, &options);
+    smap_destroy(&options);
+}
+
+static void
  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
                      struct hmap *lflows)
  {
@@ -5654,6 +5678,9 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap 
*ports,
          }
if (op->nbrp) {
+            /* Copy 'arp_config' options into SB DB */
+            copy_arp_to_sb(op);
+
              /* This is a logical router port. If next-hop IP address in
               * '[xx]reg0' matches IP address of this router port, then
               * the packet is intended to eventually be sent to this
diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema
index fcd878c..4fe10dd 100644
--- a/ovn/ovn-nb.ovsschema
+++ b/ovn/ovn-nb.ovsschema
@@ -1,7 +1,7 @@
  {
      "name": "OVN_Northbound",
-    "version": "5.8.1",
-    "cksum": "607160660 16929",
+    "version": "5.8.2",
+    "cksum": "1021825167 17088",
      "tables": {
          "NB_Global": {
              "columns": {
@@ -225,6 +225,9 @@
                  "ipv6_ra_configs": {
                      "type": {"key": "string", "value": "string",
                               "min": 0, "max": "unlimited"}},
+                "arp_configs": {
+                    "type": {"key": "string", "value": "string",
+                             "min": 0, "max": "unlimited"}},
                  "external_ids": {
                      "type": {"key": "string", "value": "string",
                               "min": 0, "max": "unlimited"}}},
diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml
index f843b16..73096a0 100644
--- a/ovn/ovn-nb.xml
+++ b/ovn/ovn-nb.xml
@@ -1394,6 +1394,15 @@
        </column>
      </group>
+ <group title="arp_configs">
+      <column name="arp_configs" key="timeout">
+        The router should revalidate its ARP entries from time to time,
+        deleting them if necessary. This sets the timeout, in seconds, between
+        revalidations (while deletion would happen 2*timeout). The default is
+        60 seconds.
+      </column>
+    </group>
+
      <group title="Options">
        <p>
          Additional options for the logical router port.
diff --git a/ovn/ovn-sb.ovsschema b/ovn/ovn-sb.ovsschema
index 2abcc6b..c45b599 100644
--- a/ovn/ovn-sb.ovsschema
+++ b/ovn/ovn-sb.ovsschema
@@ -1,7 +1,7 @@
  {
      "name": "OVN_Southbound",
-    "version": "1.15.0",
-    "cksum": "70426956 13327",
+    "version": "1.15.1",
+    "cksum": "373984372 13377",
      "tables": {
          "SB_Global": {
              "columns": {
@@ -149,6 +149,7 @@
                  "logical_port": {"type": "string"},
                  "ip": {"type": "string"},
                  "mac": {"type": "string"},
+                "timestamp": {"type": "integer"},
                  "datapath": {"type": {"key": {"type": "uuid",
                                                "refTable": 
"Datapath_Binding"}}}},
              "indexes": [["logical_port", "ip"]],
diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml
index ca8cbec..cf9cffd 100644
--- a/ovn/ovn-sb.xml
+++ b/ovn/ovn-sb.xml
@@ -2466,9 +2466,14 @@ tcp.flags = RST;
      <column name="mac">
        The Ethernet address to which the IP is bound.
      </column>
+
      <column name="datapath">
        The logical datapath to which the logical port belongs.
      </column>
+
+    <column name="timestamp">
+      The time at which the entry was created/last modified.
+    </column>
    </table>
<table name="DHCP_Options" title="DHCP Options supported by native OVN DHCP">
diff --git a/tests/ovn.at b/tests/ovn.at
index 2490e4d..40bd288 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -9209,4 +9209,170 @@ ovn-nbctl --wait=hv set Logical_Router_Port ro-sw 
ipv6_ra_configs:address_mode=d
  ra_test 000005dc 40 80 40 aef00000000000000000000000000000 30 
fd0f0000000000000000000000000000
OVN_CLEANUP([hv1],[hv2])
+
+AT_CLEANUP
+
+AT_SETUP([ovn -- ARP revalidate expire])
+ovn_start
+
+# Sets up two hypervisors, hv1 and hv2, with two logical switches and one
+# logical router.  Each logical switch has two ports, one of type 'router'
+# that's attached to the logical router, and one VIF.
+
+# The purpose of using two hypervisors is to validate that each ovn-controller
+# only triggers ARP revalidation / expiration for the ports bound to their
+# chassis.
+
+net_add n1
+sim_add hv1
+sim_add hv2
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.10.1
+as hv2
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.10.2
+
+ovn-nbctl lr-add ro
+ovn-nbctl lrp-add ro ro-sw1 00:00:00:00:00:01 192.168.0.1/24
+ovn-nbctl set Logical_Router_Port ro-sw1 arp_configs:timeout=3
+ovn-nbctl lrp-add ro ro-sw2 00:00:00:00:00:11 192.168.1.1/24
+ovn-nbctl set Logical_Router_Port ro-sw2 arp_configs:timeout=3
+
+as hv1
+ovn-nbctl ls-add sw1
+ovn-nbctl lsp-add sw1 sw1-ro
+ovn-nbctl lsp-set-type sw1-ro router
+ovn-nbctl lsp-set-options sw1-ro router-port=ro-sw1
+ovn-nbctl lsp-set-addresses sw1-ro "00:00:00:00:00:01" unknown
+ovn-nbctl lsp-add sw1 sw1-p1
+ovn-nbctl lsp-set-addresses sw1-p1 "00:00:00:00:00:33 192.168.0.33" unknown
+
+as hv2
+ovn-nbctl ls-add sw2
+ovn-nbctl lsp-add sw2 sw2-ro
+ovn-nbctl lsp-set-type sw2-ro router
+ovn-nbctl lsp-set-options sw2-ro router-port=ro-sw2
+ovn-nbctl lsp-set-addresses sw2-ro "00:00:00:00:00:11" unknown
+ovn-nbctl lsp-add sw2 sw2-p1
+ovn-nbctl lsp-set-addresses sw2-p1 "00:00:00:00:00:44 192.168.1.44" unknown
+
+# Add hv1-vif[123] to hv1 and hv2-vif[123] to hv2.
+j=1
+for i in hv1 hv2; do
+    as $i
+    ovs-vsctl -- add-port br-int $i-vif1 -- \
+        set interface $i-vif1 external-ids:iface-id=sw$j-p1 \
+        options:tx_pcap=$i/vif1-tx.pcap \
+        options:rxq_pcap=$i/vif1-rx.pcap \
+        ofport-request=1
+    ovs-vsctl -- add-port br-int $i-vif2 -- \
+        set interface $i-vif2 external-ids:iface-id=sw$j-ro \
+        options:tx_pcap=$i/vif2-tx.pcap \
+        options:rxq_pcap=$i/vif2-rx.pcap \
+        ofport-request=1
+    ovs-vsctl -- add-port br-int $i-vif3 -- \
+        set interface $i-vif3 external-ids:iface-id=ro-sw$j \
+        options:tx_pcap=$i/vif3-tx.pcap \
+        options:rxq_pcap=$i/vif3-rx.pcap \
+        ofport-request=1
+
+    (( j += 1 ))
+done
+
+# Allow time for ovn-northd and ovn-controller to catch up
+sleep 1
+
+echo "------ SB dump: Port Binding ------"
+ovn-sbctl list port_binding
+echo "------ SB dump: Mac Binding ------"
+ovn-sbctl list mac_binding
+echo "------ SB dump: Dump-Flows ------"
+ovn-sbctl dump-flows
+
+for i in hv1 hv2; do
+    as $i
+    echo "------ $i dump: show ------"
+    ovs-vsctl show
+    echo "------ $i dump: show br-phys ------"
+    ovs-ofctl -O OpenFlow13 show br-phys
+    echo "------ $i dump: dump-flows br-phys ------"
+    ovs-ofctl -O OpenFlow13 dump-flows br-phys
+    echo "------ $i dump: show br-int ------"
+    ovs-ofctl -O OpenFlow13 show br-int
+    echo "------ $i dump: dump-flows br-int ------"
+    ovs-ofctl -O OpenFlow13 dump-flows br-int
+done
+
+# send_arp_reply INPORT SHA THA SPA TPA
+#
+# Causes an ARP reply (op = 2) packet to be received on INPORT, as if INPORT
+# (with SHA) had sent a first ARP request (op = 1). Hence why SHA, SPA, THA
+# and TPA are set inversed when sending the packet.
+#
+# INPORT is an logical switch port number, e.g. 1 for vif1.
+# SHA and THA are each 12 hex digits.
+# SPA and TPA are each 8 hex digits.
+send_arp_reply() {
+    local inport=hv2-vif$1 sha=$2 tha=$3 spa=$4 tpa=$5
+    local reply=${sha}${tha}08060001080006040002${tha}${tpa}${sha}${spa}
+    as hv2 ovs-appctl netdev-dummy/receive $inport $reply
+}
+
+expected_arp_packets() {
+    local sha=$1 # smac
+    local tha=$2 # dmac
+    local spa=$3 # sip
+    local tpa=$4 # dip
+
+    # Write expected ARP Unicast
+    local reply=${tha}${sha}08060001080006040001${sha}${spa}${tha}${tpa}
+    echo $reply >> expected
+
+    # Set Broadcast dst MAC address
+    local tha=ffffffffffff
+
+    # Write expected ARP Broadcast
+    local reply=${tha}${sha}08060001080006040001${sha}${spa}${tha}${tpa}
+    echo $reply >> expected
+}
+
+arp_test() {
+    expected_arp_packets $@
+
+    OVS_WAIT_UNTIL([test 24 = $(wc -c hv2/vif1-rx.pcap | cut -d " " -f1)])
+
+    # Send emulated ARP reply
+    send_arp_reply 1 $1 $2 $3 $4
+
+    # Wait for both ARP Unicast and Broadcast
+
+    OVS_WAIT_WHILE([test 82 != $(wc -c hv2/vif1-rx.pcap | cut -d " " -f1)])
+    OVS_WAIT_WHILE([test 140 != $(wc -c hv2/vif1-tx.pcap | cut -d " " -f1)])
+
+    # Check received content equals the expected
+
+    OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
+
+    # Confirm ARP Unicast and Bordcastr packets are X seconds apart
+    #
+    # Skip ICMPv6 checksum.
+    #cat expected | cut -c 117- > expout
+    #AT_CHECK([cat packets | cut -c 117-], [0], [expout])
+
+    # Check that hv1 hasn't received anything
+    AT_CHECK([test 24 = $(wc -c hv1/vif1-rx.pcap | cut -d " " -f1)], [0], [])
+    AT_CHECK([test 24 = $(wc -c hv1/vif1-tx.pcap | cut -d " " -f1)], [0], [])
+
+    rm -f expected
+}
+
+# Test with 3 seconds timeout; An ARP reply is sent to smac, which triggers a
+# MAC_Binding entry to be created. The test then waits for an ARP Unicast and
+# Broadcast to sent to that bind (dmac and dip).
+# Arguments: smac, dmac, sip, dip
+arp_test 000000000011 000000000015 c0a80101 c0a80105
+
+OVN_CLEANUP([hv1],[hv2])
+
  AT_CLEANUP

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

Reply via email to