networkd-dhcp6: Set one unreachable route per DHCPv6 delegated prefix
authorPatrik Flykt <patrik.flykt@linux.intel.com>
Wed, 19 Sep 2018 00:32:19 +0000 (18:32 -0600)
committerPatrik Flykt <patrik.flykt@linux.intel.com>
Wed, 19 Sep 2018 19:45:18 +0000 (13:45 -0600)
Instead of setting many small unreachable routes for each of the /64
subnets that were not distributed between the links requesting delegated
prefixes, set one unreachable route for the size of the delegated
prefix. Each subnet asssigned to a downstream link will add a routable
subnet for that link, and as the subnet assigned to the downstream link
has a longer prefix than the whole delegated prefix, the downstream
link subnet routes are preferred over the unroutable delegated one.
The unreachable route is not added when the delegated prefix is exactly
a /64 as the prefix size cannot be used to sort out the order of routing
into a bigger blocking subnet with the smaller /64 punching routable
"holes" into it.

When stopping the DHCPv6 client, the unroutable delegated prefix is
removed before the downstream link prefixes are unassigned.

src/network/networkd-dhcp6.c

index f9b97d1..c0572ad 100644 (file)
@@ -111,6 +111,80 @@ static Network *dhcp6_reset_pd_prefix_network(Link *link) {
         return link->manager->networks;
 }
 
+static int dhcp6_route_remove_cb(sd_netlink *nl, sd_netlink_message *m,
+                                 void *userdata) {
+        Link *l = userdata;
+        int r;
+
+        r = sd_netlink_message_get_errno(m);
+        if (r < 0)
+                log_link_debug_errno(l, r, "Received error on unreachable route removal for DHCPv6 delegated subnetl: %m");
+
+        l = link_unref(l);
+
+        return 0;
+}
+
+static int dhcp6_lease_pd_prefix_lost(sd_dhcp6_client *client, Link* link) {
+        int r;
+        sd_dhcp6_lease *lease;
+        union in_addr_union pd_prefix;
+        uint8_t pd_prefix_len;
+        uint32_t lifetime_preferred, lifetime_valid;
+
+        r = sd_dhcp6_client_get_lease(client, &lease);
+        if (r < 0)
+                return r;
+
+        dhcp6_reset_pd_prefix_network(link);
+        sd_dhcp6_lease_reset_pd_prefix_iter(lease);
+
+        while (sd_dhcp6_lease_get_pd(lease, &pd_prefix.in6, &pd_prefix_len,
+                                     &lifetime_preferred,
+                                     &lifetime_valid) >= 0) {
+                _cleanup_free_ char *buf = NULL;
+                _cleanup_free_ Route *route;
+
+                if (pd_prefix_len > 64)
+                        continue;
+
+                (void) in_addr_to_string(AF_INET6, &pd_prefix, &buf);
+
+                if (pd_prefix_len < 64) {
+                        r = route_new(&route);
+                        if (r < 0) {
+                                log_link_warning_errno(link, r, "Cannot create unreachable route to delete for DHCPv6 delegated subnet %s/%u: %m",
+                                                       strnull(buf),
+                                                       pd_prefix_len);
+                                continue;
+                        }
+
+                        route_add(link, AF_INET6, &pd_prefix, pd_prefix_len,
+                                  0, 0, 0, &route);
+                        route_update(route, NULL, 0, NULL, NULL, 0, 0,
+                                     RTN_UNREACHABLE);
+
+                        r = route_remove(route, link, dhcp6_route_remove_cb);
+                        if (r < 0) {
+                                (void) in_addr_to_string(AF_INET6,
+                                                         &pd_prefix, &buf);
+
+                                log_link_warning_errno(link, r, "Cannot delete unreachable route for DHCPv6 delegated subnet %s/%u: %m",
+                                                       strnull(buf),
+                                                       pd_prefix_len);
+                                route_free(route);
+                                continue;
+                        }
+                        link = link_ref(link);
+
+                        log_link_debug(link, "Removing unreachable route %s/%u",
+                                       strnull(buf), pd_prefix_len);
+                }
+        }
+
+        return 0;
+}
+
 static int dhcp6_pd_prefix_distribute(Link *dhcp6_link, Iterator *i,
                                       struct in6_addr *pd_prefix,
                                       uint8_t pd_prefix_len,
@@ -184,39 +258,28 @@ static int dhcp6_pd_prefix_distribute(Link *dhcp6_link, Iterator *i,
                         return r;
         }
 
-        if (n_used < n_prefixes) {
-                Route *route;
-                uint64_t n = n_used;
-
-                r = route_new(&route);
-                if (r < 0)
-                        return r;
-
-                route->family = AF_INET6;
+        return 0;
+}
 
-                while (n < n_prefixes) {
-                        route_update(route, &prefix, pd_prefix_len, NULL, NULL,
-                                     0, 0, RTN_UNREACHABLE);
+static int dhcp6_route_add_cb(sd_netlink *nl, sd_netlink_message *m,
+                              void *userdata) {
+        Link *l = userdata;
+        int r;
 
-                        r = route_configure(route, dhcp6_link, NULL);
-                        if (r < 0) {
-                                route_free(route);
-                                return r;
-                        }
+        r = sd_netlink_message_get_errno(m);
+        if (r < 0 && r !=  -EEXIST)
+                log_link_debug_errno(l, r, "Received error when adding unreachable route for DHCPv6 delegated subnet: %m");
 
-                        r = in_addr_prefix_next(AF_INET6, &prefix, pd_prefix_len);
-                        if (r < 0)
-                                return r;
-                }
-        }
+        l = link_unref(l);
 
-        return n_used;
+        return 0;
 }
 
+
 static int dhcp6_lease_pd_prefix_acquired(sd_dhcp6_client *client, Link *link) {
         int r;
         sd_dhcp6_lease *lease;
-        struct in6_addr pd_prefix;
+        union in_addr_union pd_prefix;
         uint8_t pd_prefix_len;
         uint32_t lifetime_preferred, lifetime_valid;
         _cleanup_free_ char *buf = NULL;
@@ -229,24 +292,60 @@ static int dhcp6_lease_pd_prefix_acquired(sd_dhcp6_client *client, Link *link) {
         dhcp6_reset_pd_prefix_network(link);
         sd_dhcp6_lease_reset_pd_prefix_iter(lease);
 
-        while (sd_dhcp6_lease_get_pd(lease, &pd_prefix, &pd_prefix_len,
+        while (sd_dhcp6_lease_get_pd(lease, &pd_prefix.in6, &pd_prefix_len,
                                      &lifetime_preferred,
                                      &lifetime_valid) >= 0) {
 
                 if (pd_prefix_len > 64) {
-                        (void) in_addr_to_string(AF_INET6, (union in_addr_union*) &pd_prefix, &buf);
+                        (void) in_addr_to_string(AF_INET6, &pd_prefix, &buf);
                         log_link_debug(link, "PD Prefix length > 64, ignoring prefix %s/%u",
                                        strnull(buf), pd_prefix_len);
                         continue;
                 }
 
                 if (pd_prefix_len < 48) {
-                        (void) in_addr_to_string(AF_INET6, (union in_addr_union*) &pd_prefix, &buf);
+                        (void) in_addr_to_string(AF_INET6, &pd_prefix, &buf);
                         log_link_warning(link, "PD Prefix length < 48, looks unusual %s/%u",
                                        strnull(buf), pd_prefix_len);
                 }
 
-                r = dhcp6_pd_prefix_distribute(link, &i, &pd_prefix,
+                if (pd_prefix_len < 64) {
+                        Route *route = NULL;
+
+                        (void) in_addr_to_string(AF_INET6, &pd_prefix, &buf);
+
+                        r = route_new(&route);
+                        if (r < 0) {
+                                log_link_warning_errno(link, r, "Cannot create unreachable route for DHCPv6 delegated subnet %s/%u: %m",
+                                                       strnull(buf),
+                                                       pd_prefix_len);
+                                continue;
+                        }
+
+                        route_add(link, AF_INET6, &pd_prefix, pd_prefix_len,
+                                  0, 0, 0, &route);
+                        route_update(route, NULL, 0, NULL, NULL, 0, 0,
+                                     RTN_UNREACHABLE);
+
+                        r = route_configure(route, link, dhcp6_route_add_cb);
+                        if (r < 0) {
+                                log_link_warning_errno(link, r, "Cannot configure unreachable route for delegated subnet %s/%u: %m",
+                                                       strnull(buf),
+                                                       pd_prefix_len);
+                                route_free(route);
+                                continue;
+                        }
+                        link = link_ref(link);
+
+                        route_free(route);
+
+                        log_link_debug(link, "Configuring unreachable route for %s/%u",
+                                       strnull(buf), pd_prefix_len);
+
+                } else
+                        log_link_debug(link, "Not adding a blocking route since distributed prefix is /64");
+
+                r = dhcp6_pd_prefix_distribute(link, &i, &pd_prefix.in6,
                                                pd_prefix_len,
                                                lifetime_preferred,
                                                lifetime_valid);
@@ -364,6 +463,7 @@ static void dhcp6_handler(sd_dhcp6_client *client, int event, void *userdata) {
                 if (sd_dhcp6_client_get_lease(client, NULL) >= 0)
                         log_link_warning(link, "DHCPv6 lease lost");
 
+                (void) dhcp6_lease_pd_prefix_lost(client, link);
                 (void) manager_dhcp6_prefix_remove_all(link->manager, link);
 
                 link->dhcp6_configured = false;