#include "log.h"
#include "machine.h"
#include "machine-dbus.h"
+#include "machined-resolve-hook.h"
#include "machined.h"
#include "mkdir-label.h"
#include "namespace-util.h"
machine_send_signal(m, "MachineNew");
+ (void) manager_notify_hook_filters(m->manager);
+
return 0;
}
if (m->started) {
machine_send_signal(m, "MachineRemoved");
m->started = false;
+
+ (void) manager_notify_hook_filters(m->manager);
}
return 0;
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "sd-json.h"
+
+#include "dns-answer.h"
+#include "dns-domain.h"
+#include "dns-packet.h"
+#include "dns-question.h"
+#include "dns-rr.h"
+#include "hashmap.h"
+#include "json-util.h"
+#include "local-addresses.h"
+#include "log.h"
+#include "machine.h"
+#include "machined.h"
+#include "machined-resolve-hook.h"
+#include "resolve-hook-util.h"
+#include "set.h"
+#include "varlink-util.h"
+
+static int manager_make_machine_array(Manager *m, sd_json_variant **ret) {
+ int r;
+
+ assert(m);
+ assert(ret);
+
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL;
+ Machine *machine;
+ HASHMAP_FOREACH(machine, m->machines) {
+ if (machine->class == MACHINE_HOST)
+ continue;
+ if (!machine->started)
+ continue;
+
+ r = sd_json_variant_append_arrayb(&array, SD_JSON_BUILD_STRING(machine->name));
+ if (r < 0)
+ return r;
+ }
+
+ if (!array)
+ return sd_json_variant_new_array(ret, /* array= */ NULL, /* n= */ 0);
+
+ *ret = TAKE_PTR(array);
+ return 0;
+}
+
+int manager_notify_hook_filters(Manager *m) {
+ int r;
+
+ assert(m);
+
+ /* Called whenever a machine is added or dropped from the list */
+
+ if (set_isempty(m->query_filter_subscriptions))
+ return 0;
+
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL;
+ r = manager_make_machine_array(m, &array);
+ if (r < 0)
+ return log_error_errno(r, "Failed to generate JSON array with machine names: %m");
+
+ r = varlink_many_notifybo(m->query_filter_subscriptions, SD_JSON_BUILD_PAIR_VARIANT("filterDomains", array));
+ if (r < 0)
+ return log_error_errno(r, "Failed to notify filter subscribers: %m");
+
+ return 0;
+}
+
+int vl_method_query_filter(
+ sd_varlink *link,
+ sd_json_variant *parameters,
+ sd_varlink_method_flags_t flags,
+ void *userdata) {
+
+ Manager *m = ASSERT_PTR(userdata);
+ int r;
+
+ assert(link);
+
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL;
+ r = manager_make_machine_array(m, &array);
+ if (r < 0)
+ return r;
+
+ if (flags & SD_VARLINK_METHOD_MORE) {
+ /* If 'more' is set, this is a subscription request, keep track of the link */
+
+ r = sd_varlink_notifybo(link, SD_JSON_BUILD_PAIR_VARIANT("filterDomains", array));
+ if (r < 0)
+ return log_error_errno(r, "Failed to notify filter subscribers: %m");
+
+ r = set_ensure_put(&m->query_filter_subscriptions, &varlink_hash_ops, link);
+ if (r < 0)
+ return r;
+
+ sd_varlink_ref(link);
+ } else {
+ r = sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_VARIANT("filterDomains", array));
+ if (r < 0)
+ return log_error_errno(r, "Failed to notify filter subscribers: %m");
+ }
+
+ return 0;
+}
+
+int vl_method_resolve_record(
+ sd_varlink *link,
+ sd_json_variant *parameters,
+ sd_varlink_method_flags_t flags,
+ void *userdata) {
+
+ Manager *m = ASSERT_PTR(userdata);
+ int r;
+
+ assert(link);
+
+ _cleanup_(resolve_record_parameters_done) ResolveRecordParameters p = {};
+ r = sd_varlink_dispatch(link, parameters, resolve_record_parameters_dispatch_table, &p);
+ if (r != 0)
+ return r;
+
+ if (dns_question_isempty(p.question))
+ return sd_varlink_error_invalid_parameter_name(link, "question");
+
+ _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
+
+ _cleanup_free_ struct local_address *addresses = NULL;
+ bool found = false, nxdomain = false;
+ int n_addresses = -1;
+
+ DnsResourceKey *key;
+ DNS_QUESTION_FOREACH(key, p.question) {
+ Machine *machine = hashmap_get(m->machines, dns_resource_key_name(key));
+ if (machine) {
+ /* We found a perfect match, yay! */
+ found = true;
+
+ if (!dns_resource_key_is_address(key))
+ continue;
+
+ if (n_addresses < 0) {
+ n_addresses = machine_get_addresses(machine, &addresses);
+ if (n_addresses < 0)
+ return n_addresses;
+ }
+
+ int family = dns_type_to_af(key->type);
+ FOREACH_ARRAY(address, addresses, n_addresses) {
+ if (address->family != family)
+ continue;
+
+ _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
+ r = dns_resource_record_new_address(&rr, address->family, &address->address, machine->name);
+ if (r < 0)
+ return r;
+
+ r = dns_answer_add_extend(
+ &answer,
+ rr,
+ machine->n_netif == 1 ? machine->netif[0] : -1,
+ DNS_ANSWER_AUTHENTICATED,
+ /* rrsig= */ NULL);
+ if (r < 0)
+ return r;
+ }
+ }
+
+ /* So this is not a direct match? Then check if we find a prefix match */
+ const char *q = dns_resource_key_name(key);
+ while (!nxdomain) {
+ r = dns_name_parent(&q);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ nxdomain = !!hashmap_get(m->machines, q);
+ }
+ }
+
+ if (!found) {
+ /* If we found a prefix match we own the subtree, and thus return NXDOMAIN because we know
+ * that we only expose the machine A/AAAA records on the primary name, but nothing below. */
+ if (nxdomain)
+ return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_INTEGER("rcode", DNS_RCODE_NXDOMAIN));
+
+ /* Otherwise we return an empty response, which means: continue with the usual lookup */
+ return sd_varlink_reply(link, /* parameters= */ NULL);
+ }
+
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *ja = NULL;
+ r = dns_answer_to_json(answer, &ja);
+ if (r < 0)
+ return r;
+
+ return sd_varlink_replybo(
+ link,
+ SD_JSON_BUILD_PAIR_INTEGER("rcode", DNS_RCODE_SUCCESS),
+ SD_JSON_BUILD_PAIR_VARIANT("answer", ja));
+}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "machine-forward.h"
+
+int vl_method_query_filter(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
+int vl_method_resolve_record(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
+
+int manager_notify_hook_filters(Manager *m);
#include "machine.h"
#include "machine-varlink.h"
#include "machined.h"
+#include "machined-resolve-hook.h"
#include "machined-varlink.h"
#include "path-lookup.h"
+#include "set.h"
#include "string-util.h"
#include "strv.h"
#include "user-util.h"
#include "varlink-io.systemd.Machine.h"
#include "varlink-io.systemd.MachineImage.h"
#include "varlink-io.systemd.UserDatabase.h"
+#include "varlink-io.systemd.Resolve.Hook.h"
#include "varlink-io.systemd.service.h"
#include "varlink-util.h"
return 0;
}
+static void on_resolve_hook_disconnect(sd_varlink_server *server, sd_varlink *link, void *userdata) {
+ Manager *m = ASSERT_PTR(userdata);
+
+ if (set_remove(m->query_filter_subscriptions, link))
+ sd_varlink_unref(link);
+}
+
+static int manager_varlink_init_resolve_hook(Manager *m) {
+ _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL;
+ int r;
+
+ assert(m);
+
+ if (m->varlink_resolve_hook_server)
+ return 0;
+ if (m->runtime_scope != RUNTIME_SCOPE_SYSTEM) /* no resolved in per-user mode! */
+ return 0;
+
+ r = varlink_server_new(&s, SD_VARLINK_SERVER_ACCOUNT_UID|SD_VARLINK_SERVER_INHERIT_USERDATA, m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate varlink server object: %m");
+
+ (void) sd_varlink_server_set_description(s, "varlink-resolve-hook");
+
+ r = sd_varlink_server_add_interface(s, &vl_interface_io_systemd_Resolve_Hook);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add Resolve.Hook interface to varlink server: %m");
+
+ r = sd_varlink_server_bind_method_many(
+ s,
+ "io.systemd.Resolve.Hook.QueryFilter", vl_method_query_filter,
+ "io.systemd.Resolve.Hook.ResolveRecord", vl_method_resolve_record);
+ if (r < 0)
+ return log_error_errno(r, "Failed to register varlink methods: %m");
+
+ r = sd_varlink_server_bind_disconnect(s, on_resolve_hook_disconnect);
+ if (r < 0)
+ return log_error_errno(r, "Failed to bind on resolve hook disconnection events: %m");
+
+ r = sd_varlink_server_listen_address(s, "/run/systemd/resolve.hook/io.systemd.Machine", 0666 | SD_VARLINK_SERVER_MODE_MKDIR_0755);
+ if (r < 0)
+ return log_error_errno(r, "Failed to bind to varlink socket: %m");
+
+ r = sd_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_resolve_hook_server = TAKE_PTR(s);
+ return 0;
+}
+
int manager_varlink_init(Manager *m) {
int r;
if (r < 0)
return r;
+ r = manager_varlink_init_resolve_hook(m);
+ if (r < 0)
+ return r;
+
return 0;
}
m->varlink_userdb_server = sd_varlink_server_unref(m->varlink_userdb_server);
m->varlink_machine_server = sd_varlink_server_unref(m->varlink_machine_server);
+ m->varlink_resolve_hook_server = sd_varlink_server_unref(m->varlink_resolve_hook_server);
}
#include "operation.h"
#include "path-lookup.h"
#include "service-util.h"
+#include "set.h"
#include "signal-util.h"
#include "socket-util.h"
#include "special.h"
manager_varlink_done(m);
+ m->query_filter_subscriptions = set_free(m->query_filter_subscriptions);
+
sd_bus_flush_close_unref(m->api_bus);
sd_bus_flush_close_unref(m->system_bus);
sd_event_unref(m->event);
sd_varlink_server *varlink_userdb_server;
sd_varlink_server *varlink_machine_server;
+ sd_varlink_server *varlink_resolve_hook_server;
+ Set *query_filter_subscriptions;
RuntimeScope runtime_scope;
char *state_dir;
'machine.c',
'machined-core.c',
'machined-dbus.c',
+ 'machined-resolve-hook.c',
'machined-varlink.c',
'operation.c',
)
#include <stdio.h>
+#include "sd-json.h"
+
#include "alloc-util.h"
#include "dns-answer.h"
#include "dns-domain.h"
return ttl;
}
+
+int dns_answer_to_json(DnsAnswer *answer, sd_json_variant **ret) {
+ int r;
+
+ assert(ret);
+
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *ja = NULL;
+ DnsResourceRecord *rr;
+ DNS_ANSWER_FOREACH(rr, answer) {
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
+
+ r = dns_resource_record_to_json(rr, &v);
+ if (r < 0)
+ return r;
+
+ r = dns_resource_record_to_wire_format(rr, /* canonical= */ false);
+ if (r < 0)
+ return r;
+
+ r = sd_json_variant_append_arraybo(
+ &ja,
+ SD_JSON_BUILD_PAIR_VARIANT("rr", v),
+ SD_JSON_BUILD_PAIR_BASE64("raw", rr->wire_format, rr->wire_format_size));
+ if (r < 0)
+ return r;
+ }
+
+ if (!ja)
+ return sd_json_variant_new_array(ret, /* array=*/ NULL, /* n= */ 0);
+
+ *ret = TAKE_PTR(ja);
+ return 0;
+}
uint32_t dns_answer_min_ttl(DnsAnswer *a);
+int dns_answer_to_json(DnsAnswer *answer, sd_json_variant **ret);
+
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsAnswer*, dns_answer_unref);
typedef struct DnsAnswerIterator {
#include "dns-question.h"
#include "dns-rr.h"
#include "dns-type.h"
+#include "json-util.h"
#include "socket-util.h"
#include "string-util.h"
*ret = TAKE_PTR(k);
return 0;
}
+
+int dns_json_dispatch_question(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
+ DnsQuestion **q = ASSERT_PTR(userdata);
+ int r;
+
+ if (!sd_json_variant_is_array(variant))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name));
+
+ _cleanup_(dns_question_unrefp) DnsQuestion *nq = NULL;
+ nq = dns_question_new(sd_json_variant_elements(variant));
+ if (!nq)
+ return json_log_oom(variant, flags);
+
+ sd_json_variant *i;
+ JSON_VARIANT_ARRAY_FOREACH(i, variant) {
+ _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
+
+ static const sd_json_dispatch_field dispatch_table[] = {
+ { "key", SD_JSON_VARIANT_OBJECT, dns_json_dispatch_resource_key, 0, SD_JSON_MANDATORY },
+ {}
+ };
+
+ r = sd_json_dispatch(i, dispatch_table, flags, &key);
+ if (r < 0)
+ return r;
+
+ if (dns_question_add(nq, key, /* flags= */ 0) < 0)
+ return json_log_oom(variant, flags);
+ }
+
+ dns_question_unref(*q);
+ *q = TAKE_PTR(nq);
+ return 0;
+}
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
+#include "sd-json.h"
+
#include "shared-forward.h"
/* A simple array of resource keys */
int dns_question_merge(DnsQuestion *a, DnsQuestion *b, DnsQuestion **ret);
+int dns_json_dispatch_question(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
+
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQuestion*, dns_question_unref);
#define _DNS_QUESTION_FOREACH(u, k, q) \
[SSHFP_KEY_TYPE_SHA256] = "SHA-256", /* RFC 4255 */
};
DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(sshfp_key_type, int, 255);
+
+int dns_json_dispatch_resource_key(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
+ DnsResourceKey **k = ASSERT_PTR(userdata);
+ int r;
+
+ _cleanup_(dns_resource_key_unrefp) DnsResourceKey *nk = NULL;
+ r = dns_resource_key_from_json(variant, &nk);
+ if (r < 0)
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid resource record key.", strna(name));
+
+ DNS_RESOURCE_KEY_REPLACE(*k, TAKE_PTR(nk));
+ return 0;
+}
int sshfp_key_type_to_string_alloc(int i, char **ret);
int sshfp_key_type_from_string(const char *s) _pure_;
+
+int dns_json_dispatch_resource_key(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
'recovery-key.c',
'reread-partition-table.c',
'resize-fs.c',
+ 'resolve-hook-util.c',
'resolve-util.c',
'rm-rf.c',
'seccomp-util.c',
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "resolve-hook-util.h"
+#include "dns-question.h"
+
+void resolve_record_parameters_done(ResolveRecordParameters *p) {
+ assert(p);
+
+ dns_question_unref(p->question);
+}
+
+const sd_json_dispatch_field resolve_record_parameters_dispatch_table[] = {
+ { "question", SD_JSON_VARIANT_ARRAY, dns_json_dispatch_question, offsetof(ResolveRecordParameters, question), SD_JSON_MANDATORY },
+ {}
+};
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-json.h"
+
+#include "shared-forward.h"
+
+typedef struct ResolveRecordParameters {
+ DnsQuestion *question;
+} ResolveRecordParameters;
+
+void resolve_record_parameters_done(ResolveRecordParameters *p);
+
+extern const sd_json_dispatch_field resolve_record_parameters_dispatch_table[];
(! getent group -s mymachines foo 11)
(! getent passwd -s mymachines foo 11)
+# Now check the machined's hook for resolved too
+run_and_grep "10\.1\.0\.2" resolvectl query nss-mymachines-singleip
+
+run_and_grep "fd00:dead:beef:cafe::2" resolvectl query nss-mymachines-manyips
+for i in {100..120}; do
+ run_and_grep "10\.2\.0\.$i" resolvectl query nss-mymachines-manyips
+done
+
machinectl stop nss-mymachines-{noip,singleip,manyips}