Skip to content

Commit 730b9ce

Browse files
committed
Proper EDNS handling and cleaner NOERROR response
1 parent 72a582b commit 730b9ce

File tree

3 files changed

+105
-7
lines changed

3 files changed

+105
-7
lines changed

libraries/DNSServer/library.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name=DNSServer
2-
version=3.2.0
2+
version=3.3.0
33
author=Kristijan Novoselić
44
maintainer=Kristijan Novoselić, <[email protected]>
55
sentence=A simple DNS server for ESP32.

libraries/DNSServer/src/DNSServer.cpp

Lines changed: 84 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
DNSServer::DNSServer() : _port(DNS_DEFAULT_PORT), _ttl(htonl(DNS_DEFAULT_TTL)), _errorReplyCode(DNSReplyCode::NonExistentDomain) {}
1616

1717
DNSServer::DNSServer(const String &domainName)
18-
: _port(DNS_DEFAULT_PORT), _ttl(htonl(DNS_DEFAULT_TTL)), _errorReplyCode(DNSReplyCode::NonExistentDomain), _domainName(domainName){};
18+
: _port(DNS_DEFAULT_PORT), _ttl(htonl(DNS_DEFAULT_TTL)), _errorReplyCode(DNSReplyCode::NonExistentDomain), _domainName(domainName) {};
1919

2020
bool DNSServer::start() {
2121
if (_resolvedIP.operator uint32_t() == 0) { // no address is set, try to obtain AP interface's IP
@@ -111,16 +111,22 @@ void DNSServer::_handleUDP(AsyncUDPPacket &pkt) {
111111
// will reply with IP only to "*" or if domain matches without www. subdomain
112112
if (dnsHeader.OPCode == DNS_OPCODE_QUERY && requestIncludesOnlyOneQuestion(dnsHeader)
113113
&& (_domainName.isEmpty() || getDomainNameWithoutWwwPrefix(static_cast<const unsigned char *>(dnsQuestion.QName), dnsQuestion.QNameLength) == _domainName)) {
114-
replyWithIP(pkt, dnsHeader, dnsQuestion);
114+
115+
// Qtype = A (1) or ANY (255): send an A record otherwise an empty response
116+
if (ntohs(dnsQuestion.QType) == 1 || ntohs(dnsQuestion.QType) == 255) {
117+
replyWithIP(pkt, dnsHeader, dnsQuestion);
118+
} else {
119+
replyWithNoAnsw(pkt, dnsHeader, dnsQuestion);
120+
}
115121
return;
116122
}
117-
118123
// otherwise reply with custom code
119124
replyWithCustomCode(pkt, dnsHeader);
120125
}
121126

122127
bool DNSServer::requestIncludesOnlyOneQuestion(DNSHeader &dnsHeader) {
123-
return ntohs(dnsHeader.QDCount) == 1 && dnsHeader.ANCount == 0 && dnsHeader.NSCount == 0 && dnsHeader.ARCount == 0;
128+
dnsHeader.ARCount = 0; // We assume that if ARCount !=0 there is a EDNS OPT packet, just ignore
129+
return ntohs(dnsHeader.QDCount) == 1 && dnsHeader.ANCount == 0 && dnsHeader.NSCount == 0; // && dnsHeader.ARCount == 0;
124130
}
125131

126132
String DNSServer::getDomainNameWithoutWwwPrefix(const unsigned char *start, size_t len) {
@@ -139,7 +145,6 @@ String DNSServer::getDomainNameWithoutWwwPrefix(const unsigned char *start, size
139145

140146
void DNSServer::replyWithIP(AsyncUDPPacket &req, DNSHeader &dnsHeader, DNSQuestion &dnsQuestion) {
141147
AsyncUDPMessage rpl;
142-
143148
// Change the type of message to a response and set the number of answers equal to
144149
// the number of questions in the header
145150
dnsHeader.QR = DNS_QR_RESPONSE;
@@ -187,3 +192,77 @@ void DNSServer::replyWithCustomCode(AsyncUDPPacket &req, DNSHeader &dnsHeader) {
187192
rpl.write(reinterpret_cast<const uint8_t *>(&dnsHeader), sizeof(DNSHeader));
188193
_udp.sendTo(rpl, req.remoteIP(), req.remotePort());
189194
}
195+
196+
void DNSServer::replyWithNoAnsw(AsyncUDPPacket &req, DNSHeader &dnsHeader, DNSQuestion &dnsQuestion) {
197+
198+
dnsHeader.QR = DNS_QR_RESPONSE;
199+
// dnsHeader.QDCount = 1;
200+
dnsHeader.ANCount = 0;
201+
dnsHeader.NSCount = htons(1);
202+
203+
AsyncUDPMessage rpl;
204+
rpl.write(reinterpret_cast<const uint8_t *>(&dnsHeader), sizeof(DNSHeader));
205+
206+
// Write the question
207+
rpl.write(dnsQuestion.QName, dnsQuestion.QNameLength);
208+
rpl.write((uint8_t *)&dnsQuestion.QType, 2);
209+
rpl.write((uint8_t *)&dnsQuestion.QClass, 2);
210+
211+
// An empty answer contains an authority section with a SOA,
212+
// We take the name of the query as the root of the zone for which the SOA is generated
213+
// and use a value of DNS_MINIMAL_TTL seconds in order to minimize negative caching
214+
// Write the authority section:
215+
// The SOA RR's ownername is set equal to the query name, and we use made up names for
216+
// the MNAME and RNAME - it doesn't really matter from a protocol perspective - as for
217+
// a no such QTYPE answer only the timing fields are used.
218+
// a protocol perspective - it
219+
// Use DNS name compression : instead of repeating the name in this RNAME occurrence,
220+
// set the two MSB of the byte corresponding normally to the length to 1. The following
221+
// 14 bits must be used to specify the offset of the domain name in the message
222+
// (<255 here so the first byte has the 6 LSB at 0)
223+
rpl.write((uint8_t)0xC0);
224+
rpl.write((uint8_t)DNS_OFFSET_DOMAIN_NAME);
225+
226+
// DNS type A : host address, DNS class IN for INternet, returning an IPv4 address
227+
uint16_t answerType = htons(DNS_TYPE_SOA), answerClass = htons(DNS_CLASS_IN);
228+
uint32_t Serial = htonl(DNS_SOA_SERIAL); // Date type serial based on the date this piece of code was written
229+
uint32_t Refresh = htonl(DNS_SOA_REFRESH); // These timers don't matter, we don't serve zone transfers
230+
uint32_t Retry = htonl(DNS_SOA_RETRY);
231+
uint32_t Expire = htonl(DNS_SOA_EXPIRE);
232+
uint32_t MinTTL = htonl(DNS_MINIMAL_TTL); // See RFC2308 section 5
233+
char MLabel[] = DNS_SOA_MNAME_LABEL;
234+
char RLabel[] = DNS_SOA_RNAME_LABEL;
235+
char PostFixLabel[] = DNS_SOA_POSTFIX_LABEL;
236+
237+
// 4 accounts for len fields and for both rname
238+
// and lname and their postfix labels and there are 5 32 bit fields
239+
240+
uint16_t RdataLength = htons((uint16_t)(strlen(MLabel) + strlen(RLabel) + 2 * strlen(PostFixLabel) + 4 + 5 * sizeof(Serial)));
241+
242+
rpl.write((unsigned char *)&answerType, 2);
243+
rpl.write((unsigned char *)&answerClass, 2);
244+
rpl.write((unsigned char *)&MinTTL, 4); // DNS Time To Live
245+
246+
rpl.write((unsigned char *)&RdataLength, 2);
247+
248+
rpl.write((uint8_t)strlen(MLabel));
249+
rpl.write((unsigned char *)&MLabel, strlen(MLabel));
250+
251+
rpl.write((unsigned char *)&PostFixLabel, strlen(PostFixLabel));
252+
rpl.write((uint8_t)0);
253+
// rpl.write((uint8_t)0xC0);
254+
// rpl.write((uint8_t)DNS_OFFSET_DOMAIN_NAME);
255+
256+
rpl.write((uint8_t)strlen(RLabel));
257+
rpl.write((unsigned char *)&RLabel, strlen(RLabel));
258+
rpl.write((unsigned char *)&PostFixLabel, strlen(PostFixLabel));
259+
rpl.write((uint8_t)0);
260+
261+
rpl.write((unsigned char *)&Serial, 4);
262+
rpl.write((unsigned char *)&Refresh, 4);
263+
rpl.write((unsigned char *)&Retry, 4);
264+
rpl.write((unsigned char *)&Expire, 4);
265+
rpl.write((unsigned char *)&MinTTL, 4);
266+
267+
_udp.sendTo(rpl, req.remoteIP(), req.remotePort());
268+
}

libraries/DNSServer/src/DNSServer.h

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,23 @@
99
#define DNS_OFFSET_DOMAIN_NAME DNS_HEADER_SIZE // Offset in bytes to reach the domain name labels in the DNS message
1010
#define DNS_DEFAULT_PORT 53
1111

12+
#define DNS_SOA_MNAME_LABEL "ns"
13+
#define DNS_SOA_RNAME_LABEL "esp32"
14+
// The POSTFIX_LABEL will be concatinated to the RName and MName Label label
15+
// do not use a multilabel name here. "local" is a good choice as it is reserved for
16+
// local use by IANA
17+
// The postfix label is defined as an array of characters that follows the
18+
// definition of RFC1035 3.1
19+
// for instance, a postfix of example.com would be defined as:
20+
// #define DNS_SOA_POSTFIX_LABEL {'\7', 'e', 'x', 'a', 'm', 'p', 'l', 'e', '\3', 'c', 'o', 'm', '\0'}
21+
#define DNS_SOA_POSTFIX_LABEL {'\5', 'l', 'o', 'c', 'a', 'l', '\0'}
22+
// From the following values only the MINIMAL_TTL has relevance
23+
// in the context of client-server protocol interactions.
24+
#define DNS_SOA_SERIAL 2025052900 // Arbitrary
25+
#define DNS_SOA_REFRESH 100000 // Arbitrary
26+
#define DNS_SOA_RETRY 10000 // Arbitrary
27+
#define DNS_SOA_EXPIRE 1000000 // Arbitrary
28+
#define DNS_MINIMAL_TTL 5 // Time to live for negative answers RFC2308
1229
enum class DNSReplyCode : uint16_t {
1330
NoError = 0,
1431
FormError = 1,
@@ -82,7 +99,7 @@ class DNSServer {
8299
* @param domainName - domain name to serve
83100
*/
84101
DNSServer(const String &domainName);
85-
~DNSServer(){}; // default d-tor
102+
~DNSServer() {}; // default d-tor
86103

87104
// Copy semantics not implemented (won't run on same UDP port anyway)
88105
DNSServer(const DNSServer &) = delete;
@@ -179,5 +196,7 @@ class DNSServer {
179196
inline bool requestIncludesOnlyOneQuestion(DNSHeader &dnsHeader);
180197
void replyWithIP(AsyncUDPPacket &req, DNSHeader &dnsHeader, DNSQuestion &dnsQuestion);
181198
inline void replyWithCustomCode(AsyncUDPPacket &req, DNSHeader &dnsHeader);
199+
inline void replyWithNoAnsw(AsyncUDPPacket &req, DNSHeader &dnsHeader, DNSQuestion &dnsQuestion);
200+
182201
void _handleUDP(AsyncUDPPacket &pkt);
183202
};

0 commit comments

Comments
 (0)