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.
547 lines
19 KiB
C
547 lines
19 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
|
|
#include <net/if_arp.h>
|
|
|
|
#include "sd-id128.h"
|
|
|
|
#include "arphrd-util.h"
|
|
#include "id128-util.h"
|
|
#include "memory-util.h"
|
|
#include "networkd-address-generation.h"
|
|
#include "networkd-link.h"
|
|
#include "networkd-network.h"
|
|
#include "string-util.h"
|
|
|
|
#define DAD_CONFLICTS_IDGEN_RETRIES_RFC7217 3
|
|
|
|
/* https://www.iana.org/assignments/ipv6-interface-ids/ipv6-interface-ids.xml */
|
|
#define SUBNET_ROUTER_ANYCAST_ADDRESS ((const struct in6_addr) { .s6_addr = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } })
|
|
#define SUBNET_ROUTER_ANYCAST_PREFIXLEN 64
|
|
#define RESERVED_INTERFACE_IDENTIFIERS_ADDRESS ((const struct in6_addr) { .s6_addr = { 0x02, 0x00, 0x5E, 0xFF, 0xFE } })
|
|
#define RESERVED_INTERFACE_IDENTIFIERS_PREFIXLEN 40
|
|
#define RESERVED_SUBNET_ANYCAST_ADDRESSES ((const struct in6_addr) { .s6_addr = { 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80 } })
|
|
#define RESERVED_SUBNET_ANYCAST_PREFIXLEN 57
|
|
|
|
#define DHCP_PD_APP_ID SD_ID128_MAKE(fb,b9,37,ca,4a,ed,4a,4d,b0,70,7f,aa,71,c0,c9,85)
|
|
#define NDISC_APP_ID SD_ID128_MAKE(13,ac,81,a7,d5,3f,49,78,92,79,5d,0c,29,3a,bc,7e)
|
|
#define RADV_APP_ID SD_ID128_MAKE(1f,1e,90,c8,5c,78,4f,dc,8e,61,2d,59,0d,53,c1,25)
|
|
|
|
typedef enum AddressGenerationType {
|
|
ADDRESS_GENERATION_EUI64,
|
|
ADDRESS_GENERATION_STATIC,
|
|
ADDRESS_GENERATION_PREFIXSTABLE,
|
|
_ADDRESS_GENERATION_TYPE_MAX,
|
|
_ADDRESS_GENERATION_TYPE_INVALID = -EINVAL,
|
|
} AddressGenerationType;
|
|
|
|
struct IPv6Token {
|
|
unsigned n_ref;
|
|
AddressGenerationType type;
|
|
struct in6_addr address;
|
|
sd_id128_t secret_key;
|
|
};
|
|
|
|
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);
|
|
assert(prefix);
|
|
assert(ret);
|
|
|
|
memcpy(ret->s6_addr, prefix, 8);
|
|
|
|
switch (link->iftype) {
|
|
case ARPHRD_INFINIBAND:
|
|
/* Use last 8 byte. See RFC4391 section 8 */
|
|
memcpy(&ret->s6_addr[8], &link->hw_addr.infiniband[INFINIBAND_ALEN - 8], 8);
|
|
break;
|
|
case ARPHRD_ETHER:
|
|
/* see RFC4291 section 2.5.1 */
|
|
ret->s6_addr[8] = link->hw_addr.ether.ether_addr_octet[0];
|
|
ret->s6_addr[9] = link->hw_addr.ether.ether_addr_octet[1];
|
|
ret->s6_addr[10] = link->hw_addr.ether.ether_addr_octet[2];
|
|
ret->s6_addr[11] = 0xff;
|
|
ret->s6_addr[12] = 0xfe;
|
|
ret->s6_addr[13] = link->hw_addr.ether.ether_addr_octet[3];
|
|
ret->s6_addr[14] = link->hw_addr.ether.ether_addr_octet[4];
|
|
ret->s6_addr[15] = link->hw_addr.ether.ether_addr_octet[5];
|
|
break;
|
|
default:
|
|
return log_link_debug_errno(link, SYNTHETIC_ERRNO(EINVAL),
|
|
"Token=eui64 is not supported for interface type %s, ignoring.",
|
|
strna(arphrd_to_name(link->iftype)));
|
|
}
|
|
|
|
ret->s6_addr[8] ^= 1 << 1;
|
|
return 0;
|
|
}
|
|
|
|
static bool stable_private_address_is_valid(const struct in6_addr *addr) {
|
|
assert(addr);
|
|
|
|
/* According to rfc4291, generated address should not be in the following ranges. */
|
|
|
|
if (in6_addr_prefix_covers(&SUBNET_ROUTER_ANYCAST_ADDRESS, SUBNET_ROUTER_ANYCAST_PREFIXLEN, addr))
|
|
return false;
|
|
|
|
if (in6_addr_prefix_covers(&RESERVED_INTERFACE_IDENTIFIERS_ADDRESS, RESERVED_INTERFACE_IDENTIFIERS_PREFIXLEN, addr))
|
|
return false;
|
|
|
|
if (in6_addr_prefix_covers(&RESERVED_SUBNET_ANYCAST_ADDRESSES, RESERVED_SUBNET_ANYCAST_PREFIXLEN, addr))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void generate_stable_private_address_one(
|
|
Link *link,
|
|
const sd_id128_t *secret_key,
|
|
const struct in6_addr *prefix,
|
|
uint8_t dad_counter,
|
|
struct in6_addr *ret) {
|
|
|
|
struct siphash state;
|
|
uint64_t rid;
|
|
|
|
assert(link);
|
|
assert(secret_key);
|
|
assert(prefix);
|
|
assert(ret);
|
|
|
|
/* According to RFC7217 section 5.1
|
|
* RID = F(Prefix, Net_Iface, Network_ID, DAD_Counter, secret_key) */
|
|
|
|
siphash24_init(&state, secret_key->bytes);
|
|
|
|
siphash24_compress(prefix, 8, &state);
|
|
siphash24_compress_string(link->ifname, &state);
|
|
if (link->iftype == ARPHRD_INFINIBAND)
|
|
/* Only last 8 bytes of IB MAC are stable */
|
|
siphash24_compress(&link->hw_addr.infiniband[INFINIBAND_ALEN - 8], 8, &state);
|
|
else
|
|
siphash24_compress(link->hw_addr.bytes, link->hw_addr.length, &state);
|
|
|
|
if (link->ssid)
|
|
siphash24_compress_string(link->ssid, &state);
|
|
|
|
siphash24_compress_typesafe(dad_counter, &state);
|
|
|
|
rid = htole64(siphash24_finalize(&state));
|
|
|
|
memcpy(ret->s6_addr, prefix->s6_addr, 8);
|
|
memcpy(ret->s6_addr + 8, &rid, 8);
|
|
}
|
|
|
|
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,
|
|
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;
|
|
|
|
assert(link);
|
|
assert(app_id);
|
|
assert(secret_key);
|
|
assert(prefix);
|
|
assert(ret);
|
|
|
|
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);
|
|
|
|
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) {
|
|
/* propagate recognizable errors. */
|
|
if (previous && !found)
|
|
return -EADDRNOTAVAIL;
|
|
|
|
return log_link_debug_errno(link, SYNTHETIC_ERRNO(EADDRINUSE),
|
|
"Failed to generate stable private address.");
|
|
}
|
|
|
|
*ret = addr;
|
|
return 1;
|
|
}
|
|
|
|
static int generate_addresses(
|
|
Link *link,
|
|
Set *tokens,
|
|
const sd_id128_t *app_id,
|
|
const struct in6_addr *prefix,
|
|
uint8_t prefixlen,
|
|
Hashmap **ret) {
|
|
|
|
_cleanup_hashmap_free_ Hashmap *tokens_by_address = NULL;
|
|
struct in6_addr masked, addr;
|
|
IPv6Token *j;
|
|
int r;
|
|
|
|
assert(link);
|
|
assert(app_id);
|
|
assert(prefix);
|
|
assert(prefixlen > 0 && prefixlen <= 64);
|
|
assert(ret);
|
|
|
|
masked = *prefix;
|
|
in6_addr_mask(&masked, prefixlen);
|
|
|
|
SET_FOREACH(j, tokens) {
|
|
switch (j->type) {
|
|
case ADDRESS_GENERATION_EUI64:
|
|
if (generate_eui64_address(link, &masked, &addr) < 0)
|
|
continue;
|
|
break;
|
|
|
|
case ADDRESS_GENERATION_STATIC:
|
|
memcpy(addr.s6_addr, masked.s6_addr, 8);
|
|
memcpy(addr.s6_addr + 8, j->address.s6_addr + 8, 8);
|
|
break;
|
|
|
|
case ADDRESS_GENERATION_PREFIXSTABLE:
|
|
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, /* previous = */ NULL, &addr) < 0)
|
|
continue;
|
|
|
|
break;
|
|
|
|
default:
|
|
assert_not_reached();
|
|
}
|
|
|
|
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 (hashmap_isempty(tokens_by_address)) {
|
|
AddressGenerationType type;
|
|
|
|
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 = ipv6_token_type_put_by_addr(&tokens_by_address, &addr, type);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
*ret = TAKE_PTR(tokens_by_address);
|
|
return 0;
|
|
}
|
|
|
|
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, 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, Hashmap **ret) {
|
|
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,
|
|
unsigned line,
|
|
const char *section,
|
|
unsigned section_line,
|
|
const char *lvalue,
|
|
int ltype,
|
|
const char *rvalue,
|
|
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 = ASSERT_PTR(data);
|
|
const char *addr;
|
|
int r;
|
|
|
|
assert(filename);
|
|
assert(lvalue);
|
|
assert(rvalue);
|
|
|
|
if (isempty(rvalue)) {
|
|
*tokens = set_free(*tokens);
|
|
return 0;
|
|
}
|
|
|
|
if ((addr = startswith(rvalue, "prefixstable"))) {
|
|
const char *comma;
|
|
|
|
type = ADDRESS_GENERATION_PREFIXSTABLE;
|
|
|
|
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 = TAKE_PTR(addr);
|
|
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 = id128_from_string_nonzero(comma + 1, &secret_key);
|
|
if (r < 0) {
|
|
log_syntax(unit, LOG_WARNING, filename, line, r,
|
|
r == -ENXIO ? "Secret key in %s= cannot be null, ignoring assignment: %s"
|
|
: "Failed to parse secret key in %s=, ignoring assignment: %s",
|
|
lvalue, rvalue);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
} else if (streq(rvalue, "eui64")) {
|
|
type = ADDRESS_GENERATION_EUI64;
|
|
addr = NULL;
|
|
} else {
|
|
type = ADDRESS_GENERATION_STATIC;
|
|
|
|
addr = startswith(rvalue, "static:");
|
|
if (!addr)
|
|
addr = rvalue;
|
|
}
|
|
|
|
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",
|
|
lvalue, rvalue);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
switch (type) {
|
|
case ADDRESS_GENERATION_EUI64:
|
|
assert(in6_addr_is_null(&buffer.in6));
|
|
break;
|
|
|
|
case ADDRESS_GENERATION_STATIC:
|
|
/* Only last 64 bits are used. */
|
|
memzero(buffer.in6.s6_addr, 8);
|
|
|
|
if (in6_addr_is_null(&buffer.in6)) {
|
|
log_syntax(unit, LOG_WARNING, filename, line, 0,
|
|
"IPv6 address in %s= cannot be the ANY address, ignoring assignment: %s",
|
|
lvalue, rvalue);
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case ADDRESS_GENERATION_PREFIXSTABLE:
|
|
/* At most, the initial 64 bits are used. */
|
|
(void) in6_addr_mask(&buffer.in6, 64);
|
|
break;
|
|
|
|
default:
|
|
assert_not_reached();
|
|
}
|
|
|
|
r = ipv6_token_add(tokens, type, &buffer.in6, &secret_key);
|
|
if (r < 0)
|
|
return log_oom();
|
|
|
|
return 0;
|
|
}
|