Merge pull request #6993 from rojkov/dnssd

Add support for server-side DNS-SD in mDNS zones.
This commit is contained in:
Lennart Poettering
2017-12-08 22:21:01 +01:00
committed by GitHub
29 changed files with 2005 additions and 50 deletions
+1
View File
@@ -662,6 +662,7 @@ manpages = [
['systemd.link', '5', [], ''],
['systemd.mount', '5', [], ''],
['systemd.netdev', '5', [], 'ENABLE_NETWORKD'],
['systemd.dnssd', '5', [], 'ENABLE_RESOLVE'],
['systemd.network', '5', [], 'ENABLE_NETWORKD'],
['systemd.nspawn', '5', [], ''],
['systemd.offline-updates', '7', [], ''],
+1
View File
@@ -404,6 +404,7 @@ _443._tcp.fedoraproject.org IN TLSA 0 0 1 19400be5b7a31fb733917700789d2f0a2471c0
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
<citerefentry><refentrytitle>systemd.dnssd</refentrytitle><manvolnum>5</manvolnum></citerefentry>
</para>
</refsect1>
</refentry>
+258
View File
@@ -0,0 +1,258 @@
<?xml version='1.0'?> <!--*- Mode: nxml; nxml-child-indent: 2; indent-tabs-mode: nil -*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<!--
This file is part of systemd.
Copyright 2017 Dmitry Rozhkov
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.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
-->
<refentry id="systemd.dnssd" conditional='ENABLE_RESOLVE'>
<refentryinfo>
<title>systemd.dnssd</title>
<productname>systemd</productname>
<authorgroup>
<author>
<contrib>Developer</contrib>
<firstname>Dmitry</firstname>
<surname>Rozhkov</surname>
<email>dmitry.rozhkov@intel.com</email>
</author>
</authorgroup>
</refentryinfo>
<refmeta>
<refentrytitle>systemd.dnssd</refentrytitle>
<manvolnum>5</manvolnum>
</refmeta>
<refnamediv>
<refname>systemd.dnssd</refname>
<refpurpose>DNS-SD configuration</refpurpose>
</refnamediv>
<refsynopsisdiv>
<para><filename><replaceable>network_service</replaceable>.dnssd</filename></para>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>DNS-SD setup is performed by
<citerefentry><refentrytitle>systemd-resolved</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
</para>
<para>The main network service file must have the extension <filename>.dnssd</filename>; other
extensions are ignored.</para>
<para>The <filename>.dnssd</filename> files are read from the files located in the system
network directory <filename>/usr/lib/systemd/dnssd</filename>, the volatile runtime network
directory <filename>/run/systemd/dnssd</filename> and the local administration network
directory <filename>/etc/systemd/dnssd</filename>. All configuration files are collectively
sorted and processed in lexical order, regardless of the directories in which they live.
However, files with identical filenames replace each other. Files in <filename>/etc</filename>
have the highest priority, files in <filename>/run</filename> take precedence over files with
the same name in <filename>/usr/lib</filename>. This can be used to override a system-supplied
configuration file with a local file if needed.</para>
<para>Along with the network service file <filename>foo.dnssd</filename>, a "drop-in" directory
<filename>foo.dnssd.d/</filename> may exist. All files with the suffix
<literal>.conf</literal> from this directory will be parsed after the file itself is
parsed. This is useful to alter or add configuration settings, without having to modify the main
configuration file. Each drop-in file must have appropriate section headers.</para>
<para>In addition to <filename>/etc/systemd/dnssd</filename>, drop-in <literal>.d</literal>
directories can be placed in <filename>/usr/lib/systemd/dnssd</filename> or
<filename>/run/systemd/dnssd</filename> directories. Drop-in files in
<filename>/etc</filename> take precedence over those in <filename>/run</filename> which in turn
take precedence over those in <filename>/usr/lib</filename>. Drop-in files under any of these
directories take precedence over the main network service file wherever located. (Of course, since
<filename>/run</filename> is temporary and <filename>/usr/lib</filename> is for vendors, it is
unlikely drop-ins should be used in either of those places.)</para>
</refsect1>
<refsect1>
<title>[Service] Section Options</title>
<para>The network service file contains a <literal>[Service]</literal>
section, which specifies a discoverable network service announced in a
local network with Multicast DNS broadcasts.</para>
<variablelist class='network-directives'>
<varlistentry>
<term><varname>Name=</varname></term>
<listitem>
<para>An instance name of the network service as defined in the section 4.1.1 of <ulink
url="https://tools.ietf.org/html/rfc6763">RFC 6763</ulink>, e.g. <literal>webserver</literal>.</para>
<para>The option supports simple specifier expansion. The following expansions are understood:</para>
<table>
<title>Specifiers available</title>
<tgroup cols='3' align='left' colsep='1' rowsep='1'>
<colspec colname="spec" />
<colspec colname="mean" />
<colspec colname="detail" />
<thead>
<row>
<entry>Specifier</entry>
<entry>Meaning</entry>
<entry>Details</entry>
</row>
</thead>
<tbody>
<row>
<entry><literal>%m</literal></entry>
<entry>Machine ID</entry>
<entry>The machine ID of the running system, formatted as string. See <citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry> for more information.</entry>
</row>
<row>
<entry><literal>%b</literal></entry>
<entry>Boot ID</entry>
<entry>The boot ID of the running system, formatted as string. See <citerefentry><refentrytitle>random</refentrytitle><manvolnum>4</manvolnum></citerefentry> for more information.</entry>
</row>
<row>
<entry><literal>%H</literal></entry>
<entry>Host name</entry>
<entry>The hostname of the running system.</entry>
</row>
<row>
<entry><literal>%v</literal></entry>
<entry>Kernel release</entry>
<entry>Identical to <command>uname -r</command> output.</entry>
</row>
</tbody>
</tgroup>
</table>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>Type=</varname></term>
<listitem>
<para>A type of the network service as defined in the section 4.1.2 of <ulink
url="https://tools.ietf.org/html/rfc6763">RFC 6763</ulink>, e.g. <literal>_http._tcp</literal>.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>Port=</varname></term>
<listitem>
<para>An IP port number of the network service.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>Priority=</varname></term>
<listitem>
<para>A priority number set in SRV resource records corresponding to the network service.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>Weight=</varname></term>
<listitem>
<para>A weight number set in SRV resource records corresponding to the network service.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>TxtText=</varname></term>
<listitem>
<para>A whitespace-separated list of arbitrary key/value pairs
conveying additional information about the named service in the corresponding TXT resource record,
e.g. <literal>path=/portal/index.html</literal>. Keys and values can contain C-style escape
sequences which get translated upon reading configuration files.
</para>
<para>This option together with <varname>TxtData=</varname> may be specified more than once, in which
case multiple TXT resource records will be created for the service. If the empty string is assigned to
this option, the list is reset and all prior assignments will have no effect.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>TxtData=</varname></term>
<listitem>
<para>A whitespace-separated list of arbitrary key/value pairs
conveying additional information about the named service in the corresponding TXT resource record
where values are base64-encoded string representing any binary data,
e.g. <literal>data=YW55IGJpbmFyeSBkYXRhCg==</literal>. Keys can contain C-style escape
sequences which get translated upon reading configuration files.
</para>
<para>This option together with <varname>TxtText=</varname> may be specified more than once, in which
case multiple TXT resource records will be created for the service. If the empty string is assigned to
this option, the list is reset and all prior assignments will have no effect.
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Examples</title>
<example>
<title>HTTP service</title>
<programlisting># /etc/systemd/dnssd/http.dnssd
[Service]
Name=%h
Type=_http._tcp
Port=80
TxtText=path=/stats/index.html t=temperature_sensor</programlisting>
<para>This makes the http server running on the host discoverable in the local network
given MulticastDNS is enabled on the network interface.</para>
<para>Now the utility <literal>systemd-resolve</literal> should be able to resolve the
service to the host's name:</para>
<programlisting>$ systemd-resolve --service meteo._http._tcp.local
meteo._http._tcp.local: meteo.local:80 [priority=0, weight=0]
169.254.208.106%senp0s21f0u2u4
fe80::213:3bff:fe49:8aa%senp0s21f0u2u4
path=/stats/index.html
t=temperature_sensor
(meteo/_http._tcp/local)
-- Information acquired via protocol mDNS/IPv6 in 4.0ms.
-- Data is authenticated: yes</programlisting>
<para><literal>Avahi</literal> running on a different host in the same local network should see the service as well:</para>
<programlisting>$ avahi-browse -a -r
+ enp3s0 IPv6 meteo Web Site local
+ enp3s0 IPv4 meteo Web Site local
= enp3s0 IPv6 meteo Web Site local
hostname = [meteo.local]
address = [fe80::213:3bff:fe49:8aa]
port = [80]
txt = ["path=/stats/index.html" "t=temperature_sensor"]
= enp3s0 IPv4 meteo Web Site local
hostname = [meteo.local]
address = [169.254.208.106]
port = [80]
txt = ["path=/stats/index.html" "t=temperature_sensor"]</programlisting>
</example>
</refsect1>
<refsect1>
<title>See Also</title>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
</para>
</refsect1>
</refentry>
@@ -83,6 +83,8 @@
#define BUS_ERROR_NO_SUCH_LINK "org.freedesktop.resolve1.NoSuchLink"
#define BUS_ERROR_LINK_BUSY "org.freedesktop.resolve1.LinkBusy"
#define BUS_ERROR_NETWORK_DOWN "org.freedesktop.resolve1.NetworkDown"
#define BUS_ERROR_NO_SUCH_DNSSD_SERVICE "org.freedesktop.resolve1.NoSuchDnssdService"
#define BUS_ERROR_DNSSD_SERVICE_EXISTS "org.freedesktop.resolve1.DnssdServiceExists"
#define _BUS_ERROR_DNS "org.freedesktop.resolve1.DnsError."
#define BUS_ERROR_NO_SUCH_TRANSFER "org.freedesktop.import1.NoSuchTransfer"
+20 -1
View File
@@ -35,6 +35,10 @@ systemd_resolved_only_sources = files('''
resolved.c
resolved-manager.c
resolved-manager.h
resolved-dnssd.c
resolved-dnssd.h
resolved-dnssd-bus.c
resolved-dnssd-bus.h
resolved-conf.c
resolved-conf.h
resolved-resolv-conf.c
@@ -131,8 +135,14 @@ resolved_gperf_c = custom_target(
output : 'resolved-gperf.c',
command : [gperf, '@INPUT@', '--output-file', '@OUTPUT@'])
resolved_dnssd_gperf_c = custom_target(
'resolved_dnssd_gperf.c',
input : 'resolved-dnssd-gperf.gperf',
output : 'resolved-dnssd-gperf.c',
command : [gperf, '@INPUT@', '--output-file', '@OUTPUT@'])
systemd_resolved_sources = (basic_dns_sources +
[resolved_gperf_c] +
[resolved_gperf_c, resolved_dnssd_gperf_c] +
systemd_resolved_only_sources +
dns_type_headers)
@@ -155,6 +165,15 @@ if conf.get('ENABLE_RESOLVE') == 1
install_data('resolv.conf',
install_dir : rootlibexecdir)
i18n.merge_file(
'org.freedesktop.resolve1.policy',
input : 'org.freedesktop.resolve1.policy.in',
output : 'org.freedesktop.resolve1.policy',
po_dir : po_dir,
data_dirs : po_dir,
install : install_polkit,
install_dir : polkitpolicydir)
endif
tests += [
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?> <!--*-nxml-*-->
<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
"http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
<!--
SPDX-License-Identifier: LGPL-2.1+
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.
-->
<policyconfig>
<vendor>The systemd Project</vendor>
<vendor_url>http://www.freedesktop.org/wiki/Software/systemd</vendor_url>
<action id="org.freedesktop.resolve1.register-service">
<description>Register a DNS-SD service</description>
<message>Authentication is required to register a DNS-SD service</message>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
<annotate key="org.freedesktop.policykit.owner">unix-user:systemd-resolve</annotate>
</action>
<action id="org.freedesktop.resolve1.unregister-service">
<description>Unregister a DNS-SD service</description>
<message>Authentication is required to unregister a DNS-SD service</message>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
<annotate key="org.freedesktop.policykit.owner">unix-user:systemd-resolve</annotate>
</action>
</policyconfig>
+240
View File
@@ -25,7 +25,11 @@
#include "resolved-bus.h"
#include "resolved-def.h"
#include "resolved-dns-synthesize.h"
#include "resolved-dnssd.h"
#include "resolved-dnssd-bus.h"
#include "resolved-link-bus.h"
#include "user-util.h"
#include "utf8.h"
static int reply_query_state(DnsQuery *q) {
@@ -1581,6 +1585,232 @@ static int bus_method_reset_server_features(sd_bus_message *message, void *userd
return sd_bus_reply_method_return(message, NULL);
}
static int on_bus_track(sd_bus_track *t, void *userdata) {
DnssdService *s = userdata;
assert(t);
assert(s);
log_debug("Client of active request vanished, destroying DNS-SD service.");
dnssd_service_free(s);
return 0;
}
static int bus_method_register_service(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
_cleanup_(dnssd_service_freep) DnssdService *service = NULL;
_cleanup_(sd_bus_track_unrefp) sd_bus_track *bus_track = NULL;
_cleanup_free_ char *path = NULL;
_cleanup_free_ char *instance_name = NULL;
Manager *m = userdata;
DnssdService *s = NULL;
const char *name;
const char *name_template;
const char *type;
uid_t euid;
int r;
assert(message);
assert(m);
if (m->mdns_support != RESOLVE_SUPPORT_YES)
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Support for MulticastDNS is disabled");
r = bus_verify_polkit_async(message, CAP_SYS_ADMIN,
"org.freedesktop.resolve1.register-service",
NULL, false, UID_INVALID,
&m->polkit_registry, error);
if (r < 0)
return r;
if (r == 0)
return 1; /* Polkit will call us back */
service = new0(DnssdService, 1);
if (!service)
return log_oom();
r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds);
if (r < 0)
return r;
r = sd_bus_creds_get_euid(creds, &euid);
if (r < 0)
return r;
service->originator = euid;
r = sd_bus_message_read(message, "sssqqq", &name, &name_template, &type,
&service->port, &service->priority,
&service->weight);
if (r < 0)
return r;
s = hashmap_get(m->dnssd_services, name);
if (s)
return sd_bus_error_setf(error, BUS_ERROR_DNSSD_SERVICE_EXISTS, "DNS-SD service '%s' exists already", name);
if (!dnssd_srv_type_is_valid(type))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "DNS-SD service type '%s' is invalid", type);
service->name = strdup(name);
if (!service->name)
return log_oom();
service->name_template = strdup(name_template);
if (!service->name_template)
return log_oom();
service->type = strdup(type);
if (!service->type)
return log_oom();
r = dnssd_render_instance_name(service, &instance_name);
if (r < 0)
return r;
r = sd_bus_message_enter_container(message, SD_BUS_TYPE_ARRAY, "a{say}");
if (r < 0)
return r;
while ((r = sd_bus_message_enter_container(message, SD_BUS_TYPE_ARRAY, "{say}")) > 0) {
_cleanup_(dnssd_txtdata_freep) DnssdTxtData *txt_data = NULL;
DnsTxtItem *last = NULL;
txt_data = new0(DnssdTxtData, 1);
if (!txt_data)
return log_oom();
while ((r = sd_bus_message_enter_container(message, SD_BUS_TYPE_DICT_ENTRY, "say")) > 0) {
const char *key;
const void *value;
size_t size;
DnsTxtItem *i;
r = sd_bus_message_read(message, "s", &key);
if (r < 0)
return r;
if (isempty(key))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Keys in DNS-SD TXT RRs can't be empty");
if (!ascii_is_valid(key))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "TXT key '%s' contains non-ASCII symbols", key);
r = sd_bus_message_read_array(message, 'y', &value, &size);
if (r < 0)
return r;
r = dnssd_txt_item_new_from_data(key, value, size, &i);
if (r < 0)
return r;
LIST_INSERT_AFTER(items, txt_data->txt, last, i);
last = i;
r = sd_bus_message_exit_container(message);
if (r < 0)
return r;
}
if (r < 0)
return r;
r = sd_bus_message_exit_container(message);
if (r < 0)
return r;
if (txt_data->txt) {
LIST_PREPEND(items, service->txt_data_items, txt_data);
txt_data = NULL;
}
}
if (r < 0)
return r;
r = sd_bus_message_exit_container(message);
if (r < 0)
return r;
if (!service->txt_data_items) {
_cleanup_(dnssd_txtdata_freep) DnssdTxtData *txt_data = NULL;
txt_data = new0(DnssdTxtData, 1);
if (!txt_data)
return log_oom();
r = dns_txt_item_new_empty(&txt_data->txt);
if (r < 0)
return r;
LIST_PREPEND(items, service->txt_data_items, txt_data);
txt_data = NULL;
}
r = sd_bus_path_encode("/org/freedesktop/resolve1/dnssd", service->name, &path);
if (r < 0)
return r;
r = hashmap_ensure_allocated(&m->dnssd_services, &string_hash_ops);
if (r < 0)
return r;
r = hashmap_put(m->dnssd_services, service->name, service);
if (r < 0)
return r;
r = sd_bus_track_new(sd_bus_message_get_bus(message), &bus_track, on_bus_track, service);
if (r < 0)
return r;
r = sd_bus_track_add_sender(bus_track, message);
if (r < 0)
return r;
service->manager = m;
service = NULL;
manager_refresh_rrs(m);
return sd_bus_reply_method_return(message, "o", path);
}
static int call_dnssd_method(Manager *m, sd_bus_message *message, sd_bus_message_handler_t handler, sd_bus_error *error) {
_cleanup_free_ char *name = NULL;
DnssdService *s = NULL;
const char *path;
int r;
assert(m);
assert(message);
assert(handler);
r = sd_bus_message_read(message, "o", &path);
if (r < 0)
return r;
r = sd_bus_path_decode(path, "/org/freedesktop/resolve1/dnssd", &name);
if (r == 0)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_DNSSD_SERVICE, "DNS-SD service with object path '%s' does not exist", path);
if (r < 0)
return r;
s = hashmap_get(m->dnssd_services, name);
if (!s)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_DNSSD_SERVICE, "DNS-SD service '%s' not known", name);
return handler(message, s, error);
}
static int bus_method_unregister_service(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Manager *m = userdata;
assert(message);
assert(m);
return call_dnssd_method(m, message, bus_dnssd_method_unregister, error);
}
static const sd_bus_vtable resolve_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("LLMNRHostname", "s", NULL, offsetof(Manager, llmnr_hostname), 0),
@@ -1609,6 +1839,8 @@ static const sd_bus_vtable resolve_vtable[] = {
SD_BUS_METHOD("SetLinkDNSSECNegativeTrustAnchors", "ias", NULL, bus_method_set_link_dnssec_negative_trust_anchors, 0),
SD_BUS_METHOD("RevertLink", "i", NULL, bus_method_revert_link, 0),
SD_BUS_METHOD("RegisterService", "sssqqqaa{say}", "o", bus_method_register_service, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("UnregisterService", "o", NULL, bus_method_unregister_service, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_VTABLE_END,
};
@@ -1681,6 +1913,14 @@ int manager_connect_bus(Manager *m) {
if (r < 0)
return log_error_errno(r, "Failed to register link enumerator: %m");
r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/resolve1/dnssd", "org.freedesktop.resolve1.DnssdService", dnssd_vtable, dnssd_object_find, m);
if (r < 0)
return log_error_errno(r, "Failed to register dnssd objects: %m");
r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/resolve1/dnssd", dnssd_node_enumerator, m);
if (r < 0)
return log_error_errno(r, "Failed to register dnssd enumerator: %m");
r = sd_bus_request_name(m->bus, "org.freedesktop.resolve1", 0);
if (r < 0)
return log_error_errno(r, "Failed to register name: %m");
+158
View File
@@ -22,10 +22,14 @@
#include "conf-parser.h"
#include "def.h"
#include "extract-word.h"
#include "hexdecoct.h"
#include "parse-util.h"
#include "resolved-conf.h"
#include "resolved-dnssd.h"
#include "specifier.h"
#include "string-table.h"
#include "string-util.h"
#include "utf8.h"
DEFINE_CONFIG_PARSE_ENUM(config_parse_dns_stub_listener_mode, dns_stub_listener_mode, DnsStubListenerMode, "Failed to parse DNS stub listener mode setting");
@@ -228,6 +232,160 @@ int config_parse_search_domains(
return 0;
}
int config_parse_dnssd_service_name(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) {
static const Specifier specifier_table[] = {
{ 'b', specifier_boot_id, NULL },
{ 'H', specifier_host_name, NULL },
{ 'm', specifier_machine_id, NULL },
{ 'v', specifier_kernel_release, NULL },
{}
};
DnssdService *s = userdata;
_cleanup_free_ char *name = NULL;
int r;
assert(filename);
assert(lvalue);
assert(rvalue);
assert(s);
if (isempty(rvalue)) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Service instance name can't be empty. Ignoring.");
return -EINVAL;
}
r = free_and_strdup(&s->name_template, rvalue);
if (r < 0)
return log_oom();
r = specifier_printf(s->name_template, specifier_table, NULL, &name);
if (r < 0)
return log_debug_errno(r, "Failed to replace specifiers: %m");
if (!dns_service_name_is_valid(name)) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Service instance name template renders to invalid name '%s'. Ignoring.", name);
return -EINVAL;
}
return 0;
}
int config_parse_dnssd_service_type(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) {
DnssdService *s = userdata;
int r;
assert(filename);
assert(lvalue);
assert(rvalue);
assert(s);
if (isempty(rvalue)) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Service type can't be empty. Ignoring.");
return -EINVAL;
}
if (!dnssd_srv_type_is_valid(rvalue)) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Service type is invalid. Ignoring.");
return -EINVAL;
}
r = free_and_strdup(&s->type, rvalue);
if (r < 0)
return log_oom();
return 0;
}
int config_parse_dnssd_txt(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) {
_cleanup_(dnssd_txtdata_freep) DnssdTxtData *txt_data = NULL;
DnssdService *s = userdata;
DnsTxtItem *last = NULL;
assert(filename);
assert(lvalue);
assert(rvalue);
assert(s);
if (isempty(rvalue)) {
/* Flush out collected items */
s->txt_data_items = dnssd_txtdata_free_all(s->txt_data_items);
return 0;
}
txt_data = new0(DnssdTxtData, 1);
if (!txt_data)
return log_oom();
for (;;) {
_cleanup_free_ char *word = NULL;
_cleanup_free_ char *key = NULL;
_cleanup_free_ char *value = NULL;
_cleanup_free_ void *decoded = NULL;
size_t length = 0;
DnsTxtItem *i;
int r;
r = extract_first_word(&rvalue, &word, NULL,
EXTRACT_QUOTES|EXTRACT_CUNESCAPE|EXTRACT_CUNESCAPE_RELAX);
if (r == 0)
break;
if (r == -ENOMEM)
return log_oom();
if (r < 0)
return log_syntax(unit, LOG_ERR, filename, line, r, "Invalid syntax, ignoring: %s", rvalue);
r = split_pair(word, "=", &key, &value);
if (r == -ENOMEM)
return log_oom();
if (r == -EINVAL) {
key = word;
word = NULL;
}
if (!ascii_is_valid(key)) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid syntax, ignoring: %s", key);
return -EINVAL;
}
switch (ltype) {
case DNS_TXT_ITEM_DATA:
if (value) {
r = unbase64mem(value, strlen(value), &decoded, &length);
if (r == -ENOMEM)
return log_oom();
if (r < 0)
return log_syntax(unit, LOG_ERR, filename, line, r,
"Invalid base64 encoding, ignoring: %s", value);
}
r = dnssd_txt_item_new_from_data(key, decoded, length, &i);
if (r < 0)
return log_oom();
break;
case DNS_TXT_ITEM_TEXT:
r = dnssd_txt_item_new_from_string(key, value, &i);
if (r < 0)
return log_oom();
break;
default:
assert_not_reached("Unknown type of Txt config");
}
LIST_INSERT_AFTER(items, txt_data->txt, last, i);
last = i;
}
if (!LIST_IS_EMPTY(txt_data->txt)) {
LIST_PREPEND(items, s->txt_data_items, txt_data);
txt_data = NULL;
}
return 0;
}
int manager_parse_config_file(Manager *m) {
int r;
+5
View File
@@ -44,9 +44,14 @@ int manager_parse_dns_server_string_and_warn(Manager *m, DnsServerType type, con
const struct ConfigPerfItem* resolved_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
const struct ConfigPerfItem* resolved_dnssd_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
int config_parse_dns_servers(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_search_domains(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_dns_stub_listener_mode(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_dnssd_service_name(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_dnssd_service_type(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_dnssd_txt(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
const char* dns_stub_listener_mode_to_string(DnsStubListenerMode p) _const_;
DnsStubListenerMode dns_stub_listener_mode_from_string(const char *s) _pure_;
+3 -10
View File
@@ -1698,16 +1698,9 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_fl
case DNS_TYPE_SPF: /* exactly the same as TXT */
case DNS_TYPE_TXT:
if (rdlength <= 0) {
DnsTxtItem *i;
/* RFC 6763, section 6.1 suggests to treat
* empty TXT RRs as equivalent to a TXT record
* with a single empty string. */
i = malloc0(offsetof(DnsTxtItem, data) + 1); /* for safety reasons we add an extra NUL byte */
if (!i)
return -ENOMEM;
rr->txt.items = i;
r = dns_txt_item_new_empty(&rr->txt.items);
if (r < 0)
return r;
} else {
DnsTxtItem *last = NULL;
+29
View File
@@ -182,6 +182,19 @@ bool dns_resource_key_is_address(const DnsResourceKey *key) {
return key->class == DNS_CLASS_IN && IN_SET(key->type, DNS_TYPE_A, DNS_TYPE_AAAA);
}
bool dns_resource_key_is_dnssd_ptr(const DnsResourceKey *key) {
assert(key);
/* Check if this is a PTR resource key used in
Service Instance Enumeration as described in RFC6763 p4.1. */
if (key->type != DNS_TYPE_PTR)
return false;
return dns_name_endswith(dns_resource_key_name(key), "_tcp.local") ||
dns_name_endswith(dns_resource_key_name(key), "_udp.local");
}
int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b) {
int r;
@@ -1806,6 +1819,22 @@ DnsTxtItem *dns_txt_item_copy(DnsTxtItem *first) {
return copy;
}
int dns_txt_item_new_empty(DnsTxtItem **ret) {
DnsTxtItem *i;
/* RFC 6763, section 6.1 suggests to treat
* empty TXT RRs as equivalent to a TXT record
* with a single empty string. */
i = malloc0(offsetof(DnsTxtItem, data) + 1); /* for safety reasons we add an extra NUL byte */
if (!i)
return -ENOMEM;
*ret = i;
return 0;
}
static const char* const dnssec_algorithm_table[_DNSSEC_ALGORITHM_MAX_DEFINED] = {
/* Mnemonics as listed on https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml */
[DNSSEC_ALGORITHM_RSAMD5] = "RSAMD5",
+2
View File
@@ -298,6 +298,7 @@ DnsResourceKey* dns_resource_key_ref(DnsResourceKey *key);
DnsResourceKey* dns_resource_key_unref(DnsResourceKey *key);
const char* dns_resource_key_name(const DnsResourceKey *key);
bool dns_resource_key_is_address(const DnsResourceKey *key);
bool dns_resource_key_is_dnssd_ptr(const DnsResourceKey *key);
int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b);
int dns_resource_key_match_rr(const DnsResourceKey *key, DnsResourceRecord *rr, const char *search_domain);
int dns_resource_key_match_cname_or_dname(const DnsResourceKey *key, const DnsResourceKey *cname, const char *search_domain);
@@ -341,6 +342,7 @@ int dns_resource_record_clamp_ttl(DnsResourceRecord **rr, uint32_t max_ttl);
DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i);
bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b);
DnsTxtItem *dns_txt_item_copy(DnsTxtItem *i);
int dns_txt_item_new_empty(DnsTxtItem **ret);
void dns_resource_record_hash_func(const void *i, struct siphash *state);
+144 -8
View File
@@ -27,7 +27,9 @@
#include "hostname-util.h"
#include "missing.h"
#include "random-util.h"
#include "resolved-dnssd.h"
#include "resolved-dns-scope.h"
#include "resolved-dns-zone.h"
#include "resolved-llmnr.h"
#include "resolved-mdns.h"
#include "socket-util.h"
@@ -1064,7 +1066,12 @@ static int on_announcement_timeout(sd_event_source *s, usec_t usec, void *userda
int dns_scope_announce(DnsScope *scope, bool goodbye) {
_cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
_cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
LinkAddress *a;
_cleanup_set_free_ Set *types = NULL;
DnsTransaction *t;
DnsZoneItem *z, *i;
unsigned size = 0;
Iterator iterator;
char *service_type;
int r;
if (!scope)
@@ -1073,18 +1080,85 @@ int dns_scope_announce(DnsScope *scope, bool goodbye) {
if (scope->protocol != DNS_PROTOCOL_MDNS)
return 0;
answer = dns_answer_new(scope->link->n_addresses * 2);
/* Check if we're done with probing. */
LIST_FOREACH(transactions_by_scope, t, scope->transactions)
if (DNS_TRANSACTION_IS_LIVE(t->state))
return 0;
/* Check if there're services pending conflict resolution. */
if (manager_next_dnssd_names(scope->manager))
return 0; /* we reach this point only if changing hostname didn't help */
/* Calculate answer's size. */
HASHMAP_FOREACH(z, scope->zone.by_key, iterator) {
if (z->state != DNS_ZONE_ITEM_ESTABLISHED)
continue;
if (z->rr->key->type == DNS_TYPE_PTR &&
!dns_zone_contains_name(&scope->zone, z->rr->ptr.name)) {
char key_str[DNS_RESOURCE_KEY_STRING_MAX];
log_debug("Skip PTR RR <%s> since its counterparts seem to be withdrawn", dns_resource_key_to_string(z->rr->key, key_str, sizeof key_str));
z->state = DNS_ZONE_ITEM_WITHDRAWN;
continue;
}
/* Collect service types for _services._dns-sd._udp.local RRs in a set */
if (!scope->announced &&
dns_resource_key_is_dnssd_ptr(z->rr->key)) {
if (!set_contains(types, dns_resource_key_name(z->rr->key))) {
r = set_ensure_allocated(&types, &dns_name_hash_ops);
if (r < 0)
return log_debug_errno(r, "Failed to allocate set: %m");
r = set_put(types, dns_resource_key_name(z->rr->key));
if (r < 0)
return log_debug_errno(r, "Failed to add item to set: %m");
}
}
LIST_FOREACH(by_key, i, z)
size++;
}
answer = dns_answer_new(size + set_size(types));
if (!answer)
return log_oom();
LIST_FOREACH(addresses, a, scope->link->addresses) {
r = dns_answer_add(answer, a->mdns_address_rr, 0, goodbye ? DNS_ANSWER_GOODBYE : DNS_ANSWER_CACHE_FLUSH);
if (r < 0)
return log_debug_errno(r, "Failed to add address RR to answer: %m");
/* Second iteration, actually add RRs to the answer. */
HASHMAP_FOREACH(z, scope->zone.by_key, iterator)
LIST_FOREACH (by_key, i, z) {
DnsAnswerFlags flags;
r = dns_answer_add(answer, a->mdns_ptr_rr, 0, goodbye ? DNS_ANSWER_GOODBYE : DNS_ANSWER_CACHE_FLUSH);
if (i->state != DNS_ZONE_ITEM_ESTABLISHED)
continue;
if (dns_resource_key_is_dnssd_ptr(i->rr->key))
flags = goodbye ? DNS_ANSWER_GOODBYE : 0;
else
flags = goodbye ? (DNS_ANSWER_GOODBYE|DNS_ANSWER_CACHE_FLUSH) : DNS_ANSWER_CACHE_FLUSH;
r = dns_answer_add(answer, i->rr, 0 , flags);
if (r < 0)
return log_debug_errno(r, "Failed to add RR to announce: %m");
}
/* Since all the active services are in the zone make them discoverable now. */
SET_FOREACH(service_type, types, iterator) {
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr;
rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_PTR,
"_services._dns-sd._udp.local");
rr->ptr.name = strdup(service_type);
rr->ttl = MDNS_DEFAULT_TTL;
r = dns_zone_put(&scope->zone, scope, rr, false);
if (r < 0)
return log_debug_errno(r, "Failed to add PTR RR to answer: %m");
log_warning_errno(r, "Failed to add DNS-SD PTR record to MDNS zone: %m");
r = dns_answer_add(answer, rr, 0 , 0);
if (r < 0)
return log_debug_errno(r, "Failed to add RR to announce: %m");
}
if (dns_answer_isempty(answer))
@@ -1123,3 +1197,65 @@ int dns_scope_announce(DnsScope *scope, bool goodbye) {
return 0;
}
int dns_scope_add_dnssd_services(DnsScope *scope) {
Iterator i;
DnssdService *service;
DnssdTxtData *txt_data;
int r;
assert(scope);
if (hashmap_size(scope->manager->dnssd_services) == 0)
return 0;
scope->announced = false;
HASHMAP_FOREACH(service, scope->manager->dnssd_services, i) {
service->withdrawn = false;
r = dns_zone_put(&scope->zone, scope, service->ptr_rr, false);
if (r < 0)
log_warning_errno(r, "Failed to add PTR record to MDNS zone: %m");
r = dns_zone_put(&scope->zone, scope, service->srv_rr, true);
if (r < 0)
log_warning_errno(r, "Failed to add SRV record to MDNS zone: %m");
LIST_FOREACH(items, txt_data, service->txt_data_items) {
r = dns_zone_put(&scope->zone, scope, txt_data->rr, true);
if (r < 0)
log_warning_errno(r, "Failed to add TXT record to MDNS zone: %m");
}
}
return 0;
}
int dns_scope_remove_dnssd_services(DnsScope *scope) {
_cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
Iterator i;
DnssdService *service;
DnssdTxtData *txt_data;
int r;
assert(scope);
key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_PTR,
"_services._dns-sd._udp.local");
if (!key)
return log_oom();
r = dns_zone_remove_rrs_by_key(&scope->zone, key);
if (r < 0)
return r;
HASHMAP_FOREACH(service, scope->manager->dnssd_services, i) {
dns_zone_remove_rr(&scope->zone, service->ptr_rr);
dns_zone_remove_rr(&scope->zone, service->srv_rr);
LIST_FOREACH(items, txt_data, service->txt_data_items)
dns_zone_remove_rr(&scope->zone, txt_data->rr);
}
return 0;
}
+4
View File
@@ -119,3 +119,7 @@ bool dns_scope_network_good(DnsScope *s);
int dns_scope_ifindex(DnsScope *s);
int dns_scope_announce(DnsScope *scope, bool goodbye);
int dns_scope_add_dnssd_services(DnsScope *scope);
int dns_scope_remove_dnssd_services(DnsScope *scope);
+41 -1
View File
@@ -365,7 +365,7 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) {
SET_FOREACH_MOVE(z, t->notify_zone_items_done, t->notify_zone_items)
dns_zone_item_notify(z);
SWAP_TWO(t->notify_zone_items, t->notify_zone_items_done);
if (t->probing)
if (t->probing && t->state == DNS_TRANSACTION_ATTEMPTS_MAX_REACHED)
(void) dns_scope_announce(t->scope, false);
SET_FOREACH_MOVE(d, t->notify_transactions_done, t->notify_transactions)
@@ -1377,7 +1377,11 @@ static int dns_transaction_make_packet_mdns(DnsTransaction *t) {
_cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
bool add_known_answers = false;
DnsTransaction *other;
Iterator i;
DnsResourceKey *tkey;
_cleanup_set_free_ Set *keys = NULL;
unsigned qdcount;
unsigned nscount = 0;
usec_t ts;
int r;
@@ -1400,6 +1404,16 @@ static int dns_transaction_make_packet_mdns(DnsTransaction *t) {
if (dns_key_is_shared(t->key))
add_known_answers = true;
if (t->key->type == DNS_TYPE_ANY) {
r = set_ensure_allocated(&keys, &dns_resource_key_hash_ops);
if (r < 0)
return r;
r = set_put(keys, t->key);
if (r < 0)
return r;
}
/*
* For mDNS, we want to coalesce as many open queries in pending transactions into one single
* query packet on the wire as possible. To achieve that, we iterate through all pending transactions
@@ -1459,6 +1473,16 @@ static int dns_transaction_make_packet_mdns(DnsTransaction *t) {
if (dns_key_is_shared(other->key))
add_known_answers = true;
if (other->key->type == DNS_TYPE_ANY) {
r = set_ensure_allocated(&keys, &dns_resource_key_hash_ops);
if (r < 0)
return r;
r = set_put(keys, other->key);
if (r < 0)
return r;
}
}
DNS_PACKET_HEADER(p)->qdcount = htobe16(qdcount);
@@ -1470,6 +1494,22 @@ static int dns_transaction_make_packet_mdns(DnsTransaction *t) {
return r;
}
SET_FOREACH(tkey, keys, i) {
_cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
bool tentative;
r = dns_zone_lookup(&t->scope->zone, tkey, t->scope->link->ifindex, &answer, NULL, &tentative);
if (r < 0)
return r;
r = dns_packet_append_answer(p, answer);
if (r < 0)
return r;
nscount += dns_answer_size(answer);
}
DNS_PACKET_HEADER(p)->nscount = htobe16(nscount);
t->sent = p;
p = NULL;
+65 -5
View File
@@ -23,6 +23,7 @@
#include "list.h"
#include "resolved-dns-packet.h"
#include "resolved-dns-zone.h"
#include "resolved-dnssd.h"
#include "string-util.h"
/* Never allow more than 1K entries */
@@ -95,7 +96,7 @@ void dns_zone_flush(DnsZone *z) {
z->by_name = hashmap_free(z->by_name);
}
static DnsZoneItem* dns_zone_get(DnsZone *z, DnsResourceRecord *rr) {
DnsZoneItem* dns_zone_get(DnsZone *z, DnsResourceRecord *rr) {
DnsZoneItem *i;
assert(z);
@@ -119,6 +120,22 @@ void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr) {
dns_zone_item_remove_and_free(z, i);
}
int dns_zone_remove_rrs_by_key(DnsZone *z, DnsResourceKey *key) {
_cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
DnsResourceRecord *rr;
bool tentative;
int r;
r = dns_zone_lookup(z, key, 0, &answer, &soa, &tentative);
if (r < 0)
return r;
DNS_ANSWER_FOREACH(rr, answer)
dns_zone_remove_rr(z, rr);
return 0;
}
static int dns_zone_init(DnsZone *z) {
int r;
@@ -289,6 +306,24 @@ int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe) {
return 0;
}
static int dns_zone_add_authenticated_answer(DnsAnswer *a, DnsZoneItem *i, int ifindex) {
DnsAnswerFlags flags;
/* From RFC 6762, Section 10.2
* "They (the rules about when to set the cache-flush bit) apply to
* startup announcements as described in Section 8.3, "Announcing",
* and to responses generated as a result of receiving query messages."
* So, set the cache-flush bit for mDNS answers except for DNS-SD
* service enumeration PTRs described in RFC 6763, Section 4.1. */
if (i->scope->protocol == DNS_PROTOCOL_MDNS &&
!dns_resource_key_is_dnssd_ptr(i->rr->key))
flags = DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHE_FLUSH;
else
flags = DNS_ANSWER_AUTHENTICATED;
return dns_answer_add(a, i->rr, ifindex, flags);
}
int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, int ifindex, DnsAnswer **ret_answer, DnsAnswer **ret_soa, bool *ret_tentative) {
_cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
unsigned n_answer = 0;
@@ -394,7 +429,7 @@ int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, int ifindex, DnsAnswer **re
if (k < 0)
return k;
if (k > 0) {
r = dns_answer_add(answer, j->rr, ifindex, DNS_ANSWER_AUTHENTICATED);
r = dns_zone_add_authenticated_answer(answer, j, ifindex);
if (r < 0)
return r;
@@ -420,7 +455,7 @@ int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, int ifindex, DnsAnswer **re
if (j->state != DNS_ZONE_ITEM_PROBING)
tentative = false;
r = dns_answer_add(answer, j->rr, ifindex, DNS_ANSWER_AUTHENTICATED);
r = dns_zone_add_authenticated_answer(answer, j, ifindex);
if (r < 0)
return r;
}
@@ -491,6 +526,8 @@ void dns_zone_item_conflict(DnsZoneItem *i) {
/* Withdraw the conflict item */
i->state = DNS_ZONE_ITEM_WITHDRAWN;
dnssd_signal_conflict(i->scope->manager, dns_resource_key_name(i->rr->key));
/* Maybe change the hostname */
if (manager_is_own_hostname(i->scope->manager, dns_resource_key_name(i->rr->key)) > 0)
manager_next_hostname(i->scope->manager);
@@ -510,7 +547,9 @@ void dns_zone_item_notify(DnsZoneItem *i) {
bool we_lost = false;
/* The probe got a successful reply. If we so far
* weren't established we just give up. If we already
* weren't established we just give up.
*
* In LLMNR case if we already
* were established, and the peer has the
* lexicographically larger IP address we continue
* and defend it. */
@@ -518,7 +557,7 @@ void dns_zone_item_notify(DnsZoneItem *i) {
if (!IN_SET(i->state, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) {
log_debug("Got a successful probe for not yet established RR, we lost.");
we_lost = true;
} else {
} else if (i->probe_transaction->scope->protocol == DNS_PROTOCOL_LLMNR) {
assert(i->probe_transaction->received);
we_lost = memcmp(&i->probe_transaction->received->sender, &i->probe_transaction->received->destination, FAMILY_ADDRESS_SIZE(i->probe_transaction->received->family)) < 0;
if (we_lost)
@@ -580,6 +619,10 @@ int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr) {
if (dns_zone_get(zone, rr))
return 0;
/* No conflict if it is DNS-SD RR used for service enumeration. */
if (dns_resource_key_is_dnssd_ptr(rr->key))
return 0;
/* OK, somebody else has RRs for the same name. Yuck! Let's
* start probing again */
@@ -664,3 +707,20 @@ bool dns_zone_is_empty(DnsZone *zone) {
return hashmap_isempty(zone->by_key);
}
bool dns_zone_contains_name(DnsZone *z, const char *name) {
DnsZoneItem *i, *first;
first = hashmap_get(z->by_name, name);
if (!first)
return false;
LIST_FOREACH(by_name, i, first) {
if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
continue;
return true;
}
return false;
}
+3
View File
@@ -67,7 +67,9 @@ struct DnsZoneItem {
void dns_zone_flush(DnsZone *z);
int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe);
DnsZoneItem* dns_zone_get(DnsZone *z, DnsResourceRecord *rr);
void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr);
int dns_zone_remove_rrs_by_key(DnsZone *z, DnsResourceKey *key);
int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, int ifindex, DnsAnswer **answer, DnsAnswer **soa, bool *tentative);
@@ -83,3 +85,4 @@ void dns_zone_item_probe_stop(DnsZoneItem *i);
void dns_zone_dump(DnsZone *zone, FILE *f);
bool dns_zone_is_empty(DnsZone *zone);
bool dns_zone_contains_name(DnsZone *z, const char *name);
+146
View File
@@ -0,0 +1,146 @@
/***
This file is part of systemd.
Copyright 2017 Dmitry Rozhkov
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.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include "alloc-util.h"
#include "bus-util.h"
#include "resolved-dnssd.h"
#include "resolved-dnssd-bus.h"
#include "resolved-link.h"
#include "strv.h"
#include "user-util.h"
int bus_dnssd_method_unregister(sd_bus_message *message, void *userdata, sd_bus_error *error) {
DnssdService *s = userdata;
DnssdTxtData *txt_data;
Manager *m;
Iterator i;
Link *l;
int r;
assert(message);
assert(s);
m = s->manager;
r = bus_verify_polkit_async(message, CAP_SYS_ADMIN,
"org.freedesktop.resolve1.unregister-service",
NULL, false, s->originator,
&m->polkit_registry, error);
if (r < 0)
return r;
if (r == 0)
return 1; /* Polkit will call us back */
HASHMAP_FOREACH(l, m->links, i) {
if (l->mdns_ipv4_scope) {
r = dns_scope_announce(l->mdns_ipv4_scope, true);
if (r < 0)
log_warning_errno(r, "Failed to send goodbye messages in IPv4 scope: %m");
dns_zone_remove_rr(&l->mdns_ipv4_scope->zone, s->ptr_rr);
dns_zone_remove_rr(&l->mdns_ipv4_scope->zone, s->srv_rr);
LIST_FOREACH(items, txt_data, s->txt_data_items)
dns_zone_remove_rr(&l->mdns_ipv4_scope->zone, txt_data->rr);
}
if (l->mdns_ipv6_scope) {
r = dns_scope_announce(l->mdns_ipv6_scope, true);
if (r < 0)
log_warning_errno(r, "Failed to send goodbye messages in IPv6 scope: %m");
dns_zone_remove_rr(&l->mdns_ipv6_scope->zone, s->ptr_rr);
dns_zone_remove_rr(&l->mdns_ipv6_scope->zone, s->srv_rr);
LIST_FOREACH(items, txt_data, s->txt_data_items)
dns_zone_remove_rr(&l->mdns_ipv6_scope->zone, txt_data->rr);
}
}
dnssd_service_free(s);
manager_refresh_rrs(m);
return sd_bus_reply_method_return(message, NULL);
}
const sd_bus_vtable dnssd_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_METHOD("Unregister", NULL, NULL, bus_dnssd_method_unregister, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_SIGNAL("Conflicted", NULL, 0),
SD_BUS_VTABLE_END
};
int dnssd_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) {
_cleanup_free_ char *name = NULL;
Manager *m = userdata;
DnssdService *service;
int r;
assert(bus);
assert(path);
assert(interface);
assert(found);
assert(m);
r = sd_bus_path_decode(path, "/org/freedesktop/resolve1/dnssd", &name);
if (r <= 0)
return 0;
service = hashmap_get(m->dnssd_services, name);
if (!service)
return 0;
*found = service;
return 1;
}
int dnssd_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) {
_cleanup_strv_free_ char **l = NULL;
Manager *m = userdata;
DnssdService *service;
Iterator i;
unsigned c = 0;
int r;
assert(bus);
assert(path);
assert(m);
assert(nodes);
l = new0(char*, hashmap_size(m->dnssd_services) + 1);
if (!l)
return -ENOMEM;
HASHMAP_FOREACH(service, m->dnssd_services, i) {
char *p;
r = sd_bus_path_encode("/org/freedesktop/resolve1/dnssd", service->name, &p);
if (r < 0)
return r;
l[c++] = p;
}
l[c] = NULL;
*nodes = l;
l = NULL;
return 1;
}
+29
View File
@@ -0,0 +1,29 @@
#pragma once
/***
This file is part of systemd.
Copyright 2017 Dmitry Rozhkov
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.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include "sd-bus.h"
extern const sd_bus_vtable dnssd_vtable[];
int dnssd_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error);
int dnssd_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error);
int bus_dnssd_method_unregister(sd_bus_message *message, void *userdata, sd_bus_error *error);
+24
View File
@@ -0,0 +1,24 @@
%{
#include <stddef.h>
#include "conf-parser.h"
#include "resolved-conf.h"
#include "resolved-dnssd.h"
%}
struct ConfigPerfItem;
%null_strings
%language=ANSI-C
%define slot-name section_and_lvalue
%define hash-function-name resolved_dnssd_gperf_hash
%define lookup-function-name resolved_dnssd_gperf_lookup
%readonly-tables
%omit-struct-type
%struct-type
%includes
%%
Service.Name, config_parse_dnssd_service_name, 0, 0
Service.Type, config_parse_dnssd_service_type, 0, 0
Service.Port, config_parse_ip_port, 0, offsetof(DnssdService, port)
Service.Priority, config_parse_uint16, 0, offsetof(DnssdService, priority)
Service.Weight, config_parse_uint16, 0, offsetof(DnssdService, weight)
Service.TxtText, config_parse_dnssd_txt, DNS_TXT_ITEM_TEXT, 0
Service.TxtData, config_parse_dnssd_txt, DNS_TXT_ITEM_DATA, 0
+387
View File
@@ -0,0 +1,387 @@
/***
This file is part of systemd.
Copyright 2017 Dmitry Rozhkov
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.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include "conf-files.h"
#include "conf-parser.h"
#include "resolved-dnssd.h"
#include "resolved-dns-rr.h"
#include "resolved-manager.h"
#include "specifier.h"
#include "strv.h"
const char* const dnssd_service_dirs[] = {
"/etc/systemd/dnssd",
"/run/systemd/dnssd",
"/usr/lib/systemd/dnssd",
#ifdef HAVE_SPLIT_USR
"/lib/systemd/dnssd",
#endif
NULL
};
DnssdTxtData *dnssd_txtdata_free(DnssdTxtData *txt_data) {
if (!txt_data)
return NULL;
dns_resource_record_unref(txt_data->rr);
dns_txt_item_free_all(txt_data->txt);
return mfree(txt_data);
}
DnssdTxtData *dnssd_txtdata_free_all(DnssdTxtData *txt_data) {
DnssdTxtData *next;
if (!txt_data)
return NULL;
next = txt_data->items_next;
dnssd_txtdata_free(txt_data);
return dnssd_txtdata_free_all(next);
}
DnssdService *dnssd_service_free(DnssdService *service) {
if (!service)
return NULL;
if (service->manager)
hashmap_remove(service->manager->dnssd_services, service->name);
dns_resource_record_unref(service->ptr_rr);
dns_resource_record_unref(service->srv_rr);
dnssd_txtdata_free_all(service->txt_data_items);
free(service->filename);
free(service->name);
free(service->type);
free(service->name_template);
return mfree(service);
}
static int dnssd_service_load(Manager *manager, const char *filename) {
_cleanup_(dnssd_service_freep) DnssdService *service = NULL;
_cleanup_(dnssd_txtdata_freep) DnssdTxtData *txt_data = NULL;
char *d;
const char *dropin_dirname;
int r;
assert(manager);
assert(filename);
service = new0(DnssdService, 1);
if (!service)
return log_oom();
service->filename = strdup(filename);
if (!service->filename)
return log_oom();
service->name = strdup(basename(filename));
if (!service->name)
return log_oom();
d = endswith(service->name, ".dnssd");
if (!d)
return -EINVAL;
assert(streq(d, ".dnssd"));
*d = '\0';
dropin_dirname = strjoina(service->name, ".dnssd.d");
r = config_parse_many(filename, dnssd_service_dirs, dropin_dirname,
"Service\0",
config_item_perf_lookup, resolved_dnssd_gperf_lookup,
false, service);
if (r < 0)
return r;
if (!service->name_template) {
log_error("%s doesn't define service instance name", service->name);
return -EINVAL;
}
if (!service->type) {
log_error("%s doesn't define service type", service->name);
return -EINVAL;
}
if (LIST_IS_EMPTY(service->txt_data_items)) {
txt_data = new0(DnssdTxtData, 1);
if (!txt_data)
return log_oom();
r = dns_txt_item_new_empty(&txt_data->txt);
if (r < 0)
return r;
LIST_PREPEND(items, service->txt_data_items, txt_data);
txt_data = NULL;
}
r = hashmap_ensure_allocated(&manager->dnssd_services, &string_hash_ops);
if (r < 0)
return r;
r = hashmap_put(manager->dnssd_services, service->name, service);
if (r < 0)
return r;
service->manager = manager;
r = dnssd_update_rrs(service);
if (r < 0)
return r;
service = NULL;
return 0;
}
static int specifier_dnssd_host_name(char specifier, void *data, void *userdata, char **ret) {
DnssdService *s = (DnssdService *) userdata;
char *n;
assert(s);
assert(s->manager);
assert(s->manager->llmnr_hostname);
n = strdup(s->manager->llmnr_hostname);
if (!n)
return -ENOMEM;
*ret = n;
return 0;
}
int dnssd_render_instance_name(DnssdService *s, char **ret_name) {
static const Specifier specifier_table[] = {
{ 'b', specifier_boot_id, NULL },
{ 'H', specifier_dnssd_host_name, NULL },
{ 'm', specifier_machine_id, NULL },
{ 'v', specifier_kernel_release, NULL },
{}
};
_cleanup_free_ char *name = NULL;
int r;
assert(s);
assert(s->name_template);
r = specifier_printf(s->name_template, specifier_table, s, &name);
if (r < 0)
return log_debug_errno(r, "Failed to replace specifiers: %m");
if (!dns_service_name_is_valid(name)) {
log_debug("Service instance name '%s' is invalid.", name);
return -EINVAL;
}
*ret_name = name;
name = NULL;
return 0;
}
int dnssd_load(Manager *manager) {
_cleanup_strv_free_ char **files = NULL;
char **f;
int r;
assert(manager);
if (manager->mdns_support != RESOLVE_SUPPORT_YES)
return 0;
r = conf_files_list_strv(&files, ".dnssd", NULL, 0, dnssd_service_dirs);
if (r < 0)
return log_error_errno(r, "Failed to enumerate .dnssd files: %m");
STRV_FOREACH_BACKWARDS(f, files) {
r = dnssd_service_load(manager, *f);
if (r < 0)
log_warning_errno(r, "Failed to load '%s': %m", *f);;
}
return 0;
}
int dnssd_update_rrs(DnssdService *s) {
_cleanup_free_ char *n = NULL;
_cleanup_free_ char *service_name = NULL;
_cleanup_free_ char *full_name = NULL;
DnssdTxtData *txt_data;
int r;
assert(s);
assert(s->txt_data_items);
assert(s->manager);
s->ptr_rr = dns_resource_record_unref(s->ptr_rr);
s->srv_rr = dns_resource_record_unref(s->srv_rr);
LIST_FOREACH(items, txt_data, s->txt_data_items)
txt_data->rr = dns_resource_record_unref(txt_data->rr);
r = dnssd_render_instance_name(s, &n);
if (r < 0)
return r;
r = dns_name_concat(s->type, "local", &service_name);
if (r < 0)
return r;
r = dns_name_concat(n, service_name, &full_name);
if (r < 0)
return r;
LIST_FOREACH(items, txt_data, s->txt_data_items) {
txt_data->rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_TXT,
full_name);
if (!txt_data->rr)
goto oom;
txt_data->rr->ttl = MDNS_DEFAULT_TTL;
txt_data->rr->txt.items = dns_txt_item_copy(txt_data->txt);
if (!txt_data->rr->txt.items)
goto oom;
}
s->ptr_rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_PTR,
service_name);
if (!s->ptr_rr)
goto oom;
s->ptr_rr->ttl = MDNS_DEFAULT_TTL;
s->ptr_rr->ptr.name = strdup(full_name);
if (!s->ptr_rr->ptr.name)
goto oom;
s->srv_rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_SRV,
full_name);
if (!s->srv_rr)
goto oom;
s->srv_rr->ttl = MDNS_DEFAULT_TTL;
s->srv_rr->srv.priority = s->priority;
s->srv_rr->srv.weight = s->weight;
s->srv_rr->srv.port = s->port;
s->srv_rr->srv.name = strdup(s->manager->mdns_hostname);
if (!s->srv_rr->srv.name)
goto oom;
return 0;
oom:
LIST_FOREACH(items, txt_data, s->txt_data_items)
txt_data->rr = dns_resource_record_unref(txt_data->rr);
s->ptr_rr = dns_resource_record_unref(s->ptr_rr);
s->srv_rr = dns_resource_record_unref(s->srv_rr);
return -ENOMEM;
}
int dnssd_txt_item_new_from_string(const char *key, const char *value, DnsTxtItem **ret_item) {
size_t length;
DnsTxtItem *i;
length = strlen(key);
if (!isempty(value))
length += strlen(value) + 1; /* length of value plus '=' */
i = malloc0(offsetof(DnsTxtItem, data) + length + 1); /* for safety reasons we add an extra NUL byte */
if (!i)
return -ENOMEM;
memcpy(i->data, key, strlen(key));
if (!isempty(value)) {
memcpy(i->data + strlen(key), "=", 1);
memcpy(i->data + strlen(key) + 1, value, strlen(value));
}
i->length = length;
*ret_item = i;
i = NULL;
return 0;
}
int dnssd_txt_item_new_from_data(const char *key, const void *data, const size_t size, DnsTxtItem **ret_item) {
size_t length;
DnsTxtItem *i;
length = strlen(key);
if (size > 0)
length += size + 1; /* size of date plus '=' */
i = malloc0(offsetof(DnsTxtItem, data) + length + 1); /* for safety reasons we add an extra NUL byte */
if (!i)
return -ENOMEM;
memcpy(i->data, key, strlen(key));
if (size > 0) {
memcpy(i->data + strlen(key), "=", 1);
memcpy(i->data + strlen(key) + 1, data, size);
}
i->length = length;
*ret_item = i;
i = NULL;
return 0;
}
void dnssd_signal_conflict(Manager *manager, const char *name) {
Iterator i;
DnssdService *s;
int r;
HASHMAP_FOREACH(s, manager->dnssd_services, i) {
if (s->withdrawn)
continue;
if (dns_name_equal(dns_resource_key_name(s->srv_rr->key), name)) {
_cleanup_free_ char *path = NULL;
s->withdrawn = true;
r = sd_bus_path_encode("/org/freedesktop/resolve1/dnssd", s->name, &path);
if (r < 0) {
log_error_errno(r, "Can't get D-BUS object path: %m");
return;
}
r = sd_bus_emit_signal(manager->bus,
path,
"org.freedesktop.resolve1.DnssdService",
"Conflicted",
NULL);
if (r < 0) {
log_error_errno(r, "Cannot emit signal: %m");
return;
}
break;
}
}
}
+78
View File
@@ -0,0 +1,78 @@
#pragma once
/***
This file is part of systemd.
Copyright 2017 Dmitry Rozhkov
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.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include "list.h"
typedef struct DnssdService DnssdService;
typedef struct DnssdTxtData DnssdTxtData;
typedef struct Manager Manager;
typedef struct DnsResourceRecord DnsResourceRecord;
typedef struct DnsTxtItem DnsTxtItem;
enum {
DNS_TXT_ITEM_TEXT,
DNS_TXT_ITEM_DATA
};
struct DnssdTxtData {
DnsResourceRecord *rr;
LIST_HEAD(DnsTxtItem, txt);
LIST_FIELDS(DnssdTxtData, items);
};
struct DnssdService {
char *filename;
char *name;
char *name_template;
char *type;
uint16_t port;
uint16_t priority;
uint16_t weight;
DnsResourceRecord *ptr_rr;
DnsResourceRecord *srv_rr;
/* Section 6.8 of RFC 6763 allows having service
* instances with multiple TXT resource records. */
LIST_HEAD(DnssdTxtData, txt_data_items);
Manager *manager;
bool withdrawn:1;
uid_t originator;
};
DnssdService *dnssd_service_free(DnssdService *service);
DnssdTxtData *dnssd_txtdata_free(DnssdTxtData *txt_data);
DnssdTxtData *dnssd_txtdata_free_all(DnssdTxtData *txt_data);
DEFINE_TRIVIAL_CLEANUP_FUNC(DnssdService*, dnssd_service_free);
DEFINE_TRIVIAL_CLEANUP_FUNC(DnssdTxtData*, dnssd_txtdata_free);
int dnssd_render_instance_name(DnssdService *s, char **ret_name);
int dnssd_load(Manager *manager);
int dnssd_txt_item_new_from_string(const char *key, const char *value, DnsTxtItem **ret_item);
int dnssd_txt_item_new_from_data(const char *key, const void *value, const size_t size, DnsTxtItem **ret_item);
int dnssd_update_rrs(DnssdService *s);
void dnssd_signal_conflict(Manager *manager, const char *name);
+32
View File
@@ -191,9 +191,41 @@ void link_allocate_scopes(Link *l) {
void link_add_rrs(Link *l, bool force_remove) {
LinkAddress *a;
int r;
LIST_FOREACH(addresses, a, l->addresses)
link_address_add_rrs(a, force_remove);
if (!force_remove &&
l->mdns_support == RESOLVE_SUPPORT_YES &&
l->manager->mdns_support == RESOLVE_SUPPORT_YES) {
if (l->mdns_ipv4_scope) {
r = dns_scope_add_dnssd_services(l->mdns_ipv4_scope);
if (r < 0)
log_warning_errno(r, "Failed to add IPv4 DNS-SD services: %m");
}
if (l->mdns_ipv6_scope) {
r = dns_scope_add_dnssd_services(l->mdns_ipv6_scope);
if (r < 0)
log_warning_errno(r, "Failed to add IPv6 DNS-SD services: %m");
}
} else {
if (l->mdns_ipv4_scope) {
r = dns_scope_remove_dnssd_services(l->mdns_ipv4_scope);
if (r < 0)
log_warning_errno(r, "Failed to remove IPv4 DNS-SD services: %m");
}
if (l->mdns_ipv6_scope) {
r = dns_scope_remove_dnssd_services(l->mdns_ipv6_scope);
if (r < 0)
log_warning_errno(r, "Failed to remove IPv6 DNS-SD services: %m");
}
}
}
int link_process_rtnl(Link *l, sd_netlink_message *m) {
+73 -17
View File
@@ -41,6 +41,7 @@
#include "random-util.h"
#include "resolved-bus.h"
#include "resolved-conf.h"
#include "resolved-dnssd.h"
#include "resolved-dns-stub.h"
#include "resolved-etc-hosts.h"
#include "resolved-llmnr.h"
@@ -621,6 +622,10 @@ int manager_new(Manager **ret) {
if (r < 0)
return r;
r = dnssd_load(m);
if (r < 0)
log_warning_errno(r, "Failed to load DNS-SD configuration files: %m");
r = dns_scope_new(m, &m->unicast_scope, NULL, DNS_PROTOCOL_DNS, AF_UNSPEC);
if (r < 0)
return r;
@@ -663,6 +668,7 @@ int manager_start(Manager *m) {
Manager *manager_free(Manager *m) {
Link *l;
DnssdService *s;
if (!m)
return NULL;
@@ -719,6 +725,10 @@ Manager *manager_free(Manager *m) {
free(m->llmnr_hostname);
free(m->mdns_hostname);
while ((s = hashmap_first(m->dnssd_services)))
dnssd_service_free(s);
hashmap_free(m->dnssd_services);
dns_trust_anchor_flush(&m->trust_anchor);
manager_etc_hosts_flush(m);
@@ -1094,6 +1104,7 @@ int manager_find_ifindex(Manager *m, int family, const union in_addr_union *in_a
void manager_refresh_rrs(Manager *m) {
Iterator i;
Link *l;
DnssdService *s;
assert(m);
@@ -1102,25 +1113,27 @@ void manager_refresh_rrs(Manager *m) {
m->mdns_host_ipv4_key = dns_resource_key_unref(m->mdns_host_ipv4_key);
m->mdns_host_ipv6_key = dns_resource_key_unref(m->mdns_host_ipv6_key);
if (m->mdns_support == RESOLVE_SUPPORT_YES)
HASHMAP_FOREACH(s, m->dnssd_services, i)
if (dnssd_update_rrs(s) < 0)
log_warning("Failed to refresh DNS-SD service '%s'", s->name);
HASHMAP_FOREACH(l, m->links, i) {
link_add_rrs(l, true);
link_add_rrs(l, false);
}
}
int manager_next_hostname(Manager *m) {
static int manager_next_random_name(const char *old, char **ret_new) {
const char *p;
uint64_t u, a;
char *h, *k;
int r;
char *n;
assert(m);
p = strchr(m->llmnr_hostname, 0);
p = strchr(old, 0);
assert(p);
while (p > m->llmnr_hostname) {
if (!strchr("0123456789", p[-1]))
while (p > old) {
if (!strchr(DIGITS, p[-1]))
break;
p--;
@@ -1139,22 +1152,32 @@ int manager_next_hostname(Manager *m) {
random_bytes(&a, sizeof(a));
u += 1 + a % 10;
if (asprintf(&h, "%.*s%" PRIu64, (int) (p - m->llmnr_hostname), m->llmnr_hostname, u) < 0)
if (asprintf(&n, "%.*s%" PRIu64, (int) (p - old), old, u) < 0)
return -ENOMEM;
*ret_new = n;
return 0;
}
int manager_next_hostname(Manager *m) {
_cleanup_free_ char *h = NULL, *k = NULL;
int r;
assert(m);
r = manager_next_random_name(m->llmnr_hostname, &h);
if (r < 0)
return r;
r = dns_name_concat(h, "local", &k);
if (r < 0) {
free(h);
if (r < 0)
return r;
}
log_info("Hostname conflict, changing published hostname from '%s' to '%s'.", m->llmnr_hostname, h);
free(m->llmnr_hostname);
m->llmnr_hostname = h;
free(m->mdns_hostname);
m->mdns_hostname = k;
free_and_replace(m->llmnr_hostname, h);
free_and_replace(m->mdns_hostname, k);
manager_refresh_rrs(m);
@@ -1488,3 +1511,36 @@ void manager_cleanup_saved_user(Manager *m) {
(void) unlink(p);
}
}
bool manager_next_dnssd_names(Manager *m) {
Iterator i;
DnssdService *s;
bool tried = false;
int r;
assert(m);
HASHMAP_FOREACH(s, m->dnssd_services, i) {
_cleanup_free_ char * new_name = NULL;
if (!s->withdrawn)
continue;
r = manager_next_random_name(s->name_template, &new_name);
if (r < 0) {
log_warning_errno(r, "Failed to get new name for service '%s': %m", s->name);
continue;
}
free_and_replace(s->name_template, new_name);
s->withdrawn = false;
tried = true;
}
if (tried)
manager_refresh_rrs(m);
return tried;
}
+7
View File
@@ -102,6 +102,9 @@ struct Manager {
int mdns_ipv4_fd;
int mdns_ipv6_fd;
/* DNS-SD */
Hashmap *dnssd_services;
sd_event_source *mdns_ipv4_event_source;
sd_event_source *mdns_ipv6_event_source;
@@ -143,6 +146,8 @@ struct Manager {
sd_event_source *dns_stub_udp_event_source;
sd_event_source *dns_stub_tcp_event_source;
Hashmap *polkit_registry;
};
/* Manager */
@@ -189,3 +194,5 @@ void manager_flush_caches(Manager *m);
void manager_reset_server_features(Manager *m);
void manager_cleanup_saved_user(Manager *m);
bool manager_next_dnssd_names(Manager *m);
+178 -8
View File
@@ -22,10 +22,13 @@
#include <netinet/in.h>
#include <arpa/inet.h>
#include "alloc-util.h"
#include "fd-util.h"
#include "resolved-manager.h"
#include "resolved-mdns.h"
#define CLEAR_CACHE_FLUSH(x) (~MDNS_RR_CACHE_FLUSH & (x))
void manager_mdns_stop(Manager *m) {
assert(m);
@@ -68,10 +71,145 @@ eaddrinuse:
return 0;
}
static int mdns_rr_compare(const void *a, const void *b) {
DnsResourceRecord **x = (DnsResourceRecord**) a, **y = (DnsResourceRecord**) b;
size_t m;
int r;
assert(x);
assert(*x);
assert(y);
assert(*y);
if (CLEAR_CACHE_FLUSH((*x)->key->class) < CLEAR_CACHE_FLUSH((*y)->key->class))
return -1;
else if (CLEAR_CACHE_FLUSH((*x)->key->class) > CLEAR_CACHE_FLUSH((*y)->key->class))
return 1;
if ((*x)->key->type < (*y)->key->type)
return -1;
else if ((*x)->key->type > (*y)->key->type)
return 1;
r = dns_resource_record_to_wire_format(*x, false);
if (r < 0) {
log_warning_errno(r, "Can't wire-format RR: %m");
return 0;
}
r = dns_resource_record_to_wire_format(*y, false);
if (r < 0) {
log_warning_errno(r, "Can't wire-format RR: %m");
return 0;
}
m = MIN(DNS_RESOURCE_RECORD_RDATA_SIZE(*x), DNS_RESOURCE_RECORD_RDATA_SIZE(*y));
r = memcmp(DNS_RESOURCE_RECORD_RDATA(*x), DNS_RESOURCE_RECORD_RDATA(*y), m);
if (r != 0)
return r;
if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) < DNS_RESOURCE_RECORD_RDATA_SIZE(*y))
return -1;
else if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) > DNS_RESOURCE_RECORD_RDATA_SIZE(*y))
return 1;
return 0;
}
static int proposed_rrs_cmp(DnsResourceRecord **x, unsigned x_size, DnsResourceRecord **y, unsigned y_size) {
unsigned m;
int r;
m = MIN(x_size, y_size);
for (unsigned i = 0; i < m; i++) {
r = mdns_rr_compare(&x[i], &y[i]);
if (r != 0)
return r;
}
if (x_size < y_size)
return -1;
if (x_size > y_size)
return 1;
return 0;
}
static int mdns_packet_extract_matching_rrs(DnsPacket *p, DnsResourceKey *key, DnsResourceRecord ***ret_rrs) {
_cleanup_free_ DnsResourceRecord **list = NULL;
unsigned n = 0, size = 0;
int r;
assert(p);
assert(key);
assert(ret_rrs);
assert_return(DNS_PACKET_NSCOUNT(p) > 0, -EINVAL);
for (unsigned i = DNS_PACKET_ANCOUNT(p); i < (DNS_PACKET_ANCOUNT(p) + DNS_PACKET_NSCOUNT(p)); i++) {
r = dns_resource_key_match_rr(key, p->answer->items[i].rr, NULL);
if (r < 0)
return r;
if (r > 0)
size++;
}
if (size == 0)
return 0;
list = new(DnsResourceRecord *, size);
if (!list)
return -ENOMEM;
for (unsigned i = DNS_PACKET_ANCOUNT(p); i < (DNS_PACKET_ANCOUNT(p) + DNS_PACKET_NSCOUNT(p)); i++) {
r = dns_resource_key_match_rr(key, p->answer->items[i].rr, NULL);
if (r < 0)
return r;
if (r > 0)
list[n++] = p->answer->items[i].rr;
}
assert(n == size);
qsort_safe(list, size, sizeof(DnsResourceRecord*), mdns_rr_compare);
*ret_rrs = list;
list = NULL;
return size;
}
static int mdns_do_tiebreak(DnsResourceKey *key, DnsAnswer *answer, DnsPacket *p) {
_cleanup_free_ DnsResourceRecord **our = NULL, **remote = NULL;
DnsResourceRecord *rr;
unsigned i = 0;
unsigned size;
int r;
size = dns_answer_size(answer);
our = new(DnsResourceRecord *, size);
if (!our)
return -ENOMEM;
DNS_ANSWER_FOREACH(rr, answer)
our[i++] = rr;
qsort_safe(our, size, sizeof(DnsResourceRecord*), mdns_rr_compare);
r = mdns_packet_extract_matching_rrs(p, key, &remote);
if (r < 0)
return r;
assert(r > 0);
if (proposed_rrs_cmp(remote, r, our, size) > 0)
return 1;
return 0;
}
static int mdns_scope_process_query(DnsScope *s, DnsPacket *p) {
_cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
_cleanup_(dns_answer_unrefp) DnsAnswer *full_answer = NULL;
_cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL;
DnsResourceKey *key = NULL;
DnsResourceRecord *rr;
bool tentative = false;
int r;
@@ -82,17 +220,49 @@ static int mdns_scope_process_query(DnsScope *s, DnsPacket *p) {
if (r < 0)
return log_debug_errno(r, "Failed to extract resource records from incoming packet: %m");
/* TODO: there might be more than one question in mDNS queries. */
assert_return((dns_question_size(p->question) > 0), -EINVAL);
key = p->question->keys[0];
r = dns_zone_lookup(&s->zone, key, 0, &answer, &soa, &tentative);
if (r < 0)
return log_debug_errno(r, "Failed to lookup key: %m");
if (r == 0)
DNS_QUESTION_FOREACH(key, p->question) {
_cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
r = dns_zone_lookup(&s->zone, key, 0, &answer, &soa, &tentative);
if (r < 0)
return log_debug_errno(r, "Failed to lookup key: %m");
if (tentative && DNS_PACKET_NSCOUNT(p) > 0) {
/*
* A race condition detected with the probe packet from
* a remote host.
* Do simultaneous probe tiebreaking as described in
* RFC 6762, Section 8.2. In case we lost don't reply
* the question and withdraw conflicting RRs.
*/
r = mdns_do_tiebreak(key, answer, p);
if (r < 0)
return log_debug_errno(r, "Failed to do tiebreaking");
if (r > 0) { /* we lost */
DNS_ANSWER_FOREACH(rr, answer) {
DnsZoneItem *i;
i = dns_zone_get(&s->zone, rr);
if (i)
dns_zone_item_conflict(i);
}
continue;
}
}
r = dns_answer_extend(&full_answer, answer);
if (r < 0)
return log_debug_errno(r, "Failed to extend answer: %m");
}
if (dns_answer_isempty(full_answer))
return 0;
r = dns_scope_make_reply_packet(s, DNS_PACKET_ID(p), DNS_RCODE_SUCCESS, NULL, answer, NULL, false, &reply);
r = dns_scope_make_reply_packet(s, DNS_PACKET_ID(p), DNS_RCODE_SUCCESS, NULL, full_answer, NULL, false, &reply);
if (r < 0)
return log_debug_errno(r, "Failed to build reply packet: %m");
+6
View File
@@ -959,6 +959,12 @@ bool dns_srv_type_is_valid(const char *name) {
return c == 2; /* exactly two labels */
}
bool dnssd_srv_type_is_valid(const char *name) {
return dns_srv_type_is_valid(name) &&
((dns_name_endswith(name, "_tcp") > 0) ||
(dns_name_endswith(name, "_udp") > 0)); /* Specific to DNS-SD. RFC 6763, Section 7 */
}
bool dns_service_name_is_valid(const char *name) {
size_t l;
+1
View File
@@ -96,6 +96,7 @@ bool dns_name_is_single_label(const char *name);
int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, bool canonical);
bool dns_srv_type_is_valid(const char *name);
bool dnssd_srv_type_is_valid(const char *name);
bool dns_service_name_is_valid(const char *name);
int dns_service_join(const char *name, const char *type, const char *domain, char **ret);
+25
View File
@@ -427,6 +427,30 @@ static void test_dns_srv_type_is_valid(void) {
assert_se(!dns_srv_type_is_valid("_piep._foo._udp"));
}
static void test_dnssd_srv_type_is_valid(void) {
assert_se(dnssd_srv_type_is_valid("_http._tcp"));
assert_se(dnssd_srv_type_is_valid("_foo-bar._tcp"));
assert_se(dnssd_srv_type_is_valid("_w._udp"));
assert_se(dnssd_srv_type_is_valid("_a800._tcp"));
assert_se(dnssd_srv_type_is_valid("_a-800._tcp"));
assert_se(!dnssd_srv_type_is_valid(NULL));
assert_se(!dnssd_srv_type_is_valid(""));
assert_se(!dnssd_srv_type_is_valid("x"));
assert_se(!dnssd_srv_type_is_valid("_foo"));
assert_se(!dnssd_srv_type_is_valid("_tcp"));
assert_se(!dnssd_srv_type_is_valid("_"));
assert_se(!dnssd_srv_type_is_valid("_foo."));
assert_se(!dnssd_srv_type_is_valid("_föo._tcp"));
assert_se(!dnssd_srv_type_is_valid("_f\no._tcp"));
assert_se(!dnssd_srv_type_is_valid("_800._tcp"));
assert_se(!dnssd_srv_type_is_valid("_-800._tcp"));
assert_se(!dnssd_srv_type_is_valid("_-foo._tcp"));
assert_se(!dnssd_srv_type_is_valid("_piep._foo._udp"));
assert_se(!dnssd_srv_type_is_valid("_foo._unknown"));
}
static void test_dns_service_join_one(const char *a, const char *b, const char *c, int r, const char *d) {
_cleanup_free_ char *x = NULL, *y = NULL, *z = NULL, *t = NULL;
@@ -699,6 +723,7 @@ int main(int argc, char *argv[]) {
test_dns_name_to_wire_format();
test_dns_service_name_is_valid();
test_dns_srv_type_is_valid();
test_dnssd_srv_type_is_valid();
test_dns_service_join();
test_dns_service_split();
test_dns_name_change_suffix();