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 = [