diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index ab08797a9e..1dd4d15010 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -2185,219 +2185,253 @@ Table=1234
[IPv6AcceptRA] Section Options
- The [IPv6AcceptRA] section configures the IPv6 Router Advertisement (RA) client, if it is enabled
- with the IPv6AcceptRA= setting described above:
+ The [IPv6AcceptRA] section configures the IPv6 Router Advertisement (RA) client, if it is enabled
+ with the IPv6AcceptRA= setting described above:
-
-
- Token=
-
- Specifies an optional address generation mode for the Stateless Address
- Autoconfiguration (SLAAC). Supported modes are eui64,
- static, and prefixstable.
+
+
+ Token=
+
+ Specifies an optional address generation mode for the Stateless Address
+ Autoconfiguration (SLAAC). The following values are supported:
- When the mode is set to eui64, then the EUI-64 algorithm will be
- used to generate an address for that prefix.
+
+
+
+
+
+ The EUI-64 algorithm will be used to generate an address for that prefix.
+
+
+
+
+
+
+
+ An IPv6 address must be specified after a colon (:), and the
+ lower bits of the supplied address are combined with the upper bits of a prefix
+ received in a Router Advertisement (RA) message to form a complete address. Note
+ that if multiple prefixes are received in an RA message, or in multiple RA messages,
+ addresses will be formed from each of them using the supplied address. This mode
+ implements SLAAC but uses a static interface identifier instead of an identifier
+ generated by using the EUI-64 algorithm. Because the interface identifier is static,
+ if Duplicate Address Detection detects that the computed address is a duplicate
+ (in use by another node on the link), then this mode will fail to provide an address
+ for that prefix. If an IPv6 address without mode is specified, then
+ static mode is assumed.
+
+
+
+
+
+
+
+ The algorithm specified in
+ RFC 7217 will be used to
+ generate interface identifiers. This mode can optionally take an IPv6 address
+ separated with a colon (:). If an IPv6 address is specified,
+ then an interface identifier is generated only when a prefix received in an RA
+ message matches the supplied address.
+
+
+ This mode can also optionally take a non-null UUID in the format which
+ sd_id128_from_string() accepts, e.g.
+ 86b123b969ba4b7eb8b3d8605123525a or
+ 86b123b9-69ba-4b7e-b8b3-d8605123525a. If a UUID is specified, the
+ value is used as the secret key to generate interface identifiers. If not specified,
+ then an application specific ID generated with the system's machine-ID will be used
+ as the secret key. See
+ sd-id1283,
+ sd_id128_from_string3,
+ and
+ sd_id128_get_machine3,
+
+
+ Note that the prefixstable algorithm uses both the interface
+ name and MAC address as input to the hash to compute the interface identifier, so
+ if either of those are changed the resulting interface identifier (and address)
+ will be changed, even if the prefix received in the RA message has not been
+ changed.
+
+
+
+
- When the mode is set to static, an IPv6 address must be
- specified after a colon (:), and the lower bits of the supplied
- address are combined with the upper bits of a prefix received in a Router Advertisement
- (RA) message to form a complete address. Note that if multiple prefixes are received in an
- RA message, or in multiple RA messages, addresses will be formed from each of them using
- the supplied address. This mode implements SLAAC but uses a static interface identifier
- instead of an identifier generated by using the EUI-64 algorithm. Because the interface
- identifier is static, if Duplicate Address Detection detects that the computed address is a
- duplicate (in use by another node on the link), then this mode will fail to provide an
- address for that prefix. If an IPv6 address without mode is specified, then
- static mode is assumed.
+ If no address generation mode is specified (which is the default), or a received
+ prefix does not match any of the addresses provided in prefixstable
+ mode, then the EUI-64 algorithm will be used to form an interface identifier for that
+ prefix.
- When the mode is set to prefixstable the
- RFC 7217 algorithm for generating
- interface identifiers will be used. This mode can optionally take an IPv6 address separated
- with a colon (:). If an IPv6 address is specified, then an interface
- identifier is generated only when a prefix received in an RA message matches the supplied
- address.
+ This setting can be specified multiple times. If an empty string is assigned, then
+ the all previous assignments are cleared.
- If no address generation mode is specified (which is the default), or a received
- prefix does not match any of the addresses provided in prefixstable
- mode, then the EUI-64 algorithm will be used to form an interface identifier for that
- prefix.
-
- Note that the prefixstable algorithm uses both the interface
- name and MAC address as input to the hash to compute the interface identifier, so if either
- of those are changed the resulting interface identifier (and address) will be changed, even
- if the prefix received in the RA message has not been changed.
-
- This setting can be specified multiple times. If an empty string is assigned, then
- the all previous assignments are cleared.
-
- Examples:
- Token=eui64
+ Examples:
+ Token=eui64
Token=::1a:2b:3c:4d
Token=static:::1a:2b:3c:4d
Token=prefixstable
Token=prefixstable:2002:da8:1::
-
-
+
+
-
- UseDNS=
-
- When true (the default), the DNS servers received in the Router Advertisement will be used.
+
+ UseDNS=
+
+ When true (the default), the DNS servers received in the Router Advertisement will be used.
- This corresponds to the option in resolv.conf5.
-
-
+ This corresponds to the option in resolv.conf5.
+
+
-
- UseDomains=
-
- Takes a boolean, or the special value route. When true, the domain name
- received via IPv6 Router Advertisement (RA) will be used as DNS search domain over this link, similar to
- the effect of the setting. If set to route, the domain name
- received via IPv6 RA will be used for routing DNS queries only, but not for searching, similar to the
- effect of the setting when the argument is prefixed with
- ~. Defaults to false.
+
+ UseDomains=
+
+ Takes a boolean, or the special value route. When true, the domain name
+ received via IPv6 Router Advertisement (RA) will be used as DNS search domain over this link, similar to
+ the effect of the setting. If set to route, the domain name
+ received via IPv6 RA will be used for routing DNS queries only, but not for searching, similar to the
+ effect of the setting when the argument is prefixed with
+ ~. Defaults to false.
- It is recommended to enable this option only on trusted networks, as setting this affects resolution
- of all hostnames, in particular of single-label names. It is generally safer to use the supplied domain
- only as routing domain, rather than as search domain, in order to not have it affect local resolution of
- single-label names.
+ It is recommended to enable this option only on trusted networks, as setting this affects resolution
+ of all hostnames, in particular of single-label names. It is generally safer to use the supplied domain
+ only as routing domain, rather than as search domain, in order to not have it affect local resolution of
+ single-label names.
- When set to true, this setting corresponds to the option in resolv.conf5.
-
-
+ When set to true, this setting corresponds to the option in resolv.conf5.
+
+
-
- RouteTable=num
-
- The table identifier for the routes received in the Router Advertisement
- (a number between 1 and 4294967295, or 0 to unset).
- The table can be retrieved using ip route show table num.
-
-
-
+
+ RouteTable=num
+
+ The table identifier for the routes received in the Router Advertisement
+ (a number between 1 and 4294967295, or 0 to unset).
+ The table can be retrieved using ip route show table num.
+
+
+
-
- RouteMetric=
-
- Set the routing metric for the routes received in the Router Advertisement. Takes an
- unsigned integer in the range 0…4294967295. Defaults to 1024.
-
-
+
+ RouteMetric=
+
+ Set the routing metric for the routes received in the Router Advertisement. Takes an
+ unsigned integer in the range 0…4294967295. Defaults to 1024.
+
+
-
- UseMTU=
-
- Takes a boolean. When true, the MTU received in the Router Advertisement will be
- used. Defaults to true.
-
-
+
+ UseMTU=
+
+ Takes a boolean. When true, the MTU received in the Router Advertisement will be
+ used. Defaults to true.
+
+
-
- UseGateway=
-
- When true (the default), the router address will be configured as the default gateway.
-
-
-
+
+ UseGateway=
+
+ When true (the default), the router address will be configured as the default gateway.
+
+
+
-
- UseRoutePrefix=
-
- When true (the default), the routes corresponding to the route prefixes received in
- the Router Advertisement will be configured.
-
-
+
+ UseRoutePrefix=
+
+ When true (the default), the routes corresponding to the route prefixes received in
+ the Router Advertisement will be configured.
+
+
-
- UseAutonomousPrefix=
-
- When true (the default), the autonomous prefix received in the Router Advertisement will be used and take
- precedence over any statically configured ones.
-
-
+
+ UseAutonomousPrefix=
+
+ When true (the default), the autonomous prefix received in the Router Advertisement will be used and take
+ precedence over any statically configured ones.
+
+
-
- UseOnLinkPrefix=
-
- When true (the default), the onlink prefix received in the Router Advertisement will be
- used and takes precedence over any statically configured ones.
-
-
+
+ UseOnLinkPrefix=
+
+ When true (the default), the onlink prefix received in the Router Advertisement will be
+ used and takes precedence over any statically configured ones.
+
+
-
- RouterDenyList=
-
- A whitespace-separated list of IPv6 router addresses. Each address can optionally
- take a prefix length after /. Any information advertised by the listed
- router is ignored.
-
-
+
+ RouterDenyList=
+
+ A whitespace-separated list of IPv6 router addresses. Each address can optionally
+ take a prefix length after /. Any information advertised by the listed
+ router is ignored.
+
+
-
- RouterAllowList=
-
- A whitespace-separated list of IPv6 router addresses. Each address can optionally
- take a prefix length after /. Only information advertised by the listed
- router is accepted. Note that if RouterAllowList= is configured then
- RouterDenyList= is ignored.
-
-
+
+ RouterAllowList=
+
+ A whitespace-separated list of IPv6 router addresses. Each address can optionally
+ take a prefix length after /. Only information advertised by the listed
+ router is accepted. Note that if RouterAllowList= is configured then
+ RouterDenyList= is ignored.
+
+
-
- PrefixDenyList=
-
- A whitespace-separated list of IPv6 prefixes. Each prefix can optionally take its
- prefix length after /. IPv6 prefixes supplied via router advertisements
- in the list are ignored.
-
-
+
+ PrefixDenyList=
+
+ A whitespace-separated list of IPv6 prefixes. Each prefix can optionally take its
+ prefix length after /. IPv6 prefixes supplied via router advertisements
+ in the list are ignored.
+
+
-
- PrefixAllowList=
-
- A whitespace-separated list of IPv6 prefixes. Each prefix can optionally take its
- prefix length after /. IPv6 prefixes supplied via router advertisements
- in the list are allowed. Note that if PrefixAllowList= is configured
- then PrefixDenyList= is ignored.
-
-
+
+ PrefixAllowList=
+
+ A whitespace-separated list of IPv6 prefixes. Each prefix can optionally take its
+ prefix length after /. IPv6 prefixes supplied via router advertisements
+ in the list are allowed. Note that if PrefixAllowList= is configured
+ then PrefixDenyList= is ignored.
+
+
-
- RouteDenyList=
-
- A whitespace-separated list of IPv6 route prefixes. Each prefix can optionally take
- its prefix length after /. IPv6 route prefixes supplied via router
- advertisements in the list are ignored.
-
-
+
+ RouteDenyList=
+
+ A whitespace-separated list of IPv6 route prefixes. Each prefix can optionally take
+ its prefix length after /. IPv6 route prefixes supplied via router
+ advertisements in the list are ignored.
+
+
-
- RouteAllowList=
-
- A whitespace-separated list of IPv6 route prefixes. Each prefix can optionally take
- its prefix length after /. IPv6 route prefixes supplied via router
- advertisements in the list are allowed. Note that if RouteAllowList= is
- configured then RouteDenyList= is ignored.
-
-
+
+ RouteAllowList=
+
+ A whitespace-separated list of IPv6 route prefixes. Each prefix can optionally take
+ its prefix length after /. IPv6 route prefixes supplied via router
+ advertisements in the list are allowed. Note that if RouteAllowList= is
+ configured then RouteDenyList= is ignored.
+
+
-
- DHCPv6Client=
-
- Takes a boolean, or the special value always. When true, the
- DHCPv6 client will be started when the RA has the managed or other information flag. If set
- to always, the DHCPv6 client will be started in managed mode when an RA
- is received, even if neither managed nor other information flag is set in the RA. This will
- be ignored when WithoutRA= in the [DHCPv6] section is enabled, or
- UplinkInterface=:self in the [DHCPv6PrefixDelegation] section is
- specified. Defaults to true.
-
-
-
+
+ DHCPv6Client=
+
+ Takes a boolean, or the special value always. When true, the
+ DHCPv6 client will be started when the RA has the managed or other information flag. If set
+ to always, the DHCPv6 client will be started in managed mode when an RA
+ is received, even if neither managed nor other information flag is set in the RA. This will
+ be ignored when WithoutRA= in the [DHCPv6] section is enabled, or
+ UplinkInterface=:self in the [DHCPv6PrefixDelegation] section is
+ specified. Defaults to true.
+
+
+
diff --git a/src/network/networkd-address-generation.c b/src/network/networkd-address-generation.c
index 2d574ee78b..0d7d3ee653 100644
--- a/src/network/networkd-address-generation.c
+++ b/src/network/networkd-address-generation.c
@@ -4,6 +4,7 @@
#include "sd-id128.h"
+#include "id128-util.h"
#include "memory-util.h"
#include "networkd-address-generation.h"
#include "networkd-link.h"
@@ -35,6 +36,7 @@ typedef enum AddressGenerationType {
typedef struct IPv6Token {
AddressGenerationType type;
struct in6_addr address;
+ sd_id128_t secret_key;
} IPv6Token;
static void generate_eui64_address(const Link *link, const struct in6_addr *prefix, struct in6_addr *ret) {
@@ -117,29 +119,35 @@ static void generate_stable_private_address_one(
static int generate_stable_private_address(
Link *link,
const sd_id128_t *app_id,
+ const sd_id128_t *secret_key,
const struct in6_addr *prefix,
struct in6_addr *ret) {
+ sd_id128_t secret_machine_key;
struct in6_addr addr;
- sd_id128_t secret_key;
uint8_t i;
int r;
assert(link);
assert(app_id);
+ assert(secret_key);
assert(prefix);
assert(ret);
- r = sd_id128_get_machine_app_specific(*app_id, &secret_key);
- if (r < 0)
- return log_link_debug_errno(link, r, "Failed to generate secret key for IPv6 stable private address: %m");
+ if (sd_id128_is_null(*secret_key)) {
+ r = sd_id128_get_machine_app_specific(*app_id, &secret_machine_key);
+ if (r < 0)
+ return log_link_debug_errno(link, r, "Failed to generate secret key for IPv6 stable private address: %m");
+
+ secret_key = &secret_machine_key;
+ }
/* While this loop uses dad_counter and a retry limit as specified in RFC 7217, the loop does
* not actually attempt Duplicate Address Detection; the counter will be incremented only when
* the address generation algorithm produces an invalid address, and the loop may exit with an
* address which ends up being unusable due to duplication on the link. */
for (i = 0; i < DAD_CONFLICTS_IDGEN_RETRIES_RFC7217; i++) {
- generate_stable_private_address_one(link, &secret_key, prefix, i, &addr);
+ generate_stable_private_address_one(link, secret_key, prefix, i, &addr);
if (stable_private_address_is_valid(&addr))
break;
@@ -192,7 +200,7 @@ static int generate_addresses(
if (in6_addr_is_set(&j->address) && !in6_addr_equal(&j->address, &masked))
continue;
- if (generate_stable_private_address(link, app_id, &masked, &addr) < 0)
+ if (generate_stable_private_address(link, app_id, &j->secret_key, &masked, &addr) < 0)
continue;
break;
@@ -244,6 +252,7 @@ int radv_generate_addresses(Link *link, Set *tokens, const struct in6_addr *pref
static void ipv6_token_hash_func(const IPv6Token *p, struct siphash *state) {
siphash24_compress(&p->type, sizeof(p->type), state);
siphash24_compress(&p->address, sizeof(p->address), state);
+ id128_hash_func(&p->secret_key, state);
}
static int ipv6_token_compare_func(const IPv6Token *a, const IPv6Token *b) {
@@ -253,7 +262,11 @@ static int ipv6_token_compare_func(const IPv6Token *a, const IPv6Token *b) {
if (r != 0)
return r;
- return memcmp(&a->address, &b->address, sizeof(struct in6_addr));
+ r = memcmp(&a->address, &b->address, sizeof(struct in6_addr));
+ if (r != 0)
+ return r;
+
+ return id128_compare_func(&a->secret_key, &b->secret_key);
}
DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
@@ -263,12 +276,13 @@ DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
ipv6_token_compare_func,
free);
-static int ipv6_token_add(Set **tokens, AddressGenerationType type, const struct in6_addr *addr) {
+static int ipv6_token_add(Set **tokens, AddressGenerationType type, const struct in6_addr *addr, const sd_id128_t *secret_key) {
IPv6Token *p;
assert(tokens);
assert(type >= 0 && type < _ADDRESS_GENERATION_TYPE_MAX);
assert(addr);
+ assert(secret_key);
p = new(IPv6Token, 1);
if (!p)
@@ -277,6 +291,7 @@ static int ipv6_token_add(Set **tokens, AddressGenerationType type, const struct
*p = (IPv6Token) {
.type = type,
.address = *addr,
+ .secret_key = *secret_key,
};
return set_ensure_consume(tokens, &ipv6_token_hash_ops, p);
@@ -294,10 +309,12 @@ int config_parse_address_generation_type(
void *data,
void *userdata) {
+ _cleanup_free_ char *addr_alloc = NULL;
+ sd_id128_t secret_key = SD_ID128_NULL;
union in_addr_union buffer = {};
AddressGenerationType type;
Set **tokens = data;
- const char *p;
+ const char *addr;
int r;
assert(filename);
@@ -310,33 +327,64 @@ int config_parse_address_generation_type(
return 0;
}
- if ((p = startswith(rvalue, "prefixstable"))) {
+ if ((addr = startswith(rvalue, "prefixstable"))) {
+ const char *comma;
+
type = ADDRESS_GENERATION_PREFIXSTABLE;
- if (*p == ':')
- p++;
- else if (*p == '\0')
- p = NULL;
- else {
+ if (*addr == ':') {
+ addr++;
+
+ comma = strchr(addr, ',');
+ if (comma) {
+ addr_alloc = strndup(addr, comma - addr);
+ if (!addr_alloc)
+ return log_oom();
+
+ addr = addr_alloc;
+ }
+ } else if (*addr == ',') {
+ comma = addr;
+ addr = NULL;
+ } else if (*addr == '\0') {
+ comma = NULL;
+ addr = NULL;
+ } else {
log_syntax(unit, LOG_WARNING, filename, line, 0,
"Invalid IPv6 token mode in %s=, ignoring assignment: %s",
lvalue, rvalue);
return 0;
}
+ if (comma) {
+ r = sd_id128_from_string(comma + 1, &secret_key);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to parse secret key in %s=, ignoring assignment: %s",
+ lvalue, rvalue);
+ return 0;
+ }
+ if (sd_id128_is_null(secret_key)) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Secret key in %s= cannot be null, ignoring assignment: %s",
+ lvalue, rvalue);
+ return 0;
+ }
+ }
+
} else if (streq(rvalue, "eui64")) {
type = ADDRESS_GENERATION_EUI64;
- p = NULL;
+ addr = NULL;
} else {
type = ADDRESS_GENERATION_STATIC;
- p = startswith(rvalue, "static:");
- if (!p)
- p = rvalue;
+ addr = startswith(rvalue, "static:");
+ if (!addr)
+ addr = rvalue;
}
- if (p) {
- r = in_addr_from_string(AF_INET6, p, &buffer);
+ if (addr) {
+ r = in_addr_from_string(AF_INET6, addr, &buffer);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to parse IP address in %s=, ignoring assignment: %s",
@@ -371,7 +419,7 @@ int config_parse_address_generation_type(
assert_not_reached();
}
- r = ipv6_token_add(tokens, type, &buffer.in6);
+ r = ipv6_token_add(tokens, type, &buffer.in6, &secret_key);
if (r < 0)
return log_oom();
diff --git a/test/test-network/conf/ipv6-prefix-veth-token-prefixstable-without-address.network b/test/test-network/conf/ipv6-prefix-veth-token-prefixstable-without-address.network
index 7613a8258d..eda4ce1915 100644
--- a/test/test-network/conf/ipv6-prefix-veth-token-prefixstable-without-address.network
+++ b/test/test-network/conf/ipv6-prefix-veth-token-prefixstable-without-address.network
@@ -7,3 +7,4 @@ IPv6AcceptRA=true
[IPv6AcceptRA]
Token=prefixstable
+Token=prefixstable,86b123b969ba4b7eb8b3d8605123525a
diff --git a/test/test-network/conf/ipv6-prefix-veth-token-prefixstable.network b/test/test-network/conf/ipv6-prefix-veth-token-prefixstable.network
index 02056d335b..ac50700401 100644
--- a/test/test-network/conf/ipv6-prefix-veth-token-prefixstable.network
+++ b/test/test-network/conf/ipv6-prefix-veth-token-prefixstable.network
@@ -7,7 +7,12 @@ IPv6AcceptRA=true
[IPv6AcceptRA]
Token=prefixstable:2002:da8:1::
+Token=prefixstable:2002:da8:1::,86b123b969ba4b7eb8b3d8605123525a
# invalid tokens
+Token=prefixstable:2002:da8:1::,00000000000000000000000000000000
+Token=prefixstable:2002:da8:1::,
+Token=prefixstable,00000000000000000000000000000000
+Token=prefixstable,
Token=prefixstable@
Token=static
Token=static:
diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py
index a68055086c..58ca5b5446 100755
--- a/test/test-network/systemd-networkd-tests.py
+++ b/test/test-network/systemd-networkd-tests.py
@@ -3951,8 +3951,8 @@ class NetworkdRATests(unittest.TestCase, Utilities):
output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth99', env=env)
print(output)
- self.assertRegex(output, '2002:da8:1:0')
- self.assertRegex(output, '2002:da8:2:0.*78:9abc') # EUI
+ self.assertIn('2002:da8:1:0:b47e:7975:fc7a:7d6e', output)
+ self.assertIn('2002:da8:2:0:1034:56ff:fe78:9abc', output) # EUI64
def test_ipv6_token_prefixstable_without_address(self):
copy_unit_to_networkd_unit_path('25-veth.netdev', 'ipv6-prefix.network', 'ipv6-prefix-veth-token-prefixstable-without-address.network')
@@ -3961,8 +3961,8 @@ class NetworkdRATests(unittest.TestCase, Utilities):
output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth99', env=env)
print(output)
- self.assertRegex(output, '2002:da8:1:0')
- self.assertRegex(output, '2002:da8:2:0')
+ self.assertIn('2002:da8:1:0:b47e:7975:fc7a:7d6e', output)
+ self.assertIn('2002:da8:2:0:f689:561a:8eda:7443', output)
class NetworkdDHCPServerTests(unittest.TestCase, Utilities):
links = [