]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/network/networkd-ndisc.c
network: ndisc: remove old addresses and routes after at least one SLAAC address...
[thirdparty/systemd.git] / src / network / networkd-ndisc.c
index 4bdc26a38fc78ba91d88bc52edeecaeef14c05e3..0c86fa139b7375652dab7faae8905f797df89781 100644 (file)
@@ -13,6 +13,7 @@
 #include "networkd-manager.h"
 #include "networkd-ndisc.h"
 #include "networkd-route.h"
+#include "string-table.h"
 #include "string-util.h"
 #include "strv.h"
 
 
 #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. */
+static int ndisc_remove_old(Link *link, bool force);
 
-        if (memcmp(addr, &SUBNET_ROUTER_ANYCAST_ADDRESS_RFC4291, SUBNET_ROUTER_ANYCAST_PREFIXLEN) == 0)
-                return false;
+static int ndisc_address_callback(Address *address) {
+        Address *a;
+        Iterator i;
 
-        if (memcmp(addr, &RESERVED_IPV6_INTERFACE_IDENTIFIERS_ADDRESS_RFC4291, RESERVED_IPV6_INTERFACE_IDENTIFIERS_PREFIXLEN) == 0)
-                return false;
+        assert(address);
+        assert(address->link);
 
-        if (memcmp(addr, &RESERVED_SUBNET_ANYCAST_ADDRESSES_RFC4291, RESERVED_SUBNET_ANYCAST_PREFIXLEN) == 0)
-                return false;
+        /* Make this called only once */
+        SET_FOREACH(a, address->link->ndisc_addresses, i)
+                a->callback = NULL;
 
-        return true;
+        return ndisc_remove_old(address->link, 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;
+static int ndisc_remove_old(Link *link, bool force) {
+        Address *address;
+        Route *route;
+        Iterator i;
+        int k, r = 0;
 
-        /* According to rfc7217 section 5.1
-         * RID = F(Prefix, Net_Iface, Network_ID, DAD_Counter, secret_key) */
+        assert(link);
 
-        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");
+        if (!force) {
+                bool set_callback = !set_isempty(link->ndisc_addresses);
 
-        siphash24_init(&state, secret_key.bytes);
+                if (!link->ndisc_addresses_configured || !link->ndisc_routes_configured)
+                        return 0;
 
-        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);
+                SET_FOREACH(address, link->ndisc_addresses, i)
+                        if (address_is_ready(address)) {
+                                set_callback = false;
+                                break;
+                        }
 
-        rid = htole64(siphash24_finalize(&state));
+                if (set_callback) {
+                        SET_FOREACH(address, link->ndisc_addresses, i)
+                                address->callback = ndisc_address_callback;
+                        return 0;
+                }
+        }
 
-        memcpy(addr->s6_addr, prefix->s6_addr, l);
-        memcpy((uint8_t *) &addr->s6_addr + l, &rid, 16 - l);
+        if (!set_isempty(link->ndisc_addresses_old) || !set_isempty(link->ndisc_routes_old))
+                log_link_debug(link, "Removing old NDisc addresses and routes.");
 
-        return 0;
+        link_dirty(link);
+
+        SET_FOREACH(address, link->ndisc_addresses_old, i) {
+                k = address_remove(address, link, NULL);
+                if (k < 0)
+                        r = k;
+        }
+
+        SET_FOREACH(route, link->ndisc_routes_old, i) {
+                k = route_remove(route, link, NULL);
+                if (k < 0)
+                        r = k;
+        }
+
+        return r;
 }
 
-static int ndisc_netlink_route_message_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+static int ndisc_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
         int r;
 
         assert(link);
-        assert(link->ndisc_messages > 0);
+        assert(link->ndisc_routes_messages > 0);
 
-        link->ndisc_messages--;
+        link->ndisc_routes_messages--;
 
         if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
                 return 1;
 
         r = sd_netlink_message_get_errno(m);
         if (r < 0 && r != -EEXIST) {
-                log_link_message_error_errno(link, m, r, "Could not set NDisc route or address");
+                log_link_message_error_errno(link, m, r, "Could not set NDisc route");
                 link_enter_failed(link);
                 return 1;
         }
 
-        if (link->ndisc_messages == 0) {
-                link->ndisc_configured = true;
-                r = link_request_set_routes(link);
+        if (link->ndisc_routes_messages == 0) {
+                log_link_debug(link, "NDisc routes set.");
+                link->ndisc_routes_configured = true;
+
+                r = ndisc_remove_old(link, false);
                 if (r < 0) {
                         link_enter_failed(link);
                         return 1;
                 }
+
                 link_check_ready(link);
         }
 
         return 1;
 }
 
-static int ndisc_netlink_address_message_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+static int ndisc_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
         int r;
 
         assert(link);
-        assert(link->ndisc_messages > 0);
+        assert(link->ndisc_addresses_messages > 0);
 
-        link->ndisc_messages--;
+        link->ndisc_addresses_messages--;
 
         if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
                 return 1;
 
         r = sd_netlink_message_get_errno(m);
         if (r < 0 && r != -EEXIST) {
-                log_link_message_error_errno(link, m, r, "Could not set NDisc route or address");
+                log_link_message_error_errno(link, m, r, "Could not set NDisc address");
                 link_enter_failed(link);
                 return 1;
         } else if (r >= 0)
                 (void) manager_rtnl_process_address(rtnl, m, link->manager);
 
-        if (link->ndisc_messages == 0) {
-                link->ndisc_configured = true;
+        if (link->ndisc_addresses_messages == 0) {
+                log_link_debug(link, "NDisc SLAAC addresses set.");
+                link->ndisc_addresses_configured = true;
+
+                r = ndisc_remove_old(link, false);
+                if (r < 0) {
+                        link_enter_failed(link);
+                        return 1;
+                }
+
                 r = link_request_set_routes(link);
                 if (r < 0) {
                         link_enter_failed(link);
                         return 1;
                 }
-                link_check_ready(link);
         }
 
         return 1;
 }
 
+static int ndisc_route_configure(Route *route, Link *link) {
+        Route *ret;
+        int r;
+
+        assert(route);
+        assert(link);
+
+        r = route_configure(route, link, ndisc_route_handler, &ret);
+        if (r < 0)
+                return log_link_error_errno(link, r, "Failed to set NDisc route: %m");
+
+        link->ndisc_routes_messages++;
+
+        r = set_ensure_put(&link->ndisc_routes, &route_hash_ops, ret);
+        if (r < 0)
+                return log_link_error_errno(link, r, "Failed to store NDisc route: %m");
+
+        (void) set_remove(link->ndisc_routes_old, ret);
+
+        return 0;
+}
+
+static int ndisc_address_configure(Address *address, Link *link) {
+        Address *ret;
+        int r;
+
+        assert(address);
+        assert(link);
+
+        r = address_configure(address, link, ndisc_address_handler, true, &ret);
+        if (r < 0)
+                return log_link_error_errno(link, r, "Failed to set NDisc SLAAC address: %m");
+
+        link->ndisc_addresses_messages++;
+
+        r = set_ensure_put(&link->ndisc_addresses, &address_hash_ops, ret);
+        if (r < 0)
+                return log_link_error_errno(link, r, "Failed to store NDisc SLAAC address: %m");
+
+        (void) set_remove(link->ndisc_addresses_old, ret);
+
+        return 0;
+}
+
 static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) {
         _cleanup_(route_freep) Route *route = NULL;
         union in_addr_union gateway;
@@ -152,84 +223,62 @@ static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) {
         uint32_t mtu;
         usec_t time_now;
         int r;
-        Address *address;
-        Iterator i;
 
         assert(link);
         assert(rt);
 
         r = sd_ndisc_router_get_lifetime(rt, &lifetime);
         if (r < 0)
-                return log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m");
+                return log_link_error_errno(link, r, "Failed to get gateway lifetime from RA: %m");
 
         if (lifetime == 0) /* not a default router */
                 return 0;
 
         r = sd_ndisc_router_get_address(rt, &gateway.in6);
         if (r < 0)
-                return log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m");
-
-        SET_FOREACH(address, link->addresses, i) {
-                if (address->family != AF_INET6)
-                        continue;
-                if (in_addr_equal(AF_INET6, &gateway, &address->in_addr)) {
-                        _cleanup_free_ char *buffer = NULL;
+                return log_link_error_errno(link, r, "Failed to get gateway address from RA: %m");
 
-                        (void) in_addr_to_string(AF_INET6, &address->in_addr, &buffer);
-                        log_link_debug(link, "No NDisc route added, gateway %s matches local address",
-                                       strnull(buffer));
-                        return 0;
-                }
-        }
-
-        SET_FOREACH(address, link->addresses_foreign, i) {
-                if (address->family != AF_INET6)
-                        continue;
-                if (in_addr_equal(AF_INET6, &gateway, &address->in_addr)) {
+        if (address_exists(link, AF_INET6, &gateway)) {
+                if (DEBUG_LOGGING) {
                         _cleanup_free_ char *buffer = NULL;
 
-                        (void) in_addr_to_string(AF_INET6, &address->in_addr, &buffer);
+                        (void) in_addr_to_string(AF_INET6, &gateway, &buffer);
                         log_link_debug(link, "No NDisc route added, gateway %s matches local address",
                                        strnull(buffer));
-                        return 0;
                 }
+                return 0;
         }
 
         r = sd_ndisc_router_get_preference(rt, &preference);
         if (r < 0)
-                return log_link_warning_errno(link, r, "Failed to get default router preference from RA: %m");
+                return log_link_error_errno(link, r, "Failed to get default router preference from RA: %m");
 
         r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now);
         if (r < 0)
-                return log_link_warning_errno(link, r, "Failed to get RA timestamp: %m");
+                return log_link_error_errno(link, r, "Failed to get RA timestamp: %m");
 
         r = sd_ndisc_router_get_mtu(rt, &mtu);
         if (r == -ENODATA)
                 mtu = 0;
         else if (r < 0)
-                return log_link_warning_errno(link, r, "Failed to get default router MTU from RA: %m");
+                return log_link_error_errno(link, r, "Failed to get default router MTU from RA: %m");
 
         r = route_new(&route);
         if (r < 0)
-                return log_link_error_errno(link, r, "Could not allocate route: %m");
+                return log_oom();
 
         route->family = AF_INET6;
         route->table = link_get_ipv6_accept_ra_route_table(link);
-        route->priority = link->network->dhcp_route_metric;
+        route->priority = link->network->dhcp6_route_metric;
         route->protocol = RTPROT_RA;
         route->pref = preference;
         route->gw = gateway;
         route->lifetime = time_now + lifetime * USEC_PER_SEC;
         route->mtu = mtu;
 
-        r = route_configure(route, link, ndisc_netlink_route_message_handler);
-        if (r < 0) {
-                log_link_warning_errno(link, r, "Could not set default route: %m");
-                link_enter_failed(link);
-                return r;
-        }
-        if (r > 0)
-                link->ndisc_messages++;
+        r = ndisc_route_configure(route, link);
+        if (r < 0)
+                return log_link_error_errno(link, r, "Could not set default route: %m");
 
         Route *route_gw;
         LIST_FOREACH(routes, route_gw, link->network->static_routes) {
@@ -241,75 +290,155 @@ static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) {
 
                 route_gw->gw = gateway;
 
-                r = route_configure(route_gw, link, ndisc_netlink_route_message_handler);
-                if (r < 0) {
-                        log_link_error_errno(link, r, "Could not set gateway: %m");
-                        link_enter_failed(link);
-                        return r;
-                }
-                if (r > 0)
-                        link->ndisc_messages++;
+                r = ndisc_route_configure(route_gw, link);
+                if (r < 0)
+                        return log_link_error_errno(link, r, "Could not set gateway: %m");
         }
 
         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;
+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 **ret) {
+        _cleanup_free_ struct in6_addr *addr = NULL;
+        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_string(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));
+
+        addr = new(struct in6_addr, 1);
+        if (!addr)
+                return log_oom();
+
+        memcpy(addr->s6_addr, prefix->s6_addr, l);
+        memcpy(addr->s6_addr + l, &rid, 16 - l);
+
+        if (!stableprivate_address_is_valid(addr)) {
+                *ret = NULL;
+                return 0;
+        }
+
+        *ret = TAKE_PTR(addr);
+        return 1;
+}
+
+static int ndisc_router_generate_addresses(Link *link, struct in6_addr *address, uint8_t prefixlen, Set **ret) {
+        _cleanup_set_free_free_ Set *addresses = NULL;
         IPv6Token *j;
         Iterator i;
         int r;
 
-        assert(address);
         assert(link);
+        assert(address);
+        assert(ret);
+
+        addresses = set_new(&in6_addr_hash_ops);
+        if (!addresses)
+                return log_oom();
+
+        ORDERED_SET_FOREACH(j, link->network->ipv6_tokens, i) {
+                _cleanup_free_ struct in6_addr *new_address = NULL;
 
-        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) {
+                    && IN6_ARE_ADDR_EQUAL(&j->prefix, address)) {
+                        /* 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 (; 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);
+                                r = make_stableprivate_address(link, &j->prefix, prefixlen, j->dad_counter, &new_address);
                                 if (r < 0)
                                         return r;
-
-                                if (stableprivate_address_is_valid(&address->in_addr.in6)) {
-                                        prefix = true;
+                                if (r > 0)
                                         break;
-                                }
                         }
                 } else if (j->address_generation_type == IPV6_TOKEN_ADDRESS_GENERATION_STATIC) {
-                        memcpy(((uint8_t *)&address->in_addr.in6) + 8, ((uint8_t *) &j->prefix) + 8, 8);
-                        prefix = true;
-                        break;
+                        new_address = new(struct in6_addr, 1);
+                        if (!new_address)
+                                return log_oom();
+
+                        memcpy(new_address->s6_addr, address->s6_addr, 8);
+                        memcpy(new_address->s6_addr + 8, j->prefix.s6_addr + 8, 8);
                 }
 
-        /* fallback to eui64 if prefixstable or static 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];
+                if (new_address) {
+                        r = set_put(addresses, new_address);
+                        if (r < 0)
+                                return log_link_error_errno(link, r, "Failed to store SLAAC address: %m");
+                        else if (r == 0)
+                                log_link_debug_errno(link, r, "Generated SLAAC address is duplicated, ignoring.");
+                        else
+                                TAKE_PTR(new_address);
+                }
         }
 
-        address->prefixlen = prefixlen;
-        address->flags = IFA_F_NOPREFIXROUTE|IFA_F_MANAGETEMPADDR;
-        address->cinfo.ifa_prefered = lifetime_preferred;
+        /* fall back to EUI-64 if no tokens provided addresses */
+        if (set_isempty(addresses)) {
+                _cleanup_free_ struct in6_addr *new_address = NULL;
+
+                new_address = newdup(struct in6_addr, address, 1);
+                if (!new_address)
+                        return log_oom();
+
+                r = generate_ipv6_eui_64_address(link, new_address);
+                if (r < 0)
+                        return log_link_error_errno(link, r, "Failed to generate EUI64 address: %m");
+
+                r = set_put(addresses, new_address);
+                if (r < 0)
+                        return log_link_error_errno(link, r, "Failed to store SLAAC address: %m");
+
+                TAKE_PTR(new_address);
+        }
+
+        *ret = TAKE_PTR(addresses);
 
         return 0;
 }
+
 static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *rt) {
         uint32_t lifetime_valid, lifetime_preferred, lifetime_remaining;
+        _cleanup_set_free_free_ Set *addresses = NULL;
         _cleanup_(address_freep) Address *address = NULL;
-        Address *existing_address;
+        struct in6_addr addr, *a;
         unsigned prefixlen;
         usec_t time_now;
+        Iterator i;
         int r;
 
         assert(link);
@@ -317,7 +446,7 @@ static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *r
 
         r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now);
         if (r < 0)
-                return log_link_warning_errno(link, r, "Failed to get RA timestamp: %m");
+                return log_link_error_errno(link, r, "Failed to get RA timestamp: %m");
 
         r = sd_ndisc_router_prefix_get_prefixlen(rt, &prefixlen);
         if (r < 0)
@@ -335,45 +464,50 @@ static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *r
         if (lifetime_preferred > lifetime_valid)
                 return 0;
 
-        r = address_new(&address);
+        r = sd_ndisc_router_prefix_get_address(rt, &addr);
         if (r < 0)
-                return log_link_error_errno(link, r, "Could not allocate address: %m");
+                return log_link_error_errno(link, r, "Failed to get prefix address: %m");
 
-        address->family = AF_INET6;
-        r = sd_ndisc_router_prefix_get_address(rt, &address->in_addr.in6);
+        r = ndisc_router_generate_addresses(link, &addr, prefixlen, &addresses);
         if (r < 0)
-                return log_link_error_errno(link, r, "Failed to get prefix address: %m");
+                return r;
 
-        r = ndisc_router_generate_address(link, prefixlen, lifetime_preferred, address);
+        r = address_new(&address);
         if (r < 0)
-                return log_link_error_errno(link, r, "Falied to generate prefix stable address: %m");
+                return log_oom();
 
-        /* see RFC4862 section 5.5.3.e */
-        r = address_get(link, address->family, &address->in_addr, address->prefixlen, &existing_address);
-        if (r > 0) {
-                lifetime_remaining = existing_address->cinfo.tstamp / 100 + existing_address->cinfo.ifa_valid - time_now / USEC_PER_SEC;
-                if (lifetime_valid > NDISC_PREFIX_LFT_MIN || lifetime_valid > lifetime_remaining)
+        address->family = AF_INET6;
+        address->prefixlen = prefixlen;
+        address->flags = IFA_F_NOPREFIXROUTE|IFA_F_MANAGETEMPADDR;
+        address->cinfo.ifa_prefered = lifetime_preferred;
+
+        SET_FOREACH(a, addresses, i) {
+                Address *existing_address;
+
+                /* see RFC4862 section 5.5.3.e */
+                r = address_get(link, AF_INET6, (union in_addr_union *) a, prefixlen, &existing_address);
+                if (r > 0) {
+                        lifetime_remaining = existing_address->cinfo.tstamp / 100 + existing_address->cinfo.ifa_valid - time_now / USEC_PER_SEC;
+                        if (lifetime_valid > NDISC_PREFIX_LFT_MIN || lifetime_valid > lifetime_remaining)
+                                address->cinfo.ifa_valid = lifetime_valid;
+                        else if (lifetime_remaining <= NDISC_PREFIX_LFT_MIN)
+                                address->cinfo.ifa_valid = lifetime_remaining;
+                        else
+                                address->cinfo.ifa_valid = NDISC_PREFIX_LFT_MIN;
+                } else if (lifetime_valid > 0)
                         address->cinfo.ifa_valid = lifetime_valid;
-                else if (lifetime_remaining <= NDISC_PREFIX_LFT_MIN)
-                        address->cinfo.ifa_valid = lifetime_remaining;
                 else
-                        address->cinfo.ifa_valid = NDISC_PREFIX_LFT_MIN;
-        } else if (lifetime_valid > 0)
-                address->cinfo.ifa_valid = lifetime_valid;
-        else
-                return 0; /* see RFC4862 section 5.5.3.d */
+                        continue; /* see RFC4862 section 5.5.3.d */
 
-        if (address->cinfo.ifa_valid == 0)
-                return 0;
+                if (address->cinfo.ifa_valid == 0)
+                        continue;
 
-        r = address_configure(address, link, ndisc_netlink_address_message_handler, true);
-        if (r < 0) {
-                log_link_warning_errno(link, r, "Could not set SLAAC address: %m");
-                link_enter_failed(link);
-                return r;
+                address->in_addr.in6 = *a;
+
+                r = ndisc_address_configure(address, link);
+                if (r < 0)
+                        return log_link_error_errno(link, r, "Could not set SLAAC address: %m");
         }
-        if (r > 0)
-                link->ndisc_messages++;
 
         return 0;
 }
@@ -390,7 +524,7 @@ static int ndisc_router_process_onlink_prefix(Link *link, sd_ndisc_router *rt) {
 
         r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now);
         if (r < 0)
-                return log_link_warning_errno(link, r, "Failed to get RA timestamp: %m");
+                return log_link_error_errno(link, r, "Failed to get RA timestamp: %m");
 
         r = sd_ndisc_router_prefix_get_prefixlen(rt, &prefixlen);
         if (r < 0)
@@ -402,11 +536,11 @@ static int ndisc_router_process_onlink_prefix(Link *link, sd_ndisc_router *rt) {
 
         r = route_new(&route);
         if (r < 0)
-                return log_link_error_errno(link, r, "Could not allocate route: %m");
+                return log_oom();
 
         route->family = AF_INET6;
         route->table = link_get_ipv6_accept_ra_route_table(link);
-        route->priority = link->network->dhcp_route_metric;
+        route->priority = link->network->dhcp6_route_metric;
         route->protocol = RTPROT_RA;
         route->flags = RTM_F_PREFIX;
         route->dst_prefixlen = prefixlen;
@@ -416,14 +550,9 @@ static int ndisc_router_process_onlink_prefix(Link *link, sd_ndisc_router *rt) {
         if (r < 0)
                 return log_link_error_errno(link, r, "Failed to get prefix address: %m");
 
-        r = route_configure(route, link, ndisc_netlink_route_message_handler);
-        if (r < 0) {
-                log_link_warning_errno(link, r, "Could not set prefix route: %m");
-                link_enter_failed(link);
-                return r;
-        }
-        if (r > 0)
-                link->ndisc_messages++;
+        r = ndisc_route_configure(route, link);
+        if (r < 0)
+                return log_link_error_errno(link, r, "Could not set prefix route: %m");;
 
         return 0;
 }
@@ -440,33 +569,34 @@ static int ndisc_router_process_route(Link *link, sd_ndisc_router *rt) {
 
         r = sd_ndisc_router_route_get_lifetime(rt, &lifetime);
         if (r < 0)
-                return log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m");
+                return log_link_error_errno(link, r, "Failed to get gateway lifetime from RA: %m");
 
         if (lifetime == 0)
                 return 0;
 
         r = sd_ndisc_router_get_address(rt, &gateway);
         if (r < 0)
-                return log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m");
+                return log_link_error_errno(link, r, "Failed to get gateway address from RA: %m");
 
         r = sd_ndisc_router_route_get_prefixlen(rt, &prefixlen);
         if (r < 0)
-                return log_link_warning_errno(link, r, "Failed to get route prefix length: %m");
+                return log_link_error_errno(link, r, "Failed to get route prefix length: %m");
 
         r = sd_ndisc_router_route_get_preference(rt, &preference);
         if (r < 0)
-                return log_link_warning_errno(link, r, "Failed to get default router preference from RA: %m");
+                return log_link_error_errno(link, r, "Failed to get default router preference from RA: %m");
 
         r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now);
         if (r < 0)
-                return log_link_warning_errno(link, r, "Failed to get RA timestamp: %m");
+                return log_link_error_errno(link, r, "Failed to get RA timestamp: %m");
 
         r = route_new(&route);
         if (r < 0)
-                return log_link_error_errno(link, r, "Could not allocate route: %m");
+                return log_oom();
 
         route->family = AF_INET6;
         route->table = link_get_ipv6_accept_ra_route_table(link);
+        route->priority = link->network->dhcp6_route_metric;
         route->protocol = RTPROT_RA;
         route->pref = preference;
         route->gw.in6 = gateway;
@@ -477,14 +607,9 @@ static int ndisc_router_process_route(Link *link, sd_ndisc_router *rt) {
         if (r < 0)
                 return log_link_error_errno(link, r, "Failed to get route address: %m");
 
-        r = route_configure(route, link, ndisc_netlink_route_message_handler);
-        if (r < 0) {
-                log_link_warning_errno(link, r, "Could not set additional route: %m");
-                link_enter_failed(link);
-                return r;
-        }
-        if (r > 0)
-                link->ndisc_messages++;
+        r = ndisc_route_configure(route, link);
+        if (r < 0)
+                return log_link_error_errno(link, r, "Could not set additional route: %m");
 
         return 0;
 }
@@ -503,24 +628,24 @@ static int ndisc_router_process_rdnss(Link *link, sd_ndisc_router *rt) {
         uint32_t lifetime;
         const struct in6_addr *a;
         usec_t time_now;
-        int i, n, r;
+        int n, r;
 
         assert(link);
         assert(rt);
 
         r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now);
         if (r < 0)
-                return log_link_warning_errno(link, r, "Failed to get RA timestamp: %m");
+                return log_link_error_errno(link, r, "Failed to get RA timestamp: %m");
 
         r = sd_ndisc_router_rdnss_get_lifetime(rt, &lifetime);
         if (r < 0)
-                return log_link_warning_errno(link, r, "Failed to get RDNSS lifetime: %m");
+                return log_link_error_errno(link, r, "Failed to get RDNSS lifetime: %m");
 
         n = sd_ndisc_router_rdnss_get_addresses(rt, &a);
         if (n < 0)
-                return log_link_warning_errno(link, n, "Failed to get RDNSS addresses: %m");
+                return log_link_error_errno(link, n, "Failed to get RDNSS addresses: %m");
 
-        for (i = 0; i < n; i++) {
+        for (int i = 0; i < n; i++) {
                 _cleanup_free_ NDiscRDNSS *x = NULL;
                 NDiscRDNSS d = {
                         .address = a[i],
@@ -545,10 +670,6 @@ static int ndisc_router_process_rdnss(Link *link, sd_ndisc_router *rt) {
                         continue;
                 }
 
-                r = set_ensure_allocated(&link->ndisc_rdnss, &ndisc_rdnss_hash_ops);
-                if (r < 0)
-                        return log_oom();
-
                 x = new(NDiscRDNSS, 1);
                 if (!x)
                         return log_oom();
@@ -558,13 +679,11 @@ static int ndisc_router_process_rdnss(Link *link, sd_ndisc_router *rt) {
                         .valid_until = time_now + lifetime * USEC_PER_SEC,
                 };
 
-                r = set_put(link->ndisc_rdnss, x);
+                r = set_ensure_consume(&link->ndisc_rdnss, &ndisc_rdnss_hash_ops, TAKE_PTR(x));
                 if (r < 0)
                         return log_oom();
-
-                TAKE_PTR(x);
-
                 assert(r > 0);
+
                 link_dirty(link);
         }
 
@@ -572,7 +691,7 @@ static int ndisc_router_process_rdnss(Link *link, sd_ndisc_router *rt) {
 }
 
 static void ndisc_dnssl_hash_func(const NDiscDNSSL *x, struct siphash *state) {
-        siphash24_compress(NDISC_DNSSL_DOMAIN(x), strlen(NDISC_DNSSL_DOMAIN(x)), state);
+        siphash24_compress_string(NDISC_DNSSL_DOMAIN(x), state);
 }
 
 static int ndisc_dnssl_compare_func(const NDiscDNSSL *a, const NDiscDNSSL *b) {
@@ -581,7 +700,7 @@ static int ndisc_dnssl_compare_func(const NDiscDNSSL *a, const NDiscDNSSL *b) {
 
 DEFINE_PRIVATE_HASH_OPS(ndisc_dnssl_hash_ops, NDiscDNSSL, ndisc_dnssl_hash_func, ndisc_dnssl_compare_func);
 
-static void ndisc_router_process_dnssl(Link *link, sd_ndisc_router *rt) {
+static int ndisc_router_process_dnssl(Link *link, sd_ndisc_router *rt) {
         _cleanup_strv_free_ char **l = NULL;
         uint32_t lifetime;
         usec_t time_now;
@@ -592,32 +711,24 @@ static void ndisc_router_process_dnssl(Link *link, sd_ndisc_router *rt) {
         assert(rt);
 
         r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now);
-        if (r < 0) {
-                log_link_warning_errno(link, r, "Failed to get RA timestamp: %m");
-                return;
-        }
+        if (r < 0)
+                return log_link_error_errno(link, r, "Failed to get RA timestamp: %m");
 
         r = sd_ndisc_router_dnssl_get_lifetime(rt, &lifetime);
-        if (r < 0) {
-                log_link_warning_errno(link, r, "Failed to get RDNSS lifetime: %m");
-                return;
-        }
+        if (r < 0)
+                return log_link_error_errno(link, r, "Failed to get DNSSL lifetime: %m");
 
         r = sd_ndisc_router_dnssl_get_domains(rt, &l);
-        if (r < 0) {
-                log_link_warning_errno(link, r, "Failed to get RDNSS addresses: %m");
-                return;
-        }
+        if (r < 0)
+                return log_link_error_errno(link, r, "Failed to get DNSSL addresses: %m");
 
         STRV_FOREACH(i, l) {
-                _cleanup_free_ NDiscDNSSL *s;
+                _cleanup_free_ NDiscDNSSL *s = NULL;
                 NDiscDNSSL *x;
 
                 s = malloc0(ALIGN(sizeof(NDiscDNSSL)) + strlen(*i) + 1);
-                if (!s) {
-                        log_oom();
-                        return;
-                }
+                if (!s)
+                        return log_oom();
 
                 strcpy(NDISC_DNSSL_DOMAIN(s), *i);
 
@@ -640,45 +751,35 @@ static void ndisc_router_process_dnssl(Link *link, sd_ndisc_router *rt) {
                         continue;
                 }
 
-                r = set_ensure_allocated(&link->ndisc_dnssl, &ndisc_dnssl_hash_ops);
-                if (r < 0) {
-                        log_oom();
-                        return;
-                }
-
                 s->valid_until = time_now + lifetime * USEC_PER_SEC;
 
-                r = set_put(link->ndisc_dnssl, s);
-                if (r < 0) {
-                        log_oom();
-                        return;
-                }
-
-                s = NULL;
+                r = set_ensure_consume(&link->ndisc_dnssl, &ndisc_dnssl_hash_ops, TAKE_PTR(s));
+                if (r < 0)
+                        return log_oom();
                 assert(r > 0);
+
                 link_dirty(link);
         }
+
+        return 0;
 }
 
 static int ndisc_router_process_options(Link *link, sd_ndisc_router *rt) {
-        int r;
-
         assert(link);
         assert(link->network);
         assert(rt);
 
-        r = sd_ndisc_router_option_rewind(rt);
-        for (;;) {
+        for (int r = sd_ndisc_router_option_rewind(rt); ; r = sd_ndisc_router_option_next(rt)) {
                 uint8_t type;
 
                 if (r < 0)
-                        return log_link_warning_errno(link, r, "Failed to iterate through options: %m");
+                        return log_link_error_errno(link, r, "Failed to iterate through options: %m");
                 if (r == 0) /* EOF */
-                        break;
+                        return 0;
 
                 r = sd_ndisc_router_option_get_type(rt, &type);
                 if (r < 0)
-                        return log_link_warning_errno(link, r, "Failed to get RA option type: %m");
+                        return log_link_error_errno(link, r, "Failed to get RA option type: %m");
 
                 switch (type) {
 
@@ -690,54 +791,64 @@ static int ndisc_router_process_options(Link *link, sd_ndisc_router *rt) {
                         if (r < 0)
                                 return log_link_error_errno(link, r, "Failed to get prefix address: %m");
 
-                        if (set_contains(link->network->ndisc_black_listed_prefix, &a.in6)) {
+                        if (set_contains(link->network->ndisc_deny_listed_prefix, &a.in6)) {
                                 if (DEBUG_LOGGING) {
                                         _cleanup_free_ char *b = NULL;
 
                                         (void) in_addr_to_string(AF_INET6, &a, &b);
-                                        log_link_debug(link, "Prefix '%s' is black listed, ignoring", strna(b));
+                                        log_link_debug(link, "Prefix '%s' is deny-listed, ignoring", strna(b));
                                 }
-
                                 break;
                         }
 
                         r = sd_ndisc_router_prefix_get_flags(rt, &flags);
                         if (r < 0)
-                                return log_link_warning_errno(link, r, "Failed to get RA prefix flags: %m");
+                                return log_link_error_errno(link, r, "Failed to get RA prefix flags: %m");
 
                         if (link->network->ipv6_accept_ra_use_onlink_prefix &&
-                            FLAGS_SET(flags, ND_OPT_PI_FLAG_ONLINK))
-                                (void) ndisc_router_process_onlink_prefix(link, rt);
+                            FLAGS_SET(flags, ND_OPT_PI_FLAG_ONLINK)) {
+                                r = ndisc_router_process_onlink_prefix(link, rt);
+                                if (r < 0)
+                                        return r;
+                        }
 
                         if (link->network->ipv6_accept_ra_use_autonomous_prefix &&
-                            FLAGS_SET(flags, ND_OPT_PI_FLAG_AUTO))
-                                (void) ndisc_router_process_autonomous_prefix(link, rt);
-
+                            FLAGS_SET(flags, ND_OPT_PI_FLAG_AUTO)) {
+                                r = ndisc_router_process_autonomous_prefix(link, rt);
+                                if (r < 0)
+                                        return r;
+                        }
                         break;
                 }
 
                 case SD_NDISC_OPTION_ROUTE_INFORMATION:
-                        (void) ndisc_router_process_route(link, rt);
+                        r = ndisc_router_process_route(link, rt);
+                        if (r < 0)
+                                return r;
                         break;
 
                 case SD_NDISC_OPTION_RDNSS:
-                        if (link->network->ipv6_accept_ra_use_dns)
-                                (void) ndisc_router_process_rdnss(link, rt);
+                        if (link->network->ipv6_accept_ra_use_dns) {
+                                r = ndisc_router_process_rdnss(link, rt);
+                                if (r < 0)
+                                        return r;
+                        }
                         break;
 
                 case SD_NDISC_OPTION_DNSSL:
-                        if (link->network->ipv6_accept_ra_use_dns)
-                                (void) ndisc_router_process_dnssl(link, rt);
+                        if (link->network->ipv6_accept_ra_use_dns) {
+                                r = ndisc_router_process_dnssl(link, rt);
+                                if (r < 0)
+                                        return r;
+                        }
                         break;
                 }
-
-                r = sd_ndisc_router_option_next(rt);
         }
-
-        return 0;
 }
 
 static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) {
+        Address *address;
+        Route *route;
         uint64_t flags;
         int r;
 
@@ -746,29 +857,79 @@ static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) {
         assert(link->manager);
         assert(rt);
 
+        link->ndisc_addresses_configured = false;
+        link->ndisc_routes_configured = false;
+
+        link_dirty(link);
+
+        while ((address = set_steal_first(link->ndisc_addresses))) {
+                r = set_ensure_put(&link->ndisc_addresses_old, &address_hash_ops, address);
+                if (r < 0)
+                        return log_link_error_errno(link, r, "Failed to store old NDisc SLAAC address: %m");
+        }
+
+        while ((route = set_steal_first(link->ndisc_routes))) {
+                r = set_ensure_put(&link->ndisc_routes_old, &route_hash_ops, route);
+                if (r < 0)
+                        return log_link_error_errno(link, r, "Failed to store old NDisc route: %m");
+        }
+
         r = sd_ndisc_router_get_flags(rt, &flags);
         if (r < 0)
-                return log_link_warning_errno(link, r, "Failed to get RA flags: %m");
+                return log_link_error_errno(link, r, "Failed to get RA flags: %m");
 
-        if (flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER)) {
-                /* (re)start DHCPv6 client in stateful or stateless mode according to RA flags */
-                r = dhcp6_request_address(link, !(flags & ND_RA_FLAG_MANAGED));
+        if ((flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER) && link->network->ipv6_accept_ra_start_dhcp6_client)) {
+
+                if (link->network->ipv6_accept_ra_start_dhcp6_client == IPV6_ACCEPT_RA_START_DHCP6_CLIENT_ALWAYS)
+                        r = dhcp6_request_address(link, false);
+                else
+                        /* (re)start DHCPv6 client in stateful or stateless mode according to RA flags */
+                        r = dhcp6_request_address(link, !(flags & ND_RA_FLAG_MANAGED));
                 if (r < 0 && r != -EBUSY)
-                        log_link_warning_errno(link, r, "Could not acquire DHCPv6 lease on NDisc request: %m");
-                else {
+                        return log_link_error_errno(link, r, "Could not acquire DHCPv6 lease on NDisc request: %m");
+                else
                         log_link_debug(link, "Acquiring DHCPv6 lease on NDisc request");
-                        r = 0;
-                }
         }
 
-        (void) ndisc_router_process_default(link, rt);
-        (void) ndisc_router_process_options(link, rt);
+        r = ndisc_router_process_default(link, rt);
+        if (r < 0)
+                return r;
+        r = ndisc_router_process_options(link, rt);
+        if (r < 0)
+                return r;
 
-        return r;
+        if (link->ndisc_addresses_messages == 0)
+                link->ndisc_addresses_configured = true;
+        else {
+                log_link_debug(link, "Setting SLAAC addresses.");
+
+                /* address_handler calls link_request_set_routes() and link_request_set_nexthop().
+                 * Before they are called, the related flags must be cleared. Otherwise, the link
+                 * becomes configured state before routes are configured. */
+                link->static_routes_configured = false;
+                link->static_nexthops_configured = false;
+        }
+
+        if (link->ndisc_routes_messages == 0)
+                link->ndisc_routes_configured = true;
+        else
+                log_link_debug(link, "Setting NDisc routes.");
+
+        r = ndisc_remove_old(link, false);
+        if (r < 0)
+                return r;
+
+        if (link->ndisc_addresses_configured && link->ndisc_routes_configured)
+                link_check_ready(link);
+        else
+                link_set_state(link, LINK_STATE_CONFIGURING);
+
+        return 0;
 }
 
 static void ndisc_handler(sd_ndisc *nd, sd_ndisc_event event, sd_ndisc_router *rt, void *userdata) {
         Link *link = userdata;
+        int r;
 
         assert(link);
 
@@ -778,16 +939,23 @@ static void ndisc_handler(sd_ndisc *nd, sd_ndisc_event event, sd_ndisc_router *r
         switch (event) {
 
         case SD_NDISC_EVENT_ROUTER:
-                (void) ndisc_router_handler(link, rt);
+                r = ndisc_router_handler(link, rt);
+                if (r < 0) {
+                        link_enter_failed(link);
+                        return;
+                }
                 break;
 
         case SD_NDISC_EVENT_TIMEOUT:
-                link->ndisc_configured = true;
-                link_check_ready(link);
-
+                log_link_debug(link, "NDisc handler get timeout event");
+                if (link->ndisc_addresses_messages == 0 && link->ndisc_routes_messages == 0) {
+                        link->ndisc_addresses_configured = true;
+                        link->ndisc_routes_configured = true;
+                        link_check_ready(link);
+                }
                 break;
         default:
-                log_link_warning(link, "IPv6 Neighbor Discovery unknown event: %d", event);
+                assert_not_reached("Unknown NDisc event");
         }
 }
 
@@ -869,15 +1037,29 @@ int ipv6token_new(IPv6Token **ret) {
         return 0;
 }
 
-DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
+static void ipv6_token_hash_func(const IPv6Token *p, struct siphash *state) {
+        siphash24_compress(&p->address_generation_type, sizeof(p->address_generation_type), state);
+        siphash24_compress(&p->prefix, sizeof(p->prefix), state);
+}
+
+static int ipv6_token_compare_func(const IPv6Token *a, const IPv6Token *b) {
+        int r;
+
+        r = CMP(a->address_generation_type, b->address_generation_type);
+        if (r != 0)
+                return r;
+
+        return memcmp(&a->prefix, &b->prefix, sizeof(struct in6_addr));
+}
+
+DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(
                 ipv6_token_hash_ops,
-                void,
-                trivial_hash_func,
-                trivial_compare_func,
                 IPv6Token,
+                ipv6_token_hash_func,
+                ipv6_token_compare_func,
                 free);
 
-int config_parse_ndisc_black_listed_prefix(
+int config_parse_ndisc_deny_listed_prefix(
                 const char *unit,
                 const char *filename,
                 unsigned line,
@@ -899,7 +1081,7 @@ int config_parse_ndisc_black_listed_prefix(
         assert(data);
 
         if (isempty(rvalue)) {
-                network->ndisc_black_listed_prefix = set_free_free(network->ndisc_black_listed_prefix);
+                network->ndisc_deny_listed_prefix = set_free_free(network->ndisc_deny_listed_prefix);
                 return 0;
         }
 
@@ -909,9 +1091,11 @@ int config_parse_ndisc_black_listed_prefix(
                 union in_addr_union ip;
 
                 r = extract_first_word(&p, &n, NULL, 0);
+                if (r == -ENOMEM)
+                        return log_oom();
                 if (r < 0) {
-                        log_syntax(unit, LOG_ERR, filename, line, r,
-                                   "Failed to parse NDISC black listed prefix, ignoring assignment: %s",
+                        log_syntax(unit, LOG_WARNING, filename, line, r,
+                                   "Failed to parse NDisc deny-listed prefix, ignoring assignment: %s",
                                    rvalue);
                         return 0;
                 }
@@ -920,33 +1104,22 @@ int config_parse_ndisc_black_listed_prefix(
 
                 r = in_addr_from_string(AF_INET6, n, &ip);
                 if (r < 0) {
-                        log_syntax(unit, LOG_ERR, filename, line, r,
-                                   "NDISC black listed prefix is invalid, ignoring assignment: %s", n);
+                        log_syntax(unit, LOG_WARNING, filename, line, r,
+                                   "NDisc deny-listed prefix is invalid, ignoring assignment: %s", n);
                         continue;
                 }
 
-                if (set_contains(network->ndisc_black_listed_prefix, &ip.in6))
+                if (set_contains(network->ndisc_deny_listed_prefix, &ip.in6))
                         continue;
 
-                r = set_ensure_allocated(&network->ndisc_black_listed_prefix, &in6_addr_hash_ops);
-                if (r < 0)
-                        return log_oom();
-
                 a = newdup(struct in6_addr, &ip.in6, 1);
                 if (!a)
                         return log_oom();
 
-                r = set_put(network->ndisc_black_listed_prefix, a);
-                if (r < 0) {
-                        log_syntax(unit, LOG_ERR, filename, line, r,
-                                   "Failed to store NDISC black listed prefix '%s', ignoring assignment: %m", n);
-                        continue;
-                }
-
-                TAKE_PTR(a);
+                r = set_ensure_consume(&network->ndisc_deny_listed_prefix, &in6_addr_hash_ops, TAKE_PTR(a));
+                if (r < 0)
+                        return log_oom();
         }
-
-        return 0;
 }
 
 int config_parse_address_generation_type(
@@ -962,7 +1135,6 @@ int config_parse_address_generation_type(
                 void *userdata) {
 
         _cleanup_free_ IPv6Token *token = NULL;
-        _cleanup_free_ char *word = NULL;
         union in_addr_union buffer;
         Network *network = data;
         const char *p;
@@ -974,17 +1146,7 @@ int config_parse_address_generation_type(
         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);
+                network->ipv6_tokens = ordered_set_free(network->ipv6_tokens);
                 return 0;
         }
 
@@ -992,48 +1154,53 @@ int config_parse_address_generation_type(
         if (r < 0)
                 return log_oom();
 
-        if (streq(word, "static"))
+        if ((p = startswith(rvalue, "static:")))
                 token->address_generation_type = IPV6_TOKEN_ADDRESS_GENERATION_STATIC;
-        else if (streq(word, "prefixstable"))
+        else if ((p = startswith(rvalue, "prefixstable:")))
                 token->address_generation_type = IPV6_TOKEN_ADDRESS_GENERATION_PREFIXSTABLE;
         else {
                 token->address_generation_type = IPV6_TOKEN_ADDRESS_GENERATION_STATIC;
                 p = rvalue;
         }
 
-        if (isempty(p)) {
-                log_syntax(unit, LOG_ERR, filename, line, 0,
-                           "Invalid IPv6Token= , ignoring assignment: %s", rvalue);
-                return 0;
-        }
-
         r = in_addr_from_string(AF_INET6, p, &buffer);
         if (r < 0) {
-                log_syntax(unit, LOG_ERR, filename, line, r,
+                log_syntax(unit, LOG_WARNING, 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,
+                log_syntax(unit, LOG_WARNING, 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);
+        r = ordered_set_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);
+        r = ordered_set_put(network->ipv6_tokens, token);
+        if (r == -EEXIST)
+                log_syntax(unit, LOG_DEBUG, filename, line, r,
+                           "IPv6 token '%s' is duplicated, ignoring: %m", rvalue);
+        else if (r < 0)
+                log_syntax(unit, LOG_WARNING, filename, line, r,
+                           "Failed to store IPv6 token '%s', ignoring: %m", rvalue);
+        else
+                TAKE_PTR(token);
 
         return 0;
 }
+
+DEFINE_CONFIG_PARSE_ENUM(config_parse_ipv6_accept_ra_start_dhcp6_client, ipv6_accept_ra_start_dhcp6_client, IPv6AcceptRAStartDHCP6Client,
+                         "Failed to parse DHCPv6Client= setting")
+static const char* const ipv6_accept_ra_start_dhcp6_client_table[_IPV6_ACCEPT_RA_START_DHCP6_CLIENT_MAX] = {
+        [IPV6_ACCEPT_RA_START_DHCP6_CLIENT_NO]     = "no",
+        [IPV6_ACCEPT_RA_START_DHCP6_CLIENT_ALWAYS] = "always",
+        [IPV6_ACCEPT_RA_START_DHCP6_CLIENT_YES]    = "yes",
+};
+
+DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(ipv6_accept_ra_start_dhcp6_client, IPv6AcceptRAStartDHCP6Client, IPV6_ACCEPT_RA_START_DHCP6_CLIENT_YES);