mirror of
https://github.com/systemd/systemd-stable.git
synced 2025-02-04 17:47:03 +03:00
Merge pull request #14415 from ssahani/prefixstable-rfc-7217-new
network: Allow to specify multiple IPv6Token for SLAAC
This commit is contained in:
commit
5bbcff2a1d
@ -308,11 +308,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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -0,0 +1,6 @@
|
||||
[Match]
|
||||
Name=veth99
|
||||
|
||||
[Network]
|
||||
IPv6AcceptRA=true
|
||||
IPv6Token=::1a:2b:3c:4d
|
@ -0,0 +1,6 @@
|
||||
[Match]
|
||||
Name=veth99
|
||||
|
||||
[Network]
|
||||
IPv6AcceptRA=true
|
||||
IPv6Token=prefixstable:2002:da8:1::
|
@ -2644,7 +2644,9 @@ class NetworkdRATests(unittest.TestCase, Utilities):
|
||||
units = [
|
||||
'25-veth.netdev',
|
||||
'ipv6-prefix.network',
|
||||
'ipv6-prefix-veth.network']
|
||||
'ipv6-prefix-veth.network',
|
||||
'ipv6-prefix-veth-token-eui64.network',
|
||||
'ipv6-prefix-veth-token-prefixstable.network']
|
||||
|
||||
def setUp(self):
|
||||
remove_links(self.links)
|
||||
@ -2669,6 +2671,24 @@ class NetworkdRATests(unittest.TestCase, Utilities):
|
||||
print(output)
|
||||
self.assertRegex(output, '2002:da8:1:0')
|
||||
|
||||
def test_ipv6_token_eui64(self):
|
||||
copy_unit_to_networkd_unit_path('25-veth.netdev', 'ipv6-prefix.network', 'ipv6-prefix-veth-token-eui64.network')
|
||||
start_networkd()
|
||||
self.wait_online(['veth99:routable', 'veth-peer:degraded'])
|
||||
|
||||
output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth99', env=env)
|
||||
print(output)
|
||||
self.assertRegex(output, '2002:da8:1:0:1a:2b:3c:4d')
|
||||
|
||||
def test_ipv6_token_prefixstable(self):
|
||||
copy_unit_to_networkd_unit_path('25-veth.netdev', 'ipv6-prefix.network', 'ipv6-prefix-veth-token-prefixstable.network')
|
||||
start_networkd()
|
||||
self.wait_online(['veth99:routable', 'veth-peer:degraded'])
|
||||
|
||||
output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth99', env=env)
|
||||
print(output)
|
||||
self.assertRegex(output, '2002:da8:1:0')
|
||||
|
||||
class NetworkdDHCPServerTests(unittest.TestCase, Utilities):
|
||||
links = ['veth99']
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user