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(&ether_packet[
317 	            iphdr_len + sizeof(struct udphdr)],
318 	            domain_name,
319 		    ip_version);
320 	fill_udphdr(&ether_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