net: dns: enable dns service discovery for mdns responder

This change enables support for DNS service discovery
(RFC 6763) in the mdns_responder service and sample app.

Fixes #29429

Signed-off-by: Christopher Friedt <chrisfriedt@gmail.com>
This commit is contained in:
Christopher Friedt 2020-11-09 10:26:21 -05:00 committed by Andrew Boie
commit 0fc80cf79f
10 changed files with 313 additions and 7 deletions

View file

@ -30,7 +30,7 @@ Build and run the mdns-responder sample application like this:
:goals: build
:compact:
After the mdns-responder sample application is started, it will wait queries
After the mdns-responder sample application is started, it will await queries
from the network.
Open a terminal window in your host and type:
@ -56,3 +56,20 @@ If the query is successful, then following information is printed:
.. code-block:: console
zephyr.local 2001:db8::1
Lastly, resolve services using DNS Service Discovery:
.. code-block:: console
$ avahi-browse -t -r _zephyr._tcp
If the query is successful, then the following information is printed:
.. code-block:: console
+ zeth IPv6 zephyr _zephyr._tcp local
= zeth IPv6 zephyr _zephyr._tcp local
hostname = [zephyr.local]
address = [192.0.2.1]
port = [4242]
txt = []

View file

@ -0,0 +1,11 @@
CONFIG_NET_IPV4=n
CONFIG_NET_CONFIG_NEED_IPV4=n
CONFIG_NET_CONFIG_MY_IPV4_ADDR=""
CONFIG_NET_CONFIG_PEER_IPV4_ADDR=""
CONFIG_NET_CONFIG_MY_IPV4_GW=""
CONFIG_NET_L2_IEEE802154=y
CONFIG_NET_L2_IEEE802154_SHELL=y
CONFIG_NET_L2_IEEE802154_LOG_LEVEL_INF=y
CONFIG_NET_CONFIG_IEEE802154_CHANNEL=26

View file

@ -0,0 +1,17 @@
CONFIG_BT=y
CONFIG_BT_DEBUG_LOG=y
CONFIG_BT_SMP=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_CENTRAL=y
CONFIG_BT_L2CAP_DYNAMIC_CHANNEL=y
CONFIG_BT_DEVICE_NAME="Zephyr Echo Server"
CONFIG_NET_L2_BT=y
CONFIG_NET_IPV4=n
CONFIG_NET_IPV6=y
CONFIG_NET_CONFIG_BT_NODE=y
CONFIG_NET_CONFIG_NEED_IPV6=y
CONFIG_NET_IPV4=n
CONFIG_NET_CONFIG_NEED_IPV4=n
CONFIG_NET_CONFIG_MY_IPV4_ADDR=""
CONFIG_NET_CONFIG_PEER_IPV4_ADDR=""
CONFIG_NET_CONFIG_MY_IPV4_GW=""

View file

@ -0,0 +1,7 @@
CONFIG_NET_L2_ETHERNET=y
CONFIG_NET_QEMU_ETHERNET=y
CONFIG_ETH_STELLARIS=y
CONFIG_NET_SLIP_TAP=n
CONFIG_SLIP=n

View file

@ -10,6 +10,8 @@ CONFIG_NET_HOSTNAME_UNIQUE=n
CONFIG_NET_HOSTNAME="zephyr"
CONFIG_MDNS_RESPONDER=y
CONFIG_DNS_SD=y
CONFIG_MDNS_RESPONDER_DNS_SD=y
CONFIG_ENTROPY_GENERATOR=y
CONFIG_TEST_RANDOM_GENERATOR=y
@ -36,3 +38,6 @@ CONFIG_NET_CONFIG_PEER_IPV6_ADDR="2001:db8::2"
CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.1"
CONFIG_NET_CONFIG_PEER_IPV4_ADDR="192.0.2.2"
CONFIG_NET_CONFIG_MY_IPV4_GW="192.0.2.2"
CONFIG_NET_SOCKETS=y
CONFIG_NET_SOCKETS_POSIX_NAMES=y

View file

@ -12,10 +12,13 @@ LOG_MODULE_REGISTER(net_mdns_responder_sample, LOG_LEVEL_DBG);
#include <zephyr.h>
#include <net/net_core.h>
extern void service(void);
/* Note that this application does not do anything itself.
* It is just a placeholder for waiting mDNS queries.
*/
void main(void)
{
LOG_INF("Waiting mDNS queries...");
service();
}

View file

@ -0,0 +1,144 @@
/*
* Copyright (c) 2020 Friedt Professional Engineering Services, Inc
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <net/dns_sd.h>
#include <net/socket.h>
#include <posix/netinet/in.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <zephyr.h>
#include <logging/log.h>
LOG_MODULE_REGISTER(mdns_echo_service, LOG_LEVEL_DBG);
/* A default port of 0 causes bind(2) to request an ephemeral port */
#define DEFAULT_PORT 0
static void welcome(int fd)
{
static const char msg[] = "Bonjour, Zephyr world!\n";
send(fd, msg, sizeof(msg), 0);
}
/* This is mainly here to bind to a port to get service advertisement
* to work.. but since we're already here we might as well do something
* useful.
*/
void service(void)
{
int r;
int server_fd;
int client_fd;
socklen_t len;
void *addrp;
uint16_t *portp;
struct sockaddr client_addr;
char addrstr[INET6_ADDRSTRLEN];
uint8_t line[64];
static struct sockaddr server_addr;
#if DEFAULT_PORT == 0
/* The advanced use case: ephemeral port */
#if defined(CONFIG_NET_IPV6)
DNS_SD_REGISTER_SERVICE(zephyr, CONFIG_NET_HOSTNAME,
"_zephyr", "_tcp", "local", DNS_SD_EMPTY_TXT,
&((struct sockaddr_in6 *)&server_addr)->sin6_port);
#elif defined(CONFIG_NET_IPV4)
DNS_SD_REGISTER_SERVICE(zephyr, CONFIG_NET_HOSTNAME,
"_zephyr", "_tcp", "local", DNS_SD_EMPTY_TXT,
&((struct sockaddr_in *)&server_addr)->sin_port);
#endif
#else
/* The simple use case: fixed port */
DNS_SD_REGISTER_TCP_SERVICE(zephyr, CONFIG_NET_HOSTNAME,
"_zephyr", "local", DNS_SD_EMPTY_TXT, DEFAULT_PORT);
#endif
if (IS_ENABLED(CONFIG_NET_IPV6)) {
net_sin6(&server_addr)->sin6_family = AF_INET6;
net_sin6(&server_addr)->sin6_addr = in6addr_any;
net_sin6(&server_addr)->sin6_port = sys_cpu_to_be16(DEFAULT_PORT);
} else if (IS_ENABLED(CONFIG_NET_IPV4)) {
net_sin(&server_addr)->sin_family = AF_INET;
net_sin(&server_addr)->sin_addr.s_addr = htonl(INADDR_ANY);
net_sin(&server_addr)->sin_port = sys_cpu_to_be16(DEFAULT_PORT);
} else {
__ASSERT(false, "Neither IPv6 nor IPv4 are enabled");
}
r = socket(server_addr.sa_family, SOCK_STREAM, 0);
if (r == -1) {
NET_DBG("socket() failed (%d)", errno);
return;
}
server_fd = r;
NET_DBG("server_fd is %d", server_fd);
r = bind(server_fd, &server_addr, sizeof(server_addr));
if (r == -1) {
NET_DBG("bind() failed (%d)", errno);
return;
}
if (server_addr.sa_family == AF_INET6) {
addrp = &net_sin6(&server_addr)->sin6_addr;
portp = &net_sin6(&server_addr)->sin6_port;
} else {
addrp = &net_sin(&server_addr)->sin_addr;
portp = &net_sin(&server_addr)->sin_port;
}
inet_ntop(server_addr.sa_family, addrp, addrstr, sizeof(addrstr));
NET_DBG("bound to [%s]:%u",
log_strdup(addrstr), ntohs(*portp));
r = listen(server_fd, 1);
if (r == -1) {
NET_DBG("listen() failed (%d)", errno);
return;
}
for (;;) {
len = sizeof(client_addr);
r = accept(server_fd, (struct sockaddr *)&client_addr, &len);
if (r == -1) {
NET_DBG("accept() failed (%d)", errno);
continue;
}
client_fd = r;
inet_ntop(server_addr.sa_family, addrp, addrstr, sizeof(addrstr));
NET_DBG("accepted connection from [%s]:%u",
log_strdup(addrstr), ntohs(*portp));
/* send a banner */
welcome(client_fd);
for (;;) {
/* echo 1 line at a time */
r = recv(client_fd, line, sizeof(line), 0);
if (r == -1) {
NET_DBG("recv() failed (%d)", errno);
close(client_fd);
break;
}
len = r;
r = send(client_fd, line, len, 0);
if (r == -1) {
NET_DBG("send() failed (%d)", errno);
close(client_fd);
break;
}
}
}
}

View file

@ -140,6 +140,16 @@ config MDNS_RESPONDER_INIT_PRIO
Note that if NET_CONFIG_AUTO_INIT is enabled, then this value
should be bigger than its value.
config MDNS_RESPONDER_DNS_SD
bool "Enable DNS Service Discovery via mDNS"
default y
depends on DNS_SD
help
Selecting this option ensures that the MDNS Responder
processes PTR, SRV, and TXT records according to RFC 6763.
By doing so, Zephyr network services are discoverable
using e.g. 'avahi-browse -t -r _greybus._tcp'.
module = MDNS_RESPONDER
module-dep = NET_LOG
module-str = Log level for mDNS responder

View file

@ -557,7 +557,10 @@ int dns_unpack_query(struct dns_msg_t *dns_msg, struct net_buf *buf,
}
query_type = dns_unpack_query_qtype(end_of_label);
if (query_type != DNS_RR_TYPE_A && query_type != DNS_RR_TYPE_AAAA) {
if (query_type != DNS_RR_TYPE_A && query_type != DNS_RR_TYPE_AAAA
&& query_type != DNS_RR_TYPE_PTR
&& query_type != DNS_RR_TYPE_SRV
&& query_type != DNS_RR_TYPE_TXT) {
return -EINVAL;
}

View file

@ -6,6 +6,7 @@
/*
* Copyright (c) 2017 Intel Corporation
* Copyright (c) 2020 Friedt Professional Engineering Services, Inc
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -20,10 +21,12 @@ LOG_MODULE_REGISTER(net_mdns_responder, CONFIG_MDNS_RESPONDER_LOG_LEVEL);
#include <errno.h>
#include <stdlib.h>
#include <net/net_core.h>
#include <net/net_ip.h>
#include <net/net_pkt.h>
#include <net/dns_resolve.h>
#include "dns_sd.h"
#include "dns_pack.h"
#include "ipv6.h"
@ -51,7 +54,6 @@ static struct net_context *ipv6;
NET_BUF_POOL_DEFINE(mdns_msg_pool, DNS_RESOLVER_BUF_CTR,
DNS_RESOLVER_MAX_BUF_SIZE, 0, NULL);
#if defined(CONFIG_NET_IPV6)
static void create_ipv6_addr(struct sockaddr_in6 *addr)
{
addr->sin6_family = AF_INET6;
@ -61,9 +63,7 @@ static void create_ipv6_addr(struct sockaddr_in6 *addr)
net_ipv6_addr_create(&addr->sin6_addr,
0xff02, 0, 0, 0, 0, 0, 0, 0x00fb);
}
#endif
#if defined(CONFIG_NET_IPV4)
static void create_ipv4_addr(struct sockaddr_in *addr)
{
addr->sin_family = AF_INET;
@ -72,7 +72,6 @@ static void create_ipv4_addr(struct sockaddr_in *addr)
/* Well known IPv4 224.0.0.251 address */
addr->sin_addr.s_addr = htonl(0xE00000FB);
}
#endif
static struct net_context *get_ctx(sa_family_t family)
{
@ -274,6 +273,92 @@ static int send_response(struct net_context *ctx,
return ret;
}
static const char *qtype_to_string(int qtype)
{
switch (qtype) {
case DNS_RR_TYPE_A: return "A";
case DNS_RR_TYPE_CNAME: return "CNAME";
case DNS_RR_TYPE_PTR: return "PTR";
case DNS_RR_TYPE_TXT: return "TXT";
case DNS_RR_TYPE_AAAA: return "AAAA";
case DNS_RR_TYPE_SRV: return "SRV";
default: return "<unknown type>";
}
}
static void send_sd_response(struct net_context *ctx,
struct net_pkt *pkt, union net_ip_header *ip_hdr,
struct dns_msg_t *dns_msg, struct net_buf *result)
{
int ret;
const struct dns_sd_rec *record;
struct dns_sd_rec filter;
struct sockaddr dst;
const struct in6_addr *addr6 = NULL;
const struct in_addr *addr4 = NULL;
char service_buf[DNS_SD_SERVICE_MAX_SIZE + 1];
char proto_buf[DNS_SD_PROTO_SIZE + 1];
char domain_buf[DNS_SD_DOMAIN_MAX_SIZE + 1];
/* This actually is used but the compiler doesn't see that */
ARG_UNUSED(record);
if (IS_ENABLED(CONFIG_NET_IPV4)) {
/* Look up the local IPv4 address */
addr4 = net_if_ipv4_select_src_addr(net_pkt_iface(pkt),
&ip_hdr->ipv4->src);
create_ipv4_addr(net_sin(&dst));
net_context_set_ipv4_ttl(ctx, 255);
}
if (IS_ENABLED(CONFIG_NET_IPV6)) {
/* Look up the local IPv6 address */
addr6 = net_if_ipv6_select_src_addr(net_pkt_iface(pkt),
&ip_hdr->ipv6->src);
create_ipv6_addr(net_sin6(&dst));
net_context_set_ipv6_hop_limit(ctx, 255);
}
ret = dns_sd_extract_service_proto_domain(dns_msg->msg,
dns_msg->msg_size, &filter, service_buf, sizeof(service_buf),
proto_buf, sizeof(proto_buf), domain_buf, sizeof(domain_buf));
if (ret < 0) {
NET_DBG("unable to extract service.proto.domain (%d)", ret);
return;
}
DNS_SD_FOREACH(record) {
/* Checks validity and then compare */
if (dns_sd_rec_match(record, &filter)) {
NET_DBG("matched query: %s.%s.%s.%s port: %u",
record->instance, record->service,
record->proto, record->domain,
ntohs(*(record->port)));
/* Construct the response */
ret = dns_sd_handle_ptr_query(record,
addr4, addr6,
result->data, result->size);
if (ret < 0) {
NET_DBG("dns_sd_handle_ptr_query() failed (%d)",
ret);
continue;
}
result->len = ret;
/* Send the response */
ret = net_context_sendto(ctx, result->data,
result->len, &dst, sizeof(dst), NULL,
K_NO_WAIT, NULL);
if (ret < 0) {
NET_DBG("Cannot send mDNS reply (%d)", ret);
continue;
}
}
}
}
static int dns_read(struct net_context *ctx,
struct net_pkt *pkt,
union net_ip_header *ip_hdr,
@ -344,7 +429,7 @@ static int dns_read(struct net_context *ctx,
}
NET_DBG("[%d] query %s/%s label %s (%d bytes)", queries,
qtype == DNS_RR_TYPE_A ? "A" : "AAAA", "IN",
qtype_to_string(qtype), "IN",
log_strdup(result->data), ret);
/* If the query matches to our hostname, then send reply.
@ -357,7 +442,11 @@ static int dns_read(struct net_context *ctx,
NET_DBG("mDNS query to our hostname %s.local",
hostname);
send_response(ctx, pkt, ip_hdr, result, qtype);
} else if (IS_ENABLED(CONFIG_MDNS_RESPONDER_DNS_SD)
&& qtype == DNS_RR_TYPE_PTR) {
send_sd_response(ctx, pkt, ip_hdr, &dns_msg, result);
}
} while (--queries);
ret = 0;