diff --git a/man/systemd.network.xml b/man/systemd.network.xml index cd1aa73f469..cdcca4aca80 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -1322,6 +1322,14 @@ project='man-pages'>resolv.conf5. + + RoutesToDNS= + + When true, the routes to the DNS servers received from the DHCP server will be + configured. When UseDNS= is disabled, this setting is ignored. + Defaults to false. + + UseNTP= diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index 1bb1df32e9e..f20254fc827 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -15,12 +15,13 @@ #include "string-util.h" #include "sysctl-util.h" -static int dhcp_remove_routes(Link *link, sd_dhcp_lease *lease, sd_dhcp_lease *new_lease, struct in_addr *address); -static int dhcp_remove_router(Link *link, sd_dhcp_lease *lease, struct in_addr *address); -static int dhcp_remove_address(Link *link, sd_dhcp_lease *lease, struct in_addr *address); +static int dhcp_remove_routes(Link *link, sd_dhcp_lease *lease, const struct in_addr *address, bool remove_all); +static int dhcp_remove_router(Link *link, sd_dhcp_lease *lease, const struct in_addr *address, bool remove_all); +static int dhcp_remove_dns_routes(Link *link, sd_dhcp_lease *lease, const struct in_addr *address, bool remove_all); +static int dhcp_remove_address(Link *link, sd_dhcp_lease *lease, const struct in_addr *address); void dhcp4_release_old_lease(Link *link) { - union in_addr_union address = IN_ADDR_NULL, address_old = IN_ADDR_NULL; + struct in_addr address = {}, address_old = {}; assert(link); @@ -29,15 +30,15 @@ void dhcp4_release_old_lease(Link *link) { assert(link->dhcp_lease); - (void) sd_dhcp_lease_get_address(link->dhcp_lease_old, &address_old.in); - (void) sd_dhcp_lease_get_address(link->dhcp_lease, &address.in); + (void) sd_dhcp_lease_get_address(link->dhcp_lease_old, &address_old); + (void) sd_dhcp_lease_get_address(link->dhcp_lease, &address); - (void) dhcp_remove_routes(link, link->dhcp_lease_old, link->dhcp_lease, &address_old.in); + (void) dhcp_remove_routes(link, link->dhcp_lease_old, &address_old, false); + (void) dhcp_remove_router(link, link->dhcp_lease_old, &address_old, false); + (void) dhcp_remove_dns_routes(link, link->dhcp_lease_old, &address_old, false); - if (!in_addr_equal(AF_INET, &address_old, &address)) { - (void) dhcp_remove_router(link, link->dhcp_lease_old, &address_old.in); - (void) dhcp_remove_address(link, link->dhcp_lease_old, &address_old.in); - } + if (!in4_addr_equal(&address_old, &address)) + (void) dhcp_remove_address(link, link->dhcp_lease_old, &address_old); link->dhcp_lease_old = sd_dhcp_lease_unref(link->dhcp_lease_old); link_dirty(link); @@ -84,6 +85,77 @@ static int route_scope_from_address(const Route *route, const struct in_addr *se return RT_SCOPE_UNIVERSE; } +static int dhcp_route_configure(Route **route, Link *link) { + int r; + + assert(route); + assert(*route); + assert(link); + + if (set_contains(link->dhcp_routes, *route)) + return 0; + + r = route_configure(*route, link, dhcp4_route_handler); + if (r <= 0) + return r; + + link->dhcp4_messages++; + + r = set_put(link->dhcp_routes, *route); + if (r < 0) + return r; + + TAKE_PTR(*route); + return 0; +} + +static int link_set_dns_routes(Link *link, const struct in_addr *address) { + const struct in_addr *dns; + uint32_t table; + int i, n, r; + + assert(link); + assert(link->dhcp_lease); + assert(link->network); + + if (!link->network->dhcp_use_dns || + !link->network->dhcp_routes_to_dns) + return 0; + + n = sd_dhcp_lease_get_dns(link->dhcp_lease, &dns); + if (IN_SET(n, 0, -ENODATA)) + return 0; + if (n < 0) + return log_link_warning_errno(link, n, "DHCP error: could not get DNS servers: %m"); + + table = link_get_dhcp_route_table(link); + + for (i = 0; i < n; i ++) { + _cleanup_(route_freep) Route *route = NULL; + + r = route_new(&route); + if (r < 0) + return log_link_error_errno(link, r, "Could not allocate route: %m"); + + /* Set routes to DNS servers. */ + + route->family = AF_INET; + route->dst.in = dns[i]; + 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 = table; + + r = dhcp_route_configure(&route, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not set route to DNS server: %m"); + } + + return 0; +} + static int link_set_dhcp_routes(Link *link) { _cleanup_free_ sd_dhcp_route **static_routes = NULL; bool classless_route = false, static_route = false; @@ -108,6 +180,13 @@ static int link_set_dhcp_routes(Link *link) { * the addresses now, let's not configure the routes either. */ return 0; + r = set_ensure_allocated(&link->dhcp_routes, &route_full_hash_ops); + if (r < 0) + return log_oom(); + + /* Clear old entries in case the set was already allocated */ + set_clear(link->dhcp_routes); + table = link_get_dhcp_route_table(link); r = sd_dhcp_lease_get_address(link->dhcp_lease, &address); @@ -155,11 +234,12 @@ static int link_set_dhcp_routes(Link *link) { if (IN_SET(route->scope, RT_SCOPE_LINK, RT_SCOPE_UNIVERSE)) route->prefsrc.in = address; - r = route_configure(route, link, dhcp4_route_handler); + if (set_contains(link->dhcp_routes, route)) + continue; + + r = dhcp_route_configure(&route, link); if (r < 0) - return log_link_error_errno(link, r, "Could not set host route: %m"); - if (r > 0) - link->dhcp4_messages++; + return log_link_error_errno(link, r, "Could not set route: %m"); } r = sd_dhcp_lease_get_router(link->dhcp_lease, &router); @@ -194,11 +274,9 @@ static int link_set_dhcp_routes(Link *link) { route_gw->priority = link->network->dhcp_route_metric; route_gw->table = table; - r = route_configure(route_gw, link, dhcp4_route_handler); + r = dhcp_route_configure(&route_gw, link); if (r < 0) return log_link_error_errno(link, r, "Could not set host route: %m"); - if (r > 0) - link->dhcp4_messages++; r = route_new(&route); if (r < 0) @@ -211,42 +289,18 @@ static int link_set_dhcp_routes(Link *link) { route->priority = link->network->dhcp_route_metric; route->table = table; - r = route_configure(route, link, dhcp4_route_handler); + r = dhcp_route_configure(&route, link); if (r < 0) - return log_link_error_errno(link, r, "Could not set routes: %m"); - if (r > 0) - link->dhcp4_messages++; + return log_link_error_errno(link, r, "Could not set router: %m"); } - return 0; + return link_set_dns_routes(link, &address); } -static bool route_present_in_routes(const Route *route, sd_dhcp_route **routes, unsigned n_routes) { - assert(n_routes == 0 || routes); - - for (unsigned j = 0; j < n_routes; j++) { - union in_addr_union a; - unsigned char l; - - assert_se(sd_dhcp_route_get_gateway(routes[j], &a.in) >= 0); - if (!in_addr_equal(AF_INET, &a, &route->gw)) - continue; - assert_se(sd_dhcp_route_get_destination(routes[j], &a.in) >= 0); - if (!in_addr_equal(AF_INET, &a, &route->dst)) - continue; - assert_se(sd_dhcp_route_get_destination_prefix_length(routes[j], &l) >= 0); - if (l != route->dst_prefixlen) - continue; - return true; - } - - return false; -} - -static int dhcp_remove_routes(Link *link, sd_dhcp_lease *lease, sd_dhcp_lease *new_lease, struct in_addr *address) { - _cleanup_free_ sd_dhcp_route **routes = NULL, **new_routes = NULL; +static int dhcp_remove_routes(Link *link, sd_dhcp_lease *lease, const struct in_addr *address, bool remove_all) { + _cleanup_free_ sd_dhcp_route **routes = NULL; uint32_t table; - int m = 0, n, i, r; + int n, i, r; assert(link); assert(address); @@ -260,14 +314,6 @@ static int dhcp_remove_routes(Link *link, sd_dhcp_lease *lease, sd_dhcp_lease *n else if (n < 0) return log_link_error_errno(link, n, "DHCP error: Failed to get routes: %m"); - if (new_lease) { - m = sd_dhcp_lease_get_routes(new_lease, &new_routes); - if (m == -ENODATA) - m = 0; - else if (m < 0) - return log_link_error_errno(link, m, "DHCP error: Failed to get routes: %m"); - } - table = link_get_dhcp_route_table(link); for (i = 0; i < n; i++) { @@ -287,7 +333,7 @@ static int dhcp_remove_routes(Link *link, sd_dhcp_lease *lease, sd_dhcp_lease *n if (IN_SET(route->scope, RT_SCOPE_LINK, RT_SCOPE_UNIVERSE)) route->prefsrc.in = *address; - if (route_present_in_routes(route, new_routes, m)) + if (!remove_all && set_contains(link->dhcp_routes, route)) continue; (void) route_remove(route, link, NULL); @@ -296,7 +342,7 @@ static int dhcp_remove_routes(Link *link, sd_dhcp_lease *lease, sd_dhcp_lease *n return n; } -static int dhcp_remove_router(Link *link, sd_dhcp_lease *lease, struct in_addr *address) { +static int dhcp_remove_router(Link *link, sd_dhcp_lease *lease, const struct in_addr *address, bool remove_all) { _cleanup_(route_freep) Route *route_gw = NULL, *route = NULL; const struct in_addr *router; uint32_t table; @@ -334,7 +380,8 @@ static int dhcp_remove_router(Link *link, sd_dhcp_lease *lease, struct in_addr * route_gw->priority = link->network->dhcp_route_metric; route_gw->table = table; - (void) route_remove(route_gw, link, NULL); + if (remove_all || !set_contains(link->dhcp_routes, route_gw)) + (void) route_remove(route_gw, link, NULL); r = route_new(&route); if (r < 0) @@ -347,12 +394,59 @@ static int dhcp_remove_router(Link *link, sd_dhcp_lease *lease, struct in_addr * route->priority = link->network->dhcp_route_metric; route->table = table; - (void) route_remove(route, link, NULL); + if (remove_all || !set_contains(link->dhcp_routes, route)) + (void) route_remove(route, link, NULL); return 0; } -static int dhcp_remove_address(Link *link, sd_dhcp_lease *lease, struct in_addr *address) { +static int dhcp_remove_dns_routes(Link *link, sd_dhcp_lease *lease, const struct in_addr *address, bool remove_all) { + const struct in_addr *dns; + uint32_t table; + int i, n, r; + + assert(link); + assert(lease); + assert(link->network); + + if (!link->network->dhcp_use_dns || + !link->network->dhcp_routes_to_dns) + return 0; + + n = sd_dhcp_lease_get_dns(lease, &dns); + if (IN_SET(n, 0, -ENODATA)) + return 0; + if (n < 0) + return log_link_warning_errno(link, n, "DHCP error: could not get DNS servers: %m"); + + table = link_get_dhcp_route_table(link); + + for (i = 0; i < n; i ++) { + _cleanup_(route_freep) Route *route = NULL; + + r = route_new(&route); + if (r < 0) + return log_link_error_errno(link, r, "Could not allocate route: %m"); + + route->family = AF_INET; + route->dst.in = dns[i]; + 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 = table; + + if (!remove_all && set_contains(link->dhcp_routes, route)) + continue; + + (void) route_remove(route, link, NULL); + } + + return 0; +} + +static int dhcp_remove_address(Link *link, sd_dhcp_lease *lease, const struct in_addr *address) { _cleanup_(address_freep) Address *a = NULL; struct in_addr netmask; int r; @@ -439,8 +533,9 @@ static int dhcp_lease_lost(Link *link) { link->dhcp4_configured = false; (void) sd_dhcp_lease_get_address(link->dhcp_lease, &address); - (void) dhcp_remove_routes(link, link->dhcp_lease, NULL, &address); - (void) dhcp_remove_router(link, link->dhcp_lease, &address); + (void) dhcp_remove_routes(link, link->dhcp_lease, &address, true); + (void) dhcp_remove_router(link, link->dhcp_lease, &address, true); + (void) dhcp_remove_dns_routes(link, link->dhcp_lease, &address, true); (void) dhcp_remove_address(link, link->dhcp_lease, &address); (void) dhcp_reset_mtu(link); (void) dhcp_reset_hostname(link); diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 63f74220964..8ca4583034a 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -703,6 +703,7 @@ static Link *link_free(Link *link) { sd_dhcp_server_unref(link->dhcp_server); sd_dhcp_client_unref(link->dhcp_client); sd_dhcp_lease_unref(link->dhcp_lease); + set_free(link->dhcp_routes); link_lldp_emit_stop(link); diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index bfdcef0e50f..cd88388015b 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -83,6 +83,7 @@ typedef struct Link { sd_dhcp_client *dhcp_client; sd_dhcp_lease *dhcp_lease, *dhcp_lease_old; + Set *dhcp_routes; char *lease_file; uint32_t original_mtu; unsigned dhcp4_messages; diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 3501ffdd104..f5f837ad63b 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -142,6 +142,7 @@ Route.FastOpenNoCookie, config_parse_fast_open_no_cookie, Route.TTLPropagate, config_parse_route_ttl_propagate, 0, 0 DHCPv4.ClientIdentifier, config_parse_dhcp_client_identifier, 0, offsetof(Network, dhcp_client_identifier) DHCPv4.UseDNS, config_parse_bool, 0, offsetof(Network, dhcp_use_dns) +DHCPv4.RoutesToDNS, config_parse_bool, 0, offsetof(Network, dhcp_routes_to_dns) DHCPv4.UseNTP, config_parse_bool, 0, offsetof(Network, dhcp_use_ntp) DHCPv4.UseMTU, config_parse_bool, 0, offsetof(Network, dhcp_use_mtu) DHCPv4.UseHostname, config_parse_bool, 0, offsetof(Network, dhcp_use_hostname) diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 82fad492a8f..0fa800841cd 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -91,6 +91,7 @@ struct Network { bool dhcp_broadcast; int dhcp_critical; bool dhcp_use_dns; + bool dhcp_routes_to_dns; bool dhcp_use_ntp; bool dhcp_use_mtu; bool dhcp_use_routes; diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c index a4b56ab46bf..25ab527284d 100644 --- a/src/network/networkd-route.c +++ b/src/network/networkd-route.c @@ -207,6 +207,103 @@ static int route_compare_func(const Route *a, const Route *b) { DEFINE_PRIVATE_HASH_OPS(route_hash_ops, Route, route_hash_func, route_compare_func); +static void route_full_hash_func(const Route *route, struct siphash *state) { + assert(route); + + siphash24_compress(&route->family, sizeof(route->family), state); + + switch (route->family) { + case AF_INET: + case AF_INET6: + siphash24_compress(&route->gw, FAMILY_ADDRESS_SIZE(route->family), state); + siphash24_compress(&route->dst, FAMILY_ADDRESS_SIZE(route->family), state); + siphash24_compress(&route->dst_prefixlen, sizeof(route->dst_prefixlen), state); + siphash24_compress(&route->src, FAMILY_ADDRESS_SIZE(route->family), state); + siphash24_compress(&route->src_prefixlen, sizeof(route->src_prefixlen), state); + siphash24_compress(&route->prefsrc, FAMILY_ADDRESS_SIZE(route->family), state); + + siphash24_compress(&route->tos, sizeof(route->tos), state); + siphash24_compress(&route->priority, sizeof(route->priority), state); + siphash24_compress(&route->table, sizeof(route->table), state); + siphash24_compress(&route->protocol, sizeof(route->protocol), state); + siphash24_compress(&route->scope, sizeof(route->scope), state); + siphash24_compress(&route->type, sizeof(route->type), state); + + break; + default: + /* treat any other address family as AF_UNSPEC */ + break; + } +} + +static int route_full_compare_func(const Route *a, const Route *b) { + int r; + + r = CMP(a->family, b->family); + if (r != 0) + return r; + + switch (a->family) { + case AF_INET: + case AF_INET6: + r = CMP(a->dst_prefixlen, b->dst_prefixlen); + if (r != 0) + return r; + + r = CMP(a->src_prefixlen, b->src_prefixlen); + if (r != 0) + return r; + + r = CMP(a->tos, b->tos); + if (r != 0) + return r; + + r = CMP(a->priority, b->priority); + if (r != 0) + return r; + + r = CMP(a->table, b->table); + if (r != 0) + return r; + + r = CMP(a->protocol, b->protocol); + if (r != 0) + return r; + + r = CMP(a->scope, b->scope); + if (r != 0) + return r; + + r = CMP(a->type, b->type); + if (r != 0) + return r; + + r = memcmp(&a->gw, &b->gw, FAMILY_ADDRESS_SIZE(a->family)); + if (r != 0) + return r; + + r = memcmp(&a->dst, &b->dst, FAMILY_ADDRESS_SIZE(a->family)); + if (r != 0) + return r; + + r = memcmp(&a->src, &b->src, FAMILY_ADDRESS_SIZE(a->family)); + if (r != 0) + return r; + + return memcmp(&a->prefsrc, &b->prefsrc, FAMILY_ADDRESS_SIZE(a->family)); + default: + /* treat any other address family as AF_UNSPEC */ + return 0; + } +} + +DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR( + route_full_hash_ops, + Route, + route_full_hash_func, + route_full_compare_func, + route_free); + bool route_equal(Route *r1, Route *r2) { if (r1 == r2) return true; diff --git a/src/network/networkd-route.h b/src/network/networkd-route.h index fda7ddedfab..9d9c980d903 100644 --- a/src/network/networkd-route.h +++ b/src/network/networkd-route.h @@ -49,6 +49,8 @@ struct Route { LIST_FIELDS(Route, routes); }; +extern const struct hash_ops route_full_hash_ops; + int route_new(Route **ret); void route_free(Route *route); int route_configure(Route *route, Link *link, link_netlink_message_handler_t callback); diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network index 69a42815427..c3af95db0db 100644 --- a/test/fuzz/fuzz-network-parser/directives.network +++ b/test/fuzz/fuzz-network-parser/directives.network @@ -69,6 +69,7 @@ SendRelease= MaxAttempts= [DHCPv4] UseDNS= +RoutesToDNS= UseDomains= UseRoutes= IAID= diff --git a/test/test-network/conf/dhcp-client-ipv4-only-ipv6-disabled.network b/test/test-network/conf/dhcp-client-ipv4-only-ipv6-disabled.network index c980bf9fca5..b0d0d8605e3 100644 --- a/test/test-network/conf/dhcp-client-ipv4-only-ipv6-disabled.network +++ b/test/test-network/conf/dhcp-client-ipv4-only-ipv6-disabled.network @@ -4,3 +4,6 @@ Name=veth99 [Network] DHCP=ipv4 IPv6AcceptRA=false + +[DHCPv4] +RoutesToDNS=yes diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 2f13bc2bbb0..d73895aeab1 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -2391,13 +2391,47 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): start_networkd() self.wait_online(['veth-peer:carrier']) - start_dnsmasq() + start_dnsmasq(additional_options='--dhcp-option=option:dns-server,192.168.5.6,192.168.5.7', lease_time='2m') self.wait_online(['veth99:routable', 'veth-peer:routable']) output = check_output(*networkctl_cmd, 'status', 'veth99', env=env) print(output) self.assertNotRegex(output, '2600::') self.assertRegex(output, '192.168.5') + self.assertRegex(output, '192.168.5.6') + self.assertRegex(output, '192.168.5.7') + + # checking routes to DNS servers + output = check_output('ip route show dev veth99') + print(output) + self.assertRegex(output, r'192.168.5.1 proto dhcp scope link src 192.168.5.181 metric 1024') + 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') + + stop_dnsmasq(dnsmasq_pid_file) + start_dnsmasq(additional_options='--dhcp-option=option:dns-server,192.168.5.1,192.168.5.7,192.168.5.8', lease_time='2m') + + # Sleep for 120 sec as the dnsmasq minimum lease time can only be set to 120 + print('Wait for the dynamic address to be renewed') + time.sleep(125) + + self.wait_online(['veth99:routable', 'veth-peer:routable']) + + output = check_output(*networkctl_cmd, 'status', 'veth99', env=env) + print(output) + self.assertNotRegex(output, '2600::') + self.assertRegex(output, '192.168.5') + self.assertNotRegex(output, '192.168.5.6') + self.assertRegex(output, '192.168.5.7') + self.assertRegex(output, '192.168.5.8') + + # checking routes to DNS servers + output = check_output('ip route show dev veth99') + print(output) + self.assertNotRegex(output, r'192.168.5.6') + self.assertRegex(output, r'192.168.5.1 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.8 proto dhcp scope link src 192.168.5.181 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',