diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index 9856f397c2..75ee2ea8ad 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -143,19 +143,6 @@ static int dhcp4_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *li return 1; } -static int route_scope_from_address(const Route *route, const struct in_addr *self_addr) { - assert(route); - assert(self_addr); - - if (in4_addr_is_localhost(&route->dst.in) || - (in4_addr_is_set(self_addr) && in4_addr_equal(&route->dst.in, self_addr))) - return RT_SCOPE_HOST; - else if (in4_addr_is_null(&route->gw.in)) - return RT_SCOPE_LINK; - else - return RT_SCOPE_UNIVERSE; -} - static int dhcp_route_configure(Route *route, Link *link) { Route *ret; int r; @@ -215,6 +202,7 @@ static int link_set_dhcp_prefix_route(Link *link) { route->scope = RT_SCOPE_LINK; route->protocol = RTPROT_DHCP; route->table = link_get_dhcp_route_table(link); + route->mtu = link->network->dhcp_route_mtu; return dhcp_route_configure(route, link); } @@ -249,23 +237,105 @@ static int link_set_dhcp_route_to_gateway(Link *link, const struct in_addr *gw) return dhcp_route_configure(route, link); } -static int link_set_dhcp_static_routes(Link *link) { - _cleanup_free_ sd_dhcp_route **static_routes = NULL; - bool classless_route = false, static_route = false; - _cleanup_(route_freep) Route *route = NULL; - struct in_addr address; - int n, r; +static int dhcp_route_configure_auto( + Route *route, + Link *link, + const struct in_addr *gw) { + struct in_addr address, netmask, prefix; + uint8_t prefixlen; + int r; + + assert(route); assert(link); assert(link->dhcp_lease); + assert(gw); - if (!link->network->dhcp_use_routes) - return 0; + /* The route object may be reused in an iteration. All elements must be set or cleared. */ r = sd_dhcp_lease_get_address(link->dhcp_lease, &address); if (r < 0) return r; + r = sd_dhcp_lease_get_netmask(link->dhcp_lease, &netmask); + if (r < 0) + return r; + + prefix.s_addr = address.s_addr & netmask.s_addr; + prefixlen = in4_addr_netmask_to_prefixlen(&netmask); + + if (in4_addr_is_localhost(&route->dst.in)) { + if (in4_addr_is_set(gw)) + log_link_debug(link, "DHCP: requested route destination "IPV4_ADDRESS_FMT_STR"/%u is localhost, " + "ignoring gateway address "IPV4_ADDRESS_FMT_STR, + IPV4_ADDRESS_FMT_VAL(route->dst.in), route->dst_prefixlen, IPV4_ADDRESS_FMT_VAL(*gw)); + + route->scope = RT_SCOPE_HOST; + route->gw_family = AF_UNSPEC; + route->gw = IN_ADDR_NULL; + route->prefsrc = IN_ADDR_NULL; + + } else if (in4_addr_equal(&route->dst.in, &address)) { + if (in4_addr_is_set(gw)) + log_link_debug(link, "DHCP: requested route destination "IPV4_ADDRESS_FMT_STR"/%u is equivalent to the acquired address, " + "ignoring gateway address "IPV4_ADDRESS_FMT_STR, + IPV4_ADDRESS_FMT_VAL(route->dst.in), route->dst_prefixlen, IPV4_ADDRESS_FMT_VAL(*gw)); + + route->scope = RT_SCOPE_HOST; + route->gw_family = AF_UNSPEC; + route->gw = IN_ADDR_NULL; + route->prefsrc.in = address; + + } else if (route->dst_prefixlen >= prefixlen && + (route->dst.in.s_addr & netmask.s_addr) == prefix.s_addr) { + if (in4_addr_is_set(gw)) + log_link_debug(link, "DHCP: requested route destination "IPV4_ADDRESS_FMT_STR"/%u is in the assigned network " + IPV4_ADDRESS_FMT_STR"/%u, ignoring gateway address "IPV4_ADDRESS_FMT_STR, + IPV4_ADDRESS_FMT_VAL(route->dst.in), route->dst_prefixlen, + IPV4_ADDRESS_FMT_VAL(prefix), prefixlen, + IPV4_ADDRESS_FMT_VAL(*gw)); + + route->scope = RT_SCOPE_LINK; + route->gw_family = AF_UNSPEC; + route->gw = IN_ADDR_NULL; + route->prefsrc.in = address; + + } else { + if (in4_addr_is_null(gw)) { + log_link_debug(link, "DHCP: requested route destination "IPV4_ADDRESS_FMT_STR"/%u is not in the assigned network " + IPV4_ADDRESS_FMT_STR"/%u, but no gateway is specified, ignoring.", + IPV4_ADDRESS_FMT_VAL(route->dst.in), route->dst_prefixlen, + IPV4_ADDRESS_FMT_VAL(prefix), prefixlen); + return 0; + } + + r = link_set_dhcp_route_to_gateway(link, gw); + if (r < 0) + return r; + + route->scope = RT_SCOPE_UNIVERSE; + route->gw_family = AF_INET; + route->gw.in = *gw; + route->prefsrc.in = address; + } + + return dhcp_route_configure(route, link); +} + +static int link_set_dhcp_static_routes(Link *link, struct in_addr *ret_default_gw) { + _cleanup_free_ sd_dhcp_route **static_routes = NULL; + bool classless_route = false, static_route = false; + _cleanup_(route_freep) Route *route = NULL; + struct in_addr default_gw = {}; + int n, r; + + assert(link); + assert(link->dhcp_lease); + assert(ret_default_gw); + + if (!link->network->dhcp_use_routes) + return 0; + n = sd_dhcp_lease_get_routes(link->dhcp_lease, &static_routes); if (IN_SET(n, 0, -ENODATA)) { log_link_debug(link, "DHCP: No static routes received from DHCP server."); @@ -302,11 +372,13 @@ static int link_set_dhcp_static_routes(Link *link) { route->mtu = link->network->dhcp_route_mtu; for (int i = 0; i < n; i++) { + struct in_addr gw; + if (sd_dhcp_route_get_option(static_routes[i]) != (classless_route ? SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE : SD_DHCP_OPTION_STATIC_ROUTE)) continue; - r = sd_dhcp_route_get_gateway(static_routes[i], &route->gw.in); + r = sd_dhcp_route_get_gateway(static_routes[i], &gw); if (r < 0) return r; @@ -318,21 +390,24 @@ static int link_set_dhcp_static_routes(Link *link) { if (r < 0) return r; - route->scope = route_scope_from_address(route, &address); - if (IN_SET(route->scope, RT_SCOPE_LINK, RT_SCOPE_UNIVERSE)) - route->prefsrc.in = address; - else - route->prefsrc = IN_ADDR_NULL; + /* When classless static routes are provided, then router option will be ignored. To + * use the default gateway later in other routes, e.g., routes to dns servers, here we + * need to find the default gateway in the classless static routes. */ + if (classless_route && + in4_addr_is_null(&route->dst.in) && route->dst_prefixlen == 0 && + in4_addr_is_null(&default_gw)) + default_gw = gw; - r = dhcp_route_configure(route, link); + r = dhcp_route_configure_auto(route, link, &gw); if (r < 0) return r; } + *ret_default_gw = default_gw; return classless_route; } -static int link_set_dhcp_gateway(Link *link) { +static int link_set_dhcp_gateway(Link *link, struct in_addr *ret_gw) { _cleanup_(route_freep) Route *route = NULL; const struct in_addr *router; struct in_addr address; @@ -341,6 +416,7 @@ static int link_set_dhcp_gateway(Link *link) { assert(link); assert(link->dhcp_lease); + assert(ret_gw); if (!link->network->dhcp_use_gateway) return 0; @@ -407,18 +483,19 @@ static int link_set_dhcp_gateway(Link *link) { return r; } + *ret_gw = router[0]; return 0; } -static int link_set_dns_routes(Link *link) { +static int link_set_dns_routes(Link *link, const struct in_addr *gw) { _cleanup_(route_freep) Route *route = NULL; const struct in_addr *dns; - struct in_addr address; int n, r; assert(link); assert(link->dhcp_lease); assert(link->network); + assert(gw); if (!link->network->dhcp_use_dns || !link->network->dhcp_routes_to_dns) @@ -430,26 +507,24 @@ static int link_set_dns_routes(Link *link) { if (n < 0) return n; - r = sd_dhcp_lease_get_address(link->dhcp_lease, &address); - if (r < 0) - return r; - r = route_new(&route); if (r < 0) return r; route->family = AF_INET; route->dst_prefixlen = 32; - route->prefsrc.in = address; - route->scope = RT_SCOPE_LINK; route->protocol = RTPROT_DHCP; route->priority = link->network->dhcp_route_metric; route->table = link_get_dhcp_route_table(link); + route->mtu = link->network->dhcp_route_mtu; for (int i = 0; i < n; i ++) { + if (in4_addr_is_null(&dns[i])) + continue; + route->dst.in = dns[i]; - r = dhcp_route_configure(route, link); + r = dhcp_route_configure_auto(route, link, gw); if (r < 0) return r; } @@ -458,6 +533,7 @@ static int link_set_dns_routes(Link *link) { } static int link_set_dhcp_routes(Link *link) { + struct in_addr gw = {}; Route *rt; int r; @@ -484,18 +560,18 @@ static int link_set_dhcp_routes(Link *link) { if (r < 0) return log_link_error_errno(link, r, "DHCP error: Could not set prefix route: %m"); - r = link_set_dhcp_static_routes(link); + r = link_set_dhcp_static_routes(link, &gw); if (r < 0) return log_link_error_errno(link, r, "DHCP error: Could not set static routes: %m"); if (r == 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. */ - r = link_set_dhcp_gateway(link); + r = link_set_dhcp_gateway(link, &gw); if (r < 0) return log_link_error_errno(link, r, "DHCP error: Could not set gateway: %m"); } - r = link_set_dns_routes(link); + r = link_set_dns_routes(link, &gw); if (r < 0) return log_link_error_errno(link, r, "DHCP error: Could not set routes to DNS servers: %m"); diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 148ff6e8c0..c483d54767 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -461,7 +461,7 @@ def remove_unit_from_networkd_path(units): shutil.rmtree(os.path.join(network_unit_file_path, unit + '.d')) def start_dnsmasq(additional_options='', ipv4_range='192.168.5.10,192.168.5.200', ipv6_range='2600::10,2600::20', lease_time='1h'): - dnsmasq_command = f'dnsmasq -8 /var/run/networkd-ci/test-dnsmasq-log-file --log-queries=extra --log-dhcp --pid-file=/var/run/networkd-ci/test-test-dnsmasq.pid --conf-file=/dev/null --interface=veth-peer --enable-ra --dhcp-range={ipv6_range},{lease_time} --dhcp-range={ipv4_range},{lease_time} -R --dhcp-leasefile=/var/run/networkd-ci/lease --dhcp-option=26,1492 --dhcp-option=option:router,192.168.5.1 --dhcp-option=33,192.168.5.4,192.168.5.5 --port=0 ' + additional_options + dnsmasq_command = f'dnsmasq -8 /var/run/networkd-ci/test-dnsmasq-log-file --log-queries=extra --log-dhcp --pid-file=/var/run/networkd-ci/test-test-dnsmasq.pid --conf-file=/dev/null --interface=veth-peer --enable-ra --dhcp-range={ipv6_range},{lease_time} --dhcp-range={ipv4_range},{lease_time} -R --dhcp-leasefile=/var/run/networkd-ci/lease --dhcp-option=26,1492 --dhcp-option=option:router,192.168.5.1 --port=0 ' + additional_options check_output(dnsmasq_command) def stop_dnsmasq(pid_file): @@ -3852,55 +3852,71 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): self.assertRegex(output, r'192.168.5.8 proto dhcp scope link src 192.168.5.181 metric 1024') def test_dhcp_client_ipv4_use_routes_gateway(self): - for (routes, gateway, dnsroutes) in itertools.product([True, False, None], repeat=3): + for (routes, gateway, dnsroutes, classless) in itertools.product([True, False], repeat=4): self.setUp() - with self.subTest(routes=routes, gateway=gateway, dnsroutes=dnsroutes): - self._test_dhcp_client_ipv4_use_routes_gateway(routes, gateway, dnsroutes) + with self.subTest(routes=routes, gateway=gateway, dnsroutes=dnsroutes, classless=classless): + self._test_dhcp_client_ipv4_use_routes_gateway(routes, gateway, dnsroutes, classless) self.tearDown() - def _test_dhcp_client_ipv4_use_routes_gateway(self, routes, gateway, dnsroutes): + def _test_dhcp_client_ipv4_use_routes_gateway(self, use_routes, use_gateway, dnsroutes, classless): testunit = 'dhcp-client-ipv4-use-routes-use-gateway.network' testunits = ['25-veth.netdev', 'dhcp-server-veth-peer.network', testunit] - if routes != None: - testunits.append(f'{testunit}.d/use-routes-{routes}.conf'); - if gateway != None: - testunits.append(f'{testunit}.d/use-gateway-{gateway}.conf'); - if dnsroutes != None: - testunits.append(f'{testunit}.d/use-dns-routes-{dnsroutes}.conf'); + testunits.append(f'{testunit}.d/use-routes-{use_routes}.conf'); + testunits.append(f'{testunit}.d/use-gateway-{use_gateway}.conf'); + testunits.append(f'{testunit}.d/use-dns-routes-{dnsroutes}.conf'); copy_unit_to_networkd_unit_path(*testunits, dropins=False) start_networkd() self.wait_online(['veth-peer:carrier']) - start_dnsmasq(additional_options='--dhcp-option=option:dns-server,192.168.5.6,192.168.5.7', lease_time='2m') + additional_options = '--dhcp-option=option:dns-server,192.168.5.10,8.8.8.8 --dhcp-option=option:static-route,192.168.5.100,192.168.5.2,8.8.8.8,192.168.5.3' + if classless: + additional_options += ' --dhcp-option=option:classless-static-route,0.0.0.0/0,192.168.5.4,8.0.0.0/8,192.168.5.5' + start_dnsmasq(additional_options=additional_options, lease_time='2m') self.wait_online(['veth99:routable', 'veth-peer:routable']) - output = check_output('ip route show dev veth99') + output = check_output('ip -4 route show dev veth99') print(output) - # UseRoutes= defaults to true - useroutes = routes in [True, None] - # UseGateway= defaults to useroutes - usegateway = useroutes if gateway == None else gateway - # Check UseRoutes= - if useroutes: - self.assertRegex(output, r'192.168.5.0/24 via 192.168.5.5 proto dhcp src 192.168.5.181 metric 1024') + if use_routes: + if classless: + self.assertRegex(output, r'default via 192.168.5.4 proto dhcp src 192.168.5.[0-9]* metric 1024') + self.assertRegex(output, r'8.0.0.0/8 via 192.168.5.5 proto dhcp src 192.168.5.[0-9]* metric 1024') + self.assertRegex(output, r'192.168.5.4 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') + self.assertRegex(output, r'192.168.5.5 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') + else: + self.assertRegex(output, r'192.168.5.0/24 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') + self.assertRegex(output, r'8.0.0.0/8 via 192.168.5.3 proto dhcp src 192.168.5.[0-9]* metric 1024') + self.assertRegex(output, r'192.168.5.3 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') else: - self.assertNotRegex(output, r'192.168.5.5') + self.assertNotRegex(output, r'default via 192.168.5.4 proto dhcp src 192.168.5.[0-9]* metric 1024') + self.assertNotRegex(output, r'8.0.0.0/8 via 192.168.5.5 proto dhcp src 192.168.5.[0-9]* metric 1024') + self.assertNotRegex(output, r'192.168.5.4 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') + self.assertNotRegex(output, r'192.168.5.5 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') + self.assertNotRegex(output, r'192.168.5.0/24 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') + self.assertNotRegex(output, r'8.0.0.0/8 via 192.168.5.3 proto dhcp src 192.168.5.[0-9]* metric 1024') + self.assertNotRegex(output, r'192.168.5.3 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') # Check UseGateway= - if usegateway: - self.assertRegex(output, r'default via 192.168.5.1 proto dhcp src 192.168.5.181 metric 1024') + if use_gateway and (not classless or not use_routes): + self.assertRegex(output, r'default via 192.168.5.1 proto dhcp src 192.168.5.[0-9]* metric 1024') + self.assertRegex(output, r'192.168.5.1 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') else: - self.assertNotRegex(output, r'default via 192.168.5.1') + self.assertNotRegex(output, r'default via 192.168.5.1 proto dhcp src 192.168.5.[0-9]* metric 1024') + self.assertNotRegex(output, r'192.168.5.1 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') - # Check RoutesToDNS=, which defaults to false + # Check RoutesToDNS= if dnsroutes: - self.assertRegex(output, r'192.168.5.6 proto dhcp scope link src 192.168.5.181 metric 1024') - self.assertRegex(output, r'192.168.5.7 proto dhcp scope link src 192.168.5.181 metric 1024') + self.assertRegex(output, r'192.168.5.10 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') + elif use_gateway: + self.assertRegex(output, r'8.8.8.8 via 192.168.5.1 proto dhcp src 192.168.5.[0-9]* metric 1024') + else: + self.assertNotRegex(output, r'8.8.8.8 via 192.168.5.[0-9]* proto dhcp src 192.168.5.[0-9]* metric 1024') else: - self.assertNotRegex(output, r'192.168.5.6') - self.assertNotRegex(output, r'192.168.5.7') + self.assertNotRegex(output, r'192.168.5.10 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') + self.assertNotRegex(output, r'8.8.8.8 via 192.168.5.[0-9]* proto dhcp src 192.168.5.[0-9]* metric 1024') def test_dhcp_client_ipv4_ipv6(self): copy_unit_to_networkd_unit_path('25-veth.netdev', 'dhcp-server-veth-peer.network', 'dhcp-client-ipv6-only.network', @@ -3948,7 +3964,6 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): self.assertRegex(output, 'default via 192.168.5.1 proto dhcp') if main_table_is_empty: self.assertRegex(output, '192.168.5.0/24 proto dhcp') - self.assertRegex(output, '192.168.5.0/24 via 192.168.5.5 proto dhcp') self.assertRegex(output, '192.168.5.1 proto dhcp scope link') print('## dnsmasq log') @@ -4020,7 +4035,6 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): print(output) self.assertRegex(output, r'default via 192.168.5.1 proto dhcp src 192.168.5.[0-9]* metric 1024') self.assertRegex(output, r'192.168.5.0/24 proto kernel scope link src 192.168.5.250') - self.assertRegex(output, r'192.168.5.0/24 via 192.168.5.5 proto dhcp src 192.168.5.[0-9]* metric 1024') self.assertRegex(output, r'192.168.5.1 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') def test_dhcp_route_table_id(self): @@ -4273,7 +4287,6 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): print(output) self.assertRegex(output, 'default via 192.168.5.1 dev veth99 proto dhcp src 192.168.5.') self.assertRegex(output, '192.168.5.0/24 dev veth99 proto kernel scope link src 192.168.5') - self.assertRegex(output, '192.168.5.0/24 via 192.168.5.5 dev veth99 proto dhcp') self.assertRegex(output, '192.168.5.1 dev veth99 proto dhcp scope link src 192.168.5') print('## ip route show table main dev veth99')