diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:35:18 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:35:18 +0000 |
commit | b750101eb236130cf056c675997decbac904cc49 (patch) | |
tree | a5df1a06754bdd014cb975c051c83b01c9a97532 /src/network/networkd-address-generation.c | |
parent | Initial commit. (diff) | |
download | systemd-b750101eb236130cf056c675997decbac904cc49.tar.xz systemd-b750101eb236130cf056c675997decbac904cc49.zip |
Adding upstream version 252.22.upstream/252.22
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/network/networkd-address-generation.c')
-rw-r--r-- | src/network/networkd-address-generation.c | 440 |
1 files changed, 440 insertions, 0 deletions
diff --git a/src/network/networkd-address-generation.c b/src/network/networkd-address-generation.c new file mode 100644 index 0000000..769cccf --- /dev/null +++ b/src/network/networkd-address-generation.c @@ -0,0 +1,440 @@ +/* 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; + +typedef struct IPv6Token { + AddressGenerationType type; + struct in6_addr address; + sd_id128_t secret_key; +} IPv6Token; + +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); + siphash24_compress(&dad_counter, sizeof(uint8_t), &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, + struct in6_addr *ret) { + + sd_id128_t secret_machine_key; + struct in6_addr addr; + 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)) + break; + } + if (i >= DAD_CONFLICTS_IDGEN_RETRIES_RFC7217) + /* propagate recognizable errors. */ + return log_link_debug_errno(link, SYNTHETIC_ERRNO(ENOANO), + "Failed to generate stable private address."); + + *ret = addr; + return 0; +} + +static int generate_addresses( + Link *link, + Set *tokens, + const sd_id128_t *app_id, + const struct in6_addr *prefix, + uint8_t prefixlen, + Set **ret) { + + _cleanup_set_free_ Set *addresses = NULL; + struct in6_addr masked; + 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) { + struct in6_addr addr, *copy; + + 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, &addr) < 0) + continue; + + break; + + default: + 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); + 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; + + 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 (r < 0) + return r; + + r = set_ensure_consume(&addresses, &in6_addr_hash_ops_free, TAKE_PTR(addr)); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(addresses); + return 0; +} + +int dhcp_pd_generate_addresses(Link *link, const struct in6_addr *prefix, Set **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) { + 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) { + 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(&p->type, sizeof(p->type), state); + siphash24_compress(&p->address, sizeof(p->address), state); + id128_hash_func(&p->secret_key, state); +} + +static int ipv6_token_compare_func(const IPv6Token *a, const IPv6Token *b) { + 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, + 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 = sd_id128_from_string(comma + 1, &secret_key); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse secret key in %s=, ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + if (sd_id128_is_null(secret_key)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Secret key in %s= cannot be null, ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + } + + } else if (streq(rvalue, "eui64")) { + type = ADDRESS_GENERATION_EUI64; + 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; +} |