diff --git a/libraries/DNSServer/src/DNSServer.cpp b/libraries/DNSServer/src/DNSServer.cpp index 28cf89d6ede..640690f7de4 100644 --- a/libraries/DNSServer/src/DNSServer.cpp +++ b/libraries/DNSServer/src/DNSServer.cpp @@ -111,16 +111,22 @@ void DNSServer::_handleUDP(AsyncUDPPacket &pkt) { // will reply with IP only to "*" or if domain matches without www. subdomain if (dnsHeader.OPCode == DNS_OPCODE_QUERY && requestIncludesOnlyOneQuestion(dnsHeader) && (_domainName.isEmpty() || getDomainNameWithoutWwwPrefix(static_cast(dnsQuestion.QName), dnsQuestion.QNameLength) == _domainName)) { - replyWithIP(pkt, dnsHeader, dnsQuestion); + + // Qtype = A (1) or ANY (255): send an A record otherwise an empty response + if (ntohs(dnsQuestion.QType) == 1 || ntohs(dnsQuestion.QType) == 255) { + replyWithIP(pkt, dnsHeader, dnsQuestion); + } else { + replyWithNoAnsw(pkt, dnsHeader, dnsQuestion); + } return; } - // otherwise reply with custom code replyWithCustomCode(pkt, dnsHeader); } bool DNSServer::requestIncludesOnlyOneQuestion(DNSHeader &dnsHeader) { - return ntohs(dnsHeader.QDCount) == 1 && dnsHeader.ANCount == 0 && dnsHeader.NSCount == 0 && dnsHeader.ARCount == 0; + dnsHeader.ARCount = 0; // We assume that if ARCount !=0 there is a EDNS OPT packet, just ignore + return ntohs(dnsHeader.QDCount) == 1 && dnsHeader.ANCount == 0 && dnsHeader.NSCount == 0; } String DNSServer::getDomainNameWithoutWwwPrefix(const unsigned char *start, size_t len) { @@ -139,7 +145,6 @@ String DNSServer::getDomainNameWithoutWwwPrefix(const unsigned char *start, size void DNSServer::replyWithIP(AsyncUDPPacket &req, DNSHeader &dnsHeader, DNSQuestion &dnsQuestion) { AsyncUDPMessage rpl; - // Change the type of message to a response and set the number of answers equal to // the number of questions in the header dnsHeader.QR = DNS_QR_RESPONSE; @@ -187,3 +192,76 @@ void DNSServer::replyWithCustomCode(AsyncUDPPacket &req, DNSHeader &dnsHeader) { rpl.write(reinterpret_cast(&dnsHeader), sizeof(DNSHeader)); _udp.sendTo(rpl, req.remoteIP(), req.remotePort()); } + +void DNSServer::replyWithNoAnsw(AsyncUDPPacket &req, DNSHeader &dnsHeader, DNSQuestion &dnsQuestion) { + + dnsHeader.QR = DNS_QR_RESPONSE; + dnsHeader.ANCount = 0; + dnsHeader.NSCount = htons(1); + + AsyncUDPMessage rpl; + rpl.write(reinterpret_cast(&dnsHeader), sizeof(DNSHeader)); + + // Write the question + rpl.write(dnsQuestion.QName, dnsQuestion.QNameLength); + rpl.write((uint8_t *)&dnsQuestion.QType, 2); + rpl.write((uint8_t *)&dnsQuestion.QClass, 2); + + // An empty answer contains an authority section with a SOA, + // We take the name of the query as the root of the zone for which the SOA is generated + // and use a value of DNS_MINIMAL_TTL seconds in order to minimize negative caching + // Write the authority section: + // The SOA RR's ownername is set equal to the query name, and we use made up names for + // the MNAME and RNAME - it doesn't really matter from a protocol perspective - as for + // a no such QTYPE answer only the timing fields are used. + // a protocol perspective - it + // Use DNS name compression : instead of repeating the name in this RNAME occurrence, + // set the two MSB of the byte corresponding normally to the length to 1. The following + // 14 bits must be used to specify the offset of the domain name in the message + // (<255 here so the first byte has the 6 LSB at 0) + rpl.write((uint8_t)0xC0); + rpl.write((uint8_t)DNS_OFFSET_DOMAIN_NAME); + + // DNS type A : host address, DNS class IN for INternet, returning an IPv4 address + uint16_t answerType = htons(DNS_TYPE_SOA), answerClass = htons(DNS_CLASS_IN); + uint32_t Serial = htonl(DNS_SOA_SERIAL); // Date type serial based on the date this piece of code was written + uint32_t Refresh = htonl(DNS_SOA_REFRESH); // These timers don't matter, we don't serve zone transfers + uint32_t Retry = htonl(DNS_SOA_RETRY); + uint32_t Expire = htonl(DNS_SOA_EXPIRE); + uint32_t MinTTL = htonl(DNS_MINIMAL_TTL); // See RFC2308 section 5 + char MLabel[] = DNS_SOA_MNAME_LABEL; + char RLabel[] = DNS_SOA_RNAME_LABEL; + char PostFixLabel[] = DNS_SOA_POSTFIX_LABEL; + + // 4 accounts for len fields and for both rname + // and lname and their postfix labels and there are 5 32 bit fields + + uint16_t RdataLength = htons((uint16_t)(strlen(MLabel) + strlen(RLabel) + 2 * strlen(PostFixLabel) + 4 + 5 * sizeof(Serial))); + + rpl.write((unsigned char *)&answerType, 2); + rpl.write((unsigned char *)&answerClass, 2); + rpl.write((unsigned char *)&MinTTL, 4); // DNS Time To Live + + rpl.write((unsigned char *)&RdataLength, 2); + + rpl.write((uint8_t)strlen(MLabel)); + rpl.write((unsigned char *)&MLabel, strlen(MLabel)); + + rpl.write((unsigned char *)&PostFixLabel, strlen(PostFixLabel)); + rpl.write((uint8_t)0); + // rpl.write((uint8_t)0xC0); + // rpl.write((uint8_t)DNS_OFFSET_DOMAIN_NAME); + + rpl.write((uint8_t)strlen(RLabel)); + rpl.write((unsigned char *)&RLabel, strlen(RLabel)); + rpl.write((unsigned char *)&PostFixLabel, strlen(PostFixLabel)); + rpl.write((uint8_t)0); + + rpl.write((unsigned char *)&Serial, 4); + rpl.write((unsigned char *)&Refresh, 4); + rpl.write((unsigned char *)&Retry, 4); + rpl.write((unsigned char *)&Expire, 4); + rpl.write((unsigned char *)&MinTTL, 4); + + _udp.sendTo(rpl, req.remoteIP(), req.remotePort()); +} diff --git a/libraries/DNSServer/src/DNSServer.h b/libraries/DNSServer/src/DNSServer.h index dfd9a45604d..ac86bb330ee 100644 --- a/libraries/DNSServer/src/DNSServer.h +++ b/libraries/DNSServer/src/DNSServer.h @@ -9,6 +9,26 @@ #define DNS_OFFSET_DOMAIN_NAME DNS_HEADER_SIZE // Offset in bytes to reach the domain name labels in the DNS message #define DNS_DEFAULT_PORT 53 +#define DNS_SOA_MNAME_LABEL "ns" +#define DNS_SOA_RNAME_LABEL "esp32" +// The POSTFIX_LABEL will be concatenated to the RName and MName Label label +// do not use a multilabel name here. "local" is a good choice as it is reserved for +// local use by IANA +// The postfix label is defined as an array of characters that follows the +// definition of RFC1035 3.1 +// for instance, a postfix of example.com would be defined as: +// #define DNS_SOA_POSTFIX_LABEL {'\7', 'e', 'x', 'a', 'm', 'p', 'l', 'e', '\3', 'c', 'o', 'm', '\0'} +#define DNS_SOA_POSTFIX_LABEL \ + { '\5', 'l', 'o', 'c', 'a', 'l', '\0' } +// From the following values only the MINIMAL_TTL has relevance +// in the context of client-server protocol interactions. +// The other vallues are arbitrary chosen as they are only relevant for +// in a zone-transfer scenario. +#define DNS_SOA_SERIAL 2025052900 // Arbitrary serial (format: YYYYMMDDnn) +#define DNS_SOA_REFRESH 100000 // Arbitrary (seconds) +#define DNS_SOA_RETRY 10000 // Arbitrary (seconds) +#define DNS_SOA_EXPIRE 1000000 // Arbitrary (seconds) +#define DNS_MINIMAL_TTL 5 // Time to live for negative answers RFC2308 enum class DNSReplyCode : uint16_t { NoError = 0, FormError = 1, @@ -179,5 +199,7 @@ class DNSServer { inline bool requestIncludesOnlyOneQuestion(DNSHeader &dnsHeader); void replyWithIP(AsyncUDPPacket &req, DNSHeader &dnsHeader, DNSQuestion &dnsQuestion); inline void replyWithCustomCode(AsyncUDPPacket &req, DNSHeader &dnsHeader); + inline void replyWithNoAnsw(AsyncUDPPacket &req, DNSHeader &dnsHeader, DNSQuestion &dnsQuestion); + void _handleUDP(AsyncUDPPacket &pkt); };