1
0
mirror of https://github.com/esp8266/Arduino.git synced 2025-04-21 10:26:06 +03:00

Rework DNSServer to be more robust (#5573)

* DNSServer: Handle examplewww.com correctly

Just replacing 'www.' with the empty string when we assign the
domainname will remove all occurrences of 'www.', not just those
at the start of the string.

Change this to a startsWith check so that only "www." at the
beginning of the string is removed.

* DNSServer: Rework request handling

Rewrite the request handling in the DNSServer code to address the
following issues:

Compatibility with EDNS #1:

   RFC6891 says that
     "Responders that choose not to implement the protocol
      extensions defined in this document MUST respond with a
      return code (RCODE) of FORMERR to messages containing an
      OPT record in the additional section and MUST NOT include an
      OPT record in the response"

   If we have any additional records in the request, then we need
   to return a FORMERR, and not whatever custom error code the user
   may have set.

Compatibility with EDNS #2:

   If we're returning an error, we need to explicitly zero all of
   the record counters. In the existing code, if there is an
   additional record present in the request, we return an ARCOUNT
   of 1 in the response, despite including no additional records
   in the payload.

Don't answer non-A requests

   If we receive an AAAA request (or any other non-A record)
   requests, we shouldn't respond to it with an A record.

Don't answer non-IN requests

   If we receive a request for a non-IN type, don't answer it
   (it's unlikely that we'd see this in the real world)

Don't read off the end of malformed packets

   If a packet claims to have a query, but then doesn't include
   one, or includes a query with malformed labels, don't read off
   the end of the allocated data structure.

* DNSServer: Clarify and tidy writing the answer record

Modify the code used to write the answer record back to the server
so that it is clearer that we are writing network byte order
16-bit quantities, and to clarify what's happening with the pointer
used at the start of the answer.
This commit is contained in:
SimonWilkinson 2019-01-05 15:47:00 +00:00 committed by Develo
parent 2f0f49dbe8
commit eaac1e8b24
2 changed files with 192 additions and 116 deletions

View File

@ -1,6 +1,7 @@
#include "DNSServer.h" #include "DNSServer.h"
#include <lwip/def.h> #include <lwip/def.h>
#include <Arduino.h> #include <Arduino.h>
#include <memory>
#ifdef DEBUG_ESP_PORT #ifdef DEBUG_ESP_PORT
#define DEBUG_OUTPUT DEBUG_ESP_PORT #define DEBUG_OUTPUT DEBUG_ESP_PORT
@ -8,6 +9,8 @@
#define DEBUG_OUTPUT Serial #define DEBUG_OUTPUT Serial
#endif #endif
#define DNS_HEADER_SIZE sizeof(DNSHeader)
DNSServer::DNSServer() DNSServer::DNSServer()
{ {
_ttl = lwip_htonl(60); _ttl = lwip_htonl(60);
@ -46,149 +49,208 @@ void DNSServer::stop()
void DNSServer::downcaseAndRemoveWwwPrefix(String &domainName) void DNSServer::downcaseAndRemoveWwwPrefix(String &domainName)
{ {
domainName.toLowerCase(); domainName.toLowerCase();
domainName.replace("www.", ""); if (domainName.startsWith("www."))
domainName.remove(0, 4);
}
void DNSServer::respondToRequest(uint8_t *buffer, size_t length)
{
DNSHeader *dnsHeader;
uint8_t *query, *start;
const char *matchString;
size_t remaining, labelLength, queryLength;
uint16_t qtype, qclass;
dnsHeader = (DNSHeader *)buffer;
// Must be a query for us to do anything with it
if (dnsHeader->QR != DNS_QR_QUERY)
return;
// If operation is anything other than query, we don't do it
if (dnsHeader->OPCode != DNS_OPCODE_QUERY)
return replyWithError(dnsHeader, DNSReplyCode::NotImplemented);
// Only support requests containing single queries - everything else
// is badly defined
if (dnsHeader->QDCount != lwip_htons(1))
return replyWithError(dnsHeader, DNSReplyCode::FormError);
// We must return a FormError in the case of a non-zero ARCount to
// be minimally compatible with EDNS resolvers
if (dnsHeader->ANCount != 0 || dnsHeader->NSCount != 0
|| dnsHeader->ARCount != 0)
return replyWithError(dnsHeader, DNSReplyCode::FormError);
// Even if we're not going to use the query, we need to parse it
// so we can check the address type that's being queried
query = start = buffer + DNS_HEADER_SIZE;
remaining = length - DNS_HEADER_SIZE;
while (remaining != 0 && *start != 0) {
labelLength = *start;
if (labelLength + 1 > remaining)
return replyWithError(dnsHeader, DNSReplyCode::FormError);
remaining -= (labelLength + 1);
start += (labelLength + 1);
}
// 1 octet labelLength, 2 octet qtype, 2 octet qclass
if (remaining < 5)
return replyWithError(dnsHeader, DNSReplyCode::FormError);
start += 1; // Skip the 0 length label that we found above
memcpy(&qtype, start, sizeof(qtype));
start += 2;
memcpy(&qclass, start, sizeof(qclass));
start += 2;
queryLength = start - query;
if (qclass != lwip_htons(DNS_QCLASS_ANY)
&& qclass != lwip_htons(DNS_QCLASS_IN))
return replyWithError(dnsHeader, DNSReplyCode::NonExistentDomain,
query, queryLength);
if (qtype != lwip_htons(DNS_QTYPE_A)
&& qtype != lwip_htons(DNS_QTYPE_ANY))
return replyWithError(dnsHeader, DNSReplyCode::NonExistentDomain,
query, queryLength);
// If we have no domain name configured, just return an error
if (_domainName == "")
return replyWithError(dnsHeader, _errorReplyCode,
query, queryLength);
// If we're running with a wildcard we can just return a result now
if (_domainName == "*")
return replyWithIP(dnsHeader, query, queryLength);
matchString = _domainName.c_str();
start = query;
// If there's a leading 'www', skip it
if (*start == 3 && strncasecmp("www", (char *) start + 1, 3) == 0)
start += 4;
while (*start != 0) {
labelLength = *start;
start += 1;
while (labelLength > 0) {
if (tolower(*start) != *matchString)
return replyWithError(dnsHeader, _errorReplyCode,
query, queryLength);
++start;
++matchString;
--labelLength;
}
if (*start == 0 && *matchString == '\0')
return replyWithIP(dnsHeader, query, queryLength);
if (*matchString != '.')
return replyWithError(dnsHeader, _errorReplyCode,
query, queryLength);
++matchString;
}
return replyWithError(dnsHeader, _errorReplyCode,
query, queryLength);
} }
void DNSServer::processNextRequest() void DNSServer::processNextRequest()
{ {
size_t packetSize = _udp.parsePacket(); size_t currentPacketSize;
if (packetSize >= sizeof(DNSHeader)) currentPacketSize = _udp.parsePacket();
{ if (currentPacketSize == 0)
uint8_t* buffer = reinterpret_cast<uint8_t*>(malloc(packetSize)); return;
if (buffer == NULL) return;
_udp.read(buffer, packetSize); // The DNS RFC requires that DNS packets be less than 512 bytes in size,
// so just discard them if they are larger
if (currentPacketSize > MAX_DNS_PACKETSIZE)
return;
DNSHeader* dnsHeader = reinterpret_cast<DNSHeader*>(buffer); // If the packet size is smaller than the DNS header, then someone is
// messing with us
if (currentPacketSize < DNS_HEADER_SIZE)
return;
if (dnsHeader->QR == DNS_QR_QUERY && std::unique_ptr<uint8_t[]> buffer(new (std::nothrow) uint8_t[currentPacketSize]);
dnsHeader->OPCode == DNS_OPCODE_QUERY &&
requestIncludesOnlyOneQuestion(dnsHeader) && if (buffer == NULL)
(_domainName == "*" || getDomainNameWithoutWwwPrefix(buffer, packetSize) == _domainName) return;
)
{ _udp.read(buffer.get(), currentPacketSize);
replyWithIP(buffer, packetSize); respondToRequest(buffer.get(), currentPacketSize);
}
else if (dnsHeader->QR == DNS_QR_QUERY)
{
replyWithCustomCode(buffer, packetSize);
} }
free(buffer); void DNSServer::writeNBOShort(uint16_t value)
}
}
bool DNSServer::requestIncludesOnlyOneQuestion(const DNSHeader* dnsHeader)
{ {
return lwip_ntohs(dnsHeader->QDCount) == 1 && _udp.write((unsigned char *)&value, 2);
dnsHeader->ANCount == 0 &&
dnsHeader->NSCount == 0 &&
dnsHeader->ARCount == 0;
} }
String DNSServer::getDomainNameWithoutWwwPrefix(const uint8_t* buffer, size_t packetSize) void DNSServer::replyWithIP(DNSHeader *dnsHeader,
unsigned char * query,
size_t queryLength)
{ {
String parsedDomainName; uint16_t value;
const uint8_t* pos = buffer + sizeof(DNSHeader);
const uint8_t* end = buffer + packetSize;
// to minimize reallocations due to concats below
// we reserve enough space that a median or average domain
// name size cold be easily contained without a reallocation
// - max size would be 253, in 2013, average is 11 and max was 42
//
parsedDomainName.reserve(32);
uint8_t labelLength = *pos;
while (true)
{
if (labelLength == 0)
{
// no more labels
downcaseAndRemoveWwwPrefix(parsedDomainName);
return parsedDomainName;
}
// append next label
for (int i = 0; i < labelLength && pos < end; i++)
{
pos++;
parsedDomainName += static_cast<char>(*pos);
}
if (pos >= end)
{
// malformed packet, return an empty domain name
parsedDomainName = "";
return parsedDomainName;
}
else
{
// next label
pos++;
labelLength = *pos;
// if there is another label, add delimiter
if (labelLength != 0)
{
parsedDomainName += ".";
}
}
}
}
void DNSServer::replyWithIP(uint8_t* buffer, size_t packetSize)
{
DNSHeader* dnsHeader = reinterpret_cast<DNSHeader*>(buffer);
dnsHeader->QR = DNS_QR_RESPONSE; dnsHeader->QR = DNS_QR_RESPONSE;
dnsHeader->ANCount = dnsHeader->QDCount; dnsHeader->QDCount = lwip_htons(1);
dnsHeader->QDCount = dnsHeader->QDCount; dnsHeader->ANCount = lwip_htons(1);
//dnsHeader->RA = 1; dnsHeader->NSCount = 0;
dnsHeader->ARCount = 0;
_udp.beginPacket(_udp.remoteIP(), _udp.remotePort()); _udp.beginPacket(_udp.remoteIP(), _udp.remotePort());
_udp.write(buffer, packetSize); _udp.write((unsigned char *) dnsHeader, sizeof(DNSHeader));
_udp.write(query, queryLength);
_udp.write((uint8_t)192); // answer name is a pointer // Rather than restate the name here, we use a pointer to the name contained
_udp.write((uint8_t)12); // pointer to offset at 0x00c // in the query section. Pointers have the top two bits set.
value = 0xC000 | DNS_HEADER_SIZE;
writeNBOShort(lwip_htons(value));
_udp.write((uint8_t)0); // 0x0001 answer is type A query (host address) // Answer is type A (an IPv4 address)
_udp.write((uint8_t)1); writeNBOShort(lwip_htons(DNS_QTYPE_A));
_udp.write((uint8_t)0); //0x0001 answer is class IN (internet address) // Answer is in the Internet Class
_udp.write((uint8_t)1); writeNBOShort(lwip_htons(DNS_QCLASS_IN));
// Output TTL (already NBO)
_udp.write((unsigned char*)&_ttl, 4); _udp.write((unsigned char*)&_ttl, 4);
// Length of RData is 4 bytes (because, in this case, RData is IPv4) // Length of RData is 4 bytes (because, in this case, RData is IPv4)
_udp.write((uint8_t)0); writeNBOShort(lwip_htons(sizeof(_resolvedIP)));
_udp.write((uint8_t)4);
_udp.write(_resolvedIP, sizeof(_resolvedIP)); _udp.write(_resolvedIP, sizeof(_resolvedIP));
_udp.endPacket(); _udp.endPacket();
#ifdef DEBUG_ESP_DNS
DEBUG_OUTPUT.printf("DNS responds: %s for %s\n",
IPAddress(_resolvedIP).toString().c_str(), getDomainNameWithoutWwwPrefix(buffer, packetSize).c_str() );
#endif
} }
void DNSServer::replyWithCustomCode(uint8_t* buffer, size_t packetSize) void DNSServer::replyWithError(DNSHeader *dnsHeader,
DNSReplyCode rcode,
unsigned char *query,
size_t queryLength)
{ {
if (packetSize < sizeof(DNSHeader))
{
return;
}
DNSHeader* dnsHeader = reinterpret_cast<DNSHeader*>(buffer);
dnsHeader->QR = DNS_QR_RESPONSE; dnsHeader->QR = DNS_QR_RESPONSE;
dnsHeader->RCode = (unsigned char)_errorReplyCode; dnsHeader->RCode = (unsigned char) rcode;
if (query)
dnsHeader->QDCount = lwip_htons(1);
else
dnsHeader->QDCount = 0; dnsHeader->QDCount = 0;
dnsHeader->ANCount = 0;
dnsHeader->NSCount = 0;
dnsHeader->ARCount = 0;
_udp.beginPacket(_udp.remoteIP(), _udp.remotePort()); _udp.beginPacket(_udp.remoteIP(), _udp.remotePort());
_udp.write(buffer, sizeof(DNSHeader)); _udp.write((unsigned char *)dnsHeader, sizeof(DNSHeader));
if (query != NULL)
_udp.write(query, queryLength);
_udp.endPacket(); _udp.endPacket();
} }
void DNSServer::replyWithError(DNSHeader *dnsHeader,
DNSReplyCode rcode)
{
replyWithError(dnsHeader, rcode, NULL, 0);
}

View File

@ -6,7 +6,14 @@
#define DNS_QR_RESPONSE 1 #define DNS_QR_RESPONSE 1
#define DNS_OPCODE_QUERY 0 #define DNS_OPCODE_QUERY 0
#define DNS_QCLASS_IN 1
#define DNS_QCLASS_ANY 255
#define DNS_QTYPE_A 1
#define DNS_QTYPE_ANY 255
#define MAX_DNSNAME_LENGTH 253 #define MAX_DNSNAME_LENGTH 253
#define MAX_DNS_PACKETSIZE 512
enum class DNSReplyCode enum class DNSReplyCode
{ {
@ -65,9 +72,16 @@ class DNSServer
DNSReplyCode _errorReplyCode; DNSReplyCode _errorReplyCode;
void downcaseAndRemoveWwwPrefix(String &domainName); void downcaseAndRemoveWwwPrefix(String &domainName);
String getDomainNameWithoutWwwPrefix(const uint8_t* buffer, size_t packetSize); void replyWithIP(DNSHeader *dnsHeader,
bool requestIncludesOnlyOneQuestion(const DNSHeader* dnsHeader); unsigned char * query,
void replyWithIP(uint8_t* buffer, size_t packetSize); size_t queryLength);
void replyWithCustomCode(uint8_t* buffer, size_t packetSize); void replyWithError(DNSHeader *dnsHeader,
DNSReplyCode rcode,
unsigned char *query,
size_t queryLength);
void replyWithError(DNSHeader *dnsHeader,
DNSReplyCode rcode);
void respondToRequest(uint8_t *buffer, size_t length);
void writeNBOShort(uint16_t value);
}; };
#endif #endif