Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vmspawn: open dbus session in VM #32482

43 changes: 40 additions & 3 deletions man/org.freedesktop.machine1.xml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ node /org/freedesktop/machine1 {
in i signal);
GetMachineAddresses(in s name,
out a(iay) addresses);
GetMachineSSHInfo(in s name,
out s ssh_address,
out s ssh_private_key_path);
GetMachineOSRelease(in s name,
out a{ss} fields);
@org.freedesktop.systemd1.Privileged("true")
Expand Down Expand Up @@ -230,6 +233,8 @@ node /org/freedesktop/machine1 {

<variablelist class="dbus-method" generated="True" extra-ref="GetMachineAddresses()"/>

<variablelist class="dbus-method" generated="True" extra-ref="GetMachineSSHInfo()"/>

<variablelist class="dbus-method" generated="True" extra-ref="GetMachineOSRelease()"/>

<variablelist class="dbus-method" generated="True" extra-ref="OpenMachinePTY()"/>
Expand Down Expand Up @@ -378,6 +383,10 @@ node /org/freedesktop/machine1 {
<constant>AF_INET6</constant>) and a byte array containing the addresses. This is only supported for
containers that make use of network namespacing.</para>

<para><function>GetMachineSSHInfo()</function> retrieves the SSH information of a machine. This method
returns two strings, the SSH address which can be used to tell SSH where to connect, and the path
to the SSH private key required for the connection to succeed.</para>

<para><function>GetMachineOSRelease()</function> retrieves the OS release information of a
container. This method returns an array of key value pairs read from the
<citerefentry><refentrytitle>os-release</refentrytitle><manvolnum>5</manvolnum></citerefentry> file in
Expand Down Expand Up @@ -459,6 +468,8 @@ node /org/freedesktop/machine1/machine/rawhide {
Kill(in s who,
in i signal);
GetAddresses(out a(iay) addresses);
GetSSHInfo(out s ssh_address,
out s ssh_private_key_path);
GetOSRelease(out a{ss} fields);
GetUIDShift(out u shift);
OpenPTY(out h pty,
Expand Down Expand Up @@ -507,6 +518,12 @@ node /org/freedesktop/machine1/machine/rawhide {
readonly s RootDirectory = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly ai NetworkInterfaces = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly u VsockCid = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s SshAddress = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s SshPrivateKeyPath = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly s State = '...';
};
Expand Down Expand Up @@ -548,6 +565,8 @@ node /org/freedesktop/machine1/machine/rawhide {

<variablelist class="dbus-method" generated="True" extra-ref="GetAddresses()"/>

<variablelist class="dbus-method" generated="True" extra-ref="GetSSHInfo()"/>

<variablelist class="dbus-method" generated="True" extra-ref="GetOSRelease()"/>

<variablelist class="dbus-method" generated="True" extra-ref="GetUIDShift()"/>
Expand Down Expand Up @@ -590,6 +609,12 @@ node /org/freedesktop/machine1/machine/rawhide {

<variablelist class="dbus-property" generated="True" extra-ref="NetworkInterfaces"/>

<variablelist class="dbus-property" generated="True" extra-ref="VsockCid"/>

<variablelist class="dbus-property" generated="True" extra-ref="SshAddress"/>

<variablelist class="dbus-property" generated="True" extra-ref="SshPrivateKeyPath"/>

<variablelist class="dbus-property" generated="True" extra-ref="State"/>

<!--End of Autogenerated section-->
Expand All @@ -601,9 +626,9 @@ node /org/freedesktop/machine1/machine/rawhide {
take the same arguments as <function>TerminateMachine()</function> and
<function>KillMachine()</function> on the Manager interface, respectively.</para>

<para><function>GetAddresses()</function> and <function>GetOSRelease()</function> get the IP address and OS
release information from the machine. These methods take the same arguments as
<function>GetMachineAddresses()</function> and <function>GetMachineOSRelease()</function> of the
<para><function>GetAddresses()</function>, <function>GetSSHInfo()</function> and <function>GetOSRelease()</function> get the IP address,
SSH connection and OS release information from the machine. These methods take the same arguments as
<function>GetMachineAddresses()</function>, <function>GetMachineSSHInfo()</function> and <function>GetMachineOSRelease()</function> of the
Manager interface, respectively.</para>
</refsect2>

Expand Down Expand Up @@ -636,6 +661,15 @@ node /org/freedesktop/machine1/machine/rawhide {
towards the container, the VM or the host. For details about this information see the description of
<function>CreateMachineWithNetwork()</function> above.</para>

<para><varname>VsockCid</varname> is the VSOCK CID of the VM if it is known, or
<constant>VMADDR_CID_ANY</constant> otherwise.</para>

<para><varname>SshAddress</varname> is the address of the VM in a format <command>ssh</command> can understand
if it is known or the empty string.</para>

<para><varname>SshPrivateKeyPath</varname> is the path to the SSH private key of the VM if it is known
or the empty string.</para>

<para><varname>State</varname> is the state of the machine and is one of <literal>opening</literal>,
<literal>running</literal>, or <literal>closing</literal>. Note that the state machine is not considered
part of the API and states might be removed or added without this being considered API breakage.
Expand Down Expand Up @@ -675,11 +709,14 @@ $ gdbus introspect --system \
<title>The Manager Object</title>
<para><function>CopyFromMachineWithFlags()</function> and
<function>CopyToMachineWithFlags()</function> were added in version 252.</para>
<para><function>GetMachineSSHInfo()</function> was added in version 256.</para>
</refsect2>
<refsect2>
<title>Machine Objects</title>
<para><function>CopyFromWithFlags()</function> and
<function>CopyToWithFlags()</function> were added in version 252.</para>
<para><function>GetSSHInfo()</function>, <varname>VsockCid</varname>, <varname>SshAddress</varname>
and <varname>SshPrivateKeyPath</varname> were added in version 256.</para>
</refsect2>
</refsect1>
</refentry>
6 changes: 4 additions & 2 deletions man/systemd-machined.service.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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
to allow <command>machinectl shell</command> to work; 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
Expand Down
29 changes: 29 additions & 0 deletions src/machine/machine-dbus.c
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,27 @@ int bus_machine_method_get_addresses(sd_bus_message *message, void *userdata, sd
return sd_bus_send(NULL, reply, NULL);
}

int bus_machine_method_get_ssh_info(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
Machine *m = ASSERT_PTR(userdata);
int r;

assert(message);

r = sd_bus_message_new_method_return(message, &reply);
if (r < 0)
return r;

if (!m->ssh_address || !m->ssh_private_key_path)
return -ENOENT;

r = sd_bus_message_append(reply, "ss", m->ssh_address, m->ssh_private_key_path);
if (r < 0)
return r;

return sd_bus_send(NULL, reply, NULL);
}

#define EXIT_NOT_FOUND 2

int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Expand Down Expand Up @@ -1261,6 +1282,9 @@ static const sd_bus_vtable machine_vtable[] = {
SD_BUS_PROPERTY("Class", "s", property_get_class, offsetof(Machine, class), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RootDirectory", "s", NULL, offsetof(Machine, root_directory), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("NetworkInterfaces", "ai", property_get_netif, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("VsockCid", "u", NULL, offsetof(Machine, vsock_cid), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("SshAddress", "s", NULL, offsetof(Machine, ssh_address), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("SshPrivateKeyPath", "s", NULL, offsetof(Machine, ssh_private_key_path), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("State", "s", property_get_state, 0, 0),

SD_BUS_METHOD("Terminate",
Expand All @@ -1278,6 +1302,11 @@ static const sd_bus_vtable machine_vtable[] = {
SD_BUS_RESULT("a(iay)", addresses),
bus_machine_method_get_addresses,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_ARGS("GetSSHInfo",
SD_BUS_NO_ARGS,
SD_BUS_RESULT("s", ssh_address, "s", ssh_private_key_path),
bus_machine_method_get_ssh_info,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_ARGS("GetOSRelease",
SD_BUS_NO_ARGS,
SD_BUS_RESULT("a{ss}", fields),
Expand Down
1 change: 1 addition & 0 deletions src/machine/machine-dbus.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ int bus_machine_method_unregister(sd_bus_message *message, void *userdata, sd_bu
int bus_machine_method_terminate(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_machine_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_machine_method_get_addresses(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_machine_method_get_ssh_info(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_machine_method_open_pty(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_machine_method_open_login(sd_bus_message *message, void *userdata, sd_bus_error *error);
Expand Down
171 changes: 171 additions & 0 deletions src/machine/machine-varlink.c
Original file line number Diff line number Diff line change
@@ -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_pid_get_unit(machine->leader.pid, &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 changes: 6 additions & 0 deletions src/machine/machine-varlink.h
Original file line number Diff line number Diff line change
@@ -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);