]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
network: introduce manager_serialize()/deserialize()
authorYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 1 Nov 2024 21:03:09 +0000 (06:03 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Thu, 14 Nov 2024 01:21:55 +0000 (10:21 +0900)
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.
Preparation for later commits.

src/network/meson.build
src/network/networkd-json.c
src/network/networkd-json.h
src/network/networkd-manager.c
src/network/networkd-manager.h
src/network/networkd-serialize.c [new file with mode: 0644]
src/network/networkd-serialize.h [new file with mode: 0644]
src/network/networkd.c

index 73c48e06afcff61d260be271ca1c9234b8515854..295d015e7c41bbff29087fc8d725a15585f5fc3f 100644 (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',
index fd2b709d9df0a997153c596fb1e16493341a29b8..460fbe59685d8855d75c119b5896e3823552d928 100644 (file)
 #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;
 
index b3cdb5cae88d179cc7ac41e7ac26ad891f9cbf0e..e8be60458f3dc48ad02ce068d452ab9d2e612a8f 100644 (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);
index 47299e3b2738a7705c39aa0ed3862adfb5666b8b..ca6ab48502265bba0bc116aa97a74e4a40b04643 100644 (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);
 
@@ -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);
 }
 
index 5f4508ea6f9dab00f69d4e5775d3e40a50dce24c..faa335715169db1793a0eb73a2b577f36fc27e11 100644 (file)
@@ -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-serialize.c b/src/network/networkd-serialize.c
new file mode 100644 (file)
index 0000000..8cc74ba
--- /dev/null
@@ -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 (file)
index 0000000..f04378f
--- /dev/null
@@ -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);
index 2d7a6421f909603e15823ceeb7c9c84a4c55ec5e..883f16d81b257bc29bb6fed480457dfd091981e3 100644 (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 previous invocation, ignoring: %m");
+
         r = manager_start(m);
         if (r < 0)
                 return log_error_errno(r, "Could not start manager: %m");