summaryrefslogblamecommitdiffstats
path: root/src/core/dns_resolver.c
blob: 52f24a4d3e4fb6524c19fb56762a7f95e4da929e (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419


































































































































































































































































































































































































































                                                                                                    
/**************************************************************************
*
*    dns_resolver.c: Etherboot support for resolution of host/domain
*    names in filename parameters
*    Written 2004 by Anselm M. Hoffmeister
*    <stockholm@users.sourceforge.net>
*
*    This program is free software; you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation; either version 2 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License
*    along with this program; if not, write to the Free Software
*    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*    This code is using nuts and bolts from throughout etherboot.
*    It is a fresh implementation according to the DNS RFC, #1035
*    
*    $Revision$
*    $Author$
*    $Date$
*
*    REVISION HISTORY:
*    ================
*    2004-05-10 File created
*    2004-05-19 First release to CVS
*    2004-05-22 CNAME support first stage finished
*    2004-05-24 First "stable" release to CVS
*    2004-08-28 Improve readability, set recursion flag
***************************************************************************/

#ifdef DNS_RESOLVER
#include "etherboot.h"
#include "nic.h"
#include "dns_resolver.h"

#define	MAX_DNS_RETRIES	3
#define	MAX_CNAME_RECURSION 0x30
#undef DNSDEBUG

int	donameresolution ( char * hostname, int hnlength, char * deststring );

/*
 *	dns_resolver
 *	Function: Main function for name resolution - will be called by other
 *		parts of etherboot
 *	Param:	string filename (not containing proto prefix like "tftp://")
 *	Return:	string filename, with hostname replaced by IP in dotted quad
 *		or NULL for resolver error
 *		In case no substitution is necessary, return will be = param
 *		The returned string, if not == input, will be temporary and
 *		probably be overwritten during the next call to dns_resolver
 *		The returned string may (or may not) only contain an IP
 *		address, or an IP followed by a ":" or "/", or be NULL(=error)
 */
char *	dns_resolver ( char * filename ) {
	int	i = 0, j, k;
	static char ipaddr[16] = { 0 };
	// Search for "end of hostname" (which might be either ":" or "/")
	for ( j = i; (filename[j] != ':') && (filename[j] != '/'); ++j ) {
		// If no hostname delimiter was found, assume no name present
		if ( filename[j] == 0 )	return	filename;
	}
	// Check if the filename is an IP, in which case, leave unchanged
	k = j - i - 1;
	while ( ( '.' == filename[i+k] ) ||
		( ( '0' <= filename[i+k] ) && ( '9' >= filename[i+k] ) ) ) {
		--k;
		if ( k < 0 ) return filename; // Only had nums and dots->IP
	}
	// Now that we know it's a full hostname, attempt to resolve
	if ( donameresolution ( filename + i, j - i, ipaddr ) ) {
		return	NULL;	// Error in resolving - Fatal.
	}
	// Return the dotted-quad IP which resulted
	return	ipaddr;
}

/*
 *	await_dns
 *	Shall be called on any incoming packet during the resolution process
 *	(as is the case with all the other await_ functions in etherboot)
 *	Param:	as any await functions
 *	Return:	see dns_resolver.h for constant return values + descriptions
 */
static int await_dns (int ival, void *ptr,
	unsigned short ptype __unused, struct iphdr *ip __unused,
	struct udphdr *udp, struct tcphdr *tcp __unused) {
	int	i, j, k;
	unsigned char *p = (unsigned char *)udp + sizeof(struct udphdr);
	// p is set to the beginning of the payload
	unsigned char *q;
	unsigned int querytype = QUERYTYPE_A;
	if (  0 == udp  )	// Parser couldn't find UDP header
		return RET_PACK_GARBAG;	// Not a UDP packet
	if (( UDP_PORT_DNS != ntohs (udp->src )) ||
	    ( UDP_PORT_DNS != ntohs (udp->dest)) )
		// Neither source nor destination port is "53"
		return RET_PACK_GARBAG;	// UDP port wrong
	if (( p[QINDEX_ID  ] != 0) ||
	    ( p[QINDEX_ID+1] != (ival & 0xff)))
		// Checking if this packet has set (inside payload)
		// the sequence identifier that we expect
		return RET_PACK_GARBAG;	// Not matching our request ID
	if (( p[QINDEX_FLAGS  ] & QUERYFLAGS_MASK ) != QUERYFLAGS_WANT )
		// We only accept responses to the query(ies) we sent
		return	RET_PACK_GARBAG;	// Is not response=opcode <0>
	querytype = (ival & 0xff00) >> 8;
	if (((p[QINDEX_NUMANSW+1] + (p[QINDEX_NUMANSW+1]<<8)) == 0 ) ||
	     ( ERR_NOSUCHNAME == (p[QINDEX_FLAGS+1] & 0x0f) ) ) {
		// Answer section has 0 entries, or "no such name" returned
		if ( QUERYTYPE_A == querytype) {
			// It was an A type query, so we should try if there's
			// an alternative "CNAME" record available
			return	RET_RUN_CNAME_Q; // So try CNAME query next
		} else if ( QUERYTYPE_CNAME == querytype) {
			// There's no CNAME either, so give up
			return	RET_NOSUCHNAME;
		} else {
			// Anything else? No idea what. Should not happen.
			return	RET_NOSUCHNAME; // Bail out with error
		}
	}
	if ( 0 != ( p[QINDEX_FLAGS+1] & 0x0f ) )
		// The response packet's flag section tells us:
		return	RET_NOSUCHNAME;	// Another (unspecific) error occured
	// Now we have an answer packet in response to our query. Next thing
	// to do is to search the payload for the "answer section", as there is
	// a question section first usually that needs to be skipped
	// If question section was not repeated, that saves a lot of work :
	if ( 0 >= (i = ((p[QINDEX_NUMQUEST] << 8) + p[QINDEX_NUMQUEST+1]))) {
		q = p+ QINDEX_QUESTION;	// No question section, just continue;
	} else if ( i >= 2 ) {		// More than one query section? Error!
		return	RET_NOSUCHNAME;	// That's invalid for us anyway - 
					// We only place one query at a time
	} else {
		// We have to skip through the question section first to
		// find the beginning of the answer section
		q = p + QINDEX_QUESTION;
		while ( 0 != q[0] )
			q += q[0] + 1; // Skip through
		q += 5; // Skip over end-\0 and query type section
	}
	// Now our pointer shows the beginning of the answer section
	// So now move it past the (repeated) query string, we only
	// want the answer
	while ( 0 != q[0] ) {
		if ( 0xc0 == ( q[0] & 0xc0 ) ) { // Pointer
			++q;
			break;
		}
		q += q[0] + 1;
	}
	++q;
	// Now check wether it's an INET host address (resp. CNAME)?
	// There seem to be nameservers out there (Bind 9, for example),
	// that return CNAMEs when no As are there, e.g. in
	//   testname.test.   IN CNAME othername.test.
	// case, bind 9 would return the CNAME record on an A query.
	// Accept this case as it saves a lot of work (an extra query)
	if (( QUERYTYPE_CNAME == q[1] ) &&
	    ( QUERYTYPE_A     == querytype )) {
		// A query, but CNAME response.
		// Do simulate having done a CNAME query now and just assume
		// the answer we are working on here is that of a CNAME query
		// This works for single-depth CNAMEs fine.
		// For deeper recursion, we won't parse the answer
		// packet deeper but rely on a separate CNAME query
		querytype = QUERYTYPE_CNAME;
	}
	// Now check wether the answer packet is of the expected type
	// (remember, we just tweaked CNAME answers to A queries already)
	if ( (q[0] != 0) ||
	     (q[1] !=   querytype) ||   // query type matches?
	     (q[2] != ((QUERYCLASS_INET & 0xff00) >> 8)) ||
	     (q[3] !=  (QUERYCLASS_INET & 0x00ff))) { // class IN response?
		return	RET_DNSERROR;	// Should not happen. DNS server bad?
	}
	q += 8;	// skip querytype/-class/ttl which are uninteresting now
	if ( querytype == QUERYTYPE_A ) {
		// So what we sent was an A query - expect an IPv4 address
		// Check if datalength looks satisfactory
		if ( ( 0 != q[0] ) || ( 4 != q[1] ) ) {
			// Data length is not 4 bytes
			return	RET_DNSERROR;
		}
		// Go to the IP address and copy it to the response buffer
		p = ptr + QINDEX_STORE_A;
		for ( i = 0; i < 4; ++i ) {
			p[i] = q[i+2];
		}
		return	RET_GOT_ADDR; // OK! Address RETURNED! VERY FINE!
	} else if ( querytype == QUERYTYPE_CNAME ) { // CNAME QUERY
		// The pointer "q" now stands on the "payload length" of the
		// CNAME data [2 byte field]. We will have to check wether this
		// name is referenced in a following response, which would save
		// us further queries, or if it is standalone, which calls for
		// making a separate query
		// This statement above probably needs clarification. To help
		// the reader understand what's going on, imagine the DNS
		// answer to be 4-section: query(repeating what we sent to the
		// DNS server), answer(like the CNAME record we wanted),
		// additional (which might hold the A for the CNAME - esp.
		// Bind9 seems to provide us with this info when we didn't
		// query for it yet) and authoritative (the DNS server's info
		// on who is responsible for that data). For compression's
		// sake, instead of specifying the full hostname string all
		// the time, one can instead specify something like "same as on
		// payload offset 0x123" - if this happens here, we can check
		// if that payload offset given here by coincidence is that of
		// an additional "A" record which saves us sending a separate
		// query.
		// We will look if there's  a/ two or more answer sections
		// AND  b/ the next is a reference to us.
		p = (unsigned char *)udp + sizeof(struct udphdr);
		i = q + 2 - p;
		if ( p[7] > 1 ) { // More than one answer section
			// Check the next if it's a ref
			// For that, we have to locate the next. Do this with "q".
			p = q + (q[0] * 0x100) + q[1] + 2;
			if ( (p[0] == (0xc0 + ((i & 0xf00)/256))) &&
			     (p[1] == (i & 0xff) ) &&
			     (p[2] == ((QUERYTYPE_A     & 0xff00) >> 8)) &&
			     (p[3] ==  (QUERYTYPE_A     & 0x00ff)) &&
			     (p[4] == ((QUERYCLASS_INET & 0xff00) >> 8 )) &&
			     (p[5] ==  (QUERYCLASS_INET & 0x00ff)) &&
			     (p[10]== 0) &&		// Data length expected
			     (p[11]== 4)) {		// to be <4> (IP-addr)
				// Behind that sections: TTL, data length(4)
				for ( i = 0; i < 4; ++i ) {
				    ((unsigned char*)ptr)[QINDEX_STORE_A+i] =
					    p[12+i];
				}
				return	RET_GOT_ADDR;
			}
		}
		// Reference not found, next query (A) needed (because CNAME
		// queries usually return another hostname, not an IP address
		// as we need it - that's the point in CNAME, anyway :-)
		p = (unsigned char *)udp + sizeof(struct udphdr);
#ifdef DNSDEBUG
		printf ( " ->[");
#endif
		k = QINDEX_QUESTION;
		i = (q-p) + 2;
		// Compose the hostname that needs to be queried for
		// This looks complicated (and is a little bit) because
		// we might not be able to copy verbatim, but need to
		// check wether the CNAME looks like
		// "servername" "plus what offset 0x123 of the payload says"
		// (this saves transfer bandwidth, which is why DNS allows
		// this, but it makes programmers' lives more difficult)
		while (p[i] != 0) {
			if ( (((unsigned char *)p)[i] & 0xc0) != 0 ) {
				i = ((p[i] & 0x3f) * 0x100) + p[i+1];
				continue;
			}
			((unsigned char *)ptr)[k] = p[i];
			for ( j = i + 1; j <= i + p[i]; ++j ) {
				((unsigned char *)ptr)[k+j-i] = p[j];
#ifdef DNSDEBUG
				printf ( "%c", p[j] );
#endif
			}
			k += ((unsigned char *)ptr)[k] + 1;
			i += p[i] + 1;
#ifdef DNSDEBUG
			printf ( (p[i] ? ".": "") );
#endif
		}
		((unsigned char *)ptr)[k] = 0;
#ifdef DNSDEBUG
		printf ( "].." );
#endif
		// So we need to run another query, this time try to
		// get an "A" record for the hostname that the last CNAME
		// query pointed to
		return	RET_RUN_NEXT_A;
	}
	// We only accept CNAME or A replies from the nameserver, every-
	// thing else is of no use for us.
	// Must be an invalid packet that the nameserver sent.
	return	RET_DNSERROR;
}

int	chars_to_next_dot ( char * countfrom, int maxnum ) {
	// Count the number of characters of this part of a hostname
	int i;
	for ( i = 1; i < maxnum; ++i ) {
		if ( countfrom[i] == '.' ) return i;
	}
	return	maxnum;
}

/*
 *	donameresolution
 *	Function: Compose the initial query packet, handle answers until
 *		a/ an IP address is retrieved
 *		b/ too many CNAME references occured (max. MAX_DNS_RETRIES)
 *		c/ No matching record for A or CNAME can be found
 *	Param:	string hostname, length (hostname needs no \0-end-marker),
 *		string to which dotted-quad-IP shall be written
 *	Return:	0 for success, >0 for failure
 */
int	donameresolution ( char * hostname, int hnlength, char * deststring ) {
	unsigned char	querybuf[260+sizeof(struct iphdr)+sizeof(struct udphdr)];
		// 256 for the DNS query payload, +4 for the result temporary
	unsigned char	*query = &querybuf[sizeof(struct iphdr)+sizeof(struct udphdr)];
		// Pointer to the payload
	int	i, h = hnlength;
	long	timeout;
	int	retry, recursion;
	// Setup the query data
	query[QINDEX_ID  ]	= (QUERYIDENTIFIER & 0xff00) >> 8;
	query[QINDEX_ID+1]	=  QUERYIDENTIFIER & 0xff;
	query[QINDEX_FLAGS  ]	= (QUERYFLAGS & 0xff00) >> 8;
	query[QINDEX_FLAGS+1]	=  QUERYFLAGS & 0xff;
	query[QINDEX_NUMQUEST  ]= 0;
	query[QINDEX_NUMQUEST+1]= 1; // 1 standard query to be sent
	query[QINDEX_NUMANSW  ]	= 0;
	query[QINDEX_NUMANSW+1]	= 0;
	query[QINDEX_NUMAUTH  ]	= 0;
	query[QINDEX_NUMAUTH+1]	= 0;
	query[QINDEX_NUMADDIT  ]= 0;
	query[QINDEX_NUMADDIT+1]= 0;
	query[QINDEX_QUESTION]	= chars_to_next_dot(hostname,h);
	if ( h > 236 ) return 1;	// Hostnames longer than 236 chars are refused.
					// This is an arbitrary decision, SHOULD check
					// what the RFCs say about that
	for ( i = 0; i < h; ++i ) {
		// Compose the query section's hostname - replacing dots (and
		// preceding the string) with one-byte substring-length values
		// (for the immediately following substring) - \0 terminated
		query[QINDEX_QUESTION+i+1] = hostname[i];
		if ( hostname[i] == '.' )
			query[QINDEX_QUESTION+i+1] = chars_to_next_dot(hostname + i + 1, h - i - 1);
	}
	query[QINDEX_QTYPE+h  ]	= (QUERYTYPE_A & 0xff00) >> 8;
	query[QINDEX_QTYPE+h+1]	=  QUERYTYPE_A & 0xff;
					// First try an A query, if that
					// won't work, try CNAME
	printf ( "Resolving hostname [" );
	for ( i = 0; i < hnlength; ++i ) { printf ( "%c", hostname[i] ); }
	printf ("]" );
	for ( recursion = MAX_CNAME_RECURSION; recursion > 0; --recursion ) {
		printf ( ".." );
		// Try MAX_CNAME_RECURSION CNAME queries maximally, if then no
		// A record is found, assume the hostname to not be resolvable
		query[QINDEX_QUESTION+h+1] = 0;	// Marks the end of
						// the query string
		query[QINDEX_QCLASS+h  ]= (QUERYCLASS_INET & 0xff00) >> 8;
		query[QINDEX_QCLASS+h+1]=  QUERYCLASS_INET & 0xff;
		rx_qdrain();	// Clear NIC packet buffer -
				// there won't be anything of interest *now*.
		udp_transmit ( arptable[ARP_NAMESERVER].ipaddr.s_addr,
				UDP_PORT_DNS, UDP_PORT_DNS,
				hnlength + 18 + sizeof(struct iphdr) +
				sizeof(struct udphdr), querybuf );
		// If no answer comes in in a certain period of time, retry
		for (retry = 1; retry <= MAX_DNS_RETRIES; retry++) {
			timeout = rfc2131_sleep_interval(TIMEOUT, retry);
			i = await_reply ( await_dns,
				(query[QINDEX_QTYPE+h+1] << 8) +
				((unsigned char *)query)[QINDEX_ID+1],
				query, timeout);
			// The parameters given are
			//	bits 15...8 of integer = Query type  (low bits)
			//	bits  7...0 of integer = Query index (low bits)
			//	query + QINDEX_STORE_A points to the place
			//	where A records	shall be stored (4 bytes);
			//	query + 12(QINDEX_QUESTION) points to where
			//	a CNAME should go in case one is received
			if (i) break;
		}
		switch ( i ) {
		  case	RET_GOT_ADDR:	// Address successfully retrieved
			sprintf( deststring, "%@",
				  (long)query[QINDEX_STORE_A  ]           +
				( (long)query[QINDEX_STORE_A+1] * 256 ) +
				( (long)query[QINDEX_STORE_A+2] * 65536 )+
				( (long)query[QINDEX_STORE_A+3] * 16777216 ));
			printf ( " -> IP [%s]\n", deststring );
			return	RET_DNS_OK;
		  case	RET_RUN_CNAME_Q: // No A record found, try CNAME
			query[QINDEX_QTYPE+h  ]=(QUERYTYPE_CNAME & 0xff00)>> 8;
			query[QINDEX_QTYPE+h+1]= QUERYTYPE_CNAME & 0xff;
			break;
		  case	RET_RUN_NEXT_A:
			// Found a CNAME, now try A for the name it pointed to
			for ( i = 0; query[QINDEX_QUESTION+i] != 0;
					i += query[QINDEX_QUESTION+i] + 1 ) {;}
			h = i - 1;
			query[QINDEX_QTYPE+h  ]=(QUERYTYPE_A & 0xff00)>> 8;
			query[QINDEX_QTYPE+h+1]= QUERYTYPE_A & 0xff;
			break;
		  case	RET_CNAME_FAIL:
			// Neither A nor CNAME gave a usable result
			printf ("Host name cannot be resolved\n");
			return	RET_DNS_FAIL;
		  case	RET_NOSUCHNAME:
		  default:
			printf ( "Name resolution failed\n" );
			return	RET_DNS_FAIL;
		}
		query[QINDEX_ID  ] = 0;	// We will probably never have more
					// than 256 queries in one run
		query[QINDEX_ID+1]++;
	}
	// To deep recursion
	printf ( "CNAME recursion to deep - abort name resolver\n" );
	return	RET_DNS_FAIL;
}
#endif				/* DNS_RESOLVER */