net: dns: dns-sd: support dns service discovery
This change adds support for DNS Service Discovery (DNS-SD) as described in RFC 6763. Fixes #29099 Signed-off-by: Christopher Friedt <chrisfriedt@gmail.com>
This commit is contained in:
parent
5c691491a7
commit
e7e58439e7
|
@ -532,7 +532,7 @@
|
|||
/subsys/net/buf.c @jukkar @jhedberg @tbursztyka @pfalcon
|
||||
/subsys/net/ip/ @jukkar @tbursztyka @pfalcon
|
||||
/subsys/net/lib/ @jukkar @tbursztyka @pfalcon
|
||||
/subsys/net/lib/dns/ @jukkar @tbursztyka @pfalcon
|
||||
/subsys/net/lib/dns/ @jukkar @tbursztyka @pfalcon @cfriedt
|
||||
/subsys/net/lib/lwm2m/ @rlubos
|
||||
/subsys/net/lib/config/ @jukkar @tbursztyka @pfalcon
|
||||
/subsys/net/lib/mqtt/ @jukkar @tbursztyka @rlubos
|
||||
|
|
|
@ -134,6 +134,10 @@
|
|||
} GROUP_LINK_IN(ROMABLE_REGION)
|
||||
#endif /* CONFIG_EMUL */
|
||||
|
||||
#if defined(CONFIG_DNS_SD)
|
||||
Z_ITERABLE_SECTION_ROM(dns_sd_rec, 4)
|
||||
#endif
|
||||
|
||||
SECTION_DATA_PROLOGUE(log_const_sections,,)
|
||||
{
|
||||
__log_const_start = .;
|
||||
|
|
234
include/net/dns_sd.h
Normal file
234
include/net/dns_sd.h
Normal file
|
@ -0,0 +1,234 @@
|
|||
/** @file
|
||||
* @brief DNS Service Discovery
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2020 Friedt Professional Engineering Services, Inc
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef ZEPHYR_INCLUDE_NET_DNS_SD_H_
|
||||
#define ZEPHYR_INCLUDE_NET_DNS_SD_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sys/byteorder.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief DNS Service Discovery
|
||||
*
|
||||
* @details This API enables services to be advertised via DNS. To
|
||||
* advvertise a service, system or application code should use
|
||||
* @ref DNS_SD_REGISTER_TCP_SERVICE or
|
||||
* @ref DNS_SD_REGISTER_UDP_SERVICE.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc6763">RFC 6763</a>
|
||||
*
|
||||
* @defgroup dns_sd DNS Service Discovery
|
||||
* @ingroup networking
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** RFC 1034 Section 3.1 */
|
||||
#define DNS_SD_INSTANCE_MIN_SIZE 1
|
||||
/** RFC 1034 Section 3.1, RFC 6763 Section 7.2 */
|
||||
#define DNS_SD_INSTANCE_MAX_SIZE 63
|
||||
/** RFC 6763 Section 7.2 - inclusive of underscore */
|
||||
#define DNS_SD_SERVICE_MIN_SIZE 2
|
||||
/** RFC 6763 Section 7.2 - inclusive of underscore */
|
||||
#define DNS_SD_SERVICE_MAX_SIZE 16
|
||||
/** RFC 6763 Section 4.1.2 */
|
||||
#define DNS_SD_SERVICE_PREFIX '_'
|
||||
/** RFC 6763 Section 4.1.2 - either _tcp or _udp (case insensitive) */
|
||||
#define DNS_SD_PROTO_SIZE 4
|
||||
/** ICANN Rules for TLD naming */
|
||||
#define DNS_SD_DOMAIN_MIN_SIZE 2
|
||||
/** RFC 1034 Section 3.1, RFC 6763 Section 7.2 */
|
||||
#define DNS_SD_DOMAIN_MAX_SIZE 63
|
||||
|
||||
/**
|
||||
* @brief Register a service for DNS Service Discovery
|
||||
*
|
||||
* This macro should be used for advanced use cases. Two simple use cases are
|
||||
* when a custom @p domain or a custom (non-standard) @p proto is required.
|
||||
*
|
||||
* Another use case is when the port number is not preassigned. That could
|
||||
* be for a number of reasons, but the most common use case would be for
|
||||
* ephemeral port usage - i.e. when the service is bound using port number 0.
|
||||
* In that case, Zephyr (like other OS's) will simply choose an unused port.
|
||||
* When using ephemeral ports, it can be helpful to assign @p port to the
|
||||
* @ref sockaddr_in.sin_port field of an IPv4 @ref sockaddr_in, or to the
|
||||
* @ref sockaddr_in6.sin6_port field of an IPv6 @ref sockaddr_in6.
|
||||
*
|
||||
* The service can be referenced using the @p id variable.
|
||||
*
|
||||
* @param id variable name for the DNS-SD service record
|
||||
* @param instance name of the service instance such as "My HTTP Server"
|
||||
* @param service name of the service, such as "_http"
|
||||
* @param proto protocol used by the service - either "_tcp" or "_udp"
|
||||
* @param domain the domain of the service, such as "local"
|
||||
* @param text information for the DNS TXT record
|
||||
* @param port a pointer to the port number that this service will use
|
||||
*/
|
||||
#define DNS_SD_REGISTER_SERVICE(id, instance, service, proto, domain, \
|
||||
text, port) \
|
||||
static const Z_STRUCT_SECTION_ITERABLE(dns_sd_rec, id) = { \
|
||||
instance, \
|
||||
service, \
|
||||
proto, \
|
||||
domain, \
|
||||
(const char *)text, \
|
||||
sizeof(text) - 1, \
|
||||
port \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Register a TCP service for DNS Service Discovery
|
||||
*
|
||||
* This macro can be used for service advertisement using DNS-SD.
|
||||
*
|
||||
* The service can be referenced using the @p id variable.
|
||||
*
|
||||
* Example (with TXT):
|
||||
* @code{c}
|
||||
* #include <net/dns_sd.h>
|
||||
* static const bar_txt[] = {
|
||||
* "\x06" "path=/"
|
||||
* "\x0f" "this=is the way"
|
||||
* "\x0e" "foo or=foo not"
|
||||
* "\x17" "this=has\0embedded\0nulls"
|
||||
* "\x04" "true"
|
||||
* };
|
||||
* // Possibly use an ephemeral port
|
||||
* // Possibly only assign bar_port when the service is running
|
||||
* static uint16_t bar_port;
|
||||
* DNS_SD_REGISTER_TCP_SERVICE(bar, CONFIG_NET_HOSTNAME,
|
||||
* "_bar", "local", bar_txt, &bar_port);
|
||||
* @endcode{c}
|
||||
*
|
||||
* TXT records begin with a single length byte (hex-encoded)
|
||||
* and contain key=value pairs. Thus, the length of the key-value pair
|
||||
* must not exceed 255 bytes. Care must be taken to ensure that the
|
||||
* encoded length value is correct.
|
||||
*
|
||||
* For additional rules on TXT encoding, see RFC 6763, Section 6.
|
||||
|
||||
* @param id variable name for the DNS-SD service record
|
||||
* @param instance name of the service instance such as "My HTTP Server"
|
||||
* @param service name of the service, such as "_http"
|
||||
* @param domain the domain of the service, such as "local"
|
||||
* @param text information for the DNS TXT record
|
||||
* @param port the port number that this service will use
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc6763">RFC 6763</a>
|
||||
*/
|
||||
#define DNS_SD_REGISTER_TCP_SERVICE(id, instance, service, domain, text, \
|
||||
port) \
|
||||
static const uint16_t id ## _port = sys_cpu_to_be16(port); \
|
||||
DNS_SD_REGISTER_SERVICE(id, instance, service, "_tcp", domain, \
|
||||
text, &id ## _port)
|
||||
|
||||
/**
|
||||
* @brief Register a UDP service for DNS Service Discovery
|
||||
*
|
||||
* This macro can be used for service advertisement using DNS-SD.
|
||||
*
|
||||
* The service can be referenced using the @p id variable.
|
||||
*
|
||||
* Example (no TXT):
|
||||
* @code{c}
|
||||
* #include <net/dns_sd.h>
|
||||
* #include <sys/byteorder.h>
|
||||
* static const foo_port = sys_cpu_to_be16(4242);
|
||||
* DNS_SD_REGISTER_UDP_SERVICE(foo, CONFIG_NET_HOSTNAME,
|
||||
* "_foo", DNS_SD_EMPTY_TXT, &foo_port);
|
||||
* @endcode{c}
|
||||
*
|
||||
* @param id variable name for the DNS-SD service record
|
||||
* @param instance name of the service instance such as "My TFTP Server"
|
||||
* @param service name of the service, such as "_tftp"
|
||||
* @param domain the domain of the service, such as "local" or "zephyrproject.org"
|
||||
* @param text information for the DNS TXT record
|
||||
* @param port a pointer to the port number that this service will use
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc6763">RFC 6763</a>
|
||||
*/
|
||||
#define DNS_SD_REGISTER_UDP_SERVICE(id, instance, service, domain, text, \
|
||||
port) \
|
||||
static const uint16_t id ## _port = sys_cpu_to_be16(port); \
|
||||
DNS_SD_REGISTER_SERVICE(id, instance, service, "_udp", domain, \
|
||||
text, &id ## _port)
|
||||
|
||||
/** Empty DNS-SD TXT specifier */
|
||||
#define DNS_SD_EMPTY_TXT dns_sd_empty_txt
|
||||
|
||||
/** @cond INTERNAL_HIDDEN */
|
||||
|
||||
/**
|
||||
* @brief DNS Service Discovery record
|
||||
*
|
||||
* This structure used in the implementation of RFC 6763 and should not
|
||||
* need to be accessed directly from application code.
|
||||
*
|
||||
* The @a port pointer must be non-NULL. When the value in @a port
|
||||
* is non-zero, the service is advertized as being on that particular
|
||||
* port. When the value in @a port is zero, then the service is not
|
||||
* advertised.
|
||||
*
|
||||
* Thus, it is possible for multiple services to advertise on a
|
||||
* particular port if they hard-code the port.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc6763">RFC 6763</a>
|
||||
*/
|
||||
struct dns_sd_rec {
|
||||
/** <Instance> - e.g. "My HTTP Server" */
|
||||
const char *instance;
|
||||
/** Top half of the <Service> such as "_http" */
|
||||
const char *service;
|
||||
/** Bottom half of the <Service> "_tcp" or "_udp" */
|
||||
const char *proto;
|
||||
/** <Domain> such as "local" or "zephyrproject.org" */
|
||||
const char *domain;
|
||||
/** DNS TXT record */
|
||||
const char *text;
|
||||
/** Size (in bytes) of the DNS TXT record */
|
||||
size_t text_size;
|
||||
/** A pointer to the port number used by the service */
|
||||
const uint16_t *port;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Empty TXT specifier for DNS-SD
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
extern const char dns_sd_empty_txt[1];
|
||||
|
||||
/** @endcond */
|
||||
|
||||
/**
|
||||
* @brief Obtain the size of DNS-SD TXT data
|
||||
*
|
||||
* @param rec the record to in question
|
||||
* @return the size of the text field
|
||||
*/
|
||||
static inline size_t dns_sd_txt_size(const struct dns_sd_rec *rec)
|
||||
{
|
||||
return rec->text_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif /* ZEPHYR_INCLUDE_NET_DNS_SD_H_ */
|
|
@ -6,6 +6,7 @@ zephyr_library()
|
|||
zephyr_library_sources(dns_pack.c)
|
||||
|
||||
zephyr_library_sources_ifdef(CONFIG_DNS_RESOLVER resolve.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_DNS_SD dns_sd.c)
|
||||
|
||||
if(CONFIG_MDNS_RESPONDER)
|
||||
zephyr_library_sources(mdns_responder.c)
|
||||
|
|
|
@ -198,3 +198,21 @@ config LLMNR_RESOLVER_ADDITIONAL_BUF_CTR
|
|||
Number of additional buffers available for the LLMNR responder.
|
||||
|
||||
endif # LLMNR_RESPONDER
|
||||
|
||||
config DNS_SD
|
||||
bool "Enable DNS Service Discovery"
|
||||
help
|
||||
This option enables DNS Service Discovery for Zephyr. It can
|
||||
be enabled for virtually any network service with only a few
|
||||
lines of code and works for both Unicast and Multicast DNS.
|
||||
See RFC 6763 for more details about DNS-SD.
|
||||
|
||||
if DNS_SD
|
||||
|
||||
module = DNS_SD
|
||||
module-dep = NET_LOG
|
||||
module-str = Log level for DNS-SD
|
||||
module-help = Enables DNS Service Discovery code to output debug messages.
|
||||
source "subsys/net/Kconfig.template.log_config.net"
|
||||
|
||||
endif # DNS_SD
|
||||
|
|
|
@ -21,7 +21,10 @@
|
|||
|
||||
/* This is the label's length octet, see 4.1.2. Question section format */
|
||||
#define DNS_LABEL_LEN_SIZE 1
|
||||
#define DNS_POINTER_SIZE 2
|
||||
#define DNS_LABEL_MIN_SIZE 1
|
||||
#define DNS_LABEL_MAX_SIZE 63
|
||||
#define DNS_NAME_MAX_SIZE 255
|
||||
#define DNS_ANSWER_MIN_SIZE 12
|
||||
#define DNS_COMMON_UINT_SIZE 2
|
||||
|
||||
|
@ -86,7 +89,10 @@ enum dns_rr_type {
|
|||
DNS_RR_TYPE_INVALID = 0,
|
||||
DNS_RR_TYPE_A = 1, /* IPv4 */
|
||||
DNS_RR_TYPE_CNAME = 5, /* CNAME */
|
||||
DNS_RR_TYPE_AAAA = 28 /* IPv6 */
|
||||
DNS_RR_TYPE_PTR = 12, /* PTR */
|
||||
DNS_RR_TYPE_TXT = 16, /* TXT */
|
||||
DNS_RR_TYPE_AAAA = 28, /* IPv6 */
|
||||
DNS_RR_TYPE_SRV = 33, /* SRV */
|
||||
};
|
||||
|
||||
enum dns_response_type {
|
||||
|
@ -99,6 +105,7 @@ enum dns_response_type {
|
|||
enum dns_class {
|
||||
DNS_CLASS_INVALID = 0,
|
||||
DNS_CLASS_IN,
|
||||
DNS_CLASS_FLUSH = BIT(15)
|
||||
};
|
||||
|
||||
enum dns_msg_type {
|
||||
|
@ -115,6 +122,63 @@ enum dns_header_rcode {
|
|||
DNS_HEADER_REFUSED
|
||||
};
|
||||
|
||||
struct dns_header {
|
||||
/** Transaction ID */
|
||||
uint16_t id;
|
||||
/**
|
||||
* | Name | Bit Position | Width | Description |
|
||||
* |------|--------------|-------|-------------|
|
||||
* | RCODE | 0 | 4 | Response / Error code |
|
||||
* | CD | 4 | 1 | |
|
||||
* | AD | 5 | 1 | Authenticated Data. 0 := Unacceptable, 1 := Acceptable |
|
||||
* | Z | 6 | 1 | Reserved (WZ/RAZ) |
|
||||
* | RA | 7 | 1 | Recursion Available. 0 := Unavailable, 1 := Available |
|
||||
* | RD | 8 | 1 | Recursion Desired. 0 := No Recursion, 1 := Recursion |
|
||||
* | TC | 9 | 1 | 0 := Not Truncated, 1 := Truncated |
|
||||
* | AA | 10 | 1 | Answer Authenticated / Answer Authoritative. 0 := Not Authenticated, 1 := Authenticated|
|
||||
* | Opcode | 11 | 4 | See @ref dns_opcode |
|
||||
* | QR | 15 | 1 | 0 := Query, 1 := Response |
|
||||
*/
|
||||
uint16_t flags;
|
||||
/** Query count */
|
||||
uint16_t qdcount;
|
||||
/** Answer count */
|
||||
uint16_t ancount;
|
||||
/** Authority count */
|
||||
uint16_t nscount;
|
||||
/** Additional information count */
|
||||
uint16_t arcount;
|
||||
/** Flexible array member for records */
|
||||
uint8_t data[];
|
||||
} __packed;
|
||||
|
||||
struct dns_query {
|
||||
uint16_t type;
|
||||
uint16_t class_;
|
||||
} __packed;
|
||||
|
||||
struct dns_rr {
|
||||
uint16_t type;
|
||||
uint16_t class_;
|
||||
uint32_t ttl;
|
||||
uint16_t rdlength;
|
||||
uint8_t rdata[];
|
||||
} __packed;
|
||||
|
||||
struct dns_srv_rdata {
|
||||
uint16_t priority;
|
||||
uint16_t weight;
|
||||
uint16_t port;
|
||||
} __packed;
|
||||
|
||||
struct dns_a_rdata {
|
||||
uint32_t address;
|
||||
} __packed;
|
||||
|
||||
struct dns_aaaa_rdata {
|
||||
uint8_t address[16];
|
||||
} __packed;
|
||||
|
||||
/** It returns the ID field in the DNS msg header */
|
||||
static inline int dns_header_id(uint8_t *header)
|
||||
{
|
||||
|
|
972
subsys/net/lib/dns/dns_sd.c
Normal file
972
subsys/net/lib/dns/dns_sd.c
Normal file
|
@ -0,0 +1,972 @@
|
|||
/*
|
||||
* 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 <net/net_context.h>
|
||||
#include <net/net_core.h>
|
||||
#include <net/dns_sd.h>
|
||||
#include <sys/util.h>
|
||||
#include <zephyr.h>
|
||||
|
||||
#include "dns_pack.h"
|
||||
#include "dns_sd.h"
|
||||
|
||||
#include <logging/log.h>
|
||||
LOG_MODULE_REGISTER(net_dns_sd, CONFIG_DNS_SD_LOG_LEVEL);
|
||||
|
||||
const char dns_sd_empty_txt[1];
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
/* 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](filt_label)) {
|
||||
LOG_WRN("invalid %s label: '%s'",
|
||||
names[i], filt_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 && *(record->port) != *(filter->port)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
uint16_t offs;
|
||||
uint8_t label_size;
|
||||
|
||||
if (query == NULL || record == NULL || service == NULL
|
||||
|| proto == NULL || domain == NULL) {
|
||||
NET_DBG("one or more arguments are NULL");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (query_size <= DNS_MSG_HEADER_SIZE
|
||||
|| service_size < DNS_SD_SERVICE_MAX_SIZE + 1
|
||||
|| proto_size < DNS_SD_PROTO_SIZE + 1
|
||||
|| domain_size < DNS_SD_DOMAIN_MAX_SIZE + 1
|
||||
) {
|
||||
NET_DBG("one or more size arguments are too small");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
memset(record, 0, sizeof(*record));
|
||||
offs = DNS_MSG_HEADER_SIZE;
|
||||
|
||||
/* Copy service label to '\0'-terminated buffer */
|
||||
label_size = query[offs];
|
||||
if (label_size == 0 || label_size > service_size - 1
|
||||
|| offs + label_size > query_size) {
|
||||
NET_DBG("could not get service");
|
||||
return -EINVAL;
|
||||
} else {
|
||||
strncpy(service, &query[offs + 1], label_size);
|
||||
service[label_size] = '\0';
|
||||
offs += label_size + 1;
|
||||
}
|
||||
|
||||
/* Copy proto label to '\0'-terminated buffer */
|
||||
label_size = query[offs];
|
||||
if (label_size == 0 || label_size > proto_size - 1
|
||||
|| offs + label_size > query_size) {
|
||||
NET_DBG("could not get proto for '%s...'", service);
|
||||
return -EINVAL;
|
||||
} else {
|
||||
strncpy(proto, &query[offs + 1], label_size);
|
||||
proto[label_size] = '\0';
|
||||
offs += label_size + 1;
|
||||
}
|
||||
|
||||
/* Copy domain label to '\0'-terminated buffer */
|
||||
label_size = query[offs];
|
||||
if (label_size == 0 || label_size > domain_size - 1
|
||||
|| offs + label_size > query_size) {
|
||||
NET_DBG("could not get domain for '%s.%s...'", service,
|
||||
proto);
|
||||
return -EINVAL;
|
||||
} else {
|
||||
strncpy(domain, &query[offs + 1], label_size);
|
||||
domain[label_size] = '\0';
|
||||
offs += label_size + 1;
|
||||
}
|
||||
|
||||
/* Check that we have reached the DNS terminator */
|
||||
if (query[offs] != 0) {
|
||||
NET_DBG("ignoring request for '%s.%s.%s...'",
|
||||
service, proto, domain);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
offs++;
|
||||
record->service = service;
|
||||
record->proto = proto;
|
||||
record->domain = domain;
|
||||
|
||||
return offs;
|
||||
}
|
141
subsys/net/lib/dns/dns_sd.h
Normal file
141
subsys/net/lib/dns/dns_sd.h
Normal file
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Friedt Professional Engineering Services, Inc
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef DNS_SD_H_
|
||||
#define DNS_SD_H_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <net/dns_sd.h>
|
||||
#include <net/net_ip.h>
|
||||
|
||||
#include "dns_pack.h"
|
||||
|
||||
/* TODO: Move these into Kconfig */
|
||||
#define DNS_SD_PTR_TTL 4500
|
||||
#define DNS_SD_TXT_TTL 4500
|
||||
#define DNS_SD_SRV_TTL 120
|
||||
#define DNS_SD_A_TTL 120
|
||||
#define DNS_SD_AAAA_TTL 120
|
||||
|
||||
#define DNS_SD_PTR_MASK (NS_CMPRSFLGS << 8)
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define DNS_SD_FOREACH(it) \
|
||||
Z_STRUCT_SECTION_FOREACH(dns_sd_rec, it)
|
||||
|
||||
/**
|
||||
* @brief Extract the Service, Protocol, and Domain from a DNS-SD PTR query
|
||||
*
|
||||
* This function zero-initializes @p record and populates the appropriate
|
||||
* fields so that @p record may be subsequently passed to @ref dns_sd_rec_match.
|
||||
*
|
||||
* If a query with a supported format is found, the function returns the
|
||||
* length of the initial, variable-length portion of the query.
|
||||
*
|
||||
* For example, if the query begins with "._foo._tcp.local.", where
|
||||
* the '.' character represents a length of the subsequent string value,
|
||||
* then this function will return 17.
|
||||
*
|
||||
* @param query a pointer to the start of the query
|
||||
* @param query_size the number of bytes contained in the query
|
||||
* @param[out] record the DNS-SD record to initialize and populate
|
||||
* @param service buffer to store the null-terminated service
|
||||
* @param service_size the size of @p service
|
||||
* @param proto buffer to store the null-terminated proto
|
||||
* @param proto_size the size of @p proto
|
||||
* @param domain buffer to store the null-terminated domain
|
||||
* @param domain_size the size of @p domain
|
||||
* @return on success, a positive number representing length of the query
|
||||
* @return on failure, a negative errno value
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
* @brief See if the DNS SD @p filter matches the @p record
|
||||
*
|
||||
* The fields in @p filter should be populated with filter elements to
|
||||
* identify a possible match. If pointer fields are set to NULL, they
|
||||
* act as a wildcard in the matching process. I.e. they will match
|
||||
* anything. Similarly, the @ref dns_sd_rec.port field may be set to 0
|
||||
* to be used as a wildcard.
|
||||
*
|
||||
* The @ref dns_sd_rec.text and @ref dns_ds_rec.text_size fields
|
||||
* are not included in the matching process.
|
||||
*
|
||||
* For example, the filter below can be used to match any
|
||||
* "_http._tcp" records.
|
||||
*
|
||||
* @code{c}
|
||||
* const struct dns_sd_rec *it;
|
||||
* struct dns_sd_rec filter = {
|
||||
* // match any instance
|
||||
* .instance = NULL,
|
||||
* // match records with service "_http"
|
||||
* .service = "_http",
|
||||
* // match records with protocol "_tcp"
|
||||
* .proto = "_tcp",
|
||||
* // match any domain
|
||||
* .domain = NULL,
|
||||
* // match any port
|
||||
* .port = 0,
|
||||
* };
|
||||
*
|
||||
* DNS_SD_FOREACH(it) {
|
||||
* if (dns_sd_rec_match(it, filter)) {
|
||||
* // found a match!
|
||||
* }
|
||||
* }
|
||||
* @endcode{c}
|
||||
*
|
||||
* @param record The reference DNS-SD record
|
||||
* @param filter The DNS-SD record filter
|
||||
*
|
||||
* @return true if the @p record matches the @p filter
|
||||
* @return false if @p record is not a match for @p filter
|
||||
* @return false if either @p record or @p filter are invalid
|
||||
*/
|
||||
bool dns_sd_rec_match(const struct dns_sd_rec *record,
|
||||
const struct dns_sd_rec *filter);
|
||||
|
||||
/**
|
||||
* @brief Handle a DNS PTR Query with DNS Service Discovery
|
||||
*
|
||||
* This function should be called once for each DNS-SD record that
|
||||
* matches a particular DNS PTR query.
|
||||
*
|
||||
* If there is no IPv4 address to advertise, then @p addr4 should be
|
||||
* NULL.
|
||||
*
|
||||
* If there is no IPv6 address to advertise, then @p addr6 should be
|
||||
* NULL.
|
||||
*
|
||||
* @param inst the DNS-SD record for to advertise
|
||||
* @param addr4 pointer to the IPv4 address
|
||||
* @param addr6 pointer to the IPv6 address
|
||||
* @param buf output buffer
|
||||
* @param buf_size size of the output buffer
|
||||
*
|
||||
* @return on success, number of bytes written to @p buf
|
||||
* @return on failure, a negative errno value
|
||||
*/
|
||||
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);
|
||||
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif /* DNS_SD_H_ */
|
Loading…
Reference in a new issue