resolved: support multiple TXT RRs per DNS-SD service

Section 6.8 of RFC 6763 allows having service instances with
multiple TXT resource records.
This commit is contained in:
Dmitry Rozhkov 2017-11-29 11:03:44 +02:00
parent 84b0f133e4
commit 400f54fb36
7 changed files with 186 additions and 73 deletions

View File

@ -174,6 +174,10 @@
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>
@ -185,6 +189,10 @@
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>

View 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);
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,
};

View 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;
}

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

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

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

View File

@ -19,7 +19,10 @@
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);