From 47834ec5b20982c0d9980f044af160238c7ebabe Mon Sep 17 00:00:00 2001 From: Jasper Verschueren Date: Tue, 30 Oct 2018 10:41:37 +0100 Subject: [PATCH] mDNS: responder upgraded with delay functionality + cleanup Squashed commit of the following: commit 833acba123af7aad92a4b70fc01403c69885bc89 Author: Jasper Verschueren Date: Thu Oct 25 09:41:00 2018 +0200 mDNS: add new files to filelists + solve compile errors ttl setting removed from example code. string.h included where needed. commit d45bcaf61346efd0d5da79a5dc9002136083f0b7 Author: Jasper Verschueren Date: Wed Oct 24 16:15:11 2018 +0200 mDNS: author comment update + things left to implement update commit e533d525da610f836f6a363174a130dc97386065 Author: Jasper Verschueren Date: Wed Oct 24 16:02:08 2018 +0200 mDNS: add #if LWIP_IPVx to new code LWIP_IPV4/6 can be enabled or disabled, all combination should work. commit 3240334d302f824d1d277c3509f711be49823fbb Author: Jasper Verschueren Date: Wed Oct 24 15:13:15 2018 +0200 mDNS: solve commenting and style issues commit 6e04523a50f49900d94fb54745802a009e37e2ab Author: Jasper Verschueren Date: Wed Oct 24 14:38:50 2018 +0200 mDNS: timeouts -> create function for mdns timeout handling mdns_set_timeout will check if the timer is running or not and will update the flag to running after starting the timer. Multicast timeouts were not set everywhere they needed to be. This is solved. mulit <-> multi typo fixed. commit 74b380a38328457ff6fe1e08898abc45ca811955 Author: Jasper Verschueren Date: Tue Oct 23 10:45:01 2018 +0200 mDNS: send multicast response on QU questions if not multicasted recently. If a QU question is received, the responder should multicast the answer if it did not multicast that record within 25% of it's ttl. we implemented a stripped down version, meaning that we look at the records as one set and use one timer for all records. So if the responder multicasted a record within 30s of the QU question it will respond with a unicast answer. if not, it will respond multicast. commit fb29425ade94407ffe639657467a13fea97a3585 Author: Jasper Verschueren Date: Mon Oct 22 16:44:40 2018 +0200 MDNS: change timeouts from max time to random time commit ea3febfc77069b48f2d8653a8716c56047cc6391 Author: Jasper Verschueren Date: Mon Oct 22 16:40:27 2018 +0200 MDNS: changed printfs to lwip debug messages commit 76275584306af516da1a070a03f93065c7f4be27 Author: Jasper Verschueren Date: Mon Oct 22 16:26:16 2018 +0200 mDNS: improved split for unicast vs multicast and direct vs delayed unicast delayed message are now possible and multicast direct msgs to. commit 945c6fe17a010b0be0747cf2cf5ea146d7a2e92b Author: Jasper Verschueren Date: Fri Oct 19 16:09:07 2018 +0200 mDNS: ignore responses with src port != 5353 (rfc6762 section 6) commit d2baf4c5dc47bd1fdd8fe3d707fd8d34f8714f86 Author: Jasper Verschueren Date: Fri Oct 19 13:58:49 2018 +0200 mDNS: source address check added RFC6762 section 5.5 and 11 define that we should check that all packets originated on the local link. commit 8f2dfdf44e3df30a0ff0045f6cc7ae025fd51dad Author: Jasper Verschueren Date: Wed Oct 17 14:03:40 2018 +0200 mDNS: do not multicast a rr within one second. RFC6762 section 6: prevent network flooding. When a multicast packet is send out, we start a timeout of 1s within this 1 second all multicast requests are ignored. We do not make a difference between the records, we set the delay for all records. commit eb57affd9ba15243bd5f735f67ea7b154821da0a Author: Jasper Verschueren Date: Wed Oct 17 13:47:02 2018 +0200 mDNS: addr bug, we should not clear full outmsg memset deleted also the dest_addr and dest_port, which should remain and is constant. This commit contains a function that resets only the needed parts of the outmsg struct. commit 02b4d8393785922cd62778e80cd6e05430c3b776 Author: Jasper Verschueren Date: Wed Oct 17 09:54:40 2018 +0200 mDNS split delayed multicast msgs into ipv4 and ipv6 buffers. We are implementing a two resolvers in one (IPv6 and IPv4 together). For directly send answers, this does not matter. But for delayed answers, we need to make a separate buffer for both. commit 01399ca63ec08053c3fd3a0a84f9500c14138a17 Author: Jasper Verschueren Date: Tue Oct 16 16:10:16 2018 +0200 mDNS: add probe query detection. If a probe query is detected a direct unicast respond is send. Independent of the QU/QM bit. commit d1686d1d823bf31690c4054cb53ac3bca3a2b9e1 Author: Jasper Verschueren Date: Mon Oct 15 16:16:05 2018 +0200 mDNS improved delay vs direct send behavior on questions All multicast answers are delayed at the moment. While all unicast answers are send out directly. A unicast answer is send when this is requested by the QU bit, when a unicast question was send or when the question originated from a legacy querier. commit 969c6aece4f3423a7f6c0e204f0360ecf3c40d33 Author: Jasper Verschueren Date: Fri Oct 12 15:35:02 2018 +0200 mDNS: support for direct and delayed sends There are two ways to send a response, directly and after a delay. A probe or an announce msg are now send via the direct way and all responses are send via the delayed way. commit 69b37f30407d76fa1354e0442c1fb9cd5c6c1002 Author: Jasper Verschueren Date: Fri Oct 12 10:01:04 2018 +0200 mDNS: remove netif from mDNS structs it's more practical to use netif as a wrapper instead of wrapping netif in the mdns packets. netif contains all information. Netif is passed along were needed. commit bf500b27e1934bb91692d193d49310c3a918863f Author: Jasper Verschueren Date: Thu Oct 11 15:16:21 2018 +0200 mDNS fix TTL bug MDNS_TTL changed to MDNS_IP_TTL for clarity. The mDNS RR ttl does not need to be settalbe, this is against the RFC. RFC6762 states that for rr's with the hostname in them somewhere, the ttl should be 120s. If it's not in their it should be 75 minutes and if the answer is send to a legacy dns querier, it should be no more then 10s. This patch corrects the ttl behavior to the RFC. commit 939e7fda84fe7767f20b950b9cfd665ef410d6a8 Author: Jasper Verschueren Date: Thu Oct 11 10:43:34 2018 +0200 mDNS split off output related functionality and put in other file A lot of functions are only needed for the generation of the pbuf, by separating them into another file we clean up the mdns file. We only need the mdns_send_outpacket function as interface. Packet definition is now completely separated from packet generation. commit a95c659e94eeb0507d8bdf60bf245188ad346983 Author: Jasper Verschueren Date: Wed Oct 10 14:37:22 2018 +0200 mDNS take out domain related functionality and put in other file. The mDNS file is getting very big and a lot still needs to be added. For clarity reasons it's better to split these domain functions from the main mDNS file. commit 3dbb0e15eea9c137b42b14cc567ca31bd74548e1 Author: Jasper Verschueren Date: Wed Oct 10 09:59:02 2018 +0200 mDNS: authority section of probe message needs to contain all records see RFC 6762 section 8.2: for tiebreaking to work correctly in all cases, the Authority section must contain all the records and proposed rdata being probed for uniqueness. commit bfe8159332fb00d6381e4f145a729bc897dd1e0a Author: Jasper Verschueren Date: Wed Oct 10 09:57:45 2018 +0200 mDNS: move legacy question generation to mdns_send_outpacket It's better to do the pbuf generation in one place. Especially important for message delaying etc. commit 0e1f49c0f1b373d0632c53833f8eac6e3bbc34be Author: Jasper Verschueren Date: Wed Oct 10 09:40:03 2018 +0200 mDNS move probe question packet generation to mdns_send_outpacket The probe any questions were added to the pbuf in the send_probe routine. It is better if we move all pbuf generation to the output function so later on packets can be delayed etc. keep it all in one place. --- contrib/examples/mdns/mdns_example.c | 4 +- src/Filelists.cmake | 2 + src/Filelists.mk | 4 +- src/apps/mdns/mdns.c | 1798 ++++++++------------------ src/apps/mdns/mdns_domain.c | 548 ++++++++ src/apps/mdns/mdns_out.c | 992 ++++++++++++++ src/include/lwip/apps/mdns.h | 8 +- src/include/lwip/apps/mdns_domain.h | 75 ++ src/include/lwip/apps/mdns_opts.h | 1 + src/include/lwip/apps/mdns_out.h | 121 ++ src/include/lwip/apps/mdns_priv.h | 127 +- test/unit/mdns/test_mdns.c | 1 + 12 files changed, 2436 insertions(+), 1245 deletions(-) create mode 100644 src/apps/mdns/mdns_domain.c create mode 100644 src/apps/mdns/mdns_out.c create mode 100644 src/include/lwip/apps/mdns_domain.h create mode 100644 src/include/lwip/apps/mdns_out.h diff --git a/contrib/examples/mdns/mdns_example.c b/contrib/examples/mdns/mdns_example.c index f5cc0585a..208048a2d 100644 --- a/contrib/examples/mdns/mdns_example.c +++ b/contrib/examples/mdns/mdns_example.c @@ -56,8 +56,8 @@ mdns_example_init(void) #if LWIP_MDNS_RESPONDER mdns_resp_register_name_result_cb(mdns_example_report); mdns_resp_init(); - mdns_resp_add_netif(netif_default, "lwip", 3600); - mdns_resp_add_service(netif_default, "myweb", "_http", DNSSD_PROTO_TCP, 80, 3600, srv_txt, NULL); + mdns_resp_add_netif(netif_default, "lwip"); + mdns_resp_add_service(netif_default, "myweb", "_http", DNSSD_PROTO_TCP, 80, srv_txt, NULL); mdns_resp_announce(netif_default); #endif } diff --git a/src/Filelists.cmake b/src/Filelists.cmake index 21830332f..6003c6fc0 100644 --- a/src/Filelists.cmake +++ b/src/Filelists.cmake @@ -199,6 +199,8 @@ set(lwipsntp_SRCS # MDNS responder set(lwipmdns_SRCS ${LWIP_DIR}/src/apps/mdns/mdns.c + ${LWIP_DIR}/src/apps/mdns/mdns_out.c + ${LWIP_DIR}/src/apps/mdns/mdns_domain.c ) # NetBIOS name server diff --git a/src/Filelists.mk b/src/Filelists.mk index 65ebda9ee..7e076f347 100644 --- a/src/Filelists.mk +++ b/src/Filelists.mk @@ -177,7 +177,9 @@ SMTPFILES=$(LWIPDIR)/apps/smtp/smtp.c SNTPFILES=$(LWIPDIR)/apps/sntp/sntp.c # MDNSFILES: MDNS responder -MDNSFILES=$(LWIPDIR)/apps/mdns/mdns.c +MDNSFILES=$(LWIPDIR)/apps/mdns/mdns.c \ + $(LWIPDIR)/apps/mdns/mdns_out.c \ + $(LWIPDIR)/apps/mdns/mdns_domain.c # NETBIOSNSFILES: NetBIOS name server NETBIOSNSFILES=$(LWIPDIR)/apps/netbiosns/netbiosns.c diff --git a/src/apps/mdns/mdns.c b/src/apps/mdns/mdns.c index aad272889..f7240fe24 100644 --- a/src/apps/mdns/mdns.c +++ b/src/apps/mdns/mdns.c @@ -14,11 +14,11 @@ * ------------------------- * * - Tiebreaking for simultaneous probing + * - Correct announcing method * - Sending goodbye messages (zero ttl) - shutdown, DHCP lease about to expire, DHCP turned off... - * - Checking that source address of unicast requests are on the same network - * - Limiting multicast responses to 1 per second per resource record + * - Sending negative responses NSEC * - Fragmenting replies if required - * - Handling multi-packet known answers + * - Handling multi-packet known answers (TC bit) * - Individual known answer detection for all local IPv6 addresses * - Dynamic size of outgoing packet */ @@ -52,11 +52,14 @@ * This file is part of the lwIP TCP/IP stack. * * Author: Erik Ekman + * Author: Jasper Verschueren * */ #include "lwip/apps/mdns.h" #include "lwip/apps/mdns_priv.h" +#include "lwip/apps/mdns_domain.h" +#include "lwip/apps/mdns_out.h" #include "lwip/netif.h" #include "lwip/udp.h" #include "lwip/ip_addr.h" @@ -78,6 +81,9 @@ #if (!LWIP_UDP) #error "If you want to use MDNS, you have to define LWIP_UDP=1 in your lwipopts.h" #endif +#ifndef LWIP_RAND +#error "If you want to use MDNS, you have to define LWIP_RAND=(random function) in your lwipopts.h" +#endif #if LWIP_IPV4 #include "lwip/igmp.h" @@ -91,14 +97,8 @@ static const ip_addr_t v4group = DNS_MQUERY_IPV4_GROUP_INIT; static const ip_addr_t v6group = DNS_MQUERY_IPV6_GROUP_INIT; #endif -#define MDNS_TTL 255 +#define MDNS_IP_TTL 255 -/* Stored offsets to beginning of domain names - * Used for compression. - */ -#define NUM_DOMAIN_OFFSETS 10 -#define DOMAIN_JUMP_SIZE 2 -#define DOMAIN_JUMP 0xc000 static u8_t mdns_netif_client_id; static struct udp_pcb *mdns_pcb; @@ -109,36 +109,13 @@ static mdns_name_result_cb_t mdns_name_result_cb; #define NETIF_TO_HOST(netif) (struct mdns_host*)(netif_get_client_data(netif, mdns_netif_client_id)) -#define TOPDOMAIN_LOCAL "local" - -#define REVERSE_PTR_TOPDOMAIN "arpa" -#define REVERSE_PTR_V4_DOMAIN "in-addr" -#define REVERSE_PTR_V6_DOMAIN "ip6" - -#define SRV_PRIORITY 0 -#define SRV_WEIGHT 0 - -/* Payload size allocated for each outgoing UDP packet */ -#define OUTPACKET_SIZE 500 - -/* Lookup from hostname -> IPv4 */ -#define REPLY_HOST_A 0x01 -/* Lookup from IPv4/v6 -> hostname */ -#define REPLY_HOST_PTR_V4 0x02 -/* Lookup from hostname -> IPv6 */ -#define REPLY_HOST_AAAA 0x04 -/* Lookup from hostname -> IPv6 */ -#define REPLY_HOST_PTR_V6 0x08 - -/* Lookup for service types */ -#define REPLY_SERVICE_TYPE_PTR 0x10 -/* Lookup for instances of service */ -#define REPLY_SERVICE_NAME_PTR 0x20 -/* Lookup for location of service instance */ -#define REPLY_SERVICE_SRV 0x40 -/* Lookup for text info on service instance */ -#define REPLY_SERVICE_TXT 0x80 +/** Delayed response defines */ +#define MDNS_RESPONSE_DELAY_MAX 120 +#define MDNS_RESPONSE_DELAY_MIN 20 +#define MDNS_RESPONSE_DELAY (LWIP_RAND() %(MDNS_RESPONSE_DELAY_MAX - \ + MDNS_RESPONSE_DELAY_MIN) + MDNS_RESPONSE_DELAY_MIN) +/** Probing defines */ #define MDNS_PROBE_DELAY_MS 250 #define MDNS_PROBE_COUNT 3 #ifdef LWIP_RAND @@ -152,45 +129,6 @@ static mdns_name_result_cb_t mdns_name_result_cb; #define MDNS_PROBING_ONGOING 1 #define MDNS_PROBING_COMPLETE 2 -static const char *dnssd_protos[] = { - "_udp", /* DNSSD_PROTO_UDP */ - "_tcp", /* DNSSD_PROTO_TCP */ -}; - -/** Description of a service */ -struct mdns_service { - /** TXT record to answer with */ - struct mdns_domain txtdata; - /** Name of service, like 'myweb' */ - char name[MDNS_LABEL_MAXLEN + 1]; - /** Type of service, like '_http' */ - char service[MDNS_LABEL_MAXLEN + 1]; - /** Callback function and userdata - * to update txtdata buffer */ - service_get_txt_fn_t txt_fn; - void *txt_userdata; - /** TTL in seconds of SRV/TXT replies */ - u32_t dns_ttl; - /** Protocol, TCP or UDP */ - u16_t proto; - /** Port of the service */ - u16_t port; -}; - -/** Description of a host/netif */ -struct mdns_host { - /** Hostname */ - char name[MDNS_LABEL_MAXLEN + 1]; - /** Pointer to services */ - struct mdns_service *services[MDNS_MAX_SERVICES]; - /** TTL in seconds of A/AAAA/PTR replies */ - u32_t dns_ttl; - /** Number of probes sent for the current name */ - u8_t probes_sent; - /** State in probing sequence */ - u8_t probing_state; -}; - /** Information about received packet */ struct mdns_packet { /** Sender IP/port */ @@ -198,8 +136,6 @@ struct mdns_packet { u16_t source_port; /** If packet was received unicast */ u16_t recv_unicast; - /** Netif that received the packet */ - struct netif *netif; /** Packet data */ struct pbuf *pbuf; /** Current parsing offset in packet */ @@ -211,51 +147,18 @@ struct mdns_packet { u16_t questions; /** Number of unparsed questions */ u16_t questions_left; - /** Number of answers in packet, - * (sum of normal, authoritative and additional answers) - * read from packet header */ + /** Number of answers in packet */ u16_t answers; /** Number of unparsed answers */ u16_t answers_left; -}; - -/** Information about outgoing packet */ -struct mdns_outpacket { - /** Netif to send the packet on */ - struct netif *netif; - /** Packet data */ - struct pbuf *pbuf; - /** Current write offset in packet */ - u16_t write_offset; - /** Identifier. Used in legacy queries */ - u16_t tx_id; - /** Destination IP/port if sent unicast */ - ip_addr_t dest_addr; - u16_t dest_port; - /** Number of questions written */ - u16_t questions; - /** Number of normal answers written */ - u16_t answers; - /** Number of authoritative answers written */ + /** Number of authoritative answers in packet */ u16_t authoritative; - /** Number of additional answers written */ + /** Number of unparsed authoritative answers */ + u16_t authoritative_left; + /** Number of additional answers in packet */ u16_t additional; - /** Offsets for written domain names in packet. - * Used for compression */ - u16_t domain_offsets[NUM_DOMAIN_OFFSETS]; - /** If all answers in packet should set cache_flush bit */ - u8_t cache_flush; - /** If reply should be sent unicast */ - u8_t unicast_reply; - /** If legacy query. (tx_id needed, and write - * question again in reply before answer) */ - u8_t legacy_query; - /* Reply bitmask for host information */ - u8_t host_replies; - /* Bitmask for which reverse IPv6 hosts to answer */ - u8_t host_reverse_v6_replies; - /* Reply bitmask per service */ - u8_t serv_replies[MDNS_MAX_SERVICES]; + /** Number of unparsed additional answers */ + u16_t additional_left; }; /** Domain, type and class. @@ -284,377 +187,31 @@ struct mdns_answer { u16_t rd_offset; }; -static err_t mdns_send_outpacket(struct mdns_outpacket *outpkt, u8_t flags); static void mdns_probe(void* arg); -static err_t -mdns_domain_add_label_base(struct mdns_domain *domain, u8_t len) -{ - if (len > MDNS_LABEL_MAXLEN) { - return ERR_VAL; - } - 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; - } - domain->name[domain->length] = len; - domain->length++; - return ERR_OK; -} - -/** - * Add a label part to a domain - * @param domain The domain to add a label to - * @param label The label to add, like <hostname>, 'local', 'com' or '' - * @param len The length of the label - * @return ERR_OK on success, an err_t otherwise if label too long - */ -err_t -mdns_domain_add_label(struct mdns_domain *domain, const char *label, u8_t len) -{ - err_t err = mdns_domain_add_label_base(domain, len); - if (err != ERR_OK) { - return err; - } - if (len) { - MEMCPY(&domain->name[domain->length], label, len); - domain->length += len; - } - return ERR_OK; -} - -/** - * Add a label part to a domain (@see mdns_domain_add_label but copy directly from pbuf) - */ -static err_t -mdns_domain_add_label_pbuf(struct mdns_domain *domain, const struct pbuf *p, u16_t offset, u8_t len) -{ - err_t err = mdns_domain_add_label_base(domain, len); - if (err != ERR_OK) { - return err; - } - if (len) { - if (pbuf_copy_partial(p, &domain->name[domain->length], len, offset) != len) { - /* take back the ++ done before */ - domain->length--; - return ERR_ARG; - } - domain->length += len; - } - return ERR_OK; -} - -/** - * Internal readname function with max 6 levels of recursion following jumps - * while decompressing name - */ -static u16_t -mdns_readname_loop(struct pbuf *p, u16_t offset, struct mdns_domain *domain, unsigned depth) -{ - u8_t c; - - do { - if (depth > 5) { - /* Too many jumps */ - return MDNS_READNAME_ERROR; - } - - c = pbuf_get_at(p, offset); - offset++; - - /* is this a compressed label? */ - if ((c & 0xc0) == 0xc0) { - u16_t jumpaddr; - if (offset >= p->tot_len) { - /* Make sure both jump bytes fit in the packet */ - return MDNS_READNAME_ERROR; - } - jumpaddr = (((c & 0x3f) << 8) | (pbuf_get_at(p, offset) & 0xff)); - offset++; - if (jumpaddr >= SIZEOF_DNS_HDR && jumpaddr < p->tot_len) { - u16_t res; - /* Recursive call, maximum depth will be checked */ - res = mdns_readname_loop(p, jumpaddr, domain, depth + 1); - /* Dont return offset since new bytes were not read (jumped to somewhere in packet) */ - if (res == MDNS_READNAME_ERROR) { - return res; - } - } else { - return MDNS_READNAME_ERROR; - } - break; - } - - /* normal label */ - if (c <= MDNS_LABEL_MAXLEN) { - err_t res; - - if (c + domain->length >= MDNS_DOMAIN_MAXLEN) { - return MDNS_READNAME_ERROR; - } - res = mdns_domain_add_label_pbuf(domain, p, offset, c); - if (res != ERR_OK) { - return MDNS_READNAME_ERROR; - } - offset += c; - } else { - /* bad length byte */ - return MDNS_READNAME_ERROR; - } - } while (c != 0); - - return offset; -} - -/** - * Read possibly compressed domain name from packet buffer - * @param p The packet - * @param offset start position of domain name in packet - * @param domain The domain name destination - * @return The new offset after the domain, or MDNS_READNAME_ERROR - * if reading failed - */ -u16_t -mdns_readname(struct pbuf *p, u16_t offset, struct mdns_domain *domain) -{ - memset(domain, 0, sizeof(struct mdns_domain)); - return mdns_readname_loop(p, offset, domain, 0); -} - -/** - * Print domain name to debug output - * @param domain The domain name - */ -static void -mdns_domain_debug_print(struct mdns_domain *domain) -{ - u8_t *src = domain->name; - u8_t i; - - while (*src) { - u8_t label_len = *src; - src++; - for (i = 0; i < label_len; i++) { - LWIP_DEBUGF(MDNS_DEBUG, ("%c", src[i])); - } - src += label_len; - LWIP_DEBUGF(MDNS_DEBUG, (".")); - } -} - -/** - * Return 1 if contents of domains match (case-insensitive) - * @param a Domain name to compare 1 - * @param b Domain name to compare 2 - * @return 1 if domains are equal ignoring case, 0 otherwise - */ -int -mdns_domain_eq(struct mdns_domain *a, struct mdns_domain *b) -{ - u8_t *ptra, *ptrb; - u8_t len; - int res; - - if (a->length != b->length) { - return 0; - } - - ptra = a->name; - ptrb = b->name; - while (*ptra && *ptrb && ptra < &a->name[a->length]) { - if (*ptra != *ptrb) { - return 0; - } - len = *ptra; - ptra++; - ptrb++; - res = lwip_strnicmp((char *) ptra, (char *) ptrb, len); - if (res != 0) { - return 0; - } - ptra += len; - ptrb += len; - } - if (*ptra != *ptrb && ptra < &a->name[a->length]) { - return 0; - } - return 1; -} - -/** - * Call user supplied function to setup TXT data - * @param service The service to build TXT record for - */ -static void -mdns_prepare_txtdata(struct mdns_service *service) -{ - memset(&service->txtdata, 0, sizeof(struct mdns_domain)); - if (service->txt_fn) { - service->txt_fn(service, service->txt_userdata); - } -} - -#if LWIP_IPV4 -/** - * Build domain for reverse lookup of IPv4 address - * like 12.0.168.192.in-addr.arpa. for 192.168.0.12 - * @param domain Where to write the domain name - * @param addr Pointer to an IPv4 address to encode - * @return ERR_OK if domain was written, an err_t otherwise - */ -static err_t -mdns_build_reverse_v4_domain(struct mdns_domain *domain, const ip4_addr_t *addr) -{ - int i; - err_t res; - const u8_t *ptr; - - LWIP_UNUSED_ARG(res); - if (!domain || !addr) { - return ERR_ARG; - } - memset(domain, 0, sizeof(struct mdns_domain)); - ptr = (const u8_t *) addr; - for (i = sizeof(ip4_addr_t) - 1; i >= 0; i--) { - char buf[4]; - u8_t val = ptr[i]; - - lwip_itoa(buf, sizeof(buf), val); - res = mdns_domain_add_label(domain, buf, (u8_t)strlen(buf)); - LWIP_ERROR("mdns_build_reverse_v4_domain: Failed to add label", (res == ERR_OK), return res); - } - res = mdns_domain_add_label(domain, REVERSE_PTR_V4_DOMAIN, (u8_t)(sizeof(REVERSE_PTR_V4_DOMAIN) - 1)); - LWIP_ERROR("mdns_build_reverse_v4_domain: Failed to add label", (res == ERR_OK), return res); - res = mdns_domain_add_label(domain, REVERSE_PTR_TOPDOMAIN, (u8_t)(sizeof(REVERSE_PTR_TOPDOMAIN) - 1)); - LWIP_ERROR("mdns_build_reverse_v4_domain: Failed to add label", (res == ERR_OK), return res); - res = mdns_domain_add_label(domain, NULL, 0); - LWIP_ERROR("mdns_build_reverse_v4_domain: Failed to add label", (res == ERR_OK), return res); - - return ERR_OK; -} -#endif - -#if LWIP_IPV6 -/** - * Build domain for reverse lookup of IP address - * like b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa. for 2001:db8::567:89ab - * @param domain Where to write the domain name - * @param addr Pointer to an IPv6 address to encode - * @return ERR_OK if domain was written, an err_t otherwise - */ -static err_t -mdns_build_reverse_v6_domain(struct mdns_domain *domain, const ip6_addr_t *addr) -{ - int i; - err_t res; - const u8_t *ptr; - LWIP_UNUSED_ARG(res); - if (!domain || !addr) { - return ERR_ARG; - } - memset(domain, 0, sizeof(struct mdns_domain)); - ptr = (const u8_t *) addr; - for (i = sizeof(ip6_addr_p_t) - 1; i >= 0; i--) { - char buf; - u8_t byte = ptr[i]; - int j; - for (j = 0; j < 2; j++) { - if ((byte & 0x0F) < 0xA) { - buf = '0' + (byte & 0x0F); - } else { - buf = 'a' + (byte & 0x0F) - 0xA; - } - res = mdns_domain_add_label(domain, &buf, sizeof(buf)); - LWIP_ERROR("mdns_build_reverse_v6_domain: Failed to add label", (res == ERR_OK), return res); - byte >>= 4; - } - } - res = mdns_domain_add_label(domain, REVERSE_PTR_V6_DOMAIN, (u8_t)(sizeof(REVERSE_PTR_V6_DOMAIN) - 1)); - LWIP_ERROR("mdns_build_reverse_v6_domain: Failed to add label", (res == ERR_OK), return res); - res = mdns_domain_add_label(domain, REVERSE_PTR_TOPDOMAIN, (u8_t)(sizeof(REVERSE_PTR_TOPDOMAIN) - 1)); - LWIP_ERROR("mdns_build_reverse_v6_domain: Failed to add label", (res == ERR_OK), return res); - res = mdns_domain_add_label(domain, NULL, 0); - LWIP_ERROR("mdns_build_reverse_v6_domain: Failed to add label", (res == ERR_OK), return res); - - return ERR_OK; -} -#endif - -/* Add .local. to domain */ -static err_t -mdns_add_dotlocal(struct mdns_domain *domain) -{ - err_t res = mdns_domain_add_label(domain, TOPDOMAIN_LOCAL, (u8_t)(sizeof(TOPDOMAIN_LOCAL) - 1)); - LWIP_UNUSED_ARG(res); - LWIP_ERROR("mdns_add_dotlocal: Failed to add label", (res == ERR_OK), return res); - return mdns_domain_add_label(domain, NULL, 0); -} - /** - * Build the .local. domain name - * @param domain Where to write the domain name - * @param mdns TMDNS netif descriptor. - * @return ERR_OK if domain .local. was written, an err_t otherwise + * Construction to make mdns struct accessible from mdns_out.c + * TODO: + * can we add the mdns struct to the netif like we do for dhcp, autoip,...? + * Then this is not needed any more. + * + * @param netif The network interface + * @return mdns struct */ -static err_t -mdns_build_host_domain(struct mdns_domain *domain, struct mdns_host *mdns) -{ - err_t res; - LWIP_UNUSED_ARG(res); - memset(domain, 0, sizeof(struct mdns_domain)); - LWIP_ERROR("mdns_build_host_domain: mdns != NULL", (mdns != NULL), return ERR_VAL); - res = mdns_domain_add_label(domain, mdns->name, (u8_t)strlen(mdns->name)); - LWIP_ERROR("mdns_build_host_domain: Failed to add label", (res == ERR_OK), return res); - return mdns_add_dotlocal(domain); +struct mdns_host* +netif_mdns_data(struct netif *netif) { + return NETIF_TO_HOST(netif); } /** - * Build the lookup-all-services special DNS-SD domain name - * @param domain Where to write the domain name - * @return ERR_OK if domain _services._dns-sd._udp.local. was written, an err_t otherwise + * Construction to access the mdns udp pcb. + * + * @return udp_pcb struct of mdns */ -static err_t -mdns_build_dnssd_domain(struct mdns_domain *domain) +struct udp_pcb* +get_mdns_pcb(void) { - err_t res; - LWIP_UNUSED_ARG(res); - memset(domain, 0, sizeof(struct mdns_domain)); - res = mdns_domain_add_label(domain, "_services", (u8_t)(sizeof("_services") - 1)); - LWIP_ERROR("mdns_build_dnssd_domain: Failed to add label", (res == ERR_OK), return res); - res = mdns_domain_add_label(domain, "_dns-sd", (u8_t)(sizeof("_dns-sd") - 1)); - LWIP_ERROR("mdns_build_dnssd_domain: Failed to add label", (res == ERR_OK), return res); - res = mdns_domain_add_label(domain, dnssd_protos[DNSSD_PROTO_UDP], (u8_t)strlen(dnssd_protos[DNSSD_PROTO_UDP])); - LWIP_ERROR("mdns_build_dnssd_domain: Failed to add label", (res == ERR_OK), return res); - return mdns_add_dotlocal(domain); -} - -/** - * Build domain name for a service - * @param domain Where to write the domain name - * @param service The service 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, - * ...local. will be written, otherwise ..local. - * An err_t is returned on error. - */ -static err_t -mdns_build_service_domain(struct mdns_domain *domain, struct mdns_service *service, int include_name) -{ - err_t res; - LWIP_UNUSED_ARG(res); - memset(domain, 0, sizeof(struct mdns_domain)); - if (include_name) { - res = mdns_domain_add_label(domain, service->name, (u8_t)strlen(service->name)); - LWIP_ERROR("mdns_build_service_domain: Failed to add label", (res == ERR_OK), return res); - } - res = mdns_domain_add_label(domain, service->service, (u8_t)strlen(service->service)); - LWIP_ERROR("mdns_build_service_domain: Failed to add label", (res == ERR_OK), return res); - res = mdns_domain_add_label(domain, dnssd_protos[service->proto], (u8_t)strlen(dnssd_protos[service->proto])); - LWIP_ERROR("mdns_build_service_domain: Failed to add label", (res == ERR_OK), return res); - return mdns_add_dotlocal(domain); + return mdns_pcb; } /** @@ -772,271 +329,6 @@ check_service(struct mdns_service *service, struct mdns_rr_info *rr) return replies; } -/** - * Return bytes needed to write before jump for best result of compressing supplied domain - * against domain in outpacket starting at specified offset. - * If a match is found, offset is updated to where to jump to - * @param pbuf Pointer to pbuf with the partially constructed DNS packet - * @param offset Start position of a domain written earlier. If this location is suitable - * for compression, the pointer is updated to where in the domain to jump to. - * @param domain The domain to write - * @return Number of bytes to write of the new domain before writing a jump to the offset. - * If compression can not be done against this previous domain name, the full new - * domain length is returned. - */ -u16_t -mdns_compress_domain(struct pbuf *pbuf, u16_t *offset, struct mdns_domain *domain) -{ - struct mdns_domain target; - u16_t target_end; - u8_t target_len; - u8_t writelen = 0; - u8_t *ptr; - if (pbuf == NULL) { - return domain->length; - } - target_end = mdns_readname(pbuf, *offset, &target); - if (target_end == MDNS_READNAME_ERROR) { - return domain->length; - } - target_len = (u8_t)(target_end - *offset); - ptr = domain->name; - while (writelen < domain->length) { - u8_t domainlen = (u8_t)(domain->length - writelen); - u8_t labellen; - if (domainlen <= target.length && domainlen > DOMAIN_JUMP_SIZE) { - /* Compare domains if target is long enough, and we have enough left of the domain */ - u8_t targetpos = (u8_t)(target.length - domainlen); - if ((targetpos + DOMAIN_JUMP_SIZE) >= target_len) { - /* We are checking at or beyond a jump in the original, stop looking */ - break; - } - if (target.length >= domainlen && - memcmp(&domain->name[writelen], &target.name[targetpos], domainlen) == 0) { - *offset += targetpos; - return writelen; - } - } - /* Skip to next label in domain */ - labellen = *ptr; - writelen += 1 + labellen; - ptr += 1 + labellen; - } - /* Nothing found */ - return domain->length; -} - -/** - * Write domain to outpacket. Compression will be attempted, - * unless domain->skip_compression is set. - * @param outpkt The outpacket to write to - * @param domain The domain name to write - * @return ERR_OK on success, an err_t otherwise - */ -static err_t -mdns_write_domain(struct mdns_outpacket *outpkt, struct mdns_domain *domain) -{ - int i; - err_t res; - u16_t writelen = domain->length; - u16_t jump_offset = 0; - u16_t jump; - - if (!domain->skip_compression) { - for (i = 0; i < NUM_DOMAIN_OFFSETS; i++) { - u16_t offset = outpkt->domain_offsets[i]; - if (offset) { - u16_t len = mdns_compress_domain(outpkt->pbuf, &offset, domain); - if (len < writelen) { - writelen = len; - jump_offset = offset; - } - } - } - } - - if (writelen) { - /* Write uncompressed part of name */ - res = pbuf_take_at(outpkt->pbuf, domain->name, writelen, outpkt->write_offset); - if (res != ERR_OK) { - return res; - } - - /* Store offset of this new domain */ - for (i = 0; i < NUM_DOMAIN_OFFSETS; i++) { - if (outpkt->domain_offsets[i] == 0) { - outpkt->domain_offsets[i] = outpkt->write_offset; - break; - } - } - - outpkt->write_offset += writelen; - } - if (jump_offset) { - /* Write jump */ - jump = lwip_htons(DOMAIN_JUMP | jump_offset); - res = pbuf_take_at(outpkt->pbuf, &jump, DOMAIN_JUMP_SIZE, outpkt->write_offset); - if (res != ERR_OK) { - return res; - } - outpkt->write_offset += DOMAIN_JUMP_SIZE; - } - return ERR_OK; -} - -/** - * Write a question to an outpacket - * A question contains domain, type and class. Since an answer also starts with these fields this function is also - * called from mdns_add_answer(). - * @param outpkt The outpacket to write to - * @param domain The domain name the answer is for - * @param type The DNS type of the answer (like 'AAAA', 'SRV') - * @param klass The DNS type of the answer (like 'IN') - * @param unicast If highest bit in class should be set, to instruct the responder to - * reply with a unicast packet - * @return ERR_OK on success, an err_t otherwise - */ -static err_t -mdns_add_question(struct mdns_outpacket *outpkt, struct mdns_domain *domain, u16_t type, u16_t klass, u16_t unicast) -{ - u16_t question_len; - u16_t field16; - err_t res; - - if (!outpkt->pbuf) { - /* If no pbuf is active, allocate one */ - outpkt->pbuf = pbuf_alloc(PBUF_TRANSPORT, OUTPACKET_SIZE, PBUF_RAM); - if (!outpkt->pbuf) { - return ERR_MEM; - } - outpkt->write_offset = SIZEOF_DNS_HDR; - } - - /* Worst case calculation. Domain string might be compressed */ - question_len = domain->length + sizeof(type) + sizeof(klass); - if (outpkt->write_offset + question_len > outpkt->pbuf->tot_len) { - /* No space */ - return ERR_MEM; - } - - /* Write name */ - res = mdns_write_domain(outpkt, domain); - if (res != ERR_OK) { - return res; - } - - /* Write type */ - field16 = lwip_htons(type); - res = pbuf_take_at(outpkt->pbuf, &field16, sizeof(field16), outpkt->write_offset); - if (res != ERR_OK) { - return res; - } - outpkt->write_offset += sizeof(field16); - - /* Write class */ - if (unicast) { - klass |= 0x8000; - } - field16 = lwip_htons(klass); - res = pbuf_take_at(outpkt->pbuf, &field16, sizeof(field16), outpkt->write_offset); - if (res != ERR_OK) { - return res; - } - outpkt->write_offset += sizeof(field16); - - return ERR_OK; -} - -/** - * Write answer to reply packet. - * buf or answer_domain can be null. The rd_length written will be buf_length + - * size of (compressed) domain. Most uses will need either buf or answer_domain, - * special case is SRV that starts with 3 u16 and then a domain name. - * @param reply The outpacket to write to - * @param domain The domain name the answer is for - * @param type The DNS type of the answer (like 'AAAA', 'SRV') - * @param klass The DNS type of the answer (like 'IN') - * @param cache_flush If highest bit in class should be set, to instruct receiver that - * this reply replaces any earlier answer for this domain/type/class - * @param ttl Validity time in seconds to send out for IP address data in DNS replies - * @param buf Pointer to buffer of answer data - * @param buf_length Length of variable data - * @param answer_domain A domain to write after any buffer data as answer - * @return ERR_OK on success, an err_t otherwise - */ -static err_t -mdns_add_answer(struct mdns_outpacket *reply, struct mdns_domain *domain, u16_t type, u16_t klass, u16_t cache_flush, - u32_t ttl, const u8_t *buf, size_t buf_length, struct mdns_domain *answer_domain) -{ - u16_t answer_len; - u16_t field16; - u16_t rdlen_offset; - u16_t answer_offset; - u32_t field32; - err_t res; - - if (!reply->pbuf) { - /* If no pbuf is active, allocate one */ - reply->pbuf = pbuf_alloc(PBUF_TRANSPORT, OUTPACKET_SIZE, PBUF_RAM); - if (!reply->pbuf) { - return ERR_MEM; - } - reply->write_offset = SIZEOF_DNS_HDR; - } - - /* Worst case calculation. Domain strings might be compressed */ - answer_len = domain->length + sizeof(type) + sizeof(klass) + sizeof(ttl) + sizeof(field16)/*rd_length*/; - if (buf) { - answer_len += (u16_t)buf_length; - } - if (answer_domain) { - answer_len += answer_domain->length; - } - if (reply->write_offset + answer_len > reply->pbuf->tot_len) { - /* No space */ - return ERR_MEM; - } - - /* Answer starts with same data as question, then more fields */ - mdns_add_question(reply, domain, type, klass, cache_flush); - - /* Write TTL */ - field32 = lwip_htonl(ttl); - res = pbuf_take_at(reply->pbuf, &field32, sizeof(field32), reply->write_offset); - if (res != ERR_OK) { - return res; - } - reply->write_offset += sizeof(field32); - - /* Store offsets and skip forward to the data */ - rdlen_offset = reply->write_offset; - reply->write_offset += sizeof(field16); - answer_offset = reply->write_offset; - - if (buf) { - /* Write static data */ - res = pbuf_take_at(reply->pbuf, buf, (u16_t)buf_length, reply->write_offset); - if (res != ERR_OK) { - return res; - } - reply->write_offset += (u16_t)buf_length; - } - - if (answer_domain) { - /* Write name answer (compressed if possible) */ - res = mdns_write_domain(reply, answer_domain); - if (res != ERR_OK) { - return res; - } - } - - /* Write rd_length after when we know the answer size */ - field16 = lwip_htons(reply->write_offset - answer_offset); - res = pbuf_take_at(reply->pbuf, &field16, sizeof(field16), rdlen_offset); - - return res; -} - /** * Helper function for mdns_read_question/mdns_read_answer * Reads a domain, type and class from the packet @@ -1109,13 +401,14 @@ mdns_read_question(struct mdns_packet *pkt, struct mdns_question *question) /** * Read an answer from the packet * The variable length reply is not copied, its pbuf offset and length is stored instead. - * @param pkt The MDNS packet to read. The answers_left field will be decremented and + * @param pkt The MDNS packet to read. The num_left field will be decremented and * the parse_offset will be updated. - * @param answer The struct to fill with answer data + * @param answer The struct to fill with answer data + * @param num_left number of answers left -> answers, authoritative or additional * @return ERR_OK on success, an err_t otherwise */ static err_t -mdns_read_answer(struct mdns_packet *pkt, struct mdns_answer *answer) +mdns_read_answer(struct mdns_packet *pkt, struct mdns_answer *answer, u16_t *num_left) { /* Read questions first */ if (pkt->questions_left) { @@ -1127,387 +420,42 @@ mdns_read_answer(struct mdns_packet *pkt, struct mdns_answer *answer) return ERR_VAL; } - if (pkt->answers_left) { + if (*num_left) { u16_t copied, field16; - u32_t ttl; - err_t res; - pkt->answers_left--; - - memset(answer, 0, sizeof(struct mdns_answer)); - res = mdns_read_rr_info(pkt, &answer->info); - if (res != ERR_OK) { - return res; - } - - /* Extract cache_flush flag from class field */ - answer->cache_flush = answer->info.klass & 0x8000; - answer->info.klass &= 0x7FFF; - - copied = pbuf_copy_partial(pkt->pbuf, &ttl, sizeof(ttl), pkt->parse_offset); - if (copied != sizeof(ttl)) { - return ERR_VAL; - } - pkt->parse_offset += copied; - answer->ttl = lwip_ntohl(ttl); - - copied = pbuf_copy_partial(pkt->pbuf, &field16, sizeof(field16), pkt->parse_offset); - if (copied != sizeof(field16)) { - return ERR_VAL; - } - pkt->parse_offset += copied; - answer->rd_length = lwip_ntohs(field16); - - answer->rd_offset = pkt->parse_offset; - pkt->parse_offset += answer->rd_length; - - return ERR_OK; - } - return ERR_VAL; -} - -#if LWIP_IPV4 -/** Write an IPv4 address (A) RR to outpacket */ -static err_t -mdns_add_a_answer(struct mdns_outpacket *reply, u16_t cache_flush, struct netif *netif) -{ - struct mdns_domain host; - mdns_build_host_domain(&host, NETIF_TO_HOST(netif)); - LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with A record\n")); - return mdns_add_answer(reply, &host, DNS_RRTYPE_A, DNS_RRCLASS_IN, cache_flush, (NETIF_TO_HOST(netif))->dns_ttl, (const u8_t *) netif_ip4_addr(netif), sizeof(ip4_addr_t), NULL); -} - -/** Write a 4.3.2.1.in-addr.arpa -> hostname.local PTR RR to outpacket */ -static err_t -mdns_add_hostv4_ptr_answer(struct mdns_outpacket *reply, u16_t cache_flush, struct netif *netif) -{ - struct mdns_domain host, revhost; - mdns_build_host_domain(&host, NETIF_TO_HOST(netif)); - mdns_build_reverse_v4_domain(&revhost, netif_ip4_addr(netif)); - LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with v4 PTR record\n")); - return mdns_add_answer(reply, &revhost, DNS_RRTYPE_PTR, DNS_RRCLASS_IN, cache_flush, (NETIF_TO_HOST(netif))->dns_ttl, NULL, 0, &host); -} -#endif - -#if LWIP_IPV6 -/** Write an IPv6 address (AAAA) RR to outpacket */ -static err_t -mdns_add_aaaa_answer(struct mdns_outpacket *reply, u16_t cache_flush, struct netif *netif, int addrindex) -{ - struct mdns_domain host; - mdns_build_host_domain(&host, NETIF_TO_HOST(netif)); - LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with AAAA record\n")); - return mdns_add_answer(reply, &host, DNS_RRTYPE_AAAA, DNS_RRCLASS_IN, cache_flush, (NETIF_TO_HOST(netif))->dns_ttl, (const u8_t *) netif_ip6_addr(netif, addrindex), sizeof(ip6_addr_p_t), NULL); -} - -/** Write a x.y.z.ip6.arpa -> hostname.local PTR RR to outpacket */ -static err_t -mdns_add_hostv6_ptr_answer(struct mdns_outpacket *reply, u16_t cache_flush, struct netif *netif, int addrindex) -{ - struct mdns_domain host, revhost; - mdns_build_host_domain(&host, NETIF_TO_HOST(netif)); - mdns_build_reverse_v6_domain(&revhost, netif_ip6_addr(netif, addrindex)); - LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with v6 PTR record\n")); - return mdns_add_answer(reply, &revhost, DNS_RRTYPE_PTR, DNS_RRCLASS_IN, cache_flush, (NETIF_TO_HOST(netif))->dns_ttl, NULL, 0, &host); -} -#endif - -/** Write an all-services -> servicetype PTR RR to outpacket */ -static err_t -mdns_add_servicetype_ptr_answer(struct mdns_outpacket *reply, struct mdns_service *service) -{ - struct mdns_domain service_type, service_dnssd; - mdns_build_service_domain(&service_type, service, 0); - mdns_build_dnssd_domain(&service_dnssd); - LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with service type PTR record\n")); - return mdns_add_answer(reply, &service_dnssd, DNS_RRTYPE_PTR, DNS_RRCLASS_IN, 0, service->dns_ttl, NULL, 0, &service_type); -} - -/** Write a servicetype -> servicename PTR RR to outpacket */ -static err_t -mdns_add_servicename_ptr_answer(struct mdns_outpacket *reply, struct mdns_service *service) -{ - struct mdns_domain service_type, service_instance; - mdns_build_service_domain(&service_type, service, 0); - mdns_build_service_domain(&service_instance, service, 1); - LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with service name PTR record\n")); - return mdns_add_answer(reply, &service_type, DNS_RRTYPE_PTR, DNS_RRCLASS_IN, 0, service->dns_ttl, NULL, 0, &service_instance); -} - -/** Write a SRV RR to outpacket */ -static err_t -mdns_add_srv_answer(struct mdns_outpacket *reply, u16_t cache_flush, struct mdns_host *mdns, struct mdns_service *service) -{ - struct mdns_domain service_instance, srvhost; - u16_t srvdata[3]; - mdns_build_service_domain(&service_instance, service, 1); - mdns_build_host_domain(&srvhost, mdns); - if (reply->legacy_query) { - /* RFC 6762 section 18.14: - * In legacy unicast responses generated to answer legacy queries, - * name compression MUST NOT be performed on SRV records. - */ - srvhost.skip_compression = 1; - } - srvdata[0] = lwip_htons(SRV_PRIORITY); - srvdata[1] = lwip_htons(SRV_WEIGHT); - srvdata[2] = lwip_htons(service->port); - LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with SRV record\n")); - return mdns_add_answer(reply, &service_instance, DNS_RRTYPE_SRV, DNS_RRCLASS_IN, cache_flush, service->dns_ttl, - (const u8_t *) &srvdata, sizeof(srvdata), &srvhost); -} - -/** Write a TXT RR to outpacket */ -static err_t -mdns_add_txt_answer(struct mdns_outpacket *reply, u16_t cache_flush, struct mdns_service *service) -{ - struct mdns_domain service_instance; - mdns_build_service_domain(&service_instance, service, 1); - mdns_prepare_txtdata(service); - LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with TXT record\n")); - return mdns_add_answer(reply, &service_instance, DNS_RRTYPE_TXT, DNS_RRCLASS_IN, cache_flush, service->dns_ttl, - (u8_t *) &service->txtdata.name, service->txtdata.length, NULL); -} - -/** - * Setup outpacket as a reply to the incoming packet - */ -static void -mdns_init_outpacket(struct mdns_outpacket *out, struct mdns_packet *in) -{ - memset(out, 0, sizeof(struct mdns_outpacket)); - out->cache_flush = 1; - out->netif = in->netif; - - /* Copy source IP/port to use when responding unicast, or to choose - * which pcb to use for multicast (IPv4/IPv6) - */ - SMEMCPY(&out->dest_addr, &in->source_addr, sizeof(ip_addr_t)); - out->dest_port = in->source_port; - - if (in->source_port != LWIP_IANA_PORT_MDNS) { - out->unicast_reply = 1; - out->cache_flush = 0; - if (in->questions == 1) { - out->legacy_query = 1; - out->tx_id = in->tx_id; - } - } - - if (in->recv_unicast) { - out->unicast_reply = 1; - } -} - -/** - * Send chosen answers as a reply - * - * Add all selected answers (first write will allocate pbuf) - * Add additional answers based on the selected answers - * Send the packet - */ -static err_t -mdns_send_outpacket(struct mdns_outpacket *outpkt, u8_t flags) -{ - struct mdns_service *service; - err_t res = ERR_ARG; - int i; - struct mdns_host *mdns = NETIF_TO_HOST(outpkt->netif); - u16_t answers = 0; - - /* Write answers to host questions */ -#if LWIP_IPV4 - if (outpkt->host_replies & REPLY_HOST_A) { - res = mdns_add_a_answer(outpkt, outpkt->cache_flush, outpkt->netif); - if (res != ERR_OK) { - goto cleanup; - } - answers++; - } - if (outpkt->host_replies & REPLY_HOST_PTR_V4) { - res = mdns_add_hostv4_ptr_answer(outpkt, outpkt->cache_flush, outpkt->netif); - if (res != ERR_OK) { - goto cleanup; - } - answers++; - } -#endif -#if LWIP_IPV6 - if (outpkt->host_replies & REPLY_HOST_AAAA) { - int addrindex; - for (addrindex = 0; addrindex < LWIP_IPV6_NUM_ADDRESSES; addrindex++) { - if (ip6_addr_isvalid(netif_ip6_addr_state(outpkt->netif, addrindex))) { - res = mdns_add_aaaa_answer(outpkt, outpkt->cache_flush, outpkt->netif, addrindex); - if (res != ERR_OK) { - goto cleanup; - } - answers++; - } - } - } - if (outpkt->host_replies & REPLY_HOST_PTR_V6) { - u8_t rev_addrs = outpkt->host_reverse_v6_replies; - int addrindex = 0; - while (rev_addrs) { - if (rev_addrs & 1) { - res = mdns_add_hostv6_ptr_answer(outpkt, outpkt->cache_flush, outpkt->netif, addrindex); - if (res != ERR_OK) { - goto cleanup; - } - answers++; - } - addrindex++; - rev_addrs >>= 1; - } - } -#endif - - /* Write answers to service questions */ - for (i = 0; i < MDNS_MAX_SERVICES; i++) { - service = mdns->services[i]; - if (!service) { - continue; - } - - if (outpkt->serv_replies[i] & REPLY_SERVICE_TYPE_PTR) { - res = mdns_add_servicetype_ptr_answer(outpkt, service); - if (res != ERR_OK) { - goto cleanup; - } - answers++; - } - - if (outpkt->serv_replies[i] & REPLY_SERVICE_NAME_PTR) { - res = mdns_add_servicename_ptr_answer(outpkt, service); - if (res != ERR_OK) { - goto cleanup; - } - answers++; - } - - if (outpkt->serv_replies[i] & REPLY_SERVICE_SRV) { - res = mdns_add_srv_answer(outpkt, outpkt->cache_flush, mdns, service); - if (res != ERR_OK) { - goto cleanup; - } - answers++; - } - - if (outpkt->serv_replies[i] & REPLY_SERVICE_TXT) { - res = mdns_add_txt_answer(outpkt, outpkt->cache_flush, service); - if (res != ERR_OK) { - goto cleanup; - } - answers++; - } - } - - /* if this is a response, the data above is anwers, else this is a probe and the answers above goes into auth section */ - if (flags & DNS_FLAG1_RESPONSE) { - outpkt->answers += answers; - } else { - outpkt->authoritative += answers; - } - - /* All answers written, add additional RRs */ - for (i = 0; i < MDNS_MAX_SERVICES; i++) { - service = mdns->services[i]; - if (!service) { - continue; - } - - if (outpkt->serv_replies[i] & REPLY_SERVICE_NAME_PTR) { - /* Our service instance requested, include SRV & TXT - * if they are already not requested. */ - if (!(outpkt->serv_replies[i] & REPLY_SERVICE_SRV)) { - res = mdns_add_srv_answer(outpkt, outpkt->cache_flush, mdns, service); - if (res != ERR_OK) { - goto cleanup; - } - outpkt->additional++; - } - - if (!(outpkt->serv_replies[i] & REPLY_SERVICE_TXT)) { - res = mdns_add_txt_answer(outpkt, outpkt->cache_flush, service); - if (res != ERR_OK) { - goto cleanup; - } - outpkt->additional++; - } - } - - /* If service instance, SRV, record or an IP address is requested, - * supply all addresses for the host - */ - if ((outpkt->serv_replies[i] & (REPLY_SERVICE_NAME_PTR | REPLY_SERVICE_SRV)) || - (outpkt->host_replies & (REPLY_HOST_A | REPLY_HOST_AAAA))) { -#if LWIP_IPV6 - if (!(outpkt->host_replies & REPLY_HOST_AAAA)) { - int addrindex; - for (addrindex = 0; addrindex < LWIP_IPV6_NUM_ADDRESSES; addrindex++) { - if (ip6_addr_isvalid(netif_ip6_addr_state(outpkt->netif, addrindex))) { - res = mdns_add_aaaa_answer(outpkt, outpkt->cache_flush, outpkt->netif, addrindex); - if (res != ERR_OK) { - goto cleanup; - } - outpkt->additional++; - } - } - } -#endif -#if LWIP_IPV4 - if (!(outpkt->host_replies & REPLY_HOST_A) && - !ip4_addr_isany_val(*netif_ip4_addr(outpkt->netif))) { - res = mdns_add_a_answer(outpkt, outpkt->cache_flush, outpkt->netif); - if (res != ERR_OK) { - goto cleanup; - } - outpkt->additional++; - } -#endif - } - } - - if (outpkt->pbuf) { - const ip_addr_t *mcast_destaddr; - struct dns_hdr hdr; + u32_t ttl; + err_t res; + (*num_left)--; - /* Write header */ - memset(&hdr, 0, sizeof(hdr)); - hdr.flags1 = flags; - hdr.numquestions = lwip_htons(outpkt->questions); - hdr.numanswers = lwip_htons(outpkt->answers); - hdr.numauthrr = lwip_htons(outpkt->authoritative); - hdr.numextrarr = lwip_htons(outpkt->additional); - hdr.id = lwip_htons(outpkt->tx_id); - pbuf_take(outpkt->pbuf, &hdr, sizeof(hdr)); + memset(answer, 0, sizeof(struct mdns_answer)); + res = mdns_read_rr_info(pkt, &answer->info); + if (res != ERR_OK) { + return res; + } - /* Shrink packet */ - pbuf_realloc(outpkt->pbuf, outpkt->write_offset); + /* Extract cache_flush flag from class field */ + answer->cache_flush = answer->info.klass & 0x8000; + answer->info.klass &= 0x7FFF; - if (IP_IS_V6_VAL(outpkt->dest_addr)) { -#if LWIP_IPV6 - mcast_destaddr = &v6group; -#endif - } else { -#if LWIP_IPV4 - mcast_destaddr = &v4group; -#endif + copied = pbuf_copy_partial(pkt->pbuf, &ttl, sizeof(ttl), pkt->parse_offset); + if (copied != sizeof(ttl)) { + return ERR_VAL; } - /* Send created packet */ - LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Sending packet, len=%d, unicast=%d\n", outpkt->write_offset, outpkt->unicast_reply)); - if (outpkt->unicast_reply) { - res = udp_sendto_if(mdns_pcb, outpkt->pbuf, &outpkt->dest_addr, outpkt->dest_port, outpkt->netif); - } else { - res = udp_sendto_if(mdns_pcb, outpkt->pbuf, mcast_destaddr, LWIP_IANA_PORT_MDNS, outpkt->netif); + pkt->parse_offset += copied; + answer->ttl = lwip_ntohl(ttl); + + copied = pbuf_copy_partial(pkt->pbuf, &field16, sizeof(field16), pkt->parse_offset); + if (copied != sizeof(field16)) { + return ERR_VAL; } - } + pkt->parse_offset += copied; + answer->rd_length = lwip_ntohs(field16); -cleanup: - if (outpkt->pbuf) { - pbuf_free(outpkt->pbuf); - outpkt->pbuf = NULL; + answer->rd_offset = pkt->parse_offset; + pkt->parse_offset += answer->rd_length; + + return ERR_OK; } - return res; + return ERR_VAL; } /** @@ -1518,12 +466,11 @@ cleanup: static void mdns_announce(struct netif *netif, const ip_addr_t *destination) { - struct mdns_outpacket announce; + struct mdns_outmsg announce; int i; struct mdns_host *mdns = NETIF_TO_HOST(netif); memset(&announce, 0, sizeof(announce)); - announce.netif = netif; announce.cache_flush = 1; #if LWIP_IPV4 if (!ip4_addr_isany_val(*netif_ip4_addr(netif))) { @@ -1549,32 +496,26 @@ mdns_announce(struct netif *netif, const ip_addr_t *destination) announce.dest_port = LWIP_IANA_PORT_MDNS; SMEMCPY(&announce.dest_addr, destination, sizeof(announce.dest_addr)); - mdns_send_outpacket(&announce, DNS_FLAG1_RESPONSE | DNS_FLAG1_AUTHORATIVE); + announce.flags = DNS_FLAG1_RESPONSE | DNS_FLAG1_AUTHORATIVE; + mdns_send_outpacket(&announce, netif); } /** - * Handle question MDNS packet - * 1. Parse all questions and set bits what answers to send - * 2. Clear pending answers if known answers are supplied - * 3. Put chosen answers in new packet and send as reply + * Check the incomming packet and parse all questions + * + * @param netif network interface of incoming packet + * @param pkt incoming packet + * @param reply outgoing message + * @return err_t */ -static void -mdns_handle_question(struct mdns_packet *pkt) +static err_t +mdns_parse_pkt_questions(struct netif *netif, struct mdns_packet *pkt, + struct mdns_outmsg *reply) { + struct mdns_host *mdns = NETIF_TO_HOST(netif); struct mdns_service *service; - struct mdns_outpacket reply; - int replies = 0; int i; err_t res; - struct mdns_host *mdns = NETIF_TO_HOST(pkt->netif); - - if (mdns->probing_state != MDNS_PROBING_COMPLETE) { - /* Don't answer questions until we've verified our domains via probing */ - /* @todo we should check incoming questions during probing for tiebreaking */ - return; - } - - mdns_init_outpacket(&reply, pkt); while (pkt->questions_left) { struct mdns_question q; @@ -1582,7 +523,7 @@ mdns_handle_question(struct mdns_packet *pkt) res = mdns_read_question(pkt, &q); if (res != ERR_OK) { LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to parse question, skipping query packet\n")); - return; + return res; } LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Query for domain ")); @@ -1590,42 +531,51 @@ mdns_handle_question(struct mdns_packet *pkt) LWIP_DEBUGF(MDNS_DEBUG, (" type %d class %d\n", q.info.type, q.info.klass)); if (q.unicast) { - /* Reply unicast if any question is unicast */ - reply.unicast_reply = 1; + /* Reply unicast if it is requested in the question */ + reply->unicast_reply_requested = 1; } - reply.host_replies |= check_host(pkt->netif, &q.info, &reply.host_reverse_v6_replies); - replies |= reply.host_replies; + reply->host_replies |= check_host(netif, &q.info, &reply->host_reverse_v6_replies); for (i = 0; i < MDNS_MAX_SERVICES; i++) { service = mdns->services[i]; if (!service) { continue; } - reply.serv_replies[i] |= check_service(service, &q.info); - replies |= reply.serv_replies[i]; - } - - if (replies && reply.legacy_query) { - /* Add question to reply packet (legacy packet only has 1 question) */ - res = mdns_add_question(&reply, &q.info.domain, q.info.type, q.info.klass, 0); - reply.questions = 1; - if (res != ERR_OK) { - goto cleanup; - } + reply->serv_replies[i] |= check_service(service, &q.info); } } - /* Handle known answers */ + return ERR_OK; +} + +/** + * Check the incomming packet and parse all (known) answers + * + * @param netif network interface of incoming packet + * @param pkt incoming packet + * @param reply outgoing message + * @return err_t + */ +static err_t +mdns_parse_pkt_known_answers(struct netif *netif, struct mdns_packet *pkt, + struct mdns_outmsg *reply) +{ + struct mdns_host *mdns = NETIF_TO_HOST(netif); + struct mdns_service *service; + int i; + err_t res; + while (pkt->answers_left) { struct mdns_answer ans; u8_t rev_v6; int match; + u32_t rr_ttl = MDNS_TTL_120; - res = mdns_read_answer(pkt, &ans); + res = mdns_read_answer(pkt, &ans, &pkt->answers_left); if (res != ERR_OK) { LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to parse answer, skipping query packet\n")); - goto cleanup; + return res; } LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Known answer for domain ")); @@ -1639,8 +589,8 @@ mdns_handle_question(struct mdns_packet *pkt) } rev_v6 = 0; - match = reply.host_replies & check_host(pkt->netif, &ans.info, &rev_v6); - if (match && (ans.ttl > (mdns->dns_ttl / 2))) { + match = reply->host_replies & check_host(netif, &ans.info, &rev_v6); + if (match && (ans.ttl > (rr_ttl / 2))) { /* The RR in the known answer matches an RR we are planning to send, * and the TTL is less than half gone. * If the payload matches we should not send that answer. @@ -1655,15 +605,15 @@ mdns_handle_question(struct mdns_packet *pkt) #if LWIP_IPV4 if (match & REPLY_HOST_PTR_V4) { LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: v4 PTR\n")); - reply.host_replies &= ~REPLY_HOST_PTR_V4; + reply->host_replies &= ~REPLY_HOST_PTR_V4; } #endif #if LWIP_IPV6 if (match & REPLY_HOST_PTR_V6) { LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: v6 PTR\n")); - reply.host_reverse_v6_replies &= ~rev_v6; - if (reply.host_reverse_v6_replies == 0) { - reply.host_replies &= ~REPLY_HOST_PTR_V6; + reply->host_reverse_v6_replies &= ~rev_v6; + if (reply->host_reverse_v6_replies == 0) { + reply->host_replies &= ~REPLY_HOST_PTR_V6; } } #endif @@ -1671,18 +621,18 @@ mdns_handle_question(struct mdns_packet *pkt) } else if (match & REPLY_HOST_A) { #if LWIP_IPV4 if (ans.rd_length == sizeof(ip4_addr_t) && - pbuf_memcmp(pkt->pbuf, ans.rd_offset, netif_ip4_addr(pkt->netif), ans.rd_length) == 0) { + pbuf_memcmp(pkt->pbuf, ans.rd_offset, netif_ip4_addr(netif), ans.rd_length) == 0) { LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: A\n")); - reply.host_replies &= ~REPLY_HOST_A; + reply->host_replies &= ~REPLY_HOST_A; } #endif } else if (match & REPLY_HOST_AAAA) { #if LWIP_IPV6 if (ans.rd_length == sizeof(ip6_addr_p_t) && /* TODO this clears all AAAA responses if first addr is set as known */ - pbuf_memcmp(pkt->pbuf, ans.rd_offset, netif_ip6_addr(pkt->netif, 0), ans.rd_length) == 0) { + pbuf_memcmp(pkt->pbuf, ans.rd_offset, netif_ip6_addr(netif, 0), ans.rd_length) == 0) { LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: AAAA\n")); - reply.host_replies &= ~REPLY_HOST_AAAA; + reply->host_replies &= ~REPLY_HOST_AAAA; } #endif } @@ -1693,8 +643,11 @@ mdns_handle_question(struct mdns_packet *pkt) if (!service) { continue; } - match = reply.serv_replies[i] & check_service(service, &ans.info); - if (match && (ans.ttl > (service->dns_ttl / 2))) { + match = reply->serv_replies[i] & check_service(service, &ans.info); + if (match & REPLY_SERVICE_TYPE_PTR) { + rr_ttl = MDNS_TTL_4500; + } + if (match && (ans.ttl > (rr_ttl / 2))) { /* The RR in the known answer matches an RR we are planning to send, * and the TTL is less than half gone. * If the payload matches we should not send that answer. @@ -1709,14 +662,14 @@ mdns_handle_question(struct mdns_packet *pkt) res = mdns_build_service_domain(&my_ans, service, 0); if (res == ERR_OK && mdns_domain_eq(&known_ans, &my_ans)) { LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: service type PTR\n")); - reply.serv_replies[i] &= ~REPLY_SERVICE_TYPE_PTR; + reply->serv_replies[i] &= ~REPLY_SERVICE_TYPE_PTR; } } if (match & REPLY_SERVICE_NAME_PTR) { res = mdns_build_service_domain(&my_ans, service, 1); if (res == ERR_OK && mdns_domain_eq(&known_ans, &my_ans)) { LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: service name PTR\n")); - reply.serv_replies[i] &= ~REPLY_SERVICE_NAME_PTR; + reply->serv_replies[i] &= ~REPLY_SERVICE_NAME_PTR; } } } @@ -1751,27 +704,373 @@ mdns_handle_question(struct mdns_packet *pkt) break; } LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: SRV\n")); - reply.serv_replies[i] &= ~REPLY_SERVICE_SRV; + reply->serv_replies[i] &= ~REPLY_SERVICE_SRV; } while (0); } else if (match & REPLY_SERVICE_TXT) { mdns_prepare_txtdata(service); if (service->txtdata.length == ans.rd_length && pbuf_memcmp(pkt->pbuf, ans.rd_offset, service->txtdata.name, ans.rd_length) == 0) { LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: TXT\n")); - reply.serv_replies[i] &= ~REPLY_SERVICE_TXT; + reply->serv_replies[i] &= ~REPLY_SERVICE_TXT; } } } } } - mdns_send_outpacket(&reply, DNS_FLAG1_RESPONSE | DNS_FLAG1_AUTHORATIVE); + return ERR_OK; +} -cleanup: - if (reply.pbuf) { - /* This should only happen if we fail to alloc/write question for legacy query */ - pbuf_free(reply.pbuf); - reply.pbuf = NULL; +/** + * Check the incomming packet and parse all authoritative answers to see if the + * query is a probe query. + * + * @param netif network interface of incoming packet + * @param pkt incoming packet + * @param reply outgoing message + * @return err_t + */ +static err_t +mdns_parse_pkt_authoritative_answers(struct netif *netif, struct mdns_packet *pkt, + struct mdns_outmsg *reply) +{ + struct mdns_host *mdns = NETIF_TO_HOST(netif); + struct mdns_service *service; + int i; + err_t res; + + while (pkt->authoritative_left) { + struct mdns_answer ans; + u8_t rev_v6; + int match; + + res = mdns_read_answer(pkt, &ans, &pkt->authoritative_left); + if (res != ERR_OK) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to parse answer, skipping query packet\n")); + return res; + } + + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Authoritative answer for domain ")); + mdns_domain_debug_print(&ans.info.domain); + LWIP_DEBUGF(MDNS_DEBUG, (" type %d class %d\n", ans.info.type, ans.info.klass)); + + + if (ans.info.type == DNS_RRTYPE_ANY || ans.info.klass == DNS_RRCLASS_ANY) { + /* Skip known answers for ANY type & class */ + continue; + } + + rev_v6 = 0; + match = reply->host_replies & check_host(netif, &ans.info, &rev_v6); + if (match) { + reply->probe_query_recv = 1; + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Probe for own host info received\r\n")); + } + + for (i = 0; i < MDNS_MAX_SERVICES; i++) { + service = mdns->services[i]; + if (!service) { + continue; + } + match = reply->serv_replies[i] & check_service(service, &ans.info); + + if (match) { + reply->probe_query_recv = 1; + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Probe for own service info received\r\n")); + } + } + } + + return ERR_OK; +} + +/** + * Add / copy message to delaying message buffer. + * + * @param dest destination msg struct + * @param src source msg struct + */ +static void +mdns_add_msg_to_delayed(struct mdns_outmsg *dest, struct mdns_outmsg *src) +{ + dest->host_questions |= src->host_questions; + dest->host_replies |= src->host_replies; + dest->host_reverse_v6_replies |= src->host_reverse_v6_replies; + for (int i = 0; i < MDNS_MAX_SERVICES; i++) { + dest->serv_questions[i] |= src->serv_questions[i]; + dest->serv_replies[i] |= src->serv_replies[i]; + } + + dest->flags = src->flags; + dest->cache_flush = src->cache_flush; + dest->tx_id = src->tx_id; + dest->legacy_query = src->legacy_query; +} + +/** + * Handle question MDNS packet + * 1. Parse all questions and set bits what answers to send + * 2. Clear pending answers if known answers are supplied + * 3. Define which type of answer is requested + * 4. Send out packet or put it on hold until after random time + * + * @param pkt incoming packet + * @param netif network interface of incoming packet + */ +static void +mdns_handle_question(struct mdns_packet *pkt, struct netif *netif) +{ + struct mdns_host *mdns = NETIF_TO_HOST(netif); + struct mdns_outmsg reply; + u8_t rrs_to_send; + u8_t shared_answer = 0; + u8_t delay_response = 1; + u8_t send_unicast = 0; + u8_t listen_to_QU_bit = 0; + int i; + err_t res; + + if (mdns->probing_state != MDNS_PROBING_COMPLETE) { + /* Don't answer questions until we've verified our domains via probing */ + /* @todo we should check incoming questions during probing for tiebreaking */ + return; + } + + memset(&reply, 0, sizeof(struct mdns_outmsg)); + + /* Parse question */ + res = mdns_parse_pkt_questions(netif, pkt, &reply); + if (res != ERR_OK) { + return; + } + /* Parse answers -> count as known answers because it's a question */ + res = mdns_parse_pkt_known_answers(netif, pkt, &reply); + if (res != ERR_OK) { + return; + } + /* Parse authoritative answers -> probing */ + /* If it's a probe query, we need to directly answer via unicast. */ + res = mdns_parse_pkt_authoritative_answers(netif, pkt, &reply); + if (res != ERR_OK) { + return; + } + /* Ignore additional answers -> do not have any need for them at the moment */ + if(pkt->additional) { + LWIP_DEBUGF(MDNS_DEBUG, + ("MDNS: Query contains additional answers -> they are discarded \r\n")); + } + + /* Any replies on question? */ + rrs_to_send = reply.host_replies | reply.host_questions; + for (i = 0; i < MDNS_MAX_SERVICES; i++) { + rrs_to_send |= reply.serv_replies[i] | reply.serv_questions[i]; + } + + if (!rrs_to_send) { + /* This case is most common */ + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Nothing to answer\r\n")); + return; + } + + reply.flags = DNS_FLAG1_RESPONSE | DNS_FLAG1_AUTHORATIVE; + + /* Detect if it's a legacy querier asking the question + * How to detect legacy DNS query? (RFC6762 section 6.7) + * - source port != 5353 + * - a legacy query can only contain 1 question + */ + if (pkt->source_port != LWIP_IANA_PORT_MDNS) { + if (pkt->questions == 1) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: request from legacy querier\r\n")); + reply.legacy_query = 1; + reply.tx_id = pkt->tx_id; + reply.cache_flush = 0; + } + else { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: ignore query if (src UDP port != 5353) && (!= legacy query)\r\n")); + return; + } + } + else { + reply.cache_flush = 1; + } + + /* Delaying response. + * Always delay the response, unicast or multicast, except when: + * - Answering to a single question with a unique answer (RFC6762 section 6) + * - Answering to a probe query via unicast (RFC6762 section 6) + * + * unique answer? -> not if it includes service type or name ptr's + */ + for (i = 0; i < MDNS_MAX_SERVICES; i++) { + shared_answer |= (reply.serv_replies[i] & + (REPLY_SERVICE_TYPE_PTR | REPLY_SERVICE_NAME_PTR)); + } + if (((pkt->questions == 1) && (!shared_answer)) || reply.probe_query_recv) { + delay_response = 0; + } + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: response %s delayed\r\n", (delay_response ? "randomly" : "not"))); + + /* Unicast / multicast response: + * Answering to (m)DNS querier via unicast response. + * When: + * a) Unicast reply requested && recently multicasted 1/4ttl (RFC6762 section 5.4) + * b) Direct unicast query to port 5353 (RFC6762 section 5.5) + * c) Reply to Legacy DNS querier (RFC6762 section 6.7) + * d) A probe message is received (RFC6762 section 6) + */ + +#if LWIP_IPV6 + if ((IP_IS_V6_VAL(pkt->source_addr) && mdns->ipv6.multicast_timeout_25TTL)) { + listen_to_QU_bit = 1; + } +#endif +#if LWIP_IPV4 + if ((IP_IS_V4_VAL(pkt->source_addr) && mdns->ipv4.multicast_timeout_25TTL)) { + listen_to_QU_bit = 1; + } +#endif + if ( (reply.unicast_reply_requested && listen_to_QU_bit) + || pkt->recv_unicast + || reply.legacy_query + || reply.probe_query_recv ) { + send_unicast = 1; + } + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: send response via %s\r\n", (send_unicast ? "unicast" : "multicast"))); + + /* Send out or put on waiting list */ + if (delay_response) { + if (send_unicast) { +#if LWIP_IPV6 + /* Add answers to IPv6 waiting list if: + * - it's a IPv6 incoming packet + * - no message is in it yet + */ + if (IP_IS_V6_VAL(pkt->source_addr) && !mdns->ipv6.unicast_msg_in_use) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: add answers to unicast IPv6 waiting list\r\n")); + SMEMCPY(&mdns->ipv6.delayed_msg_unicast.dest_addr, &pkt->source_addr, sizeof(ip_addr_t)); + mdns->ipv6.delayed_msg_unicast.dest_port = pkt->source_port; + + mdns_add_msg_to_delayed(&mdns->ipv6.delayed_msg_unicast, &reply); + + mdns_set_timeout(netif, MDNS_RESPONSE_DELAY, mdns_send_unicast_msg_delayed_ipv6, + &mdns->ipv6.unicast_msg_in_use); + } +#endif +#if LWIP_IPV4 + /* Add answers to IPv4 waiting list if: + * - it's a IPv4 incoming packet + * - no message is in it yet + */ + if (IP_IS_V4_VAL(pkt->source_addr) && !mdns->ipv4.unicast_msg_in_use) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: add answers to unicast IPv4 waiting list\r\n")); + SMEMCPY(&mdns->ipv4.delayed_msg_unicast.dest_addr, &pkt->source_addr, sizeof(ip_addr_t)); + mdns->ipv4.delayed_msg_unicast.dest_port = pkt->source_port; + + mdns_add_msg_to_delayed(&mdns->ipv4.delayed_msg_unicast, &reply); + + mdns_set_timeout(netif, MDNS_RESPONSE_DELAY, mdns_send_unicast_msg_delayed_ipv4, + &mdns->ipv4.unicast_msg_in_use); + } +#endif + } + else { +#if LWIP_IPV6 + /* Add answers to IPv6 waiting list if: + * - it's a IPv6 incoming packet + * - and the 1 second timeout is passed (RFC6762 section 6) + */ + if (IP_IS_V6_VAL(pkt->source_addr) && !mdns->ipv6.multicast_timeout) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: add answers to multicast IPv6 waiting list\r\n")); + + mdns_add_msg_to_delayed(&mdns->ipv6.delayed_msg_multicast, &reply); + + mdns_set_timeout(netif, MDNS_RESPONSE_DELAY, mdns_send_multicast_msg_delayed_ipv6, + &mdns->ipv6.multicast_msg_waiting); + } +#endif +#if LWIP_IPV4 + /* Add answers to IPv4 waiting list if: + * - it's a IPv4 incoming packet + * - and the 1 second timeout is passed (RFC6762 section 6) + */ + if (IP_IS_V4_VAL(pkt->source_addr) && !mdns->ipv4.multicast_timeout) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: add answers to multicast IPv4 waiting list\r\n")); + + mdns_add_msg_to_delayed(&mdns->ipv4.delayed_msg_multicast, &reply); + + mdns_set_timeout(netif, MDNS_RESPONSE_DELAY, mdns_send_multicast_msg_delayed_ipv4, + &mdns->ipv4.multicast_msg_waiting); + } +#endif + } + } + else { + if (send_unicast) { + /* Copy source IP/port to use when responding unicast */ + SMEMCPY(&reply.dest_addr, &pkt->source_addr, sizeof(ip_addr_t)); + reply.dest_port = pkt->source_port; + /* send answer directly via unicast */ + res = mdns_send_outpacket(&reply, netif); + if (res != ERR_OK) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Unicast answer could not be send\r\n")); + } + else { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Unicast answer send successfully\r\n")); + } + return; + } + else { + /* Set IP/port to use when responding multicast */ +#if LWIP_IPV6 + if (IP_IS_V6_VAL(pkt->source_addr)) { + if (mdns->ipv6.multicast_timeout) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: we just multicasted, ignore question\r\n")); + return; + } + SMEMCPY(&reply.dest_addr, &v6group, sizeof(ip_addr_t)); + } +#endif +#if LWIP_IPV4 + if (IP_IS_V4_VAL(pkt->source_addr)) { + if (mdns->ipv4.multicast_timeout) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: we just multicasted, ignore question\r\n")); + return; + } + SMEMCPY(&reply.dest_addr, &v4group, sizeof(ip_addr_t)); + } +#endif + reply.dest_port = LWIP_IANA_PORT_MDNS; + /* send answer directly via multicast */ + res = mdns_send_outpacket(&reply, netif); + if (res != ERR_OK) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Multicast answer could not be send\r\n")); + } + else { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Multicast answer send successfully\r\n")); +#if LWIP_IPV6 + if (IP_IS_V6_VAL(pkt->source_addr)) { + mdns_set_timeout(netif, MDNS_MULTICAST_TIMEOUT, mdns_multicast_timeout_reset_ipv6, + &mdns->ipv6.multicast_timeout); + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout started - IPv6\n")); + mdns_set_timeout(netif, MDNS_MULTICAST_TIMEOUT_25TTL, mdns_multicast_timeout_25ttl_reset_ipv6, + &mdns->ipv6.multicast_timeout_25TTL); + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout 1/4 of ttl started - IPv6\n")); + } +#endif +#if LWIP_IPV4 + if (IP_IS_V4_VAL(pkt->source_addr)) { + mdns_set_timeout(netif, MDNS_MULTICAST_TIMEOUT, mdns_multicast_timeout_reset_ipv4, + &mdns->ipv4.multicast_timeout); + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout started - IPv4\n")); + mdns_set_timeout(netif, MDNS_MULTICAST_TIMEOUT_25TTL, mdns_multicast_timeout_25ttl_reset_ipv4, + &mdns->ipv4.multicast_timeout_25TTL); + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout 1/4 of ttl started - IPv4\n")); + } +#endif + } + return; + } } } @@ -1780,9 +1079,15 @@ cleanup: * Only prints debug for now. Will need more code to do conflict resolution. */ static void -mdns_handle_response(struct mdns_packet *pkt) +mdns_handle_response(struct mdns_packet *pkt, struct netif *netif) { - struct mdns_host* mdns = NETIF_TO_HOST(pkt->netif); + struct mdns_host* mdns = NETIF_TO_HOST(netif); + + /* Ignore responses with a source port different from 5353 + * (LWIP_IANA_PORT_MDNS) -> RFC6762 section 6 */ + if (pkt->source_port != LWIP_IANA_PORT_MDNS) { + return; + } /* Ignore all questions */ while (pkt->questions_left) { @@ -1800,7 +1105,7 @@ mdns_handle_response(struct mdns_packet *pkt) struct mdns_answer ans; err_t res; - res = mdns_read_answer(pkt, &ans); + res = mdns_read_answer(pkt, &ans, &pkt->answers_left); if (res != ERR_OK) { LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to parse answer, skipping response packet\n")); return; @@ -1836,9 +1141,9 @@ mdns_handle_response(struct mdns_packet *pkt) } if (conflict != 0) { - sys_untimeout(mdns_probe, pkt->netif); + sys_untimeout(mdns_probe, netif); if (mdns_name_result_cb != NULL) { - mdns_name_result_cb(pkt->netif, MDNS_PROBING_CONFLICT); + mdns_name_result_cb(netif, MDNS_PROBING_CONFLICT); } } } @@ -1881,18 +1186,30 @@ mdns_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, memset(&packet, 0, sizeof(packet)); SMEMCPY(&packet.source_addr, addr, sizeof(packet.source_addr)); packet.source_port = port; - packet.netif = recv_netif; packet.pbuf = p; packet.parse_offset = offset; packet.tx_id = lwip_ntohs(hdr.id); packet.questions = packet.questions_left = lwip_ntohs(hdr.numquestions); - packet.answers = packet.answers_left = lwip_ntohs(hdr.numanswers) + lwip_ntohs(hdr.numauthrr) + lwip_ntohs(hdr.numextrarr); - + packet.answers = packet.answers_left = lwip_ntohs(hdr.numanswers); + packet.authoritative = packet.authoritative_left = lwip_ntohs(hdr.numauthrr); + packet.additional = packet.additional_left = lwip_ntohs(hdr.numextrarr); + + /* Source address check (RFC6762 section 11) -> for responses. + * Source address check (RFC6762 section 5.5) -> for queries. + * When the dest addr == multicast addr we know the packet originated on that + * link. If not, we need to check the source address. We only accept queries + * that originated on the link. Others are discarded. + */ #if LWIP_IPV6 if (IP_IS_V6(ip_current_dest_addr())) { /* instead of having one 'v6group' per netif, just compare zoneless here */ if (!ip_addr_cmp_zoneless(ip_current_dest_addr(), &v6group)) { packet.recv_unicast = 1; + + if (ip6_addr_ismulticast_global(ip_2_ip6(ip_current_src_addr())) + || ip6_addr_isglobal(ip_2_ip6(ip_current_src_addr()))) { + goto dealloc; + } } } #endif @@ -1900,14 +1217,20 @@ mdns_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, if (!IP_IS_V6(ip_current_dest_addr())) { if (!ip_addr_cmp(ip_current_dest_addr(), &v4group)) { packet.recv_unicast = 1; + + if (!ip4_addr_netcmp(ip_2_ip4(ip_current_src_addr()), + netif_ip4_addr(recv_netif), + netif_ip4_netmask(recv_netif))){ + goto dealloc; + } } } #endif if (hdr.flags1 & DNS_FLAG1_RESPONSE) { - mdns_handle_response(&packet); + mdns_handle_response(&packet, recv_netif); } else { - mdns_handle_question(&packet); + mdns_handle_question(&packet, recv_netif); } dealloc: @@ -1948,46 +1271,36 @@ static err_t mdns_send_probe(struct netif* netif, const ip_addr_t *destination) { struct mdns_host* mdns; - struct mdns_outpacket pkt; - struct mdns_domain domain; + struct mdns_outmsg outmsg; u8_t i; err_t res; mdns = NETIF_TO_HOST(netif); - memset(&pkt, 0, sizeof(pkt)); - pkt.netif = netif; + memset(&outmsg, 0, sizeof(outmsg)); /* Add unicast questions with rtype ANY for all our desired records */ - mdns_build_host_domain(&domain, mdns); - res = mdns_add_question(&pkt, &domain, DNS_RRTYPE_ANY, DNS_RRCLASS_IN, 1); - if (res != ERR_OK) { - goto cleanup; - } - pkt.questions++; + outmsg.host_questions = QUESTION_PROBE_HOST_ANY; + for (i = 0; i < MDNS_MAX_SERVICES; i++) { struct mdns_service* service = mdns->services[i]; if (!service) { continue; } - mdns_build_service_domain(&domain, service, 1); - res = mdns_add_question(&pkt, &domain, DNS_RRTYPE_ANY, DNS_RRCLASS_IN, 1); - if (res != ERR_OK) { - goto cleanup; - } - pkt.questions++; + outmsg.serv_questions[i] = QUESTION_PROBE_SERVICE_NAME_ANY; } /* Add answers to the questions above into the authority section for tiebreaking */ #if LWIP_IPV4 if (!ip4_addr_isany_val(*netif_ip4_addr(netif))) { - pkt.host_replies = REPLY_HOST_A; + outmsg.host_replies = REPLY_HOST_A | REPLY_HOST_PTR_V4; } #endif #if LWIP_IPV6 for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) { if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i))) { - pkt.host_replies |= REPLY_HOST_AAAA; + outmsg.host_replies |= REPLY_HOST_AAAA | REPLY_HOST_PTR_V6; + outmsg.host_reverse_v6_replies |= (1 << i); } } #endif @@ -1995,20 +1308,16 @@ mdns_send_probe(struct netif* netif, const ip_addr_t *destination) for (i = 0; i < MDNS_MAX_SERVICES; i++) { struct mdns_service *serv = mdns->services[i]; if (serv) { - pkt.serv_replies[i] = REPLY_SERVICE_SRV | REPLY_SERVICE_TXT; + outmsg.serv_replies[i] = REPLY_SERVICE_SRV | REPLY_SERVICE_TXT + | REPLY_SERVICE_TYPE_PTR | REPLY_SERVICE_NAME_PTR; } } - pkt.tx_id = 0; - pkt.dest_port = LWIP_IANA_PORT_MDNS; - SMEMCPY(&pkt.dest_addr, destination, sizeof(pkt.dest_addr)); - res = mdns_send_outpacket(&pkt, 0); + outmsg.tx_id = 0; + outmsg.dest_port = LWIP_IANA_PORT_MDNS; + SMEMCPY(&outmsg.dest_addr, destination, sizeof(outmsg.dest_addr)); + res = mdns_send_outpacket(&outmsg, netif); -cleanup: - if (pkt.pbuf) { - pbuf_free(pkt.pbuf); - pkt.pbuf = NULL; - } return res; } @@ -2032,11 +1341,11 @@ mdns_probe(void* arg) #if LWIP_IPV4 /*if ipv4 wait with probing until address is set*/ if (!ip4_addr_isany_val(*netif_ip4_addr(netif)) && - mdns_send_probe(netif, IP4_ADDR_ANY) == ERR_OK) + mdns_send_probe(netif, &v4group) == ERR_OK) #endif { #if LWIP_IPV6 - if (mdns_send_probe(netif, IP6_ADDR_ANY) == ERR_OK) + if (mdns_send_probe(netif, &v6group) == ERR_OK) #endif { mdns->probes_sent++; @@ -2053,11 +1362,10 @@ mdns_probe(void* arg) * @param hostname Name to use. Queries for <hostname>.local will be answered * with the IP addresses of the netif. The hostname will be copied, the * given pointer can be on the stack. - * @param dns_ttl Validity time in seconds to send out for IP address data in DNS replies * @return ERR_OK if netif was added, an err_t otherwise */ err_t -mdns_resp_add_netif(struct netif *netif, const char *hostname, u32_t dns_ttl) +mdns_resp_add_netif(struct netif *netif, const char *hostname) { err_t res; struct mdns_host *mdns; @@ -2073,10 +1381,22 @@ mdns_resp_add_netif(struct netif *netif, const char *hostname, u32_t dns_ttl) netif_set_client_data(netif, mdns_netif_client_id, mdns); MEMCPY(&mdns->name, hostname, LWIP_MIN(MDNS_LABEL_MAXLEN, strlen(hostname))); - mdns->dns_ttl = dns_ttl; mdns->probes_sent = 0; mdns->probing_state = MDNS_PROBING_NOT_STARTED; + /* Init delayed message structs with address and port */ +#if LWIP_IPV4 + mdns->ipv4.delayed_msg_multicast.dest_port = LWIP_IANA_PORT_MDNS; + SMEMCPY(&mdns->ipv4.delayed_msg_multicast.dest_addr, &v4group, + sizeof(ip_addr_t)); +#endif + +#if LWIP_IPV6 + mdns->ipv6.delayed_msg_multicast.dest_port = LWIP_IANA_PORT_MDNS; + SMEMCPY(&mdns->ipv6.delayed_msg_multicast.dest_addr, &v6group, + sizeof(ip_addr_t)); +#endif + /* Join multicast groups */ #if LWIP_IPV4 res = igmp_joingroup_netif(netif, ip_2_ip4(&v4group)); @@ -2182,14 +1502,13 @@ mdns_resp_rename_netif(struct netif *netif, const char *hostname) * @param proto The service protocol, DNSSD_PROTO_TCP for TCP ("_tcp") and DNSSD_PROTO_UDP * for others ("_udp") * @param port The port the service listens to - * @param dns_ttl Validity time in seconds to send out for service data in DNS replies * @param txt_fn Callback to get TXT data. Will be called each time a TXT reply is created to * allow dynamic replies. * @param txt_data Userdata pointer for txt_fn * @return service_id if the service was added to the netif, an err_t otherwise */ s8_t -mdns_resp_add_service(struct netif *netif, const char *name, const char *service, enum mdns_sd_proto proto, u16_t port, u32_t dns_ttl, service_get_txt_fn_t txt_fn, void *txt_data) +mdns_resp_add_service(struct netif *netif, const char *name, const char *service, enum mdns_sd_proto proto, u16_t port, service_get_txt_fn_t txt_fn, void *txt_data) { s8_t i; s8_t slot = -1; @@ -2222,7 +1541,6 @@ mdns_resp_add_service(struct netif *netif, const char *name, const char *service srv->txt_userdata = txt_data; srv->proto = (u16_t)proto; srv->port = port; - srv->dns_ttl = dns_ttl; mdns->services[slot] = srv; @@ -2328,11 +1646,23 @@ mdns_resp_announce(struct netif *netif) if (mdns->probing_state == MDNS_PROBING_COMPLETE) { /* Announce on IPv6 and IPv4 */ #if LWIP_IPV6 - mdns_announce(netif, IP6_ADDR_ANY); + mdns_announce(netif, &v6group); + mdns_set_timeout(netif, MDNS_MULTICAST_TIMEOUT, mdns_multicast_timeout_reset_ipv6, + &mdns->ipv6.multicast_timeout); + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout started - IPv6\n")); + mdns_set_timeout(netif, MDNS_MULTICAST_TIMEOUT_25TTL, mdns_multicast_timeout_25ttl_reset_ipv6, + &mdns->ipv6.multicast_timeout_25TTL); + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout 1/4 of ttl started - IPv6\n")); #endif #if LWIP_IPV4 if (!ip4_addr_isany_val(*netif_ip4_addr(netif))) { - mdns_announce(netif, IP4_ADDR_ANY); + mdns_announce(netif, &v4group); + mdns_set_timeout(netif, MDNS_MULTICAST_TIMEOUT, mdns_multicast_timeout_reset_ipv4, + &mdns->ipv4.multicast_timeout); + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout started - IPv4\n")); + mdns_set_timeout(netif, MDNS_MULTICAST_TIMEOUT_25TTL, mdns_multicast_timeout_25ttl_reset_ipv4, + &mdns->ipv4.multicast_timeout_25TTL); + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout 1/4 of ttl started - IPv4\n")); } #endif } /* else: ip address changed while probing was ongoing? @todo reset counter to restart? */ @@ -2387,9 +1717,9 @@ mdns_resp_init(void) mdns_pcb = udp_new_ip_type(IPADDR_TYPE_ANY); LWIP_ASSERT("Failed to allocate pcb", mdns_pcb != NULL); #if LWIP_MULTICAST_TX_OPTIONS - udp_set_multicast_ttl(mdns_pcb, MDNS_TTL); + udp_set_multicast_ttl(mdns_pcb, MDNS_IP_TTL); #else - mdns_pcb->ttl = MDNS_TTL; + mdns_pcb->ttl = MDNS_IP_TTL; #endif res = udp_bind(mdns_pcb, IP_ANY_TYPE, LWIP_IANA_PORT_MDNS); LWIP_UNUSED_ARG(res); /* in case of LWIP_NOASSERT */ diff --git a/src/apps/mdns/mdns_domain.c b/src/apps/mdns/mdns_domain.c new file mode 100644 index 000000000..2ad922135 --- /dev/null +++ b/src/apps/mdns/mdns_domain.c @@ -0,0 +1,548 @@ +/** + * @file + * MDNS responder implementation - domain related functionalities + */ + +/* + * Copyright (c) 2015 Verisure Innovation AB + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * This file is part of the lwIP TCP/IP stack. + * + * Author: Erik Ekman + * Author: Jasper Verschueren + * + */ + +#include "lwip/apps/mdns.h" +#include "lwip/apps/mdns_domain.h" +#include "lwip/apps/mdns_priv.h" +#include "lwip/prot/dns.h" + +#include + +#if LWIP_IPV6 +#include "lwip/prot/ip6.h" +#endif + +#if LWIP_MDNS_RESPONDER + +/* Stored offsets to beginning of domain names + * Used for compression. + */ +#define DOMAIN_JUMP_SIZE 2 +#define DOMAIN_JUMP 0xc000 + +#define TOPDOMAIN_LOCAL "local" + +#define REVERSE_PTR_TOPDOMAIN "arpa" +#define REVERSE_PTR_V4_DOMAIN "in-addr" +#define REVERSE_PTR_V6_DOMAIN "ip6" + +static const char *dnssd_protos[] = { + "_udp", /* DNSSD_PROTO_UDP */ + "_tcp", /* DNSSD_PROTO_TCP */ +}; + +/* forward declarations (function prototypes)*/ +static err_t mdns_domain_add_label_base(struct mdns_domain *domain, u8_t len); +static err_t mdns_domain_add_label_pbuf(struct mdns_domain *domain, + const struct pbuf *p, u16_t offset, + u8_t len); +static u16_t mdns_readname_loop(struct pbuf *p, u16_t offset, + struct mdns_domain *domain, unsigned depth); +static err_t mdns_add_dotlocal(struct mdns_domain *domain); + + +static err_t +mdns_domain_add_label_base(struct mdns_domain *domain, u8_t len) +{ + if (len > MDNS_LABEL_MAXLEN) { + return ERR_VAL; + } + 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; + } + domain->name[domain->length] = len; + domain->length++; + return ERR_OK; +} + +/** + * Add a label part to a domain + * @param domain The domain to add a label to + * @param label The label to add, like <hostname>, 'local', 'com' or '' + * @param len The length of the label + * @return ERR_OK on success, an err_t otherwise if label too long + */ +err_t +mdns_domain_add_label(struct mdns_domain *domain, const char *label, u8_t len) +{ + err_t err = mdns_domain_add_label_base(domain, len); + if (err != ERR_OK) { + return err; + } + if (len) { + MEMCPY(&domain->name[domain->length], label, len); + domain->length += len; + } + return ERR_OK; +} + +/** + * Add a label part to a domain (@see mdns_domain_add_label but copy directly from pbuf) + */ +static err_t +mdns_domain_add_label_pbuf(struct mdns_domain *domain, const struct pbuf *p, u16_t offset, u8_t len) +{ + err_t err = mdns_domain_add_label_base(domain, len); + if (err != ERR_OK) { + return err; + } + if (len) { + if (pbuf_copy_partial(p, &domain->name[domain->length], len, offset) != len) { + /* take back the ++ done before */ + domain->length--; + return ERR_ARG; + } + domain->length += len; + } + return ERR_OK; +} + +/** + * Internal readname function with max 6 levels of recursion following jumps + * while decompressing name + */ +static u16_t +mdns_readname_loop(struct pbuf *p, u16_t offset, struct mdns_domain *domain, unsigned depth) +{ + u8_t c; + + do { + if (depth > 5) { + /* Too many jumps */ + return MDNS_READNAME_ERROR; + } + + c = pbuf_get_at(p, offset); + offset++; + + /* is this a compressed label? */ + if ((c & 0xc0) == 0xc0) { + u16_t jumpaddr; + if (offset >= p->tot_len) { + /* Make sure both jump bytes fit in the packet */ + return MDNS_READNAME_ERROR; + } + jumpaddr = (((c & 0x3f) << 8) | (pbuf_get_at(p, offset) & 0xff)); + offset++; + if (jumpaddr >= SIZEOF_DNS_HDR && jumpaddr < p->tot_len) { + u16_t res; + /* Recursive call, maximum depth will be checked */ + res = mdns_readname_loop(p, jumpaddr, domain, depth + 1); + /* Dont return offset since new bytes were not read (jumped to somewhere in packet) */ + if (res == MDNS_READNAME_ERROR) { + return res; + } + } else { + return MDNS_READNAME_ERROR; + } + break; + } + + /* normal label */ + if (c <= MDNS_LABEL_MAXLEN) { + err_t res; + + if (c + domain->length >= MDNS_DOMAIN_MAXLEN) { + return MDNS_READNAME_ERROR; + } + res = mdns_domain_add_label_pbuf(domain, p, offset, c); + if (res != ERR_OK) { + return MDNS_READNAME_ERROR; + } + offset += c; + } else { + /* bad length byte */ + return MDNS_READNAME_ERROR; + } + } while (c != 0); + + return offset; +} + +/** + * Read possibly compressed domain name from packet buffer + * @param p The packet + * @param offset start position of domain name in packet + * @param domain The domain name destination + * @return The new offset after the domain, or MDNS_READNAME_ERROR + * if reading failed + */ +u16_t +mdns_readname(struct pbuf *p, u16_t offset, struct mdns_domain *domain) +{ + memset(domain, 0, sizeof(struct mdns_domain)); + return mdns_readname_loop(p, offset, domain, 0); +} + +/** + * Print domain name to debug output + * @param domain The domain name + */ +void +mdns_domain_debug_print(struct mdns_domain *domain) +{ + u8_t *src = domain->name; + u8_t i; + + while (*src) { + u8_t label_len = *src; + src++; + for (i = 0; i < label_len; i++) { + LWIP_DEBUGF(MDNS_DEBUG, ("%c", src[i])); + } + src += label_len; + LWIP_DEBUGF(MDNS_DEBUG, (".")); + } +} + +/** + * Return 1 if contents of domains match (case-insensitive) + * @param a Domain name to compare 1 + * @param b Domain name to compare 2 + * @return 1 if domains are equal ignoring case, 0 otherwise + */ +int +mdns_domain_eq(struct mdns_domain *a, struct mdns_domain *b) +{ + u8_t *ptra, *ptrb; + u8_t len; + int res; + + if (a->length != b->length) { + return 0; + } + + ptra = a->name; + ptrb = b->name; + while (*ptra && *ptrb && ptra < &a->name[a->length]) { + if (*ptra != *ptrb) { + return 0; + } + len = *ptra; + ptra++; + ptrb++; + res = lwip_strnicmp((char *) ptra, (char *) ptrb, len); + if (res != 0) { + return 0; + } + ptra += len; + ptrb += len; + } + if (*ptra != *ptrb && ptra < &a->name[a->length]) { + return 0; + } + return 1; +} + +#if LWIP_IPV4 +/** + * Build domain for reverse lookup of IPv4 address + * like 12.0.168.192.in-addr.arpa. for 192.168.0.12 + * @param domain Where to write the domain name + * @param addr Pointer to an IPv4 address to encode + * @return ERR_OK if domain was written, an err_t otherwise + */ +err_t +mdns_build_reverse_v4_domain(struct mdns_domain *domain, const ip4_addr_t *addr) +{ + int i; + err_t res; + const u8_t *ptr; + + LWIP_UNUSED_ARG(res); + if (!domain || !addr) { + return ERR_ARG; + } + memset(domain, 0, sizeof(struct mdns_domain)); + ptr = (const u8_t *) addr; + for (i = sizeof(ip4_addr_t) - 1; i >= 0; i--) { + char buf[4]; + u8_t val = ptr[i]; + + lwip_itoa(buf, sizeof(buf), val); + res = mdns_domain_add_label(domain, buf, (u8_t)strlen(buf)); + LWIP_ERROR("mdns_build_reverse_v4_domain: Failed to add label", (res == ERR_OK), return res); + } + res = mdns_domain_add_label(domain, REVERSE_PTR_V4_DOMAIN, (u8_t)(sizeof(REVERSE_PTR_V4_DOMAIN) - 1)); + LWIP_ERROR("mdns_build_reverse_v4_domain: Failed to add label", (res == ERR_OK), return res); + res = mdns_domain_add_label(domain, REVERSE_PTR_TOPDOMAIN, (u8_t)(sizeof(REVERSE_PTR_TOPDOMAIN) - 1)); + LWIP_ERROR("mdns_build_reverse_v4_domain: Failed to add label", (res == ERR_OK), return res); + res = mdns_domain_add_label(domain, NULL, 0); + LWIP_ERROR("mdns_build_reverse_v4_domain: Failed to add label", (res == ERR_OK), return res); + + return ERR_OK; +} +#endif + +#if LWIP_IPV6 +/** + * Build domain for reverse lookup of IP address + * like b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa. for 2001:db8::567:89ab + * @param domain Where to write the domain name + * @param addr Pointer to an IPv6 address to encode + * @return ERR_OK if domain was written, an err_t otherwise + */ +err_t +mdns_build_reverse_v6_domain(struct mdns_domain *domain, const ip6_addr_t *addr) +{ + int i; + err_t res; + const u8_t *ptr; + LWIP_UNUSED_ARG(res); + if (!domain || !addr) { + return ERR_ARG; + } + memset(domain, 0, sizeof(struct mdns_domain)); + ptr = (const u8_t *) addr; + for (i = sizeof(ip6_addr_p_t) - 1; i >= 0; i--) { + char buf; + u8_t byte = ptr[i]; + int j; + for (j = 0; j < 2; j++) { + if ((byte & 0x0F) < 0xA) { + buf = '0' + (byte & 0x0F); + } else { + buf = 'a' + (byte & 0x0F) - 0xA; + } + res = mdns_domain_add_label(domain, &buf, sizeof(buf)); + LWIP_ERROR("mdns_build_reverse_v6_domain: Failed to add label", (res == ERR_OK), return res); + byte >>= 4; + } + } + res = mdns_domain_add_label(domain, REVERSE_PTR_V6_DOMAIN, (u8_t)(sizeof(REVERSE_PTR_V6_DOMAIN) - 1)); + LWIP_ERROR("mdns_build_reverse_v6_domain: Failed to add label", (res == ERR_OK), return res); + res = mdns_domain_add_label(domain, REVERSE_PTR_TOPDOMAIN, (u8_t)(sizeof(REVERSE_PTR_TOPDOMAIN) - 1)); + LWIP_ERROR("mdns_build_reverse_v6_domain: Failed to add label", (res == ERR_OK), return res); + res = mdns_domain_add_label(domain, NULL, 0); + LWIP_ERROR("mdns_build_reverse_v6_domain: Failed to add label", (res == ERR_OK), return res); + + return ERR_OK; +} +#endif + +/* Add .local. to domain */ +static err_t +mdns_add_dotlocal(struct mdns_domain *domain) +{ + err_t res = mdns_domain_add_label(domain, TOPDOMAIN_LOCAL, (u8_t)(sizeof(TOPDOMAIN_LOCAL) - 1)); + LWIP_UNUSED_ARG(res); + LWIP_ERROR("mdns_add_dotlocal: Failed to add label", (res == ERR_OK), return res); + return mdns_domain_add_label(domain, NULL, 0); +} + +/** + * Build the .local. domain name + * @param domain Where to write the domain name + * @param mdns TMDNS netif descriptor. + * @return ERR_OK if domain .local. was written, an err_t otherwise + */ +err_t +mdns_build_host_domain(struct mdns_domain *domain, struct mdns_host *mdns) +{ + err_t res; + LWIP_UNUSED_ARG(res); + memset(domain, 0, sizeof(struct mdns_domain)); + LWIP_ERROR("mdns_build_host_domain: mdns != NULL", (mdns != NULL), return ERR_VAL); + res = mdns_domain_add_label(domain, mdns->name, (u8_t)strlen(mdns->name)); + LWIP_ERROR("mdns_build_host_domain: Failed to add label", (res == ERR_OK), return res); + return mdns_add_dotlocal(domain); +} + +/** + * Build the lookup-all-services special DNS-SD domain name + * @param domain Where to write the domain name + * @return ERR_OK if domain _services._dns-sd._udp.local. was written, an err_t otherwise + */ +err_t +mdns_build_dnssd_domain(struct mdns_domain *domain) +{ + err_t res; + LWIP_UNUSED_ARG(res); + memset(domain, 0, sizeof(struct mdns_domain)); + res = mdns_domain_add_label(domain, "_services", (u8_t)(sizeof("_services") - 1)); + LWIP_ERROR("mdns_build_dnssd_domain: Failed to add label", (res == ERR_OK), return res); + res = mdns_domain_add_label(domain, "_dns-sd", (u8_t)(sizeof("_dns-sd") - 1)); + LWIP_ERROR("mdns_build_dnssd_domain: Failed to add label", (res == ERR_OK), return res); + res = mdns_domain_add_label(domain, dnssd_protos[DNSSD_PROTO_UDP], (u8_t)strlen(dnssd_protos[DNSSD_PROTO_UDP])); + LWIP_ERROR("mdns_build_dnssd_domain: Failed to add label", (res == ERR_OK), return res); + return mdns_add_dotlocal(domain); +} + +/** + * Build domain name for a service + * @param domain Where to write the domain name + * @param service The service 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, + * ...local. will be written, otherwise ..local. + * An err_t is returned on error. + */ +err_t +mdns_build_service_domain(struct mdns_domain *domain, struct mdns_service *service, int include_name) +{ + err_t res; + LWIP_UNUSED_ARG(res); + memset(domain, 0, sizeof(struct mdns_domain)); + if (include_name) { + res = mdns_domain_add_label(domain, service->name, (u8_t)strlen(service->name)); + LWIP_ERROR("mdns_build_service_domain: Failed to add label", (res == ERR_OK), return res); + } + res = mdns_domain_add_label(domain, service->service, (u8_t)strlen(service->service)); + LWIP_ERROR("mdns_build_service_domain: Failed to add label", (res == ERR_OK), return res); + res = mdns_domain_add_label(domain, dnssd_protos[service->proto], (u8_t)strlen(dnssd_protos[service->proto])); + LWIP_ERROR("mdns_build_service_domain: Failed to add label", (res == ERR_OK), return res); + return mdns_add_dotlocal(domain); +} + +/** + * Return bytes needed to write before jump for best result of compressing supplied domain + * against domain in outpacket starting at specified offset. + * If a match is found, offset is updated to where to jump to + * @param pbuf Pointer to pbuf with the partially constructed DNS packet + * @param offset Start position of a domain written earlier. If this location is suitable + * for compression, the pointer is updated to where in the domain to jump to. + * @param domain The domain to write + * @return Number of bytes to write of the new domain before writing a jump to the offset. + * If compression can not be done against this previous domain name, the full new + * domain length is returned. + */ +u16_t +mdns_compress_domain(struct pbuf *pbuf, u16_t *offset, struct mdns_domain *domain) +{ + struct mdns_domain target; + u16_t target_end; + u8_t target_len; + u8_t writelen = 0; + u8_t *ptr; + if (pbuf == NULL) { + return domain->length; + } + target_end = mdns_readname(pbuf, *offset, &target); + if (target_end == MDNS_READNAME_ERROR) { + return domain->length; + } + target_len = (u8_t)(target_end - *offset); + ptr = domain->name; + while (writelen < domain->length) { + u8_t domainlen = (u8_t)(domain->length - writelen); + u8_t labellen; + if (domainlen <= target.length && domainlen > DOMAIN_JUMP_SIZE) { + /* Compare domains if target is long enough, and we have enough left of the domain */ + u8_t targetpos = (u8_t)(target.length - domainlen); + if ((targetpos + DOMAIN_JUMP_SIZE) >= target_len) { + /* We are checking at or beyond a jump in the original, stop looking */ + break; + } + if (target.length >= domainlen && + memcmp(&domain->name[writelen], &target.name[targetpos], domainlen) == 0) { + *offset += targetpos; + return writelen; + } + } + /* Skip to next label in domain */ + labellen = *ptr; + writelen += 1 + labellen; + ptr += 1 + labellen; + } + /* Nothing found */ + return domain->length; +} + +/** + * Write domain to outpacket. Compression will be attempted, + * unless domain->skip_compression is set. + * @param outpkt The outpacket to write to + * @param domain The domain name to write + * @return ERR_OK on success, an err_t otherwise + */ +err_t +mdns_write_domain(struct mdns_outpacket *outpkt, struct mdns_domain *domain) +{ + int i; + err_t res; + u16_t writelen = domain->length; + u16_t jump_offset = 0; + u16_t jump; + + if (!domain->skip_compression) { + for (i = 0; i < NUM_DOMAIN_OFFSETS; i++) { + u16_t offset = outpkt->domain_offsets[i]; + if (offset) { + u16_t len = mdns_compress_domain(outpkt->pbuf, &offset, domain); + if (len < writelen) { + writelen = len; + jump_offset = offset; + } + } + } + } + + if (writelen) { + /* Write uncompressed part of name */ + res = pbuf_take_at(outpkt->pbuf, domain->name, writelen, outpkt->write_offset); + if (res != ERR_OK) { + return res; + } + + /* Store offset of this new domain */ + for (i = 0; i < NUM_DOMAIN_OFFSETS; i++) { + if (outpkt->domain_offsets[i] == 0) { + outpkt->domain_offsets[i] = outpkt->write_offset; + break; + } + } + + outpkt->write_offset += writelen; + } + if (jump_offset) { + /* Write jump */ + jump = lwip_htons(DOMAIN_JUMP | jump_offset); + res = pbuf_take_at(outpkt->pbuf, &jump, DOMAIN_JUMP_SIZE, outpkt->write_offset); + if (res != ERR_OK) { + return res; + } + outpkt->write_offset += DOMAIN_JUMP_SIZE; + } + return ERR_OK; +} + +#endif /* LWIP_MDNS_RESPONDER */ diff --git a/src/apps/mdns/mdns_out.c b/src/apps/mdns/mdns_out.c new file mode 100644 index 000000000..ac4d37ace --- /dev/null +++ b/src/apps/mdns/mdns_out.c @@ -0,0 +1,992 @@ +/** + * @file + * MDNS responder implementation - output related functionalities + */ + +/* + * Copyright (c) 2015 Verisure Innovation AB + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * This file is part of the lwIP TCP/IP stack. + * + * Author: Erik Ekman + * Author: Jasper Verschueren + * + */ + +#include "lwip/apps/mdns_out.h" +#include "lwip/apps/mdns_priv.h" +#include "lwip/apps/mdns_domain.h" +#include "lwip/prot/dns.h" +#include "lwip/prot/iana.h" +#include "lwip/udp.h" + +#include + +#if LWIP_IPV6 +#include "lwip/prot/ip6.h" +#endif + + +#if LWIP_MDNS_RESPONDER + +/* Payload size allocated for each outgoing UDP packet */ +#define OUTPACKET_SIZE 500 + +/* Function prototypes */ +static void mdns_clear_outmsg(struct mdns_outmsg *outmsg); + +/** + * Call user supplied function to setup TXT data + * @param service The service to build TXT record for + */ +void +mdns_prepare_txtdata(struct mdns_service *service) +{ + memset(&service->txtdata, 0, sizeof(struct mdns_domain)); + if (service->txt_fn) { + service->txt_fn(service, service->txt_userdata); + } +} + +/** + * Write a question to an outpacket + * A question contains domain, type and class. Since an answer also starts with these fields this function is also + * called from mdns_add_answer(). + * @param outpkt The outpacket to write to + * @param domain The domain name the answer is for + * @param type The DNS type of the answer (like 'AAAA', 'SRV') + * @param klass The DNS type of the answer (like 'IN') + * @param unicast If highest bit in class should be set, to instruct the responder to + * reply with a unicast packet + * @return ERR_OK on success, an err_t otherwise + */ +static err_t +mdns_add_question(struct mdns_outpacket *outpkt, struct mdns_domain *domain, + u16_t type, u16_t klass, u16_t unicast) +{ + u16_t question_len; + u16_t field16; + err_t res; + + if (!outpkt->pbuf) { + /* If no pbuf is active, allocate one */ + outpkt->pbuf = pbuf_alloc(PBUF_TRANSPORT, OUTPACKET_SIZE, PBUF_RAM); + if (!outpkt->pbuf) { + return ERR_MEM; + } + outpkt->write_offset = SIZEOF_DNS_HDR; + } + + /* Worst case calculation. Domain string might be compressed */ + question_len = domain->length + sizeof(type) + sizeof(klass); + if (outpkt->write_offset + question_len > outpkt->pbuf->tot_len) { + /* No space */ + return ERR_MEM; + } + + /* Write name */ + res = mdns_write_domain(outpkt, domain); + if (res != ERR_OK) { + return res; + } + + /* Write type */ + field16 = lwip_htons(type); + res = pbuf_take_at(outpkt->pbuf, &field16, sizeof(field16), outpkt->write_offset); + if (res != ERR_OK) { + return res; + } + outpkt->write_offset += sizeof(field16); + + /* Write class */ + if (unicast) { + klass |= 0x8000; + } + field16 = lwip_htons(klass); + res = pbuf_take_at(outpkt->pbuf, &field16, sizeof(field16), outpkt->write_offset); + if (res != ERR_OK) { + return res; + } + outpkt->write_offset += sizeof(field16); + + return ERR_OK; +} + +/** + * Write answer to reply packet. + * buf or answer_domain can be null. The rd_length written will be buf_length + + * size of (compressed) domain. Most uses will need either buf or answer_domain, + * special case is SRV that starts with 3 u16 and then a domain name. + * @param reply The outpacket to write to + * @param domain The domain name the answer is for + * @param type The DNS type of the answer (like 'AAAA', 'SRV') + * @param klass The DNS type of the answer (like 'IN') + * @param cache_flush If highest bit in class should be set, to instruct receiver that + * this reply replaces any earlier answer for this domain/type/class + * @param ttl Validity time in seconds to send out for IP address data in DNS replies + * @param buf Pointer to buffer of answer data + * @param buf_length Length of variable data + * @param answer_domain A domain to write after any buffer data as answer + * @return ERR_OK on success, an err_t otherwise + */ +static err_t +mdns_add_answer(struct mdns_outpacket *reply, struct mdns_domain *domain, + u16_t type, u16_t klass, u16_t cache_flush, u32_t ttl, + const u8_t *buf, size_t buf_length, struct mdns_domain *answer_domain) +{ + u16_t answer_len; + u16_t field16; + u16_t rdlen_offset; + u16_t answer_offset; + u32_t field32; + err_t res; + + if (!reply->pbuf) { + /* If no pbuf is active, allocate one */ + reply->pbuf = pbuf_alloc(PBUF_TRANSPORT, OUTPACKET_SIZE, PBUF_RAM); + if (!reply->pbuf) { + return ERR_MEM; + } + reply->write_offset = SIZEOF_DNS_HDR; + } + + /* Worst case calculation. Domain strings might be compressed */ + answer_len = domain->length + sizeof(type) + sizeof(klass) + sizeof(ttl) + sizeof(field16)/*rd_length*/; + if (buf) { + answer_len += (u16_t)buf_length; + } + if (answer_domain) { + answer_len += answer_domain->length; + } + if (reply->write_offset + answer_len > reply->pbuf->tot_len) { + /* No space */ + return ERR_MEM; + } + + /* Answer starts with same data as question, then more fields */ + mdns_add_question(reply, domain, type, klass, cache_flush); + + /* Write TTL */ + field32 = lwip_htonl(ttl); + res = pbuf_take_at(reply->pbuf, &field32, sizeof(field32), reply->write_offset); + if (res != ERR_OK) { + return res; + } + reply->write_offset += sizeof(field32); + + /* Store offsets and skip forward to the data */ + rdlen_offset = reply->write_offset; + reply->write_offset += sizeof(field16); + answer_offset = reply->write_offset; + + if (buf) { + /* Write static data */ + res = pbuf_take_at(reply->pbuf, buf, (u16_t)buf_length, reply->write_offset); + if (res != ERR_OK) { + return res; + } + reply->write_offset += (u16_t)buf_length; + } + + if (answer_domain) { + /* Write name answer (compressed if possible) */ + res = mdns_write_domain(reply, answer_domain); + if (res != ERR_OK) { + return res; + } + } + + /* Write rd_length after when we know the answer size */ + field16 = lwip_htons(reply->write_offset - answer_offset); + res = pbuf_take_at(reply->pbuf, &field16, sizeof(field16), rdlen_offset); + + return res; +} + +/** Write an ANY host question to outpacket */ +static err_t +mdns_add_any_host_question(struct mdns_outpacket *outpkt, + struct mdns_host *mdns, + u16_t request_unicast_reply) +{ + struct mdns_domain host; + mdns_build_host_domain(&host, mdns); + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Adding host question for ANY type\n")); + return mdns_add_question(outpkt, &host, DNS_RRTYPE_ANY, DNS_RRCLASS_IN, + request_unicast_reply); +} + +/** Write an ANY service instance question to outpacket */ +static err_t +mdns_add_any_service_question(struct mdns_outpacket *outpkt, + struct mdns_service *service, + u16_t request_unicast_reply) +{ + struct mdns_domain domain; + mdns_build_service_domain(&domain, service, 1); + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Adding service instance question for ANY type\n")); + return mdns_add_question(outpkt, &domain, DNS_RRTYPE_ANY, DNS_RRCLASS_IN, + request_unicast_reply); +} + +#if LWIP_IPV4 +/** Write an IPv4 address (A) RR to outpacket */ +static err_t +mdns_add_a_answer(struct mdns_outpacket *reply, struct mdns_outmsg *msg, + struct netif *netif) +{ + err_t res; + u32_t ttl = MDNS_TTL_120; + struct mdns_domain host; + mdns_build_host_domain(&host, netif_mdns_data(netif)); + /* When answering to a legacy querier, we need to repeat the question and + * limit the ttl to the short legacy ttl */ + if(msg->legacy_query) { + /* Repeating the question only needs to be done for the question asked + * (max one question), not for the additional records. */ + if(reply->questions < 1) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Add question for legacy query\n")); + res = mdns_add_question(reply, &host, DNS_RRTYPE_A, DNS_RRCLASS_IN, 0); + if (res != ERR_OK) { + return res; + } + reply->questions = 1; + } + /* ttl of legacy answer may not be greater then 10 seconds */ + ttl = MDNS_TTL_10; + } + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with A record\n")); + return mdns_add_answer(reply, &host, DNS_RRTYPE_A, DNS_RRCLASS_IN, msg->cache_flush, + ttl, (const u8_t *) netif_ip4_addr(netif), + sizeof(ip4_addr_t), NULL); +} + +/** Write a 4.3.2.1.in-addr.arpa -> hostname.local PTR RR to outpacket */ +static err_t +mdns_add_hostv4_ptr_answer(struct mdns_outpacket *reply, struct mdns_outmsg *msg, + struct netif *netif) +{ + err_t res; + u32_t ttl = MDNS_TTL_120; + struct mdns_domain host, revhost; + mdns_build_host_domain(&host, netif_mdns_data(netif)); + mdns_build_reverse_v4_domain(&revhost, netif_ip4_addr(netif)); + /* When answering to a legacy querier, we need to repeat the question and + * limit the ttl to the short legacy ttl */ + if(msg->legacy_query) { + /* Repeating the question only needs to be done for the question asked + * (max one question), not for the additional records. */ + if(reply->questions < 1) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Add question for legacy query\n")); + res = mdns_add_question(reply, &revhost, DNS_RRTYPE_PTR, DNS_RRCLASS_IN, 0); + if (res != ERR_OK) { + return res; + } + reply->questions = 1; + } + /* ttl of legacy answer may not be greater then 10 seconds */ + ttl = MDNS_TTL_10; + } + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with v4 PTR record\n")); + return mdns_add_answer(reply, &revhost, DNS_RRTYPE_PTR, DNS_RRCLASS_IN, + msg->cache_flush, ttl, NULL, 0, &host); +} +#endif + +#if LWIP_IPV6 +/** Write an IPv6 address (AAAA) RR to outpacket */ +static err_t +mdns_add_aaaa_answer(struct mdns_outpacket *reply, struct mdns_outmsg *msg, + struct netif *netif, int addrindex) +{ + err_t res; + u32_t ttl = MDNS_TTL_120; + struct mdns_domain host; + mdns_build_host_domain(&host, netif_mdns_data(netif)); + /* When answering to a legacy querier, we need to repeat the question and + * limit the ttl to the short legacy ttl */ + if(msg->legacy_query) { + /* Repeating the question only needs to be done for the question asked + * (max one question), not for the additional records. */ + if(reply->questions < 1) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Add question for legacy query\n")); + res = mdns_add_question(reply, &host, DNS_RRTYPE_AAAA, DNS_RRCLASS_IN, 0); + if (res != ERR_OK) { + return res; + } + reply->questions = 1; + } + /* ttl of legacy answer may not be greater then 10 seconds */ + ttl = MDNS_TTL_10; + } + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with AAAA record\n")); + return mdns_add_answer(reply, &host, DNS_RRTYPE_AAAA, DNS_RRCLASS_IN, msg->cache_flush, + ttl, (const u8_t *) netif_ip6_addr(netif, addrindex), + sizeof(ip6_addr_p_t), NULL); +} + +/** Write a x.y.z.ip6.arpa -> hostname.local PTR RR to outpacket */ +static err_t +mdns_add_hostv6_ptr_answer(struct mdns_outpacket *reply, struct mdns_outmsg *msg, + struct netif *netif, int addrindex) +{ + err_t res; + u32_t ttl = MDNS_TTL_120; + struct mdns_domain host, revhost; + mdns_build_host_domain(&host, netif_mdns_data(netif)); + mdns_build_reverse_v6_domain(&revhost, netif_ip6_addr(netif, addrindex)); + /* When answering to a legacy querier, we need to repeat the question and + * limit the ttl to the short legacy ttl */ + if(msg->legacy_query) { + /* Repeating the question only needs to be done for the question asked + * (max one question), not for the additional records. */ + if(reply->questions < 1) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Add question for legacy query\n")); + res = mdns_add_question(reply, &revhost, DNS_RRTYPE_PTR, DNS_RRCLASS_IN, 0); + if (res != ERR_OK) { + return res; + } + reply->questions = 1; + } + /* ttl of legacy answer may not be greater then 10 seconds */ + ttl = MDNS_TTL_10; + } + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with v6 PTR record\n")); + return mdns_add_answer(reply, &revhost, DNS_RRTYPE_PTR, DNS_RRCLASS_IN, + msg->cache_flush, ttl, NULL, 0, &host); +} +#endif + +/** Write an all-services -> servicetype PTR RR to outpacket */ +static err_t +mdns_add_servicetype_ptr_answer(struct mdns_outpacket *reply, struct mdns_outmsg *msg, + struct mdns_service *service) +{ + err_t res; + u32_t ttl = MDNS_TTL_4500; + struct mdns_domain service_type, service_dnssd; + mdns_build_service_domain(&service_type, service, 0); + mdns_build_dnssd_domain(&service_dnssd); + /* When answering to a legacy querier, we need to repeat the question and + * limit the ttl to the short legacy ttl */ + if(msg->legacy_query) { + /* Repeating the question only needs to be done for the question asked + * (max one question), not for the additional records. */ + if(reply->questions < 1) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Add question for legacy query\n")); + res = mdns_add_question(reply, &service_dnssd, DNS_RRTYPE_PTR, DNS_RRCLASS_IN, 0); + if (res != ERR_OK) { + return res; + } + reply->questions = 1; + } + /* ttl of legacy answer may not be greater then 10 seconds */ + ttl = MDNS_TTL_10; + } + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with service type PTR record\n")); + return mdns_add_answer(reply, &service_dnssd, DNS_RRTYPE_PTR, DNS_RRCLASS_IN, + 0, ttl, NULL, 0, &service_type); +} + +/** Write a servicetype -> servicename PTR RR to outpacket */ +static err_t +mdns_add_servicename_ptr_answer(struct mdns_outpacket *reply, struct mdns_outmsg *msg, + struct mdns_service *service) +{ + err_t res; + u32_t ttl = MDNS_TTL_120; + struct mdns_domain service_type, service_instance; + mdns_build_service_domain(&service_type, service, 0); + mdns_build_service_domain(&service_instance, service, 1); + /* When answering to a legacy querier, we need to repeat the question and + * limit the ttl to the short legacy ttl */ + if(msg->legacy_query) { + /* Repeating the question only needs to be done for the question asked + * (max one question), not for the additional records. */ + if(reply->questions < 1) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Add question for legacy query\n")); + res = mdns_add_question(reply, &service_type, DNS_RRTYPE_PTR, DNS_RRCLASS_IN, 0); + if (res != ERR_OK) { + return res; + } + reply->questions = 1; + } + /* ttl of legacy answer may not be greater then 10 seconds */ + ttl = MDNS_TTL_10; + } + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with service name PTR record\n")); + return mdns_add_answer(reply, &service_type, DNS_RRTYPE_PTR, DNS_RRCLASS_IN, + 0, ttl, NULL, 0, &service_instance); +} + +/** Write a SRV RR to outpacket */ +static err_t +mdns_add_srv_answer(struct mdns_outpacket *reply, struct mdns_outmsg *msg, + struct mdns_host *mdns, struct mdns_service *service) +{ + err_t res; + u32_t ttl = MDNS_TTL_120; + struct mdns_domain service_instance, srvhost; + u16_t srvdata[3]; + mdns_build_service_domain(&service_instance, service, 1); + mdns_build_host_domain(&srvhost, mdns); + if (msg->legacy_query) { + /* RFC 6762 section 18.14: + * In legacy unicast responses generated to answer legacy queries, + * name compression MUST NOT be performed on SRV records. + */ + srvhost.skip_compression = 1; + /* When answering to a legacy querier, we need to repeat the question and + * limit the ttl to the short legacy ttl. + * Repeating the question only needs to be done for the question asked + * (max one question), not for the additional records. */ + if(reply->questions < 1) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Add question for legacy query\n")); + res = mdns_add_question(reply, &service_instance, DNS_RRTYPE_SRV, DNS_RRCLASS_IN, 0); + if (res != ERR_OK) { + return res; + } + reply->questions = 1; + } + /* ttl of legacy answer may not be greater then 10 seconds */ + ttl = MDNS_TTL_10; + } + srvdata[0] = lwip_htons(SRV_PRIORITY); + srvdata[1] = lwip_htons(SRV_WEIGHT); + srvdata[2] = lwip_htons(service->port); + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with SRV record\n")); + return mdns_add_answer(reply, &service_instance, DNS_RRTYPE_SRV, DNS_RRCLASS_IN, + msg->cache_flush, ttl, + (const u8_t *) &srvdata, sizeof(srvdata), &srvhost); +} + +/** Write a TXT RR to outpacket */ +static err_t +mdns_add_txt_answer(struct mdns_outpacket *reply, struct mdns_outmsg *msg, + struct mdns_service *service) +{ + err_t res; + u32_t ttl = MDNS_TTL_120; + struct mdns_domain service_instance; + mdns_build_service_domain(&service_instance, service, 1); + mdns_prepare_txtdata(service); + /* When answering to a legacy querier, we need to repeat the question and + * limit the ttl to the short legacy ttl */ + if(msg->legacy_query) { + /* Repeating the question only needs to be done for the question asked + * (max one question), not for the additional records. */ + if(reply->questions < 1) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Add question for legacy query\n")); + res = mdns_add_question(reply, &service_instance, DNS_RRTYPE_TXT, DNS_RRCLASS_IN, 0); + if (res != ERR_OK) { + return res; + } + reply->questions = 1; + } + /* ttl of legacy answer may not be greater then 10 seconds */ + ttl = MDNS_TTL_10; + } + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with TXT record\n")); + return mdns_add_answer(reply, &service_instance, DNS_RRTYPE_TXT, DNS_RRCLASS_IN, + msg->cache_flush, ttl, (u8_t *) &service->txtdata.name, + service->txtdata.length, NULL); +} + + +static err_t +mdns_add_probe_questions_to_outpacket(struct mdns_outpacket *outpkt, struct mdns_outmsg *msg, + struct netif *netif) +{ + err_t res; + int i; + struct mdns_host *mdns = netif_mdns_data(netif); + + /* Write host questions (probing or legacy query) */ + if(msg->host_questions & QUESTION_PROBE_HOST_ANY) { + res = mdns_add_any_host_question(outpkt, mdns, 1); + if (res != ERR_OK) { + return res; + } + outpkt->questions++; + } + /* Write service questions (probing or legacy query) */ + for (i = 0; i < MDNS_MAX_SERVICES; i++) { + struct mdns_service* service = mdns->services[i]; + if (!service) { + continue; + } + if(msg->serv_questions[i] & QUESTION_PROBE_SERVICE_NAME_ANY) { + res = mdns_add_any_service_question(outpkt, service, 1); + if (res != ERR_OK) { + return res; + } + outpkt->questions++; + } + } + return ERR_OK; +} + +/** + * Send chosen answers as a reply + * + * Add all selected answers (first write will allocate pbuf) + * Add additional answers based on the selected answers + * Send the packet + */ +err_t +mdns_send_outpacket(struct mdns_outmsg *msg, struct netif *netif) +{ + struct mdns_service *service; + struct mdns_outpacket outpkt; + err_t res = ERR_ARG; + int i; + struct mdns_host *mdns = netif_mdns_data(netif); + u16_t answers = 0; + + memset(&outpkt, 0, sizeof(outpkt)); + + res = mdns_add_probe_questions_to_outpacket(&outpkt, msg, netif); + if (res != ERR_OK) { + goto cleanup; + } + + /* Write answers to host questions */ +#if LWIP_IPV4 + if (msg->host_replies & REPLY_HOST_A) { + res = mdns_add_a_answer(&outpkt, msg, netif); + if (res != ERR_OK) { + goto cleanup; + } + answers++; + } + if (msg->host_replies & REPLY_HOST_PTR_V4) { + res = mdns_add_hostv4_ptr_answer(&outpkt, msg, netif); + if (res != ERR_OK) { + goto cleanup; + } + answers++; + } +#endif +#if LWIP_IPV6 + if (msg->host_replies & REPLY_HOST_AAAA) { + int addrindex; + for (addrindex = 0; addrindex < LWIP_IPV6_NUM_ADDRESSES; addrindex++) { + if (ip6_addr_isvalid(netif_ip6_addr_state(netif, addrindex))) { + res = mdns_add_aaaa_answer(&outpkt, msg, netif, addrindex); + if (res != ERR_OK) { + goto cleanup; + } + answers++; + } + } + } + if (msg->host_replies & REPLY_HOST_PTR_V6) { + u8_t rev_addrs = msg->host_reverse_v6_replies; + int addrindex = 0; + while (rev_addrs) { + if (rev_addrs & 1) { + res = mdns_add_hostv6_ptr_answer(&outpkt, msg, netif, addrindex); + if (res != ERR_OK) { + goto cleanup; + } + answers++; + } + addrindex++; + rev_addrs >>= 1; + } + } +#endif + + /* Write answers to service questions */ + for (i = 0; i < MDNS_MAX_SERVICES; i++) { + service = mdns->services[i]; + if (!service) { + continue; + } + + if (msg->serv_replies[i] & REPLY_SERVICE_TYPE_PTR) { + res = mdns_add_servicetype_ptr_answer(&outpkt, msg, service); + if (res != ERR_OK) { + goto cleanup; + } + answers++; + } + + if (msg->serv_replies[i] & REPLY_SERVICE_NAME_PTR) { + res = mdns_add_servicename_ptr_answer(&outpkt, msg, service); + if (res != ERR_OK) { + goto cleanup; + } + answers++; + } + + if (msg->serv_replies[i] & REPLY_SERVICE_SRV) { + res = mdns_add_srv_answer(&outpkt, msg, mdns, service); + if (res != ERR_OK) { + goto cleanup; + } + answers++; + } + + if (msg->serv_replies[i] & REPLY_SERVICE_TXT) { + res = mdns_add_txt_answer(&outpkt, msg, service); + if (res != ERR_OK) { + goto cleanup; + } + answers++; + } + } + + /* if this is a response, the data above is anwers, else this is a probe and + * the answers above goes into auth section */ + if (msg->flags & DNS_FLAG1_RESPONSE) { + outpkt.answers += answers; + } else { + outpkt.authoritative += answers; + } + + /* All answers written, add additional RRs */ + for (i = 0; i < MDNS_MAX_SERVICES; i++) { + service = mdns->services[i]; + if (!service) { + continue; + } + + if (msg->serv_replies[i] & REPLY_SERVICE_NAME_PTR) { + /* Our service instance requested, include SRV & TXT + * if they are already not requested. */ + if (!(msg->serv_replies[i] & REPLY_SERVICE_SRV)) { + res = mdns_add_srv_answer(&outpkt, msg, mdns, service); + if (res != ERR_OK) { + goto cleanup; + } + outpkt.additional++; + } + + if (!(msg->serv_replies[i] & REPLY_SERVICE_TXT)) { + res = mdns_add_txt_answer(&outpkt, msg, service); + if (res != ERR_OK) { + goto cleanup; + } + outpkt.additional++; + } + } + + /* If service instance, SRV, record or an IP address is requested, + * supply all addresses for the host + */ + if ((msg->serv_replies[i] & (REPLY_SERVICE_NAME_PTR | REPLY_SERVICE_SRV)) || + (msg->host_replies & (REPLY_HOST_A | REPLY_HOST_AAAA))) { +#if LWIP_IPV6 + if (!(msg->host_replies & REPLY_HOST_AAAA)) { + int addrindex; + for (addrindex = 0; addrindex < LWIP_IPV6_NUM_ADDRESSES; addrindex++) { + if (ip6_addr_isvalid(netif_ip6_addr_state(netif, addrindex))) { + res = mdns_add_aaaa_answer(&outpkt, msg, netif, addrindex); + if (res != ERR_OK) { + goto cleanup; + } + outpkt.additional++; + } + } + } +#endif +#if LWIP_IPV4 + if (!(msg->host_replies & REPLY_HOST_A) && + !ip4_addr_isany_val(*netif_ip4_addr(netif))) { + res = mdns_add_a_answer(&outpkt, msg, netif); + if (res != ERR_OK) { + goto cleanup; + } + outpkt.additional++; + } +#endif + } + } + + if (outpkt.pbuf) { + struct dns_hdr hdr; + + /* Write header */ + memset(&hdr, 0, sizeof(hdr)); + hdr.flags1 = msg->flags; + hdr.numquestions = lwip_htons(outpkt.questions); + hdr.numanswers = lwip_htons(outpkt.answers); + hdr.numauthrr = lwip_htons(outpkt.authoritative); + hdr.numextrarr = lwip_htons(outpkt.additional); + hdr.id = lwip_htons(msg->tx_id); + pbuf_take(outpkt.pbuf, &hdr, sizeof(hdr)); + + /* Shrink packet */ + pbuf_realloc(outpkt.pbuf, outpkt.write_offset); + + /* Send created packet */ + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Sending packet, len=%d\n", + outpkt.write_offset)); + + res = udp_sendto_if(get_mdns_pcb(), outpkt.pbuf, &msg->dest_addr, msg->dest_port, netif); + } + +cleanup: + if (outpkt.pbuf) { + pbuf_free(outpkt.pbuf); + outpkt.pbuf = NULL; + } + return res; +} + +#if LWIP_IPV4 +/** + * Called by timeouts when timer is passed, allows multicast IPv4 traffic again. + * + * @param arg pointer to netif of timeout. + */ +void +mdns_multicast_timeout_reset_ipv4(void *arg) +{ + struct netif *netif = (struct netif*)arg; + struct mdns_host *mdns = netif_mdns_data(netif); + + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout finished - IPv4\n")); + + mdns->ipv4.multicast_timeout = 0; +} + +/** + * Called by timeouts when timer is passed, allows to send an answer on a QU + * question via multicast. + * + * @param arg pointer to netif of timeout. + */ +void +mdns_multicast_timeout_25ttl_reset_ipv4(void *arg) +{ + struct netif *netif = (struct netif*)arg; + struct mdns_host *mdns = netif_mdns_data(netif); + + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout 1/4 of ttl finished - IPv4\n")); + + mdns->ipv4.multicast_timeout_25TTL = 0; +} + +/** + * Called by timeouts when timer is passed, sends out delayed multicast IPv4 response. + * + * @param arg pointer to netif of timeout. + */ +void +mdns_send_multicast_msg_delayed_ipv4(void *arg) +{ + struct netif *netif = (struct netif*)arg; + struct mdns_host *mdns = netif_mdns_data(netif); + err_t res; + + res = mdns_send_outpacket(&mdns->ipv4.delayed_msg_multicast, netif); + if(res != ERR_OK) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Delayed multicast send failed - IPv4\n")); + } + else { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Delayed multicast send successful - IPv4\n")); + mdns_clear_outmsg(&mdns->ipv4.delayed_msg_multicast); + mdns->ipv4.multicast_msg_waiting = 0; + mdns_set_timeout(netif, MDNS_MULTICAST_TIMEOUT, mdns_multicast_timeout_reset_ipv4, + &mdns->ipv4.multicast_timeout); + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout started - IPv4\n")); + mdns_set_timeout(netif, MDNS_MULTICAST_TIMEOUT_25TTL, mdns_multicast_timeout_25ttl_reset_ipv4, + &mdns->ipv4.multicast_timeout_25TTL); + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout 1/4 of ttl started - IPv4\n")); + } +} + +/** + * Called by timeouts when timer is passed, sends out delayed unicast IPv4 response. + * + * @param arg pointer to netif of timeout. + */ +void +mdns_send_unicast_msg_delayed_ipv4(void *arg) +{ + struct netif *netif = (struct netif*)arg; + struct mdns_host *mdns = netif_mdns_data(netif); + err_t res; + + res = mdns_send_outpacket(&mdns->ipv4.delayed_msg_unicast, netif); + if(res != ERR_OK) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Delayed unicast send failed - IPv4\n")); + } + else { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Delayed unicast send successful - IPv4\n")); + mdns_clear_outmsg(&mdns->ipv4.delayed_msg_unicast); + mdns->ipv4.unicast_msg_in_use = 0; + } +} + +#endif + +#if LWIP_IPV6 +/** + * Called by timeouts when timer is passed, allows multicast IPv6 traffic again. + * + * @param arg pointer to netif of timeout. + */ +void +mdns_multicast_timeout_reset_ipv6(void *arg) +{ + struct netif *netif = (struct netif*)arg; + struct mdns_host *mdns = netif_mdns_data(netif); + + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout finished - IPv6\n")); + + mdns->ipv6.multicast_timeout = 0; +} + +/** + * Called by timeouts when timer is passed, allows to send an answer on a QU + * question via multicast. + * + * @param arg pointer to netif of timeout. + */ +void +mdns_multicast_timeout_25ttl_reset_ipv6(void *arg) +{ + struct netif *netif = (struct netif*)arg; + struct mdns_host *mdns = netif_mdns_data(netif); + + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout 1/4 of ttl finished - IPv6\n")); + + mdns->ipv6.multicast_timeout_25TTL = 0; +} + +/** + * Called by timeouts when timer is passed, sends out delayed multicast IPv6 response. + * + * @param arg pointer to netif of timeout. + */ +void +mdns_send_multicast_msg_delayed_ipv6(void *arg) +{ + struct netif *netif = (struct netif*)arg; + struct mdns_host *mdns = netif_mdns_data(netif); + err_t res; + + res = mdns_send_outpacket(&mdns->ipv6.delayed_msg_multicast, netif); + if(res != ERR_OK) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Delayed multicast send failed - IPv6\n")); + } + else { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Delayed multicast send successful - IPv6\n")); + mdns_clear_outmsg(&mdns->ipv6.delayed_msg_multicast); + mdns->ipv6.multicast_msg_waiting = 0; + mdns_set_timeout(netif, MDNS_MULTICAST_TIMEOUT, mdns_multicast_timeout_reset_ipv6, + &mdns->ipv6.multicast_timeout); + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout started - IPv6\n")); + mdns_set_timeout(netif, MDNS_MULTICAST_TIMEOUT_25TTL, mdns_multicast_timeout_25ttl_reset_ipv6, + &mdns->ipv6.multicast_timeout_25TTL); + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout 1/4 of ttl started - IPv6\n")); + } +} + +/** + * Called by timeouts when timer is passed, sends out delayed unicast IPv6 response. + * + * @param arg pointer to netif of timeout. + */ +void +mdns_send_unicast_msg_delayed_ipv6(void *arg) +{ + struct netif *netif = (struct netif*)arg; + struct mdns_host *mdns = netif_mdns_data(netif); + err_t res; + + res = mdns_send_outpacket(&mdns->ipv6.delayed_msg_unicast, netif); + if(res != ERR_OK) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Delayed unicast send failed - IPv6\n")); + } + else { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Delayed unicast send successful - IPv6\n")); + mdns_clear_outmsg(&mdns->ipv6.delayed_msg_unicast); + mdns->ipv6.unicast_msg_in_use = 0; + } +} + +#endif + +/** + * This function clears the output message without changing the destination + * address or port. This is useful for clearing the delayed msg structs without + * loosing the set IP. + * + * @param outmsg pointer to output message to clear. + */ +static void +mdns_clear_outmsg(struct mdns_outmsg *outmsg) +{ + int i; + + outmsg->tx_id = 0; + outmsg->flags = 0; + outmsg->cache_flush = 0; + outmsg->unicast_reply_requested = 0; + outmsg->legacy_query = 0; + outmsg->probe_query_recv = 0; + outmsg->host_questions = 0; + outmsg->host_replies = 0; + outmsg->host_reverse_v6_replies = 0; + + for(i = 0; i < MDNS_MAX_SERVICES; i++) { + outmsg->serv_questions[i] = 0; + outmsg->serv_replies[i] = 0; + } +} + +/** + * Sets a timer that calls the handler when finished. + * Depending on the busy_flag the timer is restarted or started. The flag is + * set before return. Sys_timeout does not give us this functionality. + * + * @param netif Network interface info + * @param msecs Time value to set + * @param handler Callback function to call + * @param busy_flag Pointer to flag that displays if the timer is running or not. + */ +void +mdns_set_timeout(struct netif *netif, u32_t msecs, sys_timeout_handler handler, + u8_t *busy_flag) +{ + if(*busy_flag) { + /* restart timer */ + sys_untimeout(handler, netif); + sys_timeout(msecs, handler, netif); + } + else { + /* start timer */ + sys_timeout(msecs, handler, netif); + } + /* Now we have a timer running */ + *busy_flag = 1; +} + +#endif /* LWIP_MDNS_RESPONDER */ diff --git a/src/include/lwip/apps/mdns.h b/src/include/lwip/apps/mdns.h index 20d7ee2a2..077bd45d6 100644 --- a/src/include/lwip/apps/mdns.h +++ b/src/include/lwip/apps/mdns.h @@ -32,6 +32,7 @@ * This file is part of the lwIP TCP/IP stack. * * Author: Erik Ekman + * Author: Jasper Verschueren * */ @@ -73,11 +74,11 @@ void mdns_resp_init(void); void mdns_resp_register_name_result_cb(mdns_name_result_cb_t cb); -err_t mdns_resp_add_netif(struct netif *netif, const char *hostname, u32_t dns_ttl); +err_t mdns_resp_add_netif(struct netif *netif, const char *hostname); err_t mdns_resp_remove_netif(struct netif *netif); err_t mdns_resp_rename_netif(struct netif *netif, const char *hostname); -s8_t mdns_resp_add_service(struct netif *netif, const char *name, const char *service, enum mdns_sd_proto proto, u16_t port, u32_t dns_ttl, service_get_txt_fn_t txt_fn, void *txt_userdata); +s8_t mdns_resp_add_service(struct netif *netif, const char *name, const char *service, enum mdns_sd_proto proto, u16_t port, service_get_txt_fn_t txt_fn, void *txt_userdata); err_t mdns_resp_del_service(struct netif *netif, s8_t slot); err_t mdns_resp_rename_service(struct netif *netif, s8_t slot, const char *name); @@ -96,6 +97,9 @@ void mdns_resp_announce(struct netif *netif); */ #define mdns_resp_netif_settings_changed(netif) mdns_resp_announce(netif) +struct mdns_host* netif_mdns_data(struct netif *netif); +struct udp_pcb* get_mdns_pcb(void); + #endif /* LWIP_MDNS_RESPONDER */ #ifdef __cplusplus diff --git a/src/include/lwip/apps/mdns_domain.h b/src/include/lwip/apps/mdns_domain.h new file mode 100644 index 000000000..941ce63c5 --- /dev/null +++ b/src/include/lwip/apps/mdns_domain.h @@ -0,0 +1,75 @@ +/** + * @file + * MDNS responder - domain related functionalities + */ + + /* + * Copyright (c) 2015 Verisure Innovation AB + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * This file is part of the lwIP TCP/IP stack. + * + * Author: Erik Ekman + * Author: Jasper Verschueren + * + */ + +#ifndef LWIP_HDR_APPS_MDNS_DOMAIN_H +#define LWIP_HDR_APPS_MDNS_DOMAIN_H + +#include "lwip/apps/mdns_opts.h" +#include "lwip/apps/mdns_priv.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if LWIP_MDNS_RESPONDER + +/* Domain methods - also visible for unit tests */ + +err_t mdns_domain_add_label(struct mdns_domain *domain, const char *label, u8_t len); +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); +#if LWIP_IPV4 +err_t mdns_build_reverse_v4_domain(struct mdns_domain *domain, const ip4_addr_t *addr); +#endif +#if LWIP_IPV6 +err_t mdns_build_reverse_v6_domain(struct mdns_domain *domain, const ip6_addr_t *addr); +#endif +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); +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); + +#endif /* LWIP_MDNS_RESPONDER */ + +#ifdef __cplusplus +} +#endif + +#endif /* LWIP_HDR_APPS_MDNS_DOMAIN_H */ diff --git a/src/include/lwip/apps/mdns_opts.h b/src/include/lwip/apps/mdns_opts.h index ac2322007..d805e8c7a 100644 --- a/src/include/lwip/apps/mdns_opts.h +++ b/src/include/lwip/apps/mdns_opts.h @@ -32,6 +32,7 @@ * This file is part of the lwIP TCP/IP stack. * * Author: Erik Ekman + * Author: Jasper Verschueren * */ diff --git a/src/include/lwip/apps/mdns_out.h b/src/include/lwip/apps/mdns_out.h new file mode 100644 index 000000000..f36eeeda8 --- /dev/null +++ b/src/include/lwip/apps/mdns_out.h @@ -0,0 +1,121 @@ +/** + * @file + * MDNS responder - output related functionalities + */ + + /* + * Copyright (c) 2015 Verisure Innovation AB + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * This file is part of the lwIP TCP/IP stack. + * + * Author: Erik Ekman + * Author: Jasper Verschueren + * + */ + +#ifndef LWIP_HDR_APPS_MDNS_OUT_H +#define LWIP_HDR_APPS_MDNS_OUT_H + +#include "lwip/apps/mdns_opts.h" +#include "lwip/apps/mdns_priv.h" +#include "lwip/netif.h" +#include "lwip/timeouts.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if LWIP_MDNS_RESPONDER + +/** Bitmasks outmsg generation */ +/* Probe for ALL types with hostname */ +#define QUESTION_PROBE_HOST_ANY 0x10 +/* Probe for ALL types with service instance name */ +#define QUESTION_PROBE_SERVICE_NAME_ANY 0x10 + +/* Lookup from hostname -> IPv4 */ +#define REPLY_HOST_A 0x01 +/* Lookup from IPv4/v6 -> hostname */ +#define REPLY_HOST_PTR_V4 0x02 +/* Lookup from hostname -> IPv6 */ +#define REPLY_HOST_AAAA 0x04 +/* Lookup from hostname -> IPv6 */ +#define REPLY_HOST_PTR_V6 0x08 + +/* Lookup for service types */ +#define REPLY_SERVICE_TYPE_PTR 0x10 +/* Lookup for instances of service */ +#define REPLY_SERVICE_NAME_PTR 0x20 +/* Lookup for location of service instance */ +#define REPLY_SERVICE_SRV 0x40 +/* Lookup for text info on service instance */ +#define REPLY_SERVICE_TXT 0x80 + +/* RFC6762 section 6: + * To protect the network against excessive packet flooding due to software bugs + * or malicious attack, a Multicast DNS responder MUST NOT (except in the one + * special case of answering probe queries) multicast a record on a given + * interface until at least one second has elapsed since the last time that + * record was multicast on that particular interface. + */ +#define MDNS_MULTICAST_TIMEOUT 1000 + +/* RFC6762 section 5.4: + * When receiving a question with the unicast-response bit set, a responder + * SHOULD usually respond with a unicast packet directed back to the querier. + * However, if the responder has not multicast that record recently (within one + * quarter of its TTL), then the responder SHOULD instead multicast the response + * so as to keep all the peer caches up to date, and to permit passive conflict + * detection. + * -> we implement a stripped down version. Depending on a timeout of 30s + * (25% of 120s) all QU questions are send via multicast or unicast. + */ +#define MDNS_MULTICAST_TIMEOUT_25TTL 30000 + +err_t mdns_send_outpacket(struct mdns_outmsg *msg, struct netif *netif); +void mdns_set_timeout(struct netif *netif, u32_t msecs, + sys_timeout_handler handler, u8_t *busy_flag); +#if LWIP_IPV4 +void mdns_multicast_timeout_reset_ipv4(void *arg); +void mdns_multicast_timeout_25ttl_reset_ipv4(void *arg); +void mdns_send_multicast_msg_delayed_ipv4(void *arg); +void mdns_send_unicast_msg_delayed_ipv4(void *arg); +#endif +#if LWIP_IPV6 +void mdns_multicast_timeout_reset_ipv6(void *arg); +void mdns_multicast_timeout_25ttl_reset_ipv6(void *arg); +void mdns_send_multicast_msg_delayed_ipv6(void *arg); +void mdns_send_unicast_msg_delayed_ipv6(void *arg); +#endif +void mdns_prepare_txtdata(struct mdns_service *service); + +#endif /* LWIP_MDNS_RESPONDER */ + +#ifdef __cplusplus +} +#endif + +#endif /* LWIP_HDR_APPS_MDNS_OUT_H */ diff --git a/src/include/lwip/apps/mdns_priv.h b/src/include/lwip/apps/mdns_priv.h index 9635b5b10..23f015dd1 100644 --- a/src/include/lwip/apps/mdns_priv.h +++ b/src/include/lwip/apps/mdns_priv.h @@ -32,11 +32,13 @@ * This file is part of the lwIP TCP/IP stack. * * Author: Erik Ekman + * Author: Jasper Verschueren * */ #ifndef LWIP_HDR_MDNS_PRIV_H #define LWIP_HDR_MDNS_PRIV_H +#include "lwip/apps/mdns.h" #include "lwip/apps/mdns_opts.h" #include "lwip/pbuf.h" @@ -46,10 +48,23 @@ extern "C" { #if LWIP_MDNS_RESPONDER -/* Domain struct and methods - visible for unit tests */ - #define MDNS_DOMAIN_MAXLEN 256 #define MDNS_READNAME_ERROR 0xFFFF +#define NUM_DOMAIN_OFFSETS 10 + +#define SRV_PRIORITY 0 +#define SRV_WEIGHT 0 + +/* mDNS TTL: (RFC6762 section 10) + * - 120 seconds if the hostname appears somewhere in the RR + * - 75 minutes if not (4500 seconds) + * - 10 seconds if responding to a legacy query + */ +#define MDNS_TTL_10 10 +#define MDNS_TTL_120 120 +#define MDNS_TTL_4500 4500 + +/* Domain structs - also visible for unit tests */ struct mdns_domain { /* Encoded domain name */ @@ -60,10 +75,110 @@ struct mdns_domain { u8_t skip_compression; }; -err_t mdns_domain_add_label(struct mdns_domain *domain, const char *label, u8_t len); -u16_t mdns_readname(struct pbuf *p, u16_t offset, struct mdns_domain *domain); -int mdns_domain_eq(struct mdns_domain *a, struct mdns_domain *b); -u16_t mdns_compress_domain(struct pbuf *pbuf, u16_t *offset, struct mdns_domain *domain); +/** Description of a service */ +struct mdns_service { + /** TXT record to answer with */ + struct mdns_domain txtdata; + /** Name of service, like 'myweb' */ + char name[MDNS_LABEL_MAXLEN + 1]; + /** Type of service, like '_http' */ + char service[MDNS_LABEL_MAXLEN + 1]; + /** Callback function and userdata + * to update txtdata buffer */ + service_get_txt_fn_t txt_fn; + void *txt_userdata; + /** Protocol, TCP or UDP */ + u16_t proto; + /** Port of the service */ + u16_t port; +}; + +/** mDNS output packet */ +struct mdns_outpacket { + /** Packet data */ + struct pbuf *pbuf; + /** Current write offset in packet */ + u16_t write_offset; + /** Number of questions written */ + u16_t questions; + /** Number of normal answers written */ + u16_t answers; + /** Number of authoritative answers written */ + u16_t authoritative; + /** Number of additional answers written */ + u16_t additional; + /** Offsets for written domain names in packet. + * Used for compression */ + u16_t domain_offsets[NUM_DOMAIN_OFFSETS]; +}; + +/** mDNS output message */ +struct mdns_outmsg { + /** Identifier. Used in legacy queries */ + u16_t tx_id; + /** dns flags */ + u8_t flags; + /** Destination IP/port if sent unicast */ + ip_addr_t dest_addr; + u16_t dest_port; + /** If all answers in packet should set cache_flush bit */ + u8_t cache_flush; + /** If reply should be sent unicast (as requested) */ + u8_t unicast_reply_requested; + /** If legacy query. (tx_id needed, and write + * question again in reply before answer) */ + u8_t legacy_query; + /** If the query is a probe msg we need to respond immediatly. Independent of + * the QU or QM flag. */ + u8_t probe_query_recv; + /* Question bitmask for host information */ + u8_t host_questions; + /* Questions bitmask per service */ + u8_t serv_questions[MDNS_MAX_SERVICES]; + /* Reply bitmask for host information */ + u8_t host_replies; + /* Bitmask for which reverse IPv6 hosts to answer */ + u8_t host_reverse_v6_replies; + /* Reply bitmask per service */ + u8_t serv_replies[MDNS_MAX_SERVICES]; +}; + +/** Delayed msg info */ +struct mdns_delayed_msg { + /** Timer state multicast */ + u8_t multicast_msg_waiting; + /** Multicast timeout on */ + u8_t multicast_timeout; + /** Output msg used for delayed multicast responses */ + struct mdns_outmsg delayed_msg_multicast; + /** Prefer multicast over unicast timeout -> 25% of TTL = we take 30s as + general delay. */ + u8_t multicast_timeout_25TTL; + /** Only send out new unicast message if previous was send */ + u8_t unicast_msg_in_use; + /** Output msg used for delayed unicast responses */ + struct mdns_outmsg delayed_msg_unicast; +}; + +/** Description of a host/netif */ +struct mdns_host { + /** Hostname */ + char name[MDNS_LABEL_MAXLEN + 1]; + /** Pointer to services */ + struct mdns_service *services[MDNS_MAX_SERVICES]; + /** Number of probes sent for the current name */ + u8_t probes_sent; + /** State in probing sequence */ + u8_t probing_state; +#if LWIP_IPV4 + /** delayed msg struct for IPv4 */ + struct mdns_delayed_msg ipv4; +#endif +#if LWIP_IPV6 + /** delayed msg struct for IPv6 */ + struct mdns_delayed_msg ipv6; +#endif +}; #endif /* LWIP_MDNS_RESPONDER */ diff --git a/test/unit/mdns/test_mdns.c b/test/unit/mdns/test_mdns.c index 638516370..5a630f478 100644 --- a/test/unit/mdns/test_mdns.c +++ b/test/unit/mdns/test_mdns.c @@ -34,6 +34,7 @@ #include "lwip/pbuf.h" #include "lwip/apps/mdns.h" +#include "lwip/apps/mdns_domain.h" #include "lwip/apps/mdns_priv.h" START_TEST(readname_basic) -- 2.19.1