-
Notifications
You must be signed in to change notification settings - Fork 13.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rework DNSServer to be more robust #5573
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall this looks very good, I can clearly see additional sanity checks that are currently missing, and the code does loo a bit cleaner.
My comments are minor, mostly about readability and style.
{ | ||
replyWithCustomCode(buffer, packetSize); | ||
} | ||
buffer = (unsigned char *)malloc(currentPacketSize); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of malloc to a C-style ptr, and then manually freeing below, consider using a smart pointer. E.g.:
std::unique_ptr<uint8_t*>(new uint8_t[currentPacketSize]);
Notice the use of uint8_t* instead of unsigned char*. Although it's the same underlying type, we use this distinction to indicate that the ptr is meant for binary data and not for chars.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, I hope. I'm not particularly familiar with smart pointers (spend most of my life in C), so please check this bit carefully!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not bad for a first try, but it's not quite right 😄
- Keep in mind that in C++ you don't need to declare all vars at the top. In the case of smart pointers, there are reasons to not do so, and declare and construct in one go. My suggestion was this:
std::unique_ptr<uint8_t[]> buffer(new uint8_t(std::nothrow) [currentPacketSize]);
- Then pass the internal buffer to respondToRequest like so:
respondToRequest(*buffer, currentPacketSize)
or so:
respondToRequest(buffer.get(), currentPacketSize)
With that, respondToRequest can receive a uint8_t * as before (no need to change the arg type to be unique_ptr).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've changed this as suggested
64fef43
to
ec481d3
Compare
I've updated this PR with all of the requested changes |
respondToRequest(buffer, currentPacketSize); | ||
} | ||
|
||
void DNSServer::writeNBOShort(uint16_t value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this factoring out, but my previous comment about the literal 2 in the write() below is still not addressed.
I suggest the following:
_udp.write((usigned char *)&value, sizeof(value));
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll change this. Writing 2 octets of data here is a property of the protocol, though, and not of the C++ representation that's feeding it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand, and that's why I like the factoring out into a function: it makes it clear that it's a protocol property. My comment is more for maintainability and readability. If the function ever gets copied for e.g.: 4 octets or whatever, or templated for the arg type, with sizeof() there's no need to change anything => less error-prone. And when glancing at the code, sizeof() makes what is going on evident.
It's just good habits and programming guidelines, not that there's anything with your original code.
Also, I'll be porting these changes to my async dnsserver 😆
libraries/DNSServer/src/DNSServer.h
Outdated
@@ -1,12 +1,20 @@ | |||
#ifndef DNSServer_h | |||
#define DNSServer_h | |||
#include <WiFiUdp.h> | |||
#include <memory> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I explained in my reply, this include shouldn't be necessary, because you can keep the signature for respondToRequest as it was before.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed it
libraries/DNSServer/src/DNSServer.h
Outdated
size_t queryLength); | ||
void replyWithError(DNSHeader *dnsHeader, | ||
DNSReplyCode rcode); | ||
void respondToRequest(std::unique_ptr<uint8_t []> & buffer, size_t length); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I explained in my reply, this change in signature shouldn't be necessary, because you won't be passing the unique_ptr, but rather its internal buffer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed it back
Rewrite the request handling in the DNSServer code to address the following issues: Compatibility with EDNS esp8266#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 esp8266#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.
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.
ec481d3
to
ed919eb
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm ok with this. Is there anything further you want to add?
No, I think that’s it for now. It might be interesting to actually add proper EDNS support at some point in the future, but that would be a separate PR. |
If you're up for it later on, I'll take a look. |
These changes rework DNSServer so that it's more robust in the face of real-world DNS queries. They should resolve issues #5534 #5529 and #3357