Merge pull request #13070 from yuwata/network-set-route-to-dhcp-dns

This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2019-07-19 09:34:22 +02:00
commit f7e7bb6546
11 changed files with 308 additions and 64 deletions

View File

@ -1322,6 +1322,14 @@
project='man-pages'><refentrytitle>resolv.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>RoutesToDNS=</varname></term>
<listitem>
<para>When true, the routes to the DNS servers received from the DHCP server will be
configured. When <varname>UseDNS=</varname> is disabled, this setting is ignored.
Defaults to false.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>UseNTP=</varname></term>
<listitem>

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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)

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -69,6 +69,7 @@ SendRelease=
MaxAttempts=
[DHCPv4]
UseDNS=
RoutesToDNS=
UseDomains=
UseRoutes=
IAID=

View File

@ -4,3 +4,6 @@ Name=veth99
[Network]
DHCP=ipv4
IPv6AcceptRA=false
[DHCPv4]
RoutesToDNS=yes

View File

@ -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',