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::
```
This commit is contained in:
Susant Sahani 2020-01-09 13:19:53 +01:00 committed by Yu Watanabe
parent 641aa41200
commit 5f506a5560
6 changed files with 262 additions and 26 deletions

View File

@ -307,11 +307,19 @@
<varlistentry>
<term><varname>IPv6Token=</varname></term>
<listitem>
<para>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.</para>
<para>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
<literal><replaceable>type</replaceable>:<replaceable>prefix</replaceable></literal>. The
address generation mechanism may be either <constant>prefixstable</constant> or
<constant>eui64</constant>. If not specified, <constant>eui64</constant> is assumed. When
set to <literal>prefixstable</literal> a method for generating IPv6 Interface Identifiers to
be used with IPv6 Stateless Address Autocon figuration (SLAAC). See
<ulink url="https://tools.ietf.org/html/rfc7217">RFC 7217</ulink>. When IPv6 address is set,
indicates the 64-bit interface part of SLAAC IPv6 addresses for this link.</para>
<para>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.
</para>
</listitem>
</varlistentry>
<varlistentry>

View File

@ -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;
}

View File

@ -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);

View File

@ -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

View File

@ -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);
}

View File

@ -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;