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);