1 // Arduino DNS client for WizNet5100-based Ethernet shield
2 // (c) Copyright 2009-2010 MCQN Ltd.
3 // Released under Apache License, version 2.0
4 
5 #include "utility/w5100.h"
6 #include "EthernetUdp.h"
7 #include "utility/util.h"
8 
9 #include "Dns.h"
10 #include <string.h>
11 //#include <stdlib.h>
12 #include "Arduino.h"
13 
14 
15 #define SOCKET_NONE	255
16 // Various flags and header field values for a DNS message
17 #define UDP_HEADER_SIZE	8
18 #define DNS_HEADER_SIZE	12
19 #define TTL_SIZE        4
20 #define QUERY_FLAG               (0)
21 #define RESPONSE_FLAG            (1<<15)
22 #define QUERY_RESPONSE_MASK      (1<<15)
23 #define OPCODE_STANDARD_QUERY    (0)
24 #define OPCODE_INVERSE_QUERY     (1<<11)
25 #define OPCODE_STATUS_REQUEST    (2<<11)
26 #define OPCODE_MASK              (15<<11)
27 #define AUTHORITATIVE_FLAG       (1<<10)
28 #define TRUNCATION_FLAG          (1<<9)
29 #define RECURSION_DESIRED_FLAG   (1<<8)
30 #define RECURSION_AVAILABLE_FLAG (1<<7)
31 #define RESP_NO_ERROR            (0)
32 #define RESP_FORMAT_ERROR        (1)
33 #define RESP_SERVER_FAILURE      (2)
34 #define RESP_NAME_ERROR          (3)
35 #define RESP_NOT_IMPLEMENTED     (4)
36 #define RESP_REFUSED             (5)
37 #define RESP_MASK                (15)
38 #define TYPE_A                   (0x0001)
39 #define CLASS_IN                 (0x0001)
40 #define LABEL_COMPRESSION_MASK   (0xC0)
41 // Port number that DNS servers listen on
42 #define DNS_PORT        53
43 
44 // Possible return codes from ProcessResponse
45 #define SUCCESS          1
46 #define TIMED_OUT        -1
47 #define INVALID_SERVER   -2
48 #define TRUNCATED        -3
49 #define INVALID_RESPONSE -4
50 
begin(const IPAddress & aDNSServer)51 void DNSClient::begin(const IPAddress& aDNSServer)
52 {
53     iDNSServer = aDNSServer;
54     iRequestId = 0;
55 }
56 
57 
inet_aton(const char * address,IPAddress & result)58 int DNSClient::inet_aton(const char* address, IPAddress& result)
59 {
60     uint16_t acc = 0; // Accumulator
61     uint8_t dots = 0;
62 
63     while (*address)
64     {
65         char c = *address++;
66         if (c >= '0' && c <= '9')
67         {
68             acc = acc * 10 + (c - '0');
69             if (acc > 255) {
70                 // Value out of [0..255] range
71                 return 0;
72             }
73         }
74         else if (c == '.')
75         {
76             if (dots == 3) {
77                 // Too much dots (there must be 3 dots)
78                 return 0;
79             }
80             result[dots++] = acc;
81             acc = 0;
82         }
83         else
84         {
85             // Invalid char
86             return 0;
87         }
88     }
89 
90     if (dots != 3) {
91         // Too few dots (there must be 3 dots)
92         return 0;
93     }
94     result[3] = acc;
95     return 1;
96 }
97 
getHostByName(const char * aHostname,IPAddress & aResult)98 int DNSClient::getHostByName(const char* aHostname, IPAddress& aResult)
99 {
100     int ret =0;
101 
102     // See if it's a numeric IP address
103     if (inet_aton(aHostname, aResult))
104     {
105         // It is, our work here is done
106         return 1;
107     }
108 
109     // Check we've got a valid DNS server to use
110     if (iDNSServer == INADDR_NONE)
111     {
112         return INVALID_SERVER;
113     }
114 
115     // Find a socket to use
116     if (iUdp.begin(1024+(millis() & 0xF)) == 1)
117     {
118         // Try up to three times
119         int retries = 0;
120 //        while ((retries < 3) && (ret <= 0))
121         {
122             // Send DNS request
123             ret = iUdp.beginPacket(iDNSServer, DNS_PORT);
124             if (ret != 0)
125             {
126                 // Now output the request data
127                 ret = BuildRequest(aHostname);
128                 if (ret != 0)
129                 {
130                     // And finally send the request
131                     ret = iUdp.endPacket();
132                     if (ret != 0)
133                     {
134                         // Now wait for a response
135                         int wait_retries = 0;
136                         ret = TIMED_OUT;
137                         while ((wait_retries < 3) && (ret == TIMED_OUT))
138                         {
139                             ret = ProcessResponse(5000, aResult);
140                             wait_retries++;
141                         }
142                     }
143                 }
144             }
145             retries++;
146         }
147 
148         // We're done with the socket now
149         iUdp.stop();
150     }
151 
152     return ret;
153 }
154 
BuildRequest(const char * aName)155 uint16_t DNSClient::BuildRequest(const char* aName)
156 {
157     // Build header
158     //                                    1  1  1  1  1  1
159     //      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
160     //    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
161     //    |                      ID                       |
162     //    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
163     //    |QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
164     //    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
165     //    |                    QDCOUNT                    |
166     //    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
167     //    |                    ANCOUNT                    |
168     //    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
169     //    |                    NSCOUNT                    |
170     //    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
171     //    |                    ARCOUNT                    |
172     //    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
173     // As we only support one request at a time at present, we can simplify
174     // some of this header
175     iRequestId = millis(); // generate a random ID
176     uint16_t twoByteBuffer;
177 
178     // FIXME We should also check that there's enough space available to write to, rather
179     // FIXME than assume there's enough space (as the code does at present)
180     uint16_t _id = htons(iRequestId);
181     iUdp.write((uint8_t*)&_id, sizeof(_id));
182 
183     twoByteBuffer = htons(QUERY_FLAG | OPCODE_STANDARD_QUERY | RECURSION_DESIRED_FLAG);
184     iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer));
185 
186     twoByteBuffer = htons(1);  // One question record
187     iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer));
188 
189     twoByteBuffer = 0;  // Zero answer records
190     iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer));
191 
192     iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer));
193     // and zero additional records
194     iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer));
195 
196     // Build question
197     const char* start =aName;
198     const char* end =start;
199     uint8_t len;
200     // Run through the name being requested
201     while (*end)
202     {
203         // Find out how long this section of the name is
204         end = start;
205         while (*end && (*end != '.') )
206         {
207             end++;
208         }
209 
210         if (end-start > 0)
211         {
212             // Write out the size of this section
213             len = end-start;
214             iUdp.write(&len, sizeof(len));
215             // And then write out the section
216             iUdp.write((uint8_t*)start, end-start);
217         }
218         start = end+1;
219     }
220 
221     // We've got to the end of the question name, so
222     // terminate it with a zero-length section
223     len = 0;
224     iUdp.write(&len, sizeof(len));
225     // Finally the type and class of question
226     twoByteBuffer = htons(TYPE_A);
227     iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer));
228 
229     twoByteBuffer = htons(CLASS_IN);  // Internet class of question
230     iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer));
231     // Success!  Everything buffered okay
232     return 1;
233 }
234 
235 
ProcessResponse(uint16_t aTimeout,IPAddress & aAddress)236 uint16_t DNSClient::ProcessResponse(uint16_t aTimeout, IPAddress& aAddress)
237 {
238     uint32_t startTime = millis();
239 
240     // Wait for a response packet
241     while(iUdp.parsePacket() <= 0)
242     {
243         if((millis() - startTime) > aTimeout)
244             return TIMED_OUT;
245         delay(50);
246     }
247 
248     // We've had a reply!
249     // Read the UDP header
250     uint8_t header[DNS_HEADER_SIZE]; // Enough space to reuse for the DNS header
251     // Check that it's a response from the right server and the right port
252     if ( (iDNSServer != iUdp.remoteIP()) ||
253         (iUdp.remotePort() != DNS_PORT) )
254     {
255         // It's not from who we expected
256         return INVALID_SERVER;
257     }
258 
259     // Read through the rest of the response
260     if (iUdp.available() < DNS_HEADER_SIZE)
261     {
262         return TRUNCATED;
263     }
264     iUdp.read(header, DNS_HEADER_SIZE);
265 
266     uint16_t header_flags = word(header[2], header[3]);
267     // Check that it's a response to this request
268     if ( (iRequestId != word(header[0], header[1])) ||
269         ((header_flags & QUERY_RESPONSE_MASK) != (uint16_t)RESPONSE_FLAG) )
270     {
271         // Mark the entire packet as read
272         iUdp.flush();
273         return INVALID_RESPONSE;
274     }
275     // Check for any errors in the response (or in our request)
276     // although we don't do anything to get round these
277     if ( (header_flags & TRUNCATION_FLAG) || (header_flags & RESP_MASK) )
278     {
279         // Mark the entire packet as read
280         iUdp.flush();
281         return -5; //INVALID_RESPONSE;
282     }
283 
284     // And make sure we've got (at least) one answer
285     uint16_t answerCount = word(header[6], header[7]);
286     if (answerCount == 0 )
287     {
288         // Mark the entire packet as read
289         iUdp.flush();
290         return -6; //INVALID_RESPONSE;
291     }
292 
293     // Skip over any questions
294     for (uint16_t i =0; i < word(header[4], header[5]); i++)
295     {
296         // Skip over the name
297         uint8_t len;
298         do
299         {
300             iUdp.read(&len, sizeof(len));
301             if (len > 0)
302             {
303                 // Don't need to actually read the data out for the string, just
304                 // advance ptr to beyond it
305                 while(len--)
306                 {
307                     iUdp.read(); // we don't care about the returned byte
308                 }
309             }
310         } while (len != 0);
311 
312         // Now jump over the type and class
313         for (int i =0; i < 4; i++)
314         {
315             iUdp.read(); // we don't care about the returned byte
316         }
317     }
318 
319     // Now we're up to the bit we're interested in, the answer
320     // There might be more than one answer (although we'll just use the first
321     // type A answer) and some authority and additional resource records but
322     // we're going to ignore all of them.
323 
324     for (uint16_t i =0; i < answerCount; i++)
325     {
326         // Skip the name
327         uint8_t len;
328         do
329         {
330             iUdp.read(&len, sizeof(len));
331             if ((len & LABEL_COMPRESSION_MASK) == 0)
332             {
333                 // It's just a normal label
334                 if (len > 0)
335                 {
336                     // And it's got a length
337                     // Don't need to actually read the data out for the string,
338                     // just advance ptr to beyond it
339                     while(len--)
340                     {
341                         iUdp.read(); // we don't care about the returned byte
342                     }
343                 }
344             }
345             else
346             {
347                 // This is a pointer to a somewhere else in the message for the
348                 // rest of the name.  We don't care about the name, and RFC1035
349                 // says that a name is either a sequence of labels ended with a
350                 // 0 length octet or a pointer or a sequence of labels ending in
351                 // a pointer.  Either way, when we get here we're at the end of
352                 // the name
353                 // Skip over the pointer
354                 iUdp.read(); // we don't care about the returned byte
355                 // And set len so that we drop out of the name loop
356                 len = 0;
357             }
358         } while (len != 0);
359 
360         // Check the type and class
361         uint16_t answerType;
362         uint16_t answerClass;
363         iUdp.read((uint8_t*)&answerType, sizeof(answerType));
364         iUdp.read((uint8_t*)&answerClass, sizeof(answerClass));
365 
366         // Ignore the Time-To-Live as we don't do any caching
367         for (int i =0; i < TTL_SIZE; i++)
368         {
369             iUdp.read(); // we don't care about the returned byte
370         }
371 
372         // And read out the length of this answer
373         // Don't need header_flags anymore, so we can reuse it here
374         iUdp.read((uint8_t*)&header_flags, sizeof(header_flags));
375 
376         if ( (htons(answerType) == TYPE_A) && (htons(answerClass) == CLASS_IN) )
377         {
378             if (htons(header_flags) != 4)
379             {
380                 // It's a weird size
381                 // Mark the entire packet as read
382                 iUdp.flush();
383                 return -9;//INVALID_RESPONSE;
384             }
385             iUdp.read(aAddress.raw_address(), 4);
386             return SUCCESS;
387         }
388         else
389         {
390             // This isn't an answer type we're after, move onto the next one
391             for (uint16_t i =0; i < htons(header_flags); i++)
392             {
393                 iUdp.read(); // we don't care about the returned byte
394             }
395         }
396     }
397 
398     // Mark the entire packet as read
399     iUdp.flush();
400 
401     // If we get here then we haven't found an answer
402     return -10;//INVALID_RESPONSE;
403 }
404 
405