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