Skip to content

Commit 53a9ecd

Browse files
committed
Rework MDNS parsing to support longer domains and use progmem
1 parent 6b0690c commit 53a9ecd

File tree

4 files changed

+165
-154
lines changed

4 files changed

+165
-154
lines changed

examples/MDNS_WiFiWebServer/MDNS_WiFiWebServer.ino

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ void setup() {
9494
void loop() {
9595
// Call the update() function on the MDNS responder every loop iteration to
9696
// make sure it can detect and respond to name requests.
97-
mdns.update();
97+
mdns.poll();
9898

9999
// listen for incoming clients
100100
WiFiClient client = server.available();

keywords.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ RSSI KEYWORD2
3737
encryptionType KEYWORD2
3838
getResult KEYWORD2
3939
getSocket KEYWORD2
40+
poll KEYWORD2
4041
WiFiClient KEYWORD2
4142
WiFiServer KEYWORD2
4243
WiFiSSLClient KEYWORD2

src/WiFiMdns.cpp

Lines changed: 151 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -21,177 +21,189 @@
2121
// License along with this library; if not, write to the Free Software
2222
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
2323

24+
#include <avr/pgmspace.h>
25+
2426
#include "Arduino.h"
2527
#include "WiFiMdns.h"
2628

2729
// Important RFC's for reference:
2830
// - DNS request and response: http://www.ietf.org/rfc/rfc1035.txt
2931
// - Multicast DNS: http://www.ietf.org/rfc/rfc6762.txt
3032

31-
#define READ_BUFFER_SIZE 20
3233
#define HEADER_SIZE 12
33-
#define QDCOUNT_OFFSET 4
34-
#define A_RECORD_SIZE 14
35-
#define NSEC_RECORD_SIZE 20
3634
#define TTL_OFFSET 4
3735
#define IP_OFFSET 10
3836

37+
const uint8_t expectedRequestHeader[HEADER_SIZE] PROGMEM = {
38+
0x00, 0x00,
39+
0x00, 0x00,
40+
0x00, 0x01,
41+
0x00, 0x00,
42+
0x00, 0x00,
43+
0x00, 0x00
44+
};
3945

40-
MDNSResponder::MDNSResponder()
41-
: _expected(NULL)
42-
, _expectedLen(0)
43-
, _index(0)
44-
, _response(NULL)
45-
, _responseLen(0)
46-
, _mdnsSocket()
47-
{ }
46+
const uint8_t responseHeader[] PROGMEM = {
47+
0x00, 0x00, // ID = 0
48+
0x84, 0x00, // Flags = response + authoritative answer
49+
0x00, 0x00, // Question count = 0
50+
0x00, 0x01, // Answer count = 1
51+
0x00, 0x00, // Name server records = 0
52+
0x00, 0x01 // Additional records = 1
53+
};
4854

49-
MDNSResponder::~MDNSResponder() {
50-
if (_expected != NULL) {
51-
free(_expected);
52-
}
53-
if (_response != NULL) {
54-
free(_response);
55-
}
55+
// Generate positive response for IPV4 address
56+
const uint8_t aRecord[] PROGMEM = {
57+
0x00, 0x01, // Type = 1, A record/IPV4 address
58+
0x80, 0x01, // Class = Internet, with cache flush bit
59+
0x00, 0x00, 0x00, 0x00, // TTL in seconds, to be filled in later
60+
0x00, 0x04, // Length of record
61+
0x00, 0x00, 0x00, 0x00 // IP address, to be filled in later
62+
};
63+
64+
// Generate negative response for IPV6 address (CC3000 doesn't support IPV6)
65+
const uint8_t nsecRecord[] PROGMEM = {
66+
0xC0, 0x0C, // Name offset
67+
0x00, 0x2F, // Type = 47, NSEC (overloaded by MDNS)
68+
0x80, 0x01, // Class = Internet, with cache flush bit
69+
0x00, 0x00, 0x00, 0x00, // TTL in seconds, to be filled in later
70+
0x00, 0x08, // Length of record
71+
0xC0, 0x0C, // Next domain = offset to FQDN
72+
0x00, // Block number = 0
73+
0x04, // Length of bitmap = 4 bytes
74+
0x40, 0x00, 0x00, 0x00 // Bitmap value = Only first bit (A record/IPV4) is set
75+
};
76+
77+
const uint8_t domain[] PROGMEM = { 'l', 'o', 'c', 'a', 'l' };
78+
79+
MDNSResponder::MDNSResponder() :
80+
expectedRequestLength(0)
81+
{
5682
}
5783

58-
bool MDNSResponder::begin(const char* domain, uint32_t ttlSeconds)
84+
MDNSResponder::~MDNSResponder()
5985
{
60-
// Construct DNS request/response fully qualified domain name of form:
61-
// <domain length>, <domain characters>, 5, "local"
62-
size_t n = strlen(domain);
63-
if (n > 255) {
64-
// Can only handle domains that are 255 chars in length.
65-
return false;
66-
}
67-
_expectedLen = 12 + n;
68-
if (_expected != NULL) {
69-
free(_expected);
70-
}
71-
_expected = (uint8_t*) malloc(_expectedLen);
72-
if (_expected == NULL) {
73-
return false;
74-
}
75-
_expected[0] = (uint8_t)n;
76-
// Copy in domain characters as lowercase
77-
for (unsigned int i = 0; i < n; ++i) {
78-
_expected[1+i] = tolower(domain[i]);
79-
}
80-
// Values for:
81-
// - 5 (length)
82-
// - "local"
83-
// - 0x00 (end of domain)
84-
// - 0x00 0x01 (A record query)
85-
// - 0x00 0x01 (Class IN)
86-
uint8_t local[] = { 0x05, 0x6C, 0x6F, 0x63, 0x61, 0x6C, 0x00, 0x00, 0x01, 0x00, 0x01 };
87-
memcpy(&_expected[1+n], local, 11);
88-
89-
// Construct DNS query response
90-
// TODO: Move these to flash or just construct in code.
91-
uint8_t respHeader[] = { 0x00, 0x00, // ID = 0
92-
0x84, 0x00, // Flags = response + authoritative answer
93-
0x00, 0x00, // Question count = 0
94-
0x00, 0x01, // Answer count = 1
95-
0x00, 0x00, // Name server records = 0
96-
0x00, 0x01 // Additional records = 1
97-
};
98-
// Generate positive response for IPV4 address
99-
uint8_t aRecord[] = { 0x00, 0x01, // Type = 1, A record/IPV4 address
100-
0x80, 0x01, // Class = Internet, with cache flush bit
101-
0x00, 0x00, 0x00, 0x00, // TTL in seconds, to be filled in later
102-
0x00, 0x04, // Length of record
103-
0x00, 0x00, 0x00, 0x00 // IP address, to be filled in later
104-
};
105-
// Generate negative response for IPV6 address (CC3000 doesn't support IPV6)
106-
uint8_t nsecRecord[] = { 0xC0, 0x0C, // Name offset
107-
0x00, 0x2F, // Type = 47, NSEC (overloaded by MDNS)
108-
0x80, 0x01, // Class = Internet, with cache flush bit
109-
0x00, 0x00, 0x00, 0x00, // TTL in seconds, to be filled in later
110-
0x00, 0x08, // Length of record
111-
0xC0, 0x0C, // Next domain = offset to FQDN
112-
0x00, // Block number = 0
113-
0x04, // Length of bitmap = 4 bytes
114-
0x40, 0x00, 0x00, 0x00 // Bitmap value = Only first bit (A record/IPV4) is set
115-
};
116-
// Allocate memory for response.
117-
int queryFQDNLen = _expectedLen - 4;
118-
_responseLen = HEADER_SIZE + queryFQDNLen + A_RECORD_SIZE + NSEC_RECORD_SIZE;
119-
if (_response != NULL) {
120-
free(_response);
121-
}
122-
_response = (uint8_t*) malloc(_responseLen);
123-
if (_response == NULL) {
86+
}
87+
88+
bool MDNSResponder::begin(const char* _name, uint32_t _ttlSeconds)
89+
{
90+
int nameLength = strlen(_name);
91+
92+
if (nameLength > 255) {
93+
// Can only handle domains that are upto 255 chars in length.
94+
expectedRequestLength = 0;
12495
return false;
12596
}
126-
// Copy data into response.
127-
memcpy(_response, respHeader, HEADER_SIZE);
128-
memcpy(_response + HEADER_SIZE, _expected, queryFQDNLen);
129-
uint8_t* records = _response + HEADER_SIZE + queryFQDNLen;
130-
memcpy(records, aRecord, A_RECORD_SIZE);
131-
memcpy(records + A_RECORD_SIZE, nsecRecord, NSEC_RECORD_SIZE);
132-
// Add TTL to records.
133-
uint8_t ttl[4] = { (uint8_t)(ttlSeconds >> 24), (uint8_t)(ttlSeconds >> 16), (uint8_t)(ttlSeconds >> 8), (uint8_t)ttlSeconds };
134-
memcpy(records + TTL_OFFSET, ttl, 4);
135-
memcpy(records + A_RECORD_SIZE + 2 + TTL_OFFSET, ttl, 4);
136-
// Add IP address to response
137-
uint32_t ipAddress = WiFi.localIP();
138-
records[IP_OFFSET] = (uint8_t) ipAddress;
139-
records[IP_OFFSET + 1] = (uint8_t)(ipAddress >> 8);
140-
records[IP_OFFSET + 2] = (uint8_t)(ipAddress >> 16);
141-
records[IP_OFFSET + 3] = (uint8_t)(ipAddress >> 24);
97+
98+
name = _name;
99+
ttlSeconds = _ttlSeconds;
100+
101+
name.toLowerCase();
102+
expectedRequestLength = HEADER_SIZE + 1 + nameLength + 1 + sizeof(domain) + 5;
142103

143104
// Open the MDNS UDP listening socket on port 5353 with multicast address
144105
// 224.0.0.251 (0xE00000FB)
145-
if (!_mdnsSocket.beginMulti(IPAddress(224, 0, 0, 251), 5353)) {
106+
if (!udpSocket.beginMulti(IPAddress(224, 0, 0, 251), 5353)) {
146107
return false;
147108
}
148109

149110
return true;
150111
}
151112

152-
void MDNSResponder::update() {
153-
// Check if there's data to read from the UDP socket.
154-
int available = _mdnsSocket.parsePacket();
155-
// Stop processing if no data is available.
156-
if (available <= 0) {
157-
return;
158-
}
159-
// Otherwise there is data to read so grab it all and parse the data.
160-
uint8_t buffer[READ_BUFFER_SIZE];
161-
int n = _mdnsSocket.read((unsigned char*)&buffer, sizeof(buffer));
162-
if (n < 1) {
163-
// Error getting data.
164-
return;
113+
void MDNSResponder::poll()
114+
{
115+
if (parseRequest()) {
116+
replyToRequest();
165117
}
166-
// Look for domain name in request and respond with canned response if found.
167-
for (int i = 0; i < n; ++i) {
168-
uint8_t ch = tolower(buffer[i]);
169-
// Check character matches expected.
170-
if (ch == _expected[_index])
171-
{
172-
_index++;
173-
// Check if domain name was found and send a response.
174-
if (_index >= _expectedLen) {
175-
// Send response to multicast address.
176-
_broadcastResponse();
177-
_index = 0;
118+
}
119+
120+
bool MDNSResponder::parseRequest()
121+
{
122+
if (udpSocket.parsePacket()) {
123+
// check if parsed packet matches expected request length
124+
if (udpSocket.available() != expectedRequestLength) {
125+
// it does not, read the full packet in and drop data
126+
while(udpSocket.available()) {
127+
udpSocket.read();
178128
}
129+
130+
return false;
179131
}
180-
else if (ch == _expected[0]) {
181-
// Found a character that doesn't match, but does match the start of the domain.
182-
_index = 1;
183-
}
184-
else {
185-
// Found a character that doesn't match the expected character or start of domain.
186-
_index = 0;
132+
133+
// read packet
134+
uint8_t request[expectedRequestLength];
135+
udpSocket.read(request, expectedRequestLength);
136+
137+
// parse request
138+
uint8_t requestNameLength = request[HEADER_SIZE];
139+
uint8_t* requestName = &request[HEADER_SIZE + 1];
140+
uint8_t requestDomainLength = request[HEADER_SIZE + 1 + requestNameLength];
141+
uint8_t* requestDomain = &request[HEADER_SIZE + 1 + requestNameLength + 1];
142+
uint16_t requestQtype;
143+
uint16_t requestQclass;
144+
145+
memcpy(&requestQtype, &request[expectedRequestLength - 4], sizeof(requestQtype));
146+
memcpy(&requestQclass, &request[expectedRequestLength - 2], sizeof(requestQclass));
147+
148+
requestQtype = _ntohs(requestQtype);
149+
requestQclass = _ntohs(requestQclass);
150+
151+
// compare request
152+
if (memcmp_P(request, expectedRequestHeader, HEADER_SIZE) == 0 && // request header match
153+
requestNameLength == name.length() && // name length match
154+
strncasecmp(name.c_str(), (char*)requestName, requestNameLength) == 0 && // name match
155+
requestDomainLength == sizeof(domain) && // domain length match
156+
memcmp_P(requestDomain, domain, requestDomainLength) == 0 && // suffix match
157+
requestQtype == 0x0001 && // request QType match
158+
(requestQclass == 0x0001 || requestQclass == 0x8001) ) { // request QClass match
159+
160+
return true;
187161
}
188162
}
163+
164+
return false;
189165
}
190166

191-
void MDNSResponder::_broadcastResponse() {
192-
// Send a MDNS name query response for this device.
193-
// Use the MDNS multicast address 224.0.0.251 port 5353.
194-
_mdnsSocket.beginPacket(IPAddress(224, 0, 0, 251), 5353);
195-
_mdnsSocket.write(_response, _responseLen);
196-
_mdnsSocket.endPacket();
167+
void MDNSResponder::replyToRequest()
168+
{
169+
int nameLength = name.length();
170+
int domainLength = sizeof(domain);
171+
uint32_t ipAddress = WiFi.localIP();
172+
uint32_t ttl = _htonl(ttlSeconds);
173+
174+
int responseSize = sizeof(responseHeader) + 1 + nameLength + 1 + domainLength + 1 + sizeof(aRecord) + sizeof(nsecRecord);
175+
uint8_t response[responseSize];
176+
uint8_t* r = response;
177+
178+
// copy header
179+
memcpy_P(r, responseHeader, sizeof(responseHeader));
180+
r += sizeof(responseHeader);
181+
182+
// copy name
183+
*r = nameLength;
184+
memcpy(r + 1, name.c_str(), nameLength);
185+
r += (1 + nameLength);
186+
187+
// copy domain
188+
*r = domainLength;
189+
memcpy_P(r + 1, domain, domainLength);
190+
r += (1 + domainLength);
191+
192+
// terminator
193+
*r = 0x00;
194+
r++;
195+
196+
// copy A record
197+
memcpy_P(r, aRecord, sizeof(aRecord));
198+
memcpy(r + TTL_OFFSET, &ttl, sizeof(ttl)); // replace TTL value
199+
memcpy(r + IP_OFFSET, &ipAddress, sizeof(ipAddress)); // replace IP address value
200+
r += sizeof(aRecord);
201+
202+
// copy NSEC record
203+
memcpy_P(r, nsecRecord, sizeof(nsecRecord));
204+
r += sizeof(nsecRecord);
205+
206+
udpSocket.beginPacket(IPAddress(224, 0, 0, 251), 5353);
207+
udpSocket.write(response, responseSize);
208+
udpSocket.endPacket();
197209
}

src/WiFiMdns.h

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,23 +31,21 @@ class MDNSResponder {
3131
public:
3232
MDNSResponder();
3333
~MDNSResponder();
34-
bool begin(const char* domain, uint32_t ttlSeconds = 3600);
35-
void update();
34+
bool begin(const char* _name, uint32_t _ttlSeconds = 3600);
35+
void poll();
3636

3737
private:
38-
void _broadcastResponse();
39-
40-
// Expected query values
41-
static uint8_t _queryHeader[];
42-
uint8_t* _expected;
43-
int _expectedLen;
44-
// Current parsing state
45-
int _index;
46-
// Response data
47-
uint8_t* _response;
48-
int _responseLen;
38+
bool parseRequest();
39+
void replyToRequest();
40+
41+
private:
42+
String name;
43+
uint32_t ttlSeconds;
44+
45+
int expectedRequestLength;
46+
4947
// UDP socket for receiving/sending MDNS data.
50-
WiFiUDP _mdnsSocket;
48+
WiFiUDP udpSocket;
5149
};
5250

5351
#endif

0 commit comments

Comments
 (0)