mirror of
https://github.com/systemd/systemd
synced 2026-03-31 04:04:54 +02:00
Compare commits
12 Commits
fe9bccb2b7
...
c15bd80eb9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c15bd80eb9 | ||
|
|
a719802ca3 | ||
|
|
6850f7f510 | ||
|
|
5ba49cc834 | ||
|
|
ec9ec7fa3a | ||
|
|
5615ac1f0d | ||
|
|
a7fa29b1b5 | ||
|
|
f041d40dee | ||
|
|
4408db6908 | ||
|
|
3cd929f837 | ||
|
|
594352611b | ||
|
|
8209f4adcd |
15
TODO
15
TODO
@ -134,6 +134,14 @@ Deprecations and removals:
|
||||
|
||||
Features:
|
||||
|
||||
* networkd/machined: implement reverse name lookups in the resolved hook
|
||||
|
||||
* networkd's resolved hook: optionally map all lease IP addresses handed out to
|
||||
the same hostname which is configured on the .network file. Optionally, even
|
||||
derive this single name from the network interface name (i.e. probably
|
||||
altname or so). This way, when spawning a VM the host could pick the hostname
|
||||
for it and the client gets no say.
|
||||
|
||||
* systemd-repart: add --ghost, that creates file systems, updates the kernel's
|
||||
partition table but does *not* update partition table on disk. This way, we
|
||||
have disk backed file systems that go effectively disappear on reboot. This
|
||||
@ -230,12 +238,6 @@ Features:
|
||||
so that we add correct ordering between the two. Use MSG_PEEK + SCM_TIMESTAMP
|
||||
for this.
|
||||
|
||||
* maybe replace nss-machines with logic in networkd that registers records with
|
||||
systemd-resolved, based on DHCP leases, so that we gain compat with VMs.
|
||||
Implementation idea: encode in an ifaltname the intended local name to expose this
|
||||
under and then parse that out and map it to the combined A/AAAA of all handed
|
||||
out leases.
|
||||
|
||||
* bsod: add target "bsod.target" or so, which invokes systemd-bsod.target and
|
||||
waits and then reboots. Then use OnFailure=bsod.target from various jobs that
|
||||
should result in system reboots, such as TPM tamper detection cases.
|
||||
@ -2138,7 +2140,6 @@ Features:
|
||||
names, so that for the container case we can establish the same name
|
||||
(maybe "host") for referencing the server, everywhere.
|
||||
- allow clients to request DNSSEC for a single lookup even if DNSSEC is off (?)
|
||||
- hook up resolved with machined-based address resolution
|
||||
|
||||
* refcounting in sd-resolve is borked
|
||||
|
||||
|
||||
@ -497,6 +497,7 @@ node /org/freedesktop/resolve1 {
|
||||
#define SD_RESOLVED_FROM_ZONE (UINT64_C(1) << 21)
|
||||
#define SD_RESOLVED_FROM_TRUST_ANCHOR (UINT64_C(1) << 22)
|
||||
#define SD_RESOLVED_FROM_NETWORK (UINT64_C(1) << 23)
|
||||
#define SD_RESOLVED_FROM_HOOK (UINT64_C(1) << 27)
|
||||
</programlisting>
|
||||
|
||||
<para>On input, the first five flags control the protocols to use for the look-up. They refer to
|
||||
|
||||
@ -4201,6 +4201,23 @@ ServerAddress=192.168.0.1/24</programlisting>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>LocalLeaseDomain=</varname></term>
|
||||
<listitem>
|
||||
<para>Takes a DNS domain name as argument. If specified,
|
||||
<citerefentry><refentrytitle>systemd-networkd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
will integrate with
|
||||
<citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
and ensure that the hostnames associated with each handed out DHCP lease may be resolved to the IP
|
||||
addresses of the lease. The hostnames are suffixed with the specified domain name.</para>
|
||||
|
||||
<para>Note that this purely about hostname resolution on the local system, i.e. from programs with
|
||||
access to the same <filename>systemd-resolved</filename> instances via D-Bus IPC, Varlink IPC, or
|
||||
the local DNS stub.</para>
|
||||
<xi:include href="version-info.xml" xpointer="v259"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
|
||||
@ -30,3 +30,4 @@ IPv6SendRA=yes
|
||||
|
||||
[DHCPServer]
|
||||
PersistLeases=runtime
|
||||
LocalLeaseDomain=_dhcp
|
||||
|
||||
@ -29,3 +29,4 @@ IPv6SendRA=yes
|
||||
|
||||
[DHCPServer]
|
||||
PersistLeases=runtime
|
||||
LocalLeaseDomain=_dhcp
|
||||
|
||||
@ -30,3 +30,4 @@ IPv6SendRA=yes
|
||||
|
||||
[DHCPServer]
|
||||
PersistLeases=runtime
|
||||
LocalLeaseDomain=_dhcp
|
||||
|
||||
@ -30,3 +30,4 @@ IPv6SendRA=yes
|
||||
|
||||
[DHCPServer]
|
||||
PersistLeases=runtime
|
||||
LocalLeaseDomain=_dhcp
|
||||
|
||||
@ -29,3 +29,4 @@ IPv6SendRA=yes
|
||||
|
||||
[DHCPServer]
|
||||
PersistLeases=runtime
|
||||
LocalLeaseDomain=_dhcp
|
||||
|
||||
@ -19,3 +19,6 @@ DHCPServer=yes
|
||||
IPMasquerade=both
|
||||
IPv6AcceptRA=no
|
||||
IPv6SendRA=yes
|
||||
|
||||
[DHCPServer]
|
||||
LocalLeaseDomain=home.arpa
|
||||
|
||||
@ -1711,3 +1711,38 @@ int sd_dhcp_server_set_lease_file(sd_dhcp_server *server, int dir_fd, const char
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int find_lease_address(Hashmap *h, const char *name, struct in_addr *ret) {
|
||||
int r;
|
||||
|
||||
assert(name);
|
||||
|
||||
sd_dhcp_server_lease *lease;
|
||||
HASHMAP_FOREACH(lease, h) {
|
||||
if (!lease->hostname)
|
||||
continue;
|
||||
|
||||
r = dns_name_equal(lease->hostname, name);
|
||||
if (r <= 0)
|
||||
continue;
|
||||
|
||||
if (ret)
|
||||
ret->s_addr = lease->address;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
int sd_dhcp_server_get_lease_address_by_name(sd_dhcp_server *server, const char *name, struct in_addr *ret) {
|
||||
int r;
|
||||
|
||||
assert_return(server, -EINVAL);
|
||||
assert_return(dns_name_is_valid(name), -EINVAL);
|
||||
|
||||
r = find_lease_address(server->static_leases_by_address, name, ret);
|
||||
if (r != -ENOENT)
|
||||
return r;
|
||||
|
||||
return find_lease_address(server->bound_leases_by_address, name, ret);
|
||||
}
|
||||
|
||||
@ -870,12 +870,11 @@ const BusObjectImplementation machine_object = {
|
||||
.node_enumerator = machine_node_enumerator,
|
||||
};
|
||||
|
||||
int machine_send_signal(Machine *m, bool new_machine) {
|
||||
_cleanup_free_ char *p = NULL;
|
||||
|
||||
int machine_send_signal(Machine *m, const char *signal_name) {
|
||||
assert(m);
|
||||
assert(signal_name);
|
||||
|
||||
p = machine_bus_path(m);
|
||||
_cleanup_free_ char *p = machine_bus_path(m);
|
||||
if (!p)
|
||||
return -ENOMEM;
|
||||
|
||||
@ -883,7 +882,7 @@ int machine_send_signal(Machine *m, bool new_machine) {
|
||||
m->manager->api_bus,
|
||||
"/org/freedesktop/machine1",
|
||||
"org.freedesktop.machine1.Manager",
|
||||
new_machine ? "MachineNew" : "MachineRemoved",
|
||||
signal_name,
|
||||
"so", m->name, p);
|
||||
}
|
||||
|
||||
|
||||
@ -26,5 +26,5 @@ int bus_machine_method_copy(sd_bus_message *message, void *userdata, sd_bus_erro
|
||||
int bus_machine_method_open_root_directory(sd_bus_message *message, void *userdata, sd_bus_error *error);
|
||||
int bus_machine_method_get_uid_shift(sd_bus_message *message, void *userdata, sd_bus_error *error);
|
||||
|
||||
int machine_send_signal(Machine *m, bool new_machine);
|
||||
int machine_send_signal(Machine *m, const char *signal_name);
|
||||
int machine_send_create_reply(Machine *m, sd_bus_error *error);
|
||||
|
||||
@ -24,6 +24,7 @@
|
||||
#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"
|
||||
@ -672,7 +673,9 @@ int machine_start(Machine *m, sd_bus_message *properties, sd_bus_error *error) {
|
||||
/* Save new machine data */
|
||||
machine_save(m);
|
||||
|
||||
machine_send_signal(m, true);
|
||||
machine_send_signal(m, "MachineNew");
|
||||
|
||||
(void) manager_notify_hook_filters(m->manager);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -730,8 +733,10 @@ int machine_finalize(Machine *m) {
|
||||
machine_add_to_gc_queue(m);
|
||||
|
||||
if (m->started) {
|
||||
machine_send_signal(m, false);
|
||||
machine_send_signal(m, "MachineRemoved");
|
||||
m->started = false;
|
||||
|
||||
(void) manager_notify_hook_filters(m->manager);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
200
src/machine/machined-resolve-hook.c
Normal file
200
src/machine/machined-resolve-hook.c
Normal file
@ -0,0 +1,200 @@
|
||||
/* 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));
|
||||
}
|
||||
9
src/machine/machined-resolve-hook.h
Normal file
9
src/machine/machined-resolve-hook.h
Normal file
@ -0,0 +1,9 @@
|
||||
/* 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);
|
||||
@ -17,14 +17,17 @@
|
||||
#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"
|
||||
|
||||
@ -847,6 +850,57 @@ static int manager_varlink_init_machine(Manager *m) {
|
||||
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;
|
||||
|
||||
@ -858,6 +912,10 @@ int manager_varlink_init(Manager *m) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = manager_varlink_init_resolve_hook(m);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -866,4 +924,5 @@ void manager_varlink_done(Manager *m) {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -29,6 +29,7 @@
|
||||
#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"
|
||||
@ -110,6 +111,8 @@ static Manager* manager_unref(Manager *m) {
|
||||
|
||||
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);
|
||||
|
||||
@ -32,6 +32,8 @@ typedef struct Manager {
|
||||
|
||||
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;
|
||||
|
||||
@ -16,6 +16,7 @@ systemd_machined_extract_sources = files(
|
||||
'machine.c',
|
||||
'machined-core.c',
|
||||
'machined-dbus.c',
|
||||
'machined-resolve-hook.c',
|
||||
'machined-varlink.c',
|
||||
'operation.c',
|
||||
)
|
||||
|
||||
@ -74,6 +74,7 @@ systemd_networkd_extract_sources = files(
|
||||
'networkd-ntp.c',
|
||||
'networkd-queue.c',
|
||||
'networkd-radv.c',
|
||||
'networkd-resolve-hook.c',
|
||||
'networkd-route.c',
|
||||
'networkd-route-metric.c',
|
||||
'networkd-route-nexthop.c',
|
||||
|
||||
@ -25,6 +25,7 @@
|
||||
#include "networkd-network.h"
|
||||
#include "networkd-ntp.h"
|
||||
#include "networkd-queue.h"
|
||||
#include "networkd-resolve-hook.h"
|
||||
#include "networkd-route-util.h"
|
||||
#include "path-util.h"
|
||||
#include "set.h"
|
||||
@ -747,6 +748,8 @@ static int dhcp4_server_configure(Link *link) {
|
||||
if (r < 0)
|
||||
return log_link_error_errno(link, r, "Could not start DHCPv4 server instance: %m");
|
||||
|
||||
manager_notify_hook_filters(link->manager);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
#include "arphrd-util.h"
|
||||
#include "bitfield.h"
|
||||
#include "device-util.h"
|
||||
#include "dns-domain.h"
|
||||
#include "errno-util.h"
|
||||
#include "ethtool-util.h"
|
||||
#include "event-util.h"
|
||||
@ -53,6 +54,7 @@
|
||||
#include "networkd-nexthop.h"
|
||||
#include "networkd-queue.h"
|
||||
#include "networkd-radv.h"
|
||||
#include "networkd-resolve-hook.h"
|
||||
#include "networkd-route.h"
|
||||
#include "networkd-route-util.h"
|
||||
#include "networkd-routing-policy-rule.h"
|
||||
@ -1070,6 +1072,8 @@ static Link *link_drop(Link *link) {
|
||||
|
||||
assert(link->manager);
|
||||
|
||||
bool notify = link_has_local_lease_domain(link);
|
||||
|
||||
link_set_state(link, LINK_STATE_LINGER);
|
||||
|
||||
/* Drop all references from other links and manager. Note that async netlink calls may have
|
||||
@ -1098,6 +1102,10 @@ static Link *link_drop(Link *link) {
|
||||
|
||||
/* The following must be called at last. */
|
||||
assert_se(hashmap_remove(link->manager->links_by_index, INT_TO_PTR(link->ifindex)) == link);
|
||||
|
||||
if (notify)
|
||||
manager_notify_hook_filters(link->manager);
|
||||
|
||||
return link_unref(link);
|
||||
}
|
||||
|
||||
@ -1351,6 +1359,8 @@ static void link_enter_unmanaged(Link *link) {
|
||||
if (link->state == LINK_STATE_UNMANAGED)
|
||||
return;
|
||||
|
||||
bool notify = link_has_local_lease_domain(link);
|
||||
|
||||
log_link_full(link, link->state == LINK_STATE_INITIALIZED ? LOG_DEBUG : LOG_INFO,
|
||||
"Unmanaging interface.");
|
||||
|
||||
@ -1367,6 +1377,9 @@ static void link_enter_unmanaged(Link *link) {
|
||||
|
||||
link->network = network_unref(link->network);
|
||||
link_set_state(link, LINK_STATE_UNMANAGED);
|
||||
|
||||
if (notify)
|
||||
manager_notify_hook_filters(link->manager);
|
||||
}
|
||||
|
||||
static int link_managed_by_us(Link *link) {
|
||||
@ -3061,3 +3074,12 @@ static const char * const kernel_operstate_table[] = {
|
||||
};
|
||||
|
||||
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(kernel_operstate, int);
|
||||
|
||||
bool link_has_local_lease_domain(Link *link) {
|
||||
assert(link);
|
||||
|
||||
return link->dhcp_server &&
|
||||
link->network &&
|
||||
link->network->dhcp_server_local_lease_domain &&
|
||||
!dns_name_is_root(link->network->dhcp_server_local_lease_domain);
|
||||
}
|
||||
|
||||
@ -257,3 +257,5 @@ const char* kernel_operstate_to_string(int t) _const_;
|
||||
|
||||
void link_required_operstate_for_online(Link *link, LinkOperationalStateRange *ret);
|
||||
AddressFamily link_required_family_for_online(Link *link);
|
||||
|
||||
bool link_has_local_lease_domain(Link *link);
|
||||
|
||||
@ -279,9 +279,9 @@ static int vl_method_set_persistent_storage(sd_varlink *vlink, sd_json_variant *
|
||||
return sd_varlink_reply(vlink, NULL);
|
||||
}
|
||||
|
||||
int manager_connect_varlink(Manager *m, int fd) {
|
||||
int manager_varlink_init(Manager *m, int fd) {
|
||||
_cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL;
|
||||
_unused_ _cleanup_close_ int fd_close = fd;
|
||||
_unused_ _cleanup_close_ int fd_close = fd; /* take possession */
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
@ -335,9 +335,3 @@ int manager_connect_varlink(Manager *m, int fd) {
|
||||
m->varlink_server = TAKE_PTR(s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void manager_varlink_done(Manager *m) {
|
||||
assert(m);
|
||||
|
||||
m->varlink_server = sd_varlink_server_unref(m->varlink_server);
|
||||
}
|
||||
|
||||
@ -3,5 +3,4 @@
|
||||
|
||||
#include "networkd-forward.h"
|
||||
|
||||
int manager_connect_varlink(Manager *m, int fd);
|
||||
void manager_varlink_done(Manager *m);
|
||||
int manager_varlink_init(Manager *m, int fd);
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
#include "sd-event.h"
|
||||
#include "sd-netlink.h"
|
||||
#include "sd-resolve.h"
|
||||
#include "sd-varlink.h"
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "bus-error.h"
|
||||
@ -37,6 +38,7 @@
|
||||
#include "networkd-neighbor.h"
|
||||
#include "networkd-nexthop.h"
|
||||
#include "networkd-queue.h"
|
||||
#include "networkd-resolve-hook.h"
|
||||
#include "networkd-route.h"
|
||||
#include "networkd-routing-policy-rule.h"
|
||||
#include "networkd-serialize.h"
|
||||
@ -205,13 +207,14 @@ static int manager_connect_udev(Manager *m) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int manager_listen_fds(Manager *m, int *ret_rtnl_fd, int *ret_varlink_fd) {
|
||||
static int manager_listen_fds(Manager *m, int *ret_rtnl_fd, int *ret_varlink_fd, int *ret_resolve_hook_fd) {
|
||||
_cleanup_strv_free_ char **names = NULL;
|
||||
int n, rtnl_fd = -EBADF, varlink_fd = -EBADF;
|
||||
int n, rtnl_fd = -EBADF, varlink_fd = -EBADF, resolve_hook_fd = -EBADF;
|
||||
|
||||
assert(m);
|
||||
assert(ret_rtnl_fd);
|
||||
assert(ret_varlink_fd);
|
||||
assert(ret_resolve_hook_fd);
|
||||
|
||||
n = sd_listen_fds_with_names(/* unset_environment = */ true, &names);
|
||||
if (n < 0)
|
||||
@ -235,6 +238,11 @@ static int manager_listen_fds(Manager *m, int *ret_rtnl_fd, int *ret_varlink_fd)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (streq(names[i], "resolve-hook")) {
|
||||
resolve_hook_fd = fd;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (manager_set_serialization_fd(m, fd, names[i]) >= 0)
|
||||
continue;
|
||||
|
||||
@ -250,6 +258,7 @@ static int manager_listen_fds(Manager *m, int *ret_rtnl_fd, int *ret_varlink_fd)
|
||||
|
||||
*ret_rtnl_fd = rtnl_fd;
|
||||
*ret_varlink_fd = varlink_fd;
|
||||
*ret_resolve_hook_fd = resolve_hook_fd;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -543,7 +552,7 @@ static int manager_set_keep_configuration(Manager *m) {
|
||||
}
|
||||
|
||||
int manager_setup(Manager *m) {
|
||||
_cleanup_close_ int rtnl_fd = -EBADF, varlink_fd = -EBADF;
|
||||
_cleanup_close_ int rtnl_fd = -EBADF, varlink_fd = -EBADF, resolve_hook_fd = -EBADF;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
@ -567,7 +576,7 @@ int manager_setup(Manager *m) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = manager_listen_fds(m, &rtnl_fd, &varlink_fd);
|
||||
r = manager_listen_fds(m, &rtnl_fd, &varlink_fd, &resolve_hook_fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@ -586,7 +595,11 @@ int manager_setup(Manager *m) {
|
||||
if (m->test_mode)
|
||||
return 0;
|
||||
|
||||
r = manager_connect_varlink(m, TAKE_FD(varlink_fd));
|
||||
r = manager_varlink_init(m, TAKE_FD(varlink_fd));
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = manager_varlink_init_resolve_hook(m, TAKE_FD(resolve_hook_fd));
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@ -737,7 +750,9 @@ Manager* manager_free(Manager *m) {
|
||||
|
||||
sd_device_monitor_unref(m->device_monitor);
|
||||
|
||||
manager_varlink_done(m);
|
||||
m->varlink_server = sd_varlink_server_unref(m->varlink_server);
|
||||
m->varlink_resolve_hook_server = sd_varlink_server_unref(m->varlink_resolve_hook_server);
|
||||
m->query_filter_subscriptions = set_free(m->query_filter_subscriptions);
|
||||
hashmap_free(m->polkit_registry);
|
||||
sd_bus_flush_close_unref(m->bus);
|
||||
|
||||
|
||||
@ -22,6 +22,8 @@ typedef struct Manager {
|
||||
sd_resolve *resolve;
|
||||
sd_bus *bus;
|
||||
sd_varlink_server *varlink_server;
|
||||
sd_varlink_server *varlink_resolve_hook_server;
|
||||
Set *query_filter_subscriptions;
|
||||
sd_device_monitor *device_monitor;
|
||||
Hashmap *polkit_registry;
|
||||
int ethtool_fd;
|
||||
|
||||
@ -393,6 +393,7 @@ DHCPServer.BootServerName, config_parse_dns_name,
|
||||
DHCPServer.BootFilename, config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, offsetof(Network, dhcp_server_boot_filename)
|
||||
DHCPServer.RapidCommit, config_parse_bool, 0, offsetof(Network, dhcp_server_rapid_commit)
|
||||
DHCPServer.PersistLeases, config_parse_dhcp_server_persist_leases, 0, offsetof(Network, dhcp_server_persist_leases)
|
||||
DHCPServer.LocalLeaseDomain, config_parse_dns_name, 0, offsetof(Network, dhcp_server_local_lease_domain)
|
||||
DHCPServerStaticLease.Address, config_parse_dhcp_static_lease_address, 0, 0
|
||||
DHCPServerStaticLease.MACAddress, config_parse_dhcp_static_lease_hwaddr, 0, 0
|
||||
DHCPServerStaticLease.Hostname, config_parse_dhcp_static_lease_hostname, 0, 0
|
||||
|
||||
@ -761,6 +761,7 @@ static Network *network_free(Network *network) {
|
||||
free(network->dhcp_server_emit[t].addresses);
|
||||
ordered_hashmap_free(network->dhcp_server_send_options);
|
||||
ordered_hashmap_free(network->dhcp_server_send_vendor_options);
|
||||
free(network->dhcp_server_local_lease_domain);
|
||||
|
||||
/* DHCP client */
|
||||
free(network->dhcp_vendor_class_identifier);
|
||||
|
||||
@ -233,6 +233,7 @@ typedef struct Network {
|
||||
usec_t dhcp_server_ipv6_only_preferred_usec;
|
||||
bool dhcp_server_rapid_commit;
|
||||
DHCPServerPersistLeases dhcp_server_persist_leases;
|
||||
char *dhcp_server_local_lease_domain;
|
||||
|
||||
/* link-local addressing support */
|
||||
AddressFamily link_local;
|
||||
|
||||
261
src/network/networkd-resolve-hook.c
Normal file
261
src/network/networkd-resolve-hook.c
Normal file
@ -0,0 +1,261 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "sd-dhcp-server.h"
|
||||
#include "sd-json.h"
|
||||
#include "sd-varlink.h"
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "dns-answer.h"
|
||||
#include "dns-domain.h"
|
||||
#include "dns-packet.h"
|
||||
#include "dns-question.h"
|
||||
#include "dns-rr.h"
|
||||
#include "env-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "networkd-link.h"
|
||||
#include "networkd-manager.h"
|
||||
#include "networkd-resolve-hook.h"
|
||||
#include "resolve-hook-util.h"
|
||||
#include "set.h"
|
||||
#include "varlink-io.systemd.Resolve.Hook.h"
|
||||
#include "varlink-util.h"
|
||||
|
||||
static int manager_make_domain_array(Manager *m, sd_json_variant **ret) {
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
assert(ret);
|
||||
|
||||
_cleanup_(set_freep) Set *domains = NULL;
|
||||
Link *link;
|
||||
HASHMAP_FOREACH(link, m->links_by_index) {
|
||||
if (!link_has_local_lease_domain(link))
|
||||
continue;
|
||||
|
||||
r = set_put_strdup_full(&domains, &dns_name_hash_ops_free, link->network->dhcp_server_local_lease_domain);
|
||||
if (r < 0 && r != -EEXIST)
|
||||
return r;
|
||||
}
|
||||
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL;
|
||||
char *s;
|
||||
SET_FOREACH(s, domains) {
|
||||
r = sd_json_variant_append_arrayb(&array, SD_JSON_BUILD_STRING(s));
|
||||
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_domain_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;
|
||||
}
|
||||
|
||||
static 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_domain_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;
|
||||
}
|
||||
|
||||
static 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;
|
||||
bool found_address = false, found_domain = false;
|
||||
DnsResourceKey *key;
|
||||
DNS_QUESTION_FOREACH(key, p.question) {
|
||||
const char *name = dns_resource_key_name(key);
|
||||
|
||||
Link *l;
|
||||
HASHMAP_FOREACH(l, m->links_by_index) {
|
||||
|
||||
if (!link_has_local_lease_domain(l))
|
||||
continue;
|
||||
|
||||
/* Try to strip the local lease domain suffix from name, so that we have the short hostname left. */
|
||||
_cleanup_free_ char *prefix = NULL;
|
||||
r = dns_name_change_suffix(name, l->network->dhcp_server_local_lease_domain, /* new_suffix= */ NULL, &prefix);
|
||||
if (r <= 0) /* no match? */
|
||||
continue;
|
||||
|
||||
found_domain = true;
|
||||
|
||||
struct in_addr address;
|
||||
r = sd_dhcp_server_get_lease_address_by_name(l->dhcp_server, prefix, &address);
|
||||
if (r <= 0)
|
||||
continue;
|
||||
|
||||
/* The domain exists, so we can give a positive reply. But only for A lookups we have addresses to return. */
|
||||
if (key->type != DNS_TYPE_A)
|
||||
continue;
|
||||
|
||||
found_address = true;
|
||||
|
||||
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
|
||||
r = dns_resource_record_new_address(&rr, AF_INET, (union in_addr_union*) &address, name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = dns_answer_add_extend(
|
||||
&answer,
|
||||
rr,
|
||||
l->ifindex,
|
||||
DNS_ANSWER_AUTHENTICATED,
|
||||
/* rrsig= */ NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_address) {
|
||||
/* If this was a lookup in one of our domains, return NXDOMAIN, we are authoritative on that */
|
||||
if (found_domain)
|
||||
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));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
int manager_varlink_init_resolve_hook(Manager *m, int fd) {
|
||||
_cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL;
|
||||
_unused_ _cleanup_close_ int fd_close = fd; /* take possession */
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
|
||||
if (m->varlink_resolve_hook_server)
|
||||
return 0;
|
||||
|
||||
r = getenv_bool("SYSTEMD_NETWORK_RESOLVE_HOOK");
|
||||
if (r < 0 && r != -ENXIO)
|
||||
log_warning_errno(r, "Failed to parse $SYSTEMD_NETWORK_RESOLVE_HOOK, ignoring: %m");
|
||||
if (r == 0) {
|
||||
log_notice("Resolve hook disabled via $SYSTEMD_NETWORK_RESOLVE_HOOK.");
|
||||
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");
|
||||
|
||||
if (fd < 0)
|
||||
r = sd_varlink_server_listen_address(s, "/run/systemd/resolve.hook/io.systemd.Network", 0666 | SD_VARLINK_SERVER_MODE_MKDIR_0755);
|
||||
else
|
||||
r = sd_varlink_server_listen_fd(s, fd);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to bind to systemd-resolved hook Varlink socket: %m");
|
||||
|
||||
TAKE_FD(fd_close);
|
||||
|
||||
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;
|
||||
}
|
||||
7
src/network/networkd-resolve-hook.h
Normal file
7
src/network/networkd-resolve-hook.h
Normal file
@ -0,0 +1,7 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "networkd-forward.h"
|
||||
|
||||
int manager_notify_hook_filters(Manager *m);
|
||||
int manager_varlink_init_resolve_hook(Manager *m, int fd);
|
||||
@ -28,6 +28,7 @@ systemd_resolved_extract_sources = files(
|
||||
'resolved-dnssd-bus.c',
|
||||
'resolved-dnssd.c',
|
||||
'resolved-etc-hosts.c',
|
||||
'resolved-hook.c',
|
||||
'resolved-link-bus.c',
|
||||
'resolved-link.c',
|
||||
'resolved-llmnr.c',
|
||||
|
||||
@ -253,13 +253,14 @@ static void print_source(uint64_t flags, usec_t rtt) {
|
||||
ansi_normal());
|
||||
|
||||
if ((flags & (SD_RESOLVED_FROM_MASK|SD_RESOLVED_SYNTHETIC)) != 0)
|
||||
printf("%s-- Data from:%s%s%s%s%s%s\n",
|
||||
printf("%s-- Data from:%s%s%s%s%s%s%s\n",
|
||||
ansi_grey(),
|
||||
FLAGS_SET(flags, SD_RESOLVED_SYNTHETIC) ? " synthetic" : "",
|
||||
FLAGS_SET(flags, SD_RESOLVED_FROM_CACHE) ? " cache" : "",
|
||||
FLAGS_SET(flags, SD_RESOLVED_FROM_ZONE) ? " zone" : "",
|
||||
FLAGS_SET(flags, SD_RESOLVED_FROM_TRUST_ANCHOR) ? " trust-anchor" : "",
|
||||
FLAGS_SET(flags, SD_RESOLVED_FROM_NETWORK) ? " network" : "",
|
||||
FLAGS_SET(flags, SD_RESOLVED_FROM_HOOK) ? " hook" : "",
|
||||
ansi_normal());
|
||||
}
|
||||
|
||||
|
||||
@ -1017,7 +1017,7 @@ static void resolve_service_all_complete(DnsQuery *query) {
|
||||
|
||||
assert(q);
|
||||
|
||||
if (q->block_all_complete > 0) {
|
||||
if (q->hook_query || q->block_all_complete > 0) {
|
||||
TAKE_PTR(q);
|
||||
return;
|
||||
}
|
||||
@ -1028,6 +1028,12 @@ static void resolve_service_all_complete(DnsQuery *query) {
|
||||
|
||||
LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) {
|
||||
|
||||
if (aux->hook_query) {
|
||||
/* If an auxiliary query's hook is still pending, let's wait */
|
||||
TAKE_PTR(q);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (aux->state) {
|
||||
|
||||
case DNS_TRANSACTION_PENDING:
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
#include "resolved-dns-synthesize.h"
|
||||
#include "resolved-dns-transaction.h"
|
||||
#include "resolved-etc-hosts.h"
|
||||
#include "resolved-hook.h"
|
||||
#include "resolved-manager.h"
|
||||
#include "resolved-timeouts.h"
|
||||
#include "set.h"
|
||||
@ -524,6 +525,8 @@ DnsQuery *dns_query_free(DnsQuery *q) {
|
||||
|
||||
dns_service_browser_unref(q->service_browser_request);
|
||||
|
||||
hook_query_free(q->hook_query);
|
||||
|
||||
if (q->manager) {
|
||||
LIST_REMOVE(queries, q->manager->dns_queries, q);
|
||||
q->manager->n_dns_queries--;
|
||||
@ -907,24 +910,17 @@ static int dns_query_try_etc_hosts(DnsQuery *q) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int dns_query_go(DnsQuery *q) {
|
||||
DnsScopeMatch found = DNS_SCOPE_NO;
|
||||
DnsScope *first = NULL;
|
||||
static int dns_query_go_scopes(DnsQuery *q) {
|
||||
int r;
|
||||
|
||||
assert(q);
|
||||
assert(!q->hook_query);
|
||||
assert(q->state == DNS_TRANSACTION_NULL);
|
||||
|
||||
if (q->state != DNS_TRANSACTION_NULL)
|
||||
return 0;
|
||||
|
||||
r = dns_query_try_etc_hosts(q);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0) {
|
||||
dns_query_complete(q, DNS_TRANSACTION_SUCCESS);
|
||||
return 1;
|
||||
}
|
||||
/* Start the lookup via the scopes */
|
||||
|
||||
DnsScopeMatch found = DNS_SCOPE_NO;
|
||||
DnsScope *first = NULL;
|
||||
LIST_FOREACH(scopes, s, q->manager->dns_scopes) {
|
||||
DnsScopeMatch match;
|
||||
|
||||
@ -999,6 +995,72 @@ fail:
|
||||
return r;
|
||||
}
|
||||
|
||||
static void on_hook_complete(HookQuery *hq, int rcode, DnsAnswer *answer, void *userdata) {
|
||||
DnsQuery *q = ASSERT_PTR(userdata);
|
||||
int r;
|
||||
|
||||
assert(hq);
|
||||
assert(q->hook_query == hq);
|
||||
assert(q->state == DNS_TRANSACTION_NULL);
|
||||
|
||||
q->hook_query = hook_query_free(q->hook_query);
|
||||
TAKE_PTR(hq);
|
||||
|
||||
if (rcode < 0) {
|
||||
log_debug("Hook yielded no results, proceeding.");
|
||||
r = dns_query_go_scopes(q);
|
||||
if (r < 0) {
|
||||
dns_query_reset_answer(q);
|
||||
q->answer_errno = r;
|
||||
dns_query_complete(q, DNS_TRANSACTION_ERRNO);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
dns_query_reset_answer(q);
|
||||
|
||||
q->answer = dns_answer_ref(answer);
|
||||
q->answer_rcode = rcode;
|
||||
q->answer_protocol = dns_synthesize_protocol(q->flags);
|
||||
q->answer_family = dns_synthesize_family(q->flags);
|
||||
q->answer_query_flags = SD_RESOLVED_FROM_HOOK;
|
||||
dns_query_complete(q, rcode == DNS_RCODE_SUCCESS ? DNS_TRANSACTION_SUCCESS : DNS_TRANSACTION_RCODE_FAILURE);
|
||||
}
|
||||
|
||||
int dns_query_go(DnsQuery *q) {
|
||||
int r;
|
||||
|
||||
assert(q);
|
||||
|
||||
/* Already ongoing? Then suppress */
|
||||
if (q->hook_query ||
|
||||
q->state != DNS_TRANSACTION_NULL)
|
||||
return 0;
|
||||
|
||||
r = dns_query_try_etc_hosts(q);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0) {
|
||||
dns_query_complete(q, DNS_TRANSACTION_SUCCESS);
|
||||
return 1;
|
||||
}
|
||||
|
||||
r = manager_hook_query(
|
||||
q->manager,
|
||||
q->question_bypass ? q->question_bypass->question : q->question_idna,
|
||||
q->question_bypass ? q->question_bypass->question : q->question_utf8,
|
||||
on_hook_complete,
|
||||
q,
|
||||
&q->hook_query);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0) /* hook calls are pending */
|
||||
return 0;
|
||||
|
||||
return dns_query_go_scopes(q);
|
||||
}
|
||||
|
||||
static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) {
|
||||
DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS;
|
||||
bool has_authenticated = false, has_non_authenticated = false, has_confidential = false, has_non_confidential = false;
|
||||
@ -1137,6 +1199,9 @@ void dns_query_ready(DnsQuery *q) {
|
||||
* after calling this function, unless the block_ready
|
||||
* counter was explicitly bumped before doing so. */
|
||||
|
||||
if (q->hook_query)
|
||||
return;
|
||||
|
||||
if (q->block_ready > 0)
|
||||
return;
|
||||
|
||||
|
||||
@ -110,6 +110,9 @@ typedef struct DnsQuery {
|
||||
DnssdDiscoveredService *dnsservice_request;
|
||||
DnsServiceBrowser *service_browser_request;
|
||||
|
||||
/* Pending query to any installed hooks */
|
||||
HookQuery *hook_query;
|
||||
|
||||
/* Completion callback */
|
||||
void (*complete)(DnsQuery* q);
|
||||
|
||||
|
||||
@ -34,6 +34,7 @@ typedef struct DnsSvcParam DnsSvcParam;
|
||||
typedef struct DnsTransaction DnsTransaction;
|
||||
typedef struct DnsTxtItem DnsTxtItem;
|
||||
typedef struct DnsZoneItem DnsZoneItem;
|
||||
typedef struct HookQuery HookQuery;
|
||||
typedef struct Link Link;
|
||||
typedef struct LinkAddress LinkAddress;
|
||||
typedef struct Manager Manager;
|
||||
|
||||
881
src/resolve/resolved-hook.c
Normal file
881
src/resolve/resolved-hook.c
Normal file
@ -0,0 +1,881 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "sd-event.h"
|
||||
#include "sd-varlink.h"
|
||||
|
||||
#include "dirent-util.h"
|
||||
#include "dns-domain.h"
|
||||
#include "env-util.h"
|
||||
#include "errno-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "hash-funcs.h"
|
||||
#include "iovec-util.h"
|
||||
#include "json-util.h"
|
||||
#include "ratelimit.h"
|
||||
#include "resolved-hook.h"
|
||||
#include "resolved-manager.h"
|
||||
#include "set.h"
|
||||
#include "stat-util.h"
|
||||
#include "varlink-util.h"
|
||||
|
||||
/* Controls how many idle connections to keep around at max. This is purely an optimization: an established
|
||||
* socket that has gone through connect()/accept() already is just quicker to use. Since we might get a flood
|
||||
* of resolution requests we keep multiple connections open thus, but not too many. */
|
||||
#define HOOK_IDLE_CONNECTIONS_MAX 4U
|
||||
|
||||
/* Encapsulates a specific hook, i.e. bound socket in in the /run/systemd/resolve.hook/ directory */
|
||||
typedef struct Hook {
|
||||
unsigned n_ref;
|
||||
|
||||
Manager *manager;
|
||||
char *socket_path;
|
||||
|
||||
sd_varlink *filter_link;
|
||||
Set *idle_links; /* we retry to recycle varlink connections */
|
||||
|
||||
/* This hook only shall be applied to names matching the following filter parameters */
|
||||
Set *filter_domains; /* if NULL → no filtering; if empty → do not accept anything */
|
||||
unsigned filter_labels_min; /* minimum number of labels */
|
||||
unsigned filter_labels_max; /* maximum number of labels (this is useful to hook only into single-label lookups á la LLMNR) */
|
||||
|
||||
/* timestamp we last saw this in CLOCK_MONOTONIC, for GC handling */
|
||||
uint64_t seen_usec;
|
||||
|
||||
/* When a hook never responds correctly, we'll eventually give up trying */
|
||||
RateLimit reconnect_ratelimit;
|
||||
} Hook;
|
||||
|
||||
static Hook* hook_free(Hook *h) {
|
||||
if (!h)
|
||||
return NULL;
|
||||
|
||||
mfree(h->socket_path);
|
||||
sd_varlink_unref(h->filter_link);
|
||||
set_free(h->idle_links);
|
||||
|
||||
set_free(h->filter_domains);
|
||||
|
||||
return mfree(h);
|
||||
}
|
||||
|
||||
DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(Hook, hook, hook_free);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(Hook*, hook_unref);
|
||||
|
||||
static Hook *hook_unlink(Hook *h) {
|
||||
if (!h)
|
||||
return NULL;
|
||||
|
||||
if (!h->manager)
|
||||
return NULL;
|
||||
|
||||
if (h->socket_path)
|
||||
hashmap_remove(h->manager->hooks, h->socket_path);
|
||||
h->manager = NULL;
|
||||
|
||||
return hook_unref(h);
|
||||
}
|
||||
|
||||
static int dispatch_filter_domains(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
|
||||
Hook *h = 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));
|
||||
|
||||
/* Let's explicitly allocate the set here, since we want that a NULL set means: let everything
|
||||
* through; but an empty set shall mean: let nothing through */
|
||||
r = set_ensure_allocated(&h->filter_domains, &dns_name_hash_ops_free);
|
||||
if (r < 0)
|
||||
return json_log_oom(variant, flags);
|
||||
|
||||
sd_json_variant *i;
|
||||
JSON_VARIANT_ARRAY_FOREACH(i, variant) {
|
||||
if (!sd_json_variant_is_string(i))
|
||||
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL),
|
||||
"Element of JSON field '%s' is not a string.", strna(name));
|
||||
|
||||
r = set_put_strdup_full(&h->filter_domains, &dns_name_hash_ops_free, sd_json_variant_string(i));
|
||||
if (r < 0 && r != -EEXIST)
|
||||
return json_log_oom(variant, flags);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void hook_reset_filter(Hook *h) {
|
||||
assert(h);
|
||||
|
||||
h->filter_domains = set_free(h->filter_domains);
|
||||
h->filter_labels_min = UINT_MAX;
|
||||
h->filter_labels_max = UINT_MAX;
|
||||
}
|
||||
|
||||
static int hook_acquire_filter(Hook *h);
|
||||
|
||||
static int on_filter_reply(
|
||||
sd_varlink *link,
|
||||
sd_json_variant *parameters,
|
||||
const char *error_id,
|
||||
sd_varlink_reply_flags_t flags,
|
||||
void *userdata) {
|
||||
|
||||
Hook *h = ASSERT_PTR(userdata);
|
||||
int r;
|
||||
|
||||
if (error_id) {
|
||||
if (streq(error_id, SD_VARLINK_ERROR_DISCONNECTED)) {
|
||||
/* When we are are disconnected, that's fine, maybe the other side wants to clean up
|
||||
* open connections every now and then, or is being restarted and thus a moment
|
||||
* offline. Try to reconnect immediately to recover. However, a service that
|
||||
* continously fails should not be able to get us into a busy loop, hence we apply a
|
||||
* ratelimit, and when it is hit we stop reconnecting. */
|
||||
if (ratelimit_below(&h->reconnect_ratelimit)) {
|
||||
log_debug("Connection terminated while querying filter of hook '%s', trying to reconnect.", h->socket_path);
|
||||
|
||||
h->filter_link = sd_varlink_unref(h->filter_link);
|
||||
|
||||
r = hook_acquire_filter(h);
|
||||
if (r < 0)
|
||||
goto terminate;
|
||||
} else
|
||||
log_warning("Connection terminated while querying filter of hook '%s', and reconnection attempts failed too quickly, giving up.", h->socket_path);
|
||||
|
||||
goto terminate;
|
||||
}
|
||||
|
||||
if (streq(error_id, SD_VARLINK_ERROR_METHOD_NOT_FOUND)) {
|
||||
log_debug("Hook '%s' does not implement querying filter.", h->socket_path);
|
||||
goto terminate;
|
||||
}
|
||||
|
||||
log_warning("Received error while requesting query filter: %s", error_id);
|
||||
goto terminate;
|
||||
}
|
||||
|
||||
if (!FLAGS_SET(flags, SD_VARLINK_REPLY_CONTINUES)) {
|
||||
log_debug("Final message received while querying filter, terminating connection.");
|
||||
goto terminate;
|
||||
}
|
||||
|
||||
hook_reset_filter(h);
|
||||
|
||||
static const struct sd_json_dispatch_field dispatch_table[] = {
|
||||
{ "filterDomains", SD_JSON_VARIANT_ARRAY, dispatch_filter_domains, 0, SD_JSON_NULLABLE },
|
||||
{ "filterLabelsMin", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_uint, offsetof(Hook, filter_labels_min), 0 },
|
||||
{ "filterLabelsMax", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_uint, offsetof(Hook, filter_labels_max), 0 },
|
||||
{},
|
||||
};
|
||||
|
||||
r = sd_json_dispatch(parameters, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, h);
|
||||
if (r < 0)
|
||||
goto terminate;
|
||||
|
||||
return 1;
|
||||
|
||||
terminate:
|
||||
h->filter_link = sd_varlink_unref(h->filter_link);
|
||||
hook_reset_filter(h);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int hook_varlink_connect(Hook *h, int64_t priority, sd_varlink **ret) {
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
assert(ret);
|
||||
|
||||
_cleanup_(sd_varlink_unrefp) sd_varlink *v = NULL;
|
||||
r = sd_varlink_connect_address(&v, h->socket_path);
|
||||
if (ERRNO_IS_NEG_DISCONNECT(r) || r == -ENOENT) {
|
||||
log_debug_errno(r, "Socket '%s' is not connectible, probably stale, ignoring: %m", h->socket_path);
|
||||
*ret = NULL;
|
||||
return 0; /* dead socket */
|
||||
}
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to connect to '%s': %m", h->socket_path);
|
||||
|
||||
_cleanup_free_ char *bn = NULL;
|
||||
r = path_extract_filename(h->socket_path, &bn);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to extract filename from path '%s': %m", h->socket_path);
|
||||
|
||||
_cleanup_free_ char *j = strjoin("hook-", bn);
|
||||
if (!j)
|
||||
return log_oom();
|
||||
|
||||
(void) sd_varlink_set_description(v, j);
|
||||
|
||||
r = sd_varlink_attach_event(v, h->manager->event, priority);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to attach Varlink connection to event loop: %m");
|
||||
|
||||
*ret = TAKE_PTR(v);
|
||||
return 1; /* worked */
|
||||
}
|
||||
|
||||
static int hook_acquire_filter(Hook *h) {
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
assert(h->manager);
|
||||
|
||||
if (h->filter_link)
|
||||
return 0;
|
||||
|
||||
_cleanup_(sd_varlink_unrefp) sd_varlink *v = NULL;
|
||||
r = hook_varlink_connect(h, SD_EVENT_PRIORITY_NORMAL-10, &v); /* Give the querying of the filter a bit of priority */
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
/* Turn off timeout, after all we want to continously monitor filter changes */
|
||||
r = sd_varlink_set_relative_timeout(v, UINT64_MAX);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to disable timeout on Varlink connection %m");
|
||||
|
||||
sd_varlink_set_userdata(v, h);
|
||||
r = sd_varlink_bind_reply(v, on_filter_reply);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set filter reply callback on Varlink connection: %m");
|
||||
|
||||
r = sd_varlink_observe(
|
||||
v,
|
||||
"io.systemd.Resolve.Hook.QueryFilter",
|
||||
/* parameters= */ NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to issue QueryFilter() varlink call: %m");
|
||||
|
||||
h->filter_link = TAKE_PTR(v);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hook_test_filter(Hook *h, DnsQuestion *question) {
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
assert(question);
|
||||
|
||||
const char *name = dns_question_first_name(question);
|
||||
if (!name)
|
||||
return -EINVAL;
|
||||
|
||||
if (h->filter_labels_max != UINT_MAX || h->filter_labels_min != UINT_MAX) {
|
||||
int n = dns_name_count_labels(name);
|
||||
if (n < 0)
|
||||
return n;
|
||||
|
||||
if (h->filter_labels_max != UINT_MAX && (unsigned) n > h->filter_labels_max)
|
||||
return false;
|
||||
if (h->filter_labels_min != UINT_MAX && (unsigned) n < h->filter_labels_min)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (h->filter_domains)
|
||||
for (const char *p = name;;) {
|
||||
if (set_contains(h->filter_domains, p))
|
||||
break;
|
||||
|
||||
r = dns_name_parent(&p);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int hook_compare(const Hook *a, const Hook *b) {
|
||||
assert(a);
|
||||
|
||||
/* Hooks take preference based on the name of their socket */
|
||||
return path_compare(a->socket_path, b->socket_path);
|
||||
}
|
||||
|
||||
static void hook_recycle_varlink(Hook *h, sd_varlink *vl) {
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
assert(vl);
|
||||
|
||||
/* Disable any potential callbacks while we are recycling the thing */
|
||||
sd_varlink_set_userdata(vl, NULL);
|
||||
sd_varlink_bind_reply(vl, NULL);
|
||||
|
||||
if (set_size(h->idle_links) > HOOK_IDLE_CONNECTIONS_MAX)
|
||||
return;
|
||||
|
||||
/* If we are done with a lookup don't close the connection right-away, but keep it open so that we
|
||||
* can possibly reuse it later, and can save a bit of time on future lookups. We only keep a few
|
||||
* around however. */
|
||||
|
||||
r = set_ensure_put(&h->idle_links, &varlink_hash_ops, vl);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to add varlink connection to idle set, ignoring: %m");
|
||||
else
|
||||
sd_varlink_ref(vl);
|
||||
}
|
||||
|
||||
static void manager_gc_hooks(Manager *m, usec_t seen_usec) {
|
||||
assert(m);
|
||||
|
||||
Hook *h;
|
||||
HASHMAP_FOREACH(h, m->hooks) {
|
||||
/* Keep hooks around that have been seen in this iteration */
|
||||
if (h->seen_usec == seen_usec)
|
||||
continue;
|
||||
|
||||
hook_unlink(h);
|
||||
}
|
||||
}
|
||||
|
||||
DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
|
||||
hook_hash_ops,
|
||||
char, string_hash_func, string_compare_func,
|
||||
Hook, hook_unlink);
|
||||
|
||||
static int manager_hook_add(Manager *m, const char *p, usec_t seen_usec) {
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
assert(p);
|
||||
|
||||
Hook *found = hashmap_get(m->hooks, p);
|
||||
if (found) {
|
||||
found->seen_usec = seen_usec;
|
||||
return 0;
|
||||
}
|
||||
|
||||
_cleanup_free_ char *s = strdup(p);
|
||||
if (!s)
|
||||
return log_oom();
|
||||
|
||||
_cleanup_(hook_unrefp) Hook *h = new(Hook, 1);
|
||||
if (!h)
|
||||
return log_oom();
|
||||
|
||||
*h = (Hook) {
|
||||
.n_ref = 1,
|
||||
.socket_path = TAKE_PTR(s),
|
||||
.filter_labels_min = UINT_MAX,
|
||||
.filter_labels_max = UINT_MAX,
|
||||
.reconnect_ratelimit = { 1 * USEC_PER_SEC, 5 },
|
||||
.seen_usec = seen_usec,
|
||||
};
|
||||
|
||||
if (hashmap_ensure_put(&m->hooks, &hook_hash_ops, h->socket_path, h) < 0)
|
||||
return log_oom();
|
||||
|
||||
hook_ref(h);
|
||||
h->manager = m;
|
||||
|
||||
r = hook_acquire_filter(h);
|
||||
if (r < 0) {
|
||||
hook_unlink(h);
|
||||
return r;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int manager_hook_discover(Manager *m) {
|
||||
/* You might wonder, why is this /run/systemd/resolve.hook/ and not /run/systemd/resolve/hook/?
|
||||
* That's because of permissions: resolved runs as "systemd-resolve" user and owns
|
||||
* /run/systemd/resolve/, but the hook directory is where other privileged code shall bind a socket
|
||||
* in (and where root ownership hence makes sense). Hence we do not nest the directories, but put
|
||||
* them side by side, so that they can have different ownership. */
|
||||
static const char dp[] = "/run/systemd/resolve.hook";
|
||||
_cleanup_closedir_ DIR *d = NULL;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
|
||||
usec_t seen_usec = now(CLOCK_MONOTONIC);
|
||||
|
||||
struct stat st;
|
||||
if (stat(dp, &st) < 0) {
|
||||
if (errno == ENOENT)
|
||||
r = 0;
|
||||
else
|
||||
r = log_warning_errno(errno, "Failed to stat %s/: %m", dp);
|
||||
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (stat_inode_unmodified(&st, &m->hook_stat))
|
||||
return 0;
|
||||
|
||||
d = opendir(dp);
|
||||
if (!d) {
|
||||
if (errno == ENOENT)
|
||||
r = 0;
|
||||
else
|
||||
r = log_warning_errno(errno, "Failed to enumerate %s/ contents: %m", dp);
|
||||
|
||||
goto finish;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
errno = 0;
|
||||
struct dirent *de = readdir_no_dot(d);
|
||||
if (!de) {
|
||||
if (errno == 0) /* EOD */
|
||||
break;
|
||||
|
||||
r = log_error_errno(errno, "Failed to enumerate %s/: %m", dp);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (!IN_SET(de->d_type, DT_SOCK, DT_UNKNOWN))
|
||||
continue;
|
||||
|
||||
_cleanup_free_ char *p = path_join(dp, de->d_name);
|
||||
if (!p) {
|
||||
r = log_oom();
|
||||
goto finish;
|
||||
}
|
||||
|
||||
(void) manager_hook_add(m, p, seen_usec);
|
||||
}
|
||||
|
||||
m->hook_stat = st;
|
||||
r = 0;
|
||||
|
||||
finish:
|
||||
manager_gc_hooks(m, seen_usec);
|
||||
return r;
|
||||
}
|
||||
|
||||
typedef struct HookQuery HookQuery;
|
||||
typedef struct HookQueryCandidate HookQueryCandidate;
|
||||
|
||||
/* Encapsulates a query currently being processed by various hooks */
|
||||
struct HookQuery {
|
||||
/* Question */
|
||||
DnsQuestion *question_idna;
|
||||
DnsQuestion *question_utf8;
|
||||
|
||||
/* Selected answer */
|
||||
DnsAnswer *answer;
|
||||
int answer_rcode;
|
||||
Hook *answer_hook;
|
||||
|
||||
/* Candidates for a reply, i.e, one entry for each hook */
|
||||
LIST_HEAD(HookQueryCandidate, candidates);
|
||||
|
||||
/* Completion callback to invoke */
|
||||
void (*complete)(HookQuery *q, int answer_rcode, DnsAnswer *answer, void *userdata);
|
||||
void *userdata;
|
||||
};
|
||||
|
||||
/* Encapsulates the state of a hook query to one specific hook */
|
||||
struct HookQueryCandidate {
|
||||
HookQuery *query;
|
||||
Hook *hook;
|
||||
sd_varlink *link;
|
||||
LIST_FIELDS(HookQueryCandidate, candidates);
|
||||
};
|
||||
|
||||
static HookQueryCandidate* hook_query_candidate_free(HookQueryCandidate *c) {
|
||||
if (!c)
|
||||
return NULL;
|
||||
|
||||
c->link = sd_varlink_unref(c->link);
|
||||
|
||||
if (c->query)
|
||||
LIST_REMOVE(candidates, c->query->candidates, c);
|
||||
|
||||
hook_unref(c->hook);
|
||||
return mfree(c);
|
||||
}
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(HookQueryCandidate*, hook_query_candidate_free);
|
||||
|
||||
HookQuery* hook_query_free(HookQuery *hq) {
|
||||
if (!hq)
|
||||
return NULL;
|
||||
|
||||
/* Free candidates as long as there are candidates */
|
||||
while (hq->candidates)
|
||||
hook_query_candidate_free(hq->candidates);
|
||||
|
||||
dns_question_unref(hq->question_utf8);
|
||||
dns_question_unref(hq->question_idna);
|
||||
dns_answer_unref(hq->answer);
|
||||
hook_unref(hq->answer_hook);
|
||||
|
||||
return mfree(hq);
|
||||
}
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(HookQuery*, hook_query_free);
|
||||
|
||||
static void hook_query_ready(HookQuery *hq) {
|
||||
assert(hq);
|
||||
|
||||
bool done = true;
|
||||
LIST_FOREACH(candidates, c, hq->candidates)
|
||||
if (c->link) { /* ongoing connection? */
|
||||
done = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!done)
|
||||
return;
|
||||
|
||||
/* The complete() callback quite likely will destroy 'hq', which might be what keeps the answer
|
||||
* object alive. Let's take an explicit ref here hence, so that it definitely remains alive for the
|
||||
* whole callback lifetime */
|
||||
_cleanup_(dns_answer_unrefp) DnsAnswer *answer = dns_answer_ref(hq->answer);
|
||||
hq->complete(hq, hq->answer_rcode, answer, hq->userdata);
|
||||
}
|
||||
|
||||
static int dispatch_rcode(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
|
||||
int *u = ASSERT_PTR(userdata), r;
|
||||
|
||||
assert(variant);
|
||||
|
||||
int rcode;
|
||||
r = sd_json_dispatch_int(name, variant, flags, &rcode);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (rcode < 0 || rcode >= _DNS_RCODE_MAX)
|
||||
return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "JSON field '%s' contains an invalid DNS rcode.", strna(name));
|
||||
|
||||
*u = rcode;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dispatch_answer(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
|
||||
DnsAnswer **a = ASSERT_PTR(userdata);
|
||||
int r;
|
||||
|
||||
assert(variant);
|
||||
|
||||
if (sd_json_variant_is_null(variant)) {
|
||||
*a = dns_answer_unref(*a);
|
||||
return 0;
|
||||
}
|
||||
|
||||
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_answer_unrefp) DnsAnswer *l = NULL;
|
||||
sd_json_variant *e;
|
||||
JSON_VARIANT_ARRAY_FOREACH(e, variant) {
|
||||
if (!sd_json_variant_is_object(e))
|
||||
return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not an object");
|
||||
|
||||
_cleanup_(iovec_done) struct iovec iovec = {};
|
||||
static const sd_json_dispatch_field dispatch_table[] = {
|
||||
{ "raw", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, 0, SD_JSON_MANDATORY },
|
||||
{ "rr", SD_JSON_VARIANT_OBJECT, NULL, 0, 0 },
|
||||
{}
|
||||
};
|
||||
|
||||
r = sd_json_dispatch(e, dispatch_table, flags|SD_JSON_ALLOW_EXTENSIONS, &iovec);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
|
||||
r = dns_resource_record_new_from_raw(&rr, iovec.iov_base, iovec.iov_len);
|
||||
if (r < 0)
|
||||
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL),
|
||||
"JSON field '%s' contains an invalid resource record.", strna(name));
|
||||
|
||||
if (dns_answer_add_extend(&l, rr, /* ifindex= */ 0, /* flags= */ 0, /* rrsig= */ NULL) < 0)
|
||||
return json_log_oom(e, flags);
|
||||
}
|
||||
|
||||
dns_answer_unref(*a);
|
||||
*a = TAKE_PTR(l);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
typedef struct QueryReplyParameters {
|
||||
int rcode;
|
||||
DnsAnswer *answer;
|
||||
} QueryReplyParameters;
|
||||
|
||||
static void query_reply_parameters_done(QueryReplyParameters *p) {
|
||||
assert(p);
|
||||
|
||||
p->answer = dns_answer_unref(p->answer);
|
||||
}
|
||||
|
||||
static int on_query_reply(
|
||||
sd_varlink *link,
|
||||
sd_json_variant *parameters,
|
||||
const char *error_id,
|
||||
sd_varlink_reply_flags_t flags,
|
||||
void *userdata) {
|
||||
|
||||
HookQueryCandidate *qc = ASSERT_PTR(userdata);
|
||||
HookQuery *q = ASSERT_PTR(qc->query); /* save early in case we destroy 'qc' half-way through this function */
|
||||
int r;
|
||||
|
||||
assert(link);
|
||||
|
||||
_cleanup_(query_reply_parameters_done) QueryReplyParameters p = {
|
||||
.rcode = -1,
|
||||
};
|
||||
|
||||
if (error_id) {
|
||||
log_notice("Query on hook '%s' failed with error '%s', ignoring.", qc->hook->socket_path, error_id);
|
||||
r = -EBADR;
|
||||
goto destroy;
|
||||
}
|
||||
|
||||
static const sd_json_dispatch_field dispatch_table[] = {
|
||||
{ "rcode", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_rcode, offsetof(QueryReplyParameters, rcode), 0 },
|
||||
{ "answer", SD_JSON_VARIANT_ARRAY, dispatch_answer, offsetof(QueryReplyParameters, answer), 0 },
|
||||
{},
|
||||
};
|
||||
|
||||
r = sd_json_dispatch(parameters, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &p);
|
||||
if (r < 0)
|
||||
goto destroy;
|
||||
|
||||
if (p.rcode < 0) {
|
||||
/* If no rcode is specified, then this means "continue with regular DNS based resolving" to us */
|
||||
log_debug("Query on hook '%s' returned empty reply, skipping.", qc->hook->socket_path);
|
||||
r = 0;
|
||||
goto destroy;
|
||||
}
|
||||
|
||||
bool win = false;
|
||||
if (p.rcode == DNS_RCODE_SUCCESS)
|
||||
/* if this is a successful lookup, let it win if the so far best lookup was a failure or
|
||||
* empty, or ordered later than us */
|
||||
win = q->answer_rcode != DNS_RCODE_SUCCESS ||
|
||||
dns_answer_isempty(q->answer) ||
|
||||
(!dns_answer_isempty(p.answer) &&
|
||||
hook_compare(qc->hook, q->answer_hook) < 0);
|
||||
else
|
||||
/* if this is a failure lookup, let it win if we so far haven't seen any reply at all, or the
|
||||
* winner so far us ordered later than us. */
|
||||
win = q->answer_rcode < 0 ||
|
||||
hook_compare(qc->hook, q->answer_hook) < 0;
|
||||
|
||||
if (win) {
|
||||
/* This reply wins over whatever was stored before. Let's track that */
|
||||
dns_answer_unref(q->answer);
|
||||
q->answer = TAKE_PTR(p.answer);
|
||||
q->answer_rcode = p.rcode;
|
||||
hook_unref(q->answer_hook);
|
||||
q->answer_hook = hook_ref(qc->hook);
|
||||
}
|
||||
|
||||
hook_recycle_varlink(qc->hook, qc->link);
|
||||
qc->link = sd_varlink_unref(qc->link);
|
||||
|
||||
/* Check if we are ready now, and have processed all hooks on this query (this might destroy our
|
||||
* candidate and our hook query!) */
|
||||
hook_query_ready(q);
|
||||
return 0;
|
||||
|
||||
destroy:
|
||||
qc = hook_query_candidate_free(qc);
|
||||
hook_query_ready(q);
|
||||
return r;
|
||||
}
|
||||
|
||||
static int dns_questions_to_json(DnsQuestion *a, DnsQuestion *b, sd_json_variant **ret) {
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
|
||||
/* Takes both questions and turns them into a JSON array of objects with the key. Note this takes two
|
||||
* questions, one in IDNA and one in UTF-8 encoding, and merges them, removing duplicates. */
|
||||
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *l = NULL;
|
||||
|
||||
DnsResourceKey *key;
|
||||
DNS_QUESTION_FOREACH(key, a) {
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
|
||||
r = dns_resource_key_to_json(key, &v);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_json_variant_append_arraybo(&l, SD_JSON_BUILD_PAIR_VARIANT("key", v));
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (a != b) {
|
||||
DNS_QUESTION_FOREACH(key, b) {
|
||||
if (dns_question_contains_key(a, key))
|
||||
continue;
|
||||
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
|
||||
r = dns_resource_key_to_json(key, &v);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_json_variant_append_arraybo(&l, SD_JSON_BUILD_PAIR_VARIANT("key", v));
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
*ret = TAKE_PTR(l);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hook_query_add_candidate(HookQuery *hq, Hook *h) {
|
||||
int r;
|
||||
|
||||
assert(hq);
|
||||
assert(h);
|
||||
|
||||
_cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
|
||||
for (;;) {
|
||||
/* Before we create a new connection, let's see if there are still idle connections we can
|
||||
* use. */
|
||||
vl = set_steal_first(h->idle_links);
|
||||
if (!vl) {
|
||||
/* Nope, there's nothing, let's create a new connection */
|
||||
r = hook_varlink_connect(h, SD_EVENT_PRIORITY_NORMAL, &vl);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
r = sd_varlink_is_connected(vl);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to check if varlink connection is connected: %m");
|
||||
if (r > 0)
|
||||
break;
|
||||
|
||||
vl = sd_varlink_unref(vl);
|
||||
}
|
||||
|
||||
/* Set a short timeout for hooks. Hooks should not be able to cause the DNS part of the lookup to fail. */
|
||||
r = sd_varlink_set_relative_timeout(vl, SD_RESOLVED_QUERY_TIMEOUT_USEC/4);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set Varlink connection timeout: %m");
|
||||
|
||||
r = sd_varlink_bind_reply(vl, on_query_reply);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to bind reply callback to Varlink connection: %m");
|
||||
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *jq = NULL;
|
||||
r = dns_questions_to_json(hq->question_idna, hq->question_utf8, &jq);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to convert question to JSON: %m");
|
||||
|
||||
r = sd_varlink_invokebo(
|
||||
vl,
|
||||
"io.systemd.Resolve.Hook.ResolveRecord",
|
||||
SD_JSON_BUILD_PAIR_VARIANT("question", jq));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to enqueue question onto Varlink connection: %m");
|
||||
|
||||
_cleanup_(hook_query_candidate_freep) HookQueryCandidate *qc = new(HookQueryCandidate, 1);
|
||||
if (!qc)
|
||||
return log_oom();
|
||||
|
||||
qc->query = hq;
|
||||
qc->hook = hook_ref(h);
|
||||
qc->link = TAKE_PTR(vl);
|
||||
LIST_PREPEND(candidates, hq->candidates, qc);
|
||||
|
||||
sd_varlink_set_userdata(qc->link, qc);
|
||||
|
||||
TAKE_PTR(qc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool use_hooks(void) {
|
||||
static int cache = -1;
|
||||
int r;
|
||||
|
||||
if (cache >= 0)
|
||||
return cache;
|
||||
|
||||
r = secure_getenv_bool("SYSTEMD_RESOLVED_HOOK");
|
||||
if (r < 0) {
|
||||
if (r != -ENXIO)
|
||||
log_debug_errno(r, "Failed to parse $SYSTEMD_RESOLVED_HOOK, ignoring: %m");
|
||||
|
||||
return (cache = true);
|
||||
}
|
||||
|
||||
return (cache = r);
|
||||
}
|
||||
|
||||
int manager_hook_query(
|
||||
Manager *m,
|
||||
DnsQuestion *question_idna,
|
||||
DnsQuestion *question_utf8,
|
||||
HookCompleteCallback complete_cb,
|
||||
void *userdata,
|
||||
HookQuery **ret) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
assert(ret);
|
||||
|
||||
if (!use_hooks()) {
|
||||
*ret = NULL;
|
||||
return 0; /* no relevant hooks, continue immediately */
|
||||
}
|
||||
|
||||
/* Let's bring our list of hooks up-to-date */
|
||||
(void) manager_hook_discover(m);
|
||||
|
||||
_cleanup_(hook_query_freep) HookQuery *hq = NULL;
|
||||
|
||||
Hook *h;
|
||||
HASHMAP_FOREACH(h, m->hooks) {
|
||||
r = hook_test_filter(h, question_idna);
|
||||
if (r < 0) {
|
||||
log_warning_errno(
|
||||
r, "Failed to test if hook '%s' matches IDNA question (%s), assuming not.",
|
||||
h->socket_path, dns_question_first_name(question_idna));
|
||||
continue;
|
||||
}
|
||||
if (r == 0) {
|
||||
r = hook_test_filter(h, question_utf8);
|
||||
if (r < 0) {
|
||||
log_warning_errno(
|
||||
r, "Failed to test if hook '%s' matches UTF-8 question (%s), assuming not.",
|
||||
h->socket_path, dns_question_first_name(question_utf8));
|
||||
continue;
|
||||
}
|
||||
if (r == 0) {
|
||||
log_debug("Hook %s does not match question, skipping.", h->socket_path);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hq) {
|
||||
hq = new(HookQuery, 1);
|
||||
if (!hq)
|
||||
return log_oom();
|
||||
|
||||
*hq = (HookQuery) {
|
||||
.question_idna = dns_question_ref(question_idna),
|
||||
.question_utf8 = dns_question_ref(question_utf8),
|
||||
.answer_rcode = -1,
|
||||
.complete = complete_cb,
|
||||
.userdata = userdata,
|
||||
};
|
||||
}
|
||||
|
||||
r = hook_query_add_candidate(hq, h);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (!hq || !hq->candidates) {
|
||||
*ret = NULL;
|
||||
return 0; /* no relevant hooks, continue immediately */
|
||||
}
|
||||
|
||||
*ret = TAKE_PTR(hq);
|
||||
return 1; /* please wait for the hooks to reply */
|
||||
}
|
||||
10
src/resolve/resolved-hook.h
Normal file
10
src/resolve/resolved-hook.h
Normal file
@ -0,0 +1,10 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "resolved-forward.h"
|
||||
|
||||
typedef void (HookCompleteCallback)(HookQuery *q, int rcode, DnsAnswer *answer, void *userdata);
|
||||
|
||||
int manager_hook_query(Manager *m, DnsQuestion *question_idna, DnsQuestion *question_utf8, HookCompleteCallback complete_cb, void *userdata, HookQuery **ret);
|
||||
|
||||
HookQuery* hook_query_free(HookQuery *hq);
|
||||
@ -918,6 +918,8 @@ Manager* manager_free(Manager *m) {
|
||||
dns_service_browser_free(sb);
|
||||
hashmap_free(m->dns_service_browsers);
|
||||
|
||||
hashmap_free(m->hooks);
|
||||
|
||||
return mfree(m);
|
||||
}
|
||||
|
||||
|
||||
@ -159,6 +159,9 @@ typedef struct Manager {
|
||||
|
||||
/* Map varlink links to DnsServiceBrowser instances. */
|
||||
Hashmap *dns_service_browsers;
|
||||
|
||||
Hashmap *hooks;
|
||||
struct stat hook_stat;
|
||||
} Manager;
|
||||
|
||||
/* Manager */
|
||||
|
||||
@ -700,7 +700,7 @@ static void resolve_service_all_complete(DnsQuery *query) {
|
||||
|
||||
assert(q);
|
||||
|
||||
if (q->block_all_complete > 0) {
|
||||
if (q->hook_query || q->block_all_complete > 0) {
|
||||
TAKE_PTR(q);
|
||||
return;
|
||||
}
|
||||
@ -710,6 +710,13 @@ static void resolve_service_all_complete(DnsQuery *query) {
|
||||
bool have_success = false;
|
||||
|
||||
LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) {
|
||||
|
||||
if (aux->hook_query) {
|
||||
/* If an auxiliary query's hook is still pending, let's wait */
|
||||
TAKE_PTR(q);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (aux->state) {
|
||||
|
||||
case DNS_TRANSACTION_PENDING:
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "sd-event.h"
|
||||
|
||||
#include "dns-answer.h"
|
||||
@ -890,4 +892,10 @@ TEST(dns_query_go) {
|
||||
exercise_dns_query_go(&cfg, NULL);
|
||||
}
|
||||
|
||||
DEFINE_TEST_MAIN(LOG_DEBUG);
|
||||
static int intro(void) {
|
||||
/* Disable hooks in order to make test cases hermetic */
|
||||
ASSERT_OK_ERRNO(setenv("SYSTEMD_RESOLVED_HOOK", "0", /* overwrite= */ false));
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro);
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "sd-json.h"
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "dns-answer.h"
|
||||
#include "dns-domain.h"
|
||||
@ -866,3 +868,36 @@ uint32_t dns_answer_min_ttl(DnsAnswer *a) {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -104,6 +104,8 @@ void dns_answer_randomize(DnsAnswer *a);
|
||||
|
||||
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 {
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
#include "dns-question.h"
|
||||
#include "dns-rr.h"
|
||||
#include "dns-type.h"
|
||||
#include "json-util.h"
|
||||
#include "socket-util.h"
|
||||
#include "string-util.h"
|
||||
|
||||
@ -599,3 +600,37 @@ int dns_question_merge(DnsQuestion *a, DnsQuestion *b, DnsQuestion **ret) {
|
||||
*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;
|
||||
}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "sd-json.h"
|
||||
|
||||
#include "shared-forward.h"
|
||||
|
||||
/* A simple array of resource keys */
|
||||
@ -58,6 +60,8 @@ static inline bool dns_question_isempty(DnsQuestion *q) {
|
||||
|
||||
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) \
|
||||
|
||||
@ -2558,3 +2558,16 @@ static const char* const sshfp_key_type_table[_SSHFP_KEY_TYPE_MAX_DEFINED] = {
|
||||
[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;
|
||||
}
|
||||
|
||||
@ -444,3 +444,5 @@ int sshfp_algorithm_from_string(const char *s) _pure_;
|
||||
|
||||
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);
|
||||
|
||||
@ -169,6 +169,7 @@ shared_sources = files(
|
||||
'recovery-key.c',
|
||||
'reread-partition-table.c',
|
||||
'resize-fs.c',
|
||||
'resolve-hook-util.c',
|
||||
'resolve-util.c',
|
||||
'rm-rf.c',
|
||||
'seccomp-util.c',
|
||||
@ -217,6 +218,7 @@ shared_sources = files(
|
||||
'varlink-io.systemd.PCRLock.c',
|
||||
'varlink-io.systemd.Repart.c',
|
||||
'varlink-io.systemd.Resolve.c',
|
||||
'varlink-io.systemd.Resolve.Hook.c',
|
||||
'varlink-io.systemd.Resolve.Monitor.c',
|
||||
'varlink-io.systemd.Udev.c',
|
||||
'varlink-io.systemd.Unit.c',
|
||||
|
||||
15
src/shared/resolve-hook-util.c
Normal file
15
src/shared/resolve-hook-util.c
Normal file
@ -0,0 +1,15 @@
|
||||
/* 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 },
|
||||
{}
|
||||
};
|
||||
14
src/shared/resolve-hook-util.h
Normal file
14
src/shared/resolve-hook-util.h
Normal file
@ -0,0 +1,14 @@
|
||||
/* 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[];
|
||||
@ -80,10 +80,13 @@
|
||||
#define SD_RESOLVED_QUERY_CONTINUOUS \
|
||||
(UINT64_C(1) << 26)
|
||||
|
||||
/* Output: Result was answered by hook */
|
||||
#define SD_RESOLVED_FROM_HOOK (UINT64_C(1) << 27)
|
||||
|
||||
#define SD_RESOLVED_LLMNR (SD_RESOLVED_LLMNR_IPV4|SD_RESOLVED_LLMNR_IPV6)
|
||||
#define SD_RESOLVED_MDNS (SD_RESOLVED_MDNS_IPV4|SD_RESOLVED_MDNS_IPV6)
|
||||
#define SD_RESOLVED_PROTOCOLS_ALL (SD_RESOLVED_MDNS|SD_RESOLVED_LLMNR|SD_RESOLVED_DNS)
|
||||
|
||||
#define SD_RESOLVED_FROM_MASK (SD_RESOLVED_FROM_CACHE|SD_RESOLVED_FROM_ZONE|SD_RESOLVED_FROM_TRUST_ANCHOR|SD_RESOLVED_FROM_NETWORK)
|
||||
#define SD_RESOLVED_FROM_MASK (SD_RESOLVED_FROM_CACHE|SD_RESOLVED_FROM_ZONE|SD_RESOLVED_FROM_TRUST_ANCHOR|SD_RESOLVED_FROM_NETWORK|SD_RESOLVED_FROM_HOOK)
|
||||
|
||||
#define SD_RESOLVED_QUERY_TIMEOUT_USEC (120 * USEC_PER_SEC)
|
||||
|
||||
81
src/shared/varlink-io.systemd.Resolve.Hook.c
Normal file
81
src/shared/varlink-io.systemd.Resolve.Hook.c
Normal file
@ -0,0 +1,81 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "varlink-io.systemd.Resolve.Hook.h"
|
||||
|
||||
/* We want to reuse the ResourceKey structure from the io.systemd.Resolve interface, hence import it here */
|
||||
#include "varlink-io.systemd.Resolve.h"
|
||||
|
||||
static SD_VARLINK_DEFINE_STRUCT_TYPE(
|
||||
Answer,
|
||||
SD_VARLINK_FIELD_COMMENT("A resource record that shall be looked up. Note that this field is (currently) mostly "
|
||||
"decoration, useful for debugging, and may be omitted. The data actually used is encoded in the "
|
||||
"'raw' field."),
|
||||
SD_VARLINK_DEFINE_FIELD_BY_TYPE(rr, ResourceRecord, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("A resource record encoded in DNS wire format, in turn encoded in Base64. This is the actual data "
|
||||
"returned to the application, and should carry the same information as the 'rr' field, just in a "
|
||||
"different encoding."),
|
||||
SD_VARLINK_DEFINE_FIELD(raw, SD_VARLINK_STRING, 0));
|
||||
|
||||
static SD_VARLINK_DEFINE_STRUCT_TYPE(
|
||||
Question,
|
||||
SD_VARLINK_FIELD_COMMENT("A resource record key that shall be looked up."),
|
||||
SD_VARLINK_DEFINE_FIELD_BY_TYPE(key, ResourceKey, 0));
|
||||
|
||||
static SD_VARLINK_DEFINE_METHOD_FULL(
|
||||
QueryFilter,
|
||||
SD_VARLINK_SUPPORTS_MORE,
|
||||
SD_VARLINK_FIELD_COMMENT("A list of domains this hook is interested in. Lookups for domains not listed here will not be "
|
||||
"passed to the Hook via ResolveRecord(). If this field is not set, requests for all domains "
|
||||
"will be passed to the hook. Note that this applies recursively, i.e. a domain of a lookup is "
|
||||
"considered matching the listed domains both if it exactly matches it, and in case only a suffix "
|
||||
"of it matches it. If this is set to an empty array the hook is disabled."),
|
||||
SD_VARLINK_DEFINE_OUTPUT(filterDomains, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY),
|
||||
SD_VARLINK_FIELD_COMMENT("Require the specified number of labels or more in a domain for the hook to be considered."),
|
||||
SD_VARLINK_DEFINE_OUTPUT(filterLabelsMin, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("Require the specified number of labels or less in a domain for the hook to be considered."),
|
||||
SD_VARLINK_DEFINE_OUTPUT(filterLabelsMax, SD_VARLINK_INT, SD_VARLINK_NULLABLE));
|
||||
|
||||
static SD_VARLINK_DEFINE_METHOD(
|
||||
ResolveRecord,
|
||||
SD_VARLINK_FIELD_COMMENT("The question being looked up, i.e. a combination of resource record keys. Note that unlike DNS "
|
||||
"queries on the wire these lookups can carry multiple key requests, albeit closely related ones. "
|
||||
"Specifically, lookups for A+AAAA for the the same hostname are submitted as one question, as "
|
||||
"are lookups for TXT+SRV when doing DNS-SD resolution. Moreover, when looking up resources with "
|
||||
"non-ASCII characters, they are placed together in a single question, once with labels encoded in "
|
||||
"UTF-8, and once in IDNA. Hook implementations must be able to deal with these and other similar "
|
||||
"combinations of resource key requests, and reply with all matching answers at once, or fail them "
|
||||
"as one. Partial success/failure combinations are not supported."),
|
||||
SD_VARLINK_DEFINE_INPUT_BY_TYPE(question, Question, SD_VARLINK_ARRAY),
|
||||
SD_VARLINK_FIELD_COMMENT("A DNS response code. If a hook sets this return parameter further processing of the lookup via "
|
||||
"regular proocols such as DNS, LLMNR, mDNS is skipped, and the return code returned immediately. "
|
||||
"In other words, if a hook intends to let the request pass to normal resolution, it should not "
|
||||
"set this return parameter."),
|
||||
SD_VARLINK_DEFINE_OUTPUT(rcode, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("An answer for a lookup, i.e. a combination of resource records, matching the request. This "
|
||||
"should only be set when the 'rcode' parameter is returned as 0 (SUCCESS)."),
|
||||
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(answer, Answer, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE));
|
||||
|
||||
SD_VARLINK_DEFINE_INTERFACE(
|
||||
io_systemd_Resolve_Hook,
|
||||
"io.systemd.Resolve.Hook",
|
||||
SD_VARLINK_INTERFACE_COMMENT("Generic interface for implementing a domain name resolution hook."),
|
||||
SD_VARLINK_SYMBOL_COMMENT("Encapsulates a positive lookup answer"),
|
||||
&vl_type_Answer,
|
||||
SD_VARLINK_SYMBOL_COMMENT("Encapsulates a lookup question"),
|
||||
&vl_type_Question,
|
||||
SD_VARLINK_SYMBOL_COMMENT("Encapsulates the class/type/name part of a DNS resource record."),
|
||||
&vl_type_ResourceKey,
|
||||
SD_VARLINK_SYMBOL_COMMENT("Encapsulates a DNS resource record."),
|
||||
&vl_type_ResourceRecord,
|
||||
SD_VARLINK_SYMBOL_COMMENT("Returns filter parameters for this hook. A hook service can implement this to reduce lookup "
|
||||
"requests, by enabling itself only for certain domains or certain numbers of labels in the name. "
|
||||
"It's recommended to implement this to reduce the number of redundant calls to each hook. Note "
|
||||
"that this is advisory only, and implementing services must be able to gracefully handle lookup "
|
||||
"requests that do not match this filter. This call is usually made with the 'more' flag set, in "
|
||||
"which case the connection is left open after the first reply, and the implementing hook "
|
||||
"services can send updates to the filter at any time. Whenever a further reply is sent the "
|
||||
"filter configured therein fully replaces any previously communicated filter."),
|
||||
&vl_method_QueryFilter,
|
||||
SD_VARLINK_SYMBOL_COMMENT("Sent whenever a resolution request is made. This typically takes the filter paramaters returned "
|
||||
"by QueryFilter() into account, but this is not guaranteed."),
|
||||
&vl_method_ResolveRecord);
|
||||
6
src/shared/varlink-io.systemd.Resolve.Hook.h
Normal file
6
src/shared/varlink-io.systemd.Resolve.Hook.h
Normal file
@ -0,0 +1,6 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "sd-varlink-idl.h"
|
||||
|
||||
extern const sd_varlink_interface vl_interface_io_systemd_Resolve_Hook;
|
||||
@ -98,6 +98,8 @@ int sd_dhcp_server_is_in_relay_mode(sd_dhcp_server *server);
|
||||
int sd_dhcp_server_set_relay_target(sd_dhcp_server *server, const struct in_addr* address);
|
||||
int sd_dhcp_server_set_relay_agent_information(sd_dhcp_server *server, const char* circuit_id, const char* remote_id);
|
||||
|
||||
int sd_dhcp_server_get_lease_address_by_name(sd_dhcp_server *server, const char *name, struct in_addr *ret);
|
||||
|
||||
_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_server, sd_dhcp_server_unref);
|
||||
|
||||
_SD_END_DECLARATIONS;
|
||||
|
||||
@ -37,6 +37,7 @@
|
||||
#include "varlink-io.systemd.PCRLock.h"
|
||||
#include "varlink-io.systemd.Repart.h"
|
||||
#include "varlink-io.systemd.Resolve.h"
|
||||
#include "varlink-io.systemd.Resolve.Hook.h"
|
||||
#include "varlink-io.systemd.Resolve.Monitor.h"
|
||||
#include "varlink-io.systemd.Udev.h"
|
||||
#include "varlink-io.systemd.Unit.h"
|
||||
@ -192,6 +193,7 @@ TEST(parse_format) {
|
||||
&vl_interface_io_systemd_PCRLock,
|
||||
&vl_interface_io_systemd_Repart,
|
||||
&vl_interface_io_systemd_Resolve,
|
||||
&vl_interface_io_systemd_Resolve_Hook,
|
||||
&vl_interface_io_systemd_Resolve_Monitor,
|
||||
&vl_interface_io_systemd_Udev,
|
||||
&vl_interface_io_systemd_Unit,
|
||||
|
||||
@ -65,6 +65,7 @@ def setUpModule():
|
||||
for u in [
|
||||
'systemd-networkd.socket',
|
||||
'systemd-networkd-varlink.socket',
|
||||
'systemd-networkd-resolve-hook.socket',
|
||||
'systemd-networkd.service',
|
||||
'systemd-resolved-varlink.socket',
|
||||
'systemd-resolved-monitor.socket',
|
||||
@ -90,13 +91,15 @@ def setUpModule():
|
||||
subprocess.call(['useradd', '--system', '--no-create-home', 'systemd-network'])
|
||||
|
||||
for d in ['/etc/systemd/network', '/run/systemd/network',
|
||||
'/run/systemd/netif', '/run/systemd/resolve']:
|
||||
if os.path.isdir(d):
|
||||
subprocess.check_call(["mount", "-t", "tmpfs", "none", d])
|
||||
tmpmounts.append(d)
|
||||
'/run/systemd/netif', '/run/systemd/resolve', '/run/systemd/resolve.hook']:
|
||||
subprocess.check_call(["mount", "-m", "-t", "tmpfs", "none", d])
|
||||
tmpmounts.append(d)
|
||||
if os.path.isdir('/run/systemd/resolve'):
|
||||
os.chmod('/run/systemd/resolve', 0o755)
|
||||
shutil.chown('/run/systemd/resolve', 'systemd-resolve', 'systemd-resolve')
|
||||
if os.path.isdir('/run/systemd/resolve.hook'):
|
||||
os.chmod('/run/systemd/resolve.hook', 0o755)
|
||||
shutil.chown('/run/systemd/resolve.hook', 'systemd-network', 'systemd-network')
|
||||
if os.path.isdir('/run/systemd/netif'):
|
||||
os.chmod('/run/systemd/netif', 0o755)
|
||||
shutil.chown('/run/systemd/netif', 'systemd-network', 'systemd-network')
|
||||
@ -279,6 +282,8 @@ Gateway=192.168.250.1
|
||||
|
||||
def tearDown(self):
|
||||
subprocess.check_call(['systemctl', 'stop', 'systemd-networkd.socket'])
|
||||
subprocess.check_call(['systemctl', 'stop', 'systemd-networkd-varlink.socket'])
|
||||
subprocess.check_call(['systemctl', 'stop', 'systemd-networkd-resolve-hook.socket'])
|
||||
subprocess.check_call(['systemctl', 'stop', 'systemd-networkd.service'])
|
||||
subprocess.check_call(['ip', 'link', 'del', 'mybridge'])
|
||||
subprocess.check_call(['ip', 'link', 'del', 'port1'])
|
||||
@ -374,6 +379,8 @@ class ClientTestBase(NetworkdTestingUtilities):
|
||||
def tearDown(self):
|
||||
self.shutdown_iface()
|
||||
subprocess.call(['systemctl', 'stop', 'systemd-networkd.socket'])
|
||||
subprocess.call(['systemctl', 'stop', 'systemd-networkd-varlink.socket'])
|
||||
subprocess.call(['systemctl', 'stop', 'systemd-networkd-resolve-hook.socket'])
|
||||
subprocess.call(['systemctl', 'stop', 'systemd-networkd.service'])
|
||||
subprocess.call(['ip', 'link', 'del', 'dummy0'],
|
||||
stderr=subprocess.DEVNULL)
|
||||
@ -931,9 +938,11 @@ class NetworkdClientTest(ClientTestBase, unittest.TestCase):
|
||||
set -eu
|
||||
mkdir -p /run/systemd/network
|
||||
mkdir -p /run/systemd/netif
|
||||
mkdir -p /run/systemd/resolve.hook
|
||||
mkdir -p /var/lib/systemd/network
|
||||
mount -t tmpfs none /run/systemd/network
|
||||
mount -t tmpfs none /run/systemd/netif
|
||||
mount -t tmpfs none /run/systemd/resolve.hook
|
||||
mount -t tmpfs none /var/lib/systemd/network
|
||||
[ ! -e /run/dbus ] || mount -t tmpfs none /run/dbus
|
||||
# create router/client veth pair
|
||||
@ -967,6 +976,9 @@ EOF
|
||||
# Hence, 'networkctl persistent-storage yes' cannot be used.
|
||||
export SYSTEMD_NETWORK_PERSISTENT_STORAGE_READY=1
|
||||
|
||||
# Don't try to register resolved hook for our testcase
|
||||
export SYSTEMD_NETWORK_RESOLVE_HOOK=0
|
||||
|
||||
# Generate debugging logs.
|
||||
export SYSTEMD_LOG_LEVEL=debug
|
||||
|
||||
@ -983,6 +995,7 @@ exec $(systemctl cat systemd-networkd.service | sed -n '/^ExecStart=/ {{ s/^.*=/
|
||||
'-p', 'InaccessibleDirectories=-/etc/systemd/network',
|
||||
'-p', 'InaccessibleDirectories=-/run/systemd/network',
|
||||
'-p', 'InaccessibleDirectories=-/run/systemd/netif',
|
||||
'-p', 'InaccessibleDirectories=-/run/systemd/resolve.hook',
|
||||
'-p', 'InaccessibleDirectories=-/var/lib/systemd/network',
|
||||
'--service-type=notify', script])
|
||||
|
||||
|
||||
10
test/test-network/conf/25-dhcp-client-resolve-hook.network
Normal file
10
test/test-network/conf/25-dhcp-client-resolve-hook.network
Normal file
@ -0,0 +1,10 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
[Match]
|
||||
Name=veth99
|
||||
|
||||
[Network]
|
||||
DHCP=yes
|
||||
|
||||
[DHCPv4]
|
||||
SendHostname=yes
|
||||
Hostname=flummy
|
||||
13
test/test-network/conf/25-dhcp-server-resolve-hook.network
Normal file
13
test/test-network/conf/25-dhcp-server-resolve-hook.network
Normal file
@ -0,0 +1,13 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
[Match]
|
||||
Name=veth-peer
|
||||
|
||||
[Network]
|
||||
IPv6AcceptRA=false
|
||||
DHCPServer=yes
|
||||
|
||||
[DHCPServer]
|
||||
ServerAddress=192.168.5.1/24
|
||||
PoolOffset=20
|
||||
PoolSize=10
|
||||
LocalLeaseDomain=_networkdtest
|
||||
@ -426,6 +426,7 @@ def save_active_units():
|
||||
for u in [
|
||||
'systemd-networkd.socket',
|
||||
'systemd-networkd-varlink.socket',
|
||||
'systemd-networkd-resolve-hook.socket',
|
||||
'systemd-networkd.service',
|
||||
'systemd-resolved-monitor.socket',
|
||||
'systemd-resolved-varlink.socket',
|
||||
@ -449,6 +450,10 @@ def restore_active_units():
|
||||
call('systemctl stop systemd-networkd-varlink.socket')
|
||||
has_network_socket = True
|
||||
|
||||
if 'systemd-networkd-resolve-hook.socket' in active_units:
|
||||
call('systemctl stop systemd-networkd-resolve-hook.socket')
|
||||
has_network_socket = True
|
||||
|
||||
if 'systemd-resolved-monitor.socket' in active_units:
|
||||
call('systemctl stop systemd-resolved-monitor.socket')
|
||||
has_resolve_socket = True
|
||||
@ -525,6 +530,7 @@ def setup_system_units():
|
||||
'systemd-networkd.service',
|
||||
'systemd-networkd.socket',
|
||||
'systemd-networkd-varlink.socket',
|
||||
'systemd-networkd-resolve-hook.socket',
|
||||
'systemd-networkd-persistent-storage.service',
|
||||
'systemd-resolved.service',
|
||||
'systemd-timesyncd.service',
|
||||
@ -572,6 +578,13 @@ def setup_system_units():
|
||||
'StartLimitIntervalSec=0',
|
||||
]
|
||||
)
|
||||
create_unit_dropin(
|
||||
'systemd-networkd-resolve-hook.socket',
|
||||
[
|
||||
'[Unit]',
|
||||
'StartLimitIntervalSec=0',
|
||||
]
|
||||
)
|
||||
create_unit_dropin(
|
||||
'systemd-networkd-persistent-storage.service',
|
||||
[
|
||||
@ -604,6 +617,7 @@ def clear_system_units():
|
||||
rm_unit('systemd-networkd.service')
|
||||
rm_unit('systemd-networkd.socket')
|
||||
rm_unit('systemd-networkd-varlink.socket')
|
||||
rm_unit('systemd-networkd-resolve-hook.socket')
|
||||
rm_unit('systemd-networkd-persistent-storage.service')
|
||||
rm_unit('systemd-resolved.service')
|
||||
rm_unit('systemd-timesyncd.service')
|
||||
@ -990,10 +1004,12 @@ def stop_networkd(show_logs=True, check_failed=True):
|
||||
if check_failed:
|
||||
check_output('systemctl stop systemd-networkd.socket')
|
||||
check_output('systemctl stop systemd-networkd-varlink.socket')
|
||||
check_output('systemctl stop systemd-networkd-resolve-hook.socket')
|
||||
check_output('systemctl stop systemd-networkd.service')
|
||||
else:
|
||||
call('systemctl stop systemd-networkd.socket')
|
||||
call('systemctl stop systemd-networkd-varlink.socket')
|
||||
call('systemctl stop systemd-networkd-resolve-hook.socket')
|
||||
call('systemctl stop systemd-networkd.service')
|
||||
|
||||
if show_logs:
|
||||
@ -7366,6 +7382,16 @@ class NetworkdDHCPServerTests(unittest.TestCase, Utilities):
|
||||
data = json.loads(output)
|
||||
self.assertEqual(data['DHCPv4Client']['Lease']['Hostname'], 'fqdn.example.com')
|
||||
|
||||
def test_dhcp_server_resolve_hook(self):
|
||||
copy_network_unit('25-veth.netdev', '25-dhcp-client-resolve-hook.network', '25-dhcp-server-resolve-hook.network')
|
||||
start_networkd()
|
||||
self.wait_online('veth99:routable', 'veth-peer:routable')
|
||||
|
||||
output = check_output('resolvectl query flummy._networkdtest')
|
||||
print(output)
|
||||
self.assertIn('192.168.5.2', output)
|
||||
|
||||
|
||||
class NetworkdDHCPServerRelayAgentTests(unittest.TestCase, Utilities):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
@ -136,4 +136,12 @@ done
|
||||
(! 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}
|
||||
|
||||
@ -83,6 +83,9 @@ timer2=$(systemctl show -P NextElapseUSecRealtime upgrade_timer_test.timer)
|
||||
|
||||
dnf downgrade -y --allowerasing --disablerepo '*' "$pkgdir"/distro/*.rpm
|
||||
|
||||
# FIXME: See https://github.com/systemd/systemd/pull/39293
|
||||
systemctl stop systemd-networkd-resolve-hook.socket || true
|
||||
|
||||
# Some distros don't ship networkd, so the test will always fail
|
||||
if command -v networkctl >/dev/null; then
|
||||
networkd=1
|
||||
|
||||
@ -497,6 +497,10 @@ units = [
|
||||
'file' : 'systemd-networkd-varlink.socket',
|
||||
'conditions' : ['ENABLE_NETWORKD'],
|
||||
},
|
||||
{
|
||||
'file' : 'systemd-networkd-resolve-hook.socket',
|
||||
'conditions' : ['ENABLE_NETWORKD'],
|
||||
},
|
||||
{
|
||||
'file' : 'systemd-networkd-wait-online.service.in',
|
||||
'conditions' : ['ENABLE_NETWORKD'],
|
||||
|
||||
26
units/systemd-networkd-resolve-hook.socket
Normal file
26
units/systemd-networkd-resolve-hook.socket
Normal file
@ -0,0 +1,26 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
#
|
||||
# This file is part of systemd.
|
||||
#
|
||||
# systemd is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
[Unit]
|
||||
Description=Network Management Resolve Hook Socket
|
||||
Documentation=man:systemd-networkd.service(8)
|
||||
ConditionCapability=CAP_NET_ADMIN
|
||||
DefaultDependencies=no
|
||||
Before=sockets.target shutdown.target
|
||||
Conflicts=shutdown.target
|
||||
|
||||
[Socket]
|
||||
ListenStream=/run/systemd/resolve.hook/io.systemd.Network
|
||||
FileDescriptorName=resolve-hook
|
||||
SocketMode=0666
|
||||
Service=systemd-networkd.service
|
||||
RemoveOnStop=yes
|
||||
|
||||
[Install]
|
||||
WantedBy=sockets.target
|
||||
@ -46,7 +46,7 @@ RestrictRealtime=yes
|
||||
RestrictSUIDSGID=yes
|
||||
RuntimeDirectory=systemd/netif
|
||||
RuntimeDirectoryPreserve=yes
|
||||
Sockets=systemd-networkd.socket systemd-networkd-varlink.socket
|
||||
Sockets=systemd-networkd.socket systemd-networkd-varlink.socket systemd-networkd-resolve-hook.socket
|
||||
SystemCallArchitectures=native
|
||||
SystemCallErrorNumber=EPERM
|
||||
SystemCallFilter=@system-service bpf
|
||||
@ -56,7 +56,7 @@ User=systemd-network
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Also=systemd-networkd.socket systemd-networkd-varlink.socket
|
||||
Also=systemd-networkd.socket systemd-networkd-varlink.socket systemd-networkd-resolve-hook.socket
|
||||
Alias=dbus-org.freedesktop.network1.service
|
||||
|
||||
# The output from this generator is used by udevd and networkd. Enable it by
|
||||
|
||||
@ -18,6 +18,7 @@ Conflicts=shutdown.target
|
||||
[Socket]
|
||||
ReceiveBuffer=128M
|
||||
ListenNetlink=route 1361
|
||||
FileDescriptorName=rtnl
|
||||
PassPacketInfo=yes
|
||||
|
||||
[Install]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user