From b03565bc179721458e43460e0f2446fb22f8849f Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 9 Apr 2024 11:05:24 +0900 Subject: [PATCH 1/8] network/dhcp-pd: split out dhcp_pd_request_address_one() No functional change. Preparation for later commit. --- src/network/networkd-dhcp-prefix-delegation.c | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/network/networkd-dhcp-prefix-delegation.c b/src/network/networkd-dhcp-prefix-delegation.c index 61295d9ce64..3ae62b51cab 100644 --- a/src/network/networkd-dhcp-prefix-delegation.c +++ b/src/network/networkd-dhcp-prefix-delegation.c @@ -353,6 +353,22 @@ static void log_dhcp_pd_address(Link *link, const Address *address) { FORMAT_LIFETIME(address->lifetime_preferred_usec)); } +static int dhcp_pd_request_address_one(Address *address, Link *link) { + Address *existing; + + assert(address); + assert(link); + + log_dhcp_pd_address(link, address); + + if (address_get(link, address, &existing) < 0) + link->dhcp_pd_configured = false; + else + address_unmark(existing); + + return link_request_address(link, address, &link->dhcp_pd_messages, dhcp_pd_address_handler, NULL); +} + static int dhcp_pd_request_address( Link *link, const struct in6_addr *prefix, @@ -376,7 +392,6 @@ static int dhcp_pd_request_address( SET_FOREACH(a, addresses) { _cleanup_(address_unrefp) Address *address = NULL; - Address *existing; r = address_new(&address); if (r < 0) @@ -391,19 +406,11 @@ static int dhcp_pd_request_address( SET_FLAG(address->flags, IFA_F_MANAGETEMPADDR, link->network->dhcp_pd_manage_temporary_address); address->route_metric = link->network->dhcp_pd_route_metric; - log_dhcp_pd_address(link, address); - r = free_and_strdup_warn(&address->netlabel, link->network->dhcp_pd_netlabel); if (r < 0) return r; - if (address_get(link, address, &existing) < 0) - link->dhcp_pd_configured = false; - else - address_unmark(existing); - - r = link_request_address(link, address, &link->dhcp_pd_messages, - dhcp_pd_address_handler, NULL); + r = dhcp_pd_request_address_one(address, link); if (r < 0) return log_link_error_errno(link, r, "Failed to request DHCP delegated prefix address: %m"); } From 43d184682d640e438ad140b0743e2fd258455b12 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 9 Apr 2024 12:02:20 +0900 Subject: [PATCH 2/8] network/ndisc: allow to call ndisc_request_address() without sd_ndisc_router object Preparation for later commits. --- src/network/networkd-ndisc.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index e1853bb31cc..487a1e63098 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -328,20 +328,19 @@ static int ndisc_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Reques } static int ndisc_request_address(Address *address, Link *link, sd_ndisc_router *rt) { - struct in6_addr router; bool is_new; int r; assert(address); assert(link); - assert(rt); - r = sd_ndisc_router_get_sender_address(rt, &router); - if (r < 0) - return r; + if (rt) { + r = sd_ndisc_router_get_sender_address(rt, &address->provider.in6); + if (r < 0) + return r; - address->source = NETWORK_CONFIG_SOURCE_NDISC; - address->provider.in6 = router; + address->source = NETWORK_CONFIG_SOURCE_NDISC; + } r = free_and_strdup_warn(&address->netlabel, link->network->ndisc_netlabel); if (r < 0) From ffafb56125cf0cb7d5a50989d1ea8ae7f82390a0 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 9 Apr 2024 11:13:33 +0900 Subject: [PATCH 3/8] network/address-generation: expose IPv6Token and introduce new/ref/unref functions for the type No functional change. Preparation for later commits. --- src/network/networkd-address-generation.c | 119 +++++++++++++--------- src/network/networkd-address-generation.h | 4 + 2 files changed, 73 insertions(+), 50 deletions(-) diff --git a/src/network/networkd-address-generation.c b/src/network/networkd-address-generation.c index 9a5e2c21739..293dc751574 100644 --- a/src/network/networkd-address-generation.c +++ b/src/network/networkd-address-generation.c @@ -34,11 +34,78 @@ typedef enum AddressGenerationType { _ADDRESS_GENERATION_TYPE_INVALID = -EINVAL, } AddressGenerationType; -typedef struct IPv6Token { +struct IPv6Token { + unsigned n_ref; AddressGenerationType type; struct in6_addr address; sd_id128_t secret_key; -} IPv6Token; +}; + +DEFINE_TRIVIAL_REF_UNREF_FUNC(IPv6Token, ipv6_token, mfree); +DEFINE_TRIVIAL_CLEANUP_FUNC(IPv6Token*, ipv6_token_unref); + +static void ipv6_token_hash_func(const IPv6Token *p, struct siphash *state) { + siphash24_compress_typesafe(p->type, state); + siphash24_compress_typesafe(p->address, state); + id128_hash_func(&p->secret_key, state); +} + +static int ipv6_token_compare_func(const IPv6Token *a, const IPv6Token *b) { + int r; + + r = CMP(a->type, b->type); + if (r != 0) + return r; + + 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( + ipv6_token_hash_ops, + IPv6Token, + ipv6_token_hash_func, + ipv6_token_compare_func, + ipv6_token_unref); + +static int ipv6_token_new(AddressGenerationType type, const struct in6_addr *addr, const sd_id128_t *secret_key, IPv6Token **ret) { + IPv6Token *p; + + assert(type >= 0 && type < _ADDRESS_GENERATION_TYPE_MAX); + assert(addr); + assert(secret_key); + assert(ret); + + p = new(IPv6Token, 1); + if (!p) + return -ENOMEM; + + *p = (IPv6Token) { + .n_ref = 1, + .type = type, + .address = *addr, + .secret_key = *secret_key, + }; + + *ret = p; + return 0; +} + +static int ipv6_token_add(Set **tokens, AddressGenerationType type, const struct in6_addr *addr, const sd_id128_t *secret_key) { + IPv6Token *p; + int r; + + assert(tokens); + + r = ipv6_token_new(type, addr, secret_key, &p); + if (r < 0) + return r; + + return set_ensure_consume(tokens, &ipv6_token_hash_ops, p); +} static int generate_eui64_address(const Link *link, const struct in6_addr *prefix, struct in6_addr *ret) { assert(link); @@ -268,54 +335,6 @@ int radv_generate_addresses(Link *link, Set *tokens, const struct in6_addr *pref return generate_addresses(link, tokens, &RADV_APP_ID, prefix, prefixlen, ret); } -static void ipv6_token_hash_func(const IPv6Token *p, struct siphash *state) { - siphash24_compress_typesafe(p->type, state); - siphash24_compress_typesafe(p->address, state); - id128_hash_func(&p->secret_key, state); -} - -static int ipv6_token_compare_func(const IPv6Token *a, const IPv6Token *b) { - int r; - - r = CMP(a->type, b->type); - if (r != 0) - return r; - - 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( - ipv6_token_hash_ops, - IPv6Token, - ipv6_token_hash_func, - ipv6_token_compare_func, - free); - -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) - return -ENOMEM; - - *p = (IPv6Token) { - .type = type, - .address = *addr, - .secret_key = *secret_key, - }; - - return set_ensure_consume(tokens, &ipv6_token_hash_ops, p); -} - int config_parse_address_generation_type( const char *unit, const char *filename, diff --git a/src/network/networkd-address-generation.h b/src/network/networkd-address-generation.h index 901b2ec4bf3..00f5a55eda7 100644 --- a/src/network/networkd-address-generation.h +++ b/src/network/networkd-address-generation.h @@ -5,8 +5,12 @@ #include "in-addr-util.h" #include "set.h" +typedef struct IPv6Token IPv6Token; typedef struct Link Link; +IPv6Token* ipv6_token_ref(IPv6Token *token); +IPv6Token* ipv6_token_unref(IPv6Token *token); + int dhcp_pd_generate_addresses(Link *link, const struct in6_addr *prefix, Set **ret); int ndisc_generate_addresses(Link *link, const struct in6_addr *prefix, uint8_t prefixlen, Set **ret); int radv_generate_addresses(Link *link, Set *tokens, const struct in6_addr *prefix, uint8_t prefixlen, Set **ret); From e39bb291000e3d110f64cf97919d9463efd40f9d Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 9 Apr 2024 11:22:00 +0900 Subject: [PATCH 4/8] network/address: make Address object optionally take reference to IPv6Token Currently, the element is unused. Preparation for later commits. --- src/network/networkd-address.c | 6 ++++++ src/network/networkd-address.h | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index 41d75bdf841..df9d0c03886 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -260,6 +260,7 @@ static Address* address_free(Address *address) { config_section_free(address->section); free(address->label); free(address->netlabel); + ipv6_token_unref(address->token); nft_set_context_clear(&address->nft_set_context); return mfree(address); } @@ -608,6 +609,7 @@ int address_dup(const Address *src, Address **ret) { dest->section = NULL; dest->link = NULL; dest->label = NULL; + dest->token = ipv6_token_ref(src->token); dest->netlabel = NULL; dest->nft_set_context.sets = NULL; dest->nft_set_context.n_sets = 0; @@ -1886,6 +1888,10 @@ int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message, (void) nft_set_context_dup(&a->nft_set_context, &address->nft_set_context); address->requested_as_null = a->requested_as_null; address->callback = a->callback; + + ipv6_token_ref(a->token); + ipv6_token_unref(address->token); + address->token = a->token; } /* Then, update miscellaneous info. */ diff --git a/src/network/networkd-address.h b/src/network/networkd-address.h index ac6179a9248..e09551ecda4 100644 --- a/src/network/networkd-address.h +++ b/src/network/networkd-address.h @@ -10,6 +10,7 @@ #include "hash-funcs.h" #include "in-addr-util.h" #include "network-util.h" +#include "networkd-address-generation.h" #include "networkd-link.h" #include "networkd-util.h" #include "time-util.h" @@ -63,6 +64,9 @@ struct Address { * To control DAD for IPv6 dynamic addresses, set IFA_F_NODAD to flags. */ AddressFamily duplicate_address_detection; + /* Used by address generator. */ + IPv6Token *token; + /* Called when address become ready */ address_ready_callback_t callback; From e700e482576ad9982da3bab62f613d1b7b52516f Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 9 Apr 2024 11:26:16 +0900 Subject: [PATCH 5/8] network/address-generation: also provide used tokens when generating address Then, assign the token to the corresponding Address object. --- src/network/networkd-address-generation.c | 90 +++++++++++++------ src/network/networkd-address-generation.h | 8 +- src/network/networkd-dhcp-prefix-delegation.c | 10 ++- src/network/networkd-ndisc.c | 11 ++- src/network/networkd-radv.c | 11 +-- 5 files changed, 88 insertions(+), 42 deletions(-) diff --git a/src/network/networkd-address-generation.c b/src/network/networkd-address-generation.c index 293dc751574..9957ce86efa 100644 --- a/src/network/networkd-address-generation.c +++ b/src/network/networkd-address-generation.c @@ -71,6 +71,15 @@ DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( ipv6_token_compare_func, ipv6_token_unref); +DEFINE_PRIVATE_HASH_OPS_FULL( + ipv6_token_by_addr_hash_ops, + struct in6_addr, + in6_addr_hash_func, + in6_addr_compare_func, + free, + IPv6Token, + ipv6_token_unref); + static int ipv6_token_new(AddressGenerationType type, const struct in6_addr *addr, const sd_id128_t *secret_key, IPv6Token **ret) { IPv6Token *p; @@ -107,6 +116,44 @@ static int ipv6_token_add(Set **tokens, AddressGenerationType type, const struct return set_ensure_consume(tokens, &ipv6_token_hash_ops, p); } +static int ipv6_token_put_by_addr(Hashmap **tokens_by_address, const struct in6_addr *addr, IPv6Token *token) { + _cleanup_free_ struct in6_addr *copy = NULL; + int r; + + assert(tokens_by_address); + assert(addr); + assert(token); + + copy = newdup(struct in6_addr, addr, 1); + if (!copy) + return -ENOMEM; + + r = hashmap_ensure_put(tokens_by_address, &ipv6_token_by_addr_hash_ops, copy, token); + if (r == -EEXIST) + return 0; + if (r < 0) + return r; + + TAKE_PTR(copy); + ipv6_token_ref(token); + return 1; +} + +static int ipv6_token_type_put_by_addr(Hashmap **tokens_by_addr, const struct in6_addr *addr, AddressGenerationType type) { + _cleanup_(ipv6_token_unrefp) IPv6Token *token = NULL; + int r; + + assert(tokens_by_addr); + assert(addr); + + r = ipv6_token_new(type, &(struct in6_addr) {}, &SD_ID128_NULL, &token); + if (r < 0) + return r; + + return ipv6_token_put_by_addr(tokens_by_addr, addr, token); +} + + static int generate_eui64_address(const Link *link, const struct in6_addr *prefix, struct in6_addr *ret) { assert(link); assert(prefix); @@ -247,10 +294,10 @@ static int generate_addresses( const sd_id128_t *app_id, const struct in6_addr *prefix, uint8_t prefixlen, - Set **ret) { + Hashmap **ret) { - _cleanup_set_free_ Set *addresses = NULL; - struct in6_addr masked; + _cleanup_hashmap_free_ Hashmap *tokens_by_address = NULL; + struct in6_addr masked, addr; IPv6Token *j; int r; @@ -264,8 +311,6 @@ static int generate_addresses( in6_addr_mask(&masked, prefixlen); SET_FOREACH(j, tokens) { - struct in6_addr addr, *copy; - switch (j->type) { case ADDRESS_GENERATION_EUI64: if (generate_eui64_address(link, &masked, &addr) < 0) @@ -290,48 +335,43 @@ static int generate_addresses( assert_not_reached(); } - copy = newdup(struct in6_addr, &addr, 1); - if (!copy) - return -ENOMEM; - - r = set_ensure_consume(&addresses, &in6_addr_hash_ops_free, copy); + r = ipv6_token_put_by_addr(&tokens_by_address, &addr, j); if (r < 0) return r; } /* fall back to EUI-64 if no token is provided */ - if (set_isempty(addresses)) { - _cleanup_free_ struct in6_addr *addr = NULL; + if (hashmap_isempty(tokens_by_address)) { + AddressGenerationType type; - addr = new(struct in6_addr, 1); - if (!addr) - return -ENOMEM; - - if (IN_SET(link->iftype, ARPHRD_ETHER, ARPHRD_INFINIBAND)) - r = generate_eui64_address(link, &masked, addr); - else - r = generate_stable_private_address(link, app_id, &SD_ID128_NULL, &masked, addr); + if (IN_SET(link->iftype, ARPHRD_ETHER, ARPHRD_INFINIBAND)) { + type = ADDRESS_GENERATION_EUI64; + r = generate_eui64_address(link, &masked, &addr); + } else { + type = ADDRESS_GENERATION_PREFIXSTABLE; + r = generate_stable_private_address(link, app_id, &SD_ID128_NULL, &masked, &addr); + } if (r < 0) return r; - r = set_ensure_consume(&addresses, &in6_addr_hash_ops_free, TAKE_PTR(addr)); + r = ipv6_token_type_put_by_addr(&tokens_by_address, &addr, type); if (r < 0) return r; } - *ret = TAKE_PTR(addresses); + *ret = TAKE_PTR(tokens_by_address); return 0; } -int dhcp_pd_generate_addresses(Link *link, const struct in6_addr *prefix, Set **ret) { +int dhcp_pd_generate_addresses(Link *link, const struct in6_addr *prefix, Hashmap **ret) { return generate_addresses(link, link->network->dhcp_pd_tokens, &DHCP_PD_APP_ID, prefix, 64, ret); } -int ndisc_generate_addresses(Link *link, const struct in6_addr *prefix, uint8_t prefixlen, Set **ret) { +int ndisc_generate_addresses(Link *link, const struct in6_addr *prefix, uint8_t prefixlen, Hashmap **ret) { return generate_addresses(link, link->network->ndisc_tokens, &NDISC_APP_ID, prefix, prefixlen, ret); } -int radv_generate_addresses(Link *link, Set *tokens, const struct in6_addr *prefix, uint8_t prefixlen, Set **ret) { +int radv_generate_addresses(Link *link, Set *tokens, const struct in6_addr *prefix, uint8_t prefixlen, Hashmap **ret) { return generate_addresses(link, tokens, &RADV_APP_ID, prefix, prefixlen, ret); } diff --git a/src/network/networkd-address-generation.h b/src/network/networkd-address-generation.h index 00f5a55eda7..68828b44ca0 100644 --- a/src/network/networkd-address-generation.h +++ b/src/network/networkd-address-generation.h @@ -2,8 +2,8 @@ #pragma once #include "conf-parser.h" +#include "hashmap.h" #include "in-addr-util.h" -#include "set.h" typedef struct IPv6Token IPv6Token; typedef struct Link Link; @@ -11,8 +11,8 @@ typedef struct Link Link; IPv6Token* ipv6_token_ref(IPv6Token *token); IPv6Token* ipv6_token_unref(IPv6Token *token); -int dhcp_pd_generate_addresses(Link *link, const struct in6_addr *prefix, Set **ret); -int ndisc_generate_addresses(Link *link, const struct in6_addr *prefix, uint8_t prefixlen, Set **ret); -int radv_generate_addresses(Link *link, Set *tokens, const struct in6_addr *prefix, uint8_t prefixlen, Set **ret); +int dhcp_pd_generate_addresses(Link *link, const struct in6_addr *prefix, Hashmap **ret); +int ndisc_generate_addresses(Link *link, const struct in6_addr *prefix, uint8_t prefixlen, Hashmap **ret); +int radv_generate_addresses(Link *link, Set *tokens, const struct in6_addr *prefix, uint8_t prefixlen, Hashmap **ret); CONFIG_PARSER_PROTOTYPE(config_parse_address_generation_type); diff --git a/src/network/networkd-dhcp-prefix-delegation.c b/src/network/networkd-dhcp-prefix-delegation.c index 3ae62b51cab..6e83163b047 100644 --- a/src/network/networkd-dhcp-prefix-delegation.c +++ b/src/network/networkd-dhcp-prefix-delegation.c @@ -375,8 +375,6 @@ static int dhcp_pd_request_address( usec_t lifetime_preferred_usec, usec_t lifetime_valid_usec) { - _cleanup_set_free_ Set *addresses = NULL; - struct in6_addr *a; int r; assert(link); @@ -386,11 +384,14 @@ static int dhcp_pd_request_address( if (!link->network->dhcp_pd_assign) return 0; - r = dhcp_pd_generate_addresses(link, prefix, &addresses); + _cleanup_hashmap_free_ Hashmap *tokens_by_address = NULL; + r = dhcp_pd_generate_addresses(link, prefix, &tokens_by_address); if (r < 0) return log_link_warning_errno(link, r, "Failed to generate addresses for acquired DHCP delegated prefix: %m"); - SET_FOREACH(a, addresses) { + IPv6Token *token; + struct in6_addr *a; + HASHMAP_FOREACH_KEY(token, a, tokens_by_address) { _cleanup_(address_unrefp) Address *address = NULL; r = address_new(&address); @@ -405,6 +406,7 @@ static int dhcp_pd_request_address( address->lifetime_valid_usec = lifetime_valid_usec; SET_FLAG(address->flags, IFA_F_MANAGETEMPADDR, link->network->dhcp_pd_manage_temporary_address); address->route_metric = link->network->dhcp_pd_route_metric; + address->token = ipv6_token_ref(token); r = free_and_strdup_warn(&address->netlabel, link->network->dhcp_pd_netlabel); if (r < 0) diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index 487a1e63098..e160014c5ab 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -1028,8 +1028,7 @@ static int ndisc_router_process_hop_limit(Link *link, sd_ndisc_router *rt) { static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *rt) { usec_t lifetime_valid_usec, lifetime_preferred_usec; - _cleanup_set_free_ Set *addresses = NULL; - struct in6_addr prefix, *a; + struct in6_addr prefix; uint8_t prefixlen; int r; @@ -1067,11 +1066,14 @@ static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *r if (lifetime_preferred_usec > lifetime_valid_usec) return 0; - r = ndisc_generate_addresses(link, &prefix, prefixlen, &addresses); + _cleanup_hashmap_free_ Hashmap *tokens_by_address = NULL; + r = ndisc_generate_addresses(link, &prefix, prefixlen, &tokens_by_address); if (r < 0) return log_link_warning_errno(link, r, "Failed to generate SLAAC addresses: %m"); - SET_FOREACH(a, addresses) { + IPv6Token *token; + struct in6_addr *a; + HASHMAP_FOREACH_KEY(token, a, tokens_by_address) { _cleanup_(address_unrefp) Address *address = NULL; r = address_new(&address); @@ -1084,6 +1086,7 @@ static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *r address->flags = IFA_F_NOPREFIXROUTE|IFA_F_MANAGETEMPADDR; address->lifetime_valid_usec = lifetime_valid_usec; address->lifetime_preferred_usec = lifetime_preferred_usec; + address->token = ipv6_token_ref(token); /* draft-ietf-6man-slaac-renum-07 section 4.2 * https://datatracker.ietf.org/doc/html/draft-ietf-6man-slaac-renum-07#section-4.2 diff --git a/src/network/networkd-radv.c b/src/network/networkd-radv.c index 82c62dad815..279ea8d043b 100644 --- a/src/network/networkd-radv.c +++ b/src/network/networkd-radv.c @@ -243,9 +243,6 @@ int link_request_radv_addresses(Link *link) { return 0; HASHMAP_FOREACH(p, link->network->prefixes_by_section) { - _cleanup_set_free_ Set *addresses = NULL; - struct in6_addr *a; - if (!p->assign) continue; @@ -253,11 +250,14 @@ int link_request_radv_addresses(Link *link) { if (p->prefixlen > 64) continue; - r = radv_generate_addresses(link, p->tokens, &p->prefix, p->prefixlen, &addresses); + _cleanup_hashmap_free_ Hashmap *tokens_by_address = NULL; + r = radv_generate_addresses(link, p->tokens, &p->prefix, p->prefixlen, &tokens_by_address); if (r < 0) return r; - SET_FOREACH(a, addresses) { + IPv6Token *token; + struct in6_addr *a; + HASHMAP_FOREACH_KEY(token, a, tokens_by_address) { _cleanup_(address_unrefp) Address *address = NULL; r = address_new(&address); @@ -269,6 +269,7 @@ int link_request_radv_addresses(Link *link) { address->in_addr.in6 = *a; address->prefixlen = p->prefixlen; address->route_metric = p->route_metric; + address->token = ipv6_token_ref(token); r = link_request_static_address(link, address); if (r < 0) From 0ac03e004c2430153c0d70c171aed43ee3166979 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 9 Apr 2024 11:16:33 +0900 Subject: [PATCH 6/8] network/address-generation: make generate_stable_private_address() optionally take the previously generated address Currently, the argument is not used yet. The new argument will be used later for regenerating prefix stable address on conflict. --- src/network/networkd-address-generation.c | 29 +++++++++++++++++------ 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/network/networkd-address-generation.c b/src/network/networkd-address-generation.c index 9957ce86efa..d28094d53cc 100644 --- a/src/network/networkd-address-generation.c +++ b/src/network/networkd-address-generation.c @@ -248,10 +248,12 @@ static int generate_stable_private_address( const sd_id128_t *app_id, const sd_id128_t *secret_key, const struct in6_addr *prefix, + const struct in6_addr *previous, struct in6_addr *ret) { sd_id128_t secret_machine_key; struct in6_addr addr; + bool found = false; uint8_t i; int r; @@ -276,16 +278,29 @@ static int generate_stable_private_address( for (i = 0; i < DAD_CONFLICTS_IDGEN_RETRIES_RFC7217; i++) { generate_stable_private_address_one(link, secret_key, prefix, i, &addr); - if (stable_private_address_is_valid(&addr)) - break; + if (!stable_private_address_is_valid(&addr)) + continue; + + /* When 'previous' is non-NULL, then this is called after DAD in the kernel triggered. + * Let's increment the counter and provide the next address. */ + if (previous && !found) { + found = in6_addr_equal(previous, &addr); + continue; + } + + break; } - if (i >= DAD_CONFLICTS_IDGEN_RETRIES_RFC7217) + if (i >= DAD_CONFLICTS_IDGEN_RETRIES_RFC7217) { /* propagate recognizable errors. */ - return log_link_debug_errno(link, SYNTHETIC_ERRNO(ENOANO), + if (previous && !found) + return -EADDRNOTAVAIL; + + return log_link_debug_errno(link, SYNTHETIC_ERRNO(EADDRINUSE), "Failed to generate stable private address."); + } *ret = addr; - return 0; + return 1; } static int generate_addresses( @@ -326,7 +341,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, &j->secret_key, &masked, &addr) < 0) + if (generate_stable_private_address(link, app_id, &j->secret_key, &masked, /* previous = */ NULL, &addr) < 0) continue; break; @@ -349,7 +364,7 @@ static int generate_addresses( r = generate_eui64_address(link, &masked, &addr); } else { type = ADDRESS_GENERATION_PREFIXSTABLE; - r = generate_stable_private_address(link, app_id, &SD_ID128_NULL, &masked, &addr); + r = generate_stable_private_address(link, app_id, &SD_ID128_NULL, &masked, /* previous = */ NULL, &addr); } if (r < 0) return r; From e14679ff76ad4410d5d0a611a15a3d48c9f2e0eb Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 9 Apr 2024 11:27:41 +0900 Subject: [PATCH 7/8] network/address-generation: regenerate IPv6 prefix stable address on conflict If a generated address with IPv6Token=prefixstable conflicts with the one on another node or interface, let's generate another address and try to assign it. This improves support of RFC 7217. Fixes #31605. --- src/network/networkd-address-generation.c | 33 ++++++++++ src/network/networkd-address-generation.h | 3 + src/network/networkd-address.c | 61 +++++++++++++++++-- src/network/networkd-dhcp-prefix-delegation.c | 22 +++++++ src/network/networkd-dhcp-prefix-delegation.h | 2 + src/network/networkd-ndisc.c | 22 +++++++ src/network/networkd-ndisc.h | 2 + src/network/networkd-radv.c | 23 +++++++ src/network/networkd-radv.h | 1 + 9 files changed, 165 insertions(+), 4 deletions(-) diff --git a/src/network/networkd-address-generation.c b/src/network/networkd-address-generation.c index d28094d53cc..816cdf713ac 100644 --- a/src/network/networkd-address-generation.c +++ b/src/network/networkd-address-generation.c @@ -390,6 +390,39 @@ int radv_generate_addresses(Link *link, Set *tokens, const struct in6_addr *pref return generate_addresses(link, tokens, &RADV_APP_ID, prefix, prefixlen, ret); } +int regenerate_address(Address *address, Link *link) { + struct in6_addr masked; + sd_id128_t app_id; + + assert(link); + assert(address); + assert(address->family == AF_INET6); + assert(!address->link && !address->network); + + if (!address->token || + address->token->type != ADDRESS_GENERATION_PREFIXSTABLE) + return 0; + + switch (address->source) { + case NETWORK_CONFIG_SOURCE_STATIC: + app_id = RADV_APP_ID; + break; + case NETWORK_CONFIG_SOURCE_DHCP_PD: + app_id = DHCP_PD_APP_ID; + break; + case NETWORK_CONFIG_SOURCE_NDISC: + app_id = NDISC_APP_ID; + break; + default: + assert_not_reached(); + } + + masked = address->in_addr.in6; + in6_addr_mask(&masked, address->prefixlen); + + return generate_stable_private_address(link, &app_id, &address->token->secret_key, &masked, &address->in_addr.in6, &address->in_addr.in6); +} + int config_parse_address_generation_type( const char *unit, const char *filename, diff --git a/src/network/networkd-address-generation.h b/src/network/networkd-address-generation.h index 68828b44ca0..2c6091321ae 100644 --- a/src/network/networkd-address-generation.h +++ b/src/network/networkd-address-generation.h @@ -5,6 +5,7 @@ #include "hashmap.h" #include "in-addr-util.h" +typedef struct Address Address; typedef struct IPv6Token IPv6Token; typedef struct Link Link; @@ -15,4 +16,6 @@ int dhcp_pd_generate_addresses(Link *link, const struct in6_addr *prefix, Hashma int ndisc_generate_addresses(Link *link, const struct in6_addr *prefix, uint8_t prefixlen, Hashmap **ret); int radv_generate_addresses(Link *link, Set *tokens, const struct in6_addr *prefix, uint8_t prefixlen, Hashmap **ret); +int regenerate_address(Address *address, Link *link); + CONFIG_PARSER_PROTOTYPE(config_parse_address_generation_type); diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index df9d0c03886..b4ac0bc41b6 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -10,9 +10,11 @@ #include "netlink-util.h" #include "networkd-address-pool.h" #include "networkd-address.h" +#include "networkd-dhcp-prefix-delegation.h" #include "networkd-dhcp-server.h" #include "networkd-ipv4acd.h" #include "networkd-manager.h" +#include "networkd-ndisc.h" #include "networkd-netlabel.h" #include "networkd-network.h" #include "networkd-queue.h" @@ -802,8 +804,49 @@ static int address_update(Address *address) { return 0; } -static int address_drop(Address *address) { - Link *link = ASSERT_PTR(ASSERT_PTR(address)->link); +static int address_removed_maybe_kernel_dad(Link *link, Address *address) { + int r; + + assert(link); + assert(address); + + if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) + return 0; + + if (address->family != AF_INET6) + return 0; + + if (!FLAGS_SET(address->flags, IFA_F_TENTATIVE)) + return 0; + + log_link_info(link, "Address %s with tentative flag is removed, maybe a duplicated address is assigned on another node or link?", + IN6_ADDR_TO_STRING(&address->in_addr.in6)); + + /* Reset the address state, as the object may be reused in the below. */ + address->state = 0; + + switch (address->source) { + case NETWORK_CONFIG_SOURCE_STATIC: + r = link_reconfigure_radv_address(address, link); + break; + case NETWORK_CONFIG_SOURCE_DHCP_PD: + r = dhcp_pd_reconfigure_address(address, link); + break; + case NETWORK_CONFIG_SOURCE_NDISC: + r = ndisc_reconfigure_address(address, link); + break; + default: + r = 0; + } + if (r < 0) + return log_link_warning_errno(link, r, "Failed to configure an alternative address: %m"); + + return 0; +} + +static int address_drop(Address *in, bool removed_by_us) { + _cleanup_(address_unrefp) Address *address = address_ref(ASSERT_PTR(in)); + Link *link = ASSERT_PTR(address->link); int r; r = address_set_masquerade(address, /* add = */ false); @@ -823,6 +866,14 @@ static int address_drop(Address *address) { address_detach(address); + if (!removed_by_us) { + r = address_removed_maybe_kernel_dad(link, address); + if (r < 0) { + link_enter_failed(link); + return r; + } + } + link_update_operstate(link, /* also_update_master = */ true); link_check_ready(link); return 0; @@ -1137,7 +1188,7 @@ static int address_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Remov if (address_get_request(link, address, &req) >= 0) address_enter_removed(req->userdata); - (void) address_drop(address); + (void) address_drop(address, /* removed_by_us = */ true); } } @@ -1847,9 +1898,11 @@ int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message, if (type == RTM_DELADDR) { if (address) { + bool removed_by_us = FLAGS_SET(address->state, NETWORK_CONFIG_STATE_REMOVING); + address_enter_removed(address); log_address_debug(address, "Forgetting removed", link); - (void) address_drop(address); + (void) address_drop(address, removed_by_us); } else log_address_debug(tmp, "Kernel removed unknown", link); diff --git a/src/network/networkd-dhcp-prefix-delegation.c b/src/network/networkd-dhcp-prefix-delegation.c index 6e83163b047..2e660b77631 100644 --- a/src/network/networkd-dhcp-prefix-delegation.c +++ b/src/network/networkd-dhcp-prefix-delegation.c @@ -369,6 +369,28 @@ static int dhcp_pd_request_address_one(Address *address, Link *link) { return link_request_address(link, address, &link->dhcp_pd_messages, dhcp_pd_address_handler, NULL); } +int dhcp_pd_reconfigure_address(Address *address, Link *link) { + int r; + + assert(address); + assert(address->source == NETWORK_CONFIG_SOURCE_DHCP_PD); + assert(link); + + r = regenerate_address(address, link); + if (r <= 0) + return r; + + r = dhcp_pd_request_address_one(address, link); + if (r < 0) + return r; + + if (!link->dhcp_pd_configured) + link_set_state(link, LINK_STATE_CONFIGURING); + + link_check_ready(link); + return 0; +} + static int dhcp_pd_request_address( Link *link, const struct in6_addr *prefix, diff --git a/src/network/networkd-dhcp-prefix-delegation.h b/src/network/networkd-dhcp-prefix-delegation.h index e591b8ae2e0..4a8cca92b6d 100644 --- a/src/network/networkd-dhcp-prefix-delegation.h +++ b/src/network/networkd-dhcp-prefix-delegation.h @@ -8,6 +8,7 @@ #include "conf-parser.h" +typedef struct Address Address; typedef struct Link Link; bool link_dhcp_pd_is_enabled(Link *link); @@ -19,5 +20,6 @@ int dhcp4_pd_prefix_acquired(Link *uplink); int dhcp6_pd_prefix_acquired(Link *uplink); void dhcp_pd_prefix_lost(Link *uplink); void dhcp4_pd_prefix_lost(Link *uplink); +int dhcp_pd_reconfigure_address(Address *address, Link *link); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_pd_subnet_id); diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index e160014c5ab..2b039bed968 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -377,6 +377,28 @@ static int ndisc_request_address(Address *address, Link *link, sd_ndisc_router * return 0; } +int ndisc_reconfigure_address(Address *address, Link *link) { + int r; + + assert(address); + assert(address->source == NETWORK_CONFIG_SOURCE_NDISC); + assert(link); + + r = regenerate_address(address, link); + if (r <= 0) + return r; + + r = ndisc_request_address(address, link, NULL); + if (r < 0) + return r; + + if (!link->ndisc_configured) + link_set_state(link, LINK_STATE_CONFIGURING); + + link_check_ready(link); + return 0; +} + static int ndisc_redirect_route_new(sd_ndisc_redirect *rd, Route **ret) { _cleanup_(route_unrefp) Route *route = NULL; struct in6_addr gateway, destination; diff --git a/src/network/networkd-ndisc.h b/src/network/networkd-ndisc.h index 2766f5e4350..019fe4da425 100644 --- a/src/network/networkd-ndisc.h +++ b/src/network/networkd-ndisc.h @@ -4,6 +4,7 @@ #include "conf-parser.h" #include "time-util.h" +typedef struct Address Address; typedef struct Link Link; typedef struct Network Network; @@ -61,6 +62,7 @@ int ndisc_stop(Link *link); void ndisc_flush(Link *link); int link_request_ndisc(Link *link); +int ndisc_reconfigure_address(Address *address, Link *link); CONFIG_PARSER_PROTOTYPE(config_parse_ndisc_start_dhcp6_client); CONFIG_PARSER_PROTOTYPE(config_parse_ndisc_use_domains); diff --git a/src/network/networkd-radv.c b/src/network/networkd-radv.c index 279ea8d043b..6ff09119e33 100644 --- a/src/network/networkd-radv.c +++ b/src/network/networkd-radv.c @@ -280,6 +280,29 @@ int link_request_radv_addresses(Link *link) { return 0; } +int link_reconfigure_radv_address(Address *address, Link *link) { + int r; + + assert(address); + assert(address->source == NETWORK_CONFIG_SOURCE_STATIC); + assert(link); + + r = regenerate_address(address, link); + if (r <= 0) + return r; + + r = link_request_static_address(link, address); + if (r < 0) + return r; + + if (link->static_address_messages != 0) { + link->static_addresses_configured = false; + link_set_state(link, LINK_STATE_CONFIGURING); + } + + return 0; +} + static int radv_set_prefix(Link *link, Prefix *prefix) { _cleanup_(sd_radv_prefix_unrefp) sd_radv_prefix *p = NULL; int r; diff --git a/src/network/networkd-radv.h b/src/network/networkd-radv.h index 48677b50de4..94834e77a8e 100644 --- a/src/network/networkd-radv.h +++ b/src/network/networkd-radv.h @@ -71,6 +71,7 @@ void network_drop_invalid_pref64_prefixes(Network *network); void network_adjust_radv(Network *network); int link_request_radv_addresses(Link *link); +int link_reconfigure_radv_address(Address *address, Link *link); bool link_radv_enabled(Link *link); int radv_start(Link *link); From ce4ed0ad9999ae49080be0051348e8b3b6d8b482 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 9 Apr 2024 11:28:20 +0900 Subject: [PATCH 8/8] test-network: add test case for regenerating prefix stable address on conflict For issue #31605. --- ...pv6-prefix-veth-token-prefixstable.network | 9 ++++- test/test-network/systemd-networkd-tests.py | 40 +++++++++++++++++-- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/test/test-network/conf/25-ipv6-prefix-veth-token-prefixstable.network b/test/test-network/conf/25-ipv6-prefix-veth-token-prefixstable.network index ac507004014..11502fdb535 100644 --- a/test/test-network/conf/25-ipv6-prefix-veth-token-prefixstable.network +++ b/test/test-network/conf/25-ipv6-prefix-veth-token-prefixstable.network @@ -6,8 +6,6 @@ Name=veth99 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::, @@ -17,3 +15,10 @@ Token=prefixstable@ Token=static Token=static: Token=static::: +# valid token +Token=prefixstable:2002:da8:1:: +Token=prefixstable:2002:da8:1::,86b123b969ba4b7eb8b3d8605123525a +# reset token +Token= +# set token again +Token=prefixstable:2002:da8:1::,86b123b969ba4b7eb8b3d8605123525a diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 4c69bc90336..b510207e35b 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -5577,10 +5577,44 @@ class NetworkdRATests(unittest.TestCase, Utilities): start_networkd() self.wait_online('veth99:routable', 'veth-peer:degraded') - output = networkctl_status('veth99') + output = check_output('ip -6 address show dev veth99') print(output) - self.assertIn('2002:da8:1:0:b47e:7975:fc7a:7d6e', output) - self.assertIn('2002:da8:2:0:1034:56ff:fe78:9abc', output) # EUI64 + self.assertIn('2002:da8:1:0:b47e:7975:fc7a:7d6e/64', output) # the 1st prefixstable + self.assertIn('2002:da8:2:0:1034:56ff:fe78:9abc/64', output) # EUI64 + + with open(os.path.join(network_unit_dir, '25-ipv6-prefix-veth-token-prefixstable.network'), mode='a', encoding='utf-8') as f: + f.write('\n[IPv6AcceptRA]\nPrefixAllowList=2002:da8:1:0::/64\n') + + networkctl_reload() + self.wait_online('veth99:routable') + + output = check_output('ip -6 address show dev veth99') + print(output) + self.assertIn('2002:da8:1:0:b47e:7975:fc7a:7d6e/64', output) # the 1st prefixstable + self.assertNotIn('2002:da8:2:0:1034:56ff:fe78:9abc/64', output) # EUI64 + + check_output('ip address del 2002:da8:1:0:b47e:7975:fc7a:7d6e/64 dev veth99') + check_output('ip address add 2002:da8:1:0:b47e:7975:fc7a:7d6e/64 dev veth-peer nodad') + + networkctl_reconfigure('veth99') + self.wait_online('veth99:routable') + + output = check_output('ip -6 address show dev veth99') + print(output) + self.assertNotIn('2002:da8:1:0:b47e:7975:fc7a:7d6e/64', output) # the 1st prefixstable + self.assertIn('2002:da8:1:0:da5d:e50a:43fd:5d0f/64', output) # the 2nd prefixstable + + check_output('ip address del 2002:da8:1:0:da5d:e50a:43fd:5d0f/64 dev veth99') + check_output('ip address add 2002:da8:1:0:da5d:e50a:43fd:5d0f/64 dev veth-peer nodad') + + networkctl_reconfigure('veth99') + self.wait_online('veth99:routable') + + output = check_output('ip -6 address show dev veth99') + print(output) + self.assertNotIn('2002:da8:1:0:b47e:7975:fc7a:7d6e/64', output) # the 1st prefixstable + self.assertNotIn('2002:da8:1:0:da5d:e50a:43fd:5d0f/64', output) # the 2nd prefixstable + self.assertIn('2002:da8:1:0:c7e4:77ec:eb31:1b0d/64', output) # the 3rd prefixstable def test_ipv6_token_prefixstable_without_address(self): copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-prefixstable-without-address.network')