networkd: add support for resolved hook for DHCP server
Let's synthesize DNS RRs for leases handed out by our DHCP server. This way local VMs can have resolvable hostnames locally. This does not implement reverse look ups for now. We can add this later in similar fashion.
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -4,4 +4,3 @@
|
||||
#include "networkd-forward.h"
|
||||
|
||||
int manager_connect_varlink(Manager *m, int fd);
|
||||
void manager_varlink_done(Manager *m);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -590,6 +599,10 @@ int manager_setup(Manager *m) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = manager_varlink_init_resolve_hook(m, TAKE_FD(resolve_hook_fd));
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = manager_connect_bus(m);
|
||||
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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user