]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
network/dhcp4: always find suitable gateway for destination address
authorYu Watanabe <watanabe.yu+github@gmail.com>
Mon, 24 Jul 2023 11:40:04 +0000 (20:40 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Thu, 27 Jul 2023 20:45:51 +0000 (05:45 +0900)
And if not found, refuse to configure the route.

If a DHCP server provides classless static or static routes, then we
should use the gateway for accessing a node in the range specified in
the route. E.g. if a DHCP server provides the default gateway is
192.168.0.1, and classless static route for 8.0.0.0/8 with gateway
192.168.0.2, then we should access 8.8.8.8 through 192.168.0.2 rather
than 192.168.0.1, but should use 192.168.0.1 for 9.9.9.9.

Fixes #28358.

src/network/networkd-dhcp4.c
test/test-network/systemd-networkd-tests.py

index 8a76da63c89f87d48d028ea6991751c033d06bf2..ca93c6773f073c143c76499b4b6c9f6211f6ea36 100644 (file)
@@ -134,6 +134,104 @@ static int dhcp4_get_classless_static_or_static_routes(Link *link, sd_dhcp_route
         return 0; /* static */
 }
 
+static int dhcp4_find_gateway_for_destination(
+                Link *link,
+                const struct in_addr *destination,
+                uint8_t prefixlength,
+                bool allow_null,
+                struct in_addr *ret) {
+
+        _cleanup_free_ sd_dhcp_route **routes = NULL;
+        size_t n_routes = 0;
+        bool is_classless, reachable;
+        uint8_t max_prefixlen = UINT8_MAX;
+        struct in_addr gw;
+        int r;
+
+        assert(link);
+        assert(link->dhcp_lease);
+        assert(destination);
+        assert(ret);
+
+        /* This tries to find the most suitable gateway for an address or address range.
+         * E.g. if the server provides the default gateway 192.168.0.1 and a classless static route for
+         * 8.0.0.0/8 with gateway 192.168.0.2, then this returns 192.168.0.2 for 8.8.8.8/32, and 192.168.0.1
+         * for 9.9.9.9/32. If 'allow_null' flag is set, and the input address or address range is in the
+         * assigned network, then the default gateway will be ignored and the null address will be returned
+         * unless a matching non-default gateway found. */
+
+        r = dhcp4_prefix_covers(link, destination, prefixlength);
+        if (r < 0)
+                return r;
+        reachable = r > 0;
+
+        r = dhcp4_get_classless_static_or_static_routes(link, &routes, &n_routes);
+        if (r < 0 && r != -ENODATA)
+                return r;
+        is_classless = r > 0;
+
+        /* First, find most suitable gateway. */
+        FOREACH_ARRAY(e, routes, n_routes) {
+                struct in_addr dst;
+                uint8_t len;
+
+                r = sd_dhcp_route_get_destination(*e, &dst);
+                if (r < 0)
+                        return r;
+
+                r = sd_dhcp_route_get_destination_prefix_length(*e, &len);
+                if (r < 0)
+                        return r;
+
+                r = in4_addr_prefix_covers_full(&dst, len, destination, prefixlength);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        continue;
+
+                if (max_prefixlen != UINT8_MAX && max_prefixlen > len)
+                        continue;
+
+                r = sd_dhcp_route_get_gateway(*e, &gw);
+                if (r < 0)
+                        return r;
+
+                max_prefixlen = len;
+        }
+
+        /* Found a suitable gateway in classless static routes or static routes. */
+        if (max_prefixlen != UINT8_MAX) {
+                if (max_prefixlen == 0 && reachable && allow_null)
+                        /* Do not return the default gateway, if the destination is in the assigned network. */
+                        *ret = (struct in_addr) {};
+                else
+                        *ret = gw;
+                return 0;
+        }
+
+        /* When the destination is in the assigned network, return the null address if allowed. */
+        if (reachable && allow_null) {
+                *ret = (struct in_addr) {};
+                return 0;
+        }
+
+        /* According to RFC 3442: If the DHCP server returns both a Classless Static Routes option and
+         * a Router option, the DHCP client MUST ignore the Router option. */
+        if (!is_classless) {
+                r = dhcp4_get_router(link, ret);
+                if (r >= 0)
+                        return 0;
+                if (r != -ENODATA)
+                        return r;
+        }
+
+        if (!reachable)
+                return -EHOSTUNREACH; /* Not in the same network, cannot reach the destination. */
+
+        assert(!allow_null);
+        return -ENODATA; /* No matching gateway found. */
+}
+
 static int dhcp4_remove_address_and_routes(Link *link, bool only_marked) {
         Address *address;
         Route *route;
@@ -590,20 +688,17 @@ static int dhcp4_request_gateway(Link *link, struct in_addr *gw) {
         return 0;
 }
 
-static int dhcp4_request_semi_static_routes(Link *link, const struct in_addr *gw) {
+static int dhcp4_request_semi_static_routes(Link *link) {
         Route *rt;
         int r;
 
         assert(link);
         assert(link->dhcp_lease);
         assert(link->network);
-        assert(gw);
-
-        if (in4_addr_is_null(gw))
-                return 0;
 
         HASHMAP_FOREACH(rt, link->network->routes_by_section) {
                 _cleanup_(route_freep) Route *route = NULL;
+                struct in_addr gw;
 
                 if (!rt->gateway_from_dhcp_or_ra)
                         continue;
@@ -611,7 +706,18 @@ static int dhcp4_request_semi_static_routes(Link *link, const struct in_addr *gw
                 if (rt->gw_family != AF_INET)
                         continue;
 
-                r = dhcp4_request_route_to_gateway(link, gw);
+                assert(rt->family == AF_INET);
+
+                r = dhcp4_find_gateway_for_destination(link, &rt->dst.in, rt->dst_prefixlen, /* allow_null = */ false, &gw);
+                if (IN_SET(r, -EHOSTUNREACH, -ENODATA)) {
+                        log_link_debug_errno(link, r, "DHCP: Cannot find suitable gateway for destination %s of semi-static route, ignoring: %m",
+                                             IN4_ADDR_PREFIX_TO_STRING(&rt->dst.in, rt->dst_prefixlen));
+                        continue;
+                }
+                if (r < 0)
+                        return r;
+
+                r = dhcp4_request_route_to_gateway(link, &gw);
                 if (r < 0)
                         return r;
 
@@ -619,7 +725,7 @@ static int dhcp4_request_semi_static_routes(Link *link, const struct in_addr *gw
                 if (r < 0)
                         return r;
 
-                route->gw.in = *gw;
+                route->gw.in = gw;
 
                 r = dhcp4_request_route(TAKE_PTR(route), link);
                 if (r < 0)
@@ -632,8 +738,7 @@ static int dhcp4_request_semi_static_routes(Link *link, const struct in_addr *gw
 static int dhcp4_request_routes_to_servers(
                 Link *link,
                 const struct in_addr *servers,
-                size_t n_servers,
-                const struct in_addr *gw) {
+                size_t n_servers) {
 
         int r;
 
@@ -641,14 +746,23 @@ static int dhcp4_request_routes_to_servers(
         assert(link->dhcp_lease);
         assert(link->network);
         assert(servers || n_servers == 0);
-        assert(gw);
 
         FOREACH_ARRAY(dst, servers, n_servers) {
                 _cleanup_(route_freep) Route *route = NULL;
+                struct in_addr gw;
 
                 if (in4_addr_is_null(dst))
                         continue;
 
+                r = dhcp4_find_gateway_for_destination(link, dst, 32, /* allow_null = */ true, &gw);
+                if (r == -EHOSTUNREACH) {
+                        log_link_debug_errno(link, r, "DHCP: Cannot find suitable gateway for destination %s, ignoring: %m",
+                                             IN4_ADDR_PREFIX_TO_STRING(dst, 32));
+                        continue;
+                }
+                if (r < 0)
+                        return r;
+
                 r = route_new(&route);
                 if (r < 0)
                         return r;
@@ -656,7 +770,7 @@ static int dhcp4_request_routes_to_servers(
                 route->dst.in = *dst;
                 route->dst_prefixlen = 32;
 
-                r = dhcp4_request_route_auto(TAKE_PTR(route), link, gw, /* force_use_gw = */ false);
+                r = dhcp4_request_route_auto(TAKE_PTR(route), link, &gw, /* force_use_gw = */ false);
                 if (r < 0)
                         return r;
         }
@@ -664,14 +778,13 @@ static int dhcp4_request_routes_to_servers(
         return 0;
 }
 
-static int dhcp4_request_routes_to_dns(Link *link, const struct in_addr *gw) {
+static int dhcp4_request_routes_to_dns(Link *link) {
         const struct in_addr *dns;
         int r;
 
         assert(link);
         assert(link->dhcp_lease);
         assert(link->network);
-        assert(gw);
 
         if (!link->network->dhcp_use_dns ||
             !link->network->dhcp_routes_to_dns)
@@ -683,17 +796,16 @@ static int dhcp4_request_routes_to_dns(Link *link, const struct in_addr *gw) {
         if (r < 0)
                 return r;
 
-        return dhcp4_request_routes_to_servers(link, dns, r, gw);
+        return dhcp4_request_routes_to_servers(link, dns, r);
 }
 
-static int dhcp4_request_routes_to_ntp(Link *link, const struct in_addr *gw) {
+static int dhcp4_request_routes_to_ntp(Link *link) {
         const struct in_addr *ntp;
         int r;
 
         assert(link);
         assert(link->dhcp_lease);
         assert(link->network);
-        assert(gw);
 
         if (!link->network->dhcp_use_ntp ||
             !link->network->dhcp_routes_to_ntp)
@@ -705,7 +817,7 @@ static int dhcp4_request_routes_to_ntp(Link *link, const struct in_addr *gw) {
         if (r < 0)
                 return r;
 
-        return dhcp4_request_routes_to_servers(link, ntp, r, gw);
+        return dhcp4_request_routes_to_servers(link, ntp, r);
 }
 
 static int dhcp4_request_routes(Link *link) {
@@ -730,15 +842,15 @@ static int dhcp4_request_routes(Link *link) {
                         return log_link_error_errno(link, r, "DHCP error: Could not request gateway: %m");
         }
 
-        r = dhcp4_request_semi_static_routes(link, &gw);
+        r = dhcp4_request_semi_static_routes(link);
         if (r < 0)
                 return log_link_error_errno(link, r, "DHCP error: Could not request routes with Gateway=_dhcp4 setting: %m");
 
-        r = dhcp4_request_routes_to_dns(link, &gw);
+        r = dhcp4_request_routes_to_dns(link);
         if (r < 0)
                 return log_link_error_errno(link, r, "DHCP error: Could not request routes to DNS servers: %m");
 
-        r = dhcp4_request_routes_to_ntp(link, &gw);
+        r = dhcp4_request_routes_to_ntp(link);
         if (r < 0)
                 return log_link_error_errno(link, r, "DHCP error: Could not request routes to NTP servers: %m");
 
index 01336bc1751bfa7520567c794dbfb1425d6c1ed0..f2e2279124c584bf408c813f4cc09beae8b44dfc 100755 (executable)
@@ -5042,9 +5042,13 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
         if dns_and_ntp_routes:
             self.assertRegex(output, r'192.168.5.10 proto dhcp scope link src 192.168.5.[0-9]* metric 1024')
             self.assertRegex(output, r'192.168.5.11 proto dhcp scope link src 192.168.5.[0-9]* metric 1024')
-            if classless and use_routes:
-                self.assertRegex(output, r'8.8.8.8 via 192.168.5.4 proto dhcp src 192.168.5.[0-9]* metric 1024')
-                self.assertRegex(output, r'9.9.9.9 via 192.168.5.4 proto dhcp src 192.168.5.[0-9]* metric 1024')
+            if use_routes:
+                if classless:
+                    self.assertRegex(output, r'8.8.8.8 via 192.168.5.5 proto dhcp src 192.168.5.[0-9]* metric 1024')
+                    self.assertRegex(output, r'9.9.9.9 via 192.168.5.4 proto dhcp src 192.168.5.[0-9]* metric 1024')
+                else:
+                    self.assertRegex(output, r'8.8.8.8 via 192.168.5.3 proto dhcp src 192.168.5.[0-9]* metric 1024')
+                    self.assertRegex(output, r'9.9.9.9 via 192.168.5.1 proto dhcp src 192.168.5.[0-9]* metric 1024')
             else:
                 self.assertRegex(output, r'8.8.8.8 via 192.168.5.1 proto dhcp src 192.168.5.[0-9]* metric 1024')
                 self.assertRegex(output, r'9.9.9.9 via 192.168.5.1 proto dhcp src 192.168.5.[0-9]* metric 1024')