network: further rework for reconfiguring interfaces (#35059)

Follow-ups for #35035.
Split-out of #34989.
Fixes #35092.
This commit is contained in:
Luca Boccassi
2024-11-11 12:59:31 +00:00
committed by GitHub
10 changed files with 189 additions and 101 deletions
+70 -23
View File
@@ -613,8 +613,49 @@ static int dhcp_pd_finalize(Link *link) {
return 0;
}
void dhcp_pd_prefix_lost(Link *uplink) {
static void dhcp_pd_mark_unreachable_route(Manager *manager, NetworkConfigSource source) {
assert(manager);
Route *route;
SET_FOREACH(route, manager->routes) {
if (route->source != source)
continue;
if (route->family != AF_INET6)
continue;
if (route->nexthop.ifindex != 0) /* IPv6 unreachable has 0 ifindex. */
continue;
if (!route_type_is_reject(route->type))
continue;
route_mark(route);
}
}
static int dhcp_pd_remove_unreachable_route(Manager *manager, NetworkConfigSource source, bool only_marked) {
int ret = 0;
assert(manager);
Route *route;
SET_FOREACH(route, manager->routes) {
if (route->source != source)
continue;
if (route->family != AF_INET6)
continue;
if (route->nexthop.ifindex != 0) /* IPv6 unreachable has 0 ifindex. */
continue;
if (!route_type_is_reject(route->type))
continue;
if (only_marked && !route_is_marked(route))
continue;
RET_GATHER(ret, route_remove_and_cancel(route, manager));
}
return ret;
}
static void dhcp_pd_prefix_lost(Link *uplink, NetworkConfigSource source) {
Link *link;
int r;
@@ -630,22 +671,7 @@ void dhcp_pd_prefix_lost(Link *uplink) {
link_enter_failed(link);
}
SET_FOREACH(route, uplink->manager->routes) {
if (!IN_SET(route->source, NETWORK_CONFIG_SOURCE_DHCP4, NETWORK_CONFIG_SOURCE_DHCP6))
continue;
if (route->family != AF_INET6)
continue;
if (route->type != RTN_UNREACHABLE)
continue;
if (!set_contains(uplink->dhcp_pd_prefixes,
&(struct in_addr_prefix) {
.family = AF_INET6,
.prefixlen = route->dst_prefixlen,
.address = route->dst }))
continue;
(void) route_remove_and_cancel(route, uplink->manager);
}
(void) dhcp_pd_remove_unreachable_route(uplink->manager, source, /* only_marked = */ false);
set_clear(uplink->dhcp_pd_prefixes);
}
@@ -653,13 +679,20 @@ void dhcp_pd_prefix_lost(Link *uplink) {
void dhcp4_pd_prefix_lost(Link *uplink) {
Link *tunnel;
dhcp_pd_prefix_lost(uplink);
assert(uplink);
assert(uplink->manager);
dhcp_pd_prefix_lost(uplink, NETWORK_CONFIG_SOURCE_DHCP4);
if (uplink->dhcp4_6rd_tunnel_name &&
link_get_by_name(uplink->manager, uplink->dhcp4_6rd_tunnel_name, &tunnel) >= 0)
(void) link_remove(tunnel);
}
void dhcp6_pd_prefix_lost(Link *uplink) {
dhcp_pd_prefix_lost(uplink, NETWORK_CONFIG_SOURCE_DHCP6);
}
static int dhcp4_unreachable_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, Route *route) {
int r;
@@ -1005,9 +1038,11 @@ int dhcp4_pd_prefix_acquired(Link *uplink) {
return r;
/* Request unreachable route */
dhcp_pd_mark_unreachable_route(uplink->manager, NETWORK_CONFIG_SOURCE_DHCP4);
r = dhcp4_request_unreachable_route(uplink, &pd_prefix, pd_prefixlen, lifetime_usec, &server_address);
if (r < 0)
return r;
(void) dhcp_pd_remove_unreachable_route(uplink->manager, NETWORK_CONFIG_SOURCE_DHCP4, /* only_marked = */ true);
/* Create or update 6rd SIT tunnel device. */
r = dhcp4_pd_create_6rd_tunnel(uplink, dhcp4_pd_6rd_tunnel_create_handler);
@@ -1085,11 +1120,14 @@ int dhcp6_pd_prefix_acquired(Link *uplink) {
assert(uplink);
assert(uplink->dhcp6_lease);
assert(uplink->manager);
r = sd_dhcp6_lease_get_server_address(uplink->dhcp6_lease, &server_address.in6);
if (r < 0)
return log_link_warning_errno(uplink, r, "Failed to get server address of DHCPv6 lease: %m");
dhcp_pd_mark_unreachable_route(uplink->manager, NETWORK_CONFIG_SOURCE_DHCP6);
/* First, logs acquired prefixes and request unreachable routes. */
FOREACH_DHCP6_PD_PREFIX(uplink->dhcp6_lease) {
usec_t lifetime_valid_usec;
@@ -1120,6 +1158,8 @@ int dhcp6_pd_prefix_acquired(Link *uplink) {
return r;
}
(void) dhcp_pd_remove_unreachable_route(uplink->manager, NETWORK_CONFIG_SOURCE_DHCP6, /* only_marked = */ true);
/* Then, assign subnet prefixes. */
HASHMAP_FOREACH(link, uplink->manager->links_by_index) {
if (!dhcp_pd_is_uplink(link, uplink, /* accept_auto = */ true))
@@ -1254,14 +1294,21 @@ int dhcp_request_prefix_delegation(Link *link) {
int link_drop_dhcp_pd_config(Link *link, Network *network) {
assert(link);
assert(network);
assert(link->network);
if (!link_dhcp_pd_is_enabled(link))
if (link->network == network)
return 0; /* .network file is unchanged. It is not necessary to reconfigure the client. */
if (!link_dhcp_pd_is_enabled(link)) /* Disabled now, drop all configs. */
return dhcp_pd_remove(link, /* only_marked = */ false);
/* If previously explicitly disabled, then there is nothing we need to drop.
* If this is called on start up, we do not know the previous settings, assume nothing changed. */
if (!network || !network->dhcp_pd)
return 0;
/* If will be disabled or at least one config is changed, then drop all configurations. */
if (!network->dhcp_pd ||
link->network->dhcp_pd_assign != network->dhcp_pd_assign ||
/* If at least one setting is changed, then drop all configurations. */
if (link->network->dhcp_pd_assign != network->dhcp_pd_assign ||
(link->network->dhcp_pd_assign &&
(link->network->dhcp_pd_manage_temporary_address != network->dhcp_pd_manage_temporary_address ||
!set_equal(link->network->dhcp_pd_tokens, network->dhcp_pd_tokens))) ||
@@ -20,8 +20,8 @@ int dhcp_request_prefix_delegation(Link *link);
int link_drop_dhcp_pd_config(Link *link, Network *network);
int dhcp4_pd_prefix_acquired(Link *uplink);
int dhcp6_pd_prefix_acquired(Link *uplink);
void dhcp_pd_prefix_lost(Link *uplink);
void dhcp4_pd_prefix_lost(Link *uplink);
void dhcp6_pd_prefix_lost(Link *uplink);
int dhcp_pd_reconfigure_address(Address *address, Link *link);
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_pd_subnet_id);
+13 -6
View File
@@ -247,7 +247,7 @@ static int dhcp4_remove_address_and_routes(Link *link, bool only_marked) {
SET_FOREACH(route, link->manager->routes) {
if (route->source != NETWORK_CONFIG_SOURCE_DHCP4)
continue;
if (route->nexthop.ifindex != 0 && route->nexthop.ifindex != link->ifindex)
if (route->nexthop.ifindex != link->ifindex)
continue;
if (only_marked && !route_is_marked(route))
continue;
@@ -1837,15 +1837,22 @@ int link_drop_dhcp4_config(Link *link, Network *network) {
int ret = 0;
assert(link);
assert(network);
assert(link->network);
if (!link_dhcp4_enabled(link))
return 0; /* Currently DHCPv4 client is not enabled, there is nothing we need to drop. */
if (link->network == network)
return 0; /* .network file is unchanged. It is not necessary to reconfigure the client. */
if (!FLAGS_SET(network->dhcp, ADDRESS_FAMILY_IPV4))
/* Currently enabled but will be disabled. Stop the client and drop the lease. */
if (!link_dhcp4_enabled(link)) {
/* DHCP client is disabled. Stop the client if it is running and drop the lease. */
ret = sd_dhcp_client_stop(link->dhcp_client);
/* Also explicitly drop DHCPv4 address and routes. Why? This is for the case when the DHCPv4
* client was enabled on the previous invocation of networkd, but when it is restarted, a new
* .network file may match to the interface, and DHCPv4 client may be disabled. In that case,
* the DHCPv4 client is not running, hence sd_dhcp_client_stop() in the above does nothing. */
RET_GATHER(ret, dhcp4_remove_address_and_routes(link, /* only_marked = */ false));
}
/* Even if the client is currently enabled and also enabled in the new .network file, detailed
* settings for the client may be different. Let's unref() the client. But do not unref() the lease.
* it will be unref()ed later when a new lease is acquired. */
+11 -8
View File
@@ -307,7 +307,6 @@ static int dhcp6_lease_ip_acquired(sd_dhcp6_client *client, Link *link) {
int r;
link_mark_addresses(link, NETWORK_CONFIG_SOURCE_DHCP6);
manager_mark_routes(link->manager, NULL, NETWORK_CONFIG_SOURCE_DHCP6);
r = sd_dhcp6_client_get_lease(client, &lease);
if (r < 0)
@@ -330,7 +329,7 @@ static int dhcp6_lease_ip_acquired(sd_dhcp6_client *client, Link *link) {
return r;
} else if (sd_dhcp6_lease_has_pd_prefix(lease_old))
/* When we had PD prefixes but not now, we need to remove them. */
dhcp_pd_prefix_lost(link);
dhcp6_pd_prefix_lost(link);
if (link->dhcp6_messages == 0) {
link->dhcp6_configured = true;
@@ -377,7 +376,7 @@ static int dhcp6_lease_lost(Link *link) {
log_link_info(link, "DHCPv6 lease lost");
if (sd_dhcp6_lease_has_pd_prefix(link->dhcp6_lease))
dhcp_pd_prefix_lost(link);
dhcp6_pd_prefix_lost(link);
link->dhcp6_lease = sd_dhcp6_lease_unref(link->dhcp6_lease);
@@ -845,15 +844,19 @@ int link_drop_dhcp6_config(Link *link, Network *network) {
int ret = 0;
assert(link);
assert(network);
assert(link->network);
if (!link_dhcp6_enabled(link))
return 0; /* Currently DHCPv6 client is not enabled, there is nothing we need to drop. */
if (link->network == network)
return 0; /* .network file is unchanged. It is not necessary to reconfigure the client. */
if (!FLAGS_SET(network->dhcp, ADDRESS_FAMILY_IPV6))
/* Currently enabled but will be disabled. Stop the client and drop the lease. */
if (!link_dhcp6_enabled(link)) {
/* DHCPv6 client is disabled. Stop the client if it is running and drop the lease. */
ret = sd_dhcp6_client_stop(link->dhcp6_client);
/* Also explicitly drop DHCPv6 addresses and routes. See also link_drop_dhcp4_config(). */
RET_GATHER(ret, dhcp6_remove(link, /* only_marked = */ false));
}
/* Even if the client is currently enabled and also enabled in the new .network file, detailed
* settings for the client may be different. Let's unref() the client. But do not unref() the lease.
* it will be unref()ed later when a new lease is acquired. */
+18 -10
View File
@@ -206,7 +206,7 @@ int ipv4ll_configure(Link *link) {
return 0;
if (link->ipv4ll)
return -EBUSY;
return 0;
r = sd_ipv4ll_new(&link->ipv4ll);
if (r < 0)
@@ -246,19 +246,27 @@ int link_drop_ipv4ll_config(Link *link, Network *network) {
int ret = 0;
assert(link);
assert(network);
assert(link->network);
if (!link_ipv4ll_enabled(link))
return 0;
if (link->network == network)
return 0; /* .network file is unchanged. It is not necessary to reconfigure the client. */
Network *saved = link->network;
link->network = network;
bool enabled = link_ipv4ll_enabled(link);
link->network = saved;
if (!enabled)
if (!link_ipv4ll_enabled(link)) {
/* The client is disabled. Stop if it is running, and drop the address. */
ret = sd_ipv4ll_stop(link->ipv4ll);
/* Also, explicitly drop the address for the case that this is called on start up.
* See also comments in link_drop_dhcp4_config(). */
Address *a;
SET_FOREACH(a, link->addresses) {
if (a->source != NETWORK_CONFIG_SOURCE_IPV4LL)
continue;
assert(a->family == AF_INET);
RET_GATHER(ret, address_remove_and_cancel(a, link));
}
}
link->ipv4ll = sd_ipv4ll_unref(link->ipv4ll);
return ret;
}
+38 -31
View File
@@ -1121,21 +1121,30 @@ static int link_drop_dynamic_config(Link *link, Network *network) {
int r;
assert(link);
assert(network);
assert(link->network);
/* Drop unnecessary dynamic configurations gracefully, e.g. drop DHCP lease in the case that
* previously DHCP=yes and now DHCP=no, but keep DHCP lease when DHCP setting is unchanged. */
r = link_drop_ndisc_config(link, network);
RET_GATHER(r, link_drop_radv_config(link, network));
RET_GATHER(r, link_drop_radv_config(link, network)); /* Stop before dropping DHCP-PD prefixes. */
RET_GATHER(r, link_drop_ipv4ll_config(link, network)); /* Stop before DHCPv4 client. */
RET_GATHER(r, link_drop_dhcp4_config(link, network));
RET_GATHER(r, link_drop_dhcp6_config(link, network));
RET_GATHER(r, link_drop_dhcp_pd_config(link, network));
RET_GATHER(r, link_drop_ipv4ll_config(link, network));
link->dhcp_server = sd_dhcp_server_unref(link->dhcp_server);
link->lldp_rx = sd_lldp_rx_unref(link->lldp_rx); /* TODO: keep the received neighbors. */
link->lldp_tx = sd_lldp_tx_unref(link->lldp_tx);
/* Even if we do not release DHCP lease or so, reset 'configured' flags. Otherwise, e.g. if
* previously UseDNS= was disabled but is now enabled, link will enter configured state before
* expected DNS servers being acquired. */
link->ipv4ll_address_configured = false;
link->dhcp4_configured = false;
link->dhcp6_configured = false;
link->dhcp_pd_configured = false;
link->ndisc_configured = false;
return r;
}
@@ -1148,6 +1157,10 @@ static int link_configure(Link *link) {
link_set_state(link, LINK_STATE_CONFIGURING);
r = link_drop_unmanaged_config(link);
if (r < 0)
return r;
r = link_new_bound_to_list(link);
if (r < 0)
return r;
@@ -1253,10 +1266,6 @@ static int link_configure(Link *link) {
if (r < 0)
return r;
r = link_drop_unmanaged_config(link);
if (r < 0)
return r;
r = link_request_static_configs(link);
if (r < 0)
return r;
@@ -1385,29 +1394,7 @@ int link_reconfigure_impl(Link *link, LinkReconfigurationFlag flags) {
joined,
isempty(joined) ? "" : ")");
/* Dropping old .network file */
if (FLAGS_SET(flags, LINK_RECONFIGURE_CLEANLY)) {
/* Remove all static configurations. Note, dynamic configurations are dropped by
* link_stop_engines(), and foreign configurations will be removed later by
* link_configure() -> link_drop_unmanaged_config(). */
r = link_drop_static_config(link);
if (r < 0)
return r;
/* Stop DHCP client and friends, and drop dynamic configurations like DHCP address. */
r = link_stop_engines(link, /* may_keep_dhcp = */ false);
if (r < 0)
return r;
/* Free DHCP client and friends. */
link_free_engines(link);
} else {
r = link_drop_dynamic_config(link, network);
if (r < 0)
return r;
}
/* Dropping configurations based on the old .network file. */
r = link_drop_requests(link);
if (r < 0)
return r;
@@ -1418,10 +1405,30 @@ int link_reconfigure_impl(Link *link, LinkReconfigurationFlag flags) {
* map here, as it depends on .network files assigned to other links. */
link_free_bound_to_list(link);
link->network = network_unref(link->network);
_cleanup_(network_unrefp) Network *old_network = TAKE_PTR(link->network);
/* Then, apply new .network file */
link->network = network_ref(network);
if (FLAGS_SET(network->keep_configuration, KEEP_CONFIGURATION_DHCP) ||
!FLAGS_SET(flags, LINK_RECONFIGURE_CLEANLY)) {
/* To make 'networkctl reconfigure INTERFACE' work safely for an interface whose new .network
* file has KeepConfiguration=dhcp or yes, even if a clean reconfiguration is requested,
* drop only unnecessary or possibly being changed dynamic configurations here. */
r = link_drop_dynamic_config(link, old_network);
if (r < 0)
return r;
} else {
/* Otherwise, stop DHCP client and friends unconditionally, and drop all dynamic
* configurations like DHCP address and routes. */
r = link_stop_engines(link, /* may_keep_dhcp = */ false);
if (r < 0)
return r;
/* Free DHCP client and friends. */
link_free_engines(link);
}
link_update_operstate(link, true);
link_dirty(link);
+1 -1
View File
@@ -179,7 +179,7 @@ typedef struct Link {
Set *ndisc_dnr;
uint32_t ndisc_mtu;
unsigned ndisc_messages;
bool ndisc_configured:1;
bool ndisc_configured;
sd_radv *radv;
+22 -11
View File
@@ -2672,28 +2672,39 @@ int link_drop_ndisc_config(Link *link, Network *network) {
int r, ret = 0;
assert(link);
assert(network);
assert(link->network);
if (!link_ndisc_enabled(link))
return 0; /* Currently DHCPv4 client is not enabled, there is nothing we need to drop. */
if (link->network == network)
return 0; /* .network file is unchanged. It is not necessary to reconfigure the client. */
Network *current = link->network;
link->network = network;
bool enabled = link_ndisc_enabled(link);
link->network = current;
if (!enabled) {
/* Currently enabled but will be disabled. Stop the client and flush configs. */
if (!link_ndisc_enabled(link)) {
/* NDisc is disabled. Stop the client if it is running and flush configs. */
ret = ndisc_stop(link);
ndisc_flush(link);
link->ndisc = sd_ndisc_unref(link->ndisc);
return ret;
}
/* Even if the client is currently enabled and also enabled in the new .network file, detailed
/* Even if the client was previously enabled and also enabled in the new .network file, detailed
* settings for the client may be different. Let's unref() the client. */
link->ndisc = sd_ndisc_unref(link->ndisc);
/* Get if NDisc was enabled or not. */
Network *current = link->network;
link->network = network;
bool enabled = link_ndisc_enabled(link);
link->network = current;
/* If previously explicitly disabled, there should be nothing to drop.
* If we do not know the previous setting of the client, e.g. when networkd is restarted, in that
* case we do not have the previous .network file assigned to the interface, then let's assume no
* detailed configuration is changed. Hopefully, unmatching configurations will be dropped after
* their lifetime. */
if (!enabled)
return 0;
assert(network);
/* Redirect messages will be ignored. Drop configurations based on the previously received redirect
* messages. */
if (!network->ndisc_use_redirect)
+3 -3
View File
@@ -649,10 +649,10 @@ int link_drop_radv_config(Link *link, Network *network) {
int ret = 0;
assert(link);
assert(network);
assert(link->network);
if (!link_radv_enabled(link))
return 0;
if (link->network == network)
return 0; /* .network file is unchanged. It is not necessary to reconfigure the server. */
// FIXME: check detailed settings and do not stop if nothing changed.
// FIXME: save dynamic prefixes acquired by DHCP-PD.
+12 -7
View File
@@ -6705,7 +6705,6 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
'--dhcp-option=option6:ntp-server,[2600::ff]')
networkctl_reload()
networkctl_reconfigure('veth99') # Release previously acquired lease and start new DHCPv6 handshake.
self.wait_online('veth99:routable', 'veth-peer:routable')
# checking address
@@ -7448,7 +7447,7 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
self.assertIn('inet 169.254.133.11/16 metric 2048 brd 169.254.255.255 scope link', output)
def test_dhcp_client_use_dns(self):
def check(self, ipv4, ipv6):
def check(self, ipv4, ipv6, needs_reconfigure=False):
os.makedirs(os.path.join(network_unit_dir, '25-dhcp-client.network.d'), exist_ok=True)
with open(os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), mode='w', encoding='utf-8') as f:
f.write('[DHCPv4]\nUseDNS=')
@@ -7458,6 +7457,8 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
f.write('\n[IPv6AcceptRA]\nUseDNS=no')
networkctl_reload()
if needs_reconfigure:
networkctl_reconfigure('veth99')
self.wait_online('veth99:routable')
# link becomes 'routable' when at least one protocol provide an valid address. Hence, we need to explicitly wait for both addresses.
@@ -7489,7 +7490,7 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
check(self, True, True)
check(self, True, False)
check(self, False, True)
check(self, False, True, needs_reconfigure=True)
check(self, False, False)
def test_dhcp_client_default_use_domains(self):
@@ -7541,7 +7542,7 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
check(self, False, False, False)
def test_dhcp_client_use_dnr(self):
def check(self, ipv4, ipv6):
def check(self, ipv4, ipv6, needs_reconfigure=False):
os.makedirs(os.path.join(network_unit_dir, '25-dhcp-client.network.d'), exist_ok=True)
with open(os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), mode='w', encoding='utf-8') as f:
f.write('[DHCPv4]\nUseDNS=')
@@ -7551,6 +7552,8 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
f.write('\n[IPv6AcceptRA]\nUseDNS=no')
networkctl_reload()
if needs_reconfigure:
networkctl_reconfigure('veth99')
self.wait_online('veth99:routable')
# link becomes 'routable' when at least one protocol provide an valid address. Hence, we need to explicitly wait for both addresses.
@@ -7587,11 +7590,11 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
check(self, True, True)
check(self, True, False)
check(self, False, True)
check(self, False, True, needs_reconfigure=True)
check(self, False, False)
def test_dhcp_client_use_captive_portal(self):
def check(self, ipv4, ipv6):
def check(self, ipv4, ipv6, needs_reconfigure=False):
os.makedirs(os.path.join(network_unit_dir, '25-dhcp-client.network.d'), exist_ok=True)
with open(os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), mode='w', encoding='utf-8') as f:
f.write('[DHCPv4]\nUseCaptivePortal=')
@@ -7601,6 +7604,8 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
f.write('\n[IPv6AcceptRA]\nUseCaptivePortal=no')
networkctl_reload()
if needs_reconfigure:
networkctl_reconfigure('veth99')
self.wait_online('veth99:routable')
# link becomes 'routable' when at least one protocol provide an valid address. Hence, we need to explicitly wait for both addresses.
@@ -7625,7 +7630,7 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
check(self, True, True)
check(self, True, False)
check(self, False, True)
check(self, False, True, needs_reconfigure=True)
check(self, False, False)
def test_dhcp_client_reject_captive_portal(self):