Webserver+3d print
Diff: cyclone_tcp/mdns/mdns_responder.c
- Revision:
- 0:8918a71cdbe9
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cyclone_tcp/mdns/mdns_responder.c Sat Feb 04 18:15:49 2017 +0000 @@ -0,0 +1,2350 @@ +/** + * @file mdns_responder.c + * @brief mDNS responder (Multicast DNS) + * + * @section License + * + * Copyright (C) 2010-2017 Oryx Embedded SARL. All rights reserved. + * + * This file is part of CycloneTCP Open. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * @author Oryx Embedded SARL (www.oryx-embedded.com) + * @version 1.7.6 + **/ + +//Switch to the appropriate trace level +#define TRACE_LEVEL MDNS_TRACE_LEVEL + +//Dependencies +#include <stdlib.h> +#include <ctype.h> +#include "core/net.h" +#include "mdns/mdns_responder.h" +#include "mdns/mdns_common.h" +#include "dns/dns_debug.h" +#include "dns_sd/dns_sd.h" +#include "str.h" +#include "debug.h" + +//Check TCP/IP stack configuration +#if (MDNS_RESPONDER_SUPPORT == ENABLED) + +//Tick counter to handle periodic operations +systime_t mdnsResponderTickCounter; + + +/** + * @brief Initialize settings with default values + * @param[out] settings Structure that contains mDNS responder settings + **/ + +void mdnsResponderGetDefaultSettings(MdnsResponderSettings *settings) +{ + //Use default interface + settings->interface = netGetDefaultInterface(); + + //Number of announcement packets + settings->numAnnouncements = MDNS_ANNOUNCE_NUM; + //TTL resource record + settings->ttl = MDNS_DEFAULT_RR_TTL; + //FSM state change event + settings->stateChangeEvent = NULL; +} + + +/** + * @brief mDNS responder initialization + * @param[in] context Pointer to the mDNS responder context + * @param[in] settings mDNS responder specific settings + * @return Error code + **/ + +error_t mdnsResponderInit(MdnsResponderContext *context, + const MdnsResponderSettings *settings) +{ + NetInterface *interface; + + //Debug message + TRACE_INFO("Initializing mDNS responder...\r\n"); + + //Ensure the parameters are valid + if(context == NULL || settings == NULL) + return ERROR_INVALID_PARAMETER; + + //Invalid network interface? + if(settings->interface == NULL) + return ERROR_INVALID_PARAMETER; + + //Point to the underlying network interface + interface = settings->interface; + + //Clear the mDNS responder context + memset(context, 0, sizeof(MdnsResponderContext)); + //Save user settings + context->settings = *settings; + + //mDNS responder is currently suspended + context->running = FALSE; + //Initialize state machine + context->state = MDNS_STATE_INIT; + + //Attach the mDNS responder context to the network interface + interface->mdnsResponderContext = context; + + //Successful initialization + return NO_ERROR; +} + + +/** + * @brief Start mDNS responder + * @param[in] context Pointer to the mDNS responder context + * @return Error code + **/ + +error_t mdnsResponderStart(MdnsResponderContext *context) +{ + //Check parameter + if(context == NULL) + return ERROR_INVALID_PARAMETER; + + //Debug message + TRACE_INFO("Starting mDNS responder...\r\n"); + + //Get exclusive access + osAcquireMutex(&netMutex); + + //Start mDNS responder + context->running = TRUE; + //Initialize state machine + context->state = MDNS_STATE_INIT; + + //Release exclusive access + osReleaseMutex(&netMutex); + + //Successful processing + return NO_ERROR; +} + + +/** + * @brief Stop mDNS responder + * @param[in] context Pointer to the mDNS responder context + * @return Error code + **/ + +error_t mdnsResponderStop(MdnsResponderContext *context) +{ + //Check parameter + if(context == NULL) + return ERROR_INVALID_PARAMETER; + + //Debug message + TRACE_INFO("Stopping mDNS responder...\r\n"); + + //Get exclusive access + osAcquireMutex(&netMutex); + + //Suspend mDNS responder + context->running = FALSE; + //Reinitialize state machine + context->state = MDNS_STATE_INIT; + + //Release exclusive access + osReleaseMutex(&netMutex); + + //Successful processing + return NO_ERROR; +} + + +/** + * @brief Retrieve current state + * @param[in] context Pointer to the mDNS responder context + * @return Current mDNS responder state + **/ + +MdnsState mdnsResponderGetState(MdnsResponderContext *context) +{ + MdnsState state; + + //Get exclusive access + osAcquireMutex(&netMutex); + //Get current state + state = context->state; + //Release exclusive access + osReleaseMutex(&netMutex); + + //Return current state + return state; +} + + +/** + * @brief Set hostname + * @param[in] context Pointer to the mDNS responder context + * @param[in] hostname NULL-terminated string that contains the hostname + * @return Error code + **/ + +error_t mdnsResponderSetHostname(MdnsResponderContext *context, + const char_t *hostname) +{ + NetInterface *interface; + + //Check parameters + if(context == NULL || hostname == NULL) + return ERROR_INVALID_PARAMETER; + + //Get exclusive access + osAcquireMutex(&netMutex); + + //Point to the underlying network interface + interface = context->settings.interface; + + //Check whether a hostname is already assigned + if(context->hostname[0] != '\0') + { + //Check whether the link is up + if(interface->linkState) + { + //Send a goodbye packet + mdnsResponderSendGoodbye(context); + } + } + + //Set hostname + strSafeCopy(context->hostname, hostname, + MDNS_RESPONDER_MAX_HOSTNAME_LEN); + + //Restart probing process (hostname) + mdnsResponderStartProbing(interface->mdnsResponderContext); + +#if (DNS_SD_SUPPORT == ENABLED) + //Restart probing process (service instance name) + dnsSdStartProbing(interface->dnsSdContext); +#endif + + //Release exclusive access + osReleaseMutex(&netMutex); + + //Successful processing + return NO_ERROR; +} + + +/** + * @brief Generate domain name for reverse DNS lookup (IPv4) + * @param[in] context Pointer to the mDNS responder context + * @return Error code + **/ + +error_t mdnsResponderSetIpv4ReverseName(MdnsResponderContext *context) +{ +#if (IPV4_SUPPORT == ENABLED) + uint8_t *addr; + NetInterface *interface; + + //Check whether the mDNS responder has been properly instantiated + if(context == NULL) + return ERROR_INVALID_PARAMETER; + + //Point to the underlying network interface + interface = context->settings.interface; + + //Check whether the host address is valid + if(interface->ipv4Context.addrState == IPV4_ADDR_STATE_VALID) + { + //Cast the IPv4 address as byte array + addr = (uint8_t *) &interface->ipv4Context.addr; + + //Generate the domain name for reverse DNS lookup + sprintf(context->ipv4ReverseName, "%" PRIu8 ".%" PRIu8 ".%" PRIu8 ".%" PRIu8, + addr[3], addr[2], addr[1], addr[0]); + } + else + { + //The host address is not valid + context->ipv4ReverseName[0] = '\0'; + } +#endif + + //Successful processing + return NO_ERROR; +} + + +/** + * @brief Generate domain name for reverse DNS lookup (IPv6) + * @param[in] context Pointer to the mDNS responder context + * @return Error code + **/ + +error_t mdnsResponderSetIpv6ReverseName(MdnsResponderContext *context) +{ +#if (IPV6_SUPPORT == ENABLED) + uint_t i; + uint_t m; + uint_t n; + char_t *p; + uint8_t *addr; + NetInterface *interface; + + //Check whether the mDNS responder has been properly instantiated + if(context == NULL) + return ERROR_INVALID_PARAMETER; + + //Point to the underlying network interface + interface = context->settings.interface; + + //Point to the buffer where to format the reverse name + p = context->ipv6ReverseName; + + //Check whether the link-local address is valid + if(ipv6GetLinkLocalAddrState(interface) == IPV6_ADDR_STATE_PREFERRED) + { + //Cast the IPv4 address as byte array + addr = interface->ipv6Context.addrList[0].addr.b; + + //Generate the domain name for reverse DNS lookup + for(i = 0; i < 32; i++) + { + //Calculate the shift count + n = (31 - i) / 2; + m = (i % 2) * 4; + + //Format the current digit + p += sprintf(p, "%" PRIx8, (addr[n] >> m) & 0x0F); + + //Add a delimiter character + if(i != 31) + p += sprintf(p, "."); + } + } + else + { + //The link-local address is not valid + p[0] = '\0'; + } +#endif + + //Successful processing + return NO_ERROR; +} + + +/** + * @brief Restart probing process + * @param[in] context Pointer to the mDNS responder context + * @return Error code + **/ + +error_t mdnsResponderStartProbing(MdnsResponderContext *context) +{ + //Check whether the mDNS responder has been properly instantiated + if(context == NULL) + return ERROR_INVALID_PARAMETER; + + //Generate domain names for reverse DNS lookup + mdnsResponderSetIpv4ReverseName(context); + mdnsResponderSetIpv6ReverseName(context); + + //Force mDNS responder to start probing again + context->state = MDNS_STATE_INIT; + + //Successful processing + return NO_ERROR; +} + + +/** + * @brief mDNS responder timer handler + * + * This routine must be periodically called by the TCP/IP stack to + * manage mDNS operation + * + * @param[in] context Pointer to the mDNS responder context + **/ + +void mdnsResponderTick(MdnsResponderContext *context) +{ + bool_t valid; + systime_t time; + systime_t delay; + NetInterface *interface; + IpAddr destIpAddr; + + //Make sure the mDNS responder has been properly instantiated + if(context == NULL) + return; + + //Point to the underlying network interface + interface = context->settings.interface; + + //Get current time + time = osGetSystemTime(); + + //Check current state + if(context->state == MDNS_STATE_INIT) + { + //Check whether a hostname has been assigned + if(context->hostname[0] != '\0') + { + //Make sure that the link is up + if(interface->linkState) + { + //Clear flag + valid = FALSE; + +#if (IPV4_SUPPORT == ENABLED) + //Check whether the IPv4 host address is valid + if(interface->ipv4Context.addrState == IPV4_ADDR_STATE_VALID) + valid = TRUE; +#endif + +#if (IPV6_SUPPORT == ENABLED) + //Check whether the IPv6 link-local address is valid + if(ipv6GetLinkLocalAddrState(interface) == IPV6_ADDR_STATE_PREFERRED) + valid = TRUE; +#endif + //Any valid IP address assigned to the network interface? + if(valid) + { + //Wait until both IPv4 and IPv6 addresses are valid + mdnsResponderChangeState(context, MDNS_STATE_WAITING, 0); + } + } + } + } + else if(context->state == MDNS_STATE_WAITING) + { + //Set flag + valid = TRUE; + + //Check current time + if(timeCompare(time, context->timestamp + MDNS_MAX_WAITING_DELAY) < 0) + { +#if (IPV4_SUPPORT == ENABLED) + //Check whether the IPv4 host address is valid + if(interface->ipv4Context.addrState != IPV4_ADDR_STATE_VALID) + valid = FALSE; +#endif + +#if (IPV6_SUPPORT == ENABLED) + //Check whether the IPv6 link-local address is valid + if(ipv6GetLinkLocalAddrState(interface) != IPV6_ADDR_STATE_PREFERRED) + valid = FALSE; +#endif + } + + //Start probing? + if(valid) + { + //Initial random delay + delay = netGetRandRange(MDNS_RAND_DELAY_MIN, MDNS_RAND_DELAY_MAX); + //Perform probing + mdnsResponderChangeState(context, MDNS_STATE_PROBING, delay); + } + } + else if(context->state == MDNS_STATE_PROBING) + { + //Probing failed? + if(context->conflict && context->retransmitCount > 0) + { + //Programmatically change the host name + mdnsResponderChangeHostname(context); + //Probe again, and repeat as necessary until a unique name is found + mdnsResponderChangeState(context, MDNS_STATE_PROBING, 0); + } + //Tie-break lost? + else if(context->tieBreakLost && context->retransmitCount > 0) + { + //The host defers to the winning host by waiting one second, and + //then begins probing for this record again + mdnsResponderChangeState(context, MDNS_STATE_PROBING, MDNS_PROBE_DEFER); + } + else + { + //Check current time + if(timeCompare(time, context->timestamp + context->timeout) >= 0) + { + //Probing is on-going? + if(context->retransmitCount < MDNS_PROBE_NUM) + { + //First probe? + if(context->retransmitCount == 0) + { + //Apparently conflicting mDNS responses received before the + //first probe packet is sent must be silently ignored + context->conflict = FALSE; + context->tieBreakLost = FALSE; + } + + //Send probe packet + mdnsResponderSendProbe(context); + + //Save the time at which the packet was sent + context->timestamp = time; + //Time interval between subsequent probe packets + context->timeout = MDNS_PROBE_DELAY; + //Increment retransmission counter + context->retransmitCount++; + } + //Probing is complete? + else + { + //The mDNS responder must send unsolicited mDNS responses + //containing all of its newly registered resource records + if(context->settings.numAnnouncements > 0) + mdnsResponderChangeState(context, MDNS_STATE_ANNOUNCING, 0); + else + mdnsResponderChangeState(context, MDNS_STATE_IDLE, 0); + } + } + } + } + else if(context->state == MDNS_STATE_ANNOUNCING) + { + //Whenever a mDNS responder receives any mDNS response (solicited or + //otherwise) containing a conflicting resource record, the conflict + //must be resolved + if(context->conflict) + { + //Probe again, and repeat as necessary until a unique name is found + mdnsResponderChangeState(context, MDNS_STATE_PROBING, 0); + } + else + { + //Check current time + if(timeCompare(time, context->timestamp + context->timeout) >= 0) + { + //Send announcement packet + mdnsResponderSendAnnouncement(context); + + //Save the time at which the packet was sent + context->timestamp = time; + //Increment retransmission counter + context->retransmitCount++; + + //First announcement packet? + if(context->retransmitCount == 1) + { + //The mDNS responder must send at least two unsolicited + //responses, one second apart + context->timeout = MDNS_ANNOUNCE_DELAY; + } + else + { + //To provide increased robustness against packet loss, a mDNS + //responder may send up to eight unsolicited responses, provided + //that the interval between unsolicited responses increases by + //at least a factor of two with every response sent + context->timeout *= 2; + } + + //Last announcement packet? + if(context->retransmitCount >= context->settings.numAnnouncements) + { + //A mDNS responder must not send regular periodic announcements + mdnsResponderChangeState(context, MDNS_STATE_IDLE, 0); + } + } + } + } + else if(context->state == MDNS_STATE_IDLE) + { + //Whenever a mDNS responder receives any mDNS response (solicited or + //otherwise) containing a conflicting resource record, the conflict + //must be resolved + if(context->conflict) + { + //Probe again, and repeat as necessary until a unique name is found + mdnsResponderChangeState(context, MDNS_STATE_PROBING, 0); + } + } + +#if (IPV4_SUPPORT == ENABLED) + //Any response message pending to be sent? + if(context->ipv4Response.buffer != NULL) + { + //Check whether the time delay has elapsed + if(timeCompare(time, context->ipv4Response.timestamp + + context->ipv4Response.timeout) >= 0) + { +#if (DNS_SD_SUPPORT == ENABLED) + //Additional record generation (DNS-SD) + dnsSdGenerateAdditionalRecords(interface, + &context->ipv4Response, FALSE); +#endif + //Additional record generation (mDNS) + mdnsResponderGenerateAdditionalRecords(interface, + &context->ipv4Response, FALSE); + + //Use mDNS IPv4 multicast address + destIpAddr.length = sizeof(Ipv4Addr); + destIpAddr.ipv4Addr = MDNS_IPV4_MULTICAST_ADDR; + + //Send mDNS response message + mdnsSendMessage(interface, &context->ipv4Response, + &destIpAddr, MDNS_PORT); + + //Free previously allocated memory + mdnsDeleteMessage(&context->ipv4Response); + } + } +#endif + +#if (IPV6_SUPPORT == ENABLED) + //Any response message pending to be sent? + if(context->ipv6Response.buffer != NULL) + { + //Check whether the time delay has elapsed + if(timeCompare(time, context->ipv6Response.timestamp + + context->ipv6Response.timeout) >= 0) + { +#if (DNS_SD_SUPPORT == ENABLED) + //Additional record generation (DNS-SD) + dnsSdGenerateAdditionalRecords(interface, + &context->ipv6Response, FALSE); +#endif + //Additional record generation (mDNS) + mdnsResponderGenerateAdditionalRecords(interface, + &context->ipv6Response, FALSE); + + //Use mDNS IPv6 multicast address + destIpAddr.length = sizeof(Ipv6Addr); + destIpAddr.ipv6Addr = MDNS_IPV6_MULTICAST_ADDR; + + //Send mDNS response message + mdnsSendMessage(interface, &context->ipv6Response, + &destIpAddr, MDNS_PORT); + + //Free previously allocated memory + mdnsDeleteMessage(&context->ipv6Response); + } + } +#endif +} + + +/** + * @brief Callback function for link change event + * @param[in] context Pointer to the mDNS responder context + **/ + +void mdnsResponderLinkChangeEvent(MdnsResponderContext *context) +{ + //Make sure the mDNS responder has been properly instantiated + if(context == NULL) + return; + +#if (IPV4_SUPPORT == ENABLED) + //Free any response message pending to be sent + mdnsDeleteMessage(&context->ipv4Response); +#endif + +#if (IPV6_SUPPORT == ENABLED) + //Free any response message pending to be sent + mdnsDeleteMessage(&context->ipv6Response); +#endif + + //Whenever a mDNS responder receives an indication of a link + //change event, it must perform probing and announcing + mdnsResponderChangeState(context, MDNS_STATE_INIT, 0); +} + + +/** + * @brief Update FSM state + * @param[in] context Pointer to the mDNS responder context + * @param[in] newState New state to switch to + * @param[in] delay Initial delay + **/ + +void mdnsResponderChangeState(MdnsResponderContext *context, + MdnsState newState, systime_t delay) +{ + NetInterface *interface; + + //Point to the underlying network interface + interface = context->settings.interface; + + //Set time stamp + context->timestamp = osGetSystemTime(); + //Set initial delay + context->timeout = delay; + //Reset retransmission counter + context->retransmitCount = 0; + //Switch to the new state + context->state = newState; + + //Any registered callback? + if(context->settings.stateChangeEvent != NULL) + { + //Release exclusive access + osReleaseMutex(&netMutex); + //Invoke user callback function + context->settings.stateChangeEvent(context, interface, newState); + //Get exclusive access + osAcquireMutex(&netMutex); + } +} + + +/** + * @brief Programmatically change the host name + * @param[in] context Pointer to the mDNS responder context + **/ + +void mdnsResponderChangeHostname(MdnsResponderContext *context) +{ + size_t i; + size_t m; + size_t n; + uint32_t index; + char_t s[16]; + + //Retrieve the length of the string + n = strlen(context->hostname); + + //Parse the string backwards + for(i = n; i > 0; i--) + { + //Check whether the current character is a digit + if(!isdigit((uint8_t) context->hostname[i - 1])) + break; + } + + //Any number following the host name? + if(context->hostname[i] != '\0') + { + //Retrieve the number at the end of the name + index = atoi(context->hostname + i); + //Increment the value + index++; + + //Strip the digits + context->hostname[i] = '\0'; + } + else + { + //Append the digit "2" to the name + index = 2; + } + + //Convert the number to a string of characters + m = sprintf(s, "%" PRIu32, index); + + //Sanity check + if((i + m) <= NET_MAX_HOSTNAME_LEN) + { + //Add padding if necessary + while((i + m) < n) + context->hostname[i++] = '0'; + + //Properly terminate the string + context->hostname[i] = '\0'; + //Programmatically change the host name + strcat(context->hostname, s); + } +} + + +/** + * @brief Send probe packet + * @param[in] context Pointer to the mDNS responder context + * @return Error code + **/ + +error_t mdnsResponderSendProbe(MdnsResponderContext *context) +{ + error_t error; + NetInterface *interface; + DnsQuestion *dnsQuestion; + MdnsMessage message; + + //Point to the underlying network interface + interface = context->settings.interface; + + //Create an empty mDNS query message + error = mdnsCreateMessage(&message, FALSE); + //Any error to report? + if(error) + return error; + + //Start of exception handling block + do + { + //Encode the host name using the DNS name notation + message.length += mdnsEncodeName(context->hostname, "", + ".local", message.dnsHeader->questions); + + //Point to the corresponding question structure + dnsQuestion = DNS_GET_QUESTION(message.dnsHeader, message.length); + + //The probes should be sent as QU questions with the unicast-response + //bit set, to allow a defending host to respond immediately via unicast + dnsQuestion->qtype = HTONS(DNS_RR_TYPE_ANY); + dnsQuestion->qclass = HTONS(MDNS_QCLASS_QU | DNS_RR_CLASS_IN); + + //Update the length of the mDNS query message + message.length += sizeof(DnsQuestion); + + //Format A resource record + error = mdnsResponderAddIpv4AddrRecord(interface, + &message, FALSE, MDNS_DEFAULT_RR_TTL); + //Any error to report? + if(error) + break; + + //Format AAAA resource record + error = mdnsResponderAddIpv6AddrRecord(interface, + &message, FALSE, MDNS_DEFAULT_RR_TTL); + //Any error to report? + if(error) + break; + + //Number of questions in the Question Section + message.dnsHeader->qdcount = 1; + //Number of resource records in the Authority Section + message.dnsHeader->nscount = message.dnsHeader->ancount; + //Number of resource records in the Answer Section + message.dnsHeader->ancount = 0; + + //Send mDNS message + error = mdnsSendMessage(interface, &message, NULL, MDNS_PORT); + + //End of exception handling block + } while(0); + + //Free previously allocated memory + mdnsDeleteMessage(&message); + + //Return status code + return error; +} + + +/** + * @brief Send announcement packet + * @param[in] context Pointer to the mDNS responder context + * @return Error code + **/ + +error_t mdnsResponderSendAnnouncement(MdnsResponderContext *context) +{ + error_t error; + NetInterface *interface; + MdnsMessage message; + + //Point to the underlying network interface + interface = context->settings.interface; + + //Create an empty mDNS response message + error = mdnsCreateMessage(&message, TRUE); + //Any error to report? + if(error) + return error; + + //Start of exception handling block + do + { + //Format A resource record + error = mdnsResponderAddIpv4AddrRecord(interface, + &message, TRUE, MDNS_DEFAULT_RR_TTL); + //Any error to report? + if(error) + break; + + //Format reverse address mapping PTR record (IPv4) + error = mdnsResponderAddIpv4ReversePtrRecord(interface, + &message, TRUE, MDNS_DEFAULT_RR_TTL); + //Any error to report? + if(error) + break; + + //Format AAAA resource record + error = mdnsResponderAddIpv6AddrRecord(interface, + &message, TRUE, MDNS_DEFAULT_RR_TTL); + //Any error to report? + if(error) + break; + + //Format reverse address mapping PTR record (IPv6) + error = mdnsResponderAddIpv6ReversePtrRecord(interface, + &message, TRUE, MDNS_DEFAULT_RR_TTL); + //Any error to report? + if(error) + break; + + //Send mDNS message + error = mdnsSendMessage(interface, &message, NULL, MDNS_PORT); + + //End of exception handling block + } while(0); + + //Free previously allocated memory + mdnsDeleteMessage(&message); + + //Return status code + return error; +} + + +/** + * @brief Send goodbye packet + * @param[in] context Pointer to the mDNS responder context + * @return Error code + **/ + +error_t mdnsResponderSendGoodbye(MdnsResponderContext *context) +{ + error_t error; + NetInterface *interface; + MdnsMessage message; + + //Point to the underlying network interface + interface = context->settings.interface; + + //Create an empty mDNS response message + error = mdnsCreateMessage(&message, TRUE); + //Any error to report? + if(error) + return error; + + //Start of exception handling block + do + { + //Format A resource record + error = mdnsResponderAddIpv4AddrRecord(interface, &message, TRUE, 0); + //Any error to report? + if(error) + break; + + //Format reverse address mapping PTR record (IPv4) + error = mdnsResponderAddIpv4ReversePtrRecord(interface, &message, TRUE, 0); + //Any error to report? + if(error) + break; + + //Format AAAA resource record + error = mdnsResponderAddIpv6AddrRecord(interface, &message, TRUE, 0); + //Any error to report? + if(error) + break; + + //Format reverse address mapping PTR record (IPv6) + error = mdnsResponderAddIpv6ReversePtrRecord(interface, &message, TRUE, 0); + //Any error to report? + if(error) + break; + + //Send mDNS message + error = mdnsSendMessage(interface, &message, NULL, MDNS_PORT); + + //End of exception handling block + } while(0); + + //Free previously allocated memory + mdnsDeleteMessage(&message); + + //Return status code + return error; +} + + +/** + * @brief Process mDNS query message + * @param[in] interface Underlying network interface + * @param[in] query Incoming mDNS query message + **/ + +void mdnsResponderProcessQuery(NetInterface *interface, MdnsMessage *query) +{ + error_t error; + uint_t i; + size_t n; + size_t offset; + DnsQuestion *question; + DnsResourceRecord *record; + MdnsResponderContext *context; + MdnsMessage *response; + uint16_t destPort; + IpAddr destIpAddr; + + //Point to the mDNS responder context + context = interface->mdnsResponderContext; + //Make sure the mDNS responder has been properly instantiated + if(context == NULL) + return; + +#if (IPV4_SUPPORT == ENABLED) + //IPv4 query received? + if(query->pseudoHeader->length == sizeof(Ipv4PseudoHeader)) + { + //If the source UDP port in a received Multicast DNS query is not port 5353, + //this indicates that the querier originating the query is a simple resolver + if(ntohs(query->udpHeader->srcPort) != MDNS_PORT) + { + //The mDNS responder must send a UDP response directly back to the querier, + //via unicast, to the query packet's source IP address and port + destIpAddr.length = sizeof(Ipv4Addr); + destIpAddr.ipv4Addr = query->pseudoHeader->ipv4Data.srcAddr; + } + else + { + //Use mDNS IPv4 multicast address + destIpAddr.length = sizeof(Ipv4Addr); + destIpAddr.ipv4Addr = MDNS_IPV4_MULTICAST_ADDR; + } + + //Point to the mDNS response message + response = &context->ipv4Response; + } + else +#endif +#if (IPV6_SUPPORT == ENABLED) + //IPv6 query received? + if(query->pseudoHeader->length == sizeof(Ipv6PseudoHeader)) + { + //If the source UDP port in a received Multicast DNS query is not port 5353, + //this indicates that the querier originating the query is a simple resolver + if(ntohs(query->udpHeader->srcPort) != MDNS_PORT) + { + //The mDNS responder must send a UDP response directly back to the querier, + //via unicast, to the query packet's source IP address and port + destIpAddr.length = sizeof(Ipv6Addr); + destIpAddr.ipv6Addr = query->pseudoHeader->ipv6Data.srcAddr; + } + else + { + //Use mDNS IPv6 multicast address + destIpAddr.length = sizeof(Ipv6Addr); + destIpAddr.ipv6Addr = MDNS_IPV6_MULTICAST_ADDR; + } + + //Point to the mDNS response message + response = &context->ipv6Response; + } + else +#endif + //Invalid query received? + { + //Discard the mDNS query message + return; + } + + //When possible, a responder should, for the sake of network + //efficiency, aggregate as many responses as possible into a + //single mDNS response message + if(response->buffer == NULL) + { + //Create an empty mDNS response message + error = mdnsCreateMessage(response, TRUE); + //Any error to report? + if(error) + return; + } + + //Take the identifier from the query message + response->dnsHeader->id = query->dnsHeader->id; + + //Point to the first question + offset = sizeof(DnsHeader); + + //Start of exception handling block + do + { + //Parse the Question Section + for(i = 0; i < ntohs(query->dnsHeader->qdcount); i++) + { + //Parse resource record name + n = dnsParseName(query->dnsHeader, query->length, offset, NULL, 0); + //Invalid name? + if(!n) + break; + //Malformed mDNS message? + if((n + sizeof(DnsQuestion)) > query->length) + break; + + //Point to the corresponding entry + question = DNS_GET_QUESTION(query->dnsHeader, n); + + //Parse question + error = mdnsResponderParseQuestion(interface, query, + offset, question, response); + //Any error to report? + if(error) + break; + +#if (DNS_SD_SUPPORT == ENABLED) + //Parse resource record + error = dnsSdParseQuestion(interface, query, offset, + question, response); + //Any error to report? + if(error) + break; +#endif + //Point to the next question + offset = n + sizeof(DnsQuestion); + } + + //Any error while parsing the Question Section? + if(i != ntohs(query->dnsHeader->qdcount)) + break; + + //Parse the Known-Answer Section + for(i = 0; i < ntohs(query->dnsHeader->ancount); i++) + { + //Parse resource record name + n = dnsParseName(query->dnsHeader, query->length, offset, NULL, 0); + //Invalid name? + if(!n) + break; + + //Point to the associated resource record + record = DNS_GET_RESOURCE_RECORD(query->dnsHeader, n); + //Point to the resource data + n += sizeof(DnsResourceRecord); + + //Make sure the resource record is valid + if(n > query->length) + break; + if((n + ntohs(record->rdlength)) > query->length) + break; + + //Parse resource record + mdnsResponderParseKnownAnRecord(interface, query, offset, + record, response); + + //Point to the next resource record + offset = n + ntohs(record->rdlength); + } + + //Any error while parsing the Answer Section? + if(i != ntohs(query->dnsHeader->ancount)) + break; + + //Parse Authority Section + for(i = 0; i < ntohs(query->dnsHeader->nscount); i++) + { + //Parse resource record name + n = dnsParseName(query->dnsHeader, query->length, offset, NULL, 0); + //Invalid name? + if(!n) + break; + + //Point to the associated resource record + record = DNS_GET_RESOURCE_RECORD(query->dnsHeader, n); + //Point to the resource data + n += sizeof(DnsResourceRecord); + + //Make sure the resource record is valid + if(n > query->length) + break; + if((n + ntohs(record->rdlength)) > query->length) + break; + + //Check for host name conflict + mdnsResponderParseNsRecord(interface, query, offset, record); + +#if (DNS_SD_SUPPORT == ENABLED) + //Check for service instance name conflict + dnsSdParseNsRecord(interface, query, offset, record); +#endif + //Point to the next resource record + offset = n + ntohs(record->rdlength); + } + + //Any error while parsing the Authority Section? + if(i != ntohs(query->dnsHeader->nscount)) + break; + + //End of exception handling block + } while(0); + + //Should a mDNS message be send in response to the query? + if(response->dnsHeader->ancount > 0) + { + //If the source UDP port in a received Multicast DNS query is not port 5353, + //this indicates that the querier originating the query is a simple resolver + if(ntohs(query->udpHeader->srcPort) != MDNS_PORT) + { +#if (DNS_SD_SUPPORT == ENABLED) + //Additional record generation (DNS-SD) + dnsSdGenerateAdditionalRecords(interface, response, TRUE); +#endif + //Additional record generation (mDNS) + mdnsResponderGenerateAdditionalRecords(interface, response, TRUE); + + //Destination port + destPort = ntohs(query->udpHeader->srcPort); + + //Send mDNS response message + mdnsSendMessage(interface, response, &destIpAddr, destPort); + //Free previously allocated memory + mdnsDeleteMessage(response); + } + else + { + //Check whether the answer should be delayed + if(query->dnsHeader->tc) + { + //In the case where the query has the TC (truncated) bit set, indicating + //that subsequent Known-Answer packets will follow, responders should + //delay their responses by a random amount of time selected with uniform + //random distribution in the range 400-500 ms + response->timeout = netGetRandRange(400, 500); + + //Save current time + response->timestamp = osGetSystemTime(); + } + else if(response->sharedRecordCount > 0) + { + //In any case where there may be multiple responses, such as queries + //where the answer is a member of a shared resource record set, each + //responder should delay its response by a random amount of time + //selected with uniform random distribution in the range 20-120 ms + response->timeout = netGetRandRange(20, 120); + + //Save current time + response->timestamp = osGetSystemTime(); + } + else + { +#if (DNS_SD_SUPPORT == ENABLED) + //Additional record generation (refer to RFC 6763 section 12) + dnsSdGenerateAdditionalRecords(interface, response, FALSE); +#endif + //Additional record generation (mDNS) + mdnsResponderGenerateAdditionalRecords(interface, response, FALSE); + + //Send mDNS response message + mdnsSendMessage(interface, response, &destIpAddr, MDNS_PORT); + //Free previously allocated memory + mdnsDeleteMessage(response); + } + } + } + else + { + //Free mDNS response message + mdnsDeleteMessage(response); + } +} + + +/** + * @brief Parse a question + * @param[in] interface Underlying network interface + * @param[in] query Incoming mDNS query message + * @param[in] offset Offset to first byte of the question + * @param[in] question Pointer to the question + * @param[in,out] response mDNS response message + * @return Error code + **/ + +error_t mdnsResponderParseQuestion(NetInterface *interface, const MdnsMessage *query, + size_t offset, const DnsQuestion *question, MdnsMessage *response) +{ + error_t error; + uint16_t qclass; + uint16_t qtype; + uint32_t ttl; + bool_t cacheFlush; + MdnsResponderContext *context; + + //Point to the mDNS responder context + context = interface->mdnsResponderContext; + + //Check the state of the mDNS responder + if(context->state != MDNS_STATE_ANNOUNCING && + context->state != MDNS_STATE_IDLE) + { + //Do not respond to mDNS queries during probing + return NO_ERROR; + } + + //Convert the query class to host byte order + qclass = ntohs(question->qclass); + //Discard QU flag + qclass &= ~MDNS_QCLASS_QU; + + //Convert the query type to host byte order + qtype = ntohs(question->qtype); + + //Get the TTL resource record + ttl = context->settings.ttl; + + //Check whether the querier originating the query is a simple resolver + if(ntohs(query->udpHeader->srcPort) != MDNS_PORT) + { + //The resource record TTL given in a legacy unicast response should + //not be greater than ten seconds, even if the true TTL of the mDNS + //resource record is higher + ttl = MIN(ttl, MDNS_LEGACY_UNICAST_RR_TTL); + + //The cache-flush bit must not be set in legacy unicast responses + cacheFlush = FALSE; + } + else + { + //The cache-bit should be set for unique resource records + cacheFlush = TRUE; + } + + //Check the class of the query + if(qclass == DNS_RR_CLASS_IN || qclass == DNS_RR_CLASS_ANY) + { + //Compare domain name + if(!mdnsCompareName(query->dnsHeader, query->length, + offset, context->hostname, "", ".local", 0)) + { +#if (IPV4_SUPPORT == ENABLED) + //A query? + if(qtype == DNS_RR_TYPE_A) + { + //Format A resource record + error = mdnsResponderAddIpv4AddrRecord(interface, + response, cacheFlush, ttl); + //Any error to report? + if(error) + return error; + } + else +#endif +#if (IPV6_SUPPORT == ENABLED) + //AAAA query? + if(qtype == DNS_RR_TYPE_AAAA) + { + //Format AAAA resource record + error = mdnsResponderAddIpv6AddrRecord(interface, + response, cacheFlush, ttl); + //Any error to report? + if(error) + return error; + } + else +#endif + //ANY query? + if(qtype == DNS_RR_TYPE_ANY) + { + //Format A resource record + error = mdnsResponderAddIpv4AddrRecord(interface, + response, cacheFlush, ttl); + //Any error to report? + if(error) + return error; + + //Format AAAA resource record + error = mdnsResponderAddIpv6AddrRecord(interface, + response, cacheFlush, ttl); + //Any error to report? + if(error) + return error; + + //Format NSEC resource record + error = mdnsResponderAddNsecRecord(interface, + response, cacheFlush, ttl); + //Any error to report? + if(error) + return error; + } + else + { + //Format NSEC resource record + error = mdnsResponderAddNsecRecord(interface, + response, cacheFlush, ttl); + //Any error to report? + if(error) + return error; + } + } + +#if (IPV4_SUPPORT == ENABLED) + //Reverse DNS lookup? + if(!mdnsCompareName(query->dnsHeader, query->length, + offset, context->ipv4ReverseName, "in-addr", ".arpa", 0)) + { + //PTR query? + if(qtype == DNS_RR_TYPE_PTR || qtype == DNS_RR_TYPE_ANY) + { + //Format reverse address mapping PTR record (IPv4) + error = mdnsResponderAddIpv4ReversePtrRecord(interface, + response, cacheFlush, ttl); + //Any error to report? + if(error) + return error; + } + } +#endif +#if (IPV6_SUPPORT == ENABLED) + //Reverse DNS lookup? + if(!mdnsCompareName(query->dnsHeader, query->length, + offset, context->ipv6ReverseName, "ip6", ".arpa", 0)) + { + //PTR query? + if(qtype == DNS_RR_TYPE_PTR || qtype == DNS_RR_TYPE_ANY) + { + //Format reverse address mapping PTR record (IPv6) + error = mdnsResponderAddIpv6ReversePtrRecord(interface, + response, cacheFlush, ttl); + //Any error to report? + if(error) + return error; + } + } +#endif + } + + //Successful processing + return NO_ERROR; +} + + +/** + * @brief Parse a resource record from the Known-Answer Section + * @param[in] interface Underlying network interface + * @param[in] query Incoming mDNS query message + * @param[in] queryOffset Offset to first byte of the resource record + * @param[in] queryRecord Pointer to the resource record + * @param[in,out] response mDNS response message + **/ + +void mdnsResponderParseKnownAnRecord(NetInterface *interface, const MdnsMessage *query, + size_t queryOffset, const DnsResourceRecord *queryRecord, MdnsMessage *response) +{ + size_t i; + size_t n; + size_t responseOffset; + DnsResourceRecord *responseRecord; + + //mDNS responses must not contain any questions in the Question Section + if(response->dnsHeader->qdcount == 0) + { + //Point to the first resource record + responseOffset = sizeof(DnsHeader); + + //Parse the Answer Section of the response + for(i = 0; i < response->dnsHeader->ancount; i++) + { + //Parse resource record name + n = dnsParseName(response->dnsHeader, response->length, responseOffset, NULL, 0); + //Invalid name? + if(!n) + break; + + //Point to the associated resource record + responseRecord = DNS_GET_RESOURCE_RECORD(response->dnsHeader, n); + //Point to the resource data + n += sizeof(DnsResourceRecord); + + //Make sure the resource record is valid + if(n > response->length) + break; + + //Point to the end of the resource record + n += ntohs(responseRecord->rdlength); + + //Make sure the resource record is valid + if(n > response->length) + break; + + //Compare resource record names + if(!dnsCompareEncodedName(query->dnsHeader, query->length, queryOffset, + response->dnsHeader, response->length, responseOffset, 0)) + { + //Compare the contents of the resource records + if(!mdnsCompareRecord(query, queryOffset, queryRecord, + response, responseOffset, responseRecord)) + { + //A mDNS responder must not answer a mDNS query if the answer + //it would give is already included in the Answer Section with + //an RR TTL at least half the correct value + if(ntohl(queryRecord->ttl) >= (ntohl(responseRecord->ttl) / 2)) + { + //Perform Known-Answer Suppression + memmove((uint8_t *) response->dnsHeader + responseOffset, + (uint8_t *) response->dnsHeader + n, response->length - n); + + //Update the length of the mDNS response message + response->length -= (n - responseOffset); + //Update the number of resource records in the Answer Section + response->dnsHeader->ancount--; + + //Keep at the same position + n = responseOffset; + i--; + } + } + } + + //Point to the next resource record + responseOffset = n; + } + } +} + + +/** + * @brief Parse a resource record from the Authority Section + * @param[in] interface Underlying network interface + * @param[in] query Incoming mDNS query message + * @param[in] offset Offset to first byte of the resource record + * @param[in] record Pointer to the resource record + **/ + +void mdnsResponderParseNsRecord(NetInterface *interface, + const MdnsMessage *query, size_t offset, const DnsResourceRecord *record) +{ + bool_t tieBreakLost; + uint16_t rclass; + MdnsResponderContext *context; + + //Point to the mDNS responder context + context = interface->mdnsResponderContext; + + //When a host that is probing for a record sees another host issue a query + //for the same record, it consults the Authority Section of that query. + //If it finds any resource record there which answers the query, then it + //compares the data of that resource record with its own tentative data + if(!mdnsCompareName(query->dnsHeader, query->length, + offset, context->hostname, "", ".local", 0)) + { + //Convert the class to host byte order + rclass = ntohs(record->rclass); + //Discard Cache Flush flag + rclass &= ~MDNS_RCLASS_CACHE_FLUSH; + + //Check the class of the resource record + if(rclass == DNS_RR_CLASS_IN) + { +#if (IPV4_SUPPORT == ENABLED) + //A resource record found? + if(ntohs(record->rtype) == DNS_RR_TYPE_A) + { + //Apply tie-breaking rules + tieBreakLost = TRUE; + + //Verify the length of the data field + if(ntohs(record->rdlength) == sizeof(Ipv4Addr)) + { + //Valid host address? + if(interface->ipv4Context.addrState == IPV4_ADDR_STATE_VALID) + { + //The two records are compared and the lexicographically + //later data wins + if(memcmp(&interface->ipv4Context.addr, record->rdata, + sizeof(Ipv4Addr)) >= 0) + { + tieBreakLost = FALSE; + } + } + } + + //Check whether the host has lost the tie-break + if(tieBreakLost) + context->tieBreakLost = TRUE; + } +#endif +#if (IPV6_SUPPORT == ENABLED) + //AAAA resource record found? + if(ntohs(record->rtype) == DNS_RR_TYPE_AAAA) + { + //Apply tie-breaking rules + tieBreakLost = TRUE; + + //Verify the length of the data field + if(ntohs(record->rdlength) == sizeof(Ipv6Addr)) + { + uint_t i; + Ipv6AddrEntry *entry; + + //Loop through the list of IPv6 addresses assigned to the interface + for(i = 0; i < IPV6_ADDR_LIST_SIZE; i++) + { + //Point to the current entry + entry = &interface->ipv6Context.addrList[i]; + + //Valid IPv6 address + if(entry->state != IPV6_ADDR_STATE_INVALID) + { + //The two records are compared and the lexicographically + //later data wins + if(memcmp(&interface->ipv6Context.addrList[i].addr, + record->rdata, sizeof(Ipv6Addr)) >= 0) + { + tieBreakLost = FALSE; + } + } + } + } + + //Check whether the host has lost the tie-break + if(tieBreakLost) + context->tieBreakLost = TRUE; + } +#endif + } + } +} + + +/** + * @brief Parse a resource record from the Answer Section + * @param[in] interface Underlying network interface + * @param[in] response Incoming mDNS response message + * @param[in] offset Offset to first byte of the resource record to be checked + * @param[in] record Pointer to the resource record + **/ + +void mdnsResponderParseAnRecord(NetInterface *interface, + const MdnsMessage *response, size_t offset, const DnsResourceRecord *record) +{ + bool_t conflict; + uint16_t rclass; + MdnsResponderContext *context; + + //Point to the mDNS responder context + context = interface->mdnsResponderContext; + + //Check for conflicts + if(!mdnsCompareName(response->dnsHeader, response->length, + offset, context->hostname, "", ".local", 0)) + { + //Convert the class to host byte order + rclass = ntohs(record->rclass); + //Discard Cache Flush flag + rclass &= ~MDNS_RCLASS_CACHE_FLUSH; + + //Check the class of the resource record + if(rclass == DNS_RR_CLASS_IN) + { +#if (IPV4_SUPPORT == ENABLED) + //A resource record found? + if(ntohs(record->rtype) == DNS_RR_TYPE_A) + { + //A conflict occurs when a mDNS responder has a unique record for + //which it is currently authoritative, and it receives a mDNS + //response message containing a record with the same name, rrtype + //and rrclass, but inconsistent rdata + conflict = TRUE; + + //Verify the length of the data field + if(ntohs(record->rdlength) == sizeof(Ipv4Addr)) + { + //Valid host address? + if(interface->ipv4Context.addrState == IPV4_ADDR_STATE_VALID) + { + //Check whether the rdata field is consistent + if(ipv4CompAddr(&interface->ipv4Context.addr, record->rdata)) + context->conflict = FALSE; + } + } + + //Check whether the hostname is already in use by some other host + if(conflict) + context->conflict = TRUE; + } +#endif +#if (IPV6_SUPPORT == ENABLED) + //AAAA resource record found? + if(ntohs(record->rtype) == DNS_RR_TYPE_AAAA) + { + //A conflict occurs when a mDNS responder has a unique record for + //which it is currently authoritative, and it receives a mDNS + //response message containing a record with the same name, rrtype + //and rrclass, but inconsistent rdata + conflict = TRUE; + + //Verify the length of the data field + if(ntohs(record->rdlength) == sizeof(Ipv6Addr)) + { + uint_t i; + Ipv6AddrEntry *entry; + + //Loop through the list of IPv6 addresses assigned to the interface + for(i = 0; i < IPV6_ADDR_LIST_SIZE; i++) + { + //Point to the current entry + entry = &interface->ipv6Context.addrList[i]; + + //Valid IPv6 address + if(entry->state != IPV6_ADDR_STATE_INVALID) + { + //Check whether the rdata field is consistent + if(ipv6CompAddr(&interface->ipv6Context.addrList[i].addr, record->rdata)) + conflict = FALSE; + } + } + } + + //Check whether the hostname is already in use by some other host + if(conflict) + context->conflict = TRUE; + } +#endif + } + } +} + + +/** + * @brief Additional record generation + * @param[in] interface Underlying network interface + * @param[in,out] response mDNS response message + * @param[in] legacyUnicast This flag is set for legacy unicast responses + **/ + +void mdnsResponderGenerateAdditionalRecords(NetInterface *interface, + MdnsMessage *response, bool_t legacyUnicast) +{ + error_t error; + uint_t i; + uint_t k; + size_t n; + size_t offset; + uint_t ancount; + uint16_t rclass; + uint32_t ttl; + bool_t cacheFlush; + MdnsResponderContext *context; + DnsResourceRecord *record; + + //Point to the mDNS responder context + context = interface->mdnsResponderContext; + + //mDNS responses must not contain any questions in the Question Section + if(response->dnsHeader->qdcount != 0) + return; + + //Get the TTL resource record + ttl = context->settings.ttl; + + //Check whether the querier originating the query is a simple resolver + if(legacyUnicast) + { + //The resource record TTL given in a legacy unicast response should + //not be greater than ten seconds, even if the true TTL of the mDNS + //resource record is higher + ttl = MIN(ttl, MDNS_LEGACY_UNICAST_RR_TTL); + + //The cache-flush bit must not be set in legacy unicast responses + cacheFlush = FALSE; + } + else + { + //The cache-bit should be set for unique resource records + cacheFlush = TRUE; + } + + //Point to the first resource record + offset = sizeof(DnsHeader); + + //Save the number of resource records in the Answer Section + ancount = response->dnsHeader->ancount; + + //Compute the total number of resource records + k = response->dnsHeader->ancount + response->dnsHeader->nscount + + response->dnsHeader->arcount; + + //Loop through the resource records + for(i = 0; i < k; i++) + { + //Parse resource record name + n = dnsParseName(response->dnsHeader, response->length, offset, NULL, 0); + //Invalid name? + if(!n) + break; + + //Point to the associated resource record + record = DNS_GET_RESOURCE_RECORD(response->dnsHeader, n); + //Point to the resource data + n += sizeof(DnsResourceRecord); + + //Make sure the resource record is valid + if(n > response->length) + break; + if((n + ntohs(record->rdlength)) > response->length) + break; + + //Convert the record class to host byte order + rclass = ntohs(record->rclass); + //Discard the cache-flush bit + rclass &= ~MDNS_RCLASS_CACHE_FLUSH; + + //Check the class of the resource record + if(rclass == DNS_RR_CLASS_IN) + { + //A record? + if(ntohs(record->rtype) == DNS_RR_TYPE_A) + { +#if (IPV6_SUPPORT == ENABLED) + //When a mDNS responder places an IPv4 address record into a + //response message, it should also place any IPv6 address records + //with the same name into the Additional Section + error = mdnsResponderAddIpv6AddrRecord(interface, + response, cacheFlush, ttl); +#else + //In the event that a device has only IPv4 addresses but no IPv6 + //addresses, then the appropriate NSEC record should be placed + //into the Additional Section + error = mdnsResponderAddNsecRecord(interface, + response, cacheFlush, ttl); +#endif + //Any error to report? + if(error) + return; + } + //AAAA record? + else if(ntohs(record->rtype) == DNS_RR_TYPE_AAAA) + { +#if (IPV4_SUPPORT == ENABLED) + //When a mDNS responder places an IPv6 address record into a + //response message, it should also place any IPv4 address records + //with the same name into the Additional Section + error = mdnsResponderAddIpv4AddrRecord(interface, + response, cacheFlush, ttl); +#else + //In the event that a device has only IPv6 addresses but no IPv4 + //addresses, then the appropriate NSEC record should be placed + //into the Additional Section + error = mdnsResponderAddNsecRecord(interface, + response, cacheFlush, ttl); +#endif + //Any error to report? + if(error) + return; + } + //SRV record? + else if(ntohs(record->rtype) == DNS_RR_TYPE_SRV) + { + //Format A resource record + error = mdnsResponderAddIpv4AddrRecord(interface, + response, cacheFlush, ttl); + //Any error to report? + if(error) + return; + + //Format AAAA resource record + error = mdnsResponderAddIpv6AddrRecord(interface, + response, cacheFlush, ttl); + //Any error to report? + if(error) + return; + +#if (IPV4_SUPPORT == DISABLED || IPV6_SUPPORT == DISABLED) + //In the event that a device has only IPv4 addresses but no IPv6 + //addresses, or vice versa, then the appropriate NSEC record should + //be placed into the additional section, so that queriers can know + //with certainty that the device has no addresses of that kind + error = mdnsResponderAddNsecRecord(interface, + response, cacheFlush, ttl); + //Any error to report? + if(error) + return; +#endif + } + } + + //Point to the next resource record + offset = n + ntohs(record->rdlength); + } + + //Number of resource records in the Additional Section + response->dnsHeader->arcount += response->dnsHeader->ancount - ancount; + //Number of resource records in the Answer Section + response->dnsHeader->ancount = ancount; +} + + +/** + * @brief Add A record to a mDNS message + * @param[in] interface Underlying network interface + * @param[in,out] message Pointer to the mDNS message + * @param[in] cacheFlush Cache-flush bit + * @param[in] ttl Resource record TTL (cache lifetime) + * @return Error code + **/ + +error_t mdnsResponderAddIpv4AddrRecord(NetInterface *interface, + MdnsMessage *message, bool_t cacheFlush, uint32_t ttl) +{ +#if (IPV4_SUPPORT == ENABLED) + size_t n; + size_t offset; + bool_t duplicate; + MdnsResponderContext *context; + DnsResourceRecord *record; + + //Point to the mDNS responder context + context = interface->mdnsResponderContext; + + //Valid IPv4 host address? + if(interface->ipv4Context.addrState == IPV4_ADDR_STATE_VALID) + { + //Check whether the resource record is already present in the Answer + //Section of the message + duplicate = mdnsCheckDuplicateRecord(message, context->hostname, + "", ".local", DNS_RR_TYPE_A); + + //The duplicates should be suppressed and the resource record should + //appear only once in the list + if(!duplicate) + { + //Set the position to the end of the buffer + offset = message->length; + + //The first pass calculates the length of the DNS encoded host name + n = mdnsEncodeName(context->hostname, "", ".local", NULL); + + //Check the length of the resulting mDNS message + if((offset + n) > MDNS_MESSAGE_MAX_SIZE) + return ERROR_MESSAGE_TOO_LONG; + + //The second pass encodes the host name using the DNS name notation + offset += mdnsEncodeName(context->hostname, "", ".local", + (uint8_t *) message->dnsHeader + offset); + + //Consider the length of the resource record itself + n = sizeof(DnsResourceRecord) + sizeof(Ipv4Addr); + + //Check the length of the resulting mDNS message + if((offset + n) > MDNS_MESSAGE_MAX_SIZE) + return ERROR_MESSAGE_TOO_LONG; + + //Point to the corresponding resource record + record = DNS_GET_RESOURCE_RECORD(message->dnsHeader, offset); + + //Fill in resource record + record->rtype = HTONS(DNS_RR_TYPE_A); + record->rclass = HTONS(DNS_RR_CLASS_IN); + record->ttl = htonl(ttl); + record->rdlength = HTONS(sizeof(Ipv4Addr)); + + //Check whether the cache-flush bit should be set + if(cacheFlush) + record->rclass |= HTONS(MDNS_RCLASS_CACHE_FLUSH); + + //Copy IPv4 address + ipv4CopyAddr(record->rdata, &interface->ipv4Context.addr); + + //Number of resource records in the answer section + message->dnsHeader->ancount++; + //Update the length of the mDNS response message + message->length = offset + n; + } + } +#endif + + //Successful processing + return NO_ERROR; +} + + +/** + * @brief Add AAAA record to a mDNS message + * @param[in] interface Underlying network interface + * @param[in,out] message Pointer to the mDNS message + * @param[in] cacheFlush Cache-flush bit + * @param[in] ttl Resource record TTL (cache lifetime) + * @return Error code + **/ + +error_t mdnsResponderAddIpv6AddrRecord(NetInterface *interface, + MdnsMessage *message, bool_t cacheFlush, uint32_t ttl) +{ +#if (IPV6_SUPPORT == ENABLED) + size_t n; + size_t offset; + bool_t duplicate; + MdnsResponderContext *context; + DnsResourceRecord *record; + + //Point to the mDNS responder context + context = interface->mdnsResponderContext; + + //Valid IPv6 link-local address? + if(ipv6GetLinkLocalAddrState(interface) == IPV6_ADDR_STATE_PREFERRED) + { + //Check whether the resource record is already present in the Answer + //Section of the message + duplicate = mdnsCheckDuplicateRecord(message, context->hostname, + "", ".local", DNS_RR_TYPE_AAAA); + + //The duplicates should be suppressed and the resource record should + //appear only once in the list + if(!duplicate) + { + //Set the position to the end of the buffer + offset = message->length; + + //The first pass calculates the length of the DNS encoded host name + n = mdnsEncodeName(context->hostname, "", ".local", NULL); + + //Check the length of the resulting mDNS message + if((offset + n) > MDNS_MESSAGE_MAX_SIZE) + return ERROR_MESSAGE_TOO_LONG; + + //The second pass encodes the host name using the DNS name notation + offset += mdnsEncodeName(context->hostname, "", ".local", + (uint8_t *) message->dnsHeader + offset); + + //Consider the length of the resource record itself + n = sizeof(DnsResourceRecord) + sizeof(Ipv6Addr); + + //Check the length of the resulting mDNS message + if((offset + n) > MDNS_MESSAGE_MAX_SIZE) + return ERROR_MESSAGE_TOO_LONG; + + //Point to the corresponding resource record + record = DNS_GET_RESOURCE_RECORD(message->dnsHeader, offset); + + //Fill in resource record + record->rtype = HTONS(DNS_RR_TYPE_AAAA); + record->rclass = HTONS(DNS_RR_CLASS_IN); + record->ttl = htonl(ttl); + record->rdlength = HTONS(sizeof(Ipv6Addr)); + + //Check whether the cache-flush bit should be set + if(cacheFlush) + record->rclass |= HTONS(MDNS_RCLASS_CACHE_FLUSH); + + //Copy IPv6 address + ipv6CopyAddr(record->rdata, &interface->ipv6Context.addrList[0].addr); + + //Number of resource records in the answer section + message->dnsHeader->ancount++; + //Update the length of the mDNS response message + message->length = offset + n; + } + } +#endif + + //Successful processing + return NO_ERROR; +} + + +/** + * @brief Add reverse address mapping PTR record (IPv4) + * @param[in] interface Underlying network interface + * @param[in,out] message Pointer to the mDNS message + * @param[in] cacheFlush Cache-flush bit + * @param[in] ttl Resource record TTL (cache lifetime) + * @return Error code + **/ + +error_t mdnsResponderAddIpv4ReversePtrRecord(NetInterface *interface, + MdnsMessage *message, bool_t cacheFlush, uint32_t ttl) +{ +#if (IPV4_SUPPORT == ENABLED) + size_t n; + size_t offset; + bool_t duplicate; + MdnsResponderContext *context; + DnsResourceRecord *record; + + //Point to the mDNS responder context + context = interface->mdnsResponderContext; + + //Valid reverse name? + if(context->ipv4ReverseName[0] != '\0') + { + //Check whether the resource record is already present in the Answer + //Section of the message + duplicate = mdnsCheckDuplicateRecord(message, context->ipv4ReverseName, + "in-addr", ".arpa", DNS_RR_TYPE_PTR); + + //The duplicates should be suppressed and the resource record should + //appear only once in the list + if(!duplicate) + { + //Set the position to the end of the buffer + offset = message->length; + + //The first pass calculates the length of the DNS encoded reverse name + n = mdnsEncodeName(context->ipv4ReverseName, "in-addr", ".arpa", NULL); + + //Check the length of the resulting mDNS message + if((offset + n) > MDNS_MESSAGE_MAX_SIZE) + return ERROR_MESSAGE_TOO_LONG; + + //The second pass encodes the reverse name using the DNS name notation + offset += mdnsEncodeName(context->ipv4ReverseName, "in-addr", ".arpa", + (uint8_t *) message->dnsHeader + offset); + + //Consider the length of the resource record itself + n = sizeof(DnsResourceRecord) + sizeof(Ipv4Addr); + + //Check the length of the resulting mDNS message + if((offset + n) > MDNS_MESSAGE_MAX_SIZE) + return ERROR_MESSAGE_TOO_LONG; + + //Point to the corresponding resource record + record = DNS_GET_RESOURCE_RECORD(message->dnsHeader, offset); + + //Fill in resource record + record->rtype = HTONS(DNS_RR_TYPE_PTR); + record->rclass = HTONS(DNS_RR_CLASS_IN); + record->ttl = htonl(ttl); + + //Check whether the cache-flush bit should be set + if(cacheFlush) + record->rclass |= HTONS(MDNS_RCLASS_CACHE_FLUSH); + + //Advance write index + offset += sizeof(DnsResourceRecord); + + //The first pass calculates the length of the DNS encoded host name + n = mdnsEncodeName("", context->hostname, ".local", NULL); + + //Check the length of the resulting mDNS message + if((offset + n) > MDNS_MESSAGE_MAX_SIZE) + return ERROR_MESSAGE_TOO_LONG; + + //The second pass encodes the host name using DNS notation + n = mdnsEncodeName("", context->hostname, ".local", record->rdata); + + //Convert length field to network byte order + record->rdlength = htons(n); + + //Number of resource records in the answer section + message->dnsHeader->ancount++; + //Update the length of the mDNS response message + message->length = offset + n; + } + } +#endif + + //Successful processing + return NO_ERROR; +} + + +/** + * @brief Add reverse address mapping PTR record (IPv6) + * @param[in] interface Underlying network interface + * @param[in,out] message Pointer to the mDNS message + * @param[in] cacheFlush Cache-flush bit + * @param[in] ttl Resource record TTL (cache lifetime) + * @return Error code + **/ + +error_t mdnsResponderAddIpv6ReversePtrRecord(NetInterface *interface, + MdnsMessage *message, bool_t cacheFlush, uint32_t ttl) +{ +#if (IPV6_SUPPORT == ENABLED) + size_t n; + size_t offset; + bool_t duplicate; + MdnsResponderContext *context; + DnsResourceRecord *record; + + //Point to the mDNS responder context + context = interface->mdnsResponderContext; + + //Valid reverse name? + if(context->ipv6ReverseName[0] != '\0') + { + //Check whether the resource record is already present in the Answer + //Section of the message + duplicate = mdnsCheckDuplicateRecord(message, context->ipv6ReverseName, + "ip6", ".arpa", DNS_RR_TYPE_PTR); + + //The duplicates should be suppressed and the resource record should + //appear only once in the list + if(!duplicate) + { + //Set the position to the end of the buffer + offset = message->length; + + //The first pass calculates the length of the DNS encoded reverse name + n = mdnsEncodeName(context->ipv6ReverseName, "ip6", ".arpa", NULL); + + //Check the length of the resulting mDNS message + if((offset + n) > MDNS_MESSAGE_MAX_SIZE) + return ERROR_MESSAGE_TOO_LONG; + + //The second pass encodes the reverse name using the DNS name notation + offset += mdnsEncodeName(context->ipv6ReverseName, "ip6", ".arpa", + (uint8_t *) message->dnsHeader + offset); + + //Consider the length of the resource record itself + n = sizeof(DnsResourceRecord) + sizeof(Ipv4Addr); + + //Check the length of the resulting mDNS message + if((offset + n) > MDNS_MESSAGE_MAX_SIZE) + return ERROR_MESSAGE_TOO_LONG; + + //Point to the corresponding resource record + record = DNS_GET_RESOURCE_RECORD(message->dnsHeader, offset); + + //Fill in resource record + record->rtype = HTONS(DNS_RR_TYPE_PTR); + record->rclass = HTONS(DNS_RR_CLASS_IN); + record->ttl = htonl(ttl); + + //Check whether the cache-flush bit should be set + if(cacheFlush) + record->rclass |= HTONS(MDNS_RCLASS_CACHE_FLUSH); + + //Advance write index + offset += sizeof(DnsResourceRecord); + + //The first pass calculates the length of the DNS encoded host name + n = mdnsEncodeName("", context->hostname, ".local", NULL); + + //Check the length of the resulting mDNS message + if((offset + n) > MDNS_MESSAGE_MAX_SIZE) + return ERROR_MESSAGE_TOO_LONG; + + //The second pass encodes the host name using DNS notation + n = mdnsEncodeName("", context->hostname, ".local", record->rdata); + + //Convert length field to network byte order + record->rdlength = htons(n); + + //Number of resource records in the answer section + message->dnsHeader->ancount++; + //Update the length of the mDNS response message + message->length = offset + n; + } + } +#endif + + //Successful processing + return NO_ERROR; +} + + +/** + * @brief Add NSEC record to a mDNS message + * @param[in] interface Underlying network interface + * @param[in,out] message Pointer to the mDNS message + * @param[in] cacheFlush Cache-flush bit + * @param[in] ttl Resource record TTL (cache lifetime) + * @return Error code + **/ + +error_t mdnsResponderAddNsecRecord(NetInterface *interface, + MdnsMessage *message, bool_t cacheFlush, uint32_t ttl) +{ + size_t n; + size_t offset; + bool_t duplicate; + size_t bitmapLength; + uint8_t bitmap[8]; + MdnsResponderContext *context; + DnsResourceRecord *record; + + //Point to the mDNS responder context + context = interface->mdnsResponderContext; + + //Check whether the resource record is already present in the Answer + //Section of the message + duplicate = mdnsCheckDuplicateRecord(message, context->hostname, + "", ".local", DNS_RR_TYPE_NSEC); + + //The duplicates should be suppressed and the resource record should + //appear only once in the list + if(!duplicate) + { + //The bitmap identifies the resource record types that exist + memset(bitmap, 0, sizeof(bitmap)); + +#if (IPV4_SUPPORT == ENABLED) + //A resource record is supported + DNS_SET_NSEC_BITMAP(bitmap, DNS_RR_TYPE_A); +#endif + +#if (IPV6_SUPPORT == ENABLED) + //A resource record is supported + DNS_SET_NSEC_BITMAP(bitmap, DNS_RR_TYPE_AAAA); +#endif + + //Compute the length of the bitmap + for(bitmapLength = sizeof(bitmap); bitmapLength > 0; bitmapLength--) + { + //Trailing zero octets in the bitmap must be omitted... + if(bitmap[bitmapLength - 1] != 0x00) + break; + } + + //Set the position to the end of the buffer + offset = message->length; + + //The first pass calculates the length of the DNS encoded host name + n = mdnsEncodeName(context->hostname, "", ".local", NULL); + + //Check the length of the resulting mDNS message + if((offset + n) > MDNS_MESSAGE_MAX_SIZE) + return ERROR_MESSAGE_TOO_LONG; + + //The second pass encodes the host name using the DNS name notation + offset += mdnsEncodeName(context->hostname, "", ".local", + (uint8_t *) message->dnsHeader + offset); + + //Consider the length of the resource record itself + if((offset + sizeof(DnsResourceRecord)) > MDNS_MESSAGE_MAX_SIZE) + return ERROR_MESSAGE_TOO_LONG; + + //Point to the corresponding resource record + record = DNS_GET_RESOURCE_RECORD(message->dnsHeader, offset); + + //Fill in resource record + record->rtype = HTONS(DNS_RR_TYPE_NSEC); + record->rclass = HTONS(DNS_RR_CLASS_IN); + record->ttl = htonl(ttl); + + //Check whether the cache-flush bit should be set + if(cacheFlush) + record->rclass |= HTONS(MDNS_RCLASS_CACHE_FLUSH); + + //Advance write index + offset += sizeof(DnsResourceRecord); + + //Check the length of the resulting mDNS message + if((offset + n + 2 + bitmapLength) > MDNS_MESSAGE_MAX_SIZE) + return ERROR_MESSAGE_TOO_LONG; + + //The Next Domain Name field contains the record's own name + mdnsEncodeName(context->hostname, "", ".local", record->rdata); + + //DNS NSEC record is limited to Window Block number zero + record->rdata[n++] = 0; + //The Bitmap Length is a value in the range 1-32 + record->rdata[n++] = bitmapLength; + + //The Bitmap data identifies the resource record types that exist + memcpy(record->rdata + n, bitmap, bitmapLength); + + //Convert length field to network byte order + record->rdlength = htons(n + bitmapLength); + + //Number of resource records in the answer section + message->dnsHeader->ancount++; + //Update the length of the DNS message + message->length = offset + n + bitmapLength; + } + + //Successful processing + return NO_ERROR; +} + +#endif +