From 87f9d6ea8efa55b5fc26b537588d1425287cd57f Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Sat, 8 Feb 2020 15:34:35 -0500 Subject: [PATCH 1/6] network: Improve variable name for address generation The logic which can produce an IPv6 address using SLAAC produces an address, not a prefix, so the boolean variable used to detect whether it succeeded should reflect that. --- src/network/networkd-ndisc.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index 12f8c1a1dc..cf0cd8ed5b 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -255,7 +255,7 @@ static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) { } static int ndisc_router_generate_address(Link *link, unsigned prefixlen, uint32_t lifetime_preferred, Address *address) { - bool prefix = false; + bool have_address = false; struct in6_addr addr; IPv6Token *j; Iterator i; @@ -274,18 +274,18 @@ static int ndisc_router_generate_address(Link *link, unsigned prefixlen, uint32_ return r; if (stableprivate_address_is_valid(&address->in_addr.in6)) { - prefix = true; + have_address = true; break; } } } else if (j->address_generation_type == IPV6_TOKEN_ADDRESS_GENERATION_STATIC) { memcpy(((uint8_t *)&address->in_addr.in6) + 8, ((uint8_t *) &j->prefix) + 8, 8); - prefix = true; + have_address = true; break; } - /* fallback to eui64 if prefixstable or static do not match */ - if (!prefix) { + /* fall back to EUI-64 if neither prefixstable nor static provide an address */ + if (!have_address) { /* see RFC4291 section 2.5.1 */ address->in_addr.in6.s6_addr[8] = link->mac.ether_addr_octet[0]; address->in_addr.in6.s6_addr[8] ^= 1 << 1; From 53f8cced457008fbc0f722b58a6d9ded80c20a4e Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Sat, 8 Feb 2020 15:35:47 -0500 Subject: [PATCH 2/6] network: Correct typo and naming in error message This error message will be emitted when any form of SLAAC address generation fails, not just 'prefix stable', so the message should only refer to SLAAC. --- src/network/networkd-ndisc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index cf0cd8ed5b..4a67731948 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -346,7 +346,7 @@ static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *r r = ndisc_router_generate_address(link, prefixlen, lifetime_preferred, address); if (r < 0) - return log_link_error_errno(link, r, "Falied to generate prefix stable address: %m"); + return log_link_error_errno(link, r, "Failed to generate SLAAC address: %m"); /* see RFC4862 section 5.5.3.e */ r = address_get(link, address->family, &address->in_addr, address->prefixlen, &existing_address); From 8dcce054e396801284eacb1888c010775fd509dc Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Sat, 8 Feb 2020 15:36:37 -0500 Subject: [PATCH 3/6] network: Rewrite IPv6Token documentation for new modes This patch provides new documentation for IPv6Token, reflecting the new modes (and the existing mode), and documents various caveats users should be aware of when using these modes. --- man/systemd.network.xml | 42 ++++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/man/systemd.network.xml b/man/systemd.network.xml index d61e3472c1..a88b6a447d 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -308,18 +308,38 @@ IPv6Token= - Specifies an optional address generation mechanism and an optional address prefix. If - the mechanism is present, the two parts must be separated with a colon - type:prefix. The - address generation mechanism may be either prefixstable or - static. If not specified, static is assumed. When - set to prefixstable a method for generating IPv6 Interface Identifiers to - be used with IPv6 Stateless Address Autocon figuration (SLAAC). See - RFC 7217. When IPv6 address is set, - indicates the 64-bit interface part of SLAAC IPv6 addresses for this link. + Specifies an optional address generation mode and a required IPv6 address. If + the mode is present, the two parts must be separated with a colon + mode:address. The + address generation mode may be either prefixstable or + static. If not specified, static is assumed. + + When the mode is set to static, or unspecified, the lower bits of + the supplied address are combined with the upper bits of a prefix received in a Router Advertisement + 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 + 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. - Note that the token is only ever used for SLAAC, and not for DHCPv6 addresses, even in - the case DHCP is requested by router advertisement. By default, the token is autogenerated. + Note that if multiple static IPv6Token variables are supplied, only the first + one will be used to generate addresses. + + When the mode is set to prefixstable the RFC 7217 algorithm for generating + interface identifiers will be used, but only when a prefix received in an RA message matches the supplied address. + See RFC 7217. Prefix matching will be attempted + against each prefixstable IPv6Token variable provided in the configuration; if a received + prefix does not match any of the provided addresses, then the EUI-64 algorithm will be used to form + an interface identifier for that prefix. This mode is also SLAAC, but with a potentially stable interface + identifier which does not directly map to the interface's hardware address. + + Note that the prefixstable algorithm includes both the interface's name and + MAC address in the hash used to compute the interface identifier, so if either of those are changed the resulting + interface identifier (and address) will change, even if the prefix received in the RA message has not changed. + + Note that if multiple prefixstable IPv6Token variables are supplied with addresses that + match a prefix received in an RA message, only the first one will be used to generate addresses. From 0ddad04eda2a29a8df861d8b743f3c7be0333ce8 Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Sat, 8 Feb 2020 15:40:40 -0500 Subject: [PATCH 4/6] network: Document the lack of actual DAD usage in prefixstable algorithm The RFC 7217 (prefixstable) algorithm can use Duplicate Address Detection to produce multiple candidate addresses, but the implementation here does not currently employ that mechanism. --- src/network/networkd-ndisc.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index 4a67731948..f97cd1c771 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -268,6 +268,11 @@ static int ndisc_router_generate_address(Link *link, unsigned prefixlen, uint32_ ORDERED_HASHMAP_FOREACH(j, link->network->ipv6_tokens, i) if (j->address_generation_type == IPV6_TOKEN_ADDRESS_GENERATION_PREFIXSTABLE && memcmp(&j->prefix, &addr, FAMILY_ADDRESS_SIZE(address->family)) == 0) { + /* 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 (; j->dad_counter < DAD_CONFLICTS_IDGEN_RETRIES_RFC7217; j->dad_counter++) { r = make_stableprivate_address(link, &j->prefix, prefixlen, j->dad_counter, &address->in_addr.in6); if (r < 0) From 8a98f11ed0cde548da21dd820fc3f74dc6119f4d Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Sun, 9 Feb 2020 07:18:35 -0500 Subject: [PATCH 5/6] network: Make address_hash_ops available outside of networkd-address.c In order to allow other parts of systemd-networkd to use sets/hashmaps of Address objects, the address_hash_ops structure needs to be made available to them. --- src/network/networkd-address.c | 2 +- src/network/networkd-address.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index 88bafb4d20..59775869b6 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -187,7 +187,7 @@ static int address_compare_func(const Address *a1, const Address *a2) { } } -DEFINE_PRIVATE_HASH_OPS(address_hash_ops, Address, address_hash_func, address_compare_func); +DEFINE_HASH_OPS(address_hash_ops, Address, address_hash_func, address_compare_func); bool address_equal(Address *a1, Address *a2) { if (a1 == a2) diff --git a/src/network/networkd-address.h b/src/network/networkd-address.h index 76a30a54bc..6995c5c06e 100644 --- a/src/network/networkd-address.h +++ b/src/network/networkd-address.h @@ -67,6 +67,8 @@ int configure_ipv4_duplicate_address_detection(Link *link, Address *address); DEFINE_NETWORK_SECTION_FUNCTIONS(Address, address_free); +extern const struct hash_ops address_hash_ops; + CONFIG_PARSER_PROTOTYPE(config_parse_address); CONFIG_PARSER_PROTOTYPE(config_parse_broadcast); CONFIG_PARSER_PROTOTYPE(config_parse_label); From c24c83dc67a63c88b0a537f4fa7f605b1fcbac39 Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Sun, 9 Feb 2020 07:25:59 -0500 Subject: [PATCH 6/6] network: Allow multiple IPv6Token 'static' items to generate addresses This patch allows multiple addresses using 'static' IPv6Tokens to be generated for a single network interface. --- man/systemd.network.xml | 3 - src/network/networkd-ndisc.c | 143 ++++++++++++------ ...-prefix-veth-token-static-multiple.network | 7 + test/test-network/systemd-networkd-tests.py | 11 ++ 4 files changed, 112 insertions(+), 52 deletions(-) create mode 100644 test/test-network/conf/ipv6-prefix-veth-token-static-multiple.network diff --git a/man/systemd.network.xml b/man/systemd.network.xml index a88b6a447d..3f4c18f9cd 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -322,9 +322,6 @@ 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. - - Note that if multiple static IPv6Token variables are supplied, only the first - one will be used to generate addresses. When the mode is set to prefixstable the RFC 7217 algorithm for generating interface identifiers will be used, but only when a prefix received in an RA message matches the supplied address. diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index f97cd1c771..c45fec5430 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -254,18 +254,32 @@ static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) { return 0; } -static int ndisc_router_generate_address(Link *link, unsigned prefixlen, uint32_t lifetime_preferred, Address *address) { - bool have_address = false; +static int ndisc_router_generate_addresses(Link *link, unsigned prefixlen, uint32_t lifetime_preferred, Address *address, Set **ret) { + _cleanup_set_free_free_ Set *addresses = NULL; struct in6_addr addr; IPv6Token *j; Iterator i; int r; - assert(address); assert(link); + assert(address); + assert(ret); + + addresses = set_new(&address_hash_ops); + if (!addresses) + return log_oom(); addr = address->in_addr.in6; - ORDERED_HASHMAP_FOREACH(j, link->network->ipv6_tokens, i) + ORDERED_HASHMAP_FOREACH(j, link->network->ipv6_tokens, i) { + bool have_address = false; + _cleanup_(address_freep) Address *new_address = NULL; + + r = address_new(&new_address); + if (r < 0) + return log_oom(); + + *new_address = *address; + if (j->address_generation_type == IPV6_TOKEN_ADDRESS_GENERATION_PREFIXSTABLE && memcmp(&j->prefix, &addr, FAMILY_ADDRESS_SIZE(address->family)) == 0) { /* While this loop uses dad_counter and a retry limit as specified in RFC 7217, the loop @@ -274,47 +288,75 @@ static int ndisc_router_generate_address(Link *link, unsigned prefixlen, uint32_ may exit with an address which ends up being unusable due to duplication on the link. */ for (; j->dad_counter < DAD_CONFLICTS_IDGEN_RETRIES_RFC7217; j->dad_counter++) { - r = make_stableprivate_address(link, &j->prefix, prefixlen, j->dad_counter, &address->in_addr.in6); + r = make_stableprivate_address(link, &j->prefix, prefixlen, j->dad_counter, &new_address->in_addr.in6); if (r < 0) - return r; + break; - if (stableprivate_address_is_valid(&address->in_addr.in6)) { + if (stableprivate_address_is_valid(&new_address->in_addr.in6)) { have_address = true; break; } } } else if (j->address_generation_type == IPV6_TOKEN_ADDRESS_GENERATION_STATIC) { - memcpy(((uint8_t *)&address->in_addr.in6) + 8, ((uint8_t *) &j->prefix) + 8, 8); + memcpy(((uint8_t *)&new_address->in_addr.in6) + 8, ((uint8_t *) &j->prefix) + 8, 8); have_address = true; - break; } - /* fall back to EUI-64 if neither prefixstable nor static provide an address */ - if (!have_address) { - /* see RFC4291 section 2.5.1 */ - address->in_addr.in6.s6_addr[8] = link->mac.ether_addr_octet[0]; - address->in_addr.in6.s6_addr[8] ^= 1 << 1; - address->in_addr.in6.s6_addr[9] = link->mac.ether_addr_octet[1]; - address->in_addr.in6.s6_addr[10] = link->mac.ether_addr_octet[2]; - address->in_addr.in6.s6_addr[11] = 0xff; - address->in_addr.in6.s6_addr[12] = 0xfe; - address->in_addr.in6.s6_addr[13] = link->mac.ether_addr_octet[3]; - address->in_addr.in6.s6_addr[14] = link->mac.ether_addr_octet[4]; - address->in_addr.in6.s6_addr[15] = link->mac.ether_addr_octet[5]; + if (have_address) { + new_address->prefixlen = prefixlen; + new_address->flags = IFA_F_NOPREFIXROUTE|IFA_F_MANAGETEMPADDR; + new_address->cinfo.ifa_prefered = lifetime_preferred; + + r = set_put(addresses, new_address); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to store address: %m"); + TAKE_PTR(new_address); + } } - address->prefixlen = prefixlen; - address->flags = IFA_F_NOPREFIXROUTE|IFA_F_MANAGETEMPADDR; - address->cinfo.ifa_prefered = lifetime_preferred; + /* fall back to EUI-64 if no tokens provided addresses */ + if (set_isempty(addresses)) { + _cleanup_(address_freep) Address *new_address = NULL; + + r = address_new(&new_address); + if (r < 0) + return log_oom(); + + *new_address = *address; + + /* see RFC4291 section 2.5.1 */ + new_address->in_addr.in6.s6_addr[8] = link->mac.ether_addr_octet[0]; + new_address->in_addr.in6.s6_addr[8] ^= 1 << 1; + new_address->in_addr.in6.s6_addr[9] = link->mac.ether_addr_octet[1]; + new_address->in_addr.in6.s6_addr[10] = link->mac.ether_addr_octet[2]; + new_address->in_addr.in6.s6_addr[11] = 0xff; + new_address->in_addr.in6.s6_addr[12] = 0xfe; + new_address->in_addr.in6.s6_addr[13] = link->mac.ether_addr_octet[3]; + new_address->in_addr.in6.s6_addr[14] = link->mac.ether_addr_octet[4]; + new_address->in_addr.in6.s6_addr[15] = link->mac.ether_addr_octet[5]; + new_address->prefixlen = prefixlen; + new_address->flags = IFA_F_NOPREFIXROUTE|IFA_F_MANAGETEMPADDR; + new_address->cinfo.ifa_prefered = lifetime_preferred; + + r = set_put(addresses, new_address); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to store address: %m"); + TAKE_PTR(new_address); + } + + *ret = TAKE_PTR(addresses); return 0; } + static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *rt) { uint32_t lifetime_valid, lifetime_preferred, lifetime_remaining; + _cleanup_set_free_free_ Set *addresses = NULL; _cleanup_(address_freep) Address *address = NULL; - Address *existing_address; unsigned prefixlen; usec_t time_now; + Address *existing_address, *a; + Iterator i; int r; assert(link); @@ -349,36 +391,39 @@ static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *r if (r < 0) return log_link_error_errno(link, r, "Failed to get prefix address: %m"); - r = ndisc_router_generate_address(link, prefixlen, lifetime_preferred, address); + r = ndisc_router_generate_addresses(link, prefixlen, lifetime_preferred, address, &addresses); if (r < 0) - return log_link_error_errno(link, r, "Failed to generate SLAAC address: %m"); + return log_link_error_errno(link, r, "Failed to generate SLAAC addresses: %m"); - /* see RFC4862 section 5.5.3.e */ - r = address_get(link, address->family, &address->in_addr, address->prefixlen, &existing_address); - if (r > 0) { - lifetime_remaining = existing_address->cinfo.tstamp / 100 + existing_address->cinfo.ifa_valid - time_now / USEC_PER_SEC; - if (lifetime_valid > NDISC_PREFIX_LFT_MIN || lifetime_valid > lifetime_remaining) - address->cinfo.ifa_valid = lifetime_valid; - else if (lifetime_remaining <= NDISC_PREFIX_LFT_MIN) - address->cinfo.ifa_valid = lifetime_remaining; + SET_FOREACH(a, addresses, i) { + /* see RFC4862 section 5.5.3.e */ + r = address_get(link, a->family, &a->in_addr, a->prefixlen, &existing_address); + if (r > 0) { + lifetime_remaining = existing_address->cinfo.tstamp / 100 + existing_address->cinfo.ifa_valid - time_now / USEC_PER_SEC; + if (lifetime_valid > NDISC_PREFIX_LFT_MIN || lifetime_valid > lifetime_remaining) + a->cinfo.ifa_valid = lifetime_valid; + else if (lifetime_remaining <= NDISC_PREFIX_LFT_MIN) + a->cinfo.ifa_valid = lifetime_remaining; + else + a->cinfo.ifa_valid = NDISC_PREFIX_LFT_MIN; + } else if (lifetime_valid > 0) + a->cinfo.ifa_valid = lifetime_valid; else - address->cinfo.ifa_valid = NDISC_PREFIX_LFT_MIN; - } else if (lifetime_valid > 0) - address->cinfo.ifa_valid = lifetime_valid; - else - return 0; /* see RFC4862 section 5.5.3.d */ + return 0; /* see RFC4862 section 5.5.3.d */ - if (address->cinfo.ifa_valid == 0) - return 0; + if (a->cinfo.ifa_valid == 0) + continue; - r = address_configure(address, link, ndisc_netlink_address_message_handler, true); - if (r < 0) { - log_link_warning_errno(link, r, "Could not set SLAAC address: %m"); - link_enter_failed(link); - return r; + r = address_configure(a, link, ndisc_netlink_address_message_handler, true); + if (r < 0) { + log_link_warning_errno(link, r, "Could not set SLAAC address: %m"); + link_enter_failed(link); + return r; + } + + if (r > 0) + link->ndisc_messages++; } - if (r > 0) - link->ndisc_messages++; return 0; } diff --git a/test/test-network/conf/ipv6-prefix-veth-token-static-multiple.network b/test/test-network/conf/ipv6-prefix-veth-token-static-multiple.network new file mode 100644 index 0000000000..49c6f31e77 --- /dev/null +++ b/test/test-network/conf/ipv6-prefix-veth-token-static-multiple.network @@ -0,0 +1,7 @@ +[Match] +Name=veth99 + +[Network] +IPv6AcceptRA=true +IPv6Token=::1a:2b:3c:4d +IPv6Token=::fa:de:ca:fe diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index c1a84ca9ec..88f9b6b827 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -2685,6 +2685,7 @@ class NetworkdRATests(unittest.TestCase, Utilities): 'ipv6-prefix-veth.network', 'ipv6-prefix-veth-token-static.network', 'ipv6-prefix-veth-token-static-explicit.network', + 'ipv6-prefix-veth-token-static-multiple.network', 'ipv6-prefix-veth-token-prefixstable.network'] def setUp(self): @@ -2728,6 +2729,16 @@ class NetworkdRATests(unittest.TestCase, Utilities): print(output) self.assertRegex(output, '2002:da8:1:0:1a:2b:3c:4d') + def test_ipv6_token_static_multiple(self): + copy_unit_to_networkd_unit_path('25-veth.netdev', 'ipv6-prefix.network', 'ipv6-prefix-veth-token-static-multiple.network') + start_networkd() + self.wait_online(['veth99:routable', 'veth-peer:degraded']) + + output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth99', env=env) + print(output) + self.assertRegex(output, '2002:da8:1:0:1a:2b:3c:4d') + self.assertRegex(output, '2002:da8:1:0:fa:de:ca:fe') + def test_ipv6_token_prefixstable(self): copy_unit_to_networkd_unit_path('25-veth.netdev', 'ipv6-prefix.network', 'ipv6-prefix-veth-token-prefixstable.network') start_networkd()