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.
This commit is contained in:
Yu Watanabe
2023-07-28 05:45:51 +09:00
parent 0bc8b6e265
commit 0ce86f5eeb
2 changed files with 139 additions and 23 deletions
+132 -20
View 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");
+7 -3
View File
@@ -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')