agileRTOS (zrtos)  Version 0.8.0 (ghostbuster)
dns.h
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2024 ykat UG (haftungsbeschraenkt) - All Rights Reserved
3  *
4  * Permission for non-commercial use is hereby granted,
5  * free of charge, without warranty of any kind.
6  */
7 #ifndef ZRTOS_VFS_MODULE_DNS_H
8 #define ZRTOS_VFS_MODULE_DNS_H
9 #ifdef __cplusplus
10 extern "C" {
11 #endif
12 
13 #include <zrtos/types.h>
14 
15 typedef enum{
24 
25 typedef enum{
31 
32 typedef struct{
33  uint16_t id;
36  uint16_t questions_length;
37  uint16_t answers_length;
38  uint16_t authrr_length;
39  uint16_t extrarr_length;
40 }__attribute__((packed))zrtos_vfs_module_dns_header_t;
41 
42 typedef struct{
43  /* DNS query record starts with either a domain name or a pointer
44  to a name already present somewhere in the packet. */
45  u16_t type;
46  u16_t cls;
48 
49 typedef struct{
50  /* DNS query record starts with either a domain name or a pointer
51  to a name already present somewhere in the packet. */
52  u16_t type;
53  u16_t cls;
54  u32_t ttl;
55  u16_t len;
57 
58 typedef struct{
59  u8_t state;
60  u8_t numdns;
61  u8_t tmr;
62  u8_t retries;
63  u8_t seqno;
64  u8_t err;
65  u32_t ttl;
66  char name[DNS_MAX_NAME_LENGTH];
67  ip_addr_t ipaddr;
68  /* pointer to callback on DNS query done */
69  dns_found_callback found;
70  void *arg;
72 
73 #define SOCKET_NONE 255
74 // Various flags and header field values for a DNS message
75 #define UDP_HEADER_SIZE 8
76 ZRTOS_VFS_MODULE_DNS_HEADER_SIZE 12
77 #define TTL_SIZE 4
78 #define QUERY_FLAG (0)
79 #define RESPONSE_FLAG (1<<15)
80 #define QUERY_RESPONSE_MASK (1<<15)
81 #define OPCODE_STANDARD_QUERY (0)
82 #define OPCODE_INVERSE_QUERY (1<<11)
83 #define OPCODE_STATUS_REQUEST (2<<11)
84 #define OPCODE_MASK (15<<11)
85 #define AUTHORITATIVE_FLAG (1<<10)
86 #define TRUNCATION_FLAG (1<<9)
87 #define RECURSION_DESIRED_FLAG (1<<8)
88 #define RECURSION_AVAILABLE_FLAG (1<<7)
89 #define RESP_NO_ERROR (0)
90 #define RESP_FORMAT_ERROR (1)
91 #define RESP_SERVER_FAILURE (2)
92 #define RESP_NAME_ERROR (3)
93 #define RESP_NOT_IMPLEMENTED (4)
94 #define RESP_REFUSED (5)
95 #define RESP_MASK (15)
96 #define TYPE_A (0x0001)
97 #define CLASS_IN (0x0001)
98 #define LABEL_COMPRESSION_MASK (0xC0)
99 // Port number that DNS servers listen on
100 ZRTOS_VFS_MODULE_DNS_PORT 53
101 
102 // Possible return codes from ProcessResponse
103 #define SUCCESS 1
104 #define TIMED_OUT -1
105 #define INVALID_SERVER -2
106 #define TRUNCATED -3
107 #define INVALID_RESPONSE -4
108 
109 void DNSClient::begin(const IPAddress& aDNSServer)
110 {
111  iDNSServer = aDNSServer;
112  iRequestId = 0;
113 }
114 
115 
116 int DNSClient::inet_aton(const char* aIPAddrString, IPAddress& aResult)
117 {
118  // See if we've been given a valid IP address
119  const char* p =aIPAddrString;
120  while (*p &&
121  ( (*p == '.') || ((*p >= '0') && (*p <= '9')) ))
122  {
123  p++;
124  }
125 
126  if (*p == '\0')
127  {
128  // It's looking promising, we haven't found any invalid characters
129  p = aIPAddrString;
130  int segment =0;
131  int segmentValue =0;
132  while (*p && (segment < 4))
133  {
134  if (*p == '.')
135  {
136  // We've reached the end of a segment
137  if (segmentValue > 255)
138  {
139  // You can't have IP address segments that don't fit in a byte
140  return 0;
141  }
142  else
143  {
144  aResult[segment] = (byte)segmentValue;
145  segment++;
146  segmentValue = 0;
147  }
148  }
149  else
150  {
151  // Next digit
152  segmentValue = (segmentValue*10)+(*p - '0');
153  }
154  p++;
155  }
156  // We've reached the end of address, but there'll still be the last
157  // segment to deal with
158  if ((segmentValue > 255) || (segment > 3))
159  {
160  // You can't have IP address segments that don't fit in a byte,
161  // or more than four segments
162  return 0;
163  }
164  else
165  {
166  aResult[segment] = (byte)segmentValue;
167  return 1;
168  }
169  }
170  else
171  {
172  return 0;
173  }
174 }
175 
176 int DNSClient::getHostByName(const char* aHostname, IPAddress& aResult)
177 {
178  int ret =0;
179 
180  // See if it's a numeric IP address
181  if (inet_aton(aHostname, aResult))
182  {
183  // It is, our work here is done
184  return 1;
185  }
186 
187  // Check we've got a valid DNS server to use
188  if (iDNSServer == INADDR_NONE)
189  {
190  return INVALID_SERVER;
191  }
192 
193  // Find a socket to use
194  if (iUdp.begin(1024+(millis() & 0xF)) == 1)
195  {
196  // Try up to three times
197  int retries = 0;
198 // while ((retries < 3) && (ret <= 0))
199  {
200  // Send DNS request
201  ret = iUdp.beginPacket(iDNSServer, DNS_PORT);
202  if (ret != 0)
203  {
204  // Now output the request data
205  ret = BuildRequest(aHostname);
206  if (ret != 0)
207  {
208  // And finally send the request
209  ret = iUdp.endPacket();
210  if (ret != 0)
211  {
212  // Now wait for a response
213  int wait_retries = 0;
214  ret = TIMED_OUT;
215  while ((wait_retries < 3) && (ret == TIMED_OUT))
216  {
217  ret = ProcessResponse(5000, aResult);
218  wait_retries++;
219  }
220  }
221  }
222  }
223  retries++;
224  }
225 
226  // We're done with the socket now
227  iUdp.stop();
228  }
229 
230  return ret;
231 }
232 
233 uint16_t DNSClient::BuildRequest(const char* aName)
234 {
235  // Build header
236  // 1 1 1 1 1 1
237  // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
238  // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
239  // | ID |
240  // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
241  // |QR| Opcode |AA|TC|RD|RA| Z | RCODE |
242  // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
243  // | QDCOUNT |
244  // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
245  // | ANCOUNT |
246  // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
247  // | NSCOUNT |
248  // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
249  // | ARCOUNT |
250  // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
251  // As we only support one request at a time at present, we can simplify
252  // some of this header
253  iRequestId = millis(); // generate a random ID
254  uint16_t twoByteBuffer;
255 
256  // FIXME We should also check that there's enough space available to write to, rather
257  // FIXME than assume there's enough space (as the code does at present)
258  iUdp.write((uint8_t*)&iRequestId, sizeof(iRequestId));
259 
260  twoByteBuffer = htons(QUERY_FLAG | OPCODE_STANDARD_QUERY | RECURSION_DESIRED_FLAG);
261  iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer));
262 
263  twoByteBuffer = htons(1); // One question record
264  iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer));
265 
266  twoByteBuffer = 0; // Zero answer records
267  iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer));
268 
269  iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer));
270  // and zero additional records
271  iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer));
272 
273  // Build question
274  const char* start =aName;
275  const char* end =start;
276  uint8_t len;
277  // Run through the name being requested
278  while (*end)
279  {
280  // Find out how long this section of the name is
281  end = start;
282  while (*end && (*end != '.') )
283  {
284  end++;
285  }
286 
287  if (end-start > 0)
288  {
289  // Write out the size of this section
290  len = end-start;
291  iUdp.write(&len, sizeof(len));
292  // And then write out the section
293  iUdp.write((uint8_t*)start, end-start);
294  }
295  start = end+1;
296  }
297 
298  // We've got to the end of the question name, so
299  // terminate it with a zero-length section
300  len = 0;
301  iUdp.write(&len, sizeof(len));
302  // Finally the type and class of question
303  twoByteBuffer = htons(TYPE_A);
304  iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer));
305 
306  twoByteBuffer = htons(CLASS_IN); // Internet class of question
307  iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer));
308  // Success! Everything buffered okay
309  return 1;
310 }
311 
312 
313 uint16_t DNSClient::ProcessResponse(uint16_t aTimeout, IPAddress& aAddress)
314 {
315  uint32_t startTime = millis();
316 
317  // Wait for a response packet
318  while(iUdp.parsePacket() <= 0)
319  {
320  if((millis() - startTime) > aTimeout)
321  return TIMED_OUT;
322  delay(50);
323  }
324 
325  // We've had a reply!
326  // Read the UDP header
327  uint8_t header[DNS_HEADER_SIZE]; // Enough space to reuse for the DNS header
328  // Check that it's a response from the right server and the right port
329  if ( (iDNSServer != iUdp.remoteIP()) ||
330  (iUdp.remotePort() != DNS_PORT) )
331  {
332  // It's not from who we expected
333  return INVALID_SERVER;
334  }
335 
336  // Read through the rest of the response
337  if (iUdp.available() < DNS_HEADER_SIZE)
338  {
339  return TRUNCATED;
340  }
341  iUdp.read(header, DNS_HEADER_SIZE);
342 
343  uint16_t header_flags = htons(*((uint16_t*)&header[2]));
344  // Check that it's a response to this request
345  if ( ( iRequestId != (*((uint16_t*)&header[0])) ) ||
346  ((header_flags & QUERY_RESPONSE_MASK) != (uint16_t)RESPONSE_FLAG) )
347  {
348  // Mark the entire packet as read
349  iUdp.flush();
350  return INVALID_RESPONSE;
351  }
352  // Check for any errors in the response (or in our request)
353  // although we don't do anything to get round these
354  if ( (header_flags & TRUNCATION_FLAG) || (header_flags & RESP_MASK) )
355  {
356  // Mark the entire packet as read
357  iUdp.flush();
358  return -5; //INVALID_RESPONSE;
359  }
360 
361  // And make sure we've got (at least) one answer
362  uint16_t answerCount = htons(*((uint16_t*)&header[6]));
363  if (answerCount == 0 )
364  {
365  // Mark the entire packet as read
366  iUdp.flush();
367  return -6; //INVALID_RESPONSE;
368  }
369 
370  // Skip over any questions
371  for (uint16_t i =0; i < htons(*((uint16_t*)&header[4])); i++)
372  {
373  // Skip over the name
374  uint8_t len;
375  do
376  {
377  iUdp.read(&len, sizeof(len));
378  if (len > 0)
379  {
380  // Don't need to actually read the data out for the string, just
381  // advance ptr to beyond it
382  while(len--)
383  {
384  iUdp.read(); // we don't care about the returned byte
385  }
386  }
387  } while (len != 0);
388 
389  // Now jump over the type and class
390  for (int i =0; i < 4; i++)
391  {
392  iUdp.read(); // we don't care about the returned byte
393  }
394  }
395 
396  // Now we're up to the bit we're interested in, the answer
397  // There might be more than one answer (although we'll just use the first
398  // type A answer) and some authority and additional resource records but
399  // we're going to ignore all of them.
400 
401  for (uint16_t i =0; i < answerCount; i++)
402  {
403  // Skip the name
404  uint8_t len;
405  do
406  {
407  iUdp.read(&len, sizeof(len));
408  if ((len & LABEL_COMPRESSION_MASK) == 0)
409  {
410  // It's just a normal label
411  if (len > 0)
412  {
413  // And it's got a length
414  // Don't need to actually read the data out for the string,
415  // just advance ptr to beyond it
416  while(len--)
417  {
418  iUdp.read(); // we don't care about the returned byte
419  }
420  }
421  }
422  else
423  {
424  // This is a pointer to a somewhere else in the message for the
425  // rest of the name. We don't care about the name, and RFC1035
426  // says that a name is either a sequence of labels ended with a
427  // 0 length octet or a pointer or a sequence of labels ending in
428  // a pointer. Either way, when we get here we're at the end of
429  // the name
430  // Skip over the pointer
431  iUdp.read(); // we don't care about the returned byte
432  // And set len so that we drop out of the name loop
433  len = 0;
434  }
435  } while (len != 0);
436 
437  // Check the type and class
438  uint16_t answerType;
439  uint16_t answerClass;
440  iUdp.read((uint8_t*)&answerType, sizeof(answerType));
441  iUdp.read((uint8_t*)&answerClass, sizeof(answerClass));
442 
443  // Ignore the Time-To-Live as we don't do any caching
444  for (int i =0; i < TTL_SIZE; i++)
445  {
446  iUdp.read(); // we don't care about the returned byte
447  }
448 
449  // And read out the length of this answer
450  // Don't need header_flags anymore, so we can reuse it here
451  iUdp.read((uint8_t*)&header_flags, sizeof(header_flags));
452 
453  if ( (htons(answerType) == TYPE_A) && (htons(answerClass) == CLASS_IN) )
454  {
455  if (htons(header_flags) != 4)
456  {
457  // It's a weird size
458  // Mark the entire packet as read
459  iUdp.flush();
460  return -9;//INVALID_RESPONSE;
461  }
462  iUdp.read(aAddress.raw_address(), 4);
463  return SUCCESS;
464  }
465  else
466  {
467  // This isn't an answer type we're after, move onto the next one
468  for (uint16_t i =0; i < htons(header_flags); i++)
469  {
470  iUdp.read(); // we don't care about the returned byte
471  }
472  }
473  }
474 
475  // Mark the entire packet as read
476  iUdp.flush();
477 
478  // If we get here then we haven't found an answer
479  return -10;//INVALID_RESPONSE;
480 }
481 
482 #ifdef __cplusplus
483 }
484 #endif
485 #endif
#define TIMED_OUT
Definition: dns.h:104
#define INVALID_RESPONSE
Definition: dns.h:107
#define SUCCESS
Definition: dns.h:103
u8_t tmr
Definition: dns.h:61
#define RESP_MASK
Definition: dns.h:95
u8_t retries
Definition: dns.h:62
#define CLASS_IN
Definition: dns.h:97
#define INVALID_SERVER
Definition: dns.h:105
zrtos_vfs_module_dns_header_flags_1_t
Definition: dns.h:25
u32_t ttl
Definition: dns.h:65
#define QUERY_FLAG
Definition: dns.h:78
ip_addr_t ipaddr
Definition: dns.h:67
u8_t numdns
Definition: dns.h:60
u8_t state
Definition: dns.h:59
#define LABEL_COMPRESSION_MASK
Definition: dns.h:98
dns_found_callback found
Definition: dns.h:69
zrtos_vfs_module_dns_header_flags_0_t flags0
Definition: dns.h:34
Definition: dns.h:58
#define TRUNCATED
Definition: dns.h:106
#define QUERY_RESPONSE_MASK
Definition: dns.h:80
zrtos_vfs_module_dns_header_flags_1_t flags1
Definition: dns.h:35
void * arg
Definition: dns.h:70
#define TTL_SIZE
Definition: dns.h:77
#define TRUNCATION_FLAG
Definition: dns.h:86
static uint8_t
Definition: mcp2515.h:159
#define TYPE_A
Definition: dns.h:96
u8_t seqno
Definition: dns.h:63
uint16_t questions_length
Definition: dns.h:36
u8_t err
Definition: dns.h:64
#define RESPONSE_FLAG
Definition: dns.h:79
zrtos_vfs_module_dns_header_flags_0_t
Definition: dns.h:15
#define RECURSION_DESIRED_FLAG
Definition: dns.h:87
#define OPCODE_STANDARD_QUERY
Definition: dns.h:81