| /** |
| * @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 <erik@kryo.se> |
| * |
| */ |
| |
| #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 <string.h> |
| |
| #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 <hostname>.local. domain name |
| * @param domain Where to write the domain name |
| * @param mdns TMDNS netif descriptor. |
| * @return ERR_OK if domain <hostname>.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, |
| * <name>.<type>.<proto>.local. will be written, otherwise <type>.<proto>.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 */ |