From 5f506a55606fed9fd74b851a18f15a9835a26d08 Mon Sep 17 00:00:00 2001 From: Susant Sahani Date: Thu, 9 Jan 2020 13:19:53 +0100 Subject: [PATCH] network: Allow to specify multiple IPv6Token for SLAAC Provide names to choose between different auto-generation types: 2.1 "eui64" for EUI-64 of RFC 4291 2.2 "prefixstable" for RFC 7217 ``` [Match] Name=veth99 [Network] DHCP=no IPv6AcceptRA=yes IPv6Token=prefixstable:2001:888:0db8:1:: ``` --- man/systemd.network.xml | 18 +- src/network/networkd-ndisc.c | 243 +++++++++++++++++++++-- src/network/networkd-ndisc.h | 21 ++ src/network/networkd-network-gperf.gperf | 2 +- src/network/networkd-network.c | 1 + src/network/networkd-network.h | 3 +- 6 files changed, 262 insertions(+), 26 deletions(-) diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 9e0bf69a35..abd7e04724 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -307,11 +307,19 @@ IPv6Token= - An IPv6 address with the top 64 bits unset. When set, indicates the - 64-bit interface part of SLAAC IPv6 addresses for this link. Note that - the token is only ever used for SLAAC, and not for DHCPv6 addresses, even - in the case DHCP is requested by router advertisement. By default, the - token is autogenerated. + Specifies an optional address generation mechanism and an optional address prefix. If + the mechanism is present, the two parts must be separated with a colon + type:prefix. The + address generation mechanism may be either prefixstable or + eui64. If not specified, eui64 is assumed. When + set to prefixstable a method for generating IPv6 Interface Identifiers to + be used with IPv6 Stateless Address Autocon figuration (SLAAC). See + RFC 7217. When IPv6 address is set, + indicates the 64-bit interface part of SLAAC IPv6 addresses for this link. + + Note that the token is only ever used for SLAAC, and not for DHCPv6 addresses, even in + the case DHCP is requested by router advertisement. By default, the token is autogenerated. + diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index fb3d6f2a84..ae38a36975 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -13,12 +13,74 @@ #include "networkd-manager.h" #include "networkd-ndisc.h" #include "networkd-route.h" +#include "string-util.h" #include "strv.h" #define NDISC_DNSSL_MAX 64U #define NDISC_RDNSS_MAX 64U #define NDISC_PREFIX_LFT_MIN 7200U +#define DAD_CONFLICTS_IDGEN_RETRIES_RFC7217 3 + +/* https://tools.ietf.org/html/rfc5453 */ +/* https://www.iana.org/assignments/ipv6-interface-ids/ipv6-interface-ids.xml */ + +#define SUBNET_ROUTER_ANYCAST_ADDRESS_RFC4291 ((struct in6_addr) { .s6_addr = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }) +#define SUBNET_ROUTER_ANYCAST_PREFIXLEN 8 +#define RESERVED_IPV6_INTERFACE_IDENTIFIERS_ADDRESS_RFC4291 ((struct in6_addr) { .s6_addr = { 0x02, 0x00, 0x5E, 0xFF, 0xFE } }) +#define RESERVED_IPV6_INTERFACE_IDENTIFIERS_PREFIXLEN 5 +#define RESERVED_SUBNET_ANYCAST_ADDRESSES_RFC4291 ((struct in6_addr) { .s6_addr = { 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } }) +#define RESERVED_SUBNET_ANYCAST_PREFIXLEN 7 + +#define NDISC_APP_ID SD_ID128_MAKE(13,ac,81,a7,d5,3f,49,78,92,79,5d,0c,29,3a,bc,7e) + +static bool stableprivate_address_is_valid(const struct in6_addr *addr) { + assert(addr); + + /* According to rfc4291, generated address should not be in the following ranges. */ + + if (memcmp(addr, &SUBNET_ROUTER_ANYCAST_ADDRESS_RFC4291, SUBNET_ROUTER_ANYCAST_PREFIXLEN) == 0) + return false; + + if (memcmp(addr, &RESERVED_IPV6_INTERFACE_IDENTIFIERS_ADDRESS_RFC4291, RESERVED_IPV6_INTERFACE_IDENTIFIERS_PREFIXLEN) == 0) + return false; + + if (memcmp(addr, &RESERVED_SUBNET_ANYCAST_ADDRESSES_RFC4291, RESERVED_SUBNET_ANYCAST_PREFIXLEN) == 0) + return false; + + return true; +} + +static int make_stableprivate_address(Link *link, const struct in6_addr *prefix, uint8_t prefix_len, uint8_t dad_counter, struct in6_addr *addr) { + sd_id128_t secret_key; + struct siphash state; + uint64_t rid; + size_t l; + int r; + + /* According to rfc7217 section 5.1 + * RID = F(Prefix, Net_Iface, Network_ID, DAD_Counter, secret_key) */ + + r = sd_id128_get_machine_app_specific(NDISC_APP_ID, &secret_key); + if (r < 0) + return log_error_errno(r, "Failed to generate key: %m"); + + siphash24_init(&state, secret_key.bytes); + + l = MAX(DIV_ROUND_UP(prefix_len, 8), 8); + siphash24_compress(prefix, l, &state); + siphash24_compress(link->ifname, strlen(link->ifname), &state); + siphash24_compress(&link->mac, sizeof(struct ether_addr), &state); + siphash24_compress(&dad_counter, sizeof(uint8_t), &state); + + rid = htole64(siphash24_finalize(&state)); + + memcpy(addr->s6_addr, prefix->s6_addr, l); + memcpy((uint8_t *) &addr->s6_addr + l, &rid, 16 - l); + + return 0; +} + static int ndisc_netlink_route_message_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { int r; @@ -192,12 +254,62 @@ static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) { return 0; } +static int ndisc_router_generate_address(Link *link, unsigned prefixlen, uint32_t lifetime_preferred, Address *address) { + bool prefix = false; + struct in6_addr addr; + IPv6Token *j; + Iterator i; + int r; + + assert(address); + assert(link); + + addr = address->in_addr.in6; + ORDERED_HASHMAP_FOREACH(j, link->network->ipv6_tokens, i) + if (j->address_generation_type == IPV6_TOKEN_ADDRESS_GENERATION_PREFIXSTABLE + && memcmp(&j->prefix, &addr, FAMILY_ADDRESS_SIZE(address->family)) == 0) { + for (; j->dad_counter < DAD_CONFLICTS_IDGEN_RETRIES_RFC7217; j->dad_counter++) { + r = make_stableprivate_address(link, &j->prefix, prefixlen, j->dad_counter, &address->in_addr.in6); + if (r < 0) + return r; + + if (stableprivate_address_is_valid(&address->in_addr.in6)) { + prefix = true; + break; + } + } + } else if (j->address_generation_type == IPV6_TOKEN_ADDRESS_GENERATION_EUI64) { + memcpy(((uint8_t *)&address->in_addr.in6) + 8, ((uint8_t *) &j->prefix) + 8, 8); + prefix = true; + break; + } + + /* eui64 or fallback if prefixstable do not match */ + if (!prefix) { + /* see RFC4291 section 2.5.1 */ + address->in_addr.in6.s6_addr[8] = link->mac.ether_addr_octet[0]; + address->in_addr.in6.s6_addr[8] ^= 1 << 1; + address->in_addr.in6.s6_addr[9] = link->mac.ether_addr_octet[1]; + address->in_addr.in6.s6_addr[10] = link->mac.ether_addr_octet[2]; + address->in_addr.in6.s6_addr[11] = 0xff; + address->in_addr.in6.s6_addr[12] = 0xfe; + address->in_addr.in6.s6_addr[13] = link->mac.ether_addr_octet[3]; + address->in_addr.in6.s6_addr[14] = link->mac.ether_addr_octet[4]; + address->in_addr.in6.s6_addr[15] = link->mac.ether_addr_octet[5]; + } + + address->prefixlen = prefixlen; + address->flags = IFA_F_NOPREFIXROUTE|IFA_F_MANAGETEMPADDR; + address->cinfo.ifa_prefered = lifetime_preferred; + + return 0; +} static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *rt) { + uint32_t lifetime_valid, lifetime_preferred, lifetime_remaining; _cleanup_(address_freep) Address *address = NULL; Address *existing_address; - uint32_t lifetime_valid, lifetime_preferred, lifetime_remaining; - usec_t time_now; unsigned prefixlen; + usec_t time_now; int r; assert(link); @@ -232,23 +344,9 @@ static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *r if (r < 0) return log_link_error_errno(link, r, "Failed to get prefix address: %m"); - if (in_addr_is_null(AF_INET6, (const union in_addr_union *) &link->network->ipv6_token) == 0) - memcpy(((char *)&address->in_addr.in6) + 8, ((char *)&link->network->ipv6_token) + 8, 8); - else { - /* see RFC4291 section 2.5.1 */ - address->in_addr.in6.s6_addr[8] = link->mac.ether_addr_octet[0]; - address->in_addr.in6.s6_addr[8] ^= 1 << 1; - address->in_addr.in6.s6_addr[9] = link->mac.ether_addr_octet[1]; - address->in_addr.in6.s6_addr[10] = link->mac.ether_addr_octet[2]; - address->in_addr.in6.s6_addr[11] = 0xff; - address->in_addr.in6.s6_addr[12] = 0xfe; - address->in_addr.in6.s6_addr[13] = link->mac.ether_addr_octet[3]; - address->in_addr.in6.s6_addr[14] = link->mac.ether_addr_octet[4]; - address->in_addr.in6.s6_addr[15] = link->mac.ether_addr_octet[5]; - } - address->prefixlen = prefixlen; - address->flags = IFA_F_NOPREFIXROUTE|IFA_F_MANAGETEMPADDR; - address->cinfo.ifa_prefered = lifetime_preferred; + r = ndisc_router_generate_address(link, prefixlen, lifetime_preferred, address); + if (r < 0) + return log_link_error_errno(link, r, "Falied to generate prefix stable address: %m"); /* see RFC4862 section 5.5.3.e */ r = address_get(link, address->family, &address->in_addr, address->prefixlen, &existing_address); @@ -755,6 +853,30 @@ void ndisc_flush(Link *link) { link->ndisc_dnssl = set_free_free(link->ndisc_dnssl); } +int ipv6token_new(IPv6Token **ret) { + IPv6Token *p; + + p = new(IPv6Token, 1); + if (!p) + return -ENOMEM; + + *p = (IPv6Token) { + .address_generation_type = IPV6_TOKEN_ADDRESS_GENERATION_NONE, + }; + + *ret = TAKE_PTR(p); + + return 0; +} + +DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + ipv6_token_hash_ops, + void, + trivial_hash_func, + trivial_compare_func, + IPv6Token, + free); + int config_parse_ndisc_black_listed_prefix( const char *unit, const char *filename, @@ -826,3 +948,86 @@ int config_parse_ndisc_black_listed_prefix( return 0; } + +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_ IPv6Token *token = NULL; + _cleanup_free_ char *word = NULL; + union in_addr_union buffer; + Network *network = data; + const char *p; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + network->ipv6_tokens = ordered_hashmap_free(network->ipv6_tokens); + return 0; + } + + p = rvalue; + r = extract_first_word(&p, &word, ":", 0); + if (r == -ENOMEM) + return log_oom(); + if (r <= 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Invalid IPv6Token= , ignoring assignment: %s", rvalue); + return 0; + } + + r = ipv6token_new(&token); + if (r < 0) + return log_oom(); + + if (streq(word, "eui64")) + token->address_generation_type = IPV6_TOKEN_ADDRESS_GENERATION_EUI64; + else if (streq(word, "prefixstable")) + token->address_generation_type = IPV6_TOKEN_ADDRESS_GENERATION_PREFIXSTABLE; + else { + token->address_generation_type = IPV6_TOKEN_ADDRESS_GENERATION_EUI64; + p = rvalue; + } + + r = in_addr_from_string(AF_INET6, p, &buffer); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Failed to parse IPv6 %s, ignoring: %s", lvalue, rvalue); + return 0; + } + + if (in_addr_is_null(AF_INET6, &buffer)) { + log_syntax(unit, LOG_ERR, filename, line, 0, + "IPv6 %s cannot be the ANY address, ignoring: %s", lvalue, rvalue); + return 0; + } + + token->prefix = buffer.in6; + + r = ordered_hashmap_ensure_allocated(&network->ipv6_tokens, &ipv6_token_hash_ops); + if (r < 0) + return log_oom(); + + r = ordered_hashmap_put(network->ipv6_tokens, &token->prefix, token); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Failed to store IPv6 token '%s'", rvalue); + return 0; + } + + TAKE_PTR(token); + + return 0; +} diff --git a/src/network/networkd-ndisc.h b/src/network/networkd-ndisc.h index dc0a44f523..18d1a96476 100644 --- a/src/network/networkd-ndisc.h +++ b/src/network/networkd-ndisc.h @@ -5,6 +5,16 @@ #include "networkd-link.h" #include "time-util.h" +typedef struct IPv6Token IPv6Token; + +typedef enum IPv6TokenAddressGeneration { + IPV6_TOKEN_ADDRESS_GENERATION_NONE, + IPV6_TOKEN_ADDRESS_GENERATION_EUI64, + IPV6_TOKEN_ADDRESS_GENERATION_PREFIXSTABLE, + _IPV6_TOKEN_ADDRESS_GENERATION_MAX, + _IPV6_TOKEN_ADDRESS_GENERATION_INVALID = -1, +} IPv6TokenAddressGeneration; + typedef struct NDiscRDNSS { usec_t valid_until; struct in6_addr address; @@ -15,6 +25,16 @@ typedef struct NDiscDNSSL { /* The domain name follows immediately. */ } NDiscDNSSL; +struct IPv6Token { + IPv6TokenAddressGeneration address_generation_type; + + uint8_t dad_counter; + struct in6_addr prefix; +}; + +int ipv6token_new(IPv6Token **ret); +DEFINE_TRIVIAL_CLEANUP_FUNC(IPv6Token *, freep); + static inline char* NDISC_DNSSL_DOMAIN(const NDiscDNSSL *n) { return ((char*) n) + ALIGN(sizeof(NDiscDNSSL)); } @@ -24,3 +44,4 @@ void ndisc_vacuum(Link *link); void ndisc_flush(Link *link); CONFIG_PARSER_PROTOTYPE(config_parse_ndisc_black_listed_prefix); +CONFIG_PARSER_PROTOTYPE(config_parse_address_generation_type); diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 80500332e6..6797153418 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -68,7 +68,7 @@ Network.DHCPServer, config_parse_bool, Network.LinkLocalAddressing, config_parse_link_local_address_family, 0, offsetof(Network, link_local) Network.IPv4LLRoute, config_parse_bool, 0, offsetof(Network, ipv4ll_route) Network.DefaultRouteOnDevice, config_parse_bool, 0, offsetof(Network, default_route_on_device) -Network.IPv6Token, config_parse_ipv6token, 0, offsetof(Network, ipv6_token) +Network.IPv6Token, config_parse_address_generation_type, 0, 0 Network.LLDP, config_parse_lldp_mode, 0, offsetof(Network, lldp_mode) Network.EmitLLDP, config_parse_lldp_emit, 0, offsetof(Network, lldp_emit) Network.Address, config_parse_address, 0, 0 diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index e7ff9ae54f..1323c82cc9 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -705,6 +705,7 @@ static Network *network_free(Network *network) { ordered_hashmap_free(network->dhcp_client_send_options); ordered_hashmap_free(network->dhcp_server_send_options); + ordered_hashmap_free(network->ipv6_tokens); return mfree(network); } diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 1cfbd0b6b6..c030f3451a 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -22,6 +22,7 @@ #include "networkd-ipv6-proxy-ndp.h" #include "networkd-lldp-rx.h" #include "networkd-lldp-tx.h" +#include "networkd-ndisc.h" #include "networkd-neighbor.h" #include "networkd-nexthop.h" #include "networkd-radv.h" @@ -218,8 +219,8 @@ struct Network { uint32_t ipv6_accept_ra_route_table; bool ipv6_accept_ra_route_table_set; Set *ndisc_black_listed_prefix; + OrderedHashmap *ipv6_tokens; - union in_addr_union ipv6_token; IPv6PrivacyExtensions ipv6_privacy_extensions; struct ether_addr *mac;