summaryrefslogtreecommitdiffstats
path: root/src/network
diff options
context:
space:
mode:
authorYu Watanabe <watanabe.yu+github@gmail.com>2024-11-14 03:59:44 +0100
committerGitHub <noreply@github.com>2024-11-14 03:59:44 +0100
commitbdc6edbdabfa0c8844d00f9863ec1d81d7804cc5 (patch)
treec62ad9e23c9391ff4a40bbc42da23156e960c4aa /src/network
parentnamespace-util: pin pid via pidfd during namespace_open() (diff)
parenttest-network: update KeepConfiguration=dhcp -> dynamic (diff)
downloadsystemd-bdc6edbdabfa0c8844d00f9863ec1d81d7804cc5.tar.xz
systemd-bdc6edbdabfa0c8844d00f9863ec1d81d7804cc5.zip
network: serialize and deserialize current configuration (#34989)
Replaces #34963. Fixes #26602. Fixes #32569.
Diffstat (limited to 'src/network')
-rw-r--r--src/network/meson.build1
-rw-r--r--src/network/networkd-address.c10
-rw-r--r--src/network/networkd-dhcp4.c12
-rw-r--r--src/network/networkd-ipv4ll.c23
-rw-r--r--src/network/networkd-json.c191
-rw-r--r--src/network/networkd-json.h4
-rw-r--r--src/network/networkd-link.c56
-rw-r--r--src/network/networkd-link.h2
-rw-r--r--src/network/networkd-manager.c14
-rw-r--r--src/network/networkd-manager.h2
-rw-r--r--src/network/networkd-network.c50
-rw-r--r--src/network/networkd-network.h14
-rw-r--r--src/network/networkd-route.c2
-rw-r--r--src/network/networkd-serialize.c403
-rw-r--r--src/network/networkd-serialize.h8
-rw-r--r--src/network/networkd.c5
16 files changed, 672 insertions, 125 deletions
diff --git a/src/network/meson.build b/src/network/meson.build
index 73c48e06af..295d015e7c 100644
--- a/src/network/meson.build
+++ b/src/network/meson.build
@@ -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',
diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c
index a689fa68bd..0b7fbec28b 100644
--- a/src/network/networkd-address.c
+++ b/src/network/networkd-address.c
@@ -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)
diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c
index 684580f5b6..d94ac1a213 100644
--- a/src/network/networkd-dhcp4.c
+++ b/src/network/networkd-dhcp4.c
@@ -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) {
diff --git a/src/network/networkd-ipv4ll.c b/src/network/networkd-ipv4ll.c
index 7398cefe77..046e4ca957 100644
--- a/src/network/networkd-ipv4ll.c
+++ b/src/network/networkd-ipv4ll.c
@@ -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 sure the address is not removed by link_drop_unmanaged_addresses(). */
+ a->source = NETWORK_CONFIG_SOURCE_IPV4LL;
+ return 0;
+ }
+
return 0;
}
diff --git a/src/network/networkd-json.c b/src/network/networkd-json.c
index fd2b709d9d..460fbe5968 100644
--- a/src/network/networkd-json.c
+++ b/src/network/networkd-json.c
@@ -28,55 +28,78 @@
#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_(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, *state = 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 = network_config_state_to_string_alloc(address->state, &state);
+ 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 +222,15 @@ 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_(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 +241,67 @@ 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, *state = 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 = network_config_state_to_string_alloc(route->state, &state);
+ 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 +310,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. */
+
+ if (route->source == NETWORK_CONFIG_SOURCE_FOREIGN)
+ continue;
+ if (!route_exists(route))
+ continue;
+
+ log_route_debug(route, "Serializing", manager);
+ }
- r = route_append_json(route, &array);
+ r = route_append_json(route, /* serializing = */ ifindex < 0, &array);
if (r < 0)
return r;
}
@@ -1413,7 +1468,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;
diff --git a/src/network/networkd-json.h b/src/network/networkd-json.h
index b3cdb5cae8..e8be60458f 100644
--- a/src/network/networkd-json.h
+++ b/src/network/networkd-json.h
@@ -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);
diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c
index 082c7d1c38..4b341a96c7 100644
--- a/src/network/networkd-link.c
+++ b/src/network/networkd-link.c
@@ -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) {
@@ -1339,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);
@@ -1410,10 +1410,10 @@ int link_reconfigure_impl(Link *link, LinkReconfigurationFlag flags) {
/* Then, apply new .network file */
link->network = network_ref(network);
- if (FLAGS_SET(network->keep_configuration, KEEP_CONFIGURATION_DHCP) ||
+ 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=dhcp or yes, even if a clean reconfiguration is requested,
+ * 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)
@@ -1799,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;
diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h
index 848e67c843..113217cdb6 100644
--- a/src/network/networkd-link.h
+++ b/src/network/networkd-link.h
@@ -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_;
diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c
index 47299e3b27..9290255ad0 100644
--- a/src/network/networkd-manager.c
+++ b/src/network/networkd-manager.c
@@ -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);
}
diff --git a/src/network/networkd-manager.h b/src/network/networkd-manager.h
index 5f4508ea6f..faa3357151 100644
--- a/src/network/networkd-manager.h
+++ b/src/network/networkd-manager.h
@@ -129,6 +129,8 @@ struct Manager {
unsigned reloading;
+ int serialization_fd;
+
/* sysctl */
int ip_forwarding[2];
#if HAVE_VMLINUX_H
diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c
index 2488530829..89dcf4f6c4 100644
--- a/src/network/networkd-network.c
+++ b/src/network/networkd-network.c
@@ -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);
diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h
index 3ea94e0e49..03e3108623 100644
--- a/src/network/networkd-network.h
+++ b/src/network/networkd-network.h
@@ -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 {
diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c
index b85d2a037a..0f3f79ec4f 100644
--- a/src/network/networkd-route.c
+++ b/src/network/networkd-route.c
@@ -1486,7 +1486,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;
}
}
diff --git a/src/network/networkd-serialize.c b/src/network/networkd-serialize.c
new file mode 100644
index 0000000000..8cc74baae9
--- /dev/null
+++ b/src/network/networkd-serialize.c
@@ -0,0 +1,403 @@
+/* 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);
+
+ if (p.provider.iov_len != 0 && p.provider.iov_len != FAMILY_ADDRESS_SIZE(p.route.family))
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Dispatched provider address size (%zu) is incompatible with the family (%s).",
+ p.provider.iov_len, af_to_ipv4_ipv6(p.route.family));
+
+ 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;
+}
diff --git a/src/network/networkd-serialize.h b/src/network/networkd-serialize.h
new file mode 100644
index 0000000000..f04378fa5d
--- /dev/null
+++ b/src/network/networkd-serialize.h
@@ -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);
diff --git a/src/network/networkd.c b/src/network/networkd.c
index 2d7a6421f9..883f16d81b 100644
--- a/src/network/networkd.c
+++ b/src/network/networkd.c
@@ -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 previous invocation, ignoring: %m");
+
r = manager_start(m);
if (r < 0)
return log_error_errno(r, "Could not start manager: %m");