[ovs-dev,v3,2/2] ovn-northd ipam: Support IPv6 dynamic assignment
diff mbox

Message ID 20170310021720.28430-1-nusiddiq@redhat.com
State Accepted
Delegated to: Ben Pfaff
Headers show

Commit Message

Numan Siddique March 10, 2017, 2:17 a.m. UTC
From: Numan Siddique <nusiddiq@redhat.com>

OVN will generate the IPv6 address for a logical port if requested
using the IPv6 prefix and the MAC address (as IEEE EUI64 identifier).
To generate the IPv6 address, CMS should define the IPv6 prefix in the
'Logical_switch.other_config:ipv6_prefix' column.

Signed-off-by: Numan Siddique <nusiddiq@redhat.com>
Co-authored-by: Ben Pfaff <blp@ovn.org>
---
 lib/packets.h           | 20 ++++++++++++++
 ovn/northd/ovn-northd.c | 69 +++++++++++++++++++++++++++++++++++--------------
 ovn/ovn-nb.xml          | 20 ++++++++++++++
 tests/ovn.at            | 56 +++++++++++++++++++++++++++++++++++++--
 4 files changed, 143 insertions(+), 22 deletions(-)

Patch
diff mbox

diff --git a/lib/packets.h b/lib/packets.h
index a5a483b..7a4b8a7 100644
--- a/lib/packets.h
+++ b/lib/packets.h
@@ -995,6 +995,26 @@  in6_addr_solicited_node(struct in6_addr *addr, const struct in6_addr *ip6)
 }
 
 /*
+ * Generates ipv6 EUI64 address from the given eth addr
+ * and prefix and stores it in 'lla'
+ */
+static inline void
+in6_generate_eui64(struct eth_addr ea, struct in6_addr *prefix,
+                   struct in6_addr *lla)
+{
+    union ovs_16aligned_in6_addr *taddr = (void *) lla;
+    union ovs_16aligned_in6_addr *prefix_taddr = (void *) prefix;
+    taddr->be16[0] = prefix_taddr->be16[0];
+    taddr->be16[1] = prefix_taddr->be16[1];
+    taddr->be16[2] = prefix_taddr->be16[2];
+    taddr->be16[3] = prefix_taddr->be16[3];
+    taddr->be16[4] = htons(((ea.ea[0] ^ 0x02) << 8) | ea.ea[1]);
+    taddr->be16[5] = htons(ea.ea[2] << 8 | 0x00ff);
+    taddr->be16[6] = htons(0xfe << 8 | ea.ea[3]);
+    taddr->be16[7] = ea.be16[2];
+}
+
+/*
  * Generates ipv6 link local address from the given eth addr
  * with prefix 'fe80::/64' and stores it in 'lla'
  */
diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
index a3a3524..c1b5be6 100644
--- a/ovn/northd/ovn-northd.c
+++ b/ovn/northd/ovn-northd.c
@@ -377,6 +377,8 @@  struct ipam_info {
     uint32_t start_ipv4;
     size_t total_ipv4s;
     unsigned long *allocated_ipv4s; /* A bitmap of allocated IPv4s */
+    bool ipv6_prefix_set;
+    struct in6_addr ipv6_prefix;
 };
 
 /* The 'key' comes from nbs->header_.uuid or nbr->header_.uuid or
@@ -509,6 +511,14 @@  init_ipam_info_for_datapath(struct ovn_datapath *od)
     }
 
     const char *subnet_str = smap_get(&od->nbs->other_config, "subnet");
+    const char *ipv6_prefix = smap_get(&od->nbs->other_config, "ipv6_prefix");
+
+    if (ipv6_prefix) {
+        od->ipam_info = xzalloc(sizeof *od->ipam_info);
+        od->ipam_info->ipv6_prefix_set = ipv6_parse(
+            ipv6_prefix, &od->ipam_info->ipv6_prefix);
+    }
+
     if (!subnet_str) {
         return;
     }
@@ -523,7 +533,9 @@  init_ipam_info_for_datapath(struct ovn_datapath *od)
         return;
     }
 
-    od->ipam_info = xzalloc(sizeof *od->ipam_info);
+    if (!od->ipam_info) {
+        od->ipam_info = xzalloc(sizeof *od->ipam_info);
+    }
     od->ipam_info->start_ipv4 = ntohl(subnet) + 1;
     od->ipam_info->total_ipv4s = ~ntohl(mask);
     od->ipam_info->allocated_ipv4s =
@@ -1021,42 +1033,59 @@  static bool
 ipam_allocate_addresses(struct ovn_datapath *od, struct ovn_port *op,
                         const char *addrspec)
 {
-    if (!od || !op || !op->nbsp) {
-        return false;
-    }
-
-    uint32_t ip = ipam_get_unused_ip(od);
-    if (!ip) {
+    if (!op->nbsp || !od->ipam_info) {
         return false;
     }
 
+    /* Get or generate MAC address. */
     struct eth_addr mac;
-    bool check_mac;
+    bool dynamic_mac;
     int n = 0;
-
     if (ovs_scan(addrspec, ETH_ADDR_SCAN_FMT" dynamic%n",
                  ETH_ADDR_SCAN_ARGS(mac), &n)
         && addrspec[n] == '\0') {
-        check_mac = true;
+        dynamic_mac = false;
     } else {
         uint64_t mac64 = ipam_get_unused_mac();
         if (!mac64) {
             return false;
         }
         eth_addr_from_uint64(mac64, &mac);
-        check_mac = false;
+        dynamic_mac = true;
     }
 
-    /* Add MAC to MACAM and IP to IPAM bitmap if both addresses were allocated
-     * successfully. */
-    ipam_insert_ip(od, ip);
-    ipam_insert_mac(&mac, check_mac);
+    /* Generate IPv4 address, if desirable. */
+    bool dynamic_ip4 = od->ipam_info->allocated_ipv4s != NULL;
+    uint32_t ip4 = dynamic_ip4 ? ipam_get_unused_ip(od) : 0;
 
-    char *new_addr = xasprintf(ETH_ADDR_FMT" "IP_FMT,
-                               ETH_ADDR_ARGS(mac), IP_ARGS(htonl(ip)));
-    nbrec_logical_switch_port_set_dynamic_addresses(op->nbsp, new_addr);
-    free(new_addr);
+    /* Generate IPv6 address, if desirable. */
+    bool dynamic_ip6 = od->ipam_info->ipv6_prefix_set;
+    struct in6_addr ip6;
+    if (dynamic_ip6) {
+        in6_generate_eui64(mac, &od->ipam_info->ipv6_prefix, &ip6);
+    }
 
+    /* If we didn't generate anything, bail out. */
+    if (!dynamic_ip4 && !dynamic_ip6) {
+        return false;
+    }
+
+    /* Save the dynamic addresses. */
+    struct ds new_addr = DS_EMPTY_INITIALIZER;
+    ds_put_format(&new_addr, ETH_ADDR_FMT, ETH_ADDR_ARGS(mac));
+    if (dynamic_ip4 && ip4) {
+        ipam_insert_ip(od, ip4);
+        ds_put_format(&new_addr, " "IP_FMT, IP_ARGS(htonl(ip4)));
+    }
+    if (dynamic_ip6) {
+        char ip6_s[INET6_ADDRSTRLEN + 1];
+        ipv6_string_mapped(ip6_s, &ip6);
+        ds_put_format(&new_addr, " %s", ip6_s);
+    }
+    ipam_insert_mac(&mac, !dynamic_mac);
+    nbrec_logical_switch_port_set_dynamic_addresses(op->nbsp,
+                                                    ds_cstr(&new_addr));
+    ds_destroy(&new_addr);
     return true;
 }
 
@@ -1073,7 +1102,7 @@  build_ipam(struct hmap *datapaths, struct hmap *ports)
      * ports that have the "dynamic" keyword in their addresses column. */
     struct ovn_datapath *od;
     HMAP_FOR_EACH (od, key_node, datapaths) {
-        if (!od->nbs || !od->ipam_info || !od->ipam_info->allocated_ipv4s) {
+        if (!od->nbs || !od->ipam_info) {
             continue;
         }
 
diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml
index 28fe974..d82a68e 100644
--- a/ovn/ovn-nb.xml
+++ b/ovn/ovn-nb.xml
@@ -163,6 +163,26 @@ 
           <li><code>192.168.0.110..192.168.0.120 192.168.0.25..192.168.0.30 192.168.0.144</code></li>
         </ul>
       </column>
+
+      <column name="other_config" key="ipv6_prefix">
+        Set this to an IPv6 prefix to enable <code>ovn-northd</code> to
+        automatically assign IPv6 addresses using this prefix. Use the
+        <code>dynamic</code> keyword in the <ref table="Logical_Switch_Port"/>
+        table's <ref table="Logical_Switch_Port" column="addresses"/> column to
+        request dynamic address assignment for a particular port. The assigned
+        IPv6 address will be generated using the IPv6 prefix and the
+        MAC address (as IEEE EUI64 identifier) of the port.
+        The IPv6 prefix defined here should be a valid IPv6 address ending with
+        "::".
+        <p>
+          Examples:
+        </p>
+        <ul>
+          <li><code>aef0::</code></li>
+          <li><code>bef0:1234:a890:5678::</code></li>
+          <li><code>8230:5678::</code></li>
+        </ul>
+      </column>
     </group>
 
     <group title="Common Columns">
diff --git a/tests/ovn.at b/tests/ovn.at
index fc056e3..8306b83 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -4869,14 +4869,14 @@  AT_CHECK([ovn-nbctl get Logical-Switch-Port p27 dynamic_addresses], [0],
 
 ovn-nbctl --wait=sb lsp-add sw2 p28 -- lsp-set-addresses p28 dynamic
 AT_CHECK([ovn-nbctl get Logical-Switch-Port p28 dynamic_addresses], [0],
-     [[[]]
+     ["0a:00:00:00:00:1e"
 ])
 
 # Test that address management does not add duplicate MAC for lsp/lrp peers.
 ovn-nbctl create Logical_Router name=R2
 ovn-nbctl ls-add sw3
 ovn-nbctl lsp-add sw3 p29 -- lsp-set-addresses p29 \
-"0a:00:00:00:00:1e"
+"0a:00:00:00:00:1f"
 ovn-nbctl -- --id=@lrp create Logical_Router_port name=sw3 \
 network="192.168.2.1/24" mac=\"0a:00:00:00:00:1f\" \
 -- add Logical_Router R2 ports @lrp -- lsp-add sw3 rp-sw3 \
@@ -4961,6 +4961,58 @@  AT_CHECK([ovn-nbctl get Logical-Switch-Port p36 dynamic_addresses], [0],
          [[[]]
 ])
 
+# Set IPv6 prefix
+ovn-nbctl --wait=sb set Logical-switch sw0 other_config:ipv6_prefix="aef0::"
+ovn-nbctl --wait=sb lsp-add sw0 p37 -- lsp-set-addresses p37 \
+"dynamic"
+
+# With prefix aef0 and mac 0a:00:00:00:00:26, the dynamic IPv6 should be
+# - aef0::800:ff:fe00:26 (EUI64)
+AT_CHECK([ovn-nbctl get Logical-Switch-Port p37 dynamic_addresses], [0],
+     ["0a:00:00:00:00:26 192.168.1.21 aef0::800:ff:fe00:26"
+])
+
+ovn-nbctl --wait=sb ls-add sw4
+ovn-nbctl --wait=sb set Logical-switch sw4 other_config:ipv6_prefix="bef0::"
+ovn-nbctl --wait=sb lsp-add sw4 p38 -- lsp-set-addresses p38 \
+"dynamic"
+
+AT_CHECK([ovn-nbctl get Logical-Switch-Port p38 dynamic_addresses], [0],
+     ["0a:00:00:00:00:27 bef0::800:ff:fe00:27"
+])
+
+ovn-nbctl --wait=sb lsp-add sw4 p39 -- lsp-set-addresses p39 \
+"f0:00:00:00:10:12 dynamic"
+
+AT_CHECK([ovn-nbctl get Logical-Switch-Port p39 dynamic_addresses], [0],
+     ["f0:00:00:00:10:12 bef0::f200:ff:fe00:1012"
+])
+
+# Clear the other_config for sw4. No dynamic ip should be assigned.
+ovn-nbctl --wait=sb clear Logical-switch sw4 other_config
+ovn-nbctl --wait=sb lsp-add sw4 p40 -- lsp-set-addresses p40 \
+"dynamic"
+
+AT_CHECK([ovn-nbctl get Logical-Switch-Port p40 dynamic_addresses], [0],
+         [[[]]
+])
+
+# Test the case where IPv4 addresses are exhausted and IPv6 prefix is set
+ovn-nbctl --wait=sb set Logical-switch sw4 other_config:subnet=192.168.2.0/30 \
+-- set Logical-switch sw4 other_config:ipv6_prefix="bef0::"
+
+# Now p40 should be assigned with dynamic addresses.
+AT_CHECK([ovn-nbctl get Logical-Switch-Port p40 dynamic_addresses], [0],
+         ["0a:00:00:00:00:28 192.168.2.2 bef0::800:ff:fe00:28"
+])
+
+ovn-nbctl --wait=sb lsp-add sw4 p41 -- lsp-set-addresses p41 \
+"dynamic"
+# p41 should not have IPv4 address (as the pool is exhausted).
+AT_CHECK([ovn-nbctl get Logical-Switch-Port p41 dynamic_addresses], [0],
+         ["0a:00:00:00:00:29 bef0::800:ff:fe00:29"
+])
+
 as ovn-sb
 OVS_APP_EXIT_AND_WAIT([ovsdb-server])