diff --git a/man/systemd.network.xml b/man/systemd.network.xml index f2777e640f..a10a4616d0 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -2010,8 +2010,10 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix Gateway= Takes the gateway address or the special values _dhcp4 and - _ipv6ra. If _dhcp4 or _ipv6ra is - set, then the gateway address provided by DHCPv4 or IPv6 RA is used. + _ipv6ra. If _dhcp4 or _ipv6ra is set, then + the gateway address provided by DHCPv4 or IPv6 RA is used. When_dhcp4, the + acquired DHCPv4 address will be used as the preferred source address of the route, unless it is + explicitly configured in PreferredSource=. @@ -2117,10 +2119,12 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix PreferredSource= - The preferred source address of the route. The address must be in the format described - in + The preferred source address of the route. Takes no or an address + in the format described in inet_pton3. - + If Gateway=_dhcp4 is specified, defaults to the acquired DHCPv4 address. + Otherwise, defaults to unset. The value no may be useful to configure a route + with Gateway=_dhcp4 without setting preferred source route address. diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index 30abef49a2..926e769258 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -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; diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index 42bed0d642..9c579db445 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -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) diff --git a/src/network/networkd-route-nexthop.c b/src/network/networkd-route-nexthop.c index bf0271807b..9482b25bb8 100644 --- a/src/network/networkd-route-nexthop.c +++ b/src/network/networkd-route-nexthop.c @@ -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. */ diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c index f6386e0426..e41b4c31be 100644 --- a/src/network/networkd-route.c +++ b/src/network/networkd-route.c @@ -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; } diff --git a/src/network/networkd-route.h b/src/network/networkd-route.h index b9d2dbf9a6..46bbe2899e 100644 --- a/src/network/networkd-route.h +++ b/src/network/networkd-route.h @@ -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; diff --git a/test/test-network/conf/25-dhcp-client-ipv4-only.network b/test/test-network/conf/25-dhcp-client-ipv4-only.network index 7d79ee3485..30a5fcc22c 100644 --- a/test/test-network/conf/25-dhcp-client-ipv4-only.network +++ b/test/test-network/conf/25-dhcp-client-ipv4-only.network @@ -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 diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 53de32c4b1..c569e0d2ca 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -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):