1 /******************************************************************************
2 * Copyright (c) 2004, 2008 IBM Corporation
3 * All rights reserved.
4 * This program and the accompanying materials
5 * are made available under the terms of the BSD License
6 * which accompanies this distribution, and is available at
7 * http://www.opensource.org/licenses/bsd-license.php
8 *
9 * Contributors:
10 * IBM Corporation - initial implementation
11 *****************************************************************************/
12
13 /********************** DEFINITIONS & DECLARATIONS ***********************/
14
15 #include <dns.h>
16 #include <stdio.h>
17 #include <string.h>
18 #include <time.h>
19 #include <sys/socket.h>
20
21 #include <ethernet.h>
22 #include <ipv4.h>
23 #include <ipv6.h>
24 #include <udp.h>
25
26 #define DNS_FLAG_MSGTYPE 0xF800 /**< Message type mask (opcode) */
27 #define DNS_FLAG_SQUERY 0x0000 /**< Standard query type */
28 #define DNS_FLAG_SRESPONSE 0x8000 /**< Standard response type */
29 #define DNS_FLAG_RD 0x0100 /**< Recursion desired flag */
30 #define DNS_FLAG_RCODE 0x000F /**< Response code mask
31 (stores err.cond.) code */
32 #define DNS_RCODE_NERROR 0 /**< "No errors" code */
33
34 #define DNS_QTYPE_A 1 /**< A 32-bit IP record type */
35 #define DNS_QTYPE_AAAA 0x1c /**< 128-bit IPv6 record type */
36 #define DNS_QTYPE_CNAME 5 /**< Canonical name record type */
37
38 #define DNS_QCLASS_IN 1 /**< Query class for internet msgs */
39
40 /** \struct dnshdr
41 * A header for DNS-messages (see RFC 1035, paragraph 4.1.1).
42 * <p>
43 * DNS-message consist of DNS-header and 4 optional sections,
44 * arranged in the following order:<ul>
45 * <li> DNS-header
46 * <li> question section
47 * <li> answer section
48 * <li> authority section
49 * <li> additional section
50 * </ul>
51 */
52 struct dnshdr {
53 uint16_t id; /**< an identifier used to match up replies */
54 uint16_t flags; /**< contains op_code, err_code, etc. */
55 uint16_t qdcount; /**< specifies the number of entries in the
56 question section */
57 uint16_t ancount; /**< specifies the number of entries in the
58 answer section */
59 uint16_t nscount; /**< specifies the number of entries in the
60 authority section */
61 uint16_t arcount; /**< specifies the number of entries in the
62 additional section */
63 };
64
65
66 /***************************** PROTOTYPES ********************************/
67
68 static void
69 dns_send_query(int fd, int8_t * domain_name, uint8_t ip_version);
70
71 static void
72 fill_dnshdr(uint8_t * packet, int8_t * domain_name, uint8_t ip_version);
73
74 static uint8_t *
75 dns_extract_name(uint8_t * dnsh, int8_t * head, int8_t * domain_name);
76
77 static int8_t
78 urltohost(char * url, char * host_name);
79
80 static int8_t
81 hosttodomain(char * host_name, char * domain_name);
82
83 /**************************** LOCAL VARIABLES ****************************/
84
85 static uint8_t ether_packet[ETH_MTU_SIZE];
86 static int32_t dns_server_ip = 0;
87 static uint8_t dns_server_ipv6[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
88 static int32_t dns_result_ip = 0;
89 static uint8_t dns_result_ipv6[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
90 static int8_t dns_error = 0; /**< Stores error code or 0 */
91 static int8_t dns_domain_name[0x100]; /**< Raw domain name */
92 static int8_t dns_domain_cname[0x100]; /**< Canonical domain name */
93
94 /**************************** IMPLEMENTATION *****************************/
95
96 /**
97 * DNS: Initialize the environment for DNS client.
98 * To perfrom DNS-queries use the function dns_get_ip.
99 *
100 * @param device_socket a socket number used to send and receive packets
101 * @param server_ip DNS-server IPv4 address (e.g. 127.0.0.1)
102 * @return TRUE in case of successful initialization;
103 * FALSE in case of fault (e.g. can't obtain MAC).
104 * @see dns_get_ip
105 */
106 int8_t
dns_init(uint32_t _dns_server_ip,uint8_t _dns_server_ipv6[16],uint8_t ip_version)107 dns_init(uint32_t _dns_server_ip, uint8_t _dns_server_ipv6[16], uint8_t ip_version)
108 {
109 if(ip_version == 6)
110 memcpy(dns_server_ipv6, _dns_server_ipv6, 16);
111 else
112 dns_server_ip = _dns_server_ip;
113 return 0;
114 }
115
116 /**
117 * DNS: For given URL retrieves IPv4/IPv6 from DNS-server.
118 * <p>
119 * URL can be given in one of the following form: <ul>
120 * <li> scheme with full path with (without) user and password
121 * <br>(e.g. "http://user:pass@www.host.org/url-path");
122 * <li> host name with url-path
123 * <br>(e.g. "www.host.org/url-path");
124 * <li> nothing but host name
125 * <br>(e.g. "www.host.org");
126 * </ul>
127 *
128 * @param fd socket descriptor
129 * @param url the URL to be resolved
130 * @param domain_ip In case of SUCCESS stores extracted IP.
131 * In case of FAULT stores zeros (0.0.0.0).
132 * @return TRUE - IP successfuly retrieved;
133 * FALSE - error condition occurs.
134 */
135 int8_t
dns_get_ip(int fd,char * url,uint8_t * domain_ip,uint8_t ip_version)136 dns_get_ip(int fd, char* url, uint8_t * domain_ip, uint8_t ip_version)
137 {
138 /* this counter is used so that we abort after 30 DNS request */
139 int32_t i;
140 /* this buffer stores host name retrieved from url */
141 static int8_t host_name[0x100];
142
143 (* domain_ip) = 0;
144
145 // Retrieve host name from URL
146 if (!urltohost(url, (char *) host_name)) {
147 printf("\nERROR:\t\t\tBad URL!\n");
148 return 0;
149 }
150
151 // Reformat host name into a series of labels
152 if (!hosttodomain((char *) host_name, (char *) dns_domain_name)) {
153 printf("\nERROR:\t\t\tBad host name!\n");
154 return 0;
155 }
156
157 // Check if DNS server is presented and accessible
158 if (dns_server_ip == 0) {
159 printf("\nERROR:\t\t\tCan't resolve domain name "
160 "(DNS server is not presented)!\n");
161 return 0;
162 }
163
164 // Use DNS-server to obtain IP
165 if (ip_version == 6)
166 memset(dns_result_ipv6, 0, 16);
167 else
168 dns_result_ip = 0;
169 dns_error = 0;
170 strcpy((char *) dns_domain_cname, "");
171
172 for(i = 0; i < 30; ++i) {
173 // Use canonical name in case we obtained it
174 if (strlen((char *) dns_domain_cname))
175 dns_send_query(fd, dns_domain_cname, ip_version);
176 else
177 dns_send_query(fd, dns_domain_name, ip_version);
178
179 // setting up a timer with a timeout of one seconds
180 set_timer(TICKS_SEC);
181 do {
182 receive_ether(fd);
183 if (dns_error)
184 return 0; // FALSE - error
185 if ((dns_result_ip != 0) && (ip_version == 4)) {
186 memcpy(domain_ip, &dns_result_ip, 4);
187 return 1; // TRUE - success (domain IP retrieved)
188 }
189 else if ((dns_result_ipv6[0] != 0) && (ip_version == 6)) {
190 memcpy(domain_ip, dns_result_ipv6, 16);
191 return 1; // TRUE - success (domain IP retrieved)
192 }
193 } while (get_timer() > 0);
194 }
195
196 printf("\nGiving up after %d DNS requests\n", i);
197 return 0; // FALSE - domain name wasn't retrieved
198 }
199
200 /**
201 * DNS: Handles DNS-messages according to Receive-handle diagram.
202 * Sets dns_result_ip for given dns_domain_name (see dns_get_ip)
203 * or signals error condition occurs during DNS-resolving process
204 * by setting dns_error flag.
205 *
206 * @param packet DNS-packet to be handled
207 * @param packetsize length of the packet
208 * @return ZERO - packet handled successfully;
209 * NON ZERO - packet was not handled (e.g. bad format)
210 * @see dns_get_ip
211 * @see receive_ether
212 * @see dnshdr
213 */
214 int32_t
handle_dns(uint8_t * packet,int32_t packetsize)215 handle_dns(uint8_t * packet, int32_t packetsize)
216 {
217 struct dnshdr * dnsh = (struct dnshdr *) packet;
218 uint8_t * resp_section = packet + sizeof(struct dnshdr);
219 /* This string stores domain name from DNS-packets */
220 static int8_t handle_domain_name[0x100];
221 int i;
222
223 // verify ID - is it response for our query?
224 if (dnsh -> id != htons(0x1234))
225 return 0;
226
227 // Is it DNS response?
228 if ((dnsh -> flags & htons(DNS_FLAG_MSGTYPE)) != htons(DNS_FLAG_SRESPONSE))
229 return 0;
230
231 // Is error condition occurs? (check error field in incoming packet)
232 if ((dnsh -> flags & htons(DNS_FLAG_RCODE)) != DNS_RCODE_NERROR) {
233 dns_error = 1;
234 return 0;
235 }
236
237 /* Pass all (qdcount) records in question section */
238
239 for (i = 0; i < htons(dnsh -> qdcount); i++) {
240 // pass QNAME
241 resp_section = dns_extract_name((uint8_t *) dnsh, (int8_t *) resp_section,
242 handle_domain_name);
243 if (resp_section == NULL) {
244 return -1; // incorrect domain name (bad packet)
245 }
246 // pass QTYPE & QCLASS
247 resp_section += 4;
248 }
249
250 /* Handle all (ancount) records in answer section */
251
252 for (i = 0; i < htons(dnsh -> ancount); i++) {
253 // retrieve domain name from the packet
254 resp_section = dns_extract_name((uint8_t *) dnsh, (int8_t *) resp_section,
255 handle_domain_name);
256
257 if (resp_section == NULL) {
258 return -1; // incorrect domain name (bad packet)
259 }
260
261 // Check the class of the query (should be IN for Internet)
262 if (* (uint16_t *) (resp_section + 2) == htons(DNS_QCLASS_IN)) {
263 // check if retrieved name fit raw or canonical domain name
264 if (!strcmp((char *) handle_domain_name, (char *) dns_domain_name) ||
265 !strcmp((char *) handle_domain_name, (char *) dns_domain_cname)) {
266 switch (htons(* (uint16_t *) resp_section)) {
267
268 case DNS_QTYPE_A :
269 // rdata contains IP
270 dns_result_ip = htonl(* (uint32_t *) (resp_section + 10));
271 return 0; // IP successfully obtained
272
273 case DNS_QTYPE_CNAME :
274 // rdata contains canonical name, store it for further requests
275 if (dns_extract_name((uint8_t *) dnsh, (int8_t *) resp_section + 10,
276 dns_domain_cname) == NULL) {
277 // incorrect domain name (bad packet)
278 return -1;
279 }
280 break;
281 case DNS_QTYPE_AAAA :
282 memcpy(dns_result_ipv6, (resp_section + 10), 16);
283 return 0; // IP successfully obtained
284 }
285 }
286 // continue with next record in answer section
287 resp_section += htons(* (uint16_t *) (resp_section + 8)) + 10;
288 }
289 }
290 return 0; // Packet successfully handled but IP wasn't obtained
291 }
292
293 /**
294 * DNS: Sends a standard DNS-query (read request package) to a DNS-server.
295 * DNS-server respones with host IP or signals some error condition.
296 * Responses from the server are handled by handle_dns function.
297 *
298 * @param fd socket descriptor
299 * @param domain_name the domain name given as series of labels preceded
300 * with length(label) and terminated with 0
301 * <br>(e.g. "\3,w,w,w,\4,h,o,s,t,\3,o,r,g,\0")
302 * @see handle_dns
303 */
304 static void
dns_send_query(int fd,int8_t * domain_name,uint8_t ip_version)305 dns_send_query(int fd, int8_t * domain_name, uint8_t ip_version)
306 {
307 int qry_len = strlen((char *) domain_name) + 5;
308 int iphdr_len = (ip_version == 4) ? sizeof(struct iphdr) : sizeof(struct ip6hdr);
309 ip6_addr_t server_ipv6;
310
311 uint32_t packetsize = iphdr_len +
312 sizeof(struct udphdr) + sizeof(struct dnshdr) +
313 qry_len;
314
315 memset(ether_packet, 0, packetsize);
316 fill_dnshdr(ðer_packet[
317 iphdr_len + sizeof(struct udphdr)],
318 domain_name,
319 ip_version);
320 fill_udphdr(ðer_packet[iphdr_len],
321 sizeof(struct dnshdr) +
322 sizeof(struct udphdr) + qry_len,
323 UDPPORT_DNSC, UDPPORT_DNSS);
324 if (ip_version == 4) {
325 fill_iphdr(ether_packet,
326 sizeof(struct dnshdr) + sizeof(struct udphdr) +
327 iphdr_len + qry_len,
328 IPTYPE_UDP, 0, dns_server_ip);
329 } else {
330 memcpy(server_ipv6.addr, dns_server_ipv6, 16);
331 fill_ip6hdr(ether_packet,
332 sizeof(struct dnshdr) + sizeof(struct udphdr) + qry_len,
333 IPTYPE_UDP, get_ipv6_address(),
334 &server_ipv6);
335 }
336
337 send_ip(fd, ether_packet, packetsize);
338 }
339
340 /**
341 * DNS: Creates standard DNS-query package. Places DNS-header
342 * and question section in a packet and fills it with
343 * corresponding information.
344 * <p>
345 * Use this function with similar functions for other network layers
346 * (fill_udphdr, fill_iphdr, fill_ethhdr).
347 *
348 * @param packet Points to the place where ARP-header must be placed.
349 * @param domain_name the domain name given as series of labels preceded
350 * with length(label) and terminated with 0
351 * <br>(e.g. "\3,w,w,w,\4,h,o,s,t,\3,o,r,g,\0")
352 * @see fill_udphdr
353 * @see fill_iphdr
354 * @see fill_ethhdr
355 */
356 static void
fill_dnshdr(uint8_t * packet,int8_t * domain_name,uint8_t ip_version)357 fill_dnshdr(uint8_t * packet, int8_t * domain_name, uint8_t ip_version)
358 {
359 struct dnshdr * dnsh = (struct dnshdr *) packet;
360 uint8_t * qry_section = packet + sizeof(struct dnshdr);
361
362 dnsh -> id = htons(0x1234);
363 dnsh -> flags = htons(DNS_FLAG_SQUERY) | htons(DNS_FLAG_RD);
364 dnsh -> qdcount = htons(1);
365
366 strcpy((char *) qry_section, (char *) domain_name);
367 qry_section += strlen((char *) domain_name) + 1;
368
369 // fill QTYPE (ask for IP)
370 if (ip_version == 4)
371 * (uint16_t *) qry_section = htons(DNS_QTYPE_A);
372 else
373 * (uint16_t *) qry_section = htons(DNS_QTYPE_AAAA);
374 qry_section += 2;
375 // fill QCLASS (IN is a standard class for Internet)
376 * (uint16_t *) qry_section = htons(DNS_QCLASS_IN);
377 }
378
379 /**
380 * DNS: Extracts domain name from the question or answer section of
381 * the DNS-message. This function is need to support message
382 * compression requirement (see RFC 1035, paragraph 4.1.4).
383 *
384 * @param dnsh Points at the DNS-header.
385 * @param head Points at the beginning of the domain_name
386 * which has to be extracted.
387 * @param domain_name In case of SUCCESS this string stores extracted name.
388 * In case of FAULT this string is empty.
389 * @return NULL in case of FAULT (domain name > 255 octets);
390 * otherwise pointer to the data following the name.
391 * @see dnshdr
392 */
393 static uint8_t *
dns_extract_name(uint8_t * dnsh,int8_t * head,int8_t * domain_name)394 dns_extract_name(uint8_t * dnsh, int8_t * head, int8_t * domain_name)
395 {
396 int8_t * tail = domain_name;
397 int8_t * ptr = head;
398 int8_t * next_section = NULL;
399
400 while (1) {
401 if ((ptr[0] & 0xC0) == 0xC0) {
402 // message compressed (reference is used)
403 next_section = ptr + 2;
404 ptr = (int8_t *) dnsh + (htons(* (uint16_t *) ptr) & 0x3FFF);
405 continue;
406 }
407 if (ptr[0] == 0) {
408 // message termination
409 tail[0] = 0;
410 ptr += 1;
411 break;
412 }
413 // maximum length for domain name is 255 octets w/o termination sym
414 if (tail - domain_name + ptr[0] + 1 > 255) {
415 strcpy((char *) domain_name, "");
416 return NULL;
417 }
418 memcpy(tail, ptr, ptr[0] + 1);
419 tail += ptr[0] + 1;
420 ptr += ptr[0] + 1;
421 }
422
423 if (next_section == NULL)
424 next_section = ptr;
425
426 return (uint8_t *) next_section;
427 }
428
429 /**
430 * DNS: Parses URL and returns host name.
431 * Input string can be given as: <ul>
432 * <li> scheme with full path with (without) user and password
433 * <br>(e.g. "http://user:pass@www.host.org/url-path");
434 * <li> host name with url-path
435 * <br>(e.g. "www.host.org/url-path");
436 * <li> nothing but host name
437 * <br>(e.g. "www.host.org");
438 * </ul>
439 *
440 * @param url string that stores incoming URL
441 * @param host_name In case of SUCCESS this string stores the host name,
442 * In case of FAULT this string is empty.
443 * @return TRUE - host name retrieved,
444 * FALSE - host name > 255 octets or empty.
445 */
446 static int8_t
urltohost(char * url,char * host_name)447 urltohost(char * url, char * host_name)
448 {
449 uint16_t length1;
450 uint16_t length2;
451
452 strcpy(host_name, "");
453
454 if (strstr(url, "://") != NULL)
455 url = strstr(url, "//") + 2; // URL
456
457 if (strstr(url, "@") != NULL) // truncate user & password
458 url = strstr(url, "@") + 1;
459
460 if (strstr(url, "/") != NULL) // truncate url path
461 length1 = strstr(url, "/") - url;
462 else
463 length1 = strlen(url);
464
465 if (strstr(url, ":") != NULL) // truncate port path
466 length2 = strstr(url, ":") - url;
467 else
468 length2 = strlen(url);
469
470 if(length1 > length2)
471 length1 = length2;
472
473 if (length1 == 0)
474 return 0; // string is empty
475 if(length1 >= 256)
476 return 0; // host name is too big
477
478 strncpy(host_name, url, length1);
479 host_name[length1] = 0;
480
481 return 1; // Host name is retrieved
482 }
483
484 /**
485 * DNS: Transforms host name string into a series of labels
486 * each of them preceded with length(label). 0 is a terminator.
487 * "www.domain.dom" -> "\3,w,w,w,\6,d,o,m,a,i,n,\3,c,o,m,\0"
488 * <p>
489 * This format is used in DNS-messages.
490 *
491 * @param host_name incoming string with the host name
492 * @param domain_name resulting string with series of labels
493 * or empty string in case of FAULT
494 * @return TRUE - host name transformed,
495 * FALSE - host name > 255 octets or label > 63 octets.
496 */
497 static int8_t
hosttodomain(char * host_name,char * domain_name)498 hosttodomain(char * host_name, char * domain_name)
499 {
500 char * domain_iter = domain_name;
501 char * host_iter = host_name;
502
503 strcpy(domain_name, "");
504
505 if(strlen(host_name) > 255)
506 return 0; // invalid host name (refer to RFC 1035)
507
508 for(; 1; ++host_iter) {
509 if(*host_iter != '.' && *host_iter != 0)
510 continue;
511 *domain_iter = host_iter - host_name;
512 if (*domain_iter > 63) {
513 strcpy(domain_name, "");
514 return 0; // invalid host name (refer to RFC 1035)
515 }
516 ++domain_iter;
517 strncpy(domain_iter, host_name, host_iter - host_name);
518 domain_iter += (host_iter - host_name);
519 if(*host_iter == 0) {
520 *domain_iter = 0;
521 break;
522 }
523 host_name = host_iter + 1;
524 }
525 return 1; // ok
526 }
527