From 9c5e7b73f7340e64f831aba7e90b9265f049ca96 Mon Sep 17 00:00:00 2001 From: Dmitry Rozhkov Date: Fri, 29 Sep 2017 14:13:47 +0300 Subject: [PATCH 01/16] resolved: answer all mDNS questions found in packet According to p5.3 of RFC6762 (Multicast DNS) one mDNS query message can contain more than one question sections. Generate answers for all found questions and put them to a reply message. --- src/resolve/resolved-mdns.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/resolve/resolved-mdns.c b/src/resolve/resolved-mdns.c index a492937176..1cc1195717 100644 --- a/src/resolve/resolved-mdns.c +++ b/src/resolve/resolved-mdns.c @@ -69,7 +69,7 @@ eaddrinuse: } 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; bool tentative = false; @@ -82,17 +82,24 @@ 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"); + + 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"); From 154ae08781aaba9ea30c2ec8f396c7898332ffca Mon Sep 17 00:00:00 2001 From: Dmitry Rozhkov Date: Tue, 10 Oct 2017 15:58:45 +0300 Subject: [PATCH 02/16] shared: introduce dnssd_srv_type_is_valid() function --- src/shared/dns-domain.c | 6 ++++++ src/shared/dns-domain.h | 1 + src/test/test-dns-domain.c | 25 +++++++++++++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c index 670aa48800..bdef67dc4f 100644 --- a/src/shared/dns-domain.c +++ b/src/shared/dns-domain.c @@ -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; diff --git a/src/shared/dns-domain.h b/src/shared/dns-domain.h index af74b97484..cd3dbb7a89 100644 --- a/src/shared/dns-domain.h +++ b/src/shared/dns-domain.h @@ -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); diff --git a/src/test/test-dns-domain.c b/src/test/test-dns-domain.c index 5f669df051..3105c14ee6 100644 --- a/src/test/test-dns-domain.c +++ b/src/test/test-dns-domain.c @@ -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(); From ebb779dc23bc61886a4a4d4cabece871b058f051 Mon Sep 17 00:00:00 2001 From: Dmitry Rozhkov Date: Fri, 24 Nov 2017 15:24:57 +0200 Subject: [PATCH 03/16] resolved: inroduce dns_txt_item_new_empty() function --- src/resolve/resolved-dns-packet.c | 13 +++---------- src/resolve/resolved-dns-rr.c | 16 ++++++++++++++++ src/resolve/resolved-dns-rr.h | 1 + 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index 40f35475fd..d7a839a823 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -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; diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c index 898d5b3c36..eaea30f227 100644 --- a/src/resolve/resolved-dns-rr.c +++ b/src/resolve/resolved-dns-rr.c @@ -1806,6 +1806,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", diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h index 9faf4debe7..7a12fd5c8c 100644 --- a/src/resolve/resolved-dns-rr.h +++ b/src/resolve/resolved-dns-rr.h @@ -341,6 +341,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); From 6501dd31a7c6f74f539ada652f6c7c83c2fe4e0a Mon Sep 17 00:00:00 2001 From: Dmitry Rozhkov Date: Wed, 4 Oct 2017 11:19:16 +0300 Subject: [PATCH 04/16] resolved: add enablers for DNS-SD Introduce network services loaded from .dnssd files that can be used for server-side DNS-SD implementation in systemd-resolved. --- src/resolve/meson.build | 10 +- src/resolve/resolved-conf.c | 150 +++++++++++++++ src/resolve/resolved-conf.h | 5 + src/resolve/resolved-dnssd-gperf.gperf | 24 +++ src/resolve/resolved-dnssd.c | 245 +++++++++++++++++++++++++ src/resolve/resolved-dnssd.h | 57 ++++++ src/resolve/resolved-manager.c | 10 + src/resolve/resolved-manager.h | 3 + 8 files changed, 503 insertions(+), 1 deletion(-) create mode 100644 src/resolve/resolved-dnssd-gperf.gperf create mode 100644 src/resolve/resolved-dnssd.c create mode 100644 src/resolve/resolved-dnssd.h diff --git a/src/resolve/meson.build b/src/resolve/meson.build index dbc63f15c2..2a9e9aa7f8 100644 --- a/src/resolve/meson.build +++ b/src/resolve/meson.build @@ -35,6 +35,8 @@ systemd_resolved_only_sources = files(''' resolved.c resolved-manager.c resolved-manager.h + resolved-dnssd.c + resolved-dnssd.h resolved-conf.c resolved-conf.h resolved-resolv-conf.c @@ -131,8 +133,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) diff --git a/src/resolve/resolved-conf.c b/src/resolve/resolved-conf.c index 00135b107c..71188fcec1 100644 --- a/src/resolve/resolved-conf.c +++ b/src/resolve/resolved-conf.c @@ -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,152 @@ 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) { + DnssdService *s = userdata; + DnsTxtItem *last = NULL; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(s); + + /* TODO: Since RFC6763 allows more than one TXT RR per service + * this s->txt field should be implemented as a list + * of DnsTxtItem lists. */ + s->txt = dns_txt_item_free_all(s->txt); + + if (isempty(rvalue)) + return 0; + + 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, s->txt, last, i); + last = i; + } + + return 0; +} + int manager_parse_config_file(Manager *m) { int r; diff --git a/src/resolve/resolved-conf.h b/src/resolve/resolved-conf.h index 9b0ffb6483..4ebb45f3aa 100644 --- a/src/resolve/resolved-conf.h +++ b/src/resolve/resolved-conf.h @@ -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_; diff --git a/src/resolve/resolved-dnssd-gperf.gperf b/src/resolve/resolved-dnssd-gperf.gperf new file mode 100644 index 0000000000..2780b856bf --- /dev/null +++ b/src/resolve/resolved-dnssd-gperf.gperf @@ -0,0 +1,24 @@ +%{ +#include +#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 diff --git a/src/resolve/resolved-dnssd.c b/src/resolve/resolved-dnssd.c new file mode 100644 index 0000000000..e025a53494 --- /dev/null +++ b/src/resolve/resolved-dnssd.c @@ -0,0 +1,245 @@ +/*** + 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 . +***/ + +#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 +}; + +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); + dns_resource_record_unref(service->txt_rr); + + free(service->filename); + free(service->name); + free(service->type); + free(service->name_template); + dns_txt_item_free_all(service->txt); + + return mfree(service); +} + +static int dnssd_service_load(Manager *manager, const char *filename) { + _cleanup_(dnssd_service_freep) DnssdService *service = 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 (!service->txt) { + r = dns_txt_item_new_empty(&service->txt); + if (r < 0) + return r; + } + + 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; + + 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_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; +} diff --git a/src/resolve/resolved-dnssd.h b/src/resolve/resolved-dnssd.h new file mode 100644 index 0000000000..0c0e1cddc0 --- /dev/null +++ b/src/resolve/resolved-dnssd.h @@ -0,0 +1,57 @@ +#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 . +***/ + +typedef struct DnssdService DnssdService; + +typedef struct Manager Manager; +typedef struct DnsResourceRecord DnsResourceRecord; +typedef struct DnsTxtItem DnsTxtItem; + +enum { + DNS_TXT_ITEM_TEXT, + DNS_TXT_ITEM_DATA +}; + +struct DnssdService { + char *filename; + char *name; + char *name_template; + char *type; + uint16_t port; + uint16_t priority; + uint16_t weight; + DnsTxtItem *txt; + + DnsResourceRecord *ptr_rr; + DnsResourceRecord *srv_rr; + DnsResourceRecord *txt_rr; + + Manager *manager; +}; + +DnssdService *dnssd_service_free(DnssdService *service); + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnssdService*, dnssd_service_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); diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index 4292383d21..91bed0a379 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -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); diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index c9ff788510..e16d6a4732 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -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; From 6db6a4642ebd202fda2d8cffeb0b8bd62ed043e2 Mon Sep 17 00:00:00 2001 From: Dmitry Rozhkov Date: Wed, 4 Oct 2017 11:34:39 +0300 Subject: [PATCH 05/16] resolved: put DNS-SD records to mDNS-enabled zones. --- src/resolve/resolved-dns-scope.c | 56 +++++++++++++++++++++++++ src/resolve/resolved-dns-scope.h | 4 ++ src/resolve/resolved-dns-zone.c | 16 +++++++ src/resolve/resolved-dns-zone.h | 1 + src/resolve/resolved-dnssd.c | 71 ++++++++++++++++++++++++++++++++ src/resolve/resolved-dnssd.h | 1 + src/resolve/resolved-link.c | 32 ++++++++++++++ src/resolve/resolved-manager.c | 6 +++ 8 files changed, 187 insertions(+) diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index a9071ee73e..5b7ac0ea25 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -27,6 +27,7 @@ #include "hostname-util.h" #include "missing.h" #include "random-util.h" +#include "resolved-dnssd.h" #include "resolved-dns-scope.h" #include "resolved-llmnr.h" #include "resolved-mdns.h" @@ -1123,3 +1124,58 @@ int dns_scope_announce(DnsScope *scope, bool goodbye) { return 0; } + +int dns_scope_add_dnssd_services(DnsScope *scope) { + Iterator i; + DnssdService *service; + 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) { + 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"); + + r = dns_zone_put(&scope->zone, scope, service->txt_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; + 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); + dns_zone_remove_rr(&scope->zone, service->txt_rr); + } + + return 0; +} diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h index d3da8e54ea..d46ccd884c 100644 --- a/src/resolve/resolved-dns-scope.h +++ b/src/resolve/resolved-dns-scope.h @@ -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); diff --git a/src/resolve/resolved-dns-zone.c b/src/resolve/resolved-dns-zone.c index 2c32bb4d4f..8080c3e7a1 100644 --- a/src/resolve/resolved-dns-zone.c +++ b/src/resolve/resolved-dns-zone.c @@ -119,6 +119,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; diff --git a/src/resolve/resolved-dns-zone.h b/src/resolve/resolved-dns-zone.h index f654537b45..55702fb840 100644 --- a/src/resolve/resolved-dns-zone.h +++ b/src/resolve/resolved-dns-zone.h @@ -68,6 +68,7 @@ void dns_zone_flush(DnsZone *z); int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe); 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); diff --git a/src/resolve/resolved-dnssd.c b/src/resolve/resolved-dnssd.c index e025a53494..f565460e9c 100644 --- a/src/resolve/resolved-dnssd.c +++ b/src/resolve/resolved-dnssd.c @@ -119,6 +119,10 @@ static int dnssd_service_load(Manager *manager, const char *filename) { service->manager = manager; + r = dnssd_update_rrs(service); + if (r < 0) + return r; + service = NULL; return 0; @@ -192,6 +196,73 @@ int dnssd_load(Manager *manager) { 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; + int r; + + assert(s); + assert(s->txt); + assert(s->manager); + + s->ptr_rr = dns_resource_record_unref(s->ptr_rr); + s->srv_rr = dns_resource_record_unref(s->srv_rr); + s->txt_rr = dns_resource_record_unref(s->txt_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; + + s->txt_rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_TXT, + full_name); + if (!s->txt_rr) + goto oom; + + s->txt_rr->ttl = MDNS_DEFAULT_TTL; + s->txt_rr->txt.items = dns_txt_item_copy(s->txt); + if (!s->txt_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: + s->txt_rr = dns_resource_record_unref(s->txt_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; diff --git a/src/resolve/resolved-dnssd.h b/src/resolve/resolved-dnssd.h index 0c0e1cddc0..b723698d3a 100644 --- a/src/resolve/resolved-dnssd.h +++ b/src/resolve/resolved-dnssd.h @@ -55,3 +55,4 @@ 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); diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c index a0128aace0..ed7bdc848a 100644 --- a/src/resolve/resolved-link.c +++ b/src/resolve/resolved-link.c @@ -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) { diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index 91bed0a379..2813f6ee40 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -1104,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); @@ -1112,6 +1113,11 @@ 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); From a2bf8a19cc0a87ba381451fecded15f75a1c0a7c Mon Sep 17 00:00:00 2001 From: Dmitry Rozhkov Date: Wed, 4 Oct 2017 10:07:44 +0300 Subject: [PATCH 06/16] resolved: announce DNS-SD records in mDNS scopes --- src/resolve/resolved-dns-rr.c | 13 +++++ src/resolve/resolved-dns-rr.h | 1 + src/resolve/resolved-dns-scope.c | 83 +++++++++++++++++++++++++++++--- src/resolve/resolved-dns-zone.c | 17 +++++++ src/resolve/resolved-dns-zone.h | 1 + 5 files changed, 107 insertions(+), 8 deletions(-) diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c index eaea30f227..4a0c1d6a35 100644 --- a/src/resolve/resolved-dns-rr.c +++ b/src/resolve/resolved-dns-rr.c @@ -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; diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h index 7a12fd5c8c..3f3d46e6b3 100644 --- a/src/resolve/resolved-dns-rr.h +++ b/src/resolve/resolved-dns-rr.h @@ -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); diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index 5b7ac0ea25..dcef094b07 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -29,6 +29,7 @@ #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" @@ -1065,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; + unsigned size = 0; + Iterator iterator; + char *service_type; int r; if (!scope) @@ -1074,18 +1080,79 @@ 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; + + /* 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"); + } + } + + 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) { + DnsAnswerFlags flags; - r = dns_answer_add(answer, a->mdns_ptr_rr, 0, goodbye ? DNS_ANSWER_GOODBYE : DNS_ANSWER_CACHE_FLUSH); + if (z->state != DNS_ZONE_ITEM_ESTABLISHED) + continue; + + if (dns_resource_key_is_dnssd_ptr(z->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, z->rr, 0 , flags); if (r < 0) - return log_debug_errno(r, "Failed to add PTR RR to answer: %m"); + 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) + 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)) diff --git a/src/resolve/resolved-dns-zone.c b/src/resolve/resolved-dns-zone.c index 8080c3e7a1..53a49285a8 100644 --- a/src/resolve/resolved-dns-zone.c +++ b/src/resolve/resolved-dns-zone.c @@ -680,3 +680,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; +} diff --git a/src/resolve/resolved-dns-zone.h b/src/resolve/resolved-dns-zone.h index 55702fb840..896f229855 100644 --- a/src/resolve/resolved-dns-zone.h +++ b/src/resolve/resolved-dns-zone.h @@ -84,3 +84,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); From 008d4ab7a2d06d719fe9d054a0b1bc9495e3a797 Mon Sep 17 00:00:00 2001 From: Dmitry Rozhkov Date: Fri, 13 Oct 2017 12:19:21 +0300 Subject: [PATCH 07/16] resolved: make rfc4795-specific code run for LLMNR only RFC6762 specifies different procedure for conflict resolution and the code is not applicable for the mDNS case. --- src/resolve/resolved-dns-zone.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/resolve/resolved-dns-zone.c b/src/resolve/resolved-dns-zone.c index 53a49285a8..4e543ce3d8 100644 --- a/src/resolve/resolved-dns-zone.c +++ b/src/resolve/resolved-dns-zone.c @@ -526,7 +526,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. */ @@ -534,7 +536,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) From 748a548e1b4fbecc7d7de12a6c0e87979810ac46 Mon Sep 17 00:00:00 2001 From: Dmitry Rozhkov Date: Tue, 31 Oct 2017 10:34:58 +0200 Subject: [PATCH 08/16] resolved: set cache-flush bit on mDNS responses 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 enumerattion PTRs described in RFC 6763, Section 4.1. --- src/resolve/resolved-dns-zone.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/resolve/resolved-dns-zone.c b/src/resolve/resolved-dns-zone.c index 4e543ce3d8..55db352959 100644 --- a/src/resolve/resolved-dns-zone.c +++ b/src/resolve/resolved-dns-zone.c @@ -305,6 +305,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; @@ -410,7 +428,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; @@ -436,7 +454,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; } From 0d5ee47dfd225bf73a7f1c67d1c4c9b76baa6bea Mon Sep 17 00:00:00 2001 From: Dmitry Rozhkov Date: Mon, 16 Oct 2017 17:25:17 +0300 Subject: [PATCH 09/16] resolved: add authority section to mDNS probing queries According to RFC 6762 Section 8.2 "Simultaneous Probe Tiebreaking" probing queries' Authority Section is populated with proposed resource records in order to resolve possible race conditions. --- src/resolve/resolved-dns-transaction.c | 40 ++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index a66d605405..ea81056119 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -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; From 8d67e72cbe240912e5408bc2a30d5f303678c2b8 Mon Sep 17 00:00:00 2001 From: Dmitry Rozhkov Date: Tue, 17 Oct 2017 11:35:06 +0300 Subject: [PATCH 10/16] resolved: detect and handle mDNS race condition upon probing As discussed in RFC 6762, Section 8.2 a race condition may happen when two hosts are probing for the same name simultaniously. Detect and handle such race conditions. --- src/resolve/resolved-dns-transaction.c | 2 +- src/resolve/resolved-dns-zone.c | 2 +- src/resolve/resolved-dns-zone.h | 1 + src/resolve/resolved-mdns.c | 163 +++++++++++++++++++++++++ 4 files changed, 166 insertions(+), 2 deletions(-) diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index ea81056119..792a16d693 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -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) diff --git a/src/resolve/resolved-dns-zone.c b/src/resolve/resolved-dns-zone.c index 55db352959..ca92699f82 100644 --- a/src/resolve/resolved-dns-zone.c +++ b/src/resolve/resolved-dns-zone.c @@ -95,7 +95,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); diff --git a/src/resolve/resolved-dns-zone.h b/src/resolve/resolved-dns-zone.h index 896f229855..c9c7440351 100644 --- a/src/resolve/resolved-dns-zone.h +++ b/src/resolve/resolved-dns-zone.h @@ -67,6 +67,7 @@ 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); diff --git a/src/resolve/resolved-mdns.c b/src/resolve/resolved-mdns.c index 1cc1195717..38e2c54227 100644 --- a/src/resolve/resolved-mdns.c +++ b/src/resolve/resolved-mdns.c @@ -22,10 +22,13 @@ #include #include +#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 *full_answer = NULL; _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL; DnsResourceKey *key = NULL; + DnsResourceRecord *rr; bool tentative = false; int r; @@ -91,6 +229,31 @@ static int mdns_scope_process_query(DnsScope *s, DnsPacket *p) { 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"); From c3036641f04608bba39e8fe3d482b735a473b4b0 Mon Sep 17 00:00:00 2001 From: Dmitry Rozhkov Date: Mon, 23 Oct 2017 14:46:13 +0300 Subject: [PATCH 11/16] resolved: implement D-Bus API for DNS-SD --- src/libsystemd/sd-bus/bus-common-errors.h | 2 + src/resolve/meson.build | 2 + src/resolve/resolved-bus.c | 184 ++++++++++++++++++++++ src/resolve/resolved-dns-scope.c | 2 + src/resolve/resolved-dns-zone.c | 3 + src/resolve/resolved-dnssd-bus.c | 132 ++++++++++++++++ src/resolve/resolved-dnssd-bus.h | 29 ++++ src/resolve/resolved-dnssd.c | 35 ++++ src/resolve/resolved-dnssd.h | 3 + 9 files changed, 392 insertions(+) create mode 100644 src/resolve/resolved-dnssd-bus.c create mode 100644 src/resolve/resolved-dnssd-bus.h diff --git a/src/libsystemd/sd-bus/bus-common-errors.h b/src/libsystemd/sd-bus/bus-common-errors.h index 7317b174b3..42c0f528f0 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.h +++ b/src/libsystemd/sd-bus/bus-common-errors.h @@ -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" diff --git a/src/resolve/meson.build b/src/resolve/meson.build index 2a9e9aa7f8..b01c46cf65 100644 --- a/src/resolve/meson.build +++ b/src/resolve/meson.build @@ -37,6 +37,8 @@ systemd_resolved_only_sources = files(''' 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 diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index 700a7d5ee0..bcfe434e40 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -25,7 +25,10 @@ #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 "utf8.h" static int reply_query_state(DnsQuery *q) { @@ -1581,6 +1584,177 @@ 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_(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; + DnsTxtItem *last = NULL; + const char *name; + const char *name_template; + const char *type; + 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"); + + service = new0(DnssdService, 1); + if (!service) + return log_oom(); + + 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, "{say}"); + if (r < 0) + return sd_bus_error_set_errno(error, r); + + 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 sd_bus_error_set_errno(error, r); + + if (strlen_ptr(key) == 0) + 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 sd_bus_error_set_errno(error, r); + + r = dnssd_txt_item_new_from_data(key, value, size, &i); + if (r < 0) + return sd_bus_error_set_errno(error, r); + + LIST_INSERT_AFTER(items, service->txt, last, i); + last = i; + } + + r = sd_bus_message_exit_container(message); + if (r < 0) + return sd_bus_error_set_errno(error, r); + + if (!service->txt) { + r = dns_txt_item_new_empty(&service->txt); + if (r < 0) + return sd_bus_error_set_errno(error, r); + } + + 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 +1783,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", "sssqqqa{say}", "o", bus_method_register_service, 0), + SD_BUS_METHOD("UnregisterService", "o", NULL, bus_method_unregister_service, 0), SD_BUS_VTABLE_END, }; @@ -1681,6 +1857,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"); diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index dcef094b07..5448575d5a 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -1205,6 +1205,8 @@ int dns_scope_add_dnssd_services(DnsScope *scope) { 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"); diff --git a/src/resolve/resolved-dns-zone.c b/src/resolve/resolved-dns-zone.c index ca92699f82..0ed7166e01 100644 --- a/src/resolve/resolved-dns-zone.c +++ b/src/resolve/resolved-dns-zone.c @@ -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 */ @@ -525,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); diff --git a/src/resolve/resolved-dnssd-bus.c b/src/resolve/resolved-dnssd-bus.c new file mode 100644 index 0000000000..7a9f0bccbb --- /dev/null +++ b/src/resolve/resolved-dnssd-bus.c @@ -0,0 +1,132 @@ +/*** + 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 . +***/ + +#include "alloc-util.h" +#include "resolved-dnssd.h" +#include "resolved-dnssd-bus.h" +#include "resolved-link.h" +#include "strv.h" + +int bus_dnssd_method_unregister(sd_bus_message *message, void *userdata, sd_bus_error *error) { + DnssdService *s = userdata; + Manager *m; + Iterator i; + Link *l; + int r; + + assert(message); + assert(s); + + m = s->manager; + + 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); + dns_zone_remove_rr(&l->mdns_ipv4_scope->zone, s->txt_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); + dns_zone_remove_rr(&l->mdns_ipv6_scope->zone, s->txt_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, 0), + 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; +} diff --git a/src/resolve/resolved-dnssd-bus.h b/src/resolve/resolved-dnssd-bus.h new file mode 100644 index 0000000000..ab915c86f9 --- /dev/null +++ b/src/resolve/resolved-dnssd-bus.h @@ -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 . +***/ + +#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); diff --git a/src/resolve/resolved-dnssd.c b/src/resolve/resolved-dnssd.c index f565460e9c..6d0ab39a9d 100644 --- a/src/resolve/resolved-dnssd.c +++ b/src/resolve/resolved-dnssd.c @@ -314,3 +314,38 @@ int dnssd_txt_item_new_from_data(const char *key, const void *data, const size_t 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; + } + } +} diff --git a/src/resolve/resolved-dnssd.h b/src/resolve/resolved-dnssd.h index b723698d3a..4040190cf4 100644 --- a/src/resolve/resolved-dnssd.h +++ b/src/resolve/resolved-dnssd.h @@ -45,6 +45,8 @@ struct DnssdService { DnsResourceRecord *txt_rr; Manager *manager; + + bool withdrawn:1; }; DnssdService *dnssd_service_free(DnssdService *service); @@ -56,3 +58,4 @@ 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); From 6e73d91ec8bcab9e120e8d308dd466d2a144577d Mon Sep 17 00:00:00 2001 From: Dmitry Rozhkov Date: Wed, 4 Oct 2017 15:56:19 +0300 Subject: [PATCH 12/16] resolved: add man page for systemd.dnssd --- man/rules/meson.build | 1 + man/systemd-resolve.xml | 1 + man/systemd.dnssd.xml | 250 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 252 insertions(+) create mode 100644 man/systemd.dnssd.xml diff --git a/man/rules/meson.build b/man/rules/meson.build index 7385658d6a..499fe6d19e 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -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', [], ''], diff --git a/man/systemd-resolve.xml b/man/systemd-resolve.xml index 835521aa74..c4af2d8662 100644 --- a/man/systemd-resolve.xml +++ b/man/systemd-resolve.xml @@ -404,6 +404,7 @@ _443._tcp.fedoraproject.org IN TLSA 0 0 1 19400be5b7a31fb733917700789d2f0a2471c0 systemd1, systemd-resolved.service8 + systemd.dnssd5 diff --git a/man/systemd.dnssd.xml b/man/systemd.dnssd.xml new file mode 100644 index 0000000000..4e30338bf0 --- /dev/null +++ b/man/systemd.dnssd.xml @@ -0,0 +1,250 @@ + + + + + + + + + systemd.dnssd + systemd + + + + Developer + Dmitry + Rozhkov + dmitry.rozhkov@intel.com + + + + + + systemd.dnssd + 5 + + + + systemd.dnssd + DNS-SD configuration + + + + network_service.dnssd + + + + Description + + DNS-SD setup is performed by + systemd-resolved8. + + + The main network service file must have the extension .dnssd; other + extensions are ignored. + + The .dnssd files are read from the files located in the system + network directory /usr/lib/systemd/dnssd, the volatile runtime network + directory /run/systemd/dnssd and the local administration network + directory /etc/systemd/dnssd. 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 /etc + have the highest priority, files in /run take precedence over files with + the same name in /usr/lib. This can be used to override a system-supplied + configuration file with a local file if needed. + + Along with the network service file foo.dnssd, a "drop-in" directory + foo.dnssd.d/ may exist. All files with the suffix + .conf 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. + + In addition to /etc/systemd/dnssd, drop-in .d + directories can be placed in /usr/lib/systemd/dnssd or + /run/systemd/dnssd directories. Drop-in files in + /etc take precedence over those in /run which in turn + take precedence over those in /usr/lib. Drop-in files under any of these + directories take precedence over the main network service file wherever located. (Of course, since + /run is temporary and /usr/lib is for vendors, it is + unlikely drop-ins should be used in either of those places.) + + + + [Service] Section Options + + The network service file contains a [Service] + section, which specifies a discoverable network service announced in a + local network with Multicast DNS broadcasts. + + + + Name= + + An instance name of the network service as defined in the section 4.1.1 of RFC 6763, e.g. webserver. + The option supports simple specifier expansion. The following expansions are understood: + + Specifiers available + + + + + + + Specifier + Meaning + Details + + + + + %m + Machine ID + The machine ID of the running system, formatted as string. See machine-id5 for more information. + + + %b + Boot ID + The boot ID of the running system, formatted as string. See random4 for more information. + + + %H + Host name + The hostname of the running system. + + + %v + Kernel release + Identical to uname -r output. + + + +
+
+
+ + Type= + + A type of the network service as defined in the section 4.1.2 of RFC 6763, e.g. _http._tcp. + + + + + Port= + + An IP port number of the network service. + + + + Priority= + + A priority number set in SRV resource records corresponding to the network service. + + + + Weight= + + A weight number set in SRV resource records corresponding to the network service. + + + + TxtText= + + A whitespace-separated list of arbitrary key/value pairs + conveying additional information about the named service in the corresponding TXT resource record, + e.g. path=/portal/index.html. Keys and values can contain C-style escape + sequences which get translated upon reading configuration files. + + + + + TxtData= + + 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. data=YW55IGJpbmFyeSBkYXRhCg==. Keys can contain C-style escape + sequences which get translated upon reading configuration files. + + + +
+ +
+ + + Examples + + HTTP service + + # /etc/systemd/dnssd/http.dnssd +[Service] +Name=%h +Type=_http._tcp +Port=80 +TxtText=path=/stats/index.html t=temperature_sensor + + This makes the http server running on the host discoverable in the local network + given MulticastDNS is enabled on the network interface. + + Now the utility systemd-resolve should be able to resolve the + service to the host's name: + + $ 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 + + Avahi running on a different host in the same local network should see the service as well: + + $ 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"] + + + + + + See Also + + systemd1, + systemd-resolved.service8 + + + +
From d686f15c9efbef0d64c3ce7673f1a9ed0e9700ed Mon Sep 17 00:00:00 2001 From: Dmitry Rozhkov Date: Fri, 27 Oct 2017 12:16:54 +0300 Subject: [PATCH 13/16] resolved: don't check conflicts for DNS-SD enumeration RRs --- src/resolve/resolved-dns-zone.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/resolve/resolved-dns-zone.c b/src/resolve/resolved-dns-zone.c index 0ed7166e01..dcb9702e57 100644 --- a/src/resolve/resolved-dns-zone.c +++ b/src/resolve/resolved-dns-zone.c @@ -619,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 */ From e7c1b0e4566a7f1dd24ff0a580161f5c6435f811 Mon Sep 17 00:00:00 2001 From: Dmitry Rozhkov Date: Tue, 31 Oct 2017 09:47:37 +0200 Subject: [PATCH 14/16] resolved: resolve possible conflicts for DNS-SD RRs It might happen that a DNS-SD service doesn't include local host's name in its RR keys and still conflicts with a remote service. In this case try to resolve the conflict by changing name for this particular service. --- src/resolve/resolved-dns-scope.c | 4 ++ src/resolve/resolved-manager.c | 74 ++++++++++++++++++++++++-------- src/resolve/resolved-manager.h | 2 + 3 files changed, 63 insertions(+), 17 deletions(-) diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index 5448575d5a..7a5143d8ac 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -1085,6 +1085,10 @@ int dns_scope_announce(DnsScope *scope, bool goodbye) { 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) diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index 2813f6ee40..983e6c091a 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -1124,19 +1124,16 @@ void manager_refresh_rrs(Manager *m) { } } -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--; @@ -1155,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); @@ -1504,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; +} diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index e16d6a4732..d964742921 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -192,3 +192,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); From 84b0f133e4a753552295a35a5fd86aeceac19e0a Mon Sep 17 00:00:00 2001 From: Dmitry Rozhkov Date: Wed, 22 Nov 2017 16:49:23 +0200 Subject: [PATCH 15/16] resolved: consult Polkit for privileges when manipulating DNS-SD --- src/resolve/meson.build | 9 ++++ .../org.freedesktop.resolve1.policy.in | 43 +++++++++++++++++++ src/resolve/resolved-bus.c | 25 ++++++++++- src/resolve/resolved-dnssd-bus.c | 13 +++++- src/resolve/resolved-dnssd.h | 1 + src/resolve/resolved-manager.h | 2 + 6 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 src/resolve/org.freedesktop.resolve1.policy.in diff --git a/src/resolve/meson.build b/src/resolve/meson.build index b01c46cf65..ee1acb5166 100644 --- a/src/resolve/meson.build +++ b/src/resolve/meson.build @@ -165,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 += [ diff --git a/src/resolve/org.freedesktop.resolve1.policy.in b/src/resolve/org.freedesktop.resolve1.policy.in new file mode 100644 index 0000000000..da948eb0b7 --- /dev/null +++ b/src/resolve/org.freedesktop.resolve1.policy.in @@ -0,0 +1,43 @@ + + + + + + + + The systemd Project + http://www.freedesktop.org/wiki/Software/systemd + + + Register a DNS-SD service + Authentication is required to register a DNS-SD service + + auth_admin + auth_admin + auth_admin_keep + + unix-user:systemd-resolve + + + + Unregister a DNS-SD service + Authentication is required to unregister a DNS-SD service + + auth_admin + auth_admin + auth_admin_keep + + unix-user:systemd-resolve + + + diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index bcfe434e40..7060936414 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -28,6 +28,7 @@ #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) { @@ -1597,6 +1598,7 @@ static int on_bus_track(sd_bus_track *t, void *userdata) { } 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; @@ -1607,6 +1609,7 @@ static int bus_method_register_service(sd_bus_message *message, void *userdata, const char *name; const char *name_template; const char *type; + uid_t euid; int r; assert(message); @@ -1615,10 +1618,28 @@ static int bus_method_register_service(sd_bus_message *message, void *userdata, 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); @@ -1783,8 +1804,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", "sssqqqa{say}", "o", bus_method_register_service, 0), - SD_BUS_METHOD("UnregisterService", "o", NULL, bus_method_unregister_service, 0), + SD_BUS_METHOD("RegisterService", "sssqqqa{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, }; diff --git a/src/resolve/resolved-dnssd-bus.c b/src/resolve/resolved-dnssd-bus.c index 7a9f0bccbb..e45daabd23 100644 --- a/src/resolve/resolved-dnssd-bus.c +++ b/src/resolve/resolved-dnssd-bus.c @@ -18,10 +18,12 @@ ***/ #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; @@ -35,6 +37,15 @@ int bus_dnssd_method_unregister(sd_bus_message *message, void *userdata, sd_bus_ 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); @@ -67,7 +78,7 @@ int bus_dnssd_method_unregister(sd_bus_message *message, void *userdata, sd_bus_ const sd_bus_vtable dnssd_vtable[] = { SD_BUS_VTABLE_START(0), - SD_BUS_METHOD("Unregister", NULL, NULL, bus_dnssd_method_unregister, 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 diff --git a/src/resolve/resolved-dnssd.h b/src/resolve/resolved-dnssd.h index 4040190cf4..6c4dd61a77 100644 --- a/src/resolve/resolved-dnssd.h +++ b/src/resolve/resolved-dnssd.h @@ -47,6 +47,7 @@ struct DnssdService { Manager *manager; bool withdrawn:1; + uid_t originator; }; DnssdService *dnssd_service_free(DnssdService *service); diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index d964742921..5c1a6670ef 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -146,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 */ From 400f54fb3666e49115dc36a585f68047370096af Mon Sep 17 00:00:00 2001 From: Dmitry Rozhkov Date: Wed, 29 Nov 2017 11:03:44 +0200 Subject: [PATCH 16/16] resolved: support multiple TXT RRs per DNS-SD service Section 6.8 of RFC 6763 allows having service instances with multiple TXT resource records. --- man/systemd.dnssd.xml | 8 +++ src/resolve/resolved-bus.c | 93 ++++++++++++++++++++++---------- src/resolve/resolved-conf.c | 22 +++++--- src/resolve/resolved-dns-scope.c | 43 ++++++++------- src/resolve/resolved-dnssd-bus.c | 7 ++- src/resolve/resolved-dnssd.c | 66 +++++++++++++++++------ src/resolve/resolved-dnssd.h | 20 ++++++- 7 files changed, 186 insertions(+), 73 deletions(-) diff --git a/man/systemd.dnssd.xml b/man/systemd.dnssd.xml index 4e30338bf0..1270e08cd2 100644 --- a/man/systemd.dnssd.xml +++ b/man/systemd.dnssd.xml @@ -174,6 +174,10 @@ e.g. path=/portal/index.html. Keys and values can contain C-style escape sequences which get translated upon reading configuration files. + This option together with TxtData= 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. + @@ -185,6 +189,10 @@ e.g. data=YW55IGJpbmFyeSBkYXRhCg==. Keys can contain C-style escape sequences which get translated upon reading configuration files. + This option together with TxtText= 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. + diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index 7060936414..9157d9ea68 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -1605,7 +1605,6 @@ static int bus_method_register_service(sd_bus_message *message, void *userdata, _cleanup_free_ char *instance_name = NULL; Manager *m = userdata; DnssdService *s = NULL; - DnsTxtItem *last = NULL; const char *name; const char *name_template; const char *type; @@ -1669,46 +1668,82 @@ static int bus_method_register_service(sd_bus_message *message, void *userdata, if (r < 0) return r; - r = sd_bus_message_enter_container(message, SD_BUS_TYPE_ARRAY, "{say}"); + r = sd_bus_message_enter_container(message, SD_BUS_TYPE_ARRAY, "a{say}"); if (r < 0) - return sd_bus_error_set_errno(error, r); + return r; - 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; + 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; - r = sd_bus_message_read(message, "s", &key); + 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 sd_bus_error_set_errno(error, r); + return r; - if (strlen_ptr(key) == 0) - 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); + r = sd_bus_message_exit_container(message); if (r < 0) - return sd_bus_error_set_errno(error, r); + return r; - r = dnssd_txt_item_new_from_data(key, value, size, &i); - if (r < 0) - return sd_bus_error_set_errno(error, r); - - LIST_INSERT_AFTER(items, service->txt, last, i); - last = i; + 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 sd_bus_error_set_errno(error, r); + return r; - if (!service->txt) { - r = dns_txt_item_new_empty(&service->txt); + 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 sd_bus_error_set_errno(error, r); + 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); @@ -1804,7 +1839,7 @@ 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", "sssqqqa{say}", "o", bus_method_register_service, SD_BUS_VTABLE_UNPRIVILEGED), + 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, }; diff --git a/src/resolve/resolved-conf.c b/src/resolve/resolved-conf.c index 71188fcec1..ca69d70e3c 100644 --- a/src/resolve/resolved-conf.c +++ b/src/resolve/resolved-conf.c @@ -297,6 +297,7 @@ int config_parse_dnssd_service_type(const char *unit, const char *filename, unsi } 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; @@ -305,13 +306,15 @@ int config_parse_dnssd_txt(const char *unit, const char *filename, unsigned line assert(rvalue); assert(s); - /* TODO: Since RFC6763 allows more than one TXT RR per service - * this s->txt field should be implemented as a list - * of DnsTxtItem lists. */ - s->txt = dns_txt_item_free_all(s->txt); - - if (isempty(rvalue)) + 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; @@ -371,10 +374,15 @@ int config_parse_dnssd_txt(const char *unit, const char *filename, unsigned line assert_not_reached("Unknown type of Txt config"); } - LIST_INSERT_AFTER(items, s->txt, last, i); + 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; } diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index 7a5143d8ac..0a644f4252 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -1068,7 +1068,7 @@ int dns_scope_announce(DnsScope *scope, bool goodbye) { _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; _cleanup_set_free_ Set *types = NULL; DnsTransaction *t; - DnsZoneItem *z; + DnsZoneItem *z, *i; unsigned size = 0; Iterator iterator; char *service_type; @@ -1117,7 +1117,8 @@ int dns_scope_announce(DnsScope *scope, bool goodbye) { } } - size++; + LIST_FOREACH(by_key, i, z) + size++; } answer = dns_answer_new(size + set_size(types)); @@ -1125,21 +1126,22 @@ int dns_scope_announce(DnsScope *scope, bool goodbye) { return log_oom(); /* Second iteration, actually add RRs to the answer. */ - HASHMAP_FOREACH(z, scope->zone.by_key, iterator) { - DnsAnswerFlags flags; + HASHMAP_FOREACH(z, scope->zone.by_key, iterator) + LIST_FOREACH (by_key, i, z) { + DnsAnswerFlags flags; - if (z->state != DNS_ZONE_ITEM_ESTABLISHED) - continue; + if (i->state != DNS_ZONE_ITEM_ESTABLISHED) + continue; - if (dns_resource_key_is_dnssd_ptr(z->rr->key)) - flags = goodbye ? DNS_ANSWER_GOODBYE : 0; - else - flags = goodbye ? (DNS_ANSWER_GOODBYE|DNS_ANSWER_CACHE_FLUSH) : DNS_ANSWER_CACHE_FLUSH; + 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, z->rr, 0 , flags); - if (r < 0) - return log_debug_errno(r, "Failed to add RR to announce: %m"); - } + 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) { @@ -1199,6 +1201,7 @@ int dns_scope_announce(DnsScope *scope, bool goodbye) { int dns_scope_add_dnssd_services(DnsScope *scope) { Iterator i; DnssdService *service; + DnssdTxtData *txt_data; int r; assert(scope); @@ -1219,9 +1222,11 @@ int dns_scope_add_dnssd_services(DnsScope *scope) { if (r < 0) log_warning_errno(r, "Failed to add SRV record to MDNS zone: %m"); - r = dns_zone_put(&scope->zone, scope, service->txt_rr, true); - if (r < 0) - log_warning_errno(r, "Failed to add TXT 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; @@ -1231,6 +1236,7 @@ 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); @@ -1247,7 +1253,8 @@ int dns_scope_remove_dnssd_services(DnsScope *scope) { 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); - dns_zone_remove_rr(&scope->zone, service->txt_rr); + LIST_FOREACH(items, txt_data, service->txt_data_items) + dns_zone_remove_rr(&scope->zone, txt_data->rr); } return 0; diff --git a/src/resolve/resolved-dnssd-bus.c b/src/resolve/resolved-dnssd-bus.c index e45daabd23..c914e8f8d2 100644 --- a/src/resolve/resolved-dnssd-bus.c +++ b/src/resolve/resolved-dnssd-bus.c @@ -27,6 +27,7 @@ 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; @@ -54,7 +55,8 @@ int bus_dnssd_method_unregister(sd_bus_message *message, void *userdata, sd_bus_ dns_zone_remove_rr(&l->mdns_ipv4_scope->zone, s->ptr_rr); dns_zone_remove_rr(&l->mdns_ipv4_scope->zone, s->srv_rr); - dns_zone_remove_rr(&l->mdns_ipv4_scope->zone, s->txt_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) { @@ -64,7 +66,8 @@ int bus_dnssd_method_unregister(sd_bus_message *message, void *userdata, sd_bus_ dns_zone_remove_rr(&l->mdns_ipv6_scope->zone, s->ptr_rr); dns_zone_remove_rr(&l->mdns_ipv6_scope->zone, s->srv_rr); - dns_zone_remove_rr(&l->mdns_ipv6_scope->zone, s->txt_rr); + LIST_FOREACH(items, txt_data, s->txt_data_items) + dns_zone_remove_rr(&l->mdns_ipv6_scope->zone, txt_data->rr); } } diff --git a/src/resolve/resolved-dnssd.c b/src/resolve/resolved-dnssd.c index 6d0ab39a9d..db589f4436 100644 --- a/src/resolve/resolved-dnssd.c +++ b/src/resolve/resolved-dnssd.c @@ -35,6 +35,29 @@ const char* const dnssd_service_dirs[] = { 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; @@ -44,19 +67,20 @@ DnssdService *dnssd_service_free(DnssdService *service) { dns_resource_record_unref(service->ptr_rr); dns_resource_record_unref(service->srv_rr); - dns_resource_record_unref(service->txt_rr); + + dnssd_txtdata_free_all(service->txt_data_items); free(service->filename); free(service->name); free(service->type); free(service->name_template); - dns_txt_item_free_all(service->txt); 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; @@ -103,10 +127,17 @@ static int dnssd_service_load(Manager *manager, const char *filename) { return -EINVAL; } - if (!service->txt) { - r = dns_txt_item_new_empty(&service->txt); + 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); @@ -200,15 +231,17 @@ 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); + 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); - s->txt_rr = dns_resource_record_unref(s->txt_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) @@ -221,15 +254,17 @@ int dnssd_update_rrs(DnssdService *s) { if (r < 0) return r; - s->txt_rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_TXT, - full_name); - if (!s->txt_rr) - goto oom; + 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; - s->txt_rr->ttl = MDNS_DEFAULT_TTL; - s->txt_rr->txt.items = dns_txt_item_copy(s->txt); - if (!s->txt_rr->txt.items) - 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); @@ -257,7 +292,8 @@ int dnssd_update_rrs(DnssdService *s) { return 0; oom: - s->txt_rr = dns_resource_record_unref(s->txt_rr); + 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; diff --git a/src/resolve/resolved-dnssd.h b/src/resolve/resolved-dnssd.h index 6c4dd61a77..fbc043d673 100644 --- a/src/resolve/resolved-dnssd.h +++ b/src/resolve/resolved-dnssd.h @@ -19,7 +19,10 @@ along with systemd; If not, see . ***/ +#include "list.h" + typedef struct DnssdService DnssdService; +typedef struct DnssdTxtData DnssdTxtData; typedef struct Manager Manager; typedef struct DnsResourceRecord DnsResourceRecord; @@ -30,6 +33,14 @@ enum { DNS_TXT_ITEM_DATA }; +struct DnssdTxtData { + DnsResourceRecord *rr; + + LIST_HEAD(DnsTxtItem, txt); + + LIST_FIELDS(DnssdTxtData, items); +}; + struct DnssdService { char *filename; char *name; @@ -38,11 +49,13 @@ struct DnssdService { uint16_t port; uint16_t priority; uint16_t weight; - DnsTxtItem *txt; DnsResourceRecord *ptr_rr; DnsResourceRecord *srv_rr; - DnsResourceRecord *txt_rr; + + /* Section 6.8 of RFC 6763 allows having service + * instances with multiple TXT resource records. */ + LIST_HEAD(DnssdTxtData, txt_data_items); Manager *manager; @@ -51,8 +64,11 @@ struct DnssdService { }; 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);