resolved: support multiple TXT RRs per DNS-SD service
authorDmitry Rozhkov <dmitry.rozhkov@linux.intel.com>
Wed, 29 Nov 2017 09:03:44 +0000 (11:03 +0200)
committerDmitry Rozhkov <dmitry.rozhkov@linux.intel.com>
Fri, 8 Dec 2017 12:29:27 +0000 (14:29 +0200)
Section 6.8 of RFC 6763 allows having service instances with
multiple TXT resource records.

man/systemd.dnssd.xml
src/resolve/resolved-bus.c
src/resolve/resolved-conf.c
src/resolve/resolved-dns-scope.c
src/resolve/resolved-dnssd-bus.c
src/resolve/resolved-dnssd.c
src/resolve/resolved-dnssd.h

index 4e30338..1270e08 100644 (file)
             e.g. <literal>path=/portal/index.html</literal>. Keys and values can contain C-style escape
             sequences which get translated upon reading configuration files.
             </para>
+            <para>This option together with <varname>TxtData=</varname> may be specified more than once, in which
+            case multiple TXT resource records will be created for the service. If the empty string is assigned to
+            this option, the list is reset and all prior assignments will have no effect.
+            </para>
           </listitem>
         </varlistentry>
         <varlistentry>
             e.g. <literal>data=YW55IGJpbmFyeSBkYXRhCg==</literal>. Keys can contain C-style escape
             sequences which get translated upon reading configuration files.
             </para>
+            <para>This option together with <varname>TxtText=</varname> may be specified more than once, in which
+            case multiple TXT resource records will be created for the service. If the empty string is assigned to
+            this option, the list is reset and all prior assignments will have no effect.
+            </para>
           </listitem>
         </varlistentry>
       </variablelist>
index 7060936..9157d9e 100644 (file)
@@ -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);
-                if (r < 0)
-                        return sd_bus_error_set_errno(error, r);
+                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 (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);
 
-                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 = sd_bus_message_read_array(message, 'y', &value, &size);
+                        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;
 
-                r = dnssd_txt_item_new_from_data(key, value, size, &i);
+                r = sd_bus_message_exit_container(message);
                 if (r < 0)
-                        return sd_bus_error_set_errno(error, r);
+                        return 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_data_items) {
+                _cleanup_(dnssd_txtdata_freep) DnssdTxtData *txt_data = NULL;
 
-        if (!service->txt) {
-                r = dns_txt_item_new_empty(&service->txt);
+                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,
 };
index 71188fc..ca69d70 100644 (file)
@@ -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;
 }
 
index 7a5143d..0a644f4 100644 (file)
@@ -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;
index e45daab..c914e8f 100644 (file)
@@ -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);
                 }
         }
 
index 6d0ab39..db589f4 100644 (file)
@@ -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;
index 6c4dd61..fbc043d 100644 (file)
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
+#include "list.h"
+
 typedef struct DnssdService DnssdService;
+typedef struct DnssdTxtData DnssdTxtData;
 
 typedef struct Manager Manager;
 typedef struct DnsResourceRecord DnsResourceRecord;
@@ -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);