diff mbox series

[ovs-dev,v2] ovn-controller: support MAC_Binding aging

Message ID 20180319082441.11120-1-ligs@dtdream.com
State Changes Requested
Headers show
Series [ovs-dev,v2] ovn-controller: support MAC_Binding aging | expand

Commit Message

Guoshuai Li March 19, 2018, 8:24 a.m. UTC
Add the MAC_Binding aging. The default aging time is 20 minutes.
Send the ARP request at 10(1*20/2), 13(2*20/3), 15(3*20/4) minutes.
If no ARP reply is received within 20 minutes, the MAC_Binding column will be
deleted.

Sync a MAC_Binding cache in the ovn-controller where lport redirect chassis,
to records timestamps and ARP send times. Time traversal cache to send ARP
requests or aging.

Signed-off-by: Guoshuai Li <ligs@dtdream.com>
---
 v2: Reconstruction and Fix code for check chassis.

---
 ovn/controller/pinctrl.c | 353 +++++++++++++++++++++++++++++++++++++++++++++--
 tests/ovn.at             |  70 ++++++++++
 2 files changed, 410 insertions(+), 13 deletions(-)
diff mbox series

Patch

diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c
index e8bf1c698..1e5ad4d94 100644
--- a/ovn/controller/pinctrl.c
+++ b/ovn/controller/pinctrl.c
@@ -51,6 +51,7 @@ 
 #include "timeval.h"
 #include "vswitch-idl.h"
 #include "lflow.h"
+#include "unixctl.h"
 
 VLOG_DEFINE_THIS_MODULE(pinctrl);
 
@@ -70,6 +71,25 @@  static void run_put_mac_bindings(struct controller_ctx *);
 static void wait_put_mac_bindings(struct controller_ctx *);
 static void flush_put_mac_bindings(void);
 
+static void init_aging_mac_bindings(void);
+static void destroy_aging_mac_bindings(void);
+static void aging_mac_bindings_wait(void);
+static void aging_mac_bindings_run(struct controller_ctx *,
+                                   const struct ovsrec_bridge *,
+                                   const struct sbrec_chassis *,
+                                   const struct chassis_index *,
+                                   struct hmap *,
+                                   struct sset *);
+static void init_aging_mac_binding(uint32_t, uint32_t, const char *);
+static void list_aging_mac_binding_cache(struct unixctl_conn *conn,
+                                         int argc OVS_UNUSED,
+                                         const char *argv[] OVS_UNUSED,
+                                         void *arg OVS_UNUSED);
+static void set_aging_mac_binding_time(struct unixctl_conn *conn,
+                                       int argc OVS_UNUSED,
+                                       const char *argv[],
+                                       void *arg OVS_UNUSED);
+
 static void init_send_garps(void);
 static void destroy_send_garps(void);
 static void send_garp_wait(void);
@@ -107,6 +127,7 @@  pinctrl_init(void)
     init_put_mac_bindings();
     init_send_garps();
     init_ipv6_ras();
+    init_aging_mac_bindings();
 }
 
 static ovs_be32
@@ -1166,6 +1187,8 @@  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);
+    aging_mac_bindings_run(ctx, br_int, chassis, chassis_index,
+                           local_datapaths, active_tunnels);
 }
 
 /* Table of ipv6_ra_state structures, keyed on logical port name */
@@ -1467,6 +1490,7 @@  pinctrl_wait(struct controller_ctx *ctx)
     rconn_recv_wait(swconn);
     send_garp_wait();
     ipv6_ra_wait();
+    aging_mac_bindings_wait();
 }
 
 void
@@ -1476,6 +1500,7 @@  pinctrl_destroy(void)
     destroy_put_mac_bindings();
     destroy_send_garps();
     destroy_ipv6_ras();
+    destroy_aging_mac_bindings();
 }
 
 /* Implementation of the "put_arp" and "put_nd" OVN actions.  These
@@ -1567,6 +1592,9 @@  pinctrl_handle_put_mac_binding(const struct flow *md,
     }
     pmb->timestamp = time_msec();
     pmb->mac = headers->dl_src;
+
+    /* init aging mac_binding timestamp and arp_send_count */
+    init_aging_mac_binding(dp_key, port_key, ip_s);
 }
 
 static void
@@ -1776,30 +1804,27 @@  send_garp_delete(const char *lport)
     free(garp);
 }
 
-static long long int
-send_garp(struct garp_data *garp, long long int current_time)
+static void
+send_arp_request(const struct eth_addr arp_sha, ovs_be32 arp_spa,
+                 ovs_be32 arp_tpa, int ofport, int tag)
 {
-    if (current_time < garp->announce_time) {
-        return garp->announce_time;
-    }
-
-    /* Compose a GARP request packet. */
+    /* Compose a ARP request packet. */
     uint64_t packet_stub[128 / 8];
     struct dp_packet packet;
     dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
-    compose_arp(&packet, ARP_OP_REQUEST, garp->ea, eth_addr_zero,
-                true, garp->ipv4, garp->ipv4);
+    compose_arp(&packet, ARP_OP_REQUEST, arp_sha, eth_addr_zero,
+                true, arp_spa, arp_tpa);
 
-    /* Compose a GARP request packet's vlan if exist. */
-    if (garp->tag >= 0) {
-        eth_push_vlan(&packet, htons(ETH_TYPE_VLAN), htons(garp->tag));
+    /* Compose a ARP request packet's vlan if exist. */
+    if (tag >= 0) {
+        eth_push_vlan(&packet, htons(ETH_TYPE_VLAN), htons(tag));
     }
 
     /* Compose actions.  The garp request is output on localnet ofport. */
     uint64_t ofpacts_stub[4096 / 8];
     struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub);
     enum ofp_version version = rconn_get_version(swconn);
-    ofpact_put_OUTPUT(&ofpacts)->port = garp->ofport;
+    ofpact_put_OUTPUT(&ofpacts)->port = ofport;
 
     struct ofputil_packet_out po = {
         .packet = dp_packet_data(&packet),
@@ -1813,6 +1838,17 @@  send_garp(struct garp_data *garp, long long int current_time)
     queue_msg(ofputil_encode_packet_out(&po, proto));
     dp_packet_uninit(&packet);
     ofpbuf_uninit(&ofpacts);
+}
+
+static long long int
+send_garp(struct garp_data *garp, long long int current_time)
+{
+    if (current_time < garp->announce_time) {
+        return garp->announce_time;
+    }
+
+    send_arp_request(garp->ea, garp->ipv4, garp->ipv4, garp->ofport,
+                     garp->tag);
 
     /* Set the next announcement.  At most 5 announcements are sent for a
      * vif. */
@@ -2161,6 +2197,297 @@  send_garp_run(struct controller_ctx *ctx,
     sset_destroy(&nat_ip_keys);
 }
 
+/* Buffered "aging_mac_binding" operation. */
+struct aging_mac_binding {
+    /* Key. */
+    uint32_t dp_key;
+    uint32_t port_key;
+    char ip_s[INET6_ADDRSTRLEN + 1]; /* Destination Ipv4 address. */
+    int arp_send_count;
+    long long int announce_time;     /* Next announcement in ms. */
+    struct eth_addr ea;              /* Source Ethernet address. */
+    ovs_be32 ipv4;                   /* Source Ipv4 address. */
+    ofp_port_t ofport;               /* ofport used to output this packet. */
+    int tag;                         /* VLAN tag of this ARP packet, or -1. */
+};
+
+/* Contains "struct aging_mac_binding"s.
+   The cache for mac_bindings */
+static struct shash aging_mac_bindings;
+
+/* Next aging mac binding time announcement in ms. */
+static long long int next_wait_time;
+
+static long long int base_reachable_time = 20 * 60 * 1000;
+
+static void
+init_aging_mac_bindings(void)
+{
+    shash_init(&aging_mac_bindings);
+    next_wait_time = LLONG_MAX;
+    unixctl_command_register("mac-binding-cache-list", "", 0, 0,
+                             list_aging_mac_binding_cache, NULL);
+    unixctl_command_register("set-mac-binding-aging-time", "MSECS", 1, 1,
+                             set_aging_mac_binding_time, NULL);
+}
+
+static void
+insert_aging_mac_bindings(char *name, int64_t dp_key, int64_t port_key,
+                          const char *ip_s, struct eth_addr ea, ovs_be32 ipv4,
+                          ofp_port_t ofport, int tag)
+{
+    struct aging_mac_binding *amb = xmalloc(sizeof *amb);
+    amb->dp_key = dp_key;
+    amb->port_key = port_key;
+    ovs_strlcpy(amb->ip_s, ip_s, INET6_ADDRSTRLEN);
+    amb->arp_send_count = 0;
+    amb->announce_time = time_msec();
+    amb->ea = ea;
+    amb->ipv4 = ipv4;
+    amb->ofport = ofport;
+    amb->tag = tag;
+    shash_add(&aging_mac_bindings, name, amb);
+}
+
+static void
+destroy_aging_mac_bindings(void)
+{
+    shash_destroy_free_data(&aging_mac_bindings);
+}
+
+static void
+aging_mac_bindings_wait(void)
+{
+    poll_timer_wait_until(next_wait_time);
+}
+
+static void
+remove_mac_bindings(const struct controller_ctx *ctx,
+                    const char *logical_port, const char * ip_s)
+{
+    const struct sbrec_mac_binding *b, *n;
+    SBREC_MAC_BINDING_FOR_EACH_SAFE (b, n, ctx->ovnsb_idl) {
+        if (!strcmp(b->logical_port, logical_port)
+            && !strcmp(b->ip, ip_s)) {
+            sbrec_mac_binding_delete(b);
+            VLOG_INFO("logical_port:%s ip:%s MAC_Binding aging.",
+                      b->logical_port, b->ip);
+            return;
+        }
+    }
+}
+
+static void
+init_aging_mac_binding(uint32_t dp_key, uint32_t port_key, const char *ip_s)
+{
+    char *name = xasprintf("%u-%u-%s", dp_key, port_key, ip_s);
+    struct aging_mac_binding *amb =
+        shash_find_data(&aging_mac_bindings, name);
+    if (amb) {
+        amb->announce_time = time_msec();
+        amb->arp_send_count = 0;
+    }
+    free(name);
+}
+
+static bool
+get_lport_addresses(const struct sbrec_port_binding *pb,
+                    struct lport_addresses *laddrs)
+{
+    for (int i = 0; i < pb->n_mac; i++) {
+        if (extract_lsp_addresses(pb->mac[i], laddrs)
+            && laddrs->n_ipv4_addrs) {
+            return true;
+        }
+    }
+    return false;
+}
+
+/* refresh mac bindings cache from ovn sb */
+static void
+refresh_aging_mac_bindings(struct controller_ctx *ctx,
+                           struct hmap *local_datapaths,
+                           struct simap *localnet_ofports,
+                           struct sset *local_l3gw_ports,
+                           const struct sbrec_chassis *chassis,
+                           const struct chassis_index *chassis_index,
+                           struct sset *active_tunnels)
+{
+    struct sset mac_binding_keys = SSET_INITIALIZER(&mac_binding_keys);
+
+    const struct sbrec_mac_binding *mb;
+    SBREC_MAC_BINDING_FOR_EACH (mb, ctx->ovnsb_idl) {
+        const struct sbrec_port_binding *pb
+            = lport_lookup_by_name(ctx->ovnsb_idl, mb->logical_port);
+        if (!pb) {
+            continue;
+        }
+
+        const char *peer_port = smap_get_def(&pb->options, "peer", "");
+        if (!sset_contains(local_l3gw_ports, peer_port)) {
+            continue;
+        }
+
+        if (!strcmp(pb->type, "patch")) {
+            char *redirect = xasprintf("cr-%s", mb->logical_port);
+            if (!pinctrl_is_chassis_resident(ctx, chassis, chassis_index,
+                                             active_tunnels, redirect)) {
+                free(redirect);
+                continue;
+            }
+            free(redirect);
+        }
+
+        struct lport_addresses laddrs;
+        if (!get_lport_addresses(pb, &laddrs)) {
+            continue;
+        }
+
+        const struct sbrec_port_binding *peer_pb
+            = lport_lookup_by_name(ctx->ovnsb_idl, peer_port);
+        if (!peer_pb) {
+            continue;
+        }
+
+        /* Find the localnet ofport to send this ARP. */
+        struct local_datapath *ld
+            = get_local_datapath(local_datapaths,
+                                 peer_pb->datapath->tunnel_key);
+        if (!ld || !ld->localnet_port) {
+            continue;
+        }
+        ofp_port_t ofport =
+            u16_to_ofp(simap_get(localnet_ofports,
+                                 ld->localnet_port->logical_port));
+        int tag = ld->localnet_port->n_tag ? *ld->localnet_port->tag : -1;
+
+        char *name = xasprintf("%"PRId64"-%"PRId64"-%s",
+                               pb->datapath->tunnel_key, pb->tunnel_key,
+                               mb->ip);
+        struct aging_mac_binding *amb =
+            shash_find_data(&aging_mac_bindings, name);
+        if (amb) {
+            amb->ofport = ofport;
+            amb->tag = tag;
+            amb->ipv4 = laddrs.ipv4_addrs[0].addr;
+            amb->ea = laddrs.ea;
+        } else {
+            insert_aging_mac_bindings(name, pb->datapath->tunnel_key,
+                                      pb->tunnel_key, mb->ip, laddrs.ea,
+                                      laddrs.ipv4_addrs[0].addr, ofport, tag);
+        }
+        sset_add(&mac_binding_keys, name);
+        free(name);
+    }
+
+    struct shash_node *iter, *next;
+    SHASH_FOR_EACH_SAFE (iter, next, &aging_mac_bindings) {
+        if (!sset_contains(&mac_binding_keys, iter->name)) {
+            struct aging_mac_binding *amb = iter->data;
+            shash_delete(&aging_mac_bindings, iter);
+            free(amb);
+        }
+    }
+
+    sset_destroy(&mac_binding_keys);
+}
+
+static void
+aging_mac_bindings_run(struct controller_ctx *ctx,
+                       const struct ovsrec_bridge *br_int,
+                       const struct sbrec_chassis *chassis,
+                       const struct chassis_index *chassis_index,
+                       struct hmap *local_datapaths,
+                       struct sset *active_tunnels)
+{
+    struct sset localnet_vifs = SSET_INITIALIZER(&localnet_vifs);
+    struct sset local_l3gw_ports = SSET_INITIALIZER(&local_l3gw_ports);
+    struct simap localnet_ofports = SIMAP_INITIALIZER(&localnet_ofports);
+
+    get_localnet_vifs_l3gwports(ctx, br_int, chassis, local_datapaths,
+                                &localnet_vifs, &localnet_ofports,
+                                &local_l3gw_ports);
+
+    refresh_aging_mac_bindings(ctx, local_datapaths, &localnet_ofports,
+                               &local_l3gw_ports, chassis, chassis_index,
+                               active_tunnels);
+
+    long long int current_time = time_msec();
+    next_wait_time = LLONG_MAX;
+
+    struct shash_node *iter;
+    SHASH_FOR_EACH (iter, &aging_mac_bindings) {
+        struct aging_mac_binding *amb = iter->data;
+
+        /* mac_binding aging time reachable, aging mac_binding. */
+        if (current_time >= amb->announce_time + base_reachable_time) {
+            const struct sbrec_port_binding *pb
+                = lport_lookup_by_key(ctx->ovnsb_idl, amb->dp_key,
+                                      amb->port_key);
+            remove_mac_bindings(ctx, pb->logical_port, amb->ip_s);
+            continue;
+        }
+
+        /* send arp request in 1/2 2/3 3/4 base_reachable_time, 3 times. */
+        long long int time = (amb->arp_send_count + 1) * base_reachable_time /
+            (amb->arp_send_count + 2);
+        if (2 >= amb->arp_send_count &&
+            current_time >= amb->announce_time + time) {
+            send_arp_request(amb->ea, amb->ipv4,
+                             inet_addr(amb->ip_s), amb->ofport, amb->tag);
+            amb->arp_send_count++;
+        }
+
+        long long int amb_next_wait_time;
+        if (2 < amb->arp_send_count) {
+            amb_next_wait_time = amb->announce_time + base_reachable_time;
+        } else {
+            time = (amb->arp_send_count + 1) * base_reachable_time /
+                        (amb->arp_send_count + 2);
+            amb_next_wait_time = amb->announce_time + time;
+        }
+        if (next_wait_time > amb_next_wait_time) {
+            next_wait_time = amb_next_wait_time;
+        }
+    }
+
+    sset_destroy(&localnet_vifs);
+    sset_destroy(&local_l3gw_ports);
+    simap_destroy(&localnet_ofports);
+}
+
+static void
+list_aging_mac_binding_cache(struct unixctl_conn *conn, int argc OVS_UNUSED,
+                             const char *argv[] OVS_UNUSED,
+                             void *arg OVS_UNUSED)
+{
+    struct ds ds = DS_EMPTY_INITIALIZER;
+
+    ds_put_format(&ds, "aging mac binding time is %lld s.\n",
+                  base_reachable_time / 1000);
+    struct shash_node *iter;
+    SHASH_FOR_EACH (iter, &aging_mac_bindings) {
+        struct aging_mac_binding *amb = iter->data;
+        long long int age = (time_msec() - amb->announce_time) / 1000;
+        ds_put_format(&ds, "dp:%d port:%d ip:%s age:%lld s send arp"
+                      " count:%d\n.",
+                      amb->dp_key, amb->port_key,
+                      amb->ip_s, age, amb->arp_send_count);
+    }
+
+    unixctl_command_reply(conn, ds_cstr(&ds));
+    ds_destroy(&ds);
+}
+
+static void
+set_aging_mac_binding_time(struct unixctl_conn *conn,
+                           int argc OVS_UNUSED,
+                           const char *argv[], void *arg OVS_UNUSED)
+{
+    base_reachable_time = atoll(argv[1]);
+    unixctl_command_reply(conn, "OK.");
+}
+
 static void
 reload_metadata(struct ofpbuf *ofpacts, const struct match *md)
 {
diff --git a/tests/ovn.at b/tests/ovn.at
index 5f985f345..60896ea68 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -9428,3 +9428,73 @@  done
 
 OVN_CLEANUP([hv1], [hv2], [hv3])
 AT_CLEANUP
+
+AT_SETUP([ovn -- MAC_Binding aging])
+ovn_start
+
+net_add n1
+
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-phys
+
+ovn-nbctl lr-add lr0
+ovn-nbctl ls-add ls0
+
+# Create logical router port lrp0 and peer lsp0
+ovn-nbctl lrp-add lr0 lrp0 f0:00:00:00:00:02 192.168.0.2/24 \
+    -- set Logical_Router_Port lrp0 options:redirect-chassis="hv1"
+ovn-nbctl lsp-add ls0 lsp0 \
+    -- lsp-set-addresses lsp0 router \
+    -- lsp-set-type lsp0 router \
+    -- lsp-set-options lsp0 router-port=lrp0 nat-addresses=router
+
+# Create localnet port in ls0
+ovn-nbctl lsp-add ls0 ln0 \
+    -- lsp-set-addresses ln0 unknown \
+    -- lsp-set-type ln0 localnet \
+    -- lsp-set-options ln0 network_name=physnet1
+
+ovs-appctl -t ovn-controller set-mac-binding-aging-time 4000
+
+# Create Mac_Binding in lrp0
+dp_uuid=`ovn-sbctl find datapath external_ids:name=lr0 | grep uuid | cut -f2 -d ":" | cut -f2 -d " "`
+ovn-sbctl create MAC_Binding ip=192.168.0.3 datapath=$dp_uuid logical_port=lrp0 mac="f0\:00\:00\:00\:00\:03"
+
+AT_CHECK([ovn-sbctl find MAC_Binding | grep 192.168.0.3 | wc -l ], [0], [1
+])
+
+# Wait for mac_binding aging.
+OVS_WAIT_UNTIL([grep -c "MAC_Binding aging" hv1/ovn-controller.log])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/br-phys-tx.pcap > packets
+echo "fffffffffffff0000000000208060001080006040001f00000000002c0a80002000000000000c0a80003" > expout
+echo "fffffffffffff0000000000208060001080006040001f00000000002c0a80002000000000000c0a80003" >> expout
+echo "fffffffffffff0000000000208060001080006040001f00000000002c0a80002000000000000c0a80003" >> expout
+AT_CHECK([cat packets], [0], [expout])
+
+# Check the mac binding is aged.
+AT_CHECK([ovn-sbctl find MAC_Binding | grep 192.168.0.3 | wc -l ], [0], [0
+])
+
+# Create Mac_Binding(192.168.0.1) in lrp0
+ovn-sbctl create MAC_Binding ip=192.168.0.1 datapath=$dp_uuid logical_port=lrp0 mac="f0\:00\:00\:00\:00\:01"
+
+AT_CHECK([ovn-sbctl find MAC_Binding | grep 192.168.0.1 | wc -l ], [0], [1
+])
+
+mac=`ovs-vsctl get Interface br-phys mac_in_use | sed s/\"//g | sed s/\://g`
+
+# Wait for packet to be received.
+package=f00000000002${mac}08060001080006040002${mac}c0a80001f00000000002c0a80002
+echo $package > expected
+OVN_CHECK_PACKETS([hv1/br-phys-rx.pcap], [expected])
+
+# Check the mac binding is not aged.
+AT_CHECK([ovn-sbctl find MAC_Binding | grep 192.168.0.1 | wc -l ], [0], [1
+])
+
+OVN_CLEANUP([hv1])
+AT_CLEANUP
\ No newline at end of file