Skip to content

Commit 23c6abc

Browse files
Kolkmanpre-commit-ci-lite[bot]me-no-dev
authored
Proper EDNS handling and cleaner NOERROR response in DNSSERVER (#11411)
* Proper EDNS handling and cleaner NOERROR response * fix: library.properties reverting version number update - as it is done automatically * ci(pre-commit): Apply automatic fixes * Spelling Corrected and minor clarification in comments * Removing commented out code fragments * ci(pre-commit): Apply automatic fixes * fix(pr): Fix typo --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: Me No Dev <me-no-dev@users.noreply.github.com>
1 parent 016077e commit 23c6abc

File tree

2 files changed

+104
-4
lines changed

2 files changed

+104
-4
lines changed

libraries/DNSServer/src/DNSServer.cpp

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

libraries/DNSServer/src/DNSServer.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,26 @@
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 concatenated 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 \
22+
{ '\5', 'l', 'o', 'c', 'a', 'l', '\0' }
23+
// From the following values only the MINIMAL_TTL has relevance
24+
// in the context of client-server protocol interactions.
25+
// The other values are arbitrary chosen as they are only relevant for
26+
// in a zone-transfer scenario.
27+
#define DNS_SOA_SERIAL 2025052900 // Arbitrary serial (format: YYYYMMDDnn)
28+
#define DNS_SOA_REFRESH 100000 // Arbitrary (seconds)
29+
#define DNS_SOA_RETRY 10000 // Arbitrary (seconds)
30+
#define DNS_SOA_EXPIRE 1000000 // Arbitrary (seconds)
31+
#define DNS_MINIMAL_TTL 5 // Time to live for negative answers RFC2308
1232
enum class DNSReplyCode : uint16_t {
1333
NoError = 0,
1434
FormError = 1,
@@ -179,5 +199,7 @@ class DNSServer {
179199
inline bool requestIncludesOnlyOneQuestion(DNSHeader &dnsHeader);
180200
void replyWithIP(AsyncUDPPacket &req, DNSHeader &dnsHeader, DNSQuestion &dnsQuestion);
181201
inline void replyWithCustomCode(AsyncUDPPacket &req, DNSHeader &dnsHeader);
202+
inline void replyWithNoAnsw(AsyncUDPPacket &req, DNSHeader &dnsHeader, DNSQuestion &dnsQuestion);
203+
182204
void _handleUDP(AsyncUDPPacket &pkt);
183205
};

0 commit comments

Comments
 (0)