From bed73f32acef7be6bb5d7d0184da89ddb41a8705 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 21 Jun 2024 17:05:26 +0200 Subject: [PATCH 1/5] varlink: yet another fix around handling of IDL comments Skip over them when validating a message against the IDL. Add test case that tests this. --- src/shared/varlink-idl.c | 6 ++++++ src/test/test-varlink-idl.c | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/shared/varlink-idl.c b/src/shared/varlink-idl.c index f81c38c53dd..e6a4942ad8b 100644 --- a/src/shared/varlink-idl.c +++ b/src/shared/varlink-idl.c @@ -1782,6 +1782,9 @@ static int varlink_idl_validate_symbol(const VarlinkSymbol *symbol, sd_json_vari for (const VarlinkField *field = symbol->fields; field->field_type != _VARLINK_FIELD_TYPE_END_MARKER; field++) { + if (field->field_type == _VARLINK_FIELD_COMMENT) + continue; + assert(field->field_type == VARLINK_ENUM_VALUE); if (streq_ptr(field->name, s)) { @@ -1810,6 +1813,9 @@ static int varlink_idl_validate_symbol(const VarlinkSymbol *symbol, sd_json_vari for (const VarlinkField *field = symbol->fields; field->field_type != _VARLINK_FIELD_TYPE_END_MARKER; field++) { + if (field->field_type == _VARLINK_FIELD_COMMENT) + continue; + if (field->field_direction != direction) continue; diff --git a/src/test/test-varlink-idl.c b/src/test/test-varlink-idl.c index c2be1dfec04..92a714b5bd0 100644 --- a/src/test/test-varlink-idl.c +++ b/src/test/test-varlink-idl.c @@ -279,7 +279,11 @@ TEST(validate_json) { /* This one has (nested) enonymous enums and structs */ static const char text[] = "interface validate.test\n" - "method Mymethod ( a:string, b:int, c:?bool, d:[]int, e:?[string]bool, f:?(piff, paff), g:(f:float) ) -> ()\n"; + "method Mymethod ( \n" + "# piff \n" + "a:string,\n" + "#paff\n" + "b:int, c:?bool, d:[]int, e:?[string]bool, f:?(piff, paff), g:(f:float) ) -> ()\n"; assert_se(varlink_idl_parse(text, NULL, NULL, &parsed) >= 0); test_parse_format_one(parsed); From 53dca805a2671e869cb8ed8955bc3c60365b0e8f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 24 May 2024 14:43:12 +0200 Subject: [PATCH 2/5] json: teach json_build() to serialize dual_timestamp structures reasonably --- src/libsystemd/sd-json/json-util.h | 2 ++ src/libsystemd/sd-json/sd-json.c | 34 ++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/libsystemd/sd-json/json-util.h b/src/libsystemd/sd-json/json-util.h index 72c8d80bf7d..fdfd5522db0 100644 --- a/src/libsystemd/sd-json/json-util.h +++ b/src/libsystemd/sd-json/json-util.h @@ -139,6 +139,7 @@ enum { _JSON_BUILD_HW_ADDR, _JSON_BUILD_STRING_SET, _JSON_BUILD_STRING_UNDERSCORIFY, + _JSON_BUILD_DUAL_TIMESTAMP, _JSON_BUILD_PAIR_UNSIGNED_NON_ZERO, _JSON_BUILD_PAIR_FINITE_USEC, @@ -166,6 +167,7 @@ enum { #define JSON_BUILD_HW_ADDR(v) _JSON_BUILD_HW_ADDR, (const struct hw_addr_data*) { v } #define JSON_BUILD_STRING_SET(s) _JSON_BUILD_STRING_SET, (Set *) { s } #define JSON_BUILD_STRING_UNDERSCORIFY(s) _JSON_BUILD_STRING_UNDERSCORIFY, (const char *) { s } +#define JSON_BUILD_DUAL_TIMESTAMP(t) _JSON_BUILD_DUAL_TIMESTAMP, (dual_timestamp*) { t } #define JSON_BUILD_PAIR_UNSIGNED_NON_ZERO(name, u) _JSON_BUILD_PAIR_UNSIGNED_NON_ZERO, (const char*) { name }, (uint64_t) { u } #define JSON_BUILD_PAIR_FINITE_USEC(name, u) _JSON_BUILD_PAIR_FINITE_USEC, (const char*) { name }, (usec_t) { u } diff --git a/src/libsystemd/sd-json/sd-json.c b/src/libsystemd/sd-json/sd-json.c index e092dcdbaee..1bccb2091bd 100644 --- a/src/libsystemd/sd-json/sd-json.c +++ b/src/libsystemd/sd-json/sd-json.c @@ -4079,6 +4079,40 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) { break; } + case _JSON_BUILD_DUAL_TIMESTAMP: { + dual_timestamp *ts; + + if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) { + r = -EINVAL; + goto finish; + } + + ts = va_arg(ap, dual_timestamp*); + + if (current->n_suppress == 0) { + if (dual_timestamp_is_set(ts)) { + r = sd_json_buildo( + &add, + SD_JSON_BUILD_PAIR("realtime", SD_JSON_BUILD_UNSIGNED(ts->realtime)), + SD_JSON_BUILD_PAIR("monotonic", SD_JSON_BUILD_UNSIGNED(ts->monotonic))); + if (r < 0) + return r; + } else + add = JSON_VARIANT_MAGIC_NULL; + } + + n_subtract = 1; + + if (current->expect == EXPECT_TOPLEVEL) + current->expect = EXPECT_END; + else if (current->expect == EXPECT_OBJECT_VALUE) + current->expect = EXPECT_OBJECT_KEY; + else + assert(current->expect == EXPECT_ARRAY_ELEMENT); + + break; + } + case _SD_JSON_BUILD_CALLBACK: { sd_json_build_callback_t cb; void *userdata; From 1c7642a3b7296845588bf82485bdf063136726fc Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sat, 11 May 2024 19:55:15 +0200 Subject: [PATCH 3/5] machined: add simple varlink API for listing machines --- src/machine/machined-varlink.c | 84 ++++++++++++++++++++++++- src/shared/varlink-io.systemd.Machine.c | 39 ++++++++++++ 2 files changed, 122 insertions(+), 1 deletion(-) diff --git a/src/machine/machined-varlink.c b/src/machine/machined-varlink.c index dc35877c497..38acfe97cd7 100644 --- a/src/machine/machined-varlink.c +++ b/src/machine/machined-varlink.c @@ -1,10 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "format-util.h" +#include "hostname-util.h" #include "json-util.h" #include "machine-varlink.h" #include "machined-varlink.h" #include "mkdir.h" +#include "socket-util.h" #include "user-util.h" #include "varlink.h" #include "varlink-io.systemd.Machine.h" @@ -383,6 +385,83 @@ static int vl_method_get_memberships(Varlink *link, sd_json_variant *parameters, return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); } +static int list_machine_one(Varlink *link, Machine *m, bool more) { + int r; + + assert(link); + assert(m); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + + r = sd_json_buildo( + &v, + SD_JSON_BUILD_PAIR("name", SD_JSON_BUILD_STRING(m->name)), + SD_JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(m->id), "id", SD_JSON_BUILD_ID128(m->id)), + SD_JSON_BUILD_PAIR("class", SD_JSON_BUILD_STRING(machine_class_to_string(m->class))), + SD_JSON_BUILD_PAIR_CONDITION(!!m->service, "service", SD_JSON_BUILD_STRING(m->service)), + SD_JSON_BUILD_PAIR_CONDITION(!!m->root_directory, "rootDirectory", SD_JSON_BUILD_STRING(m->root_directory)), + SD_JSON_BUILD_PAIR_CONDITION(!!m->unit, "unit", SD_JSON_BUILD_STRING(m->unit)), + SD_JSON_BUILD_PAIR_CONDITION(pidref_is_set(&m->leader), "leader", SD_JSON_BUILD_UNSIGNED(m->leader.pid)), + SD_JSON_BUILD_PAIR_CONDITION(dual_timestamp_is_set(&m->timestamp), "timestamp", JSON_BUILD_DUAL_TIMESTAMP(&m->timestamp)), + SD_JSON_BUILD_PAIR_CONDITION(m->vsock_cid != VMADDR_CID_ANY, "vSockCid", SD_JSON_BUILD_UNSIGNED(m->vsock_cid)), + SD_JSON_BUILD_PAIR_CONDITION(!!m->ssh_address, "sshAddress", SD_JSON_BUILD_STRING(m->ssh_address))); + if (r < 0) + return r; + + if (more) + return varlink_notify(link, v); + + return varlink_reply(link, v); +} + +static int vl_method_list(Varlink *link, sd_json_variant *parameters, VarlinkMethodFlags flags, void *userdata) { + Manager *m = ASSERT_PTR(userdata); + const char *mn = NULL; + + const sd_json_dispatch_field dispatch_table[] = { + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, PTR_TO_SIZE(&mn), 0 }, + {} + }; + + int r; + + assert(parameters); + + r = varlink_dispatch(link, parameters, dispatch_table, 0); + if (r != 0) + return r; + + if (mn) { + if (!hostname_is_valid(mn, /* flags= */ VALID_HOSTNAME_DOT_HOST)) + return varlink_error_invalid_parameter_name(link, "name"); + + Machine *machine = hashmap_get(m->machines, mn); + if (!machine) + return varlink_error(link, "io.systemd.Machine.NoSuchMachine", NULL); + + return list_machine_one(link, machine, /* more= */ false); + } + + if (!FLAGS_SET(flags, VARLINK_METHOD_MORE)) + return varlink_error(link, VARLINK_ERROR_EXPECTED_MORE, NULL); + + Machine *previous = NULL, *i; + HASHMAP_FOREACH(i, m->machines) { + if (previous) { + r = list_machine_one(link, previous, /* more= */ true); + if (r < 0) + return r; + } + + previous = i; + } + + if (previous) + return list_machine_one(link, previous, /* more= */ false); + + return varlink_error(link, "io.systemd.Machine.NoSuchMachine", NULL); +} + static int manager_varlink_init_userdb(Manager *m) { _cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL; int r; @@ -443,7 +522,10 @@ static int manager_varlink_init_machine(Manager *m) { 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); + r = varlink_server_bind_method_many( + s, + "io.systemd.Machine.Register", vl_method_register, + "io.systemd.Machine.List", vl_method_list); if (r < 0) return log_error_errno(r, "Failed to register varlink methods: %m"); diff --git a/src/shared/varlink-io.systemd.Machine.c b/src/shared/varlink-io.systemd.Machine.c index 2d25a345d79..b5f8f5c0751 100644 --- a/src/shared/varlink-io.systemd.Machine.c +++ b/src/shared/varlink-io.systemd.Machine.c @@ -16,10 +16,49 @@ static VARLINK_DEFINE_METHOD( VARLINK_DEFINE_INPUT(sshAddress, VARLINK_STRING, VARLINK_NULLABLE), VARLINK_DEFINE_INPUT(sshPrivateKeyPath, VARLINK_STRING, VARLINK_NULLABLE)); +static VARLINK_DEFINE_STRUCT_TYPE( + Timestamp, + VARLINK_FIELD_COMMENT("Timestamp in µs in the CLOCK_REALTIME clock (wallclock)"), + VARLINK_DEFINE_FIELD(realtime, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_FIELD_COMMENT("Timestamp in µs in the CLOCK_MONOTONIC clock"), + VARLINK_DEFINE_FIELD(monotonic, VARLINK_INT, VARLINK_NULLABLE)); + +static VARLINK_DEFINE_METHOD( + List, + VARLINK_FIELD_COMMENT("If non-null the name of a running machine to report details on. If null/unspecified enumerates all running machines."), + VARLINK_DEFINE_INPUT(name, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_FIELD_COMMENT("Name of the machine"), + VARLINK_DEFINE_OUTPUT(name, VARLINK_STRING, 0), + VARLINK_FIELD_COMMENT("128bit ID identifying this machine, formatted in hexadecimal"), + VARLINK_DEFINE_OUTPUT(id, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_FIELD_COMMENT("Name of the software that registered this machine"), + VARLINK_DEFINE_OUTPUT(service, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_FIELD_COMMENT("The class of this machine"), + VARLINK_DEFINE_OUTPUT(class, VARLINK_STRING, 0), + VARLINK_FIELD_COMMENT("Leader process PID of this machine"), + VARLINK_DEFINE_OUTPUT(leader, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_FIELD_COMMENT("Root directory of this machine, if known, relative to host file system"), + VARLINK_DEFINE_OUTPUT(rootDirectory, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_FIELD_COMMENT("The service manager unit this machine resides in"), + VARLINK_DEFINE_OUTPUT(unit, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_FIELD_COMMENT("Timestamp when the machine was activated"), + VARLINK_DEFINE_OUTPUT_BY_TYPE(timestamp, Timestamp, VARLINK_NULLABLE), + VARLINK_FIELD_COMMENT("AF_VSOCK CID of the machine if known and applicable"), + VARLINK_DEFINE_OUTPUT(vSockCid, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_FIELD_COMMENT("SSH address to connect to"), + VARLINK_DEFINE_OUTPUT(sshAddress, VARLINK_STRING, VARLINK_NULLABLE)); + +static VARLINK_DEFINE_ERROR(NoSuchMachine); static VARLINK_DEFINE_ERROR(MachineExists); VARLINK_DEFINE_INTERFACE( io_systemd_Machine, "io.systemd.Machine", + VARLINK_SYMBOL_COMMENT("A timestamp object consisting of both CLOCK_REALTIME and CLOCK_MONOTONIC timestamps"), + &vl_type_Timestamp, &vl_method_Register, + VARLINK_SYMBOL_COMMENT("List running machines"), + &vl_method_List, + VARLINK_SYMBOL_COMMENT("No matching machine currently running"), + &vl_error_NoSuchMachine, &vl_error_MachineExists); From 26b455d8156500ea883ccef5e1e4c610b8f575d8 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sat, 11 May 2024 19:56:10 +0200 Subject: [PATCH 4/5] =?UTF-8?q?ssh-proxy:=20add=20support=20for=20connecti?= =?UTF-8?q?ng=20to=20VMs=20by=20AF=5FVSOCK=20via=20"machine/=E2=80=A6"=20h?= =?UTF-8?q?ost=20specs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With this one can type "ssh machine/foobar" to connect to locally registered machine "foobar" via SSH-over-AF_VSOCK. --- .../20-systemd-ssh-proxy.conf.in | 17 ++--- src/ssh-generator/ssh-proxy.c | 68 +++++++++++++++++-- 2 files changed, 70 insertions(+), 15 deletions(-) diff --git a/src/ssh-generator/20-systemd-ssh-proxy.conf.in b/src/ssh-generator/20-systemd-ssh-proxy.conf.in index b97e0f5340b..912cc1f9ac4 100644 --- a/src/ssh-generator/20-systemd-ssh-proxy.conf.in +++ b/src/ssh-generator/20-systemd-ssh-proxy.conf.in @@ -1,8 +1,15 @@ # SPDX-License-Identifier: LGPL-2.1-or-later # -# Make sure unix/* and vsock/* can be used to connect to AF_UNIX and AF_VSOCK paths +# Allow connecting to the local host directly via ".host" +Host .host machine/.host + ProxyCommand {{LIBEXECDIR}}/systemd-ssh-proxy unix/run/ssh-unix-local/socket %p + ProxyUseFdpass yes + CheckHostIP no + +# Make sure unix/* and vsock/* can be used to connect to AF_UNIX and AF_VSOCK paths. +# Make sure machine/* can be used to connect to local machines registered in machined. # -Host unix/* vsock/* +Host unix/* vsock/* machine/* ProxyCommand {{LIBEXECDIR}}/systemd-ssh-proxy %h %p ProxyUseFdpass yes CheckHostIP no @@ -10,9 +17,3 @@ Host unix/* vsock/* # Disable all kinds of host identity checks, since these addresses are generally ephemeral. StrictHostKeyChecking no UserKnownHostsFile /dev/null - -# Allow connecting to the local host directly via ".host" -Host .host - ProxyCommand {{LIBEXECDIR}}/systemd-ssh-proxy unix/run/ssh-unix-local/socket %p - ProxyUseFdpass yes - CheckHostIP no diff --git a/src/ssh-generator/ssh-proxy.c b/src/ssh-generator/ssh-proxy.c index 7021ddd8a6e..36311136127 100644 --- a/src/ssh-generator/ssh-proxy.c +++ b/src/ssh-generator/ssh-proxy.c @@ -14,21 +14,19 @@ #include "socket-util.h" #include "string-util.h" #include "strv.h" +#include "varlink.h" -static int process_vsock(const char *host, const char *port) { +static int process_vsock_cid(unsigned cid, const char *port) { int r; - assert(host); + assert(cid != VMADDR_CID_ANY); assert(port); union sockaddr_union sa = { + .vm.svm_cid = cid, .vm.svm_family = AF_VSOCK, }; - r = vsock_parse_cid(host, &sa.vm.svm_cid); - if (r < 0) - return log_error_errno(r, "Failed to parse vsock cid: %s", host); - r = vsock_parse_port(port, &sa.vm.svm_port); if (r < 0) return log_error_errno(r, "Failed to parse vsock port: %s", port); @@ -47,6 +45,21 @@ static int process_vsock(const char *host, const char *port) { log_debug("Successfully sent AF_VSOCK socket via STDOUT."); return 0; + +} + +static int process_vsock_string(const char *host, const char *port) { + unsigned cid; + int r; + + assert(host); + assert(port); + + r = vsock_parse_cid(host, &cid); + if (r < 0) + return log_error_errno(r, "Failed to parse vsock cid: %s", host); + + return process_vsock_cid(cid, port); } static int process_unix(const char *path) { @@ -124,6 +137,43 @@ static int process_vsock_mux(const char *path, const char *port) { return 0; } +static int process_machine(const char *machine, const char *port) { + _cleanup_(varlink_unrefp) Varlink *vl = NULL; + int r; + + assert(machine); + assert(port); + + r = varlink_connect_address(&vl, "/run/systemd/machine/io.systemd.Machine"); + if (r < 0) + return log_error_errno(r, "Failed to connect to machined on /run/systemd/machine/io.systemd.Machine: %m"); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *result = NULL; + r = varlink_callbo_and_log( + vl, + "io.systemd.Machine.List", + &result, + SD_JSON_BUILD_PAIR("name", SD_JSON_BUILD_STRING(machine))); + if (r < 0) + return r; + + uint32_t cid = VMADDR_CID_ANY; + + const sd_json_dispatch_field dispatch_table[] = { + { "vSockCid", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint32, PTR_TO_SIZE(&cid), 0 }, + {} + }; + + r = sd_json_dispatch(result, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, NULL); + if (r < 0) + return log_error_errno(r, "Failed to parse Varlink reply: %m"); + + if (cid == VMADDR_CID_ANY) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Machine has no AF_VSOCK CID assigned."); + + return process_vsock_cid(cid, port); +} + static int run(int argc, char* argv[]) { log_setup(); @@ -135,7 +185,7 @@ static int run(int argc, char* argv[]) { const char *p = startswith(host, "vsock/"); if (p) - return process_vsock(p, port); + return process_vsock_string(p, port); p = startswith(host, "unix/"); if (p) @@ -145,6 +195,10 @@ static int run(int argc, char* argv[]) { if (p) return process_vsock_mux(p, port); + p = startswith(host, "machine/"); + if (p) + return process_machine(p, port); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Don't know how to parse host name specification: %s", host); } From f2b10bebb6ed3cb324372525f1b8412fe36d0957 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 23 May 2024 23:19:12 +0200 Subject: [PATCH 5/5] test: add some superficial integration tests --- test/units/TEST-13-NSPAWN.machinectl.sh | 3 +++ test/units/TEST-74-AUX-UTILS.ssh.sh | 1 + 2 files changed, 4 insertions(+) diff --git a/test/units/TEST-13-NSPAWN.machinectl.sh b/test/units/TEST-13-NSPAWN.machinectl.sh index 462cc6a8c33..7ff953bae9e 100755 --- a/test/units/TEST-13-NSPAWN.machinectl.sh +++ b/test/units/TEST-13-NSPAWN.machinectl.sh @@ -222,3 +222,6 @@ done (! machinectl read-only container1 "") (! machinectl read-only container1 foo) (! machinectl read-only container1 -- -1) + +varlinkctl --more call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List '{}' +varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List '{"name":".host"}' diff --git a/test/units/TEST-74-AUX-UTILS.ssh.sh b/test/units/TEST-74-AUX-UTILS.ssh.sh index 5d87d9f7acd..482b6a1e28b 100755 --- a/test/units/TEST-74-AUX-UTILS.ssh.sh +++ b/test/units/TEST-74-AUX-UTILS.ssh.sh @@ -53,6 +53,7 @@ mkdir -p /usr/share/empty.sshd /var/empty /var/empty/sshd /run/sshd ssh -o StrictHostKeyChecking=no -v -i "$ROOTID" .host cat /etc/machine-id | cmp - /etc/machine-id ssh -o StrictHostKeyChecking=no -v -i "$ROOTID" unix/run/ssh-unix-local/socket cat /etc/machine-id | cmp - /etc/machine-id +ssh -o StrictHostKeyChecking=no -v -i "$ROOTID" machine/.host cat /etc/machine-id | cmp - /etc/machine-id modprobe vsock_loopback ||: if test -e /dev/vsock -a -d /sys/module/vsock_loopback ; then