blob: 56be7b25028a0dff5e87e6635eafd642db29fa35 [file] [log] [blame]
/*
* Copyright (c) 2020 Friedt Professional Engineering Services, Inc
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <ctype.h>
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <zephyr/net/net_context.h>
#include <zephyr/net/net_core.h>
#include <zephyr/net/dns_sd.h>
#include <zephyr/sys/util.h>
#include <zephyr/kernel.h>
#include "dns_pack.h"
#include "dns_sd.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(net_dns_sd, CONFIG_DNS_SD_LOG_LEVEL);
const char dns_sd_empty_txt[1];
const uint16_t dns_sd_port_zero;
#ifndef CONFIG_NET_TEST
static size_t service_proto_size(const struct dns_sd_rec *inst);
static bool label_is_valid(const char *label, size_t label_size);
static int add_a_record(const struct dns_sd_rec *inst, uint32_t ttl,
uint16_t host_offset, uint32_t addr,
uint8_t *buf,
uint16_t buf_offset, uint16_t buf_size);
static int add_ptr_record(const struct dns_sd_rec *inst, uint32_t ttl,
uint8_t *buf, uint16_t buf_offset,
uint16_t buf_size,
uint16_t *service_offset,
uint16_t *instance_offset,
uint16_t *domain_offset);
static int add_txt_record(const struct dns_sd_rec *inst, uint32_t ttl,
uint16_t instance_offset, uint8_t *buf,
uint16_t buf_offset, uint16_t buf_size);
static int add_aaaa_record(const struct dns_sd_rec *inst, uint32_t ttl,
uint16_t host_offset, const uint8_t addr[16],
uint8_t *buf, uint16_t buf_offset,
uint16_t buf_size);
static int add_srv_record(const struct dns_sd_rec *inst, uint32_t ttl,
uint16_t instance_offset,
uint16_t domain_offset,
uint8_t *buf, uint16_t buf_offset,
uint16_t buf_size,
uint16_t *host_offset);
static bool rec_is_valid(const struct dns_sd_rec *inst);
#endif /* CONFIG_NET_TEST */
/**
* Calculate the size of a DNS-SD service
*
* This macro calculates the size of the DNS-SD service for a DNS
* Resource Record (RR).
*
* For example, if there is a service called 'My Foo'._http._tcp.local.,
* then the returned size is 18. That is broken down as shown below.
*
* - 1 byte for the size of "_http"
* - 5 bytes for the value of "_http"
* - 1 byte for the size of "_tcp"
* - 4 bytes for the value of "_tcp"
* - 1 byte for the size of "local"
* - 5 bytes for the value of "local"
* - 1 byte for the trailing NUL terminator '\0'
*
* @param ref the DNS-SD record
* @return the size of the DNS-SD service for a DNS Resource Record
*/
size_t service_proto_size(const struct dns_sd_rec *ref)
{
return 0
+ DNS_LABEL_LEN_SIZE + strlen(ref->service)
+ DNS_LABEL_LEN_SIZE + strlen(ref->proto)
+ DNS_LABEL_LEN_SIZE + strlen(ref->domain)
+ DNS_LABEL_LEN_SIZE
;
}
/**
* Check Label Validity according to RFC 1035, Section 3.5
*
* <label> ::= <letter> [ [ <ldh-str> ] <let-dig> ]
* <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
* <let-dig-hyp> ::= <let-dig> | -
* <let-dig> ::= <letter> | <digit>
* <letter> ::= [a-zA-Z]
* <digit> ::= [0-9]
*/
bool label_is_valid(const char *label, size_t label_size)
{
size_t i;
if (label == NULL) {
return false;
}
if (label_size == 0) {
/* automatically calculate the length of the string */
label_size = strlen(label);
}
if (label_size < DNS_LABEL_MIN_SIZE ||
label_size > DNS_LABEL_MAX_SIZE) {
return false;
}
for (i = 0; i < label_size; ++i) {
if (isalpha((int)label[i])) {
continue;
}
if (i > 0) {
if (isdigit((int)label[i])) {
continue;
}
if ('-' == label[i]) {
continue;
}
}
return false;
}
return true;
}
static bool instance_is_valid(const char *instance)
{
size_t i;
size_t instance_size;
if (instance == NULL) {
NET_DBG("label is NULL");
return false;
}
instance_size = strlen(instance);
if (instance_size < DNS_SD_INSTANCE_MIN_SIZE) {
NET_DBG("label '%s' is too small (%zu, min: %u)",
instance, instance_size,
DNS_SD_INSTANCE_MIN_SIZE);
return false;
}
if (instance_size > DNS_SD_INSTANCE_MAX_SIZE) {
NET_DBG("label '%s' is too big (%zu, max: %u)",
instance, instance_size,
DNS_SD_INSTANCE_MAX_SIZE);
return false;
}
for (i = 0; i < instance_size; ++i) {
/* RFC 6763 Section 4.1.1 */
if (instance[i] <= 0x1f ||
instance[i] == 0x7f) {
NET_DBG(
"instance '%s' contains illegal byte 0x%02x",
instance, instance[i]);
return false;
}
}
return instance_size;
}
static bool service_is_valid(const char *service)
{
size_t service_size;
if (service == NULL) {
NET_DBG("label is NULL");
return false;
}
service_size = strlen(service);
if (service_size < DNS_SD_SERVICE_MIN_SIZE) {
NET_DBG("label '%s' is too small (%zu, min: %u)",
service, service_size, DNS_SD_SERVICE_MIN_SIZE);
return false;
}
if (service_size > DNS_SD_SERVICE_MAX_SIZE) {
NET_DBG("label '%s' is too big (%zu, max: %u)",
service, service_size, DNS_SD_SERVICE_MAX_SIZE);
return false;
}
if (service[0] != DNS_SD_SERVICE_PREFIX) {
NET_DBG("service '%s' invalid (no leading underscore)",
service);
return false;
}
if (!label_is_valid(&service[1], service_size - 1)) {
NET_DBG("service '%s' contains invalid characters",
service);
return false;
}
return service_size;
}
static bool proto_is_valid(const char *proto)
{
size_t proto_size;
if (proto == NULL) {
NET_DBG("label is NULL");
return false;
}
proto_size = strlen(proto);
if (proto_size != DNS_SD_PROTO_SIZE) {
NET_DBG("label '%s' wrong size (%zu, exp: %u)",
proto, proto_size, DNS_SD_PROTO_SIZE);
return false;
}
if (!(strncasecmp("_tcp", proto, DNS_SD_PROTO_SIZE) == 0 ||
strncasecmp("_udp", proto, DNS_SD_PROTO_SIZE) == 0)) {
/* RFC 1034 Section 3.1 */
NET_DBG("proto '%s' is invalid (not _tcp or _udp)",
proto);
return false;
}
return proto_size;
}
static bool domain_is_valid(const char *domain)
{
size_t domain_size;
if (domain == NULL) {
NET_DBG("label is NULL");
return false;
}
domain_size = strlen(domain);
if (domain_size < DNS_SD_DOMAIN_MIN_SIZE) {
NET_DBG("label '%s' is too small (%zu, min: %u)",
domain, domain_size, DNS_SD_DOMAIN_MIN_SIZE);
return false;
}
if (domain_size > DNS_SD_DOMAIN_MAX_SIZE) {
NET_DBG("label '%s' is too big (%zu, max: %u)",
domain, domain_size, DNS_SD_DOMAIN_MAX_SIZE);
return false;
}
if (!label_is_valid(domain, domain_size)) {
NET_DBG("domain '%s' contains invalid characters",
domain);
return false;
}
return domain_size;
}
/**
* Check DNS SD Record for validity
*
* Our records are in the form <Instance>.<Service>.<Proto>.<Domain>
*
* Currently, <Subdomain>.<Domain> services are not supported.
*/
bool rec_is_valid(const struct dns_sd_rec *inst)
{
return true
&& inst != NULL
&& instance_is_valid(inst->instance)
&& service_is_valid(inst->service)
&& proto_is_valid(inst->proto)
&& domain_is_valid(inst->domain)
&& inst->text != NULL
&& inst->port != NULL
;
}
int add_a_record(const struct dns_sd_rec *inst, uint32_t ttl,
uint16_t host_offset, uint32_t addr, uint8_t *buf,
uint16_t buf_offset, uint16_t buf_size)
{
uint16_t total_size;
struct dns_rr *rr;
struct dns_a_rdata *rdata;
uint16_t inst_offs;
uint16_t offset = buf_offset;
if ((DNS_SD_PTR_MASK & host_offset) != 0) {
NET_DBG("offset %u too big for message compression",
host_offset);
return -E2BIG;
}
/* First, calculate that there is enough space in the buffer */
total_size =
/* pointer to .<Instance>.local. */
2 + sizeof(*rr) + sizeof(*rdata);
if (offset > buf_size || total_size >= buf_size - offset) {
NET_DBG("Buffer too small. required: %u available: %d",
total_size, (int)buf_size - (int)offset);
return -ENOSPC;
}
/* insert a pointer to the instance + service name */
inst_offs = host_offset;
inst_offs |= DNS_SD_PTR_MASK;
inst_offs = htons(inst_offs);
memcpy(&buf[offset], &inst_offs, sizeof(inst_offs));
offset += sizeof(inst_offs);
rr = (struct dns_rr *)&buf[offset];
rr->type = htons(DNS_RR_TYPE_A);
rr->class_ = htons(DNS_CLASS_IN | DNS_CLASS_FLUSH);
rr->ttl = htonl(ttl);
rr->rdlength = htons(sizeof(*rdata));
offset += sizeof(*rr);
rdata = (struct dns_a_rdata *)&buf[offset];
rdata->address = htonl(addr);
offset += sizeof(*rdata);
__ASSERT_NO_MSG(total_size == offset - buf_offset);
return offset - buf_offset;
}
int add_ptr_record(const struct dns_sd_rec *inst, uint32_t ttl,
uint8_t *buf, uint16_t buf_offset, uint16_t buf_size,
uint16_t *service_offset, uint16_t *instance_offset,
uint16_t *domain_offset)
{
uint8_t i;
int name_size;
struct dns_rr *rr;
uint16_t svc_offs;
uint16_t inst_offs;
uint16_t dom_offs;
size_t label_size;
uint16_t sp_size;
uint16_t offset = buf_offset;
const char *labels[] = {
inst->instance,
inst->service,
inst->proto,
inst->domain,
};
/* First, ensure that labels and full name are within spec */
if (!rec_is_valid(inst)) {
return -EINVAL;
}
sp_size = service_proto_size(inst);
/*
* Next, calculate that there is enough space in the buffer.
*
* We require that this is the first time names will appear in the
* DNS message. Message Compression is used in subsequent
* calculations.
*
* That is the reason there is an output variable for
* service_offset and instance_offset.
*
* For more information on DNS Message Compression, see
* RFC 1035, Section 4.1.4.
*/
name_size =
/* uncompressed. e.g. "._foo._tcp.local." */
sp_size +
sizeof(*rr)
/* compressed e.g. .My Foo" followed by (DNS_SD_PTR_MASK | 0x0abc) */
+ 1 + strlen(inst->instance) + 2;
if (offset > buf_size || name_size >= buf_size - offset) {
NET_DBG("Buffer too small. required: %u available: %d",
name_size, (int)buf_size - (int)offset);
return -ENOSPC;
}
svc_offs = offset;
if ((svc_offs & DNS_SD_PTR_MASK) != 0) {
NET_DBG("offset %u too big for message compression",
svc_offs);
return -E2BIG;
}
inst_offs = offset + sp_size + sizeof(*rr);
if ((inst_offs & DNS_SD_PTR_MASK) != 0) {
NET_DBG("offset %u too big for message compression",
inst_offs);
return -E2BIG;
}
dom_offs = offset + sp_size - 1 -
strlen(inst->domain) - 1;
/* Finally, write output with confidence that doing so is safe */
*service_offset = svc_offs;
*instance_offset = inst_offs;
*domain_offset = dom_offs;
/* copy the service name. e.g. "._foo._tcp.local." */
for (i = 1; i < ARRAY_SIZE(labels); ++i) {
label_size = strlen(labels[i]);
buf[offset++] = strlen(labels[i]);
memcpy(&buf[offset], labels[i], label_size);
offset += label_size;
if (i == ARRAY_SIZE(labels) - 1) {
/* terminator */
buf[offset++] = '\0';
}
}
__ASSERT_NO_MSG(svc_offs + sp_size == offset);
rr = (struct dns_rr *)&buf[offset];
rr->type = htons(DNS_RR_TYPE_PTR);
rr->class_ = htons(DNS_CLASS_IN);
rr->ttl = htonl(ttl);
rr->rdlength = htons(
DNS_LABEL_LEN_SIZE +
strlen(inst->instance)
+ DNS_POINTER_SIZE);
offset += sizeof(*rr);
__ASSERT_NO_MSG(inst_offs == offset);
/* copy the instance size, value, and add a pointer */
label_size = strlen(inst->instance);
buf[offset++] = label_size;
memcpy(&buf[offset], inst->instance, label_size);
offset += label_size;
svc_offs |= DNS_SD_PTR_MASK;
svc_offs = htons(svc_offs);
memcpy(&buf[offset], &svc_offs, sizeof(svc_offs));
offset += sizeof(svc_offs);
__ASSERT_NO_MSG(name_size == offset - buf_offset);
return offset - buf_offset;
}
int add_txt_record(const struct dns_sd_rec *inst, uint32_t ttl,
uint16_t instance_offset, uint8_t *buf,
uint16_t buf_offset, uint16_t buf_size)
{
uint16_t total_size;
struct dns_rr *rr;
uint16_t inst_offs;
uint16_t offset = buf_offset;
if ((DNS_SD_PTR_MASK & instance_offset) != 0) {
NET_DBG("offset %u too big for message compression",
instance_offset);
return -E2BIG;
}
/* First, calculate that there is enough space in the buffer */
total_size =
/* pointer to .<Instance>.<Service>.<Protocol>.local. */
DNS_POINTER_SIZE + sizeof(*rr) + dns_sd_txt_size(inst);
if (offset > buf_size || total_size >= buf_size - offset) {
NET_DBG("Buffer too small. required: %u available: %d",
total_size, (int)buf_size - (int)offset);
return -ENOSPC;
}
/* insert a pointer to the instance + service name */
inst_offs = instance_offset;
inst_offs |= DNS_SD_PTR_MASK;
inst_offs = htons(inst_offs);
memcpy(&buf[offset], &inst_offs, sizeof(inst_offs));
offset += sizeof(inst_offs);
rr = (struct dns_rr *)&buf[offset];
rr->type = htons(DNS_RR_TYPE_TXT);
rr->class_ = htons(DNS_CLASS_IN | DNS_CLASS_FLUSH);
rr->ttl = htonl(ttl);
rr->rdlength = htons(dns_sd_txt_size(inst));
offset += sizeof(*rr);
memcpy(&buf[offset], inst->text, dns_sd_txt_size(inst));
offset += dns_sd_txt_size(inst);
__ASSERT_NO_MSG(total_size == offset - buf_offset);
return offset - buf_offset;
}
int add_aaaa_record(const struct dns_sd_rec *inst, uint32_t ttl,
uint16_t host_offset, const uint8_t addr[16],
uint8_t *buf, uint16_t buf_offset, uint16_t buf_size)
{
uint16_t total_size;
struct dns_rr *rr;
struct dns_aaaa_rdata *rdata;
uint16_t inst_offs;
uint16_t offset = buf_offset;
if ((DNS_SD_PTR_MASK & host_offset) != 0) {
NET_DBG("offset %u too big for message compression",
host_offset);
return -E2BIG;
}
/* First, calculate that there is enough space in the buffer */
total_size =
/* pointer to .<Instance>.local. */
DNS_POINTER_SIZE + sizeof(*rr) + sizeof(*rdata);
if (offset > buf_size || total_size >= buf_size - offset) {
NET_DBG("Buffer too small. required: %u available: %d",
total_size, (int)buf_size - (int)offset);
return -ENOSPC;
}
/* insert a pointer to the instance + service name */
inst_offs = host_offset;
inst_offs |= DNS_SD_PTR_MASK;
inst_offs = htons(inst_offs);
memcpy(&buf[offset], &inst_offs, sizeof(inst_offs));
offset += sizeof(inst_offs);
rr = (struct dns_rr *)&buf[offset];
rr->type = htons(DNS_RR_TYPE_AAAA);
rr->class_ = htons(DNS_CLASS_IN | DNS_CLASS_FLUSH);
rr->ttl = htonl(ttl);
rr->rdlength = htons(sizeof(*rdata));
offset += sizeof(*rr);
rdata = (struct dns_aaaa_rdata *)&buf[offset];
memcpy(rdata->address, addr, sizeof(*rdata));
offset += sizeof(*rdata);
__ASSERT_NO_MSG(total_size == offset - buf_offset);
return offset - buf_offset;
}
int add_srv_record(const struct dns_sd_rec *inst, uint32_t ttl,
uint16_t instance_offset, uint16_t domain_offset,
uint8_t *buf, uint16_t buf_offset, uint16_t buf_size,
uint16_t *host_offset)
{
uint16_t total_size;
struct dns_rr *rr;
struct dns_srv_rdata *rdata;
size_t label_size;
uint16_t inst_offs;
uint16_t offset = buf_offset;
if ((DNS_SD_PTR_MASK & instance_offset) != 0) {
NET_DBG("offset %u too big for message compression",
instance_offset);
return -E2BIG;
}
if ((DNS_SD_PTR_MASK & domain_offset) != 0) {
NET_DBG("offset %u too big for message compression",
domain_offset);
return -E2BIG;
}
/* First, calculate that there is enough space in the buffer */
total_size =
/* pointer to .<Instance>.<Service>.<Protocol>.local. */
DNS_POINTER_SIZE + sizeof(*rr)
+ sizeof(*rdata)
/* .<Instance> */
+ DNS_LABEL_LEN_SIZE
+ strlen(inst->instance)
/* pointer to .local. */
+ DNS_POINTER_SIZE;
if (offset > buf_size || total_size >= buf_size - offset) {
NET_DBG("Buffer too small. required: %u available: %d",
total_size, (int)buf_size - (int)offset);
return -ENOSPC;
}
/* insert a pointer to the instance + service name */
inst_offs = instance_offset;
inst_offs |= DNS_SD_PTR_MASK;
inst_offs = htons(inst_offs);
memcpy(&buf[offset], &inst_offs, sizeof(inst_offs));
offset += sizeof(inst_offs);
rr = (struct dns_rr *)&buf[offset];
rr->type = htons(DNS_RR_TYPE_SRV);
rr->class_ = htons(DNS_CLASS_IN | DNS_CLASS_FLUSH);
rr->ttl = htonl(ttl);
/* .<Instance>.local. */
rr->rdlength = htons(sizeof(*rdata) + DNS_LABEL_LEN_SIZE
+ strlen(inst->instance) +
DNS_POINTER_SIZE);
offset += sizeof(*rr);
rdata = (struct dns_srv_rdata *)&buf[offset];
rdata->priority = 0;
rdata->weight = 0;
rdata->port = *(inst->port);
offset += sizeof(*rdata);
*host_offset = offset;
label_size = strlen(inst->instance);
buf[offset++] = label_size;
memcpy(&buf[offset], inst->instance, label_size);
offset += label_size;
domain_offset |= DNS_SD_PTR_MASK;
domain_offset = htons(domain_offset);
memcpy(&buf[offset], &domain_offset, sizeof(domain_offset));
offset += sizeof(domain_offset);
__ASSERT_NO_MSG(total_size == offset - buf_offset);
return offset - buf_offset;
}
#ifndef CONFIG_NET_TEST
static bool port_in_use_sockaddr(uint16_t proto, uint16_t port,
const struct sockaddr *addr)
{
const struct sockaddr_in any = {
.sin_family = AF_INET,
.sin_addr.s_addr = INADDR_ANY,
};
const struct sockaddr_in6 any6 = {
.sin6_family = AF_INET6,
.sin6_addr = in6addr_any,
};
const struct sockaddr *anyp =
(addr->sa_family == AF_INET)
? (const struct sockaddr *) &any
: (const struct sockaddr *) &any6;
return
net_context_port_in_use(proto, port, addr)
|| net_context_port_in_use(proto, port, anyp);
}
static bool port_in_use(uint16_t proto, uint16_t port, const struct in_addr *addr4,
const struct in6_addr *addr6)
{
bool r;
struct sockaddr sa;
if (addr4 != NULL) {
net_sin(&sa)->sin_family = AF_INET;
net_sin(&sa)->sin_addr = *addr4;
r = port_in_use_sockaddr(proto, port, &sa);
if (r) {
return true;
}
}
if (addr6 != NULL) {
net_sin6(&sa)->sin6_family = AF_INET6;
net_sin6(&sa)->sin6_addr = *addr6;
r = port_in_use_sockaddr(proto, port, &sa);
if (r) {
return true;
}
}
return false;
}
#else /* CONFIG_NET_TEST */
static inline bool port_in_use(uint16_t proto, uint16_t port, const struct in_addr *addr4,
const struct in6_addr *addr6)
{
ARG_UNUSED(port);
ARG_UNUSED(addr4);
ARG_UNUSED(addr6);
return true;
}
#endif /* CONFIG_NET_TEST */
int dns_sd_handle_ptr_query(const struct dns_sd_rec *inst, const struct in_addr *addr4,
const struct in6_addr *addr6, uint8_t *buf, uint16_t buf_size)
{
/*
* RFC 6763 Section 12.1
*
* When including a DNS-SD Service Instance Enumeration or Selective
* Instance Enumeration (subtype) PTR record in a response packet, the
* server/responder SHOULD include the following additional records:
*
* o The SRV record(s) named in the PTR rdata.
* o The TXT record(s) named in the PTR rdata.
* o All address records (type "A" and "AAAA") named in the SRV rdata.
* contain the SRV record(s), the TXT record(s), and the address
* records (A or AAAA)
*/
uint16_t instance_offset;
uint16_t service_offset;
uint16_t domain_offset;
uint16_t host_offset;
uint16_t proto;
uint16_t offset = sizeof(struct dns_header);
struct dns_header *rsp = (struct dns_header *)buf;
uint32_t tmp;
int r;
memset(rsp, 0, sizeof(*rsp));
if (!rec_is_valid(inst)) {
return -EINVAL;
}
if (*(inst->port) == 0) {
NET_DBG("Ephemeral port %u for %s.%s.%s.%s not initialized",
ntohs(*(inst->port)), inst->instance, inst->service, inst->proto,
inst->domain);
return -EHOSTDOWN;
}
if (strncmp("_tcp", inst->proto, DNS_SD_PROTO_SIZE) == 0) {
proto = IPPROTO_TCP;
} else if (strncmp("_udp", inst->proto, DNS_SD_PROTO_SIZE) == 0) {
proto = IPPROTO_UDP;
} else {
NET_DBG("invalid protocol %s", inst->proto);
return -EINVAL;
}
if (!port_in_use(proto, ntohs(*(inst->port)), addr4, addr6)) {
/* Service is not yet bound, so do not advertise */
return -EHOSTDOWN;
}
/* first add the answer record */
r = add_ptr_record(inst, DNS_SD_PTR_TTL, buf, offset, buf_size - offset, &service_offset,
&instance_offset, &domain_offset);
if (r < 0) {
return r; /* LCOV_EXCL_LINE */
}
rsp->ancount++;
offset += r;
/* then add the additional records */
r = add_txt_record(inst, DNS_SD_TXT_TTL, instance_offset, buf, offset, buf_size - offset);
if (r < 0) {
return r; /* LCOV_EXCL_LINE */
}
rsp->arcount++;
offset += r;
r = add_srv_record(inst, DNS_SD_SRV_TTL, instance_offset, domain_offset, buf, offset,
buf_size - offset, &host_offset);
if (r < 0) {
return r; /* LCOV_EXCL_LINE */
}
rsp->arcount++;
offset += r;
if (addr6 != NULL) {
r = add_aaaa_record(inst, DNS_SD_AAAA_TTL, host_offset, addr6->s6_addr, buf, offset,
buf_size - offset); /* LCOV_EXCL_LINE */
if (r < 0) {
return r; /* LCOV_EXCL_LINE */
}
rsp->arcount++;
offset += r;
}
if (addr4 != NULL) {
tmp = htonl(*(addr4->s4_addr32));
r = add_a_record(inst, DNS_SD_A_TTL, host_offset, tmp, buf, offset,
buf_size - offset);
if (r < 0) {
return r; /* LCOV_EXCL_LINE */
}
rsp->arcount++;
offset += r;
}
/* Set the Response and AA bits */
rsp->flags = htons(BIT(15) | BIT(10));
rsp->ancount = htons(rsp->ancount);
rsp->arcount = htons(rsp->arcount);
return offset;
}
int dns_sd_handle_service_type_enum(const struct dns_sd_rec *inst,
const struct in_addr *addr4, const struct in6_addr *addr6,
uint8_t *buf, uint16_t buf_size)
{
static const char query[] = { "\x09_services\x07_dns-sd\x04_udp\x05local" };
/* offset of '.local' in the above */
uint16_t domain_offset = DNS_SD_PTR_MASK | 35;
uint16_t proto;
int name_size;
uint16_t service_size;
uint16_t offset = sizeof(struct dns_header);
struct dns_rr *rr;
struct dns_header *const rsp = (struct dns_header *)buf;
if (!rec_is_valid(inst)) {
return -EINVAL;
}
if (*(inst->port) == 0) {
NET_DBG("Ephemeral port %u for %s.%s.%s.%s "
"not initialized",
ntohs(*(inst->port)), inst->instance, inst->service, inst->proto,
inst->domain);
return -EHOSTDOWN;
}
if (strncmp("_tcp", inst->proto, DNS_SD_PROTO_SIZE) == 0) {
proto = IPPROTO_TCP;
} else if (strncmp("_udp", inst->proto, DNS_SD_PROTO_SIZE) == 0) {
proto = IPPROTO_UDP;
} else {
NET_DBG("invalid protocol %s", inst->proto);
return -EINVAL;
}
if (!port_in_use(proto, ntohs(*(inst->port)), addr4, addr6)) {
/* Service is not yet bound, so do not advertise */
return -EHOSTDOWN;
}
service_size = strlen(inst->service);
name_size =
/* uncompressed. e.g. "._foo._tcp.local." */
sizeof(query)
+ sizeof(*rr)
/* compressed e.g. ._googlecast._tcp" followed by (DNS_SD_PTR_MASK | 0x0abc) */
+ DNS_LABEL_LEN_SIZE + service_size
+ DNS_LABEL_LEN_SIZE + DNS_SD_PROTO_SIZE
+ DNS_POINTER_SIZE;
if (offset > buf_size || name_size >= buf_size - offset) {
NET_DBG("Buffer too small. required: %u available: %d", name_size,
(int)buf_size - (int)offset);
return -ENOSPC;
}
memset(rsp, 0, sizeof(*rsp));
memcpy(&buf[offset], query, sizeof(query));
offset += sizeof(query);
rr = (struct dns_rr *)&buf[offset];
rr->type = htons(DNS_RR_TYPE_PTR);
rr->class_ = htons(DNS_CLASS_IN);
rr->ttl = htonl(DNS_SD_PTR_TTL);
rr->rdlength = htons(0
+ DNS_LABEL_LEN_SIZE + service_size
+ DNS_LABEL_LEN_SIZE + DNS_SD_PROTO_SIZE
+ DNS_POINTER_SIZE);
offset += sizeof(*rr);
buf[offset++] = service_size;
memcpy(&buf[offset], inst->service, service_size);
offset += service_size;
buf[offset++] = DNS_SD_PROTO_SIZE;
memcpy(&buf[offset], inst->proto, DNS_SD_PROTO_SIZE);
offset += DNS_SD_PROTO_SIZE;
domain_offset = htons(domain_offset);
memcpy(&buf[offset], &domain_offset, sizeof(domain_offset));
offset += sizeof(domain_offset);
/* Set the Response and AA bits */
rsp->flags = htons(BIT(15) | BIT(10));
rsp->ancount = htons(1);
return offset;
}
/* TODO: dns_sd_handle_srv_query() */
/* TODO: dns_sd_handle_txt_query() */
bool dns_sd_rec_match(const struct dns_sd_rec *record,
const struct dns_sd_rec *filter)
{
size_t i;
const char *rec_label;
const char *filt_label;
static bool (*checkers[])(const char *) = {
instance_is_valid,
service_is_valid,
proto_is_valid,
domain_is_valid,
};
static const char *names[] = {
"instance",
"service",
"protocol",
"domain",
};
if (!rec_is_valid(record)) {
LOG_WRN("DNS SD record at %p is invalid", record);
return false;
}
if (filter == NULL) {
return false;
}
/* Deref only after it is deemed safe to do so */
const char *const pairs[] = {
record->instance, filter->instance,
record->service, filter->service,
record->proto, filter->proto,
record->domain, filter->domain,
};
BUILD_ASSERT(ARRAY_SIZE(pairs) == 2 * ARRAY_SIZE(checkers));
BUILD_ASSERT(ARRAY_SIZE(names) == ARRAY_SIZE(checkers));
for (i = 0; i < ARRAY_SIZE(checkers); ++i) {
rec_label = pairs[2 * i];
filt_label = pairs[2 * i + 1];
/* check for the "wildcard" pointer */
if (filt_label != NULL) {
if (!checkers[i](rec_label)) {
LOG_WRN("invalid %s label: '%s'",
names[i], rec_label);
return false;
}
if (strncasecmp(rec_label, filt_label,
DNS_LABEL_MAX_SIZE) != 0) {
return false;
}
}
}
/* check for the "wildcard" port */
if (filter->port != NULL && *(filter->port) != 0) {
if (*(record->port) != *(filter->port)) {
return false;
}
}
return true;
}
int dns_sd_query_extract(const uint8_t *query, size_t query_size, struct dns_sd_rec *record,
char **label, size_t *size, size_t *n)
{
size_t i;
size_t offset;
size_t qlabels;
size_t qsize;
const size_t N = (n) ? (*n) : 0;
/*
* See RFC 6763, 7.2. Service Name Length Limits
*
* <sn>._tcp.<servicedomain>.<parentdomain>.
* <Instance>.<sn>._tcp.<servicedomain>.<parentdomain>.
* <sub>._sub.<sn>._tcp.<servicedomain>.<parentdomain>.
*/
__ASSERT(DNS_SD_MIN_LABELS <= N, "invalid number of labels %zu", N);
__ASSERT(!(query == NULL || label == NULL || size == NULL || n == NULL),
"one or more required arguments are NULL");
__ASSERT(query + query_size >= query, "query %p + query_size %zu wraps NULL", query,
query_size);
__ASSERT(label + N >= label, "label %p + n %zu wraps NULL", label, N);
__ASSERT(size + N >= size, "size %p + n %zu wraps NULL", size, N);
for (i = 0; i < N; ++i) {
if (label[i] == NULL) {
__ASSERT(label[i] != NULL, "label[%zu] is NULL", i);
}
}
if (query_size <= DNS_MSG_HEADER_SIZE) {
NET_DBG("query size %zu is less than DNS_MSG_HEADER_SIZE %d", query_size,
DNS_MSG_HEADER_SIZE);
return -EINVAL;
}
query += DNS_MSG_HEADER_SIZE;
query_size -= DNS_MSG_HEADER_SIZE;
offset = DNS_MSG_HEADER_SIZE;
dns_sd_create_wildcard_filter(record);
/* valid record must have non-NULL port */
record->port = &dns_sd_port_zero;
/* also counts labels */
for (i = 0, qlabels = 0; query_size > 0;) {
qsize = *query;
++offset;
++query;
--query_size;
if (qsize == 0) {
break;
}
++qlabels;
if (qsize >= query_size) {
NET_DBG("claimed query size %zu > query buffer size %zu", qsize,
query_size);
return -EINVAL;
}
if (qsize > size[i]) {
NET_DBG("qsize %zu > size[%zu] %zu", qsize, i, size[i]);
return -ENOBUFS;
}
if (i < N) {
/* only extract the label if there is storage for it */
memcpy(label[i], query, qsize);
label[i][qsize] = '\0';
size[i] = qsize;
++i;
}
offset += qsize;
query += qsize;
query_size -= qsize;
}
/* write-out the actual number of labels in 'n' */
for (*n = i; i < N; ++i) {
label[i] = NULL;
size[i] = 0;
}
if (qlabels < DNS_SD_MIN_LABELS) {
NET_DBG("too few labels in query %zu, DNS_SD_MIN_LABELS: %d", qlabels,
DNS_SD_MIN_LABELS);
return -EINVAL;
} else if (qlabels == DNS_SD_MIN_LABELS) {
/* e.g. _zephyr._tcp.local */
record->service = label[0];
record->proto = label[1];
record->domain = label[2];
if (!service_is_valid(record->service)) {
NET_DBG("service '%s' is invalid", record->service);
return -EINVAL;
}
if (!proto_is_valid(record->proto)) {
NET_DBG("proto '%s' is invalid", record->proto);
return -EINVAL;
}
if (!domain_is_valid(record->domain)) {
NET_DBG("domain '%s' is invalid", record->domain);
return -EINVAL;
}
} else if (qlabels > DNS_SD_MIN_LABELS && qlabels < DNS_SD_MAX_LABELS) {
NET_DBG("unsupported number of labels %zu", qlabels);
return -EINVAL;
} else if (qlabels >= DNS_SD_MAX_LABELS) {
/* e.g.
* "Zephyr 42"._zephyr._tcp.local, or
* _domains._dns-sd._udp.local
*/
record->instance = label[0];
record->service = label[1];
record->proto = label[2];
record->domain = label[3];
if (!instance_is_valid(record->instance)) {
NET_DBG("service '%s' is invalid", record->instance);
return -EINVAL;
}
if (!service_is_valid(record->service)) {
NET_DBG("service '%s' is invalid", record->service);
return -EINVAL;
}
if (!proto_is_valid(record->proto)) {
NET_DBG("proto '%s' is invalid", record->proto);
return -EINVAL;
}
if (!domain_is_valid(record->domain)) {
NET_DBG("domain '%s' is invalid", record->domain);
return -EINVAL;
}
} else if (qlabels > N) {
NET_DBG("too few buffers to extract query: qlabels: %zu, N: %zu",
qlabels, N);
return -ENOBUFS;
}
return offset;
}
int dns_sd_extract_service_proto_domain(const uint8_t *query, size_t query_size,
struct dns_sd_rec *record, char *service,
size_t service_size, char *proto, size_t proto_size,
char *domain, size_t domain_size)
{
char instance[DNS_SD_INSTANCE_MAX_SIZE + 1];
char *label[4];
size_t size[] = {
ARRAY_SIZE(instance),
service_size,
proto_size,
domain_size,
};
size_t n = ARRAY_SIZE(label);
BUILD_ASSERT(ARRAY_SIZE(label) == ARRAY_SIZE(size),
"label and size arrays are different size");
/*
* work around for bug in compliance scripts which say that the array
* should be static const (incorrect)
*/
label[0] = instance;
label[1] = service;
label[2] = proto;
label[3] = domain;
return dns_sd_query_extract(query, query_size, record, label, size, &n);
}
bool dns_sd_is_service_type_enumeration(const struct dns_sd_rec *rec)
{
static const struct dns_sd_rec filter = {
.instance = "_services",
.service = "_dns-sd",
.proto = "_udp",
.domain = "local",
};
return dns_sd_rec_match(rec, &filter);
}
void dns_sd_create_wildcard_filter(struct dns_sd_rec *filter)
{
if (filter != NULL) {
memset(filter, 0, sizeof(*filter));
filter->text = dns_sd_empty_txt;
filter->text_size = sizeof(dns_sd_empty_txt);
}
}