|
1 | 1 | #include "DNSServer.h"
|
2 | 2 | #include <lwip/def.h>
|
3 | 3 | #include <Arduino.h>
|
| 4 | +#include <memory> |
4 | 5 |
|
5 | 6 | #ifdef DEBUG_ESP_PORT
|
6 | 7 | #define DEBUG_OUTPUT DEBUG_ESP_PORT
|
7 | 8 | #else
|
8 | 9 | #define DEBUG_OUTPUT Serial
|
9 | 10 | #endif
|
10 | 11 |
|
| 12 | +#define DNS_HEADER_SIZE sizeof(DNSHeader) |
| 13 | + |
11 | 14 | DNSServer::DNSServer()
|
12 | 15 | {
|
13 | 16 | _ttl = lwip_htonl(60);
|
@@ -46,149 +49,208 @@ void DNSServer::stop()
|
46 | 49 | void DNSServer::downcaseAndRemoveWwwPrefix(String &domainName)
|
47 | 50 | {
|
48 | 51 | domainName.toLowerCase();
|
49 |
| - domainName.replace("www.", ""); |
| 52 | + if (domainName.startsWith("www.")) |
| 53 | + domainName.remove(0, 4); |
50 | 54 | }
|
51 | 55 |
|
52 |
| -void DNSServer::processNextRequest() |
| 56 | +void DNSServer::respondToRequest(uint8_t *buffer, size_t length) |
53 | 57 | {
|
54 |
| - size_t packetSize = _udp.parsePacket(); |
| 58 | + DNSHeader *dnsHeader; |
| 59 | + uint8_t *query, *start; |
| 60 | + const char *matchString; |
| 61 | + size_t remaining, labelLength, queryLength; |
| 62 | + uint16_t qtype, qclass; |
| 63 | + |
| 64 | + dnsHeader = (DNSHeader *)buffer; |
55 | 65 |
|
56 |
| - if (packetSize >= sizeof(DNSHeader)) |
57 |
| - { |
58 |
| - uint8_t* buffer = reinterpret_cast<uint8_t*>(malloc(packetSize)); |
59 |
| - if (buffer == NULL) return; |
| 66 | + // Must be a query for us to do anything with it |
| 67 | + if (dnsHeader->QR != DNS_QR_QUERY) |
| 68 | + return; |
60 | 69 |
|
61 |
| - _udp.read(buffer, packetSize); |
| 70 | + // If operation is anything other than query, we don't do it |
| 71 | + if (dnsHeader->OPCode != DNS_OPCODE_QUERY) |
| 72 | + return replyWithError(dnsHeader, DNSReplyCode::NotImplemented); |
| 73 | + |
| 74 | + // Only support requests containing single queries - everything else |
| 75 | + // is badly defined |
| 76 | + if (dnsHeader->QDCount != lwip_htons(1)) |
| 77 | + return replyWithError(dnsHeader, DNSReplyCode::FormError); |
| 78 | + |
| 79 | + // We must return a FormError in the case of a non-zero ARCount to |
| 80 | + // be minimally compatible with EDNS resolvers |
| 81 | + if (dnsHeader->ANCount != 0 || dnsHeader->NSCount != 0 |
| 82 | + || dnsHeader->ARCount != 0) |
| 83 | + return replyWithError(dnsHeader, DNSReplyCode::FormError); |
| 84 | + |
| 85 | + // Even if we're not going to use the query, we need to parse it |
| 86 | + // so we can check the address type that's being queried |
| 87 | + |
| 88 | + query = start = buffer + DNS_HEADER_SIZE; |
| 89 | + remaining = length - DNS_HEADER_SIZE; |
| 90 | + while (remaining != 0 && *start != 0) { |
| 91 | + labelLength = *start; |
| 92 | + if (labelLength + 1 > remaining) |
| 93 | + return replyWithError(dnsHeader, DNSReplyCode::FormError); |
| 94 | + remaining -= (labelLength + 1); |
| 95 | + start += (labelLength + 1); |
| 96 | + } |
62 | 97 |
|
63 |
| - DNSHeader* dnsHeader = reinterpret_cast<DNSHeader*>(buffer); |
| 98 | + // 1 octet labelLength, 2 octet qtype, 2 octet qclass |
| 99 | + if (remaining < 5) |
| 100 | + return replyWithError(dnsHeader, DNSReplyCode::FormError); |
64 | 101 |
|
65 |
| - if (dnsHeader->QR == DNS_QR_QUERY && |
66 |
| - dnsHeader->OPCode == DNS_OPCODE_QUERY && |
67 |
| - requestIncludesOnlyOneQuestion(dnsHeader) && |
68 |
| - (_domainName == "*" || getDomainNameWithoutWwwPrefix(buffer, packetSize) == _domainName) |
69 |
| - ) |
70 |
| - { |
71 |
| - replyWithIP(buffer, packetSize); |
72 |
| - } |
73 |
| - else if (dnsHeader->QR == DNS_QR_QUERY) |
74 |
| - { |
75 |
| - replyWithCustomCode(buffer, packetSize); |
| 102 | + start += 1; // Skip the 0 length label that we found above |
| 103 | + |
| 104 | + memcpy(&qtype, start, sizeof(qtype)); |
| 105 | + start += 2; |
| 106 | + memcpy(&qclass, start, sizeof(qclass)); |
| 107 | + start += 2; |
| 108 | + |
| 109 | + queryLength = start - query; |
| 110 | + |
| 111 | + if (qclass != lwip_htons(DNS_QCLASS_ANY) |
| 112 | + && qclass != lwip_htons(DNS_QCLASS_IN)) |
| 113 | + return replyWithError(dnsHeader, DNSReplyCode::NonExistentDomain, |
| 114 | + query, queryLength); |
| 115 | + |
| 116 | + if (qtype != lwip_htons(DNS_QTYPE_A) |
| 117 | + && qtype != lwip_htons(DNS_QTYPE_ANY)) |
| 118 | + return replyWithError(dnsHeader, DNSReplyCode::NonExistentDomain, |
| 119 | + query, queryLength); |
| 120 | + |
| 121 | + // If we have no domain name configured, just return an error |
| 122 | + if (_domainName == "") |
| 123 | + return replyWithError(dnsHeader, _errorReplyCode, |
| 124 | + query, queryLength); |
| 125 | + |
| 126 | + // If we're running with a wildcard we can just return a result now |
| 127 | + if (_domainName == "*") |
| 128 | + return replyWithIP(dnsHeader, query, queryLength); |
| 129 | + |
| 130 | + matchString = _domainName.c_str(); |
| 131 | + |
| 132 | + start = query; |
| 133 | + |
| 134 | + // If there's a leading 'www', skip it |
| 135 | + if (*start == 3 && strncasecmp("www", (char *) start + 1, 3) == 0) |
| 136 | + start += 4; |
| 137 | + |
| 138 | + while (*start != 0) { |
| 139 | + labelLength = *start; |
| 140 | + start += 1; |
| 141 | + while (labelLength > 0) { |
| 142 | + if (tolower(*start) != *matchString) |
| 143 | + return replyWithError(dnsHeader, _errorReplyCode, |
| 144 | + query, queryLength); |
| 145 | + ++start; |
| 146 | + ++matchString; |
| 147 | + --labelLength; |
76 | 148 | }
|
| 149 | + if (*start == 0 && *matchString == '\0') |
| 150 | + return replyWithIP(dnsHeader, query, queryLength); |
77 | 151 |
|
78 |
| - free(buffer); |
| 152 | + if (*matchString != '.') |
| 153 | + return replyWithError(dnsHeader, _errorReplyCode, |
| 154 | + query, queryLength); |
| 155 | + ++matchString; |
79 | 156 | }
|
80 |
| -} |
81 | 157 |
|
82 |
| -bool DNSServer::requestIncludesOnlyOneQuestion(const DNSHeader* dnsHeader) |
83 |
| -{ |
84 |
| - return lwip_ntohs(dnsHeader->QDCount) == 1 && |
85 |
| - dnsHeader->ANCount == 0 && |
86 |
| - dnsHeader->NSCount == 0 && |
87 |
| - dnsHeader->ARCount == 0; |
| 158 | + return replyWithError(dnsHeader, _errorReplyCode, |
| 159 | + query, queryLength); |
88 | 160 | }
|
89 | 161 |
|
90 |
| -String DNSServer::getDomainNameWithoutWwwPrefix(const uint8_t* buffer, size_t packetSize) |
| 162 | +void DNSServer::processNextRequest() |
91 | 163 | {
|
92 |
| - String parsedDomainName; |
93 |
| - |
94 |
| - const uint8_t* pos = buffer + sizeof(DNSHeader); |
95 |
| - const uint8_t* end = buffer + packetSize; |
96 |
| - |
97 |
| - // to minimize reallocations due to concats below |
98 |
| - // we reserve enough space that a median or average domain |
99 |
| - // name size cold be easily contained without a reallocation |
100 |
| - // - max size would be 253, in 2013, average is 11 and max was 42 |
101 |
| - // |
102 |
| - parsedDomainName.reserve(32); |
103 |
| - |
104 |
| - uint8_t labelLength = *pos; |
105 |
| - |
106 |
| - while (true) |
107 |
| - { |
108 |
| - if (labelLength == 0) |
109 |
| - { |
110 |
| - // no more labels |
111 |
| - downcaseAndRemoveWwwPrefix(parsedDomainName); |
112 |
| - return parsedDomainName; |
113 |
| - } |
| 164 | + size_t currentPacketSize; |
114 | 165 |
|
115 |
| - // append next label |
116 |
| - for (int i = 0; i < labelLength && pos < end; i++) |
117 |
| - { |
118 |
| - pos++; |
119 |
| - parsedDomainName += static_cast<char>(*pos); |
120 |
| - } |
| 166 | + currentPacketSize = _udp.parsePacket(); |
| 167 | + if (currentPacketSize == 0) |
| 168 | + return; |
121 | 169 |
|
122 |
| - if (pos >= end) |
123 |
| - { |
124 |
| - // malformed packet, return an empty domain name |
125 |
| - parsedDomainName = ""; |
126 |
| - return parsedDomainName; |
127 |
| - } |
128 |
| - else |
129 |
| - { |
130 |
| - // next label |
131 |
| - pos++; |
132 |
| - labelLength = *pos; |
133 |
| - |
134 |
| - // if there is another label, add delimiter |
135 |
| - if (labelLength != 0) |
136 |
| - { |
137 |
| - parsedDomainName += "."; |
138 |
| - } |
139 |
| - } |
140 |
| - } |
| 170 | + // The DNS RFC requires that DNS packets be less than 512 bytes in size, |
| 171 | + // so just discard them if they are larger |
| 172 | + if (currentPacketSize > MAX_DNS_PACKETSIZE) |
| 173 | + return; |
| 174 | + |
| 175 | + // If the packet size is smaller than the DNS header, then someone is |
| 176 | + // messing with us |
| 177 | + if (currentPacketSize < DNS_HEADER_SIZE) |
| 178 | + return; |
| 179 | + |
| 180 | + std::unique_ptr<uint8_t[]> buffer(new (std::nothrow) uint8_t[currentPacketSize]); |
| 181 | + |
| 182 | + if (buffer == NULL) |
| 183 | + return; |
| 184 | + |
| 185 | + _udp.read(buffer.get(), currentPacketSize); |
| 186 | + respondToRequest(buffer.get(), currentPacketSize); |
| 187 | +} |
| 188 | + |
| 189 | +void DNSServer::writeNBOShort(uint16_t value) |
| 190 | +{ |
| 191 | + _udp.write((unsigned char *)&value, 2); |
141 | 192 | }
|
142 | 193 |
|
143 |
| -void DNSServer::replyWithIP(uint8_t* buffer, size_t packetSize) |
| 194 | +void DNSServer::replyWithIP(DNSHeader *dnsHeader, |
| 195 | + unsigned char * query, |
| 196 | + size_t queryLength) |
144 | 197 | {
|
145 |
| - DNSHeader* dnsHeader = reinterpret_cast<DNSHeader*>(buffer); |
| 198 | + uint16_t value; |
146 | 199 |
|
147 | 200 | dnsHeader->QR = DNS_QR_RESPONSE;
|
148 |
| - dnsHeader->ANCount = dnsHeader->QDCount; |
149 |
| - dnsHeader->QDCount = dnsHeader->QDCount; |
150 |
| - //dnsHeader->RA = 1; |
| 201 | + dnsHeader->QDCount = lwip_htons(1); |
| 202 | + dnsHeader->ANCount = lwip_htons(1); |
| 203 | + dnsHeader->NSCount = 0; |
| 204 | + dnsHeader->ARCount = 0; |
151 | 205 |
|
152 | 206 | _udp.beginPacket(_udp.remoteIP(), _udp.remotePort());
|
153 |
| - _udp.write(buffer, packetSize); |
| 207 | + _udp.write((unsigned char *) dnsHeader, sizeof(DNSHeader)); |
| 208 | + _udp.write(query, queryLength); |
| 209 | + |
| 210 | + // Rather than restate the name here, we use a pointer to the name contained |
| 211 | + // in the query section. Pointers have the top two bits set. |
| 212 | + value = 0xC000 | DNS_HEADER_SIZE; |
| 213 | + writeNBOShort(lwip_htons(value)); |
154 | 214 |
|
155 |
| - _udp.write((uint8_t)192); // answer name is a pointer |
156 |
| - _udp.write((uint8_t)12); // pointer to offset at 0x00c |
| 215 | + // Answer is type A (an IPv4 address) |
| 216 | + writeNBOShort(lwip_htons(DNS_QTYPE_A)); |
157 | 217 |
|
158 |
| - _udp.write((uint8_t)0); // 0x0001 answer is type A query (host address) |
159 |
| - _udp.write((uint8_t)1); |
| 218 | + // Answer is in the Internet Class |
| 219 | + writeNBOShort(lwip_htons(DNS_QCLASS_IN)); |
160 | 220 |
|
161 |
| - _udp.write((uint8_t)0); //0x0001 answer is class IN (internet address) |
162 |
| - _udp.write((uint8_t)1); |
163 |
| - |
| 221 | + // Output TTL (already NBO) |
164 | 222 | _udp.write((unsigned char*)&_ttl, 4);
|
165 | 223 |
|
166 | 224 | // Length of RData is 4 bytes (because, in this case, RData is IPv4)
|
167 |
| - _udp.write((uint8_t)0); |
168 |
| - _udp.write((uint8_t)4); |
| 225 | + writeNBOShort(lwip_htons(sizeof(_resolvedIP))); |
169 | 226 | _udp.write(_resolvedIP, sizeof(_resolvedIP));
|
170 | 227 | _udp.endPacket();
|
171 |
| - |
172 |
| - #ifdef DEBUG_ESP_DNS |
173 |
| - DEBUG_OUTPUT.printf("DNS responds: %s for %s\n", |
174 |
| - IPAddress(_resolvedIP).toString().c_str(), getDomainNameWithoutWwwPrefix(buffer, packetSize).c_str() ); |
175 |
| - #endif |
176 | 228 | }
|
177 | 229 |
|
178 |
| -void DNSServer::replyWithCustomCode(uint8_t* buffer, size_t packetSize) |
| 230 | +void DNSServer::replyWithError(DNSHeader *dnsHeader, |
| 231 | + DNSReplyCode rcode, |
| 232 | + unsigned char *query, |
| 233 | + size_t queryLength) |
179 | 234 | {
|
180 |
| - if (packetSize < sizeof(DNSHeader)) |
181 |
| - { |
182 |
| - return; |
183 |
| - } |
184 |
| - |
185 |
| - DNSHeader* dnsHeader = reinterpret_cast<DNSHeader*>(buffer); |
186 |
| - |
187 | 235 | dnsHeader->QR = DNS_QR_RESPONSE;
|
188 |
| - dnsHeader->RCode = (unsigned char)_errorReplyCode; |
189 |
| - dnsHeader->QDCount = 0; |
| 236 | + dnsHeader->RCode = (unsigned char) rcode; |
| 237 | + if (query) |
| 238 | + dnsHeader->QDCount = lwip_htons(1); |
| 239 | + else |
| 240 | + dnsHeader->QDCount = 0; |
| 241 | + dnsHeader->ANCount = 0; |
| 242 | + dnsHeader->NSCount = 0; |
| 243 | + dnsHeader->ARCount = 0; |
190 | 244 |
|
191 | 245 | _udp.beginPacket(_udp.remoteIP(), _udp.remotePort());
|
192 |
| - _udp.write(buffer, sizeof(DNSHeader)); |
| 246 | + _udp.write((unsigned char *)dnsHeader, sizeof(DNSHeader)); |
| 247 | + if (query != NULL) |
| 248 | + _udp.write(query, queryLength); |
193 | 249 | _udp.endPacket();
|
194 | 250 | }
|
| 251 | + |
| 252 | +void DNSServer::replyWithError(DNSHeader *dnsHeader, |
| 253 | + DNSReplyCode rcode) |
| 254 | +{ |
| 255 | + replyWithError(dnsHeader, rcode, NULL, 0); |
| 256 | +} |
0 commit comments