1
0
mirror of https://github.com/systemd/systemd.git synced 2025-03-28 02:50:16 +03:00

network/route: improve Gateway=_dhcp4 handling (#36183)

- Also configures route to the gateway and prefix route in the specified
table, if necessary.
- Also set preferred source address of the route.

Closes #36168.
This commit is contained in:
Luca Boccassi 2025-02-05 12:19:01 +00:00 committed by GitHub
commit 2ee81b556f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 122 additions and 48 deletions

View File

@ -2010,8 +2010,10 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix</programlisting>
<term><varname>Gateway=</varname></term>
<listitem>
<para>Takes the gateway address or the special values <literal>_dhcp4</literal> and
<literal>_ipv6ra</literal>. If <literal>_dhcp4</literal> or <literal>_ipv6ra</literal> is
set, then the gateway address provided by DHCPv4 or IPv6 RA is used.</para>
<literal>_ipv6ra</literal>. If <literal>_dhcp4</literal> or <literal>_ipv6ra</literal> is set, then
the gateway address provided by DHCPv4 or IPv6 RA is used. When<literal>_dhcp4</literal>, the
acquired DHCPv4 address will be used as the preferred source address of the route, unless it is
explicitly configured in <varname>PreferredSource=</varname>.</para>
<xi:include href="version-info.xml" xpointer="v211"/>
</listitem>
@ -2117,10 +2119,12 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix</programlisting>
<varlistentry>
<term><varname>PreferredSource=</varname></term>
<listitem>
<para>The preferred source address of the route. The address must be in the format described
in
<para>The preferred source address of the route. Takes <literal>no</literal> or an address
in the format described in
<citerefentry project='man-pages'><refentrytitle>inet_pton</refentrytitle><manvolnum>3</manvolnum></citerefentry>.
</para>
If <varname>Gateway=_dhcp4</varname> is specified, defaults to the acquired DHCPv4 address.
Otherwise, defaults to unset. The value <literal>no</literal> may be useful to configure a route
with <varname>Gateway=_dhcp4</varname> without setting preferred source route address.</para>
<xi:include href="version-info.xml" xpointer="v227"/>
</listitem>

View File

@ -405,20 +405,20 @@ static int dhcp4_request_route(Route *route, Link *link) {
return link_request_route(link, route, &link->dhcp4_messages, dhcp4_route_handler);
}
static bool link_prefixroute(Link *link) {
static bool prefixroute_by_kernel(Link *link) {
return !link->network->dhcp_route_table_set ||
link->network->dhcp_route_table == RT_TABLE_MAIN;
}
static int dhcp4_request_prefix_route(Link *link) {
static int dhcp4_request_prefix_route(Link *link, Route *rt) {
_cleanup_(route_unrefp) Route *route = NULL;
int r;
assert(link);
assert(link->dhcp_lease);
if (link_prefixroute(link))
/* When true, the route will be created by kernel. See dhcp4_update_address(). */
if (prefixroute_by_kernel(link) && (!rt || !rt->table_set || rt->table == RT_TABLE_MAIN))
/* The prefix route in the main table will be created by the kernel. See dhcp4_update_address(). */
return 0;
r = route_new(&route);
@ -426,6 +426,10 @@ static int dhcp4_request_prefix_route(Link *link) {
return r;
route->scope = RT_SCOPE_LINK;
if (rt) {
route->table_set = rt->table_set;
route->table = rt->table;
}
r = sd_dhcp_lease_get_prefix(link->dhcp_lease, &route->dst.in, &route->dst_prefixlen);
if (r < 0)
@ -438,14 +442,19 @@ static int dhcp4_request_prefix_route(Link *link) {
return dhcp4_request_route(route, link);
}
static int dhcp4_request_route_to_gateway(Link *link, const struct in_addr *gw) {
static int dhcp4_request_route_to_gateway(Link *link, const Route *rt) {
_cleanup_(route_unrefp) Route *route = NULL;
struct in_addr address;
int r;
assert(link);
assert(link->dhcp_lease);
assert(gw);
assert(rt);
if (in_addr_is_set(rt->nexthop.family, &rt->nexthop.gw) <= 0)
return 0;
assert(rt->nexthop.family == AF_INET);
r = sd_dhcp_lease_get_address(link->dhcp_lease, &address);
if (r < 0)
@ -455,10 +464,12 @@ static int dhcp4_request_route_to_gateway(Link *link, const struct in_addr *gw)
if (r < 0)
return r;
route->dst.in = *gw;
route->dst.in = rt->nexthop.gw.in;
route->dst_prefixlen = 32;
route->prefsrc.in = address;
route->scope = RT_SCOPE_LINK;
route->table = rt->table;
route->table_set = rt->table_set;
return dhcp4_request_route(route, link);
}
@ -526,14 +537,14 @@ static int dhcp4_request_route_auto(
route->prefsrc.in = address;
} else {
r = dhcp4_request_route_to_gateway(link, gw);
if (r < 0)
return r;
route->scope = RT_SCOPE_UNIVERSE;
route->nexthop.family = AF_INET;
route->nexthop.gw.in = *gw;
route->prefsrc.in = address;
r = dhcp4_request_route_to_gateway(link, route);
if (r < 0)
return r;
}
return dhcp4_request_route(route, link);
@ -613,12 +624,6 @@ static int dhcp4_request_default_gateway(Link *link) {
if (r < 0)
return r;
/* The dhcp netmask may mask out the gateway. First, add an explicit route for the gateway host
* so that we can route no matter the netmask or existing kernel route tables. */
r = dhcp4_request_route_to_gateway(link, &router);
if (r < 0)
return r;
r = route_new(&route);
if (r < 0)
return r;
@ -628,6 +633,12 @@ static int dhcp4_request_default_gateway(Link *link) {
route->nexthop.gw.in = router;
route->prefsrc.in = address;
/* The dhcp netmask may mask out the gateway. First, add an explicit route for the gateway host
* so that we can route no matter the netmask or existing kernel route tables. */
r = dhcp4_request_route_to_gateway(link, route);
if (r < 0)
return r;
return dhcp4_request_route(route, link);
}
@ -643,13 +654,11 @@ static int dhcp4_request_semi_static_routes(Link *link) {
_cleanup_(route_unrefp) Route *route = NULL;
struct in_addr gw;
if (!rt->gateway_from_dhcp_or_ra)
continue;
if (rt->nexthop.family != AF_INET)
if (rt->source != NETWORK_CONFIG_SOURCE_DHCP4)
continue;
assert(rt->family == AF_INET);
assert(rt->nexthop.family == AF_INET);
r = dhcp4_find_gateway_for_destination(link, &rt->dst.in, rt->dst_prefixlen, &gw);
if (r == -EHOSTUNREACH) {
@ -666,16 +675,26 @@ static int dhcp4_request_semi_static_routes(Link *link) {
continue;
}
r = dhcp4_request_route_to_gateway(link, &gw);
if (r < 0)
return r;
r = route_dup(rt, NULL, &route);
if (r < 0)
return r;
route->nexthop.gw.in = gw;
if (!route->prefsrc_set) {
r = sd_dhcp_lease_get_address(link->dhcp_lease, &route->prefsrc.in);
if (r < 0)
return r;
}
r = dhcp4_request_prefix_route(link, route);
if (r < 0)
return r;
r = dhcp4_request_route_to_gateway(link, route);
if (r < 0)
return r;
r = dhcp4_request_route(route, link);
if (r < 0)
return r;
@ -775,7 +794,7 @@ static int dhcp4_request_routes(Link *link) {
assert(link);
assert(link->dhcp_lease);
r = dhcp4_request_prefix_route(link);
r = dhcp4_request_prefix_route(link, /* rt = */ NULL);
if (r < 0)
return log_link_error_errno(link, r, "DHCP error: Could not request prefix route: %m");
@ -965,7 +984,7 @@ static int dhcp4_request_address(Link *link, bool announce) {
r = sd_dhcp_lease_get_broadcast(link->dhcp_lease, &addr->broadcast);
if (r < 0 && r != -ENODATA)
return log_link_warning_errno(link, r, "DHCP: failed to get broadcast address: %m");
SET_FLAG(addr->flags, IFA_F_NOPREFIXROUTE, !link_prefixroute(link));
SET_FLAG(addr->flags, IFA_F_NOPREFIXROUTE, !prefixroute_by_kernel(link));
addr->route_metric = link->network->dhcp_route_metric;
addr->duplicate_address_detection = link->network->dhcp_send_decline ? ADDRESS_FAMILY_IPV4 : ADDRESS_FAMILY_NO;

View File

@ -1083,11 +1083,10 @@ static int ndisc_router_drop_default(Link *link, sd_ndisc_router *rt) {
HASHMAP_FOREACH(route_gw, link->network->routes_by_section) {
_cleanup_(route_unrefp) Route *tmp = NULL;
if (!route_gw->gateway_from_dhcp_or_ra)
if (route_gw->source != NETWORK_CONFIG_SOURCE_NDISC)
continue;
if (route_gw->nexthop.family != AF_INET6)
continue;
assert(route_gw->nexthop.family == AF_INET6);
r = route_dup(route_gw, NULL, &tmp);
if (r < 0)
@ -1158,11 +1157,10 @@ static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) {
HASHMAP_FOREACH(route_gw, link->network->routes_by_section) {
_cleanup_(route_unrefp) Route *route = NULL;
if (!route_gw->gateway_from_dhcp_or_ra)
if (route_gw->source != NETWORK_CONFIG_SOURCE_NDISC)
continue;
if (route_gw->nexthop.family != AF_INET6)
continue;
assert(route_gw->nexthop.family == AF_INET6);
r = route_dup(route_gw, NULL, &route);
if (r < 0)

View File

@ -844,11 +844,24 @@ int route_section_verify_nexthops(Route *route) {
return log_route_section(route, "Invalid route family.");
}
if (route->nexthop.family == AF_INET && !FLAGS_SET(route->network->dhcp, ADDRESS_FAMILY_IPV4))
return log_route_section(route, "Gateway=\"_dhcp4\" is specified but DHCPv4 client is disabled.");
switch (route->nexthop.family) {
case AF_INET:
if (!FLAGS_SET(route->network->dhcp, ADDRESS_FAMILY_IPV4))
return log_route_section(route, "Gateway=\"_dhcp4\" is specified but DHCPv4 client is disabled.");
if (route->nexthop.family == AF_INET6 && route->network->ndisc == 0)
return log_route_section(route, "Gateway=\"_ipv6ra\" is specified but IPv6AcceptRA= is disabled.");
route->source = NETWORK_CONFIG_SOURCE_DHCP4;
break;
case AF_INET6:
if (route->network->ndisc == 0)
return log_route_section(route, "Gateway=\"_ipv6ra\" is specified but IPv6AcceptRA= is disabled.");
route->source = NETWORK_CONFIG_SOURCE_NDISC;
break;
default:
assert_not_reached();
}
}
/* When only Gateway= is specified, assume the route family based on the Gateway address. */

View File

@ -1065,7 +1065,7 @@ int link_request_static_routes(Link *link, bool only_ipv4) {
link->static_routes_configured = false;
HASHMAP_FOREACH(route, link->network->routes_by_section) {
if (route->gateway_from_dhcp_or_ra)
if (route->source != NETWORK_CONFIG_SOURCE_STATIC)
continue;
if (only_ipv4 && route->family != AF_INET)
@ -1519,6 +1519,9 @@ int link_drop_routes(Link *link, bool only_static) {
continue;
HASHMAP_FOREACH(route, other->network->routes_by_section) {
if (route->source != NETWORK_CONFIG_SOURCE_STATIC)
continue;
if (route->family == AF_INET || ordered_set_isempty(route->nexthops)) {
r = link_unmark_route(other, route, NULL);
if (r < 0)
@ -1660,7 +1663,19 @@ static int config_parse_preferred_src(
Route *route = ASSERT_PTR(userdata);
int r;
assert(rvalue);
if (isempty(rvalue)) {
route->prefsrc_set = false;
route->prefsrc = IN_ADDR_NULL;
return 1;
}
r = parse_boolean(rvalue);
if (r == 0) {
/* Accepts only no. That prohibits prefsrc set by DHCP lease. */
route->prefsrc_set = true;
route->prefsrc = IN_ADDR_NULL;
return 1;
}
if (route->family == AF_UNSPEC)
r = in_addr_from_string_auto(rvalue, &route->family, &route->prefsrc);
@ -1669,6 +1684,7 @@ static int config_parse_preferred_src(
if (r < 0)
return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue);
route->prefsrc_set = true;
return 1;
}

View File

@ -71,6 +71,7 @@ struct Route {
bool expiration_managed_by_kernel:1; /* RTA_CACHEINFO has nonzero rta_expires */
/* Only used by conf persers and route_section_verify(). */
bool prefsrc_set:1;
bool scope_set:1;
bool table_set:1;
bool priority_set:1;

View File

@ -43,5 +43,9 @@ Destination=192.168.7.0/24
[Route]
Gateway=_dhcp4
Destination=10.0.0.0/8
Table=211
Destination=192.0.2.0/24
[Route]
Gateway=_dhcp4
Destination=198.51.100.0/24
Table=212

View File

@ -7263,7 +7263,14 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
self.assertRegex(output, f'192.168.5.1 proto dhcp scope link src {address1} metric 24')
self.assertRegex(output, f'192.168.5.6 proto dhcp scope link src {address1} metric 24')
self.assertRegex(output, f'192.168.5.7 proto dhcp scope link src {address1} metric 24')
self.assertIn('10.0.0.0/8 via 192.168.5.1 proto dhcp', output)
self.assertRegex(output, f'192.0.2.0/24 via 192.168.5.1 proto dhcp src {address1}')
print('## ip route show table 212 dev veth99')
output = check_output('ip route show table 212 dev veth99')
print(output)
self.assertRegex(output, f'192.168.5.0/24 proto dhcp scope link src {address1} metric 24')
self.assertRegex(output, f'192.168.5.1 proto dhcp scope link src {address1} metric 24')
self.assertRegex(output, f'198.51.100.0/24 via 192.168.5.1 proto dhcp src {address1}')
print('## link state file')
output = read_link_state_file('veth99')
@ -7363,7 +7370,14 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
self.assertNotIn('192.168.5.6', output)
self.assertRegex(output, f'192.168.5.7 proto dhcp scope link src {address2} metric 24')
self.assertRegex(output, f'192.168.5.8 proto dhcp scope link src {address2} metric 24')
self.assertIn('10.0.0.0/8 via 192.168.5.1 proto dhcp', output)
self.assertRegex(output, f'192.0.2.0/24 via 192.168.5.1 proto dhcp src {address2}')
print('## ip route show table 212 dev veth99')
output = check_output('ip route show table 212 dev veth99')
print(output)
self.assertRegex(output, f'192.168.5.0/24 proto dhcp scope link src {address2} metric 24')
self.assertRegex(output, f'192.168.5.1 proto dhcp scope link src {address2} metric 24')
self.assertRegex(output, f'198.51.100.0/24 via 192.168.5.1 proto dhcp src {address2}')
print('## link state file')
output = read_link_state_file('veth99')
@ -7434,6 +7448,11 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
print(output)
self.assertNotIn(f'{address2}', output)
print('## ip route show table 212 dev veth99')
output = check_output('ip route show table 212 dev veth99')
print(output)
self.assertNotIn(f'{address2}', output)
self.teardown_nftset('addr4', 'network4', 'ifindex')
def test_dhcp_client_ipv4_dbus_status(self):