Merge pull request #21000 from yuwata/network-dhcp6-pd-introduce-uplink

network: dhcp6: introduce UplinkInterface= for prefix delegation
This commit is contained in:
Yu Watanabe
2021-10-27 00:32:50 +09:00
committed by GitHub
20 changed files with 603 additions and 159 deletions
+24
View File
@@ -2063,6 +2063,19 @@ Table=1234</programlisting></para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>UseDelegatedPrefix=</varname></term>
<listitem>
<para>When true (the default), the client will request the DHCPv6 server to delegate
prefixes. If the server provides prefixes to be delegated, then subnets of the prefixes are
assigned to the interfaces which enables <varname>DHCPv6PrefixDelegation=</varname>.
See also <varname>DHCPv6PrefixDelegation=</varname> in [Network] section,
[DHCPv6PrefixDelegation] section, and
<ulink url="https://www.rfc-editor.org/rfc/rfc8415.html#section-6.3">RFC 8415</ulink>.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>RouteMetric=</varname></term>
<term><varname>UseDNS=</varname></term>
@@ -2110,6 +2123,17 @@ Table=1234</programlisting></para>
is enabled.</para>
<variablelist class='network-directives'>
<varlistentry>
<term><varname>UplinkInterface=</varname></term>
<listitem>
<para>Specifies the name or the index of the uplink interface, or one of the special values
<literal>:self</literal> and <literal>:auto</literal>. When <literal>:self</literal>, the
interface itself is considered the uplink interface. When <literal>:auto</literal>, the first
link which acquired prefixes to be delegated from the DHCPv6 server is selected. Defaults to
<literal>:auto</literal>.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>SubnetId=</varname></term>
<listitem>
+78
View File
@@ -1267,3 +1267,81 @@ int config_parse_network_duid_rawdata(
/* For backward compatibility, also set DHCPv6 DUID if not specified explicitly. */
return config_parse_duid_rawdata(unit, filename, line, section, section_line, lvalue, false, rvalue, &network->dhcp6_duid, network);
}
int config_parse_uplink(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
Network *network = userdata;
bool accept_none = true;
int *index, r;
char **name;
assert(filename);
assert(section);
assert(lvalue);
assert(rvalue);
assert(userdata);
if (streq(section, "DHCPServer")) {
index = &network->dhcp_server_uplink_index;
name = &network->dhcp_server_uplink_name;
} else if (streq(section, "IPv6SendRA")) {
index = &network->router_uplink_index;
name = &network->router_uplink_name;
} else if (streq(section, "DHCPv6PrefixDelegation")) {
index = &network->dhcp6_pd_uplink_index;
name = &network->dhcp_server_uplink_name;
accept_none = false;
} else
assert_not_reached();
if (isempty(rvalue) || streq(rvalue, ":auto")) {
*index = UPLINK_INDEX_AUTO;
*name = mfree(*name);
return 0;
}
if (accept_none && streq(rvalue, ":none")) {
*index = UPLINK_INDEX_NONE;
*name = mfree(*name);
return 0;
}
if (!accept_none && streq(rvalue, ":self")) {
*index = UPLINK_INDEX_SELF;
*name = mfree(*name);
return 0;
}
r = parse_ifindex(rvalue);
if (r > 0) {
*index = r;
*name = mfree(*name);
return 0;
}
if (!ifname_valid_full(rvalue, IFNAME_VALID_ALTERNATIVE)) {
log_syntax(unit, LOG_WARNING, filename, line, 0,
"Invalid interface name in %s=, ignoring assignment: %s", lvalue, rvalue);
return 0;
}
/* The interface name will be resolved later. */
r = free_and_strdup_warn(name, rvalue);
if (r < 0)
return r;
/* Note, if uplink_name is set, then uplink_index will be ignored. So, the below does not mean
* an uplink interface will be selected automatically. */
*index = UPLINK_INDEX_AUTO;
return 0;
}
+6
View File
@@ -9,6 +9,11 @@
#include "set.h"
#include "time-util.h"
/* Special values for *_uplink_index. */
#define UPLINK_INDEX_AUTO 0 /* uplink will be selected automatically */
#define UPLINK_INDEX_NONE -1 /* uplink will not be selected automatically */
#define UPLINK_INDEX_SELF -2 /* the interface itself is uplink */
#define DHCP_ROUTE_METRIC 1024
#define DHCP6PD_ROUTE_METRIC 256
@@ -100,3 +105,4 @@ CONFIG_PARSER_PROTOTYPE(config_parse_network_duid_type);
CONFIG_PARSER_PROTOTYPE(config_parse_duid_rawdata);
CONFIG_PARSER_PROTOTYPE(config_parse_manager_duid_rawdata);
CONFIG_PARSER_PROTOTYPE(config_parse_network_duid_rawdata);
CONFIG_PARSER_PROTOTYPE(config_parse_uplink);
+139 -87
View File
@@ -42,6 +42,22 @@ bool link_dhcp6_pd_is_enabled(Link *link) {
return link->network->dhcp6_pd;
}
static int dhcp6_pd_resolve_uplink(Link *link, Link **ret) {
if (link->network->dhcp6_pd_uplink_name)
return link_get_by_name(link->manager, link->network->dhcp6_pd_uplink_name, ret);
if (link->network->dhcp6_pd_uplink_index > 0)
return link_get_by_index(link->manager, link->network->dhcp6_pd_uplink_index, ret);
if (link->network->dhcp6_pd_uplink_index == UPLINK_INDEX_SELF) {
*ret = link;
return 0;
}
assert(link->network->dhcp6_pd_uplink_index == UPLINK_INDEX_AUTO);
return -ENOENT;
}
static bool dhcp6_lease_has_pd_prefix(sd_dhcp6_lease *lease) {
uint32_t lifetime_preferred_sec, lifetime_valid_sec;
struct in6_addr pd_prefix;
@@ -575,6 +591,7 @@ static int dhcp6_pd_distribute_prefix(
assert(pd_prefix_len <= 64);
HASHMAP_FOREACH(link, dhcp6_link->manager->links_by_index) {
Link *uplink;
if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED))
continue;
@@ -591,6 +608,15 @@ static int dhcp6_pd_distribute_prefix(
if (assign_preferred_subnet_id != link_has_preferred_subnet_id(link))
continue;
r = dhcp6_pd_resolve_uplink(link, &uplink);
if (r != -ENOENT) {
if (r < 0) /* The uplink interface does not exist yet. */
continue;
if (uplink != dhcp6_link)
continue;
}
r = dhcp6_pd_assign_prefix(link, pd_prefix, pd_prefix_len, lifetime_preferred_usec, lifetime_valid_usec);
if (r < 0) {
if (link == dhcp6_link)
@@ -968,6 +994,59 @@ static int dhcp6_pd_prefix_acquired(Link *dhcp6_link) {
return 0;
}
static int dhcp6_pd_assign_prefixes(Link *link, Link *uplink) {
usec_t timestamp_usec;
int r;
assert(link);
assert(uplink);
assert(uplink->dhcp6_lease);
/* This is similar to dhcp6_pd_prefix_acquired(), but called when a downstream interface
* appears later or reconfiguring the interface. */
r = sd_dhcp6_lease_get_timestamp(uplink->dhcp6_lease, clock_boottime_or_monotonic(), &timestamp_usec);
if (r < 0)
return r;
r = dhcp6_pd_prepare(link);
if (r < 0)
return r;
for (sd_dhcp6_lease_reset_pd_prefix_iter(uplink->dhcp6_lease);;) {
uint32_t lifetime_preferred_sec, lifetime_valid_sec;
usec_t lifetime_preferred_usec, lifetime_valid_usec;
struct in6_addr pd_prefix;
uint8_t pd_prefix_len;
r = sd_dhcp6_lease_get_pd(uplink->dhcp6_lease, &pd_prefix, &pd_prefix_len,
&lifetime_preferred_sec, &lifetime_valid_sec);
if (r < 0)
break;
lifetime_preferred_usec = usec_add(lifetime_preferred_sec * USEC_PER_SEC, timestamp_usec);
lifetime_valid_usec = usec_add(lifetime_valid_sec * USEC_PER_SEC, timestamp_usec);
if (pd_prefix_len > 64)
continue;
/* Mask prefix for safety. */
r = in6_addr_mask(&pd_prefix, pd_prefix_len);
if (r < 0)
return r;
r = dhcp6_pd_assign_prefix(link, &pd_prefix, pd_prefix_len, lifetime_preferred_usec, lifetime_valid_usec);
if (r < 0)
return r;
}
r = dhcp6_pd_finalize(link);
if (r < 0)
return r;
return 0;
}
static int dhcp6_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
int r;
@@ -1320,78 +1399,71 @@ int dhcp6_start(Link *link) {
return 1;
}
int dhcp6_request_prefix_delegation(Link *link) {
static bool dhcp6_pd_uplink_is_ready(Link *link) {
assert(link);
if (!link->network)
return false;
if (!link->network->dhcp6_use_pd_prefix)
return false;
if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED))
return false;
if (!link->dhcp6_client)
return false;
if (sd_dhcp6_client_is_running(link->dhcp6_client) <= 0)
return false;
if (!link->dhcp6_lease)
return false;
return dhcp6_lease_has_pd_prefix(link->dhcp6_lease);
}
static int dhcp6_pd_find_uplink(Link *link, Link **ret) {
Link *l;
assert(link);
assert(link->manager);
assert(link->network);
assert(ret);
if (dhcp6_pd_resolve_uplink(link, &l) >= 0) {
if (!dhcp6_pd_uplink_is_ready(l))
return -EBUSY;
*ret = l;
return 0;
}
HASHMAP_FOREACH(l, link->manager->links_by_index) {
if (!dhcp6_pd_uplink_is_ready(l))
continue;
/* Assume that there exists at most one link which acquired delegated prefixes. */
*ret = l;
return 0;
}
return -ENODEV;
}
int dhcp6_request_prefix_delegation(Link *link) {
Link *uplink;
assert(link);
if (!link_dhcp6_pd_is_enabled(link))
return 0;
log_link_debug(link, "Requesting DHCPv6 prefixes to be delegated for new link");
if (dhcp6_pd_find_uplink(link, &uplink) < 0)
return 0;
HASHMAP_FOREACH(l, link->manager->links_by_index) {
int r, enabled;
if (l == link)
continue;
if (!l->dhcp6_client)
continue;
r = sd_dhcp6_client_get_prefix_delegation(l->dhcp6_client, &enabled);
if (r < 0) {
log_link_warning_errno(l, r, "Cannot get prefix delegation when adding new link: %m");
link_enter_failed(l);
continue;
}
if (enabled == 0) {
r = sd_dhcp6_client_set_prefix_delegation(l->dhcp6_client, 1);
if (r < 0) {
log_link_warning_errno(l, r, "Cannot enable prefix delegation when adding new link: %m");
link_enter_failed(l);
continue;
}
}
r = sd_dhcp6_client_is_running(l->dhcp6_client);
if (r <= 0)
continue;
if (enabled != 0) {
if (dhcp6_lease_has_pd_prefix(l->dhcp6_lease)) {
log_link_debug(l, "Requesting re-assignment of delegated prefixes after adding new link");
r = dhcp6_pd_prefix_acquired(l);
if (r < 0)
link_enter_failed(l);
}
continue;
}
r = sd_dhcp6_client_stop(l->dhcp6_client);
if (r < 0) {
log_link_warning_errno(l, r, "Cannot stop DHCPv6 prefix delegation client after adding new link: %m");
link_enter_failed(l);
continue;
}
r = sd_dhcp6_client_start(l->dhcp6_client);
if (r < 0) {
log_link_warning_errno(l, r, "Cannot restart DHCPv6 prefix delegation client after adding new link: %m");
link_enter_failed(l);
continue;
}
log_link_debug(l, "Restarted DHCPv6 client to acquire prefix delegations after adding new link");
}
/* dhcp6_pd_prefix_acquired() may make the link in failed state. */
if (link->state == LINK_STATE_FAILED)
return -ENOANO;
return 0;
log_link_debug(link, "Requesting subnets of delegated prefixes acquired by %s", uplink->ifname);
return dhcp6_pd_assign_prefixes(link, uplink);
}
static int dhcp6_set_hostname(sd_dhcp6_client *client, Link *link) {
@@ -1423,25 +1495,6 @@ static int dhcp6_set_hostname(sd_dhcp6_client *client, Link *link) {
return 0;
}
static bool dhcp6_enable_prefix_delegation(Link *dhcp6_link) {
Link *link;
assert(dhcp6_link);
assert(dhcp6_link->manager);
HASHMAP_FOREACH(link, dhcp6_link->manager->links_by_index) {
if (link == dhcp6_link)
continue;
if (!link_dhcp6_pd_is_enabled(link))
continue;
return true;
}
return false;
}
static int dhcp6_set_identifier(Link *link, sd_dhcp6_client *client) {
const DUID *duid;
int r;
@@ -1559,11 +1612,10 @@ static int dhcp6_configure(Link *link) {
if (r < 0)
return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set callback: %m");
if (dhcp6_enable_prefix_delegation(link)) {
r = sd_dhcp6_client_set_prefix_delegation(client, true);
if (r < 0)
return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set prefix delegation: %m");
}
r = sd_dhcp6_client_set_prefix_delegation(client, link->network->dhcp6_use_pd_prefix);
if (r < 0)
return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to %s prefix delegation: %m",
enable_disable(link->network->dhcp6_use_pd_prefix));
if (link->network->dhcp6_pd_prefix_length > 0) {
r = sd_dhcp6_client_set_prefix_delegation_hint(client,
+2
View File
@@ -238,6 +238,7 @@ DHCPv4.SendVendorOption, config_parse_dhcp_send_option,
DHCPv4.RouteMTUBytes, config_parse_mtu, AF_INET, offsetof(Network, dhcp_route_mtu)
DHCPv4.FallbackLeaseLifetimeSec, config_parse_dhcp_fallback_lease_lifetime, 0, 0
DHCPv6.UseAddress, config_parse_bool, 0, offsetof(Network, dhcp6_use_address)
DHCPv6.UseDelegatedPrefix, config_parse_bool, 0, offsetof(Network, dhcp6_use_pd_prefix)
DHCPv6.UseDNS, config_parse_dhcp_use_dns, AF_INET6, 0
DHCPv6.UseHostname, config_parse_bool, 0, offsetof(Network, dhcp6_use_hostname)
DHCPv6.UseDomains, config_parse_dhcp_use_domains, AF_INET6, 0
@@ -324,6 +325,7 @@ BridgeMDB.VLANId, config_parse_mdb_vlan_id,
BridgeVLAN.PVID, config_parse_brvlan_pvid, 0, 0
BridgeVLAN.VLAN, config_parse_brvlan_vlan, 0, 0
BridgeVLAN.EgressUntagged, config_parse_brvlan_untagged, 0, 0
DHCPv6PrefixDelegation.UplinkInterface, config_parse_uplink, 0, 0
DHCPv6PrefixDelegation.SubnetId, config_parse_dhcp6_pd_subnet_id, 0, offsetof(Network, dhcp6_pd_subnet_id)
DHCPv6PrefixDelegation.Announce, config_parse_bool, 0, offsetof(Network, dhcp6_pd_announce)
DHCPv6PrefixDelegation.Assign, config_parse_bool, 0, offsetof(Network, dhcp6_pd_assign)
+1 -67
View File
@@ -400,6 +400,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi
.dhcp_broadcast = -1,
.dhcp6_use_address = true,
.dhcp6_use_pd_prefix = true,
.dhcp6_use_dns = true,
.dhcp6_use_hostname = true,
.dhcp6_use_ntp = true,
@@ -1244,73 +1245,6 @@ int config_parse_link_group(
return 0;
}
int config_parse_uplink(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
Network *network = userdata;
int *index, r;
char **name;
assert(filename);
assert(section);
assert(lvalue);
assert(rvalue);
if (streq(section, "DHCPServer")) {
index = &network->dhcp_server_uplink_index;
name = &network->dhcp_server_uplink_name;
} else if (streq(section, "IPv6SendRA")) {
index = &network->router_uplink_index;
name = &network->router_uplink_name;
} else
assert_not_reached();
if (isempty(rvalue) || streq(rvalue, ":auto")) {
*index = UPLINK_INDEX_AUTO;
*name = mfree(*name);
return 0;
}
if (streq(rvalue, ":none")) {
*index = UPLINK_INDEX_NONE;
*name = mfree(*name);
return 0;
}
r = parse_ifindex(rvalue);
if (r > 0) {
*index = r;
*name = mfree(*name);
return 0;
}
if (!ifname_valid_full(rvalue, IFNAME_VALID_ALTERNATIVE)) {
log_syntax(unit, LOG_WARNING, filename, line, 0,
"Invalid interface name in %s=, ignoring assignment: %s", lvalue, rvalue);
return 0;
}
/* The interface name will be resolved later. */
r = free_and_strdup_warn(name, rvalue);
if (r < 0)
return r;
/* Note, if uplink_name is set, then uplink_index will be ignored. So, the below does not mean
* an uplink interface will be selected automatically. */
*index = UPLINK_INDEX_AUTO;
return 0;
}
DEFINE_CONFIG_PARSE_ENUM(config_parse_required_family_for_online, link_required_address_family, AddressFamily,
"Failed to parse RequiredFamilyForOnline= setting");
+3 -5
View File
@@ -26,10 +26,6 @@
#include "resolve-util.h"
#include "socket-netlink.h"
/* Special values for *_uplink_index. */
#define UPLINK_INDEX_AUTO 0 /* uplink will be selected automatically */
#define UPLINK_INDEX_NONE -1 /* uplink will not be selected automatically */
typedef enum KeepConfiguration {
KEEP_CONFIGURATION_NO = 0,
KEEP_CONFIGURATION_DHCP_ON_START = 1 << 0,
@@ -169,6 +165,7 @@ struct Network {
/* DHCPv6 Client support */
bool dhcp6_use_address;
bool dhcp6_use_pd_prefix;
bool dhcp6_use_dns;
bool dhcp6_use_dns_set;
bool dhcp6_use_hostname;
@@ -244,6 +241,8 @@ struct Network {
int64_t dhcp6_pd_subnet_id;
uint32_t dhcp6_pd_route_metric;
Set *dhcp6_pd_tokens;
int dhcp6_pd_uplink_index;
char *dhcp6_pd_uplink_name;
/* Bridge Support */
int use_bpdu;
@@ -384,7 +383,6 @@ CONFIG_PARSER_PROTOTYPE(config_parse_keep_configuration);
CONFIG_PARSER_PROTOTYPE(config_parse_ipv6_link_local_address_gen_mode);
CONFIG_PARSER_PROTOTYPE(config_parse_activation_policy);
CONFIG_PARSER_PROTOTYPE(config_parse_link_group);
CONFIG_PARSER_PROTOTYPE(config_parse_uplink);
const struct ConfigPerfItem* network_network_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
@@ -130,6 +130,7 @@ RouteMTUBytes=
FallbackLeaseLifetimeSec=
[DHCPv6]
UseAddress=
UseDelegatedPrefix=
UseNTP=
UseDNS=
UseHostname=
@@ -150,6 +151,7 @@ DUIDType=
DUIDRawData=
RouteTable=
[DHCPv6PrefixDelegation]
UplinkInterface=
SubnetId=
Announce=
Assign=
+4
View File
@@ -0,0 +1,4 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[NetDev]
Name=dummy99
Kind=dummy
@@ -0,0 +1,9 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[NetDev]
Name=veth98
Kind=veth
MACAddress=12:34:56:78:9a:be
[Peer]
Name=veth98-peer
MACAddress=12:34:56:78:9a:bf
@@ -0,0 +1,16 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[Match]
Name=dummy97
[Network]
IPv6PrivacyExtensions=yes
IPv6AcceptRA=no
DHCP=no
DHCPv6PrefixDelegation=yes
[DHCPv6PrefixDelegation]
UplinkInterface=veth99
SubnetId=6
Announce=no
Token=eui64
Token=::1a:2b:3c:4d
@@ -0,0 +1,16 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[Match]
Name=dummy98
[Network]
IPv6PrivacyExtensions=yes
IPv6AcceptRA=no
DHCP=no
DHCPv6PrefixDelegation=yes
[DHCPv6PrefixDelegation]
UplinkInterface=veth99
SubnetId=3
Announce=no
Token=eui64
Token=::1a:2b:3c:4d
@@ -0,0 +1,14 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[Match]
Name=dummy99
[Network]
IPv6PrivacyExtensions=yes
IPv6AcceptRA=no
DHCP=no
DHCPv6PrefixDelegation=yes
[DHCPv6PrefixDelegation]
UplinkInterface=veth99
Assign=no
Announce=no
@@ -0,0 +1,16 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[Match]
Name=test1
[Network]
IPv6PrivacyExtensions=yes
IPv6AcceptRA=no
DHCP=no
DHCPv6PrefixDelegation=yes
[DHCPv6PrefixDelegation]
UplinkInterface=veth99
SubnetId=0
Announce=no
Token=eui64
Token=::1a:2b:3c:4d
@@ -0,0 +1,11 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[Match]
Name=veth98-peer
[Network]
IPv6PrivacyExtensions=yes
IPv6AcceptRA=yes
[IPv6AcceptRA]
Token=eui64
Token=::1a:2b:3c:4e
@@ -0,0 +1,22 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[Match]
Name=veth98
[Network]
IPv6PrivacyExtensions=yes
IPv6AcceptRA=no
DHCP=no
DHCPv6PrefixDelegation=yes
IPv6SendRA=yes
[DHCPv6PrefixDelegation]
UplinkInterface=veth99
SubnetId=9
Announce=yes
Token=eui64
Token=::1a:2b:3c:4d
[IPv6SendRA]
UplinkInterface=:none
EmitDNS=no
EmitDomains=no
@@ -0,0 +1,7 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[Match]
Name=veth-peer
[Network]
IPv6AcceptRA=no
Address=3ffe:501:ffff:100::1/64
@@ -0,0 +1,19 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[Match]
Name=veth99
[Network]
IPv6PrivacyExtensions=yes
IPv6AcceptRA=no
DHCP=ipv6
DHCPv6PrefixDelegation=yes
[DHCPv6]
WithoutRA=solicit
[DHCPv6PrefixDelegation]
UplinkInterface=:self
SubnetId=10
Announce=no
Token=eui64
Token=::1a:2b:3c:4d
@@ -0,0 +1,21 @@
default-lease-time 2592000;
preferred-lifetime 604800;
option dhcp-renewal-time 3600;
option dhcp-rebinding-time 7200;
# Enable RFC 5007 support (same than for DHCPv4)
allow leasequery;
option dhcp6.name-servers 3ffe:501:ffff:100:200:ff:fe00:3f3e;
option dhcp6.domain-search "test.example.com","example.com";
option dhcp6.info-refresh-time 21600;
subnet6 3ffe:501:ffff:100::/64 {
# Addresses available to clients
range6 3ffe:501:ffff:100::10 3ffe:501:ffff:100::100;
# Some /64 prefixes available for Prefix Delegation (RFC 3633)
prefix6 3ffe:501:ffff:200:: 3ffe:501:ffff:f00:: /56;
}
+193
View File
@@ -14,6 +14,7 @@ import sys
import time
import unittest
from shutil import copytree
from pathlib import Path
network_unit_file_path='/run/systemd/network'
networkd_runtime_directory='/run/systemd/netif'
@@ -26,6 +27,9 @@ dnsmasq_pid_file='/run/networkd-ci/test-dnsmasq.pid'
dnsmasq_log_file='/run/networkd-ci/test-dnsmasq.log'
dnsmasq_lease_file='/run/networkd-ci/test-dnsmasq.lease'
isc_dhcpd_pid_file='/run/networkd-ci/test-isc-dhcpd.pid'
isc_dhcpd_lease_file='/run/networkd-ci/test-isc-dhcpd.lease'
systemd_lib_paths=['/usr/lib/systemd', '/lib/systemd']
which_paths=':'.join(systemd_lib_paths + os.getenv('PATH', os.defpath).lstrip(':').split(':'))
@@ -531,6 +535,17 @@ def remove_dnsmasq_log_file():
if os.path.exists(dnsmasq_log_file):
os.remove(dnsmasq_log_file)
def start_isc_dhcpd(interface, conf_file):
conf_file_path = os.path.join(networkd_ci_path, conf_file)
isc_dhcpd_command = f'dhcpd -6 -cf {conf_file_path} -lf {isc_dhcpd_lease_file} -pf {isc_dhcpd_pid_file} {interface}'
Path(isc_dhcpd_lease_file).touch()
check_output(isc_dhcpd_command)
def stop_isc_dhcpd():
stop_by_pid_file(isc_dhcpd_pid_file)
if os.path.exists(isc_dhcpd_lease_file):
os.remove(isc_dhcpd_lease_file)
def remove_networkd_state_files():
if os.path.exists(os.path.join(networkd_runtime_directory, 'state')):
os.remove(os.path.join(networkd_runtime_directory, 'state'))
@@ -4846,6 +4861,184 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
print(output)
self.assertRegex(output, 'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic veth99')
class NetworkdDHCP6PDTests(unittest.TestCase, Utilities):
links = [
'dummy97',
'dummy98',
'dummy99',
'test1',
'veth98',
'veth99',
]
units = [
'11-dummy.netdev',
'12-dummy.netdev',
'13-dummy.netdev',
'25-veth.netdev',
'25-veth-downstream.netdev',
'dhcp6pd-downstream-dummy97.network',
'dhcp6pd-downstream-dummy98.network',
'dhcp6pd-downstream-dummy99.network',
'dhcp6pd-downstream-test1.network',
'dhcp6pd-downstream-veth98.network',
'dhcp6pd-downstream-veth98-peer.network',
'dhcp6pd-server.network',
'dhcp6pd-upstream.network',
]
def setUp(self):
stop_isc_dhcpd()
remove_links(self.links)
stop_networkd(show_logs=False)
def tearDown(self):
stop_isc_dhcpd()
remove_links(self.links)
remove_unit_from_networkd_path(self.units)
stop_networkd(show_logs=True)
def test_dhcp6pd(self):
copy_unit_to_networkd_unit_path('25-veth.netdev', 'dhcp6pd-server.network', 'dhcp6pd-upstream.network',
'25-veth-downstream.netdev', 'dhcp6pd-downstream-veth98.network', 'dhcp6pd-downstream-veth98-peer.network',
'11-dummy.netdev', 'dhcp6pd-downstream-test1.network',
'dhcp6pd-downstream-dummy97.network',
'12-dummy.netdev', 'dhcp6pd-downstream-dummy98.network',
'13-dummy.netdev', 'dhcp6pd-downstream-dummy99.network')
start_networkd()
self.wait_online(['veth-peer:carrier'])
start_isc_dhcpd('veth-peer', 'isc-dhcpd-dhcp6pd.conf')
self.wait_online(['veth-peer:routable', 'veth99:routable', 'test1:routable', 'dummy98:routable', 'dummy99:degraded',
'veth98:routable', 'veth98-peer:routable'])
print('### ip -6 address show dev veth-peer scope global')
output = check_output('ip -6 address show dev veth-peer scope global')
print(output)
self.assertIn('inet6 3ffe:501:ffff:100::1/64 scope global', output)
print('### ip -6 address show dev veth99 scope global')
output = check_output('ip -6 address show dev veth99 scope global')
print(output)
# IA_NA
self.assertRegex(output, 'inet6 3ffe:501:ffff:100::[0-9]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)')
# address in IA_PD (Token=static)
self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]10:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr')
# address in IA_PD (Token=eui64)
self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]10:1034:56ff:fe78:9abc/64 (metric 256 |)scope global dynamic mngtmpaddr')
# address in IA_PD (temporary)
# Note that the temporary addresses may appear after the link enters configured state
self.wait_address('veth99', 'inet6 3ffe:501:ffff:[2-9a-f]10:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6')
print('### ip -6 address show dev test1 scope global')
output = check_output('ip -6 address show dev test1 scope global')
print(output)
# address in IA_PD (Token=static)
self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]00:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr')
# address in IA_PD (temporary)
self.wait_address('test1', 'inet6 3ffe:501:ffff:[2-9a-f]00:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6')
print('### ip -6 address show dev dummy98 scope global')
output = check_output('ip -6 address show dev dummy98 scope global')
print(output)
# address in IA_PD (Token=static)
self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]03:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr')
# address in IA_PD (temporary)
self.wait_address('dummy98', 'inet6 3ffe:501:ffff:[2-9a-f]03:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6')
print('### ip -6 address show dev veth98 scope global')
output = check_output('ip -6 address show dev veth98 scope global')
print(output)
# address in IA_PD (Token=static)
self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]09:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr')
# address in IA_PD (Token=eui64)
self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]09:1034:56ff:fe78:9abe/64 (metric 256 |)scope global dynamic mngtmpaddr')
# address in IA_PD (temporary)
self.wait_address('veth98', 'inet6 3ffe:501:ffff:[2-9a-f]09:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6')
print('### ip -6 address show dev veth98-peer scope global')
output = check_output('ip -6 address show dev veth98-peer scope global')
print(output)
# NDisc address (Token=static)
self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]09:1a:2b:3c:4e/64 (metric 256 |)scope global dynamic mngtmpaddr')
# NDisc address (Token=eui64)
self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]09:1034:56ff:fe78:9abf/64 (metric 256 |)scope global dynamic mngtmpaddr')
# NDisc address (temporary)
self.wait_address('veth98-peer', 'inet6 3ffe:501:ffff:[2-9a-f]09:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6')
print('### ip -6 address show dev dummy99 scope global')
output = check_output('ip -6 address show dev dummy98 scope global')
print(output)
print('### ip -6 route show type unreachable')
output = check_output('ip -6 route show type unreachable')
print(output)
self.assertRegex(output, 'unreachable 3ffe:501:ffff:[2-9a-f]00::/56 dev lo proto dhcp')
print('### ip -6 route show dev veth99')
output = check_output('ip -6 route show dev veth99')
print(output)
self.assertRegex(output, '3ffe:501:ffff:[2-9a-f]10::/64 proto kernel metric [0-9]* expires')
print('### ip -6 route show dev test1')
output = check_output('ip -6 route show dev test1')
print(output)
self.assertRegex(output, '3ffe:501:ffff:[2-9a-f]00::/64 proto kernel metric [0-9]* expires')
print('### ip -6 route show dev dummy98')
output = check_output('ip -6 route show dev dummy98')
print(output)
self.assertRegex(output, '3ffe:501:ffff:[2-9a-f]03::/64 proto kernel metric [0-9]* expires')
print('### ip -6 route show dev dummy99')
output = check_output('ip -6 route show dev dummy99')
print(output)
self.assertRegex(output, '3ffe:501:ffff:[2-9a-f]01::/64 proto dhcp metric [0-9]* expires')
print('### ip -6 route show dev veth98')
output = check_output('ip -6 route show dev veth98')
print(output)
self.assertRegex(output, '3ffe:501:ffff:[2-9a-f]09::/64 proto kernel metric [0-9]* expires')
print('### ip -6 route show dev veth98-peer')
output = check_output('ip -6 route show dev veth98-peer')
print(output)
self.assertRegex(output, '3ffe:501:ffff:[2-9a-f]09::/64 proto ra metric [0-9]* expires')
# Test case for a downstream which appears later
check_output('ip link add dummy97 type dummy')
self.wait_online(['dummy97:routable'])
print('### ip -6 address show dev dummy97 scope global')
output = check_output('ip -6 address show dev dummy97 scope global')
print(output)
# address in IA_PD (Token=static)
self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]06:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr')
# address in IA_PD (temporary)
self.wait_address('dummy97', 'inet6 3ffe:501:ffff:[2-9a-f]06:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6')
print('### ip -6 route show dev dummy97')
output = check_output('ip -6 route show dev dummy97')
print(output)
self.assertRegex(output, '3ffe:501:ffff:[2-9a-f]06::/64 proto kernel metric [0-9]* expires')
# Test case for reconfigure
check_output(*networkctl_cmd, 'reconfigure', 'dummy98', env=env)
self.wait_online(['dummy98:routable'])
print('### ip -6 address show dev dummy98 scope global')
output = check_output('ip -6 address show dev dummy98 scope global')
print(output)
# address in IA_PD (Token=static)
self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]03:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr')
# address in IA_PD (temporary)
self.wait_address('dummy98', 'inet6 3ffe:501:ffff:[2-9a-f]03:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6')
print('### ip -6 route show dev dummy98')
output = check_output('ip -6 route show dev dummy98')
print(output)
self.assertRegex(output, '3ffe:501:ffff:[2-9a-f]03::/64 proto kernel metric [0-9]* expires')
class NetworkdIPv6PrefixTests(unittest.TestCase, Utilities):
links = [
'dummy98',