diff --git a/src/network/networkd-address-generation.c b/src/network/networkd-address-generation.c index 9a5e2c21739..816cdf713ac 100644 --- a/src/network/networkd-address-generation.c +++ b/src/network/networkd-address-generation.c @@ -34,11 +34,125 @@ 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); + +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; + + 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 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); @@ -134,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; @@ -162,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( @@ -180,10 +309,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; @@ -197,8 +326,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) @@ -214,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; @@ -223,97 +350,77 @@ 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, /* previous = */ NULL, &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); } -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); -} +int regenerate_address(Address *address, Link *link) { + struct in6_addr masked; + sd_id128_t app_id; -static int ipv6_token_compare_func(const IPv6Token *a, const IPv6Token *b) { - int r; + assert(link); + assert(address); + assert(address->family == AF_INET6); + assert(!address->link && !address->network); - r = CMP(a->type, b->type); - if (r != 0) - return r; + if (!address->token || + address->token->type != ADDRESS_GENERATION_PREFIXSTABLE) + return 0; - r = memcmp(&a->address, &b->address, sizeof(struct in6_addr)); - if (r != 0) - return r; + 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(); + } - return id128_compare_func(&a->secret_key, &b->secret_key); -} + masked = address->in_addr.in6; + in6_addr_mask(&masked, address->prefixlen); -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); + 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( diff --git a/src/network/networkd-address-generation.h b/src/network/networkd-address-generation.h index 901b2ec4bf3..2c6091321ae 100644 --- a/src/network/networkd-address-generation.h +++ b/src/network/networkd-address-generation.h @@ -2,13 +2,20 @@ #pragma once #include "conf-parser.h" +#include "hashmap.h" #include "in-addr-util.h" -#include "set.h" +typedef struct Address Address; +typedef struct IPv6Token IPv6Token; typedef struct Link Link; -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); +IPv6Token* ipv6_token_ref(IPv6Token *token); +IPv6Token* ipv6_token_unref(IPv6Token *token); + +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); + +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 41d75bdf841..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" @@ -260,6 +262,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 +611,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; @@ -800,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); @@ -821,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; @@ -1135,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); } } @@ -1845,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); @@ -1886,6 +1941,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; diff --git a/src/network/networkd-dhcp-prefix-delegation.c b/src/network/networkd-dhcp-prefix-delegation.c index 61295d9ce64..2e660b77631 100644 --- a/src/network/networkd-dhcp-prefix-delegation.c +++ b/src/network/networkd-dhcp-prefix-delegation.c @@ -353,14 +353,50 @@ 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); +} + +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, usec_t lifetime_preferred_usec, usec_t lifetime_valid_usec) { - _cleanup_set_free_ Set *addresses = NULL; - struct in6_addr *a; int r; assert(link); @@ -370,13 +406,15 @@ 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; - Address *existing; r = address_new(&address); if (r < 0) @@ -390,20 +428,13 @@ 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; - - log_dhcp_pd_address(link, address); + address->token = ipv6_token_ref(token); 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"); } 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 e1853bb31cc..2b039bed968 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) @@ -378,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; @@ -1029,8 +1050,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; @@ -1068,11 +1088,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); @@ -1085,6 +1108,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-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 82c62dad815..6ff09119e33 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) @@ -279,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); 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')