mdns: added support for searching services

Two new API:
err_t mdns_search_service(const char *name, const char *service, enum mdns_sd_proto proto,
                          struct netif *netif, search_result_fn_t result_fn, void *arg,
                          s8_t *request_id);
void mdns_search_stop(s8_t request_id);

One compilation flags:
LWIP_MDNS_SEARCH

One options flags:
MDNS_MAX_REQUESTS

Some structure declarations moved to allow use by callback result function.

Result domain names are early uncompress before calling application callback
because it cannot be made by application itself.

Allow search services with multiples labels included, like '_services._dns-sd'.

Search for `_services._dns-sd._udp.local.` is handled in a special way.
Only `PTR` answers are send back to the application.

The `mdns_search_service()` function won't assert if no more space in `mdns_request`
table, just return an error if too many simultanous requests.
This commit is contained in:
David Girault 2017-04-21 12:05:47 +02:00 committed by Simon Goldschmidt
parent 9263d44847
commit b4be0d8808
8 changed files with 438 additions and 31 deletions

View File

@ -99,6 +99,9 @@ static const ip_addr_t v6group = DNS_MQUERY_IPV6_GROUP_INIT;
#define MDNS_IP_TTL 255
#if LWIP_MDNS_SEARCH
static struct mdns_request mdns_requests[MDNS_MAX_REQUESTS];
#endif
static u8_t mdns_netif_client_id;
static struct udp_pcb *mdns_pcb;
@ -171,32 +174,12 @@ struct mdns_packet {
u16_t additional_left;
};
/** Domain, type and class.
* Shared between questions and answers */
struct mdns_rr_info {
struct mdns_domain domain;
u16_t type;
u16_t klass;
};
struct mdns_question {
struct mdns_rr_info info;
/** unicast reply requested */
u16_t unicast;
};
struct mdns_answer {
struct mdns_rr_info info;
/** cache flush command bit */
u16_t cache_flush;
/* Validity time in seconds */
u32_t ttl;
/** Length of variable answer */
u16_t rd_length;
/** Offset of start of variable answer in packet */
u16_t rd_offset;
};
struct mdns_answer_list {
u16_t offset[MDNS_PROBE_TIEBREAK_MAX_ANSWERS];
u16_t size;
@ -349,6 +332,45 @@ check_service(struct mdns_service *service, struct mdns_rr_info *rr)
return replies;
}
#if LWIP_MDNS_SEARCH
/**
* Check if question belong to a specified request
* @param request A ongoing MDNS request
* @param rr Domain/type/class from an answer
* @return Bitmask of which matching replies
*/
static int
check_request(struct mdns_request *request, struct mdns_rr_info *rr)
{
err_t res;
int replies = 0;
struct mdns_domain mydomain;
if (rr->klass != DNS_RRCLASS_IN && rr->klass != DNS_RRCLASS_ANY) {
/* Invalid class */
return 0;
}
res = mdns_build_request_domain(&mydomain, request, 0);
if (res == ERR_OK && mdns_domain_eq(&rr->domain, &mydomain) &&
(rr->type == DNS_RRTYPE_PTR || rr->type == DNS_RRTYPE_ANY)) {
/* Request for the instance of my service */
replies |= REPLY_SERVICE_TYPE_PTR;
}
res = mdns_build_request_domain(&mydomain, request, 1);
if (res == ERR_OK && mdns_domain_eq(&rr->domain, &mydomain)) {
/* Request for info about my service */
if (rr->type == DNS_RRTYPE_SRV || rr->type == DNS_RRTYPE_ANY) {
replies |= REPLY_SERVICE_SRV;
}
if (rr->type == DNS_RRTYPE_TXT || rr->type == DNS_RRTYPE_ANY) {
replies |= REPLY_SERVICE_TXT;
}
}
return replies;
}
#endif
/**
* Helper function for mdns_read_question/mdns_read_answer
* Reads a domain, type and class from the packet
@ -1687,6 +1709,26 @@ mdns_probe_conflict(struct netif *netif)
}
}
/**
* Loockup matching request for response MDNS packet
*/
#if LWIP_MDNS_SEARCH
static struct mdns_request *
mdns_lookup_request(struct mdns_rr_info *rr)
{
int i;
/* search originating request */
for (i = 0; i < MDNS_MAX_REQUESTS; i++) {
if ((mdns_requests[i].result_fn != NULL) &&
(check_request(&mdns_requests[i], rr) != 0)) {
return &mdns_requests[i];
}
}
return NULL;
}
#endif
/**
* Handle response MDNS packet:
* - Handle responses on probe query
@ -1700,6 +1742,10 @@ mdns_handle_response(struct mdns_packet *pkt, struct netif *netif)
{
struct mdns_host* mdns = NETIF_TO_HOST(netif);
u16_t total_answers_left;
#if LWIP_MDNS_SEARCH
struct mdns_request *req = NULL;
s8_t first = 1;
#endif
/* Ignore responses with a source port different from 5353
* (LWIP_IANA_PORT_MDNS) -> RFC6762 section 6 */
@ -1711,12 +1757,16 @@ mdns_handle_response(struct mdns_packet *pkt, struct netif *netif)
while (pkt->questions_left) {
struct mdns_question q;
err_t res;
res = mdns_read_question(pkt, &q);
if (res != ERR_OK) {
LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to parse question, skipping response packet\n"));
return;
}
#if LWIP_MDNS_SEARCH
else {
req = mdns_lookup_request(&q.info);
}
#endif
}
/* We need to check all resource record sections: answers, authoritative and additional */
total_answers_left = pkt->answers_left + pkt->authoritative_left + pkt->additional_left;
@ -1739,6 +1789,64 @@ mdns_handle_response(struct mdns_packet *pkt, struct netif *netif)
continue;
}
#if LWIP_MDNS_SEARCH
if (req && req->only_ptr) {
/* Need to recheck that this answer match request that match previous answer */
if (memcmp (req->service.name, ans.info.domain.name, req->service.length) != 0)
req = NULL;
}
if (!req) {
/* Try hard to search matching request */
req = mdns_lookup_request(&ans.info);
}
if (req && req->result_fn) {
int flags = (first ? MDNS_SEARCH_RESULT_FIRST : 0) |
(!total_answers_left ? MDNS_SEARCH_RESULT_LAST : 0);
if (req->only_ptr) {
if (ans.info.type != DNS_RRTYPE_PTR)
continue; /* Ignore non matching answer type */
flags = MDNS_SEARCH_RESULT_FIRST | MDNS_SEARCH_RESULT_LAST;
}
u16_t offset;
struct pbuf *p = pbuf_skip(pkt->pbuf, ans.rd_offset, &offset);
if (ans.info.type == DNS_RRTYPE_PTR || ans.info.type == DNS_RRTYPE_SRV)
{
/* Those RR types have compressed domain name. Must uncompress here,
since cannot be done without pbuf. */
struct {
u16_t values[3]; /* SRV: Prio, Weight, Port */
struct mdns_domain dom; /* PTR & SRV: Domain (uncompressed) */
} data;
u16_t off = (ans.info.type == DNS_RRTYPE_SRV ? 6 : 0);
u16_t len = mdns_readname(pkt->pbuf, ans.rd_offset + off, &data.dom);
if (len == MDNS_READNAME_ERROR) {
/* Ensure result_fn is called anyway, just copy failed domain as is */
data.dom.length = ans.rd_length - off;
memcpy(&data.dom, (const char *)p->payload + offset + off, data.dom.length);
}
/* Adjust len/off according RR type */
if (ans.info.type == DNS_RRTYPE_SRV)
{
memcpy(&data, (const char *)p->payload + offset, 6);
len = data.dom.length + 6;
off = 0;
}
else
{
len = data.dom.length;
off = 6;
}
req->result_fn(&ans, (const char *)&data + off, len, flags, req->arg);
}
else
{
/* Direct call result_fn with varpart pointing in pbuf payload */
req->result_fn(&ans, (const char *)p->payload + offset, ans.rd_length, flags, req->arg);
}
first = 0;
}
#endif
/* "Conflicting Multicast DNS responses received *before* the first probe
* packet is sent MUST be silently ignored" so drop answer if we haven't
* started probing yet. */
@ -2396,6 +2504,82 @@ mdns_resp_add_service_txtitem(struct mdns_service *service, const char *txt, u8_
return mdns_domain_add_label(&service->txtdata, txt, txt_len);
}
#if LWIP_MDNS_SEARCH
/**
* @ingroup mdns
* Stop a search request.
* @param req The search request to stop
*/
void
mdns_search_stop(s8_t request_id)
{
LWIP_ASSERT("mdns_search_stop: bad request_id", (request_id >= 0) && (request_id < MDNS_MAX_REQUESTS));
struct mdns_request *req = &mdns_requests[request_id];
if (req && req->result_fn)
req->result_fn = NULL;
}
/**
* @ingroup mdns
* Search a specific service on the network.
* @param name The name of the service
* @param service The service type, like "_http"
* @param proto The service protocol, DNSSD_PROTO_TCP for TCP ("_tcp") and DNSSD_PROTO_UDP
* for others ("_udp")
* @param netif The network interface where to send search request
* @param result_fn Callback to send answer received. Will be called for each answer of a
* responce frame matching request sent
* @param arg Userdata pointer for result_fn
* @param request_id Returned request identifier to allow stop it.
* @return ERR_OK if the search request was created and sent, an err_t otherwise
*/
err_t
mdns_search_service(const char *name, const char *service, enum mdns_sd_proto proto,
struct netif *netif, search_result_fn_t result_fn, void *arg,
s8_t *request_id)
{
int i;
s8_t slot = -1;
struct mdns_request *req;
if (name)
LWIP_ERROR("mdns_search_service: Name too long", (strlen(name) <= MDNS_LABEL_MAXLEN), return ERR_VAL);
LWIP_ERROR("mdns_search_service: Service too long", (strlen(service) < MDNS_DOMAIN_MAXLEN), return ERR_VAL);
LWIP_ERROR("mdns_search_service: Bad reqid pointer", request_id, return ERR_VAL);
LWIP_ERROR("mdns_search_service: Bad proto (need TCP or UDP)", (proto == DNSSD_PROTO_TCP || proto == DNSSD_PROTO_UDP), return ERR_VAL);
for (i = 0; i < MDNS_MAX_REQUESTS; i++) {
if (mdns_requests[i].result_fn == NULL) {
slot = i;
break;
}
}
if (slot < 0)
/* Don't assert if no more space in mdns_request table. Just return an error. */
return ERR_MEM;
req = &mdns_requests[slot];
memset(req, 0, sizeof(struct mdns_request));
req->result_fn = result_fn;
req->arg = arg;
req->proto = (u16_t)proto;
req->qtype = DNS_RRTYPE_PTR;
if (proto == DNSSD_PROTO_UDP && strcmp(service, "_services._dns-sd") == 0)
req->only_ptr = 1; /* don't check other answers */
mdns_domain_add_string(&req->service, service);
if (name)
MEMCPY(&req->name, name, LWIP_MIN(MDNS_LABEL_MAXLEN, strlen(name)));
/* save request id (slot) in pointer provided by caller */
*request_id = slot;
/* now prepare a MDNS request and send it (on specified interface) */
#if LWIP_IPV6
mdns_send_request(req, netif, IP6_ADDR_ANY);
#endif
#if LWIP_IPV4
mdns_send_request(req, netif, IP4_ADDR_ANY);
#endif
return ERR_OK;
}
#endif
/**
* @ingroup mdns
* Send unsolicited answer containing all our known data
@ -2487,6 +2671,9 @@ mdns_resp_init(void)
/* LWIP_ASSERT_CORE_LOCKED(); is checked by udp_new() */
#if LWIP_MDNS_SEARCH
memset(mdns_requests, 0, sizeof(mdns_requests));
#endif
mdns_pcb = udp_new_ip_type(IPADDR_TYPE_ANY);
LWIP_ASSERT("Failed to allocate pcb", mdns_pcb != NULL);
#if LWIP_MULTICAST_TX_OPTIONS

View File

@ -136,6 +136,67 @@ mdns_domain_add_label_pbuf(struct mdns_domain *domain, const struct pbuf *p, u16
return ERR_OK;
}
/**
* Add a partial domain to a domain
* @param domain The domain to add a label to
* @param source The domain to add, like &lt;\x09_services\007_dns-sd\000&gt;
* @return ERR_OK on success, an err_t otherwise if label too long
*/
err_t
mdns_domain_add_domain(struct mdns_domain *domain, struct mdns_domain *source)
{
u8_t len = source->length;
if (len > 0 && (1 + len + domain->length >= MDNS_DOMAIN_MAXLEN)) {
return ERR_VAL;
}
/* Allow only zero marker on last byte */
if (len == 0 && (1 + domain->length > MDNS_DOMAIN_MAXLEN)) {
return ERR_VAL;
}
if (len) {
/* Copy partial domain */
MEMCPY(&domain->name[domain->length], source->name, len);
domain->length += len;
}
else {
/* Add zero marker */
domain->name[domain->length] = len;
domain->length++;
}
return ERR_OK;
}
/**
* Add a string domain to a domain
* @param domain The domain to add a label to
* @param source The string to add, like &lt;_services._dns-sd&gt;
* @return ERR_OK on success, an err_t otherwise if label too long
*/
err_t
mdns_domain_add_string(struct mdns_domain *domain, const char *source)
{
u8_t *len = &domain->name[domain->length];
u8_t *end = &domain->name[MDNS_DOMAIN_MAXLEN];
u8_t *start = len + 1;
*len = 0;
while (*source && start < end) {
if (*source == '.') {
len = start++;
*len = 0;
source++;
}
else {
*start++ = *source++;
*len = *len + 1;
}
}
if (start == end)
return ERR_VAL;
domain->length = start - &domain->name[0];
return ERR_OK;
}
/**
* Internal readname function with max 6 levels of recursion following jumps
* while decompressing name
@ -433,6 +494,33 @@ mdns_build_service_domain(struct mdns_domain *domain, struct mdns_service *servi
return mdns_add_dotlocal(domain);
}
#if LWIP_MDNS_SEARCH
/**
* Build domain name for a request
* @param domain Where to write the domain name
* @param request The request struct, containing service name, type and protocol
* @param include_name Whether to include the service name in the domain
* @return ERR_OK if domain was written. If service name is included,
* <name>.<type>.<proto>.local. will be written, otherwise <type>.<proto>.local.
* An err_t is returned on error.
*/
err_t
mdns_build_request_domain(struct mdns_domain *domain, struct mdns_request *request, int include_name)
{
err_t res;
memset(domain, 0, sizeof(struct mdns_domain));
if (include_name) {
res = mdns_domain_add_label(domain, request->name, (u8_t)strlen(request->name));
LWIP_ERROR("mdns_build_request_domain: Failed to add label", (res == ERR_OK), return res);
}
res = mdns_domain_add_domain(domain, &request->service);
LWIP_ERROR("mdns_build_request_domain: Failed to add domain", (res == ERR_OK), return res);
res = mdns_domain_add_label(domain, dnssd_protos[request->proto], (u8_t)strlen(dnssd_protos[request->proto]));
LWIP_ERROR("mdns_build_request_domain: Failed to add label", (res == ERR_OK), return res);
return mdns_add_dotlocal(domain);
}
#endif
/**
* Return bytes needed to write before jump for best result of compressing supplied domain
* against domain in outpacket starting at specified offset.

View File

@ -549,6 +549,28 @@ mdns_add_probe_questions_to_outpacket(struct mdns_outpacket *outpkt, struct mdns
return ERR_OK;
}
#if LWIP_MDNS_SEARCH
static err_t
mdns_add_query_question_to_outpacket(struct mdns_outpacket *outpkt, struct mdns_outmsg *msg)
{
err_t res;
/* Write legacy query question */
if(msg->query) {
struct mdns_request *req = msg->query;
struct mdns_domain dom;
/* Build question domain */
mdns_build_request_domain(&dom, req, req->name[0]);
/* Add query question to output packet */
res = mdns_add_question(outpkt, &dom, req->qtype, DNS_RRCLASS_IN, 0);
if (res != ERR_OK) {
return res;
}
outpkt->questions++;
}
return ERR_OK;
}
#endif
/**
* Create packet with chosen answers as a reply
*
@ -565,6 +587,12 @@ mdns_create_outpacket(struct netif *netif, struct mdns_outmsg *msg,
int i;
u16_t answers = 0;
#if LWIP_MDNS_SEARCH
res = mdns_add_query_question_to_outpacket(outpkt, msg);
if (res != ERR_OK) {
return res;
}
#endif
res = mdns_add_probe_questions_to_outpacket(outpkt, msg, netif);
if (res != ERR_OK) {
@ -1107,4 +1135,32 @@ mdns_set_timeout(struct netif *netif, u32_t msecs, sys_timeout_handler handler,
*busy_flag = 1;
}
#ifdef LWIP_MDNS_SEARCH
/**
* Send search request containing all our known data
* @param req The request to send
* @param netif The network interface to send on
* @param destination The target address to send to (usually multicast address)
*/
err_t
mdns_send_request(struct mdns_request *req, struct netif *netif, const ip_addr_t *destination)
{
struct mdns_outmsg outmsg;
err_t res;
memset(&outmsg, 0, sizeof(outmsg));
outmsg.query = req;
outmsg.dest_port = LWIP_IANA_PORT_MDNS;
SMEMCPY(&outmsg.dest_addr, destination, sizeof(outmsg.dest_addr));
res = mdns_send_outpacket(&outmsg, netif);
if(res != ERR_OK) {
LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Multicast query request send failed\n"));
}
else {
LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Multicast query request send successful\n"));
}
return res;
}
#endif
#endif /* LWIP_MDNS_RESPONDER */

View File

@ -57,10 +57,42 @@ enum mdns_sd_proto {
#define MDNS_PROBING_SUCCESSFUL 1
#define MDNS_LABEL_MAXLEN 63
#define MDNS_DOMAIN_MAXLEN 256
struct mdns_host;
struct mdns_service;
/* Domain structs - also visible for unit tests */
struct mdns_domain {
/* Encoded domain name */
u8_t name[MDNS_DOMAIN_MAXLEN];
/* Total length of domain name, including zero */
u16_t length;
/* Set if compression of this domain is not allowed */
u8_t skip_compression;
};
/** Domain, type and class.
* Shared between questions and answers */
struct mdns_rr_info {
struct mdns_domain domain;
u16_t type;
u16_t klass;
};
struct mdns_answer {
struct mdns_rr_info info;
/** cache flush command bit */
u16_t cache_flush;
/* Validity time in seconds */
u32_t ttl;
/** Length of variable answer */
u16_t rd_length;
/** Offset of start of variable answer in packet */
u16_t rd_offset;
};
/** Callback function to add text to a reply, called when generating the reply */
typedef void (*service_get_txt_fn_t)(struct mdns_service *service, void *txt_userdata);
@ -97,6 +129,19 @@ void mdns_resp_announce(struct netif *netif);
*/
#define mdns_resp_netif_settings_changed(netif) mdns_resp_announce(netif)
#if LWIP_MDNS_SEARCH
typedef void (*search_result_fn_t)(struct mdns_answer *answer, const char *varpart, int varlen, int flags, void *arg);
/* flags bits, both can be set! */
#define MDNS_SEARCH_RESULT_FIRST 1 /* First answer in received frame. */
#define MDNS_SEARCH_RESULT_LAST 2 /* Last answer. */
err_t mdns_search_service(const char *name, const char *service, enum mdns_sd_proto proto,
struct netif *netif, search_result_fn_t result_fn, void *arg,
s8_t *request_id);
void mdns_search_stop(s8_t request_id);
#endif /* LWIP_MDNS_SEARCH */
#endif /* LWIP_MDNS_RESPONDER */
#ifdef __cplusplus

View File

@ -51,6 +51,8 @@ extern "C" {
/* Domain methods - also visible for unit tests */
err_t mdns_domain_add_label(struct mdns_domain *domain, const char *label, u8_t len);
err_t mdns_domain_add_domain(struct mdns_domain *domain, struct mdns_domain *source);
err_t mdns_domain_add_string(struct mdns_domain *domain, const char *source);
u16_t mdns_readname(struct pbuf *p, u16_t offset, struct mdns_domain *domain);
void mdns_domain_debug_print(struct mdns_domain *domain);
int mdns_domain_eq(struct mdns_domain *a, struct mdns_domain *b);
@ -63,6 +65,9 @@ err_t mdns_build_reverse_v6_domain(struct mdns_domain *domain, const ip6_addr_t
err_t mdns_build_host_domain(struct mdns_domain *domain, struct mdns_host *mdns);
err_t mdns_build_dnssd_domain(struct mdns_domain *domain);
err_t mdns_build_service_domain(struct mdns_domain *domain, struct mdns_service *service, int include_name);
#if LWIP_MDNS_SEARCH
err_t mdns_build_request_domain(struct mdns_domain *domain, struct mdns_request *request, int include_name);
#endif
u16_t mdns_compress_domain(struct pbuf *pbuf, u16_t *offset, struct mdns_domain *domain);
err_t mdns_write_domain(struct mdns_outpacket *outpkt, struct mdns_domain *domain);

View File

@ -67,6 +67,18 @@
#define MDNS_RESP_USENETIF_EXTCALLBACK LWIP_NETIF_EXT_STATUS_CALLBACK
#endif
/**
* LWIP_MDNS_SEARCH==1: Turn on search over multicast DNS module.
*/
#ifndef LWIP_MDNS_SEARCH
#define LWIP_MDNS_SEARCH 1
#endif
/** The maximum number of running requests */
#ifndef MDNS_MAX_REQUESTS
#define MDNS_MAX_REQUESTS 2
#endif
/**
* MDNS_DEBUG: Enable debugging for multicast DNS.
*/

View File

@ -125,6 +125,9 @@ void mdns_send_unicast_msg_delayed_ipv6(void *arg);
void mdns_start_multicast_timeouts_ipv6(struct netif *netif);
#endif
void mdns_prepare_txtdata(struct mdns_service *service);
#ifdef LWIP_MDNS_SEARCH
err_t mdns_send_request(struct mdns_request *req, struct netif *netif, const ip_addr_t *destination);
#endif
#endif /* LWIP_MDNS_RESPONDER */

View File

@ -48,7 +48,6 @@ extern "C" {
#if LWIP_MDNS_RESPONDER
#define MDNS_DOMAIN_MAXLEN 256
#define MDNS_READNAME_ERROR 0xFFFF
#define NUM_DOMAIN_OFFSETS 10
@ -72,16 +71,24 @@ extern "C" {
#define MDNS_PROBE_MAX_CONFLICTS_TIME_WINDOW 10000
#define MDNS_PROBE_MAX_CONFLICTS_TIMEOUT 5000
/* Domain structs - also visible for unit tests */
struct mdns_domain {
/* Encoded domain name */
u8_t name[MDNS_DOMAIN_MAXLEN];
/* Total length of domain name, including zero */
u16_t length;
/* Set if compression of this domain is not allowed */
u8_t skip_compression;
#if LWIP_MDNS_SEARCH
/** Description of a search request */
struct mdns_request {
/** Name of service, like 'myweb' */
char name[MDNS_LABEL_MAXLEN + 1];
/** Type of service, like '_http' or '_services._dns-sd' */
struct mdns_domain service;
/** Callback function called for each response */
search_result_fn_t result_fn;
void *arg;
/** Protocol, TCP or UDP */
u16_t proto;
/** Query type (PTR, SRV, ...) */
u8_t qtype;
/** PTR only request. */
u16_t only_ptr;
};
#endif
/** Description of a service */
struct mdns_service {
@ -149,6 +156,10 @@ struct mdns_outmsg {
u8_t host_reverse_v6_replies;
/* Reply bitmask per service */
u8_t serv_replies[MDNS_MAX_SERVICES];
#ifdef LWIP_MDNS_SEARCH
/** Search query to send */
struct mdns_request *query;
#endif
};
/** Delayed msg info */