mirror of
https://github.com/openvswitch/ovs
synced 2025-08-30 13:58:14 +00:00
ovn-northd ipam: Support IPv6 dynamic assignment
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> Signed-off-by: Ben Pfaff <blp@ovn.org>
This commit is contained in:
committed by
Ben Pfaff
parent
161ea2c871
commit
7cc0741ea2
1
NEWS
1
NEWS
@@ -14,6 +14,7 @@ Post-v2.7.0
|
|||||||
"dot1q-tunnel" port VLAN mode.
|
"dot1q-tunnel" port VLAN mode.
|
||||||
- OVN:
|
- OVN:
|
||||||
* IPAM for IPv4 can now exclude user-defined addresses from assignment.
|
* IPAM for IPv4 can now exclude user-defined addresses from assignment.
|
||||||
|
* IPAM can now assign IPv6 addresses.
|
||||||
* Make the DHCPv4 router setting optional.
|
* Make the DHCPv4 router setting optional.
|
||||||
* Gratuitous ARP for NAT addresses on a distributed logical router.
|
* Gratuitous ARP for NAT addresses on a distributed logical router.
|
||||||
* Allow ovn-controller SSL configuration to be obtained from vswitchd
|
* Allow ovn-controller SSL configuration to be obtained from vswitchd
|
||||||
|
@@ -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);
|
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
|
* Generates ipv6 link local address from the given eth addr
|
||||||
* with prefix 'fe80::/64' and stores it in 'lla'
|
* with prefix 'fe80::/64' and stores it in 'lla'
|
||||||
|
@@ -377,6 +377,8 @@ struct ipam_info {
|
|||||||
uint32_t start_ipv4;
|
uint32_t start_ipv4;
|
||||||
size_t total_ipv4s;
|
size_t total_ipv4s;
|
||||||
unsigned long *allocated_ipv4s; /* A bitmap of allocated 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
|
/* 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 *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) {
|
if (!subnet_str) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -523,7 +533,9 @@ init_ipam_info_for_datapath(struct ovn_datapath *od)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!od->ipam_info) {
|
||||||
od->ipam_info = xzalloc(sizeof *od->ipam_info);
|
od->ipam_info = xzalloc(sizeof *od->ipam_info);
|
||||||
|
}
|
||||||
od->ipam_info->start_ipv4 = ntohl(subnet) + 1;
|
od->ipam_info->start_ipv4 = ntohl(subnet) + 1;
|
||||||
od->ipam_info->total_ipv4s = ~ntohl(mask);
|
od->ipam_info->total_ipv4s = ~ntohl(mask);
|
||||||
od->ipam_info->allocated_ipv4s =
|
od->ipam_info->allocated_ipv4s =
|
||||||
@@ -1022,42 +1034,59 @@ static bool
|
|||||||
ipam_allocate_addresses(struct ovn_datapath *od, struct ovn_port *op,
|
ipam_allocate_addresses(struct ovn_datapath *od, struct ovn_port *op,
|
||||||
const char *addrspec)
|
const char *addrspec)
|
||||||
{
|
{
|
||||||
if (!od || !op || !op->nbsp) {
|
if (!op->nbsp || !od->ipam_info) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t ip = ipam_get_unused_ip(od);
|
|
||||||
if (!ip) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Get or generate MAC address. */
|
||||||
struct eth_addr mac;
|
struct eth_addr mac;
|
||||||
bool check_mac;
|
bool dynamic_mac;
|
||||||
int n = 0;
|
int n = 0;
|
||||||
|
|
||||||
if (ovs_scan(addrspec, ETH_ADDR_SCAN_FMT" dynamic%n",
|
if (ovs_scan(addrspec, ETH_ADDR_SCAN_FMT" dynamic%n",
|
||||||
ETH_ADDR_SCAN_ARGS(mac), &n)
|
ETH_ADDR_SCAN_ARGS(mac), &n)
|
||||||
&& addrspec[n] == '\0') {
|
&& addrspec[n] == '\0') {
|
||||||
check_mac = true;
|
dynamic_mac = false;
|
||||||
} else {
|
} else {
|
||||||
uint64_t mac64 = ipam_get_unused_mac();
|
uint64_t mac64 = ipam_get_unused_mac();
|
||||||
if (!mac64) {
|
if (!mac64) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
eth_addr_from_uint64(mac64, &mac);
|
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
|
/* Generate IPv4 address, if desirable. */
|
||||||
* successfully. */
|
bool dynamic_ip4 = od->ipam_info->allocated_ipv4s != NULL;
|
||||||
ipam_insert_ip(od, ip);
|
uint32_t ip4 = dynamic_ip4 ? ipam_get_unused_ip(od) : 0;
|
||||||
ipam_insert_mac(&mac, check_mac);
|
|
||||||
|
|
||||||
char *new_addr = xasprintf(ETH_ADDR_FMT" "IP_FMT,
|
/* Generate IPv6 address, if desirable. */
|
||||||
ETH_ADDR_ARGS(mac), IP_ARGS(htonl(ip)));
|
bool dynamic_ip6 = od->ipam_info->ipv6_prefix_set;
|
||||||
nbrec_logical_switch_port_set_dynamic_addresses(op->nbsp, new_addr);
|
struct in6_addr ip6;
|
||||||
free(new_addr);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1074,7 +1103,7 @@ build_ipam(struct hmap *datapaths, struct hmap *ports)
|
|||||||
* ports that have the "dynamic" keyword in their addresses column. */
|
* ports that have the "dynamic" keyword in their addresses column. */
|
||||||
struct ovn_datapath *od;
|
struct ovn_datapath *od;
|
||||||
HMAP_FOR_EACH (od, key_node, datapaths) {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -137,12 +137,19 @@
|
|||||||
<group title="IP Address Assignment">
|
<group title="IP Address Assignment">
|
||||||
<p>
|
<p>
|
||||||
These options control automatic IP address management (IPAM) for ports
|
These options control automatic IP address management (IPAM) for ports
|
||||||
attached to the logical switch. To enable IPAM, set <ref
|
attached to the logical switch. To enable IPAM for IPv4, set <ref
|
||||||
column="other_config" key="subnet"/> and optionally <ref
|
column="other_config" key="subnet"/> and optionally <ref
|
||||||
column="other_config:exclude_ips"/>. Then, to request dynamic address
|
column="other_config:exclude_ips"/>. To enable IPAM for IPv6, set
|
||||||
assignment for a particular port, use the <code>dynamic</code> keyword
|
<ref column="other_config" key="ipv6_prefix"/>. IPv4 and IPv6 may
|
||||||
in the <ref table="Logical_Switch_Port" column="addresses"/> column of
|
be enabled together or separately.
|
||||||
the port's <ref table="Logical_Switch_Port"/> row.
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
To request dynamic address assignment for a particular port, use the
|
||||||
|
<code>dynamic</code> keyword in the <ref table="Logical_Switch_Port"
|
||||||
|
column="addresses"/> column of the port's <ref
|
||||||
|
table="Logical_Switch_Port"/> row. This requests both an IPv4 and an
|
||||||
|
IPv6 address, if IPAM for IPv4 and IPv6 are both enabled.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<column name="other_config" key="subnet">
|
<column name="other_config" key="subnet">
|
||||||
@@ -172,6 +179,23 @@
|
|||||||
<li><code>192.168.0.110..192.168.0.120 192.168.0.25..192.168.0.30 192.168.0.144</code></li>
|
<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>
|
</ul>
|
||||||
</column>
|
</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. 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
|
||||||
|
<code>::</code>.
|
||||||
|
<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>
|
||||||
|
|
||||||
<group title="Common Columns">
|
<group title="Common Columns">
|
||||||
|
56
tests/ovn.at
56
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
|
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],
|
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.
|
# Test that address management does not add duplicate MAC for lsp/lrp peers.
|
||||||
ovn-nbctl create Logical_Router name=R2
|
ovn-nbctl create Logical_Router name=R2
|
||||||
ovn-nbctl ls-add sw3
|
ovn-nbctl ls-add sw3
|
||||||
ovn-nbctl lsp-add sw3 p29 -- lsp-set-addresses p29 \
|
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 \
|
ovn-nbctl -- --id=@lrp create Logical_Router_port name=sw3 \
|
||||||
network="192.168.2.1/24" mac=\"0a:00:00:00:00:1f\" \
|
network="192.168.2.1/24" mac=\"0a:00:00:00:00:1f\" \
|
||||||
-- add Logical_Router R2 ports @lrp -- lsp-add sw3 rp-sw3 \
|
-- 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
|
as ovn-sb
|
||||||
OVS_APP_EXIT_AND_WAIT([ovsdb-server])
|
OVS_APP_EXIT_AND_WAIT([ovsdb-server])
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user