From: Yu Watanabe Date: Mon, 24 Jul 2023 11:40:04 +0000 (+0900) Subject: network/dhcp4: always find suitable gateway for destination address X-Git-Tag: v255-rc1~900^2~2 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=0ce86f5eeb0921b44a9782260a8c88aafb15ffde;p=thirdparty%2Fsystemd.git network/dhcp4: always find suitable gateway for destination address 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. --- diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index 8a76da63c89..ca93c6773f0 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -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"); diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 01336bc1751..f2e2279124c 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -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')