machined: add varlink interface for registering machines

This commit adds the new varlink interface io.systemd.Machine at
/run/systemd/machine/io.systemd.Machine with a single method Register

It supports all combinations of RegisterMachine[WithSSH,WithNetwork] all
under the same method.
This commit is contained in:
Sam Leonard
2024-05-08 11:54:31 +01:00
committed by Luca Boccassi
parent 1f815bf164
commit 5b44c81ff8
10 changed files with 275 additions and 8 deletions
+4 -2
View File
@@ -100,10 +100,12 @@
<para>The daemon provides both a C library interface
(which is shared with <citerefentry><refentrytitle>systemd-logind.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>)
as well as a D-Bus interface.
as well as a D-Bus interface and a Varlink interface.
The library interface may be used to introspect and watch the state of virtual machines/containers.
The bus interface provides the same but in addition may also be used to register or terminate
machines.
machines. The Varlink interface may be used to register machines with optional extensions, e.g. with an
SSH key / address; it can be queried with
<command>varlinkctl introspect /run/systemd/machine/io.systemd.Machine io.systemd.Machine</command>.
For more information please consult
<citerefentry><refentrytitle>sd-login</refentrytitle><manvolnum>3</manvolnum></citerefentry>
and
+171
View File
@@ -0,0 +1,171 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <limits.h>
#include "sd-id128.h"
#include "hostname-util.h"
#include "json.h"
#include "machine-varlink.h"
#include "machine.h"
#include "path-util.h"
#include "pidref.h"
#include "process-util.h"
#include "socket-util.h"
#include "string-util.h"
#include "varlink.h"
static JSON_DISPATCH_ENUM_DEFINE(dispatch_machine_class, MachineClass, machine_class_from_string);
static int machine_name(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
char **m = ASSERT_PTR(userdata);
const char *hostname;
int r;
assert(variant);
if (!json_variant_is_string(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
hostname = json_variant_string(variant);
if (!hostname_is_valid(hostname, /* flags= */ 0))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Invalid machine name");
r = free_and_strdup(m, hostname);
if (r < 0)
return json_log_oom(variant, flags);
return 0;
}
static int machine_leader(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
PidRef *leader = ASSERT_PTR(userdata);
_cleanup_(pidref_done) PidRef temp = PIDREF_NULL;
uint64_t k;
int r;
if (!json_variant_is_unsigned(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an integer.", strna(name));
k = json_variant_unsigned(variant);
if (k > PID_T_MAX || !pid_is_valid(k))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid PID.", strna(name));
if (k == 1)
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid leader PID.", strna(name));
r = pidref_set_pid(&temp, k);
if (r < 0)
return json_log(variant, flags, r, "Failed to pin process " PID_FMT ": %m", leader->pid);
pidref_done(leader);
*leader = TAKE_PIDREF(temp);
return 0;
}
static int machine_ifindices(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
Machine *m = ASSERT_PTR(userdata);
_cleanup_free_ int *netif = NULL;
size_t n_netif, k = 0;
assert(variant);
if (!json_variant_is_array(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name));
n_netif = json_variant_elements(variant);
netif = new(int, n_netif);
if (!netif)
return json_log_oom(variant, flags);
JsonVariant *i;
JSON_VARIANT_ARRAY_FOREACH(i, variant) {
uint64_t b;
if (!json_variant_is_unsigned(i))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Element %zu of JSON field '%s' is not an unsigned integer.", k, strna(name));
b = json_variant_unsigned(i);
if (b > INT_MAX || b <= 0)
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Invalid network interface index %"PRIu64, b);
netif[k++] = (int) b;
}
assert(k == n_netif);
free_and_replace(m->netif, netif);
m->n_netif = n_netif;
return 0;
}
static int machine_cid(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
unsigned cid, *c = ASSERT_PTR(userdata);
assert(variant);
if (!json_variant_is_unsigned(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
cid = json_variant_unsigned(variant);
if (!VSOCK_CID_IS_REGULAR(cid))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a regular VSOCK CID.", strna(name));
*c = cid;
return 0;
}
int vl_method_register(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
Manager *manager = ASSERT_PTR(userdata);
_cleanup_(machine_freep) Machine *machine = NULL;
int r;
static const JsonDispatch dispatch_table[] = {
{ "name", JSON_VARIANT_STRING, machine_name, offsetof(Machine, name), JSON_MANDATORY },
{ "id", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(Machine, id), 0 },
{ "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(Machine, service), 0 },
{ "class", JSON_VARIANT_STRING, dispatch_machine_class, offsetof(Machine, class), JSON_MANDATORY },
{ "leader", JSON_VARIANT_UNSIGNED, machine_leader, offsetof(Machine, leader), 0 },
{ "rootDirectory", JSON_VARIANT_STRING, json_dispatch_absolute_path, offsetof(Machine, root_directory), 0 },
{ "ifIndices", JSON_VARIANT_ARRAY, machine_ifindices, 0, 0 },
{ "vsockCid", JSON_VARIANT_UNSIGNED, machine_cid, offsetof(Machine, vsock_cid), 0 },
{ "sshAddress", JSON_VARIANT_STRING, json_dispatch_string, offsetof(Machine, ssh_address), JSON_SAFE },
{ "sshPrivateKeyPath", JSON_VARIANT_STRING, json_dispatch_absolute_path, offsetof(Machine, ssh_private_key_path), 0 },
{}
};
r = machine_new(_MACHINE_CLASS_INVALID, NULL, &machine);
if (r < 0)
return r;
r = varlink_dispatch(link, parameters, dispatch_table, machine);
if (r != 0)
return r;
if (!pidref_is_set(&machine->leader)) {
r = varlink_get_peer_pidref(link, &machine->leader);
if (r < 0)
return r;
}
r = machine_link(manager, machine);
if (r < 0)
return r;
r = cg_pidref_get_unit(&machine->leader, &machine->unit);
if (r < 0)
return r;
r = machine_start(machine, NULL, NULL);
if (r < 0)
return r;
/* the manager will free this machine */
TAKE_PTR(machine);
return varlink_reply(link, NULL);
}
+6
View File
@@ -0,0 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "varlink.h"
int vl_method_register(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata);
+58 -4
View File
@@ -1,10 +1,12 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "format-util.h"
#include "machine-varlink.h"
#include "machined-varlink.h"
#include "mkdir.h"
#include "user-util.h"
#include "varlink.h"
#include "varlink-io.systemd.Machine.h"
#include "varlink-io.systemd.UserDatabase.h"
typedef struct LookupParameters {
@@ -378,13 +380,13 @@ static int vl_method_get_memberships(Varlink *link, JsonVariant *parameters, Var
return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
}
int manager_varlink_init(Manager *m) {
static int manager_varlink_init_userdb(Manager *m) {
_cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL;
int r;
assert(m);
if (m->varlink_server)
if (m->varlink_userdb_server)
return 0;
r = varlink_server_new(&s, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA);
@@ -415,12 +417,64 @@ int manager_varlink_init(Manager *m) {
if (r < 0)
return log_error_errno(r, "Failed to attach varlink connection to event loop: %m");
m->varlink_server = TAKE_PTR(s);
m->varlink_userdb_server = TAKE_PTR(s);
return 0;
}
static int manager_varlink_init_machine(Manager *m) {
_cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL;
int r;
assert(m);
if (m->varlink_machine_server)
return 0;
r = varlink_server_new(&s, VARLINK_SERVER_ROOT_ONLY|VARLINK_SERVER_INHERIT_USERDATA);
if (r < 0)
return log_error_errno(r, "Failed to allocate varlink server object: %m");
varlink_server_set_userdata(s, m);
r = varlink_server_add_interface(s, &vl_interface_io_systemd_Machine);
if (r < 0)
return log_error_errno(r, "Failed to add UserDatabase interface to varlink server: %m");
r = varlink_server_bind_method(s, "io.systemd.Machine.Register", vl_method_register);
if (r < 0)
return log_error_errno(r, "Failed to register varlink methods: %m");
(void) mkdir_p("/run/systemd/machine", 0755);
r = varlink_server_listen_address(s, "/run/systemd/machine/io.systemd.Machine", 0666);
if (r < 0)
return log_error_errno(r, "Failed to bind to varlink socket: %m");
r = varlink_server_attach_event(s, m->event, SD_EVENT_PRIORITY_NORMAL);
if (r < 0)
return log_error_errno(r, "Failed to attach varlink connection to event loop: %m");
m->varlink_machine_server = TAKE_PTR(s);
return 0;
}
int manager_varlink_init(Manager *m) {
int r;
r = manager_varlink_init_userdb(m);
if (r < 0)
return r;
r = manager_varlink_init_machine(m);
if (r < 0)
return r;
return 0;
}
void manager_varlink_done(Manager *m) {
assert(m);
m->varlink_server = varlink_server_unref(m->varlink_server);
m->varlink_userdb_server = varlink_server_unref(m->varlink_userdb_server);
m->varlink_machine_server = varlink_server_unref(m->varlink_machine_server);
}
+4 -1
View File
@@ -316,7 +316,10 @@ static bool check_idle(void *userdata) {
if (m->operations)
return false;
if (varlink_server_current_connections(m->varlink_server) > 0)
if (varlink_server_current_connections(m->varlink_userdb_server) > 0)
return false;
if (varlink_server_current_connections(m->varlink_machine_server) > 0)
return false;
manager_gc(m, true);
+2 -1
View File
@@ -40,7 +40,8 @@ struct Manager {
sd_event_source *nscd_cache_flush_event;
#endif
VarlinkServer *varlink_server;
VarlinkServer *varlink_userdb_server;
VarlinkServer *varlink_machine_server;
};
int manager_add_machine(Manager *m, const char *name, Machine **_machine);
+1
View File
@@ -3,6 +3,7 @@
libmachine_core_sources = files(
'image-dbus.c',
'machine-dbus.c',
'machine-varlink.c',
'machine.c',
'machined-core.c',
'machined-dbus.c',
+1
View File
@@ -180,6 +180,7 @@ shared_sources = files(
'varlink-io.systemd.Credentials.c',
'varlink-io.systemd.Hostname.c',
'varlink-io.systemd.Journal.c',
'varlink-io.systemd.Machine.c',
'varlink-io.systemd.ManagedOOM.c',
'varlink-io.systemd.MountFileSystem.c',
'varlink-io.systemd.NamespaceResource.c',
+22
View File
@@ -0,0 +1,22 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "varlink-idl.h"
#include "varlink-io.systemd.Machine.h"
static VARLINK_DEFINE_METHOD(
Register,
VARLINK_DEFINE_INPUT(name, VARLINK_STRING, 0),
VARLINK_DEFINE_INPUT(id, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_INPUT(service, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_INPUT(class, VARLINK_STRING, 0),
VARLINK_DEFINE_INPUT(leader, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_INPUT(rootDirectory, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_INPUT(ifIndices, VARLINK_INT, VARLINK_ARRAY|VARLINK_NULLABLE),
VARLINK_DEFINE_INPUT(vsockCid, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_INPUT(sshAddress, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_INPUT(sshPrivateKeyPath, VARLINK_STRING, VARLINK_NULLABLE));
VARLINK_DEFINE_INTERFACE(
io_systemd_Machine,
"io.systemd.Machine",
&vl_method_Register);
+6
View File
@@ -0,0 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "varlink-idl.h"
extern const VarlinkInterface vl_interface_io_systemd_Machine;