]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/network/networkd-ndisc.c
network: make address/route_configure optionally return created Address/Route object
[thirdparty/systemd.git] / src / network / networkd-ndisc.c
index 563901e33bd80a98dd12a10b2daf7499a645d14f..2f6cc4042e1901a7e8089d56fe2646540de33737 100644 (file)
 
 #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(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) {
+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 (r < 0) {
-                        link_enter_failed(link);
-                        return 1;
-                }
+        if (link->ndisc_routes_messages == 0) {
+                log_link_debug(link, "NDisc routes set.");
+                link->ndisc_routes_configured = true;
                 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 = link_request_set_routes(link);
                 if (r < 0) {
                         link_enter_failed(link);
                         return 1;
                 }
-                link_check_ready(link);
         }
 
         return 1;
@@ -153,66 +102,49 @@ 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");
+                return log_link_error_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)) {
+        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;
-                }
-        }
-
-        SET_FOREACH(address, link->addresses_foreign, i) {
-                if (address->family != AF_INET6)
-                        continue;
-                if (in_addr_equal(AF_INET6, &gateway, &address->in_addr)) {
-                        _cleanup_free_ char *buffer = NULL;
-
-                        (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;
                 }
+                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);
@@ -223,14 +155,11 @@ static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) {
         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;
-        }
+        r = route_configure(route, link, ndisc_route_handler, NULL);
+        if (r < 0)
+                return log_link_error_errno(link, r, "Could not set default route: %m");
         if (r > 0)
-                link->ndisc_messages++;
+                link->ndisc_routes_messages++;
 
         Route *route_gw;
         LIST_FOREACH(routes, route_gw, link->network->static_routes) {
@@ -242,22 +171,76 @@ 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;
-                }
+                r = route_configure(route_gw, link, ndisc_route_handler, NULL);
+                if (r < 0)
+                        return log_link_error_errno(link, r, "Could not set gateway: %m");
                 if (r > 0)
-                        link->ndisc_messages++;
+                        link->ndisc_routes_messages++;
         }
 
         return 0;
 }
 
-static int ndisc_router_generate_addresses(Link *link, unsigned prefixlen, uint32_t lifetime_preferred, Address *address, Set **ret) {
+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;
-        struct in6_addr addr;
         IPv6Token *j;
         Iterator i;
         int r;
@@ -266,76 +249,63 @@ static int ndisc_router_generate_addresses(Link *link, unsigned prefixlen, uint3
         assert(address);
         assert(ret);
 
-        addresses = set_new(&address_hash_ops);
+        addresses = set_new(&in6_addr_hash_ops);
         if (!addresses)
                 return log_oom();
 
-        addr = address->in_addr.in6;
-        ORDERED_HASHMAP_FOREACH(j, link->network->ipv6_tokens, i) {
-                bool have_address = false;
-                _cleanup_(address_freep) Address *new_address = NULL;
-
-                r = address_new(&new_address);
-                if (r < 0)
-                        return log_oom();
-
-                *new_address = *address;
+        ORDERED_SET_FOREACH(j, link->network->ipv6_tokens, i) {
+                _cleanup_free_ struct in6_addr *new_address = NULL;
 
                 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, &new_address->in_addr.in6);
+                                r = make_stableprivate_address(link, &j->prefix, prefixlen, j->dad_counter, &new_address);
                                 if (r < 0)
+                                        return r;
+                                if (r > 0)
                                         break;
-
-                                if (stableprivate_address_is_valid(&new_address->in_addr.in6)) {
-                                        have_address = true;
-                                        break;
-                                }
                         }
                 } else if (j->address_generation_type == IPV6_TOKEN_ADDRESS_GENERATION_STATIC) {
-                        memcpy(new_address->in_addr.in6.s6_addr + 8, j->prefix.s6_addr + 8, 8);
-                        have_address = true;
-                }
+                        new_address = new(struct in6_addr, 1);
+                        if (!new_address)
+                                return log_oom();
 
-                if (have_address) {
-                        new_address->prefixlen = prefixlen;
-                        new_address->flags = IFA_F_NOPREFIXROUTE|IFA_F_MANAGETEMPADDR;
-                        new_address->cinfo.ifa_prefered = lifetime_preferred;
+                        memcpy(new_address->s6_addr, address->s6_addr, 8);
+                        memcpy(new_address->s6_addr + 8, j->prefix.s6_addr + 8, 8);
+                }
 
+                if (new_address) {
                         r = set_put(addresses, new_address);
                         if (r < 0)
-                                return log_link_warning_errno(link, r, "Failed to store address: %m");
-                        TAKE_PTR(new_address);
+                                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);
                 }
         }
 
         /* fall back to EUI-64 if no tokens provided addresses */
         if (set_isempty(addresses)) {
-                _cleanup_(address_freep) Address *new_address = NULL;
+                _cleanup_free_ struct in6_addr *new_address = NULL;
 
-                r = address_new(&new_address);
-                if (r < 0)
+                new_address = newdup(struct in6_addr, address, 1);
+                if (!new_address)
                         return log_oom();
 
-                *new_address = *address;
-
-                r = generate_ipv6_eui_64_address(link, &new_address->in_addr.in6);
+                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");
 
-                new_address->prefixlen = prefixlen;
-                new_address->flags = IFA_F_NOPREFIXROUTE|IFA_F_MANAGETEMPADDR;
-                new_address->cinfo.ifa_prefered = lifetime_preferred;
-
                 r = set_put(addresses, new_address);
                 if (r < 0)
-                        return log_link_warning_errno(link, r, "Failed to store address: %m");
+                        return log_link_error_errno(link, r, "Failed to store SLAAC address: %m");
+
                 TAKE_PTR(new_address);
         }
 
@@ -348,9 +318,9 @@ static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *r
         uint32_t lifetime_valid, lifetime_preferred, lifetime_remaining;
         _cleanup_set_free_free_ Set *addresses = NULL;
         _cleanup_(address_freep) Address *address = NULL;
+        struct in6_addr addr, *a;
         unsigned prefixlen;
         usec_t time_now;
-        Address *existing_address, *a;
         Iterator i;
         int r;
 
@@ -359,7 +329,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)
@@ -377,47 +347,51 @@ 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_addresses(link, prefixlen, lifetime_preferred, address, &addresses);
+        r = address_new(&address);
         if (r < 0)
-                return log_link_error_errno(link, r, "Failed to generate SLAAC addresses: %m");
+                return log_oom();
+
+        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, a->family, &a->in_addr, a->prefixlen, &existing_address);
+                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)
-                                a->cinfo.ifa_valid = lifetime_valid;
+                                address->cinfo.ifa_valid = lifetime_valid;
                         else if (lifetime_remaining <= NDISC_PREFIX_LFT_MIN)
-                                a->cinfo.ifa_valid = lifetime_remaining;
+                                address->cinfo.ifa_valid = lifetime_remaining;
                         else
-                                a->cinfo.ifa_valid = NDISC_PREFIX_LFT_MIN;
+                                address->cinfo.ifa_valid = NDISC_PREFIX_LFT_MIN;
                 } else if (lifetime_valid > 0)
-                        a->cinfo.ifa_valid = lifetime_valid;
+                        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 (a->cinfo.ifa_valid == 0)
+                if (address->cinfo.ifa_valid == 0)
                         continue;
 
-                r = address_configure(a, 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 = address_configure(address, link, ndisc_address_handler, true, NULL);
+                if (r < 0)
+                        return log_link_error_errno(link, r, "Could not set SLAAC address: %m");
                 if (r > 0)
-                        link->ndisc_messages++;
+                        link->ndisc_addresses_messages++;
         }
 
         return 0;
@@ -435,7 +409,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)
@@ -447,7 +421,7 @@ 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);
@@ -461,14 +435,11 @@ 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;
-        }
+        r = route_configure(route, link, ndisc_route_handler, NULL);
+        if (r < 0)
+                return log_link_error_errno(link, r, "Could not set prefix route: %m");;
         if (r > 0)
-                link->ndisc_messages++;
+                link->ndisc_routes_messages++;
 
         return 0;
 }
@@ -485,30 +456,30 @@ 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);
@@ -523,14 +494,11 @@ 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;
-        }
+        r = route_configure(route, link, ndisc_route_handler, NULL);
+        if (r < 0)
+                return log_link_error_errno(link, r, "Could not set additional route: %m");
         if (r > 0)
-                link->ndisc_messages++;
+                link->ndisc_routes_messages++;
 
         return 0;
 }
@@ -549,24 +517,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],
@@ -612,7 +580,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) {
@@ -621,7 +589,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;
@@ -632,32 +600,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);
 
@@ -683,35 +643,32 @@ static void ndisc_router_process_dnssl(Link *link, sd_ndisc_router *rt) {
                 s->valid_until = time_now + lifetime * USEC_PER_SEC;
 
                 r = set_ensure_consume(&link->ndisc_dnssl, &ndisc_dnssl_hash_ops, TAKE_PTR(s));
-                if (r < 0) {
-                        log_oom();
-                        return;
-                }
+                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) {
 
@@ -730,44 +687,52 @@ static int ndisc_router_process_options(Link *link, sd_ndisc_router *rt) {
                                         (void) in_addr_to_string(AF_INET6, &a, &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) {
@@ -781,7 +746,7 @@ static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) {
 
         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) && link->network->ipv6_accept_ra_start_dhcp6_client)) {
 
@@ -790,23 +755,27 @@ static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) {
                 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");
+                        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;
 }
 
 static void ndisc_handler(sd_ndisc *nd, sd_ndisc_event event, sd_ndisc_router *rt, void *userdata) {
         Link *link = userdata;
+        int r;
 
         assert(link);
 
@@ -816,16 +785,48 @@ 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);
+                link->ndisc_addresses_configured = false;
+                link->ndisc_routes_configured = false;
+
+                r = ndisc_router_handler(link, rt);
+                if (r < 0) {
+                        link_enter_failed(link);
+                        return;
+                }
+
+                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.");
+
+                if (link->ndisc_addresses_configured && link->ndisc_routes_configured)
+                        link_check_ready(link);
+                else
+                        link_set_state(link, LINK_STATE_CONFIGURING);
                 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");
         }
 }
 
@@ -907,12 +908,26 @@ 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_deny_listed_prefix(
@@ -947,9 +962,11 @@ int config_parse_ndisc_deny_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 deny-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;
                 }
@@ -958,8 +975,8 @@ int config_parse_ndisc_deny_listed_prefix(
 
                 r = in_addr_from_string(AF_INET6, n, &ip);
                 if (r < 0) {
-                        log_syntax(unit, LOG_ERR, filename, line, r,
-                                   "NDISC deny-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;
                 }
 
@@ -974,8 +991,6 @@ int config_parse_ndisc_deny_listed_prefix(
                 if (r < 0)
                         return log_oom();
         }
-
-        return 0;
 }
 
 int config_parse_address_generation_type(
@@ -1002,7 +1017,7 @@ int config_parse_address_generation_type(
         assert(data);
 
         if (isempty(rvalue)) {
-                network->ipv6_tokens = ordered_hashmap_free(network->ipv6_tokens);
+                network->ipv6_tokens = ordered_set_free(network->ipv6_tokens);
                 return 0;
         }
 
@@ -1021,31 +1036,32 @@ int config_parse_address_generation_type(
 
         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;
 }