From 6501dd31a7c6f74f539ada652f6c7c83c2fe4e0a Mon Sep 17 00:00:00 2001 From: Dmitry Rozhkov Date: Wed, 4 Oct 2017 11:19:16 +0300 Subject: [PATCH] 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;