From e7e58439e7a41295a23d3f29acd770b87172e6bf Mon Sep 17 00:00:00 2001 From: Christopher Friedt Date: Thu, 29 Oct 2020 14:56:30 -0400 Subject: [PATCH] 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 --- CODEOWNERS | 2 +- include/linker/common-rom.ld | 4 + include/net/dns_sd.h | 234 +++++++ subsys/net/lib/dns/CMakeLists.txt | 1 + subsys/net/lib/dns/Kconfig | 18 + subsys/net/lib/dns/dns_pack.h | 66 +- subsys/net/lib/dns/dns_sd.c | 972 ++++++++++++++++++++++++++++++ subsys/net/lib/dns/dns_sd.h | 141 +++++ 8 files changed, 1436 insertions(+), 2 deletions(-) create mode 100644 include/net/dns_sd.h create mode 100644 subsys/net/lib/dns/dns_sd.c create mode 100644 subsys/net/lib/dns/dns_sd.h diff --git a/CODEOWNERS b/CODEOWNERS index aae2c164ef560b..f7cce5f414c820 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -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 diff --git a/include/linker/common-rom.ld b/include/linker/common-rom.ld index 1ac39ddbfda93a..b6fce97399e007 100644 --- a/include/linker/common-rom.ld +++ b/include/linker/common-rom.ld @@ -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 = .; diff --git a/include/net/dns_sd.h b/include/net/dns_sd.h new file mode 100644 index 00000000000000..9b281149336254 --- /dev/null +++ b/include/net/dns_sd.h @@ -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 +#include + +#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 RFC 6763 + * + * @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 + * 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 RFC 6763 + */ +#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 + * #include + * 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 RFC 6763 + */ +#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 RFC 6763 + */ +struct dns_sd_rec { + /** - e.g. "My HTTP Server" */ + const char *instance; + /** Top half of the such as "_http" */ + const char *service; + /** Bottom half of the "_tcp" or "_udp" */ + const char *proto; + /** 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_ */ diff --git a/subsys/net/lib/dns/CMakeLists.txt b/subsys/net/lib/dns/CMakeLists.txt index 6605606ff4afe7..1dae89ea37d62d 100644 --- a/subsys/net/lib/dns/CMakeLists.txt +++ b/subsys/net/lib/dns/CMakeLists.txt @@ -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) diff --git a/subsys/net/lib/dns/Kconfig b/subsys/net/lib/dns/Kconfig index 99430d58d4d622..17453185ae8752 100644 --- a/subsys/net/lib/dns/Kconfig +++ b/subsys/net/lib/dns/Kconfig @@ -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 diff --git a/subsys/net/lib/dns/dns_pack.h b/subsys/net/lib/dns/dns_pack.h index 5853f8a44571f5..7f9d29b54076e6 100644 --- a/subsys/net/lib/dns/dns_pack.h +++ b/subsys/net/lib/dns/dns_pack.h @@ -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) { diff --git a/subsys/net/lib/dns/dns_sd.c b/subsys/net/lib/dns/dns_sd.c new file mode 100644 index 00000000000000..34ad069904f93f --- /dev/null +++ b/subsys/net/lib/dns/dns_sd.c @@ -0,0 +1,972 @@ +/* + * Copyright (c) 2020 Friedt Professional Engineering Services, Inc + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "dns_pack.h" +#include "dns_sd.h" + +#include +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 + * + *