469 lines
22 KiB
C
469 lines
22 KiB
C
/* 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-nexthop.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 = nexthops_append_json(manager, /* ifindex = */ -1, &v);
|
|
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 NextHopParam {
|
|
uint32_t id;
|
|
int family;
|
|
NetworkConfigSource source;
|
|
struct iovec provider;
|
|
} NextHopParam;
|
|
|
|
static void nexthop_param_done(NextHopParam *p) {
|
|
assert(p);
|
|
|
|
iovec_done(&p->provider);
|
|
}
|
|
|
|
static int manager_deserialize_nexthop(Manager *manager, sd_json_variant *v) {
|
|
static const sd_json_dispatch_field dispatch_table[] = {
|
|
{ "ID", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint32, offsetof(NextHopParam, id), SD_JSON_MANDATORY },
|
|
{ "Family", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_address_family, offsetof(NextHopParam, family), SD_JSON_MANDATORY },
|
|
{ "ConfigSource", SD_JSON_VARIANT_STRING, json_dispatch_network_config_source, offsetof(NextHopParam, source), SD_JSON_MANDATORY },
|
|
{ "ConfigProvider", SD_JSON_VARIANT_ARRAY, json_dispatch_byte_array_iovec, offsetof(NextHopParam, provider), 0 },
|
|
{},
|
|
};
|
|
|
|
int r;
|
|
|
|
assert(manager);
|
|
assert(v);
|
|
|
|
_cleanup_(nexthop_param_done) NextHopParam p = {};
|
|
r = sd_json_dispatch(v, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p);
|
|
if (r < 0)
|
|
return log_debug_errno(r, "Failed to dispatch nexthop from json variant: %m");
|
|
|
|
if (p.id == 0)
|
|
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Dispatched nexthop ID is zero.");
|
|
|
|
if (p.provider.iov_len != 0 && p.provider.iov_len != FAMILY_ADDRESS_SIZE(p.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.family));
|
|
|
|
NextHop *nexthop;
|
|
r = nexthop_get_by_id(manager, p.id, &nexthop);
|
|
if (r < 0) {
|
|
log_debug_errno(r, "Cannot find deserialized nexthop (ID=%"PRIu32"): %m", p.id);
|
|
return 0; /* Already removed? */
|
|
}
|
|
|
|
if (nexthop->source != NETWORK_CONFIG_SOURCE_FOREIGN)
|
|
return 0; /* Huh?? Already deserialized?? */
|
|
|
|
nexthop->source = p.source;
|
|
memcpy_safe(&nexthop->provider, p.provider.iov_base, p.provider.iov_len);
|
|
|
|
log_nexthop_debug(nexthop, "Deserialized", manager);
|
|
return 0;
|
|
}
|
|
|
|
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, "NextHops"))
|
|
RET_GATHER(ret, manager_deserialize_nexthop(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;
|
|
}
|