diff --git a/NEWS b/NEWS index 8607b04b6..eb825ac72 100644 --- a/NEWS +++ b/NEWS @@ -14,6 +14,7 @@ Post-v2.7.0 "dot1q-tunnel" port VLAN mode. - OVN: * IPAM for IPv4 can now exclude user-defined addresses from assignment. + * IPAM can now assign IPv6 addresses. * Make the DHCPv4 router setting optional. * Gratuitous ARP for NAT addresses on a distributed logical router. * Allow ovn-controller SSL configuration to be obtained from vswitchd diff --git a/lib/packets.h b/lib/packets.h index 755f08d6d..d8f32fc5f 100644 --- a/lib/packets.h +++ b/lib/packets.h @@ -1038,6 +1038,26 @@ in6_addr_solicited_node(struct in6_addr *addr, const struct in6_addr *ip6) memcpy(&addr->s6_addr[13], &ip6->s6_addr[13], 3); } +/* + * 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 878fb7295..027e5a133 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 = @@ -1022,42 +1034,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; } @@ -1074,7 +1103,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 68a003927..2b416cede 100644 --- a/ovn/ovn-nb.xml +++ b/ovn/ovn-nb.xml @@ -137,12 +137,19 @@

These options control automatic IP address management (IPAM) for ports - attached to the logical switch. To enable IPAM, set and optionally . Then, to request dynamic address - assignment for a particular port, use the dynamic keyword - in the column of - the port's row. + column="other_config:exclude_ips"/>. To enable IPAM for IPv6, set + . IPv4 and IPv6 may + be enabled together or separately. +

+ +

+ To request dynamic address assignment for a particular port, use the + dynamic keyword in the column of the port's row. This requests both an IPv4 and an + IPv6 address, if IPAM for IPv4 and IPv6 are both enabled.

@@ -172,6 +179,23 @@
  • 192.168.0.110..192.168.0.120 192.168.0.25..192.168.0.30 192.168.0.144
  • + + + Set this to an IPv6 prefix to enable ovn-northd to + automatically assign IPv6 addresses using this prefix. The assigned + IPv6 address will be generated using the IPv6 prefix and the MAC + address (converted to an IEEE EUI64 identifier) of the port. The IPv6 + prefix defined here should be a valid IPv6 address ending with + ::. +

    + Examples: +

    + +
    diff --git a/tests/ovn.at b/tests/ovn.at index 472366af2..af77c19c4 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])