Compare commits

...

19 Commits

Author SHA1 Message Date
Yu Watanabe 68f5b9ad37
Merge 2e49ae7e4f into 5261c521e3 2024-11-08 13:34:38 +00:00
Yu Watanabe 5261c521e3 mount-util: make path_get_mount_info() work arbitrary inode
Follow-up for d49d95df0a.
Replaces 9a032ec55a.
Fixes #35075.
2024-11-08 13:25:17 +01:00
Franck Bui 514d9e1665 test: install integration-test-setup.sh in testdata/
integration-test-setup.sh is an auxiliary script that tests rely on at
runtime. As such, install the script in testdata/.

Follow-up for af153e36ae.
2024-11-08 12:37:40 +01:00
Lennart Poettering b480a4c15e update TODO 2024-11-08 10:10:11 +01:00
Lennart Poettering af3baf174a fs-util: add comment about XO_NOCOW 2024-11-08 09:21:25 +01:00
Ryan Wilson d8091e1281 Fix PrivatePIDs=yes integration test for kernels with no /proc/scsi 2024-11-08 13:38:35 +09:00
Yu Watanabe 2e49ae7e4f test-network: update KeepConfiguration=dhcp -> dynamic 2024-11-07 12:03:28 +09:00
Yu Watanabe 083e98670f man/network: update documentation for KeepConfiguration= 2024-11-07 12:03:28 +09:00
Yu Watanabe 45ee87df9f network: rename KeepConfiguration=dhcp -> dynamic
KeepConfiguration=dhcp keeps not only DHCP configurations but
also SLAAC or IPV4LL. Let's rename the value to 'dynamic'.
2024-11-07 12:03:28 +09:00
Yu Watanabe 4007421856 network/ipv4ll: use a foreign IPv4LL address when KeepConfiguration=dhcp
This is similar to what we do for DHCPv4 address, but for IPv4LL
address.
2024-11-07 12:03:28 +09:00
Yu Watanabe c970db1bf3 network: keep all dynamically acquired configurations when KeepConfiguration=dhcp-on-stop
By the previous commit, configuration source of addresses and routes are
saved on stop and restored on start. Hence, we can keep dynamic
configurations on stop.

Co-authored-by: Jian Zhang <zhangjian.3032@bytedance.com>
2024-11-07 12:03:28 +09:00
Yu Watanabe 80c3019982 network: introduce manager_serialize()/deserialize()
Currently, only configuration sources and providers of addresses and
routes are serialized/deserialized.
This should mostly not change behavior, as dynamic (except for DHCPv4)
configurations will be dropped before stopping networkd, and for DHCPv4
protocol, we have already had another logic to handle DHCPv4
configurations.
2024-11-07 12:03:28 +09:00
Yu Watanabe ecb6dc08e3 network: make 'networkctl reconfigure' work safely even when KeepConfiguration=dhcp or yes
Previously, even if KeepConfiguration=dhcp or yes is specified in the
new .network file, dynamic configurations like DHCP address and routes
were dropped when 'networkctl reconfigure INTERFACE' is invoked.

If the setting is specified, let's gracefully handle the dynamic
configurations. Then, 'networkctl reconfigure' can be also used for
an interface that has critical connections.
2024-11-07 12:03:28 +09:00
Yu Watanabe 32fd218add network: drop static configs later
Follow-up for dd6d53a8dc.

Unnecessary static configs will be anyway dropped later in
link_configure() -> link_drop_unmanaged_config(). Hence, even if we are
reconfiguring an interface cleanly, it is not necessary to drop static
configs here.
2024-11-07 12:03:28 +09:00
Yu Watanabe 62a4858ea7 network/dhcp-pd: do not remove unreachable route when reconfiguring non-upstream interface
Unreachable routes are not owned by any interfaces, and its ifindex is
zero. Previously, if a non-upstream interface is reconfigured, all routes
including unreachable routes configured by the upstream interface are
removed.

This makes unreachable routes are always handled by the upstream interface,
and only removed when the delegated prefixes are changed or lost.
2024-11-07 12:03:28 +09:00
Yu Watanabe a4027e291e network: reorder dropping dynamic configuration
Follow-up for 451c2baf30.
2024-11-07 12:03:28 +09:00
Yu Watanabe bd5f4b76c7 test-network: reconfigure interface cleanly to drop previous DHCP lease and friends
Follow-up for 451c2baf30.

With the commits, reloading .network files does not release previously
acquired DHCP lease and friends if possible.

On graceful reconfigure triggered by the reload, the interface may
acquire a new DHCPv4 lease earlier than DHCPv6 lease. In that case,
the check will fail as it is done with the new DHCPv4 lease and old
DHCPv6 lease, which does not contain any IPv6 DNS servers or so.
So, when switching from no -> yes, we need to wait a new lease with DNS
servers or so. To achieve that, we need to clean reconfigure the interface.
2024-11-07 12:02:47 +09:00
Yu Watanabe 407e9348c1 network: reset 'configured' flags even if we keep DHCP lease and friends on reconfigure
Follow-up for 451c2baf30.

With the commits, reloading .network files does not release previously
acquired DHCP lease and friends if possible. If previously a DHCP client
was configured as not requesting DNS servers or so, then the previously
acquired lease might not contain any DNS servers. In that case, if the
new .network file enables UseDNS=, then the interface should enter the
configured state after a new lease is acquired. To achieve that, we need
to reset the flags.

With this change, the workaround applied to the test by the commit
451c2baf30 can be dropped.
2024-11-07 12:01:59 +09:00
Yu Watanabe 2ca5445f32 network: drop unnecessary size specifier
It does not save any memory usage but increase code complexity.
2024-11-07 12:01:59 +09:00
33 changed files with 1007 additions and 310 deletions

25
TODO
View File

@ -129,6 +129,10 @@ Deprecations and removals:
Features:
* format-table: introduce new cell type for strings with ansi sequences in
them. display them in regular output mode (via strip_tab_ansi()), but
suppress them in json mode.
* machined: when registering a machine, also take a relative cgroup path,
relative to the machine's unit. This is useful when registering unpriv
machines, as they might sit down the cgroup tree, below a cgroup delegation
@ -217,12 +221,8 @@ Features:
services where mount propagation from the root fs is off, an still have
confext/sysext propagated in.
* support F_DUDFD_QUERY for comparing fds in same_fd (requires kernel 6.10)
* generic interface for varlink for setting log level and stuff that all our daemons can implement
* use pty ioctl to get peer wherever possible (TIOCGPTPEER)
* maybe teach repart.d/ dropins a new setting MakeMountNodes= or so, which is
just like MakeDirectories=, but uses an access mode of 0000 and sets the +i
chattr bit. This is useful as protection against early uses of /var/ or /tmp/
@ -253,8 +253,6 @@ Features:
* initrd: when transitioning from initrd to host, validate that
/lib/modules/`uname -r` exists, refuse otherwise
* tmpfiles: add "owning" flag for lines that limits effect of --purge
* signed bpf loading: to address need for signature verification for bpf
programs when they are loaded, and given the bpf folks don't think this is
realistic in kernel space, maybe add small daemon that facilitates this
@ -458,9 +456,6 @@ Features:
* introduce mntid_t, and make it 64bit, as apparently the kernel switched to
64bit mount ids
* use udev rule networkd ownership property to take ownership of network
interfaces nspawn creates
* mountfsd/nsresourced
- userdb: maybe allow callers to map one uid to their own uid
- bpflsm: allow writes if resulting UID on disk would be userns' owner UID
@ -647,6 +642,7 @@ Features:
- openpt_allocate_in_namespace()
- unit_attach_pid_to_cgroup_via_bus()
- cg_attach() requires new kernel feature
- journald's process cache
* ddi must be listed as block device fstype
@ -1470,9 +1466,6 @@ Features:
* in sd-id128: also parse UUIDs in RFC4122 URN syntax (i.e. chop off urn:uuid: prefix)
* DynamicUser= + StateDirectory= → use uid mapping mounts, too, in order to
make dirs appear under right UID.
* systemd-sysext: optionally, run it in initrd already, before transitioning
into host, to open up possibility for services shipped like that.
@ -1644,14 +1637,6 @@ Features:
* maybe add kernel cmdline params: to force random seed crediting
* introduce a new per-process uuid, similar to the boot id, the machine id, the
invocation id, that is derived from process creds, specifically a hashed
combination of AT_RANDOM + getpid() + the starttime from
/proc/self/status. Then add these ids implicitly when logging. Deriving this
uuid from these three things has the benefit that it can be derived easily
from /proc/$PID/ in a stable, and unique way that changes on both fork() and
exec().
* let's not GC a unit while its ratelimits are still pending
* when killing due to service watchdog timeout maybe detect whether target

View File

@ -1286,21 +1286,21 @@ DuplicateAddressDetection=none</programlisting></para>
<varlistentry>
<term><varname>KeepConfiguration=</varname></term>
<listitem>
<para>Takes a boolean or one of <literal>static</literal>, <literal>dhcp-on-stop</literal>,
<literal>dhcp</literal>. When <literal>static</literal>, <command>systemd-networkd</command>
will not drop static addresses and routes on starting up process. When set to
<literal>dhcp-on-stop</literal>, <command>systemd-networkd</command> will not drop addresses
and routes on stopping the daemon. When <literal>dhcp</literal>,
the addresses and routes provided by a DHCP server will never be dropped even if the DHCP
lease expires. This is contrary to the DHCP specification, but may be the best choice if,
e.g., the root filesystem relies on this connection. The setting <literal>dhcp</literal>
implies <literal>dhcp-on-stop</literal>, and <literal>yes</literal> implies
<literal>dhcp</literal> and <literal>static</literal>. Defaults to
<literal>dhcp-on-stop</literal> when <command>systemd-networkd</command> is running in
initrd, <literal>yes</literal> when the root filesystem is a network filesystem, and
<literal>no</literal> otherwise.</para>
<para>Takes a boolean or one of <literal>static</literal>, <literal>dynamic-on-stop</literal>, and
<literal>dynamic</literal>. When <literal>static</literal>, <command>systemd-networkd</command>
will not drop statically configured addresses and routes on starting up process. When
<literal>dynamic-on-stop</literal>, the dynamically configurad addresses and routes, such as
DHCPv4, DHCPv6, SLAAC, and IPv4 link-local address, will not be dropped when
<command>systemd-networkd</command> is being stopped. When <literal>dynamic</literal>, the
dynamically configured addresses and routes will never be dropped, and the lifetime of DHCPv4
leases will be ignored. This is contrary to the DHCP specification, but may be the best choice if,
e.g., the root filesystem relies on this connection. The setting <literal>dynamic</literal> implies
<literal>dynamic-on-stop</literal>, and <literal>yes</literal> implies <literal>dynamic</literal>
and <literal>static</literal>. Defaults to <literal>dynamic-on-stop</literal> when
<command>systemd-networkd</command> is running in initrd, <literal>yes</literal> when the root
filesystem is a network filesystem, and <literal>no</literal> otherwise.</para>
<xi:include href="version-info.xml" xpointer="v243"/>
<xi:include href="version-info.xml" xpointer="v243"/>
</listitem>
</varlistentry>
</variablelist>

View File

@ -1131,6 +1131,8 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_
* If O_CREAT is used with XO_LABEL, any created file will be immediately relabelled.
*
* If the path is specified NULL or empty, behaves like fd_reopen().
*
* If XO_NOCOW is specified will turn on the NOCOW btrfs flag on the file, if available.
*/
if (isempty(path)) {

View File

@ -76,6 +76,7 @@ sources = files(
'networkd-route-nexthop.c',
'networkd-route-util.c',
'networkd-routing-policy-rule.c',
'networkd-serialize.c',
'networkd-setlink.c',
'networkd-speed-meter.c',
'networkd-sriov.c',

View File

@ -1274,8 +1274,8 @@ bool link_address_is_dynamic(const Link *link, const Address *address) {
if (address->family != AF_INET)
return false;
/* Even when the address is leased from a DHCP server, networkd assign the address
* without lifetime when KeepConfiguration=dhcp. So, let's check that we have
/* Even if an IPv4 address is leased from a DHCP server with a finite lifetime, networkd assign the
* address without lifetime when KeepConfiguration=dynamic. So, let's check that we have
* corresponding routes with RTPROT_DHCP. */
SET_FOREACH(route, link->manager->routes) {
if (route->source != NETWORK_CONFIG_SOURCE_FOREIGN)
@ -1410,9 +1410,9 @@ int link_drop_unmanaged_addresses(Link *link) {
continue;
/* link_address_is_dynamic() is slightly heavy. Let's call the function only when
* KeepConfiguration=dhcp or static. */
if (IN_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP, KEEP_CONFIGURATION_STATIC) &&
link_address_is_dynamic(link, address) == (link->network->keep_configuration == KEEP_CONFIGURATION_DHCP))
* KeepConfiguration=dynamic or static. */
if (IN_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DYNAMIC, KEEP_CONFIGURATION_STATIC) &&
link_address_is_dynamic(link, address) == (link->network->keep_configuration == KEEP_CONFIGURATION_DYNAMIC))
continue;
} else if (address->source != NETWORK_CONFIG_SOURCE_STATIC)

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

View File

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

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;
@ -915,7 +915,7 @@ static int dhcp4_request_address(Link *link, bool announce) {
if (r < 0)
return log_link_debug_errno(link, r, "DHCP error: failed to get DHCP server IP address: %m");
if (!FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP)) {
if (!FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DYNAMIC)) {
r = sd_dhcp_lease_get_lifetime_timestamp(link->dhcp_lease, CLOCK_BOOTTIME, &lifetime_usec);
if (r < 0)
return log_link_warning_errno(link, r, "DHCP error: failed to get lifetime: %m");
@ -1168,7 +1168,7 @@ static int dhcp4_handler(sd_dhcp_client *client, int event, void *userdata) {
switch (event) {
case SD_DHCP_CLIENT_EVENT_STOP:
if (FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP)) {
if (FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DYNAMIC)) {
log_link_notice(link, "DHCPv4 connection considered critical, ignoring request to reconfigure it.");
return 0;
}
@ -1199,7 +1199,7 @@ static int dhcp4_handler(sd_dhcp_client *client, int event, void *userdata) {
break;
case SD_DHCP_CLIENT_EVENT_EXPIRED:
if (FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP)) {
if (FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DYNAMIC)) {
log_link_notice(link, "DHCPv4 connection considered critical, ignoring request to reconfigure it.");
return 0;
}
@ -1214,7 +1214,7 @@ static int dhcp4_handler(sd_dhcp_client *client, int event, void *userdata) {
break;
case SD_DHCP_CLIENT_EVENT_IP_CHANGE:
if (FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP)) {
if (FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DYNAMIC)) {
log_link_notice(link, "DHCPv4 connection considered critical, ignoring request to reconfigure it.");
return 0;
}
@ -1400,8 +1400,8 @@ static int dhcp4_set_request_address(Link *link) {
return sd_dhcp_client_set_request_address(link->dhcp_client, &link->network->dhcp_request_address);
}
/* 3. If KeepConfiguration=dhcp, use a foreign dynamic address. */
if (!FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP))
/* 3. If KeepConfiguration=dynamic, use a foreign dynamic address. */
if (!FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DYNAMIC))
return 0;
SET_FOREACH(a, link->addresses) {
@ -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. */

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. */

View File

@ -175,6 +175,8 @@ static int ipv4ll_check_mac(sd_ipv4ll *ll, const struct ether_addr *mac, void *u
}
static int ipv4ll_set_address(Link *link) {
int r;
assert(link);
assert(link->network);
assert(link->ipv4ll);
@ -193,6 +195,27 @@ static int ipv4ll_set_address(Link *link) {
if (in4_addr_is_set(&link->network->ipv4ll_start_address))
return sd_ipv4ll_set_address(link->ipv4ll, &link->network->ipv4ll_start_address);
/* 3. If KeepConfiguration=dynamic, use a foreign IPv4LL address. */
if (!FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DYNAMIC))
return 0;
SET_FOREACH(a, link->addresses) {
if (a->source != NETWORK_CONFIG_SOURCE_FOREIGN)
continue;
if (a->family != AF_INET)
continue;
if (!in4_addr_is_link_local_dynamic(&a->in_addr.in))
continue;
r = sd_ipv4ll_set_address(link->ipv4ll, &a->in_addr.in);
if (r < 0)
return r;
/* Make the address not removed by link_drop_unmanaged_addresses(). */
a->source = NETWORK_CONFIG_SOURCE_IPV4LL;
return 0;
}
return 0;
}
@ -206,7 +229,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 +269,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;
}

View File

@ -28,55 +28,79 @@
#include "user-util.h"
#include "wifi-util.h"
static int address_append_json(Address *address, sd_json_variant **array) {
_cleanup_free_ char *scope = NULL, *flags = NULL, *state = NULL;
static int address_append_json(Address *address, bool serializing, sd_json_variant **array) {
_cleanup_free_ char *state = NULL;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
int r;
assert(address);
assert(array);
r = route_scope_to_string_alloc(address->scope, &scope);
if (r < 0)
return r;
r = address_flags_to_string_alloc(address->flags, address->family, &flags);
if (r < 0)
return r;
r = network_config_state_to_string_alloc(address->state, &state);
if (r < 0)
return r;
return sd_json_variant_append_arraybo(
array,
r = sd_json_buildo(
&v,
SD_JSON_BUILD_PAIR_INTEGER("Family", address->family),
JSON_BUILD_PAIR_IN_ADDR("Address", &address->in_addr, address->family),
JSON_BUILD_PAIR_IN_ADDR_NON_NULL("Peer", &address->in_addr_peer, address->family),
JSON_BUILD_PAIR_IN4_ADDR_NON_NULL("Broadcast", &address->broadcast),
SD_JSON_BUILD_PAIR_UNSIGNED("PrefixLength", address->prefixlen),
SD_JSON_BUILD_PAIR_UNSIGNED("Scope", address->scope),
SD_JSON_BUILD_PAIR_STRING("ScopeString", scope),
SD_JSON_BUILD_PAIR_UNSIGNED("Flags", address->flags),
SD_JSON_BUILD_PAIR_STRING("FlagsString", flags),
JSON_BUILD_PAIR_STRING_NON_EMPTY("Label", address->label),
JSON_BUILD_PAIR_FINITE_USEC("PreferredLifetimeUSec", address->lifetime_preferred_usec),
JSON_BUILD_PAIR_FINITE_USEC("PreferredLifetimeUsec", address->lifetime_preferred_usec), /* for backward compat */
JSON_BUILD_PAIR_FINITE_USEC("ValidLifetimeUSec", address->lifetime_valid_usec),
JSON_BUILD_PAIR_FINITE_USEC("ValidLifetimeUsec", address->lifetime_valid_usec), /* for backward compat */
SD_JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(address->source)),
SD_JSON_BUILD_PAIR_STRING("ConfigState", state),
JSON_BUILD_PAIR_IN_ADDR_NON_NULL("ConfigProvider", &address->provider, address->family));
if (r < 0)
return r;
if (!serializing) {
_cleanup_free_ char *scope = NULL, *flags = NULL;
r = route_scope_to_string_alloc(address->scope, &scope);
if (r < 0)
return r;
r = address_flags_to_string_alloc(address->flags, address->family, &flags);
if (r < 0)
return r;
r = sd_json_variant_merge_objectbo(
&v,
JSON_BUILD_PAIR_IN4_ADDR_NON_NULL("Broadcast", &address->broadcast),
SD_JSON_BUILD_PAIR_UNSIGNED("Scope", address->scope),
SD_JSON_BUILD_PAIR_STRING("ScopeString", scope),
SD_JSON_BUILD_PAIR_UNSIGNED("Flags", address->flags),
SD_JSON_BUILD_PAIR_STRING("FlagsString", flags),
JSON_BUILD_PAIR_STRING_NON_EMPTY("Label", address->label),
JSON_BUILD_PAIR_FINITE_USEC("PreferredLifetimeUSec", address->lifetime_preferred_usec),
JSON_BUILD_PAIR_FINITE_USEC("PreferredLifetimeUsec", address->lifetime_preferred_usec), /* for backward compat */
JSON_BUILD_PAIR_FINITE_USEC("ValidLifetimeUSec", address->lifetime_valid_usec),
JSON_BUILD_PAIR_FINITE_USEC("ValidLifetimeUsec", address->lifetime_valid_usec), /* for backward compat */
SD_JSON_BUILD_PAIR_STRING("ConfigState", state));
if (r < 0)
return r;
}
return sd_json_variant_append_array(array, v);
}
static int addresses_append_json(Set *addresses, sd_json_variant **v) {
int addresses_append_json(Link *link, bool serializing, sd_json_variant **v) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL;
Address *address;
int r;
assert(link);
assert(v);
SET_FOREACH(address, addresses) {
r = address_append_json(address, &array);
SET_FOREACH(address, link->addresses) {
if (serializing) {
if (address->source == NETWORK_CONFIG_SOURCE_FOREIGN)
continue;
if (!address_is_ready(address))
continue;
log_address_debug(address, "Serializing", link);
}
r = address_append_json(address, serializing, &array);
if (r < 0)
return r;
}
@ -199,35 +223,20 @@ static int nexthops_append_json(Manager *manager, int ifindex, sd_json_variant *
return json_variant_set_field_non_null(v, "NextHops", array);
}
static int route_append_json(Route *route, sd_json_variant **array) {
_cleanup_free_ char *scope = NULL, *protocol = NULL, *table = NULL, *flags = NULL, *state = NULL;
static int route_append_json(Route *route, bool serializing, sd_json_variant **array) {
_cleanup_free_ char *state = NULL;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
int r;
assert(route);
assert(array);
r = route_scope_to_string_alloc(route->scope, &scope);
if (r < 0)
return r;
r = route_protocol_to_string_alloc(route->protocol, &protocol);
if (r < 0)
return r;
r = manager_get_route_table_to_string(route->manager, route->table, /* append_num = */ false, &table);
if (r < 0)
return r;
r = route_flags_to_string_alloc(route->flags, &flags);
if (r < 0)
return r;
r = network_config_state_to_string_alloc(route->state, &state);
if (r < 0)
return r;
return sd_json_variant_append_arraybo(
array,
r = sd_json_buildo(
&v,
SD_JSON_BUILD_PAIR_INTEGER("Family", route->family),
JSON_BUILD_PAIR_IN_ADDR("Destination", &route->dst, route->family),
SD_JSON_BUILD_PAIR_UNSIGNED("DestinationPrefixLength", route->dst_prefixlen),
@ -238,26 +247,63 @@ static int route_append_json(Route *route, sd_json_variant **array) {
JSON_BUILD_PAIR_IN_ADDR_NON_NULL("PreferredSource", &route->prefsrc, route->family),
SD_JSON_BUILD_PAIR_UNSIGNED("TOS", route->tos),
SD_JSON_BUILD_PAIR_UNSIGNED("Scope", route->scope),
SD_JSON_BUILD_PAIR_STRING("ScopeString", scope),
SD_JSON_BUILD_PAIR_UNSIGNED("Protocol", route->protocol),
SD_JSON_BUILD_PAIR_STRING("ProtocolString", protocol),
SD_JSON_BUILD_PAIR_UNSIGNED("Type", route->type),
SD_JSON_BUILD_PAIR_STRING("TypeString", route_type_to_string(route->type)),
SD_JSON_BUILD_PAIR_UNSIGNED("Priority", route->priority),
SD_JSON_BUILD_PAIR_UNSIGNED("Table", route->table),
SD_JSON_BUILD_PAIR_STRING("TableString", table),
JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("MTU", route_metric_get(&route->metric, RTAX_MTU)),
SD_JSON_BUILD_PAIR_UNSIGNED("Preference", route->pref),
SD_JSON_BUILD_PAIR_UNSIGNED("Flags", route->flags),
SD_JSON_BUILD_PAIR_STRING("FlagsString", strempty(flags)),
JSON_BUILD_PAIR_FINITE_USEC("LifetimeUSec", route->lifetime_usec),
JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("NextHopID", route->nexthop_id),
SD_JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(route->source)),
SD_JSON_BUILD_PAIR_STRING("ConfigState", state),
JSON_BUILD_PAIR_IN_ADDR_NON_NULL("ConfigProvider", &route->provider, route->family));
if (r < 0)
return r;
if (serializing) {
r = sd_json_variant_merge_objectbo(
&v,
SD_JSON_BUILD_PAIR_INTEGER("InterfaceIndex", route->nexthop.ifindex),
JSON_BUILD_PAIR_BYTE_ARRAY_NON_EMPTY("Metrics", route->metric.metrics, route->metric.n_metrics),
JSON_BUILD_PAIR_STRING_NON_EMPTY("TCPCongestionControlAlgorithm", route->metric.tcp_congestion_control_algo));
if (r < 0)
return r;
} else {
_cleanup_free_ char *scope = NULL, *protocol = NULL, *table = NULL, *flags = NULL;
r = route_scope_to_string_alloc(route->scope, &scope);
if (r < 0)
return r;
r = route_protocol_to_string_alloc(route->protocol, &protocol);
if (r < 0)
return r;
r = manager_get_route_table_to_string(route->manager, route->table, /* append_num = */ false, &table);
if (r < 0)
return r;
r = route_flags_to_string_alloc(route->flags, &flags);
if (r < 0)
return r;
r = sd_json_variant_merge_objectbo(
&v,
SD_JSON_BUILD_PAIR_STRING("ScopeString", scope),
SD_JSON_BUILD_PAIR_STRING("ProtocolString", protocol),
SD_JSON_BUILD_PAIR_STRING("TypeString", route_type_to_string(route->type)),
SD_JSON_BUILD_PAIR_STRING("TableString", table),
JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("MTU", route_metric_get(&route->metric, RTAX_MTU)),
SD_JSON_BUILD_PAIR_UNSIGNED("Preference", route->pref),
SD_JSON_BUILD_PAIR_STRING("FlagsString", strempty(flags)),
JSON_BUILD_PAIR_FINITE_USEC("LifetimeUSec", route->lifetime_usec),
SD_JSON_BUILD_PAIR_STRING("ConfigState", state));
if (r < 0)
return r;
}
return sd_json_variant_append_array(array, v);
}
static int routes_append_json(Manager *manager, int ifindex, sd_json_variant **v) {
int routes_append_json(Manager *manager, int ifindex, sd_json_variant **v) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL;
Route *route;
int r;
@ -266,10 +312,21 @@ static int routes_append_json(Manager *manager, int ifindex, sd_json_variant **v
assert(v);
SET_FOREACH(route, manager->routes) {
if (route->nexthop.ifindex != ifindex)
continue;
if (ifindex >= 0) {
if (route->nexthop.ifindex != ifindex)
continue;
} else {
/* negative ifindex means we are serializing now. */
r = route_append_json(route, &array);
if (route->source == NETWORK_CONFIG_SOURCE_FOREIGN)
continue;
if (!route_exists(route))
continue;
log_route_debug(route, "Serializing", manager);
}
r = route_append_json(route, /* serializing = */ ifindex < 0, &array);
if (r < 0)
return r;
}
@ -1413,7 +1470,7 @@ int link_build_json(Link *link, sd_json_variant **ret) {
if (r < 0)
return r;
r = addresses_append_json(link->addresses, &v);
r = addresses_append_json(link, /* serializing = */ false, &v);
if (r < 0)
return r;

View File

@ -1,10 +1,14 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <stdbool.h>
#include "sd-json.h"
typedef struct Link Link;
typedef struct Manager Manager;
int addresses_append_json(Link *link, bool serializing, sd_json_variant **v);
int routes_append_json(Manager *manager, int ifindex, sd_json_variant **v);
int link_build_json(Link *link, sd_json_variant **ret);
int manager_build_json(Manager *manager, sd_json_variant **ret);

View File

@ -369,23 +369,41 @@ void link_set_state(Link *link, LinkState state) {
link_dirty(link);
}
int link_stop_engines(Link *link, bool may_keep_dhcp) {
int link_stop_engines(Link *link, bool may_keep_dynamic) {
int r, ret = 0;
assert(link);
assert(link->manager);
assert(link->manager->event);
bool keep_dhcp =
may_keep_dhcp &&
bool keep_dynamic =
may_keep_dynamic &&
link->network &&
(link->manager->state == MANAGER_RESTARTING ||
FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP_ON_STOP));
FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DYNAMIC_ON_STOP));
if (!keep_dhcp) {
if (!keep_dynamic) {
r = sd_dhcp_client_stop(link->dhcp_client);
if (r < 0)
RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop DHCPv4 client: %m"));
r = sd_ipv4ll_stop(link->ipv4ll);
if (r < 0)
RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop IPv4 link-local: %m"));
r = sd_dhcp6_client_stop(link->dhcp6_client);
if (r < 0)
RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop DHCPv6 client: %m"));
r = dhcp_pd_remove(link, /* only_marked = */ false);
if (r < 0)
RET_GATHER(ret, log_link_warning_errno(link, r, "Could not remove DHCPv6 PD addresses and routes: %m"));
r = ndisc_stop(link);
if (r < 0)
RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop IPv6 Router Discovery: %m"));
ndisc_flush(link);
}
r = sd_dhcp_server_stop(link->dhcp_server);
@ -400,28 +418,10 @@ int link_stop_engines(Link *link, bool may_keep_dhcp) {
if (r < 0)
RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop LLDP Tx: %m"));
r = sd_ipv4ll_stop(link->ipv4ll);
if (r < 0)
RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop IPv4 link-local: %m"));
r = ipv4acd_stop(link);
if (r < 0)
RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop IPv4 ACD client: %m"));
r = sd_dhcp6_client_stop(link->dhcp6_client);
if (r < 0)
RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop DHCPv6 client: %m"));
r = dhcp_pd_remove(link, /* only_marked = */ false);
if (r < 0)
RET_GATHER(ret, log_link_warning_errno(link, r, "Could not remove DHCPv6 PD addresses and routes: %m"));
r = ndisc_stop(link);
if (r < 0)
RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop IPv6 Router Discovery: %m"));
ndisc_flush(link);
r = sd_radv_stop(link->radv);
if (r < 0)
RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop IPv6 Router Advertisement: %m"));
@ -449,7 +449,7 @@ void link_enter_failed(Link *link) {
return;
stop:
(void) link_stop_engines(link, /* may_keep_dhcp = */ false);
(void) link_stop_engines(link, /* may_keep_dynamic = */ false);
}
void link_check_ready(Link *link) {
@ -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;
}
@ -1330,7 +1339,7 @@ static void link_enter_unmanaged(Link *link) {
log_link_full(link, link->state == LINK_STATE_INITIALIZED ? LOG_DEBUG : LOG_INFO,
"Unmanaging interface.");
(void) link_stop_engines(link, /* may_keep_dhcp = */ false);
(void) link_stop_engines(link, /* may_keep_dynamic = */ false);
(void) link_drop_requests(link);
(void) link_drop_static_config(link);
@ -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_DYNAMIC) ||
!FLAGS_SET(flags, LINK_RECONFIGURE_CLEANLY)) {
/* To make 'networkctl reconfigure INTERFACE' work safely for an interface whose new .network
* file has KeepConfiguration=dynamic 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);
@ -1792,7 +1799,7 @@ static int link_carrier_lost_impl(Link *link) {
if (!link->network)
return ret;
RET_GATHER(ret, link_stop_engines(link, false));
RET_GATHER(ret, link_stop_engines(link, /* may_keep_dynamic = */ false));
RET_GATHER(ret, link_drop_static_config(link));
return ret;

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;
@ -257,7 +257,7 @@ bool link_ipv6_enabled(Link *link);
int link_ipv6ll_gained(Link *link);
bool link_has_ipv6_connectivity(Link *link);
int link_stop_engines(Link *link, bool may_keep_dhcp);
int link_stop_engines(Link *link, bool may_keep_dynamic);
const char* link_state_to_string(LinkState s) _const_;
LinkState link_state_from_string(const char *s) _pure_;

View File

@ -49,6 +49,7 @@
#include "networkd-queue.h"
#include "networkd-route.h"
#include "networkd-routing-policy-rule.h"
#include "networkd-serialize.h"
#include "networkd-speed-meter.h"
#include "networkd-state-file.h"
#include "networkd-wifi.h"
@ -245,6 +246,9 @@ static int manager_listen_fds(Manager *m, int *ret_rtnl_fd) {
continue;
}
if (manager_set_serialization_fd(m, fd, names[i]) >= 0)
continue;
if (manager_add_tuntap_fd(m, fd, names[i]) >= 0)
continue;
@ -442,6 +446,7 @@ static int manager_post_handler(sd_event_source *s, void *userdata) {
fw_ctx_get_reply_callback_count(manager->fw_ctx) > 0)
return 0; /* There are some message calls waiting for their replies. */
(void) manager_serialize(manager);
manager->state = MANAGER_STOPPED;
return sd_event_exit(sd_event_source_get_event(s), 0);
@ -476,7 +481,7 @@ static int manager_stop(Manager *manager, ManagerState state) {
Link *link;
HASHMAP_FOREACH(link, manager->links_by_index)
(void) link_stop_engines(link, /* may_keep_dhcp = */ true);
(void) link_stop_engines(link, /* may_keep_dynamic = */ true);
return 0;
}
@ -503,8 +508,8 @@ static int manager_set_keep_configuration(Manager *m) {
assert(m);
if (in_initrd()) {
log_debug("Running in initrd, keep DHCPv4 addresses on stopping networkd by default.");
m->keep_configuration = KEEP_CONFIGURATION_DHCP_ON_STOP;
log_debug("Running in initrd, keep dynamically assigned configurations on stopping networkd by default.");
m->keep_configuration = KEEP_CONFIGURATION_DYNAMIC_ON_STOP;
return 0;
}
@ -646,6 +651,7 @@ int manager_new(Manager **ret, bool test_mode) {
.dhcp6_duid.type = DUID_TYPE_EN,
.duid_product_uuid.type = DUID_TYPE_UUID,
.dhcp_server_persist_leases = true,
.serialization_fd = -EBADF,
.ip_forwarding = { -1, -1, },
#if HAVE_VMLINUX_H
.cgroup_fd = -EBADF,
@ -727,6 +733,8 @@ Manager* manager_free(Manager *m) {
m->fw_ctx = fw_ctx_free(m->fw_ctx);
m->serialization_fd = safe_close(m->serialization_fd);
return mfree(m);
}

View File

@ -129,6 +129,8 @@ struct Manager {
unsigned reloading;
int serialization_fd;
/* sysctl */
int ip_forwarding[2];
#if HAVE_VMLINUX_H

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)

View File

@ -1093,15 +1093,53 @@ int config_parse_ignore_carrier_loss(
return 0;
}
int config_parse_keep_configuration(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
KeepConfiguration t, *k = ASSERT_PTR(data);
Network *network = ASSERT_PTR(userdata);
if (isempty(rvalue)) {
*k = ASSERT_PTR(network->manager)->keep_configuration;
return 0;
}
/* backward compatibility */
if (streq(rvalue, "dhcp")) {
*k = KEEP_CONFIGURATION_DYNAMIC;
return 0;
}
if (streq(rvalue, "dhcp-on-stop")) {
*k = KEEP_CONFIGURATION_DYNAMIC_ON_STOP;
return 0;
}
t = keep_configuration_from_string(rvalue);
if (t < 0)
return log_syntax_parse_error(unit, filename, line, t, lvalue, rvalue);
*k = t;
return 0;
}
DEFINE_CONFIG_PARSE_ENUM(config_parse_required_family_for_online, link_required_address_family, AddressFamily);
DEFINE_CONFIG_PARSE_ENUM(config_parse_keep_configuration, keep_configuration, KeepConfiguration);
static const char* const keep_configuration_table[_KEEP_CONFIGURATION_MAX] = {
[KEEP_CONFIGURATION_NO] = "no",
[KEEP_CONFIGURATION_DHCP_ON_STOP] = "dhcp-on-stop",
[KEEP_CONFIGURATION_DHCP] = "dhcp",
[KEEP_CONFIGURATION_STATIC] = "static",
[KEEP_CONFIGURATION_YES] = "yes",
[KEEP_CONFIGURATION_NO] = "no",
[KEEP_CONFIGURATION_DYNAMIC_ON_STOP] = "dynamic-on-stop",
[KEEP_CONFIGURATION_DYNAMIC] = "dynamic",
[KEEP_CONFIGURATION_STATIC] = "static",
[KEEP_CONFIGURATION_YES] = "yes",
};
DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(keep_configuration, KeepConfiguration, KEEP_CONFIGURATION_YES);

View File

@ -32,14 +32,14 @@
#include "socket-netlink.h"
typedef enum KeepConfiguration {
KEEP_CONFIGURATION_NO = 0,
KEEP_CONFIGURATION_DHCP_ON_START = 1 << 0,
KEEP_CONFIGURATION_DHCP_ON_STOP = 1 << 1,
KEEP_CONFIGURATION_DHCP = KEEP_CONFIGURATION_DHCP_ON_START | KEEP_CONFIGURATION_DHCP_ON_STOP,
KEEP_CONFIGURATION_STATIC = 1 << 2,
KEEP_CONFIGURATION_YES = KEEP_CONFIGURATION_DHCP | KEEP_CONFIGURATION_STATIC,
KEEP_CONFIGURATION_NO = 0,
KEEP_CONFIGURATION_DYNAMIC_ON_START = 1 << 0,
KEEP_CONFIGURATION_DYNAMIC_ON_STOP = 1 << 1,
KEEP_CONFIGURATION_DYNAMIC = KEEP_CONFIGURATION_DYNAMIC_ON_START | KEEP_CONFIGURATION_DYNAMIC_ON_STOP,
KEEP_CONFIGURATION_STATIC = 1 << 2,
KEEP_CONFIGURATION_YES = KEEP_CONFIGURATION_DYNAMIC | KEEP_CONFIGURATION_STATIC,
_KEEP_CONFIGURATION_MAX,
_KEEP_CONFIGURATION_INVALID = -EINVAL,
_KEEP_CONFIGURATION_INVALID = -EINVAL,
} KeepConfiguration;
typedef enum ActivationPolicy {

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.

View File

@ -1471,7 +1471,7 @@ int link_drop_routes(Link *link, bool only_static) {
continue;
if (IN_SET(route->protocol, RTPROT_DHCP, RTPROT_RA, RTPROT_REDIRECT) &&
FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP))
FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DYNAMIC))
continue;
}
}

View File

@ -0,0 +1,398 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "af-list.h"
#include "daemon-util.h"
#include "data-fd-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "iovec-util.h"
#include "json-util.h"
#include "networkd-address.h"
#include "networkd-json.h"
#include "networkd-link.h"
#include "networkd-manager.h"
#include "networkd-route.h"
#include "networkd-serialize.h"
int manager_serialize(Manager *manager) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL, *array = NULL;
int r;
assert(manager);
log_debug("Serializing...");
Link *link;
HASHMAP_FOREACH(link, manager->links_by_index) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *e = NULL;
/* ignore unmanaged, failed, or removed interfaces. */
if (!IN_SET(link->state, LINK_STATE_PENDING, LINK_STATE_INITIALIZED, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED))
continue;
r = sd_json_buildo(
&e,
SD_JSON_BUILD_PAIR_INTEGER("Index", link->ifindex));
if (r < 0)
return r;
r = addresses_append_json(link, /* serializing = */ true, &e);
if (r < 0)
return r;
r = sd_json_variant_append_array(&array, e);
if (r < 0)
return r;
}
r = json_variant_set_field_non_null(&v, "Interfaces", array);
if (r < 0)
return r;
r = routes_append_json(manager, /* ifindex = */ -1, &v);
if (r < 0)
return r;
if (!v) {
log_debug("There is nothing to serialize.");
return 0;
}
_cleanup_free_ char *dump = NULL;
r = sd_json_variant_format(v, /* flags = */ 0, &dump);
if (r < 0)
return r;
_cleanup_close_ int fd = -EBADF;
fd = acquire_data_fd(dump);
if (fd < 0)
return fd;
r = notify_push_fd(fd, "manager-serialization");
if (r < 0)
return log_debug_errno(r, "Failed to push serialization file descriptor: %m");
log_debug("Serialization completed.");
return 0;
}
int manager_set_serialization_fd(Manager *manager, int fd, const char *name) {
assert(manager);
assert(fd >= 0);
assert(name);
if (!startswith(name, "manager-serialization"))
return -EINVAL;
if (manager->serialization_fd >= 0)
return -EEXIST;
manager->serialization_fd = fd;
return 0;
}
static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_network_config_source, NetworkConfigSource, network_config_source_from_string);
static int json_dispatch_address_family(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
int r, *i = ASSERT_PTR(userdata);
int64_t i64;
assert_return(variant, -EINVAL);
if (FLAGS_SET(flags, SD_JSON_RELAX) && sd_json_variant_is_null(variant)) {
*i = AF_UNSPEC;
return 0;
}
r = sd_json_dispatch_int64(name, variant, flags, &i64);
if (r < 0)
return r;
if (!IN_SET(i64, AF_INET, AF_INET6) && !(FLAGS_SET(flags, SD_JSON_RELAX) && i64 == AF_UNSPEC))
return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "JSON field '%s' out of bounds for an address family.", strna(name));
*i = (int) i64;
return 0;
}
typedef struct AddressParam {
int family;
struct iovec address;
struct iovec peer;
uint8_t prefixlen;
NetworkConfigSource source;
struct iovec provider;
} AddressParam;
static void address_param_done(AddressParam *p) {
assert(p);
iovec_done(&p->address);
iovec_done(&p->peer);
iovec_done(&p->provider);
}
static int link_deserialize_address(Link *link, sd_json_variant *v) {
static const sd_json_dispatch_field dispatch_table[] = {
{ "Family", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_address_family, offsetof(AddressParam, family), SD_JSON_MANDATORY },
{ "Address", SD_JSON_VARIANT_ARRAY, json_dispatch_byte_array_iovec, offsetof(AddressParam, address), SD_JSON_MANDATORY },
{ "Peer", SD_JSON_VARIANT_ARRAY, json_dispatch_byte_array_iovec, offsetof(AddressParam, peer), 0 },
{ "PrefixLength", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(AddressParam, prefixlen), SD_JSON_MANDATORY },
{ "ConfigSource", SD_JSON_VARIANT_STRING, json_dispatch_network_config_source, offsetof(AddressParam, source), SD_JSON_MANDATORY },
{ "ConfigProvider", SD_JSON_VARIANT_ARRAY, json_dispatch_byte_array_iovec, offsetof(AddressParam, provider), 0 },
{},
};
int r;
assert(link);
assert(v);
_cleanup_(address_param_done) AddressParam p = {};
r = sd_json_dispatch(v, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p);
if (r < 0)
return log_link_debug_errno(link, r, "Failed to dispatch address from json variant: %m");
if (p.address.iov_len != FAMILY_ADDRESS_SIZE(p.family))
return log_link_debug_errno(link, SYNTHETIC_ERRNO(EINVAL),
"Dispatched address size (%zu) is incompatible with the family (%s).",
p.address.iov_len, af_to_ipv4_ipv6(p.family));
if (p.peer.iov_len != 0 && p.peer.iov_len != FAMILY_ADDRESS_SIZE(p.family))
return log_link_debug_errno(link, SYNTHETIC_ERRNO(EINVAL),
"Dispatched peer address size (%zu) is incompatible with the family (%s).",
p.peer.iov_len, af_to_ipv4_ipv6(p.family));
if (p.provider.iov_len != 0 && p.provider.iov_len != FAMILY_ADDRESS_SIZE(p.family))
return log_link_debug_errno(link, SYNTHETIC_ERRNO(EINVAL),
"Dispatched provider address size (%zu) is incompatible with the family (%s).",
p.provider.iov_len, af_to_ipv4_ipv6(p.family));
Address tmp = {
.family = p.family,
.prefixlen = p.prefixlen,
};
memcpy_safe(&tmp.in_addr, p.address.iov_base, p.address.iov_len);
memcpy_safe(&tmp.in_addr_peer, p.peer.iov_base, p.peer.iov_len);
Address *address;
r = address_get(link, &tmp, &address);
if (r < 0) {
log_link_debug_errno(link, r, "Cannot find deserialized address %s: %m",
IN_ADDR_PREFIX_TO_STRING(tmp.family, &tmp.in_addr, tmp.prefixlen));
return 0; /* Already removed? */
}
if (address->source != NETWORK_CONFIG_SOURCE_FOREIGN)
return 0; /* Huh?? Already deserialized?? */
address->source = p.source;
memcpy_safe(&address->provider, p.provider.iov_base, p.provider.iov_len);
log_address_debug(address, "Deserialized", link);
return 0;
}
static int manager_deserialize_link(Manager *manager, sd_json_variant *v) {
typedef struct LinkParam {
int ifindex;
sd_json_variant *addresses;
} LinkParam;
static const sd_json_dispatch_field dispatch_table[] = {
{ "Index", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(LinkParam, ifindex), SD_JSON_MANDATORY | SD_JSON_REFUSE_NULL },
{ "Addresses", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant_noref, offsetof(LinkParam, addresses), 0 },
{},
};
int r, ret = 0;
assert(manager);
assert(v);
LinkParam p = {};
r = sd_json_dispatch(v, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p);
if (r < 0)
return log_debug_errno(r, "Failed to dispatch interface from json variant: %m");
Link *link;
r = link_get_by_index(manager, p.ifindex, &link);
if (r < 0) {
log_debug_errno(r, "No interface with deserialized ifindex (%i) found: %m", p.ifindex);
return 0; /* Already removed? */
}
sd_json_variant *i;
JSON_VARIANT_ARRAY_FOREACH(i, p.addresses)
RET_GATHER(ret, link_deserialize_address(link, i));
return ret;
}
typedef struct RouteParam {
Route route;
struct iovec dst;
struct iovec src;
struct iovec prefsrc;
struct iovec gw;
struct iovec metrics;
struct iovec provider;
} RouteParam;
static void route_param_done(RouteParam *p) {
assert(p);
free(p->route.metric.metrics);
iovec_done(&p->dst);
iovec_done(&p->src);
iovec_done(&p->prefsrc);
iovec_done(&p->gw);
iovec_done(&p->metrics);
iovec_done(&p->provider);
}
static int manager_deserialize_route(Manager *manager, sd_json_variant *v) {
static const sd_json_dispatch_field dispatch_table[] = {
/* rtmsg header */
{ "Family", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_address_family, offsetof(RouteParam, route.family), SD_JSON_MANDATORY },
{ "DestinationPrefixLength", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(RouteParam, route.dst_prefixlen), SD_JSON_MANDATORY },
{ "SourcePrefixLength", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(RouteParam, route.src_prefixlen), 0 },
{ "TOS", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(RouteParam, route.tos), SD_JSON_MANDATORY },
{ "Protocol", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(RouteParam, route.protocol), SD_JSON_MANDATORY },
{ "Scope", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(RouteParam, route.scope), SD_JSON_MANDATORY },
{ "Type", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(RouteParam, route.type), SD_JSON_MANDATORY },
{ "Flags", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint32, offsetof(RouteParam, route.flags), SD_JSON_MANDATORY },
/* attributes */
{ "Destination", SD_JSON_VARIANT_ARRAY, json_dispatch_byte_array_iovec, offsetof(RouteParam, dst), SD_JSON_MANDATORY },
{ "Source", SD_JSON_VARIANT_ARRAY, json_dispatch_byte_array_iovec, offsetof(RouteParam, src), 0 },
{ "Priority", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint32, offsetof(RouteParam, route.priority), SD_JSON_MANDATORY },
{ "PreferredSource", SD_JSON_VARIANT_ARRAY, json_dispatch_byte_array_iovec, offsetof(RouteParam, prefsrc), 0 },
{ "Table", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint32, offsetof(RouteParam, route.table), SD_JSON_MANDATORY },
/* nexthops */
{ "Gateway", SD_JSON_VARIANT_ARRAY, json_dispatch_byte_array_iovec, offsetof(RouteParam, gw), 0 },
{ "InterfaceIndex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(RouteParam, route.nexthop.ifindex), SD_JSON_MANDATORY | SD_JSON_RELAX },
{ "NextHopID", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint32, offsetof(RouteParam, route.nexthop_id), 0 },
/* metrics */
{ "Metrics", SD_JSON_VARIANT_ARRAY, json_dispatch_byte_array_iovec, offsetof(RouteParam, metrics), 0 },
{ "TCPCongestionControlAlgorithm", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(RouteParam, route.metric.tcp_congestion_control_algo), 0 },
/* config */
{ "ConfigSource", SD_JSON_VARIANT_STRING, json_dispatch_network_config_source, offsetof(RouteParam, route.source), SD_JSON_MANDATORY },
{ "ConfigProvider", SD_JSON_VARIANT_ARRAY, json_dispatch_byte_array_iovec, offsetof(RouteParam, provider), 0 },
{},
};
int r;
assert(manager);
assert(v);
_cleanup_(route_param_done) RouteParam p = {};
r = sd_json_dispatch(v, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p);
if (r < 0)
return log_debug_errno(r, "Failed to dispatch route from json variant: %m");
if (p.dst.iov_len != FAMILY_ADDRESS_SIZE(p.route.family))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"Dispatched destination address size (%zu) is incompatible with the family (%s).",
p.dst.iov_len, af_to_ipv4_ipv6(p.route.family));
if (p.src.iov_len != 0 && p.src.iov_len != FAMILY_ADDRESS_SIZE(p.route.family))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"Dispatched source address size (%zu) is incompatible with the family (%s).",
p.src.iov_len, af_to_ipv4_ipv6(p.route.family));
if (p.prefsrc.iov_len != 0 && p.prefsrc.iov_len != FAMILY_ADDRESS_SIZE(p.route.family))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"Dispatched preferred source address size (%zu) is incompatible with the family (%s).",
p.prefsrc.iov_len, af_to_ipv4_ipv6(p.route.family));
switch (p.gw.iov_len) {
case 0:
p.route.nexthop.family = AF_UNSPEC;
break;
case sizeof(struct in_addr):
p.route.nexthop.family = AF_INET;
break;
case sizeof(struct in6_addr):
p.route.nexthop.family = AF_INET6;
break;
default:
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"Dispatched gateway address size (%zu) is invalid.",
p.prefsrc.iov_len);
}
if (p.metrics.iov_len % sizeof(uint32_t) != 0)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"Dispatched route metric size (%zu) is invalid.",
p.metrics.iov_len);
memcpy_safe(&p.route.dst, p.dst.iov_base, p.dst.iov_len);
memcpy_safe(&p.route.src, p.src.iov_base, p.src.iov_len);
memcpy_safe(&p.route.prefsrc, p.prefsrc.iov_base, p.prefsrc.iov_len);
memcpy_safe(&p.route.nexthop.gw, p.gw.iov_base, p.gw.iov_len);
p.route.metric.n_metrics = p.metrics.iov_len / sizeof(uint32_t);
p.route.metric.metrics = new(uint32_t, p.route.metric.n_metrics);
if (!p.route.metric.metrics)
return log_oom_debug();
memcpy_safe(p.route.metric.metrics, p.metrics.iov_base, p.metrics.iov_len);
Route *route;
r = route_get(manager, &p.route, &route);
if (r < 0) {
log_route_debug(&p.route, "Cannot find deserialized", manager);
return 0; /* Already removed? */
}
if (route->source != NETWORK_CONFIG_SOURCE_FOREIGN)
return 0; /* Huh?? Already deserialized?? */
route->source = p.route.source;
memcpy_safe(&route->provider, p.provider.iov_base, p.provider.iov_len);
log_route_debug(route, "Deserialized", manager);
return 0;
}
int manager_deserialize(Manager *manager) {
int r, ret = 0;
assert(manager);
_cleanup_close_ int fd = TAKE_FD(manager->serialization_fd);
if (fd < 0)
return 0;
log_debug("Deserializing...");
_cleanup_fclose_ FILE *f = take_fdopen(&fd, "r");
if (!f)
return log_debug_errno(errno, "Failed to fdopen() serialization file descriptor: %m");
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
unsigned err_line, err_column;
r = sd_json_parse_file(
f,
/* path = */ NULL,
/* flags = */ 0,
&v,
&err_line,
&err_column);
if (r < 0)
return log_debug_errno(r, "Failed to parse json (line=%u, column=%u): %m", err_line, err_column);
sd_json_variant *i;
JSON_VARIANT_ARRAY_FOREACH(i, sd_json_variant_by_key(v, "Interfaces"))
RET_GATHER(ret, manager_deserialize_link(manager, i));
JSON_VARIANT_ARRAY_FOREACH(i, sd_json_variant_by_key(v, "Routes"))
RET_GATHER(ret, manager_deserialize_route(manager, i));
log_debug("Deserialization completed.");
return ret;
}

View File

@ -0,0 +1,8 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
typedef struct Manager Manager;
int manager_serialize(Manager *manager);
int manager_set_serialization_fd(Manager *manager, int fd, const char *name);
int manager_deserialize(Manager *manager);

View File

@ -16,6 +16,7 @@
#include "networkd-conf.h"
#include "networkd-manager-bus.h"
#include "networkd-manager.h"
#include "networkd-serialize.h"
#include "service-util.h"
#include "signal-util.h"
#include "strv.h"
@ -100,6 +101,10 @@ static int run(int argc, char *argv[]) {
if (r < 0)
return r;
r = manager_deserialize(m);
if (r < 0)
log_warning_errno(r, "Failed to deserialize the prvious invocation, ignoring: %m");
r = manager_start(m);
if (r < 0)
return log_error_errno(r, "Could not start manager: %m");

View File

@ -1808,63 +1808,81 @@ char* umount_and_unlink_and_free(char *p) {
return mfree(p);
}
static int path_get_mount_info(
static int path_get_mount_info_at(
int dir_fd,
const char *path,
char **ret_fstype,
char **ret_options) {
_cleanup_(mnt_free_tablep) struct libmnt_table *table = NULL;
_cleanup_free_ char *fstype = NULL, *options = NULL;
struct libmnt_fs *fs;
int r;
_cleanup_(mnt_free_iterp) struct libmnt_iter *iter = NULL;
int r, mnt_id;
assert(path);
assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
table = mnt_new_table();
if (!table)
return -ENOMEM;
r = mnt_table_parse_mtab(table, /* filename = */ NULL);
r = path_get_mnt_id_at(dir_fd, path, &mnt_id);
if (r < 0)
return r;
return log_debug_errno(r, "Failed to get mount ID: %m");
fs = mnt_table_find_mountpoint(table, path, MNT_ITER_FORWARD);
if (!fs)
return -EINVAL;
r = libmount_parse("/proc/self/mountinfo", NULL, &table, &iter);
if (r < 0)
return log_debug_errno(r, "Failed to parse /proc/self/mountinfo: %m");
if (ret_fstype) {
fstype = strdup(strempty(mnt_fs_get_fstype(fs)));
if (!fstype)
return -ENOMEM;
for (;;) {
struct libmnt_fs *fs;
r = mnt_table_next_fs(table, iter, &fs);
if (r == 1)
break; /* EOF */
if (r < 0)
return log_debug_errno(r, "Failed to get next entry from /proc/self/mountinfo: %m");
if (mnt_fs_get_id(fs) != mnt_id)
continue;
_cleanup_free_ char *fstype = NULL, *options = NULL;
if (ret_fstype) {
fstype = strdup(strempty(mnt_fs_get_fstype(fs)));
if (!fstype)
return log_oom_debug();
}
if (ret_options) {
options = strdup(strempty(mnt_fs_get_options(fs)));
if (!options)
return log_oom_debug();
}
if (ret_fstype)
*ret_fstype = TAKE_PTR(fstype);
if (ret_options)
*ret_options = TAKE_PTR(options);
return 0;
}
if (ret_options) {
options = strdup(strempty(mnt_fs_get_options(fs)));
if (!options)
return -ENOMEM;
}
if (ret_fstype)
*ret_fstype = TAKE_PTR(fstype);
if (ret_options)
*ret_options = TAKE_PTR(options);
return 0;
return log_debug_errno(SYNTHETIC_ERRNO(ESTALE), "Cannot find mount ID %i from /proc/self/mountinfo.", mnt_id);
}
int path_is_network_fs_harder(const char *path) {
int path_is_network_fs_harder_at(int dir_fd, const char *path) {
_cleanup_close_ int fd = -EBADF;
int r;
assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
fd = xopenat(dir_fd, path, O_PATH | O_CLOEXEC | O_NOFOLLOW);
if (fd < 0)
return fd;
r = fd_is_network_fs(fd);
if (r != 0)
return r;
_cleanup_free_ char *fstype = NULL, *options = NULL;
int r, ret;
assert(path);
ret = path_is_network_fs(path);
if (ret > 0)
return true;
r = path_get_mount_info(path, &fstype, &options);
r = path_get_mount_info_at(fd, /* path = */ NULL, &fstype, &options);
if (r < 0)
return RET_GATHER(ret, r);
return r;
if (fstype_is_network(fstype))
return true;

View File

@ -181,4 +181,7 @@ int mount_credentials_fs(const char *path, size_t size, bool ro);
int make_fsmount(int error_log_level, const char *what, const char *type, unsigned long flags, const char *options, int userns_fd);
int path_is_network_fs_harder(const char *path);
int path_is_network_fs_harder_at(int dir_fd, const char *path);
static inline int path_is_network_fs_harder(const char *path) {
return path_is_network_fs_harder_at(AT_FDCWD, path);
}

View File

@ -538,9 +538,53 @@ TEST(bind_mount_submounts) {
}
TEST(path_is_network_fs_harder) {
ASSERT_OK_ZERO(path_is_network_fs_harder("/dev"));
ASSERT_OK_ZERO(path_is_network_fs_harder("/sys"));
ASSERT_OK_ZERO(path_is_network_fs_harder("/run"));
_cleanup_close_ int dir_fd = -EBADF;
int r;
ASSERT_OK(dir_fd = open("/", O_PATH | O_CLOEXEC));
FOREACH_STRING(s,
"/", "/dev/", "/proc/", "/run/", "/sys/", "/tmp/", "/usr/", "/var/tmp/",
"", ".", "../../../", "/this/path/should/not/exist/for/test-mount-util/") {
r = path_is_network_fs_harder(s);
log_debug("path_is_network_fs_harder(%s) → %i: %s", s, r, r < 0 ? STRERROR(r) : yes_no(r));
const char *q = path_startswith(s, "/") ?: s;
r = path_is_network_fs_harder_at(dir_fd, q);
log_debug("path_is_network_fs_harder_at(root, %s) → %i: %s", q, r, r < 0 ? STRERROR(r) : yes_no(r));
}
if (geteuid() != 0 || have_effective_cap(CAP_SYS_ADMIN) <= 0) {
(void) log_tests_skipped("not running privileged");
return;
}
_cleanup_(rm_rf_physical_and_freep) char *t = NULL;
assert_se(mkdtemp_malloc("/tmp/test-mount-util.path_is_network_fs_harder.XXXXXXX", &t) >= 0);
r = safe_fork("(make_mount-point)",
FORK_RESET_SIGNALS |
FORK_CLOSE_ALL_FDS |
FORK_DEATHSIG_SIGTERM |
FORK_WAIT |
FORK_REOPEN_LOG |
FORK_LOG |
FORK_NEW_MOUNTNS |
FORK_MOUNTNS_SLAVE,
NULL);
ASSERT_OK(r);
if (r == 0) {
ASSERT_OK(mount_nofollow_verbose(LOG_INFO, "tmpfs", t, "tmpfs", 0, NULL));
ASSERT_OK_ZERO(path_is_network_fs_harder(t));
ASSERT_OK_ERRNO(umount(t));
ASSERT_OK(mount_nofollow_verbose(LOG_INFO, "tmpfs", t, "tmpfs", 0, "x-systemd-growfs,x-systemd-automount"));
ASSERT_OK_ZERO(path_is_network_fs_harder(t));
ASSERT_OK_ERRNO(umount(t));
_exit(EXIT_SUCCESS);
}
}
DEFINE_TEST_MAIN(LOG_DEBUG);

View File

@ -142,11 +142,13 @@ endif
############################################################
if install_tests
foreach script : ['integration-test-setup.sh', 'run-unit-tests.py']
install_data(script,
install_mode : 'rwxr-xr-x',
install_dir : testsdir)
endforeach
install_data('run-unit-tests.py',
install_mode : 'rwxr-xr-x',
install_dir : testsdir)
install_data('integration-test-setup.sh',
install_mode : 'rwxr-xr-x',
install_dir : testdata_dir)
endif
############################################################

View File

@ -5,4 +5,4 @@ Name=veth99
[Network]
DHCP=ipv4
IPv6AcceptRA=false
KeepConfiguration=dhcp-on-stop
KeepConfiguration=dynamic-on-stop

View File

@ -5,4 +5,4 @@ Name=veth99
[Network]
DHCP=ipv4
IPv6AcceptRA=false
KeepConfiguration=dhcp
KeepConfiguration=dynamic

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
@ -7240,10 +7239,10 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
self.assertNotIn('test-hostname', output)
self.assertNotIn('26:mtu', output)
def test_dhcp_keep_configuration_dhcp(self):
def test_dhcp_keep_configuration_dynamic(self):
copy_network_unit('25-veth.netdev',
'25-dhcp-server-veth-peer.network',
'25-dhcp-client-keep-configuration-dhcp.network')
'25-dhcp-client-keep-configuration-dynamic.network')
start_networkd()
self.wait_online('veth-peer:carrier')
start_dnsmasq()
@ -7275,7 +7274,7 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global veth99\n *'
'valid_lft forever preferred_lft forever')
with open(os.path.join(network_unit_dir, '25-dhcp-client-keep-configuration-dhcp.network'), mode='a', encoding='utf-8') as f:
with open(os.path.join(network_unit_dir, '25-dhcp-client-keep-configuration-dynamic.network'), mode='a', encoding='utf-8') as f:
f.write('[Network]\nDHCP=no\n')
start_networkd()
@ -7287,10 +7286,10 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global veth99\n *'
'valid_lft forever preferred_lft forever')
def test_dhcp_keep_configuration_dhcp_on_stop(self):
def test_dhcp_keep_configuration_dynamic_on_stop(self):
copy_network_unit('25-veth.netdev',
'25-dhcp-server-veth-peer.network',
'25-dhcp-client-keep-configuration-dhcp-on-stop.network')
'25-dhcp-client-keep-configuration-dynamic-on-stop.network')
start_networkd()
self.wait_online('veth-peer:carrier')
start_dnsmasq()
@ -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):

View File

@ -7,9 +7,9 @@ Before=getty-pre.target
[Service]
ExecStartPre=rm -f /failed /testok
ExecStartPre=/usr/lib/systemd/tests/integration-test-setup.sh setup
ExecStartPre=/usr/lib/systemd/tests/testdata/integration-test-setup.sh setup
ExecStart=@command@
ExecStopPost=/usr/lib/systemd/tests/integration-test-setup.sh finalize
ExecStopPost=/usr/lib/systemd/tests/testdata/integration-test-setup.sh finalize
Type=oneshot
MemoryAccounting=@memory-accounting@
StateDirectory=%N

View File

@ -132,10 +132,12 @@ testcase_unpriv() {
return 0
fi
# The kernel has a restriction for unprivileged user namespaces where they cannot mount a less restrictive
# instance of /proc/. So if /proc/ is masked (e.g. /proc/kmsg is over-mounted with tmpfs as systemd-nspawn does),
# then mounting a new /proc/ will fail and we will still see the host's /proc/. Thus, to allow tests to run in
# a VM or nspawn, we mount a new proc on a temporary directory with no masking to bypass this kernel restriction.
# IMPORTANT: For /proc/ to be remounted in pid namespace within an unprivileged user namespace, there needs to
# be at least 1 unmasked procfs mount in ANY directory. Otherwise, if /proc/ is masked (e.g. /proc/scsi is
# over-mounted with tmpfs), then mounting a new /proc/ will fail.
#
# Thus, to guarantee PrivatePIDs=yes tests for unprivileged users pass, we mount a new procfs on a temporary
# directory with no masking. This will guarantee an unprivileged user can mount a new /proc/ successfully.
mkdir -p /tmp/TEST-07-PID1-private-pids-proc
mount -t proc proc /tmp/TEST-07-PID1-private-pids-proc
@ -146,7 +148,16 @@ testcase_unpriv() {
umount /tmp/TEST-07-PID1-private-pids-proc
rm -rf /tmp/TEST-07-PID1-private-pids-proc
# Now verify the behavior with masking - units should fail as PrivatePIDs=yes has no graceful fallback.
# Now we will mask /proc/ by mounting tmpfs over /proc/scsi. This will guarantee that mounting /proc/ will fail
# for unprivileged users when using PrivatePIDs=yes. Now units should fail as PrivatePIDs=yes has no graceful
# fallback.
#
# Note some kernels do not have /proc/scsi so we verify the directory exists prior to running the test.
if [ ! -d /proc/scsi ]; then
echo "/proc/scsi does not exist, skipping unprivileged PrivatePIDs=yes test with masked /proc/"
return 0
fi
if [[ "$HAS_EXISTING_SCSI_MOUNT" == "no" ]]; then
mount -t tmpfs tmpfs /proc/scsi
fi