diff --git a/src/apps/mdns/mdns.c b/src/apps/mdns/mdns.c index 7f469a2d..6fc10e62 100644 --- a/src/apps/mdns/mdns.c +++ b/src/apps/mdns/mdns.c @@ -80,6 +80,9 @@ #if (!LWIP_UDP) #error "If you want to use MDNS, you have to define LWIP_UDP=1 in your lwipopts.h" #endif +#ifndef LWIP_RAND +#error "If you want to use MDNS, you have to define LWIP_RAND=(random function) in your lwipopts.h" +#endif #if LWIP_IPV4 #include "lwip/igmp.h" @@ -105,6 +108,13 @@ static mdns_name_result_cb_t mdns_name_result_cb; #define NETIF_TO_HOST(netif) (struct mdns_host*)(netif_get_client_data(netif, mdns_netif_client_id)) +/** Delayed response defines */ +#define MDNS_RESPONSE_DELAY_MAX 120 +#define MDNS_RESPONSE_DELAY_MIN 20 +#define MDNS_RESPONSE_DELAY (LWIP_RAND() %(MDNS_RESPONSE_DELAY_MAX - \ + MDNS_RESPONSE_DELAY_MIN) + MDNS_RESPONSE_DELAY_MIN) + +/** Probing defines */ #define MDNS_PROBE_DELAY_MS 250 #define MDNS_PROBE_COUNT 3 #ifdef LWIP_RAND @@ -136,12 +146,18 @@ struct mdns_packet { u16_t questions; /** Number of unparsed questions */ u16_t questions_left; - /** Number of answers in packet, - * (sum of normal, authoritative and additional answers) - * read from packet header */ + /** Number of answers in packet */ u16_t answers; /** Number of unparsed answers */ u16_t answers_left; + /** Number of authoritative answers in packet */ + u16_t authoritative; + /** Number of unparsed authoritative answers */ + u16_t authoritative_left; + /** Number of additional answers in packet */ + u16_t additional; + /** Number of unparsed additional answers */ + u16_t additional_left; }; /** Domain, type and class. @@ -172,11 +188,25 @@ struct mdns_answer { static void mdns_probe(void* arg); +/** + * Construction to make mdns struct accessible from mdns_out.c + * TODO: + * can we add the mdns struct to the netif like we do for dhcp, autoip,...? + * Then this is not needed any more. + * + * @param netif The network interface + * @return mdns struct + */ struct mdns_host* netif_mdns_data(struct netif *netif) { return NETIF_TO_HOST(netif); } +/** + * Construction to access the mdns udp pcb. + * + * @return udp_pcb struct of mdns + */ struct udp_pcb* get_mdns_pcb(void) { @@ -370,13 +400,14 @@ mdns_read_question(struct mdns_packet *pkt, struct mdns_question *question) /** * Read an answer from the packet * The variable length reply is not copied, its pbuf offset and length is stored instead. - * @param pkt The MDNS packet to read. The answers_left field will be decremented and + * @param pkt The MDNS packet to read. The num_left field will be decremented and * the parse_offset will be updated. - * @param answer The struct to fill with answer data + * @param answer The struct to fill with answer data + * @param num_left number of answers left -> answers, authoritative or additional * @return ERR_OK on success, an err_t otherwise */ static err_t -mdns_read_answer(struct mdns_packet *pkt, struct mdns_answer *answer) +mdns_read_answer(struct mdns_packet *pkt, struct mdns_answer *answer, u16_t *num_left) { /* Read questions first */ if (pkt->questions_left) { @@ -388,11 +419,11 @@ mdns_read_answer(struct mdns_packet *pkt, struct mdns_answer *answer) return ERR_VAL; } - if (pkt->answers_left) { + if (*num_left) { u16_t copied, field16; u32_t ttl; err_t res; - pkt->answers_left--; + (*num_left)--; memset(answer, 0, sizeof(struct mdns_answer)); res = mdns_read_rr_info(pkt, &answer->info); @@ -426,35 +457,6 @@ mdns_read_answer(struct mdns_packet *pkt, struct mdns_answer *answer) return ERR_VAL; } -/** - * Setup outpacket as a reply to the incoming packet - */ -static void -mdns_init_outmsg_with_in_packet(struct mdns_outmsg *out, struct mdns_packet *in) -{ - memset(out, 0, sizeof(struct mdns_outmsg)); - out->cache_flush = 1; - - /* 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 unsolicited answer containing all our known data * @param netif The network interface to send on @@ -498,27 +500,21 @@ mdns_announce(struct netif *netif, const ip_addr_t *destination) } /** - * Handle question MDNS packet - * 1. Parse all questions and set bits what answers to send - * 2. Clear pending answers if known answers are supplied - * 3. Put chosen answers in new packet and send as reply + * Check the incomming packet and parse all questions + * + * @param netif network interface of incoming packet + * @param pkt incoming packet + * @param reply outgoing message + * @return err_t */ -static void -mdns_handle_question(struct mdns_packet *pkt, struct netif *netif) +static err_t +mdns_parse_pkt_questions(struct netif *netif, struct mdns_packet *pkt, + struct mdns_outmsg *reply) { + struct mdns_host *mdns = NETIF_TO_HOST(netif); struct mdns_service *service; - struct mdns_outmsg reply; int i; err_t res; - struct mdns_host *mdns = NETIF_TO_HOST(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_outmsg_with_in_packet(&reply, pkt); while (pkt->questions_left) { struct mdns_question q; @@ -526,7 +522,7 @@ mdns_handle_question(struct mdns_packet *pkt, struct netif *netif) res = mdns_read_question(pkt, &q); if (res != ERR_OK) { LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to parse question, skipping query packet\n")); - return; + return res; } LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Query for domain ")); @@ -534,32 +530,51 @@ mdns_handle_question(struct mdns_packet *pkt, struct netif *netif) LWIP_DEBUGF(MDNS_DEBUG, (" type %d class %d\n", q.info.type, q.info.klass)); if (q.unicast) { - /* Reply unicast if any question is unicast */ - reply.unicast_reply = 1; + /* Reply unicast if it is requested in the question */ + reply->unicast_reply_requested = 1; } - reply.host_replies |= check_host(netif, &q.info, &reply.host_reverse_v6_replies); + reply->host_replies |= check_host(netif, &q.info, &reply->host_reverse_v6_replies); for (i = 0; i < MDNS_MAX_SERVICES; i++) { service = mdns->services[i]; if (!service) { continue; } - reply.serv_replies[i] |= check_service(service, &q.info); + reply->serv_replies[i] |= check_service(service, &q.info); } } - /* Handle known answers */ + return ERR_OK; +} + +/** + * Check the incomming packet and parse all (known) answers + * + * @param netif network interface of incoming packet + * @param pkt incoming packet + * @param reply outgoing message + * @return err_t + */ +static err_t +mdns_parse_pkt_known_answers(struct netif *netif, struct mdns_packet *pkt, + struct mdns_outmsg *reply) +{ + struct mdns_host *mdns = NETIF_TO_HOST(netif); + struct mdns_service *service; + int i; + err_t res; + while (pkt->answers_left) { struct mdns_answer ans; u8_t rev_v6; int match; u32_t rr_ttl = MDNS_TTL_120; - res = mdns_read_answer(pkt, &ans); + res = mdns_read_answer(pkt, &ans, &pkt->answers_left); if (res != ERR_OK) { LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to parse answer, skipping query packet\n")); - return; + return res; } LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Known answer for domain ")); @@ -573,7 +588,7 @@ mdns_handle_question(struct mdns_packet *pkt, struct netif *netif) } rev_v6 = 0; - match = reply.host_replies & check_host(netif, &ans.info, &rev_v6); + match = reply->host_replies & check_host(netif, &ans.info, &rev_v6); if (match && (ans.ttl > (rr_ttl / 2))) { /* The RR in the known answer matches an RR we are planning to send, * and the TTL is less than half gone. @@ -589,15 +604,15 @@ mdns_handle_question(struct mdns_packet *pkt, struct netif *netif) #if LWIP_IPV4 if (match & REPLY_HOST_PTR_V4) { LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: v4 PTR\n")); - reply.host_replies &= ~REPLY_HOST_PTR_V4; + reply->host_replies &= ~REPLY_HOST_PTR_V4; } #endif #if LWIP_IPV6 if (match & REPLY_HOST_PTR_V6) { LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: v6 PTR\n")); - reply.host_reverse_v6_replies &= ~rev_v6; - if (reply.host_reverse_v6_replies == 0) { - reply.host_replies &= ~REPLY_HOST_PTR_V6; + reply->host_reverse_v6_replies &= ~rev_v6; + if (reply->host_reverse_v6_replies == 0) { + reply->host_replies &= ~REPLY_HOST_PTR_V6; } } #endif @@ -607,7 +622,7 @@ mdns_handle_question(struct mdns_packet *pkt, struct netif *netif) if (ans.rd_length == sizeof(ip4_addr_t) && pbuf_memcmp(pkt->pbuf, ans.rd_offset, netif_ip4_addr(netif), ans.rd_length) == 0) { LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: A\n")); - reply.host_replies &= ~REPLY_HOST_A; + reply->host_replies &= ~REPLY_HOST_A; } #endif } else if (match & REPLY_HOST_AAAA) { @@ -616,7 +631,7 @@ mdns_handle_question(struct mdns_packet *pkt, struct netif *netif) /* TODO this clears all AAAA responses if first addr is set as known */ pbuf_memcmp(pkt->pbuf, ans.rd_offset, netif_ip6_addr(netif, 0), ans.rd_length) == 0) { LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: AAAA\n")); - reply.host_replies &= ~REPLY_HOST_AAAA; + reply->host_replies &= ~REPLY_HOST_AAAA; } #endif } @@ -627,7 +642,7 @@ mdns_handle_question(struct mdns_packet *pkt, struct netif *netif) if (!service) { continue; } - match = reply.serv_replies[i] & check_service(service, &ans.info); + match = reply->serv_replies[i] & check_service(service, &ans.info); if (match & REPLY_SERVICE_TYPE_PTR) { rr_ttl = MDNS_TTL_4500; } @@ -646,14 +661,14 @@ mdns_handle_question(struct mdns_packet *pkt, struct netif *netif) res = mdns_build_service_domain(&my_ans, service, 0); if (res == ERR_OK && mdns_domain_eq(&known_ans, &my_ans)) { LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: service type PTR\n")); - reply.serv_replies[i] &= ~REPLY_SERVICE_TYPE_PTR; + reply->serv_replies[i] &= ~REPLY_SERVICE_TYPE_PTR; } } if (match & REPLY_SERVICE_NAME_PTR) { res = mdns_build_service_domain(&my_ans, service, 1); if (res == ERR_OK && mdns_domain_eq(&known_ans, &my_ans)) { LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: service name PTR\n")); - reply.serv_replies[i] &= ~REPLY_SERVICE_NAME_PTR; + reply->serv_replies[i] &= ~REPLY_SERVICE_NAME_PTR; } } } @@ -688,37 +703,374 @@ mdns_handle_question(struct mdns_packet *pkt, struct netif *netif) break; } LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: SRV\n")); - reply.serv_replies[i] &= ~REPLY_SERVICE_SRV; + reply->serv_replies[i] &= ~REPLY_SERVICE_SRV; } while (0); } else if (match & REPLY_SERVICE_TXT) { mdns_prepare_txtdata(service); if (service->txtdata.length == ans.rd_length && pbuf_memcmp(pkt->pbuf, ans.rd_offset, service->txtdata.name, ans.rd_length) == 0) { LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: TXT\n")); - reply.serv_replies[i] &= ~REPLY_SERVICE_TXT; + reply->serv_replies[i] &= ~REPLY_SERVICE_TXT; } } } } } - reply.flags = DNS_FLAG1_RESPONSE | DNS_FLAG1_AUTHORATIVE; + return ERR_OK; +} - if(!reply.unicast_reply) { - reply.dest_port = LWIP_IANA_PORT_MDNS; +/** + * Check the incomming packet and parse all authoritative answers to see if the + * query is a probe query. + * + * @param netif network interface of incoming packet + * @param pkt incoming packet + * @param reply outgoing message + * @return err_t + */ +static err_t +mdns_parse_pkt_authoritative_answers(struct netif *netif, struct mdns_packet *pkt, + struct mdns_outmsg *reply) +{ + struct mdns_host *mdns = NETIF_TO_HOST(netif); + struct mdns_service *service; + int i; + err_t res; - if (IP_IS_V6_VAL(reply.dest_addr)) { -#if LWIP_IPV6 - SMEMCPY(&reply.dest_addr, &v6group, sizeof(reply.dest_addr)); -#endif - } else { -#if LWIP_IPV4 - SMEMCPY(&reply.dest_addr, &v4group, sizeof(reply.dest_addr)); -#endif + while (pkt->authoritative_left) { + struct mdns_answer ans; + u8_t rev_v6; + int match; + + res = mdns_read_answer(pkt, &ans, &pkt->authoritative_left); + if (res != ERR_OK) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to parse answer, skipping query packet\n")); + return res; + } + + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Authoritative answer for domain ")); + mdns_domain_debug_print(&ans.info.domain); + LWIP_DEBUGF(MDNS_DEBUG, (" type %d class %d\n", ans.info.type, ans.info.klass)); + + + if (ans.info.type == DNS_RRTYPE_ANY || ans.info.klass == DNS_RRCLASS_ANY) { + /* Skip known answers for ANY type & class */ + continue; + } + + rev_v6 = 0; + match = reply->host_replies & check_host(netif, &ans.info, &rev_v6); + if (match) { + reply->probe_query_recv = 1; + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Probe for own host info received\r\n")); + } + + for (i = 0; i < MDNS_MAX_SERVICES; i++) { + service = mdns->services[i]; + if (!service) { + continue; + } + match = reply->serv_replies[i] & check_service(service, &ans.info); + + if (match) { + reply->probe_query_recv = 1; + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Probe for own service info received\r\n")); + } } } - mdns_send_outpacket(&reply, netif); + return ERR_OK; +} + +/** + * Add / copy message to delaying message buffer. + * + * @param dest destination msg struct + * @param src source msg struct + */ +static void +mdns_add_msg_to_delayed(struct mdns_outmsg *dest, struct mdns_outmsg *src) +{ + dest->host_questions |= src->host_questions; + dest->host_replies |= src->host_replies; + dest->host_reverse_v6_replies |= src->host_reverse_v6_replies; + for (int i = 0; i < MDNS_MAX_SERVICES; i++) { + dest->serv_questions[i] |= src->serv_questions[i]; + dest->serv_replies[i] |= src->serv_replies[i]; + } + + dest->flags = src->flags; + dest->cache_flush = src->cache_flush; + dest->tx_id = src->tx_id; + dest->legacy_query = src->legacy_query; +} + +/** + * Handle question MDNS packet + * 1. Parse all questions and set bits what answers to send + * 2. Clear pending answers if known answers are supplied + * 3. Define which type of answer is requested + * 4. Send out packet or put it on hold until after random time + * + * @param pkt incoming packet + * @param netif network interface of incoming packet + */ +static void +mdns_handle_question(struct mdns_packet *pkt, struct netif *netif) +{ + struct mdns_host *mdns = NETIF_TO_HOST(netif); + struct mdns_outmsg reply; + u8_t rrs_to_send; + u8_t shared_answer = 0; + u8_t delay_response = 1; + u8_t send_unicast = 0; + u8_t listen_to_QU_bit = 0; + int i; + err_t res; + + if (mdns->probing_state != MDNS_PROBING_COMPLETE) { + /* Don't answer questions until we've verified our domains via probing */ + /* @todo we should check incoming questions during probing for tiebreaking */ + return; + } + + memset(&reply, 0, sizeof(struct mdns_outmsg)); + + /* Parse question */ + res = mdns_parse_pkt_questions(netif, pkt, &reply); + if (res != ERR_OK) { + return; + } + /* Parse answers -> count as known answers because it's a question */ + res = mdns_parse_pkt_known_answers(netif, pkt, &reply); + if (res != ERR_OK) { + return; + } + /* Parse authoritative answers -> probing */ + /* If it's a probe query, we need to directly answer via unicast. */ + res = mdns_parse_pkt_authoritative_answers(netif, pkt, &reply); + if (res != ERR_OK) { + return; + } + /* Ignore additional answers -> do not have any need for them at the moment */ + if(pkt->additional) { + LWIP_DEBUGF(MDNS_DEBUG, + ("MDNS: Query contains additional answers -> they are discarded \r\n")); + } + + /* Any replies on question? */ + rrs_to_send = reply.host_replies | reply.host_questions; + for (i = 0; i < MDNS_MAX_SERVICES; i++) { + rrs_to_send |= reply.serv_replies[i] | reply.serv_questions[i]; + } + + if (!rrs_to_send) { + /* This case is most common */ + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Nothing to answer\r\n")); + return; + } + + reply.flags = DNS_FLAG1_RESPONSE | DNS_FLAG1_AUTHORATIVE; + + /* Detect if it's a legacy querier asking the question + * How to detect legacy DNS query? (RFC6762 section 6.7) + * - source port != 5353 + * - a legacy query can only contain 1 question + */ + if (pkt->source_port != LWIP_IANA_PORT_MDNS) { + if (pkt->questions == 1) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: request from legacy querier\r\n")); + reply.legacy_query = 1; + reply.tx_id = pkt->tx_id; + reply.cache_flush = 0; + } + else { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: ignore query if (src UDP port != 5353) && (!= legacy query)\r\n")); + return; + } + } + else { + reply.cache_flush = 1; + } + + /* Delaying response. + * Always delay the response, unicast or multicast, except when: + * - Answering to a single question with a unique answer (RFC6762 section 6) + * - Answering to a probe query via unicast (RFC6762 section 6) + * + * unique answer? -> not if it includes service type or name ptr's + */ + for (i = 0; i < MDNS_MAX_SERVICES; i++) { + shared_answer |= (reply.serv_replies[i] & + (REPLY_SERVICE_TYPE_PTR | REPLY_SERVICE_NAME_PTR)); + } + if (((pkt->questions == 1) && (!shared_answer)) || reply.probe_query_recv) { + delay_response = 0; + } + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: response %s delayed\r\n", (delay_response ? "randomly" : "not"))); + + /* Unicast / multicast response: + * Answering to (m)DNS querier via unicast response. + * When: + * a) Unicast reply requested && recently multicasted 1/4ttl (RFC6762 section 5.4) + * b) Direct unicast query to port 5353 (RFC6762 section 5.5) + * c) Reply to Legacy DNS querier (RFC6762 section 6.7) + * d) A probe message is received (RFC6762 section 6) + */ + +#if LWIP_IPV6 + if ((IP_IS_V6_VAL(pkt->source_addr) && mdns->ipv6.multicast_timeout_25TTL)) { + listen_to_QU_bit = 1; + } +#endif +#if LWIP_IPV4 + if ((IP_IS_V4_VAL(pkt->source_addr) && mdns->ipv4.multicast_timeout_25TTL)) { + listen_to_QU_bit = 1; + } +#endif + if ( (reply.unicast_reply_requested && listen_to_QU_bit) + || pkt->recv_unicast + || reply.legacy_query + || reply.probe_query_recv ) { + send_unicast = 1; + } + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: send response via %s\r\n", (send_unicast ? "unicast" : "multicast"))); + + /* Send out or put on waiting list */ + if (delay_response) { + if (send_unicast) { +#if LWIP_IPV6 + /* Add answers to IPv6 waiting list if: + * - it's a IPv6 incoming packet + * - no message is in it yet + */ + if (IP_IS_V6_VAL(pkt->source_addr) && !mdns->ipv6.unicast_msg_in_use) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: add answers to unicast IPv6 waiting list\r\n")); + SMEMCPY(&mdns->ipv6.delayed_msg_unicast.dest_addr, &pkt->source_addr, sizeof(ip_addr_t)); + mdns->ipv6.delayed_msg_unicast.dest_port = pkt->source_port; + + mdns_add_msg_to_delayed(&mdns->ipv6.delayed_msg_unicast, &reply); + + mdns_set_timeout(netif, MDNS_RESPONSE_DELAY, mdns_send_unicast_msg_delayed_ipv6, + &mdns->ipv6.unicast_msg_in_use); + } +#endif +#if LWIP_IPV4 + /* Add answers to IPv4 waiting list if: + * - it's a IPv4 incoming packet + * - no message is in it yet + */ + if (IP_IS_V4_VAL(pkt->source_addr) && !mdns->ipv4.unicast_msg_in_use) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: add answers to unicast IPv4 waiting list\r\n")); + SMEMCPY(&mdns->ipv4.delayed_msg_unicast.dest_addr, &pkt->source_addr, sizeof(ip_addr_t)); + mdns->ipv4.delayed_msg_unicast.dest_port = pkt->source_port; + + mdns_add_msg_to_delayed(&mdns->ipv4.delayed_msg_unicast, &reply); + + mdns_set_timeout(netif, MDNS_RESPONSE_DELAY, mdns_send_unicast_msg_delayed_ipv4, + &mdns->ipv4.unicast_msg_in_use); + } +#endif + } + else { +#if LWIP_IPV6 + /* Add answers to IPv6 waiting list if: + * - it's a IPv6 incoming packet + * - and the 1 second timeout is passed (RFC6762 section 6) + */ + if (IP_IS_V6_VAL(pkt->source_addr) && !mdns->ipv6.multicast_timeout) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: add answers to multicast IPv6 waiting list\r\n")); + + mdns_add_msg_to_delayed(&mdns->ipv6.delayed_msg_multicast, &reply); + + mdns_set_timeout(netif, MDNS_RESPONSE_DELAY, mdns_send_multicast_msg_delayed_ipv6, + &mdns->ipv6.multicast_msg_waiting); + } +#endif +#if LWIP_IPV4 + /* Add answers to IPv4 waiting list if: + * - it's a IPv4 incoming packet + * - and the 1 second timeout is passed (RFC6762 section 6) + */ + if (IP_IS_V4_VAL(pkt->source_addr) && !mdns->ipv4.multicast_timeout) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: add answers to multicast IPv4 waiting list\r\n")); + + mdns_add_msg_to_delayed(&mdns->ipv4.delayed_msg_multicast, &reply); + + mdns_set_timeout(netif, MDNS_RESPONSE_DELAY, mdns_send_multicast_msg_delayed_ipv4, + &mdns->ipv4.multicast_msg_waiting); + } +#endif + } + } + else { + if (send_unicast) { + /* Copy source IP/port to use when responding unicast */ + SMEMCPY(&reply.dest_addr, &pkt->source_addr, sizeof(ip_addr_t)); + reply.dest_port = pkt->source_port; + /* send answer directly via unicast */ + res = mdns_send_outpacket(&reply, netif); + if (res != ERR_OK) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Unicast answer could not be send\r\n")); + } + else { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Unicast answer send successfully\r\n")); + } + return; + } + else { + /* Set IP/port to use when responding multicast */ +#if LWIP_IPV6 + if (IP_IS_V6_VAL(pkt->source_addr)) { + if (mdns->ipv6.multicast_timeout) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: we just multicasted, ignore question\r\n")); + return; + } + SMEMCPY(&reply.dest_addr, &v6group, sizeof(ip_addr_t)); + } +#endif +#if LWIP_IPV4 + if (IP_IS_V4_VAL(pkt->source_addr)) { + if (mdns->ipv4.multicast_timeout) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: we just multicasted, ignore question\r\n")); + return; + } + SMEMCPY(&reply.dest_addr, &v4group, sizeof(ip_addr_t)); + } +#endif + reply.dest_port = LWIP_IANA_PORT_MDNS; + /* send answer directly via multicast */ + res = mdns_send_outpacket(&reply, netif); + if (res != ERR_OK) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Multicast answer could not be send\r\n")); + } + else { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Multicast answer send successfully\r\n")); +#if LWIP_IPV6 + if (IP_IS_V6_VAL(pkt->source_addr)) { + mdns_set_timeout(netif, MDNS_MULTICAST_TIMEOUT, mdns_multicast_timeout_reset_ipv6, + &mdns->ipv6.multicast_timeout); + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout started - IPv6\n")); + mdns_set_timeout(netif, MDNS_MULTICAST_TIMEOUT_25TTL, mdns_multicast_timeout_25ttl_reset_ipv6, + &mdns->ipv6.multicast_timeout_25TTL); + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout 1/4 of ttl started - IPv6\n")); + } +#endif +#if LWIP_IPV4 + if (IP_IS_V4_VAL(pkt->source_addr)) { + mdns_set_timeout(netif, MDNS_MULTICAST_TIMEOUT, mdns_multicast_timeout_reset_ipv4, + &mdns->ipv4.multicast_timeout); + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout started - IPv4\n")); + mdns_set_timeout(netif, MDNS_MULTICAST_TIMEOUT_25TTL, mdns_multicast_timeout_25ttl_reset_ipv4, + &mdns->ipv4.multicast_timeout_25TTL); + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout 1/4 of ttl started - IPv4\n")); + } +#endif + } + return; + } + } } /** @@ -746,7 +1098,7 @@ mdns_handle_response(struct mdns_packet *pkt, struct netif *netif) struct mdns_answer ans; err_t res; - res = mdns_read_answer(pkt, &ans); + res = mdns_read_answer(pkt, &ans, &pkt->answers_left); if (res != ERR_OK) { LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to parse answer, skipping response packet\n")); return; @@ -831,7 +1183,9 @@ mdns_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, packet.parse_offset = offset; packet.tx_id = lwip_ntohs(hdr.id); packet.questions = packet.questions_left = lwip_ntohs(hdr.numquestions); - packet.answers = packet.answers_left = lwip_ntohs(hdr.numanswers) + lwip_ntohs(hdr.numauthrr) + lwip_ntohs(hdr.numextrarr); + packet.answers = packet.answers_left = lwip_ntohs(hdr.numanswers); + packet.authoritative = packet.authoritative_left = lwip_ntohs(hdr.numauthrr); + packet.additional = packet.additional_left = lwip_ntohs(hdr.numextrarr); #if LWIP_IPV6 if (IP_IS_V6(ip_current_dest_addr())) { @@ -1006,6 +1360,19 @@ mdns_resp_add_netif(struct netif *netif, const char *hostname) mdns->probes_sent = 0; mdns->probing_state = MDNS_PROBING_NOT_STARTED; + /* Init delayed message structs with address and port */ +#if LWIP_IPV4 + mdns->ipv4.delayed_msg_multicast.dest_port = LWIP_IANA_PORT_MDNS; + SMEMCPY(&mdns->ipv4.delayed_msg_multicast.dest_addr, &v4group, + sizeof(ip_addr_t)); +#endif + +#if LWIP_IPV6 + mdns->ipv6.delayed_msg_multicast.dest_port = LWIP_IANA_PORT_MDNS; + SMEMCPY(&mdns->ipv6.delayed_msg_multicast.dest_addr, &v6group, + sizeof(ip_addr_t)); +#endif + /* Join multicast groups */ #if LWIP_IPV4 res = igmp_joingroup_netif(netif, ip_2_ip4(&v4group)); @@ -1256,10 +1623,22 @@ mdns_resp_announce(struct netif *netif) /* Announce on IPv6 and IPv4 */ #if LWIP_IPV6 mdns_announce(netif, &v6group); + mdns_set_timeout(netif, MDNS_MULTICAST_TIMEOUT, mdns_multicast_timeout_reset_ipv6, + &mdns->ipv6.multicast_timeout); + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout started - IPv6\n")); + mdns_set_timeout(netif, MDNS_MULTICAST_TIMEOUT_25TTL, mdns_multicast_timeout_25ttl_reset_ipv6, + &mdns->ipv6.multicast_timeout_25TTL); + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout 1/4 of ttl started - IPv6\n")); #endif #if LWIP_IPV4 if (!ip4_addr_isany_val(*netif_ip4_addr(netif))) { mdns_announce(netif, &v4group); + mdns_set_timeout(netif, MDNS_MULTICAST_TIMEOUT, mdns_multicast_timeout_reset_ipv4, + &mdns->ipv4.multicast_timeout); + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout started - IPv4\n")); + mdns_set_timeout(netif, MDNS_MULTICAST_TIMEOUT_25TTL, mdns_multicast_timeout_25ttl_reset_ipv4, + &mdns->ipv4.multicast_timeout_25TTL); + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout 1/4 of ttl started - IPv4\n")); } #endif } /* else: ip address changed while probing was ongoing? @todo reset counter to restart? */ diff --git a/src/apps/mdns/mdns_out.c b/src/apps/mdns/mdns_out.c index 351d2f7d..2b9cf328 100644 --- a/src/apps/mdns/mdns_out.c +++ b/src/apps/mdns/mdns_out.c @@ -54,6 +54,9 @@ /* Payload size allocated for each outgoing UDP packet */ #define OUTPACKET_SIZE 500 +/* Function prototypes */ +static void mdns_clear_outmsg(struct mdns_outmsg *outmsg); + /** * Call user supplied function to setup TXT data * @param service The service to build TXT record for @@ -740,8 +743,8 @@ mdns_send_outpacket(struct mdns_outmsg *msg, struct netif *netif) pbuf_realloc(outpkt.pbuf, outpkt.write_offset); /* Send created packet */ - LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Sending packet, len=%d, unicast=%d\n", - outpkt.write_offset, msg->unicast_reply)); + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Sending packet, len=%d\n", + outpkt.write_offset)); res = udp_sendto_if(get_mdns_pcb(), outpkt.pbuf, &msg->dest_addr, msg->dest_port, netif); } @@ -754,4 +757,235 @@ cleanup: return res; } +#if LWIP_IPV4 +/** + * Called by timeouts when timer is passed, allows multicast IPv4 traffic again. + * + * @param arg pointer to netif of timeout. + */ +void +mdns_multicast_timeout_reset_ipv4(void *arg) +{ + struct netif *netif = (struct netif*)arg; + struct mdns_host *mdns = netif_mdns_data(netif); + + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout finished - IPv4\n")); + + mdns->ipv4.multicast_timeout = 0; +} + +/** + * Called by timeouts when timer is passed, allows to send an answer on a QU + * question via multicast. + * + * @param arg pointer to netif of timeout. + */ +void +mdns_multicast_timeout_25ttl_reset_ipv4(void *arg) +{ + struct netif *netif = (struct netif*)arg; + struct mdns_host *mdns = netif_mdns_data(netif); + + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout 1/4 of ttl finished - IPv4\n")); + + mdns->ipv4.multicast_timeout_25TTL = 0; +} + +/** + * Called by timeouts when timer is passed, sends out delayed multicast IPv4 response. + * + * @param arg pointer to netif of timeout. + */ +void +mdns_send_multicast_msg_delayed_ipv4(void *arg) +{ + struct netif *netif = (struct netif*)arg; + struct mdns_host *mdns = netif_mdns_data(netif); + err_t res; + + res = mdns_send_outpacket(&mdns->ipv4.delayed_msg_multicast, netif); + if(res != ERR_OK) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Delayed multicast send failed - IPv4\n")); + } + else { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Delayed multicast send successful - IPv4\n")); + mdns_clear_outmsg(&mdns->ipv4.delayed_msg_multicast); + mdns->ipv4.multicast_msg_waiting = 0; + mdns_set_timeout(netif, MDNS_MULTICAST_TIMEOUT, mdns_multicast_timeout_reset_ipv4, + &mdns->ipv4.multicast_timeout); + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout started - IPv4\n")); + mdns_set_timeout(netif, MDNS_MULTICAST_TIMEOUT_25TTL, mdns_multicast_timeout_25ttl_reset_ipv4, + &mdns->ipv4.multicast_timeout_25TTL); + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout 1/4 of ttl started - IPv4\n")); + } +} + +/** + * Called by timeouts when timer is passed, sends out delayed unicast IPv4 response. + * + * @param arg pointer to netif of timeout. + */ +void +mdns_send_unicast_msg_delayed_ipv4(void *arg) +{ + struct netif *netif = (struct netif*)arg; + struct mdns_host *mdns = netif_mdns_data(netif); + err_t res; + + res = mdns_send_outpacket(&mdns->ipv4.delayed_msg_unicast, netif); + if(res != ERR_OK) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Delayed unicast send failed - IPv4\n")); + } + else { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Delayed unicast send successful - IPv4\n")); + mdns_clear_outmsg(&mdns->ipv4.delayed_msg_unicast); + mdns->ipv4.unicast_msg_in_use = 0; + } +} + +#endif + +#if LWIP_IPV6 +/** + * Called by timeouts when timer is passed, allows multicast IPv6 traffic again. + * + * @param arg pointer to netif of timeout. + */ +void +mdns_multicast_timeout_reset_ipv6(void *arg) +{ + struct netif *netif = (struct netif*)arg; + struct mdns_host *mdns = netif_mdns_data(netif); + + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout finished - IPv6\n")); + + mdns->ipv6.multicast_timeout = 0; +} + +/** + * Called by timeouts when timer is passed, allows to send an answer on a QU + * question via multicast. + * + * @param arg pointer to netif of timeout. + */ +void +mdns_multicast_timeout_25ttl_reset_ipv6(void *arg) +{ + struct netif *netif = (struct netif*)arg; + struct mdns_host *mdns = netif_mdns_data(netif); + + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout 1/4 of ttl finished - IPv6\n")); + + mdns->ipv6.multicast_timeout_25TTL = 0; +} + +/** + * Called by timeouts when timer is passed, sends out delayed multicast IPv6 response. + * + * @param arg pointer to netif of timeout. + */ +void +mdns_send_multicast_msg_delayed_ipv6(void *arg) +{ + struct netif *netif = (struct netif*)arg; + struct mdns_host *mdns = netif_mdns_data(netif); + err_t res; + + res = mdns_send_outpacket(&mdns->ipv6.delayed_msg_multicast, netif); + if(res != ERR_OK) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Delayed multicast send failed - IPv6\n")); + } + else { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Delayed multicast send successful - IPv6\n")); + mdns_clear_outmsg(&mdns->ipv6.delayed_msg_multicast); + mdns->ipv6.multicast_msg_waiting = 0; + mdns_set_timeout(netif, MDNS_MULTICAST_TIMEOUT, mdns_multicast_timeout_reset_ipv6, + &mdns->ipv6.multicast_timeout); + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout started - IPv6\n")); + mdns_set_timeout(netif, MDNS_MULTICAST_TIMEOUT_25TTL, mdns_multicast_timeout_25ttl_reset_ipv6, + &mdns->ipv6.multicast_timeout_25TTL); + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout 1/4 of ttl started - IPv6\n")); + } +} + +/** + * Called by timeouts when timer is passed, sends out delayed unicast IPv6 response. + * + * @param arg pointer to netif of timeout. + */ +void +mdns_send_unicast_msg_delayed_ipv6(void *arg) +{ + struct netif *netif = (struct netif*)arg; + struct mdns_host *mdns = netif_mdns_data(netif); + err_t res; + + res = mdns_send_outpacket(&mdns->ipv6.delayed_msg_unicast, netif); + if(res != ERR_OK) { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Delayed unicast send failed - IPv6\n")); + } + else { + LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Delayed unicast send successful - IPv6\n")); + mdns_clear_outmsg(&mdns->ipv6.delayed_msg_unicast); + mdns->ipv6.unicast_msg_in_use = 0; + } +} + +#endif + +/** + * This function clears the output message without changing the destination + * address or port. This is useful for clearing the delayed msg structs without + * loosing the set IP. + * + * @param outmsg pointer to output message to clear. + */ +static void +mdns_clear_outmsg(struct mdns_outmsg *outmsg) +{ + int i; + + outmsg->tx_id = 0; + outmsg->flags = 0; + outmsg->cache_flush = 0; + outmsg->unicast_reply_requested = 0; + outmsg->legacy_query = 0; + outmsg->probe_query_recv = 0; + outmsg->host_questions = 0; + outmsg->host_replies = 0; + outmsg->host_reverse_v6_replies = 0; + + for(i = 0; i < MDNS_MAX_SERVICES; i++) { + outmsg->serv_questions[i] = 0; + outmsg->serv_replies[i] = 0; + } +} + +/** + * Sets a timer that calls the handler when finished. + * Depending on the busy_flag the timer is restarted or started. The flag is + * set before return. Sys_timeout does not give us this functionality. + * + * @param netif Network interface info + * @param msecs Time value to set + * @param handler Callback function to call + * @param busy_flag Pointer to flag that displays if the timer is running or not. + */ +void +mdns_set_timeout(struct netif *netif, u32_t msecs, sys_timeout_handler handler, + u8_t *busy_flag) +{ + if(*busy_flag) { + /* restart timer */ + sys_untimeout(handler, netif); + sys_timeout(msecs, handler, netif); + } + else { + /* start timer */ + sys_timeout(msecs, handler, netif); + } + /* Now we have a timer running */ + *busy_flag = 1; +} + #endif /* LWIP_MDNS_RESPONDER */ diff --git a/src/include/lwip/apps/mdns_domain.h b/src/include/lwip/apps/mdns_domain.h index a633fefc..941ce63c 100644 --- a/src/include/lwip/apps/mdns_domain.h +++ b/src/include/lwip/apps/mdns_domain.h @@ -54,8 +54,12 @@ err_t mdns_domain_add_label(struct mdns_domain *domain, const char *label, u8_t u16_t mdns_readname(struct pbuf *p, u16_t offset, struct mdns_domain *domain); void mdns_domain_debug_print(struct mdns_domain *domain); int mdns_domain_eq(struct mdns_domain *a, struct mdns_domain *b); +#if LWIP_IPV4 err_t mdns_build_reverse_v4_domain(struct mdns_domain *domain, const ip4_addr_t *addr); +#endif +#if LWIP_IPV6 err_t mdns_build_reverse_v6_domain(struct mdns_domain *domain, const ip6_addr_t *addr); +#endif err_t mdns_build_host_domain(struct mdns_domain *domain, struct mdns_host *mdns); err_t mdns_build_dnssd_domain(struct mdns_domain *domain); err_t mdns_build_service_domain(struct mdns_domain *domain, struct mdns_service *service, int include_name); diff --git a/src/include/lwip/apps/mdns_out.h b/src/include/lwip/apps/mdns_out.h index 2f46fa6f..f36eeeda 100644 --- a/src/include/lwip/apps/mdns_out.h +++ b/src/include/lwip/apps/mdns_out.h @@ -42,6 +42,7 @@ #include "lwip/apps/mdns_opts.h" #include "lwip/apps/mdns_priv.h" #include "lwip/netif.h" +#include "lwip/timeouts.h" #ifdef __cplusplus extern "C" { @@ -73,7 +74,42 @@ extern "C" { /* Lookup for text info on service instance */ #define REPLY_SERVICE_TXT 0x80 +/* RFC6762 section 6: + * To protect the network against excessive packet flooding due to software bugs + * or malicious attack, a Multicast DNS responder MUST NOT (except in the one + * special case of answering probe queries) multicast a record on a given + * interface until at least one second has elapsed since the last time that + * record was multicast on that particular interface. + */ +#define MDNS_MULTICAST_TIMEOUT 1000 + +/* RFC6762 section 5.4: + * When receiving a question with the unicast-response bit set, a responder + * SHOULD usually respond with a unicast packet directed back to the querier. + * However, if the responder has not multicast that record recently (within one + * quarter of its TTL), then the responder SHOULD instead multicast the response + * so as to keep all the peer caches up to date, and to permit passive conflict + * detection. + * -> we implement a stripped down version. Depending on a timeout of 30s + * (25% of 120s) all QU questions are send via multicast or unicast. + */ +#define MDNS_MULTICAST_TIMEOUT_25TTL 30000 + err_t mdns_send_outpacket(struct mdns_outmsg *msg, struct netif *netif); +void mdns_set_timeout(struct netif *netif, u32_t msecs, + sys_timeout_handler handler, u8_t *busy_flag); +#if LWIP_IPV4 +void mdns_multicast_timeout_reset_ipv4(void *arg); +void mdns_multicast_timeout_25ttl_reset_ipv4(void *arg); +void mdns_send_multicast_msg_delayed_ipv4(void *arg); +void mdns_send_unicast_msg_delayed_ipv4(void *arg); +#endif +#if LWIP_IPV6 +void mdns_multicast_timeout_reset_ipv6(void *arg); +void mdns_multicast_timeout_25ttl_reset_ipv6(void *arg); +void mdns_send_multicast_msg_delayed_ipv6(void *arg); +void mdns_send_unicast_msg_delayed_ipv6(void *arg); +#endif void mdns_prepare_txtdata(struct mdns_service *service); #endif /* LWIP_MDNS_RESPONDER */ diff --git a/src/include/lwip/apps/mdns_priv.h b/src/include/lwip/apps/mdns_priv.h index 984aa474..c9784466 100644 --- a/src/include/lwip/apps/mdns_priv.h +++ b/src/include/lwip/apps/mdns_priv.h @@ -92,18 +92,6 @@ struct mdns_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]; - /** Number of probes sent for the current name */ - u8_t probes_sent; - /** State in probing sequence */ - u8_t probing_state; -}; - /** mDNS output packet */ struct mdns_outpacket { /** Packet data */ @@ -134,11 +122,14 @@ struct mdns_outmsg { u16_t dest_port; /** 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 reply should be sent unicast (as requested) */ + u8_t unicast_reply_requested; /** If legacy query. (tx_id needed, and write * question again in reply before answer) */ u8_t legacy_query; + /** If the query is a probe msg we need to respond immediatly. Independent of + * the QU or QM flag. */ + u8_t probe_query_recv; /* Question bitmask for host information */ u8_t host_questions; /* Questions bitmask per service */ @@ -151,6 +142,43 @@ struct mdns_outmsg { u8_t serv_replies[MDNS_MAX_SERVICES]; }; +/** Delayed msg info */ +struct mdns_delayed_msg { + /** Timer state multicast */ + u8_t multicast_msg_waiting; + /** Multicast timeout on */ + u8_t multicast_timeout; + /** Output msg used for delayed multicast responses */ + struct mdns_outmsg delayed_msg_multicast; + /** Prefer multicast over unicast timeout -> 25% of TTL = we take 30s as + general delay. */ + u8_t multicast_timeout_25TTL; + /** Only send out new unicast message if previous was send */ + u8_t unicast_msg_in_use; + /** Output msg used for delayed unicast responses */ + struct mdns_outmsg delayed_msg_unicast; +}; + +/** Description of a host/netif */ +struct mdns_host { + /** Hostname */ + char name[MDNS_LABEL_MAXLEN + 1]; + /** Pointer to services */ + struct mdns_service *services[MDNS_MAX_SERVICES]; + /** Number of probes sent for the current name */ + u8_t probes_sent; + /** State in probing sequence */ + u8_t probing_state; +#if LWIP_IPV4 + /** delayed msg struct for IPv4 */ + struct mdns_delayed_msg ipv4; +#endif +#if LWIP_IPV6 + /** delayed msg struct for IPv6 */ + struct mdns_delayed_msg ipv6; +#endif +}; + #endif /* LWIP_MDNS_RESPONDER */ #ifdef __cplusplus