Merge pull request #21000 from yuwata/network-dhcp6-pd-introduce-uplink
network: dhcp6: introduce UplinkInterface= for prefix delegation
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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(), ×tamp_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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user