/** * @file * MDNS responder implementation * * @defgroup mdns MDNS * @ingroup apps * * RFC 6762 - Multicast DNS\n * RFC 6763 - DNS-Based Service Discovery\n * * @verbinclude mdns.txt * * Things left to implement: * ------------------------- * * - Tiebreaking for simultaneous probing * - 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 * - Fragmenting replies if required * - Handling multi-packet known answers * - Individual known answer detection for all local IPv6 addresses * - Dynamic size of outgoing packet */ /* * 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 * */ #include "lwip/apps/mdns.h" #include "lwip/apps/mdns_priv.h" #include "lwip/netif.h" #include "lwip/udp.h" #include "lwip/ip_addr.h" #include "lwip/mem.h" #include "lwip/prot/dns.h" #include "lwip/prot/iana.h" #include "lwip/timeouts.h" #include #if LWIP_MDNS_RESPONDER #if (LWIP_IPV4 && !LWIP_IGMP) #error "If you want to use MDNS with IPv4, you have to define LWIP_IGMP=1 in your lwipopts.h" #endif #if (LWIP_IPV6 && !LWIP_IPV6_MLD) #error "If you want to use MDNS with IPv6, you have to define LWIP_IPV6_MLD=1 in your lwipopts.h" #endif #if (!LWIP_UDP) #error "If you want to use MDNS, you have to define LWIP_UDP=1 in your lwipopts.h" #endif #if LWIP_IPV4 #include "lwip/igmp.h" /* IPv4 multicast group 224.0.0.251 */ static const ip_addr_t v4group = DNS_MQUERY_IPV4_GROUP_INIT; #endif #if LWIP_IPV6 #include "lwip/mld6.h" /* IPv6 multicast group FF02::FB */ static const ip_addr_t v6group = DNS_MQUERY_IPV6_GROUP_INIT; #endif #define MDNS_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; #if MDNS_RESP_USENETIF_EXTCALLBACK NETIF_DECLARE_EXT_CALLBACK(netif_callback) #endif 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 #define MDNS_PROBE_DELAY_MS 250 #define MDNS_PROBE_COUNT 3 #ifdef LWIP_RAND /* first probe timeout SHOULD be random 0-250 ms*/ #define MDNS_INITIAL_PROBE_DELAY_MS (LWIP_RAND() % MDNS_PROBE_DELAY_MS) #else #define MDNS_INITIAL_PROBE_DELAY_MS MDNS_PROBE_DELAY_MS #endif #define MDNS_PROBING_NOT_STARTED 0 #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 */ ip_addr_t source_addr; 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 */ u16_t parse_offset; /** Identifier. Used in legacy queries */ u16_t tx_id; /** Number of questions in packet, * read from packet header */ 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 */ 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 */ 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]; /** 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]; }; /** Domain, type and class. * Shared between questions and answers */ struct mdns_rr_info { struct mdns_domain domain; u16_t type; u16_t klass; }; struct mdns_question { struct mdns_rr_info info; /** unicast reply requested */ u16_t unicast; }; struct mdns_answer { struct mdns_rr_info info; /** cache flush command bit */ u16_t cache_flush; /* Validity time in seconds */ u32_t ttl; /** Length of variable answer */ u16_t rd_length; /** Offset of start of variable answer in packet */ u16_t rd_offset; }; 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 */ 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); } /** * 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 */ static 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. */ 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); } /** * Check which replies we should send for a host/netif based on question * @param netif The network interface that received the question * @param rr Domain/type/class from a question * @param reverse_v6_reply Bitmask of which IPv6 addresses to send reverse PTRs for * if reply bit has REPLY_HOST_PTR_V6 set * @return Bitmask of which replies to send */ static int check_host(struct netif *netif, struct mdns_rr_info *rr, u8_t *reverse_v6_reply) { err_t res; int replies = 0; struct mdns_domain mydomain; LWIP_UNUSED_ARG(reverse_v6_reply); /* if ipv6 is disabled */ if (rr->klass != DNS_RRCLASS_IN && rr->klass != DNS_RRCLASS_ANY) { /* Invalid class */ return replies; } /* Handle PTR for our addresses */ if (rr->type == DNS_RRTYPE_PTR || rr->type == DNS_RRTYPE_ANY) { #if LWIP_IPV6 int i; for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) { if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i))) { res = mdns_build_reverse_v6_domain(&mydomain, netif_ip6_addr(netif, i)); if (res == ERR_OK && mdns_domain_eq(&rr->domain, &mydomain)) { replies |= REPLY_HOST_PTR_V6; /* Mark which addresses where requested */ if (reverse_v6_reply) { *reverse_v6_reply |= (1 << i); } } } } #endif #if LWIP_IPV4 if (!ip4_addr_isany_val(*netif_ip4_addr(netif))) { res = mdns_build_reverse_v4_domain(&mydomain, netif_ip4_addr(netif)); if (res == ERR_OK && mdns_domain_eq(&rr->domain, &mydomain)) { replies |= REPLY_HOST_PTR_V4; } } #endif } res = mdns_build_host_domain(&mydomain, NETIF_TO_HOST(netif)); /* Handle requests for our hostname */ if (res == ERR_OK && mdns_domain_eq(&rr->domain, &mydomain)) { /* TODO return NSEC if unsupported protocol requested */ #if LWIP_IPV4 if (!ip4_addr_isany_val(*netif_ip4_addr(netif)) && (rr->type == DNS_RRTYPE_A || rr->type == DNS_RRTYPE_ANY)) { replies |= REPLY_HOST_A; } #endif #if LWIP_IPV6 if (rr->type == DNS_RRTYPE_AAAA || rr->type == DNS_RRTYPE_ANY) { replies |= REPLY_HOST_AAAA; } #endif } return replies; } /** * Check which replies we should send for a service based on question * @param service A registered MDNS service * @param rr Domain/type/class from a question * @return Bitmask of which replies to send */ static int check_service(struct mdns_service *service, struct mdns_rr_info *rr) { err_t res; int replies = 0; struct mdns_domain mydomain; if (rr->klass != DNS_RRCLASS_IN && rr->klass != DNS_RRCLASS_ANY) { /* Invalid class */ return 0; } res = mdns_build_dnssd_domain(&mydomain); if (res == ERR_OK && mdns_domain_eq(&rr->domain, &mydomain) && (rr->type == DNS_RRTYPE_PTR || rr->type == DNS_RRTYPE_ANY)) { /* Request for all service types */ replies |= REPLY_SERVICE_TYPE_PTR; } res = mdns_build_service_domain(&mydomain, service, 0); if (res == ERR_OK && mdns_domain_eq(&rr->domain, &mydomain) && (rr->type == DNS_RRTYPE_PTR || rr->type == DNS_RRTYPE_ANY)) { /* Request for the instance of my service */ replies |= REPLY_SERVICE_NAME_PTR; } res = mdns_build_service_domain(&mydomain, service, 1); if (res == ERR_OK && mdns_domain_eq(&rr->domain, &mydomain)) { /* Request for info about my service */ if (rr->type == DNS_RRTYPE_SRV || rr->type == DNS_RRTYPE_ANY) { replies |= REPLY_SERVICE_SRV; } if (rr->type == DNS_RRTYPE_TXT || rr->type == DNS_RRTYPE_ANY) { replies |= REPLY_SERVICE_TXT; } } return replies; } /** * 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 * @param pkt The MDNS packet to read from. The parse_offset field will be * incremented to point to the next unparsed byte. * @param info The struct to fill with domain, type and class * @return ERR_OK on success, an err_t otherwise */ static err_t mdns_read_rr_info(struct mdns_packet *pkt, struct mdns_rr_info *info) { u16_t field16, copied; pkt->parse_offset = mdns_readname(pkt->pbuf, pkt->parse_offset, &info->domain); if (pkt->parse_offset == MDNS_READNAME_ERROR) { return ERR_VAL; } copied = pbuf_copy_partial(pkt->pbuf, &field16, sizeof(field16), pkt->parse_offset); if (copied != sizeof(field16)) { return ERR_VAL; } pkt->parse_offset += copied; info->type = lwip_ntohs(field16); copied = pbuf_copy_partial(pkt->pbuf, &field16, sizeof(field16), pkt->parse_offset); if (copied != sizeof(field16)) { return ERR_VAL; } pkt->parse_offset += copied; info->klass = lwip_ntohs(field16); return ERR_OK; } /** * Read a question from the packet. * All questions have to be read before the answers. * @param pkt The MDNS packet to read from. The questions_left field will be decremented * and the parse_offset will be updated. * @param question The struct to fill with question data * @return ERR_OK on success, an err_t otherwise */ static err_t mdns_read_question(struct mdns_packet *pkt, struct mdns_question *question) { /* Safety check */ if (pkt->pbuf->tot_len < pkt->parse_offset) { return ERR_VAL; } if (pkt->questions_left) { err_t res; pkt->questions_left--; memset(question, 0, sizeof(struct mdns_question)); res = mdns_read_rr_info(pkt, &question->info); if (res != ERR_OK) { return res; } /* Extract unicast flag from class field */ question->unicast = question->info.klass & 0x8000; question->info.klass &= 0x7FFF; return ERR_OK; } return ERR_VAL; } /** * 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 * the parse_offset will be updated. * @param answer The struct to fill with answer data * @return ERR_OK on success, an err_t otherwise */ static err_t mdns_read_answer(struct mdns_packet *pkt, struct mdns_answer *answer) { /* Read questions first */ if (pkt->questions_left) { return ERR_VAL; } /* Safety check */ if (pkt->pbuf->tot_len < pkt->parse_offset) { return ERR_VAL; } if (pkt->answers_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; /* 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)); /* Shrink packet */ pbuf_realloc(outpkt->pbuf, outpkt->write_offset); if (IP_IS_V6_VAL(outpkt->dest_addr)) { #if LWIP_IPV6 mcast_destaddr = &v6group; #endif } else { #if LWIP_IPV4 mcast_destaddr = &v4group; #endif } /* 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); } } cleanup: if (outpkt->pbuf) { pbuf_free(outpkt->pbuf); outpkt->pbuf = NULL; } return res; } /** * Send unsolicited answer containing all our known data * @param netif The network interface to send on * @param destination The target address to send to (usually multicast address) */ static void mdns_announce(struct netif *netif, const ip_addr_t *destination) { struct mdns_outpacket 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))) { announce.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))) { announce.host_replies |= REPLY_HOST_AAAA | REPLY_HOST_PTR_V6; announce.host_reverse_v6_replies |= (1 << i); } } #endif for (i = 0; i < MDNS_MAX_SERVICES; i++) { struct mdns_service *serv = mdns->services[i]; if (serv) { announce.serv_replies[i] = REPLY_SERVICE_TYPE_PTR | REPLY_SERVICE_NAME_PTR | REPLY_SERVICE_SRV | REPLY_SERVICE_TXT; } } 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); } /** * 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 */ static void mdns_handle_question(struct mdns_packet *pkt) { 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; res = mdns_read_question(pkt, &q); if (res != ERR_OK) { LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to parse question, skipping query packet\n")); return; } LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Query for domain ")); mdns_domain_debug_print(&q.info.domain); 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.host_replies |= check_host(pkt->netif, &q.info, &reply.host_reverse_v6_replies); replies |= reply.host_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; } } } /* Handle known answers */ while (pkt->answers_left) { struct mdns_answer ans; u8_t rev_v6; int match; res = mdns_read_answer(pkt, &ans); if (res != ERR_OK) { LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to parse answer, skipping query packet\n")); goto cleanup; } LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Known 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(pkt->netif, &ans.info, &rev_v6); if (match && (ans.ttl > (mdns->dns_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. */ if (ans.info.type == DNS_RRTYPE_PTR) { /* Read domain and compare */ struct mdns_domain known_ans, my_ans; u16_t len; len = mdns_readname(pkt->pbuf, ans.rd_offset, &known_ans); res = mdns_build_host_domain(&my_ans, mdns); if (len != MDNS_READNAME_ERROR && res == ERR_OK && mdns_domain_eq(&known_ans, &my_ans)) { #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; } #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; } } #endif } } 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) { LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: A\n")); 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) { LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: AAAA\n")); reply.host_replies &= ~REPLY_HOST_AAAA; } #endif } } 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 && (ans.ttl > (service->dns_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. */ if (ans.info.type == DNS_RRTYPE_PTR) { /* Read domain and compare */ struct mdns_domain known_ans, my_ans; u16_t len; len = mdns_readname(pkt->pbuf, ans.rd_offset, &known_ans); if (len != MDNS_READNAME_ERROR) { if (match & REPLY_SERVICE_TYPE_PTR) { 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; } } 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; } } } } else if (match & REPLY_SERVICE_SRV) { /* Read and compare to my SRV record */ u16_t field16, len, read_pos; struct mdns_domain known_ans, my_ans; read_pos = ans.rd_offset; do { /* Check priority field */ len = pbuf_copy_partial(pkt->pbuf, &field16, sizeof(field16), read_pos); if (len != sizeof(field16) || lwip_ntohs(field16) != SRV_PRIORITY) { break; } read_pos += len; /* Check weight field */ len = pbuf_copy_partial(pkt->pbuf, &field16, sizeof(field16), read_pos); if (len != sizeof(field16) || lwip_ntohs(field16) != SRV_WEIGHT) { break; } read_pos += len; /* Check port field */ len = pbuf_copy_partial(pkt->pbuf, &field16, sizeof(field16), read_pos); if (len != sizeof(field16) || lwip_ntohs(field16) != service->port) { break; } read_pos += len; /* Check host field */ len = mdns_readname(pkt->pbuf, read_pos, &known_ans); mdns_build_host_domain(&my_ans, mdns); if (len == MDNS_READNAME_ERROR || !mdns_domain_eq(&known_ans, &my_ans)) { break; } LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: SRV\n")); 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; } } } } } mdns_send_outpacket(&reply, DNS_FLAG1_RESPONSE | DNS_FLAG1_AUTHORATIVE); 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; } } /** * Handle response MDNS packet * Only prints debug for now. Will need more code to do conflict resolution. */ static void mdns_handle_response(struct mdns_packet *pkt) { struct mdns_host* mdns = NETIF_TO_HOST(pkt->netif); /* Ignore all questions */ while (pkt->questions_left) { struct mdns_question q; err_t res; res = mdns_read_question(pkt, &q); if (res != ERR_OK) { LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to parse question, skipping response packet\n")); return; } } while (pkt->answers_left) { struct mdns_answer ans; err_t res; res = mdns_read_answer(pkt, &ans); if (res != ERR_OK) { LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to parse answer, skipping response packet\n")); return; } LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: 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)); /*"Apparently conflicting Multicast DNS responses received *before* the first probe packet is sent MUST be silently ignored" so drop answer if we haven't started probing yet*/ if ((mdns->probing_state == MDNS_PROBING_ONGOING) && (mdns->probes_sent > 0)) { struct mdns_domain domain; u8_t i; u8_t conflict = 0; res = mdns_build_host_domain(&domain, mdns); if (res == ERR_OK && mdns_domain_eq(&ans.info.domain, &domain)) { LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Probe response matches host domain!")); conflict = 1; } for (i = 0; i < MDNS_MAX_SERVICES; i++) { struct mdns_service* service = mdns->services[i]; if (!service) { continue; } res = mdns_build_service_domain(&domain, service, 1); if ((res == ERR_OK) && mdns_domain_eq(&ans.info.domain, &domain)) { LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Probe response matches service domain!")); conflict = 1; } } if (conflict != 0) { sys_untimeout(mdns_probe, pkt->netif); if (mdns_name_result_cb != NULL) { mdns_name_result_cb(pkt->netif, MDNS_PROBING_CONFLICT); } } } } } /** * Receive input function for MDNS packets. * Handles both IPv4 and IPv6 UDP pcbs. */ static void mdns_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, u16_t port) { struct dns_hdr hdr; struct mdns_packet packet; struct netif *recv_netif = ip_current_input_netif(); u16_t offset = 0; LWIP_UNUSED_ARG(arg); LWIP_UNUSED_ARG(pcb); LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Received IPv%d MDNS packet, len %d\n", IP_IS_V6(addr) ? 6 : 4, p->tot_len)); if (NETIF_TO_HOST(recv_netif) == NULL) { /* From netif not configured for MDNS */ goto dealloc; } if (pbuf_copy_partial(p, &hdr, SIZEOF_DNS_HDR, offset) < SIZEOF_DNS_HDR) { /* Too small */ goto dealloc; } offset += SIZEOF_DNS_HDR; if (DNS_HDR_GET_OPCODE(&hdr)) { /* Ignore non-standard queries in multicast packets (RFC 6762, section 18.3) */ goto dealloc; } 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); #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; } } #endif #if LWIP_IPV4 if (!IP_IS_V6(ip_current_dest_addr())) { if (!ip_addr_cmp(ip_current_dest_addr(), &v4group)) { packet.recv_unicast = 1; } } #endif if (hdr.flags1 & DNS_FLAG1_RESPONSE) { mdns_handle_response(&packet); } else { mdns_handle_question(&packet); } dealloc: pbuf_free(p); } #if LWIP_NETIF_EXT_STATUS_CALLBACK && MDNS_RESP_USENETIF_EXTCALLBACK static void mdns_netif_ext_status_callback(struct netif *netif, netif_nsc_reason_t reason, const netif_ext_callback_args_t *args) { LWIP_UNUSED_ARG(args); /* MDNS enabled on netif? */ if (NETIF_TO_HOST(netif) == NULL) { return; } if (reason & LWIP_NSC_STATUS_CHANGED) { if (args->status_changed.state != 0) { mdns_resp_restart(netif); } /* TODO: send goodbye message */ } if (reason & LWIP_NSC_LINK_CHANGED) { if (args->link_changed.state != 0) { mdns_resp_restart(netif); } } if (reason & (LWIP_NSC_IPV4_ADDRESS_CHANGED | LWIP_NSC_IPV4_GATEWAY_CHANGED | LWIP_NSC_IPV4_NETMASK_CHANGED | LWIP_NSC_IPV4_SETTINGS_CHANGED | LWIP_NSC_IPV6_SET | LWIP_NSC_IPV6_ADDR_STATE_CHANGED)) { mdns_resp_announce(netif); } } #endif /* LWIP_NETIF_EXT_STATUS_CALLBACK && MDNS_RESP_USENETIF_EXTCALLBACK */ 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; u8_t i; err_t res; mdns = NETIF_TO_HOST(netif); memset(&pkt, 0, sizeof(pkt)); pkt.netif = netif; /* 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++; 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++; } /* 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; } #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; } } #endif 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; } } 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); cleanup: if (pkt.pbuf) { pbuf_free(pkt.pbuf); pkt.pbuf = NULL; } return res; } /** * Timer callback for probing network. */ static void mdns_probe(void* arg) { struct netif *netif = (struct netif *)arg; struct mdns_host* mdns = NETIF_TO_HOST(netif); if(mdns->probes_sent >= MDNS_PROBE_COUNT) { /* probing successful, announce the new name */ mdns->probing_state = MDNS_PROBING_COMPLETE; mdns_resp_announce(netif); if (mdns_name_result_cb != NULL) { mdns_name_result_cb(netif, MDNS_PROBING_SUCCESSFUL); } } else { #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) #endif { #if LWIP_IPV6 if (mdns_send_probe(netif, IP6_ADDR_ANY) == ERR_OK) #endif { mdns->probes_sent++; } } sys_timeout(MDNS_PROBE_DELAY_MS, mdns_probe, netif); } } /** * @ingroup mdns * Activate MDNS responder for a network interface. * @param netif The network interface to activate. * @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) { err_t res; struct mdns_host *mdns; LWIP_ASSERT_CORE_LOCKED(); LWIP_ERROR("mdns_resp_add_netif: netif != NULL", (netif != NULL), return ERR_VAL); LWIP_ERROR("mdns_resp_add_netif: Hostname too long", (strlen(hostname) <= MDNS_LABEL_MAXLEN), return ERR_VAL); LWIP_ASSERT("mdns_resp_add_netif: Double add", NETIF_TO_HOST(netif) == NULL); mdns = (struct mdns_host *) mem_calloc(1, sizeof(struct mdns_host)); LWIP_ERROR("mdns_resp_add_netif: Alloc failed", (mdns != NULL), return ERR_MEM); 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; /* Join multicast groups */ #if LWIP_IPV4 res = igmp_joingroup_netif(netif, ip_2_ip4(&v4group)); if (res != ERR_OK) { goto cleanup; } #endif #if LWIP_IPV6 res = mld6_joingroup_netif(netif, ip_2_ip6(&v6group)); if (res != ERR_OK) { goto cleanup; } #endif mdns_resp_restart(netif); return ERR_OK; cleanup: mem_free(mdns); netif_set_client_data(netif, mdns_netif_client_id, NULL); return res; } /** * @ingroup mdns * Stop responding to MDNS queries on this interface, leave multicast groups, * and free the helper structure and any of its services. * @param netif The network interface to remove. * @return ERR_OK if netif was removed, an err_t otherwise */ err_t mdns_resp_remove_netif(struct netif *netif) { int i; struct mdns_host *mdns; LWIP_ASSERT_CORE_LOCKED(); LWIP_ASSERT("mdns_resp_remove_netif: Null pointer", netif); mdns = NETIF_TO_HOST(netif); LWIP_ERROR("mdns_resp_remove_netif: Not an active netif", (mdns != NULL), return ERR_VAL); if (mdns->probing_state == MDNS_PROBING_ONGOING) { sys_untimeout(mdns_probe, netif); } for (i = 0; i < MDNS_MAX_SERVICES; i++) { struct mdns_service *service = mdns->services[i]; if (service) { mem_free(service); } } /* Leave multicast groups */ #if LWIP_IPV4 igmp_leavegroup_netif(netif, ip_2_ip4(&v4group)); #endif #if LWIP_IPV6 mld6_leavegroup_netif(netif, ip_2_ip6(&v6group)); #endif mem_free(mdns); netif_set_client_data(netif, mdns_netif_client_id, NULL); return ERR_OK; } /** * @ingroup mdns * Update MDNS hostname for a network interface. * @param netif The network interface to activate. * @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. * @return ERR_OK if name could be set on netif, an err_t otherwise */ err_t mdns_resp_rename_netif(struct netif *netif, const char *hostname) { struct mdns_host *mdns; size_t len; LWIP_ASSERT_CORE_LOCKED(); len = strlen(hostname); LWIP_ERROR("mdns_resp_rename_netif: netif != NULL", (netif != NULL), return ERR_VAL); LWIP_ERROR("mdns_resp_rename_netif: Hostname too long", (len <= MDNS_LABEL_MAXLEN), return ERR_VAL); mdns = NETIF_TO_HOST(netif); LWIP_ERROR("mdns_resp_rename_netif: Not an mdns netif", (mdns != NULL), return ERR_VAL); MEMCPY(&mdns->name, hostname, LWIP_MIN(MDNS_LABEL_MAXLEN, len)); mdns->name[len] = '\0'; /* null termination in case new name is shorter than previous */ mdns_resp_restart(netif); return ERR_OK; } /** * @ingroup mdns * Add a service to the selected network interface. * @param netif The network interface to publish this service on * @param name The name of the service * @param service The service type, like "_http" * @param proto The service protocol, DNSSD_PROTO_TCP for TCP ("_tcp") and DNSSD_PROTO_UDP * for others ("_udp") * @param 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) { s8_t i; s8_t slot = -1; struct mdns_service *srv; struct mdns_host *mdns; LWIP_ASSERT_CORE_LOCKED(); LWIP_ASSERT("mdns_resp_add_service: netif != NULL", netif); mdns = NETIF_TO_HOST(netif); LWIP_ERROR("mdns_resp_add_service: Not an mdns netif", (mdns != NULL), return ERR_VAL); LWIP_ERROR("mdns_resp_add_service: Name too long", (strlen(name) <= MDNS_LABEL_MAXLEN), return ERR_VAL); LWIP_ERROR("mdns_resp_add_service: Service too long", (strlen(service) <= MDNS_LABEL_MAXLEN), return ERR_VAL); LWIP_ERROR("mdns_resp_add_service: Bad proto (need TCP or UDP)", (proto == DNSSD_PROTO_TCP || proto == DNSSD_PROTO_UDP), return ERR_VAL); for (i = 0; i < MDNS_MAX_SERVICES; i++) { if (mdns->services[i] == NULL) { slot = i; break; } } LWIP_ERROR("mdns_resp_add_service: Service list full (increase MDNS_MAX_SERVICES)", (slot >= 0), return ERR_MEM); srv = (struct mdns_service *)mem_calloc(1, sizeof(struct mdns_service)); LWIP_ERROR("mdns_resp_add_service: Alloc failed", (srv != NULL), return ERR_MEM); MEMCPY(&srv->name, name, LWIP_MIN(MDNS_LABEL_MAXLEN, strlen(name))); MEMCPY(&srv->service, service, LWIP_MIN(MDNS_LABEL_MAXLEN, strlen(service))); srv->txt_fn = txt_fn; srv->txt_userdata = txt_data; srv->proto = (u16_t)proto; srv->port = port; srv->dns_ttl = dns_ttl; mdns->services[slot] = srv; mdns_resp_restart(netif); return slot; } /** * @ingroup mdns * Delete a service on the selected network interface. * @param netif The network interface on which service should be removed * @param slot The service slot number returned by mdns_resp_add_service * @return ERR_OK if the service was removed from the netif, an err_t otherwise */ err_t mdns_resp_del_service(struct netif *netif, s8_t slot) { struct mdns_host *mdns; struct mdns_service *srv; LWIP_ASSERT("mdns_resp_del_service: netif != NULL", netif); mdns = NETIF_TO_HOST(netif); LWIP_ERROR("mdns_resp_del_service: Not an mdns netif", (mdns != NULL), return ERR_VAL); LWIP_ERROR("mdns_resp_del_service: Invalid Service ID", (slot >= 0) && (slot < MDNS_MAX_SERVICES), return ERR_VAL); LWIP_ERROR("mdns_resp_del_service: Invalid Service ID", (mdns->services[slot] != NULL), return ERR_VAL); srv = mdns->services[slot]; mdns->services[slot] = NULL; mem_free(srv); return ERR_OK; } /** * @ingroup mdns * Update name for an MDNS service. * @param netif The network interface to activate. * @param slot The service slot number returned by mdns_resp_add_service * @param name The new name for the service * @return ERR_OK if name could be set on service, an err_t otherwise */ err_t mdns_resp_rename_service(struct netif *netif, s8_t slot, const char *name) { struct mdns_service *srv; struct mdns_host *mdns; size_t len; LWIP_ASSERT_CORE_LOCKED(); len = strlen(name); LWIP_ASSERT("mdns_resp_rename_service: netif != NULL", netif); mdns = NETIF_TO_HOST(netif); LWIP_ERROR("mdns_resp_rename_service: Not an mdns netif", (mdns != NULL), return ERR_VAL); LWIP_ERROR("mdns_resp_rename_service: Name too long", (len <= MDNS_LABEL_MAXLEN), return ERR_VAL); LWIP_ERROR("mdns_resp_rename_service: Invalid Service ID", (slot >= 0) && (slot < MDNS_MAX_SERVICES), return ERR_VAL); LWIP_ERROR("mdns_resp_rename_service: Invalid Service ID", (mdns->services[slot] != NULL), return ERR_VAL); srv = mdns->services[slot]; MEMCPY(&srv->name, name, LWIP_MIN(MDNS_LABEL_MAXLEN, len)); srv->name[len] = '\0'; /* null termination in case new name is shorter than previous */ mdns_resp_restart(netif); return ERR_OK; } /** * @ingroup mdns * Call this function from inside the service_get_txt_fn_t callback to add text data. * Buffer for TXT data is 256 bytes, and each field is prefixed with a length byte. * @param service The service provided to the get_txt callback * @param txt String to add to the TXT field. * @param txt_len Length of string * @return ERR_OK if the string was added to the reply, an err_t otherwise */ err_t mdns_resp_add_service_txtitem(struct mdns_service *service, const char *txt, u8_t txt_len) { LWIP_ASSERT_CORE_LOCKED(); LWIP_ASSERT("mdns_resp_add_service_txtitem: service != NULL", service); /* Use a mdns_domain struct to store txt chunks since it is the same encoding */ return mdns_domain_add_label(&service->txtdata, txt, txt_len); } /** * @ingroup mdns * Send unsolicited answer containing all our known data * @param netif The network interface to send on */ void mdns_resp_announce(struct netif *netif) { struct mdns_host* mdns; LWIP_ASSERT_CORE_LOCKED(); LWIP_ERROR("mdns_resp_announce: netif != NULL", (netif != NULL), return); mdns = NETIF_TO_HOST(netif); if (mdns == NULL) { return; } if (mdns->probing_state == MDNS_PROBING_COMPLETE) { /* Announce on IPv6 and IPv4 */ #if LWIP_IPV6 mdns_announce(netif, IP6_ADDR_ANY); #endif #if LWIP_IPV4 if (!ip4_addr_isany_val(*netif_ip4_addr(netif))) { mdns_announce(netif, IP4_ADDR_ANY); } #endif } /* else: ip address changed while probing was ongoing? @todo reset counter to restart? */ } /** Register a callback function that is called if probing is completed successfully * or with a conflict. */ void mdns_resp_register_name_result_cb(mdns_name_result_cb_t cb) { mdns_name_result_cb = cb; } /** * @ingroup mdns * Restart mdns responder. Call this when cable is connected after being disconnected or * administrative interface is set up after being down * @param netif The network interface to send on */ void mdns_resp_restart(struct netif *netif) { struct mdns_host* mdns; LWIP_ASSERT_CORE_LOCKED(); LWIP_ERROR("mdns_resp_restart: netif != NULL", (netif != NULL), return); mdns = NETIF_TO_HOST(netif); if (mdns == NULL) { return; } if (mdns->probing_state == MDNS_PROBING_ONGOING) { sys_untimeout(mdns_probe, netif); } /* @todo if we've failed 15 times within a 10 second period we MUST wait 5 seconds (or wait 5 seconds every time except first)*/ mdns->probes_sent = 0; mdns->probing_state = MDNS_PROBING_ONGOING; sys_timeout(MDNS_INITIAL_PROBE_DELAY_MS, mdns_probe, netif); } /** * @ingroup mdns * Initiate MDNS responder. Will open UDP sockets on port 5353 */ void mdns_resp_init(void) { err_t res; /* LWIP_ASSERT_CORE_LOCKED(); is checked by udp_new() */ 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); #else mdns_pcb->ttl = MDNS_TTL; #endif res = udp_bind(mdns_pcb, IP_ANY_TYPE, LWIP_IANA_PORT_MDNS); LWIP_UNUSED_ARG(res); /* in case of LWIP_NOASSERT */ LWIP_ASSERT("Failed to bind pcb", res == ERR_OK); udp_recv(mdns_pcb, mdns_recv, NULL); mdns_netif_client_id = netif_alloc_client_data_id(); #if MDNS_RESP_USENETIF_EXTCALLBACK /* register for netif events when started on first netif */ netif_add_ext_callback(&netif_callback, mdns_netif_ext_status_callback); #endif } #endif /* LWIP_MDNS_RESPONDER */