]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
network: Allow to specify multiple IPv6Token for SLAAC
authorSusant Sahani <ssahani@vmware.com>
Thu, 9 Jan 2020 12:19:53 +0000 (13:19 +0100)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 5 Feb 2020 08:44:42 +0000 (17:44 +0900)
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
src/network/networkd-ndisc.c
src/network/networkd-ndisc.h
src/network/networkd-network-gperf.gperf
src/network/networkd-network.c
src/network/networkd-network.h

index 9e0bf69a358fa5500b7fd3bdea5e1079dfba646e..abd7e04724ada009bbc08ec51d46248bbd0b7f34 100644 (file)
         <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>
index fb3d6f2a841ace03b792adfbf004c3042ae658f5..ae38a36975a2f1cf5fbe6fdb3bff174b51f964a4 100644 (file)
 #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;
+}
index dc0a44f523096dd5b075d358994f1a9b7ff95cf3..18d1a964769e6101bc9e5a5db7457feb5ad4645a 100644 (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);
index 80500332e6ebd49db741b04a13037e0cf7c4fb17..67971534180a76482f9368df86305adc0865aed1 100644 (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
index e7ff9ae54f391b7494806c87ece547e9c81a9cf9..1323c82cc953202090cd0164f1f2468e829cf559 100644 (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);
 }
index 1cfbd0b6b6dfaa2b8872f145149cd95a296f4733..c030f3451a2548ff8d2e4d395c2dcb8c7b32eade 100644 (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;