• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2002-2004 Brian Wellington (bwelling@xbill.org)
2 
3 package org.xbill.DNS;
4 
5 import java.util.*;
6 import java.io.*;
7 import java.net.*;
8 
9 /**
10  * The Lookup object issues queries to caching DNS servers.  The input consists
11  * of a name, an optional type, and an optional class.  Caching is enabled
12  * by default and used when possible to reduce the number of DNS requests.
13  * A Resolver, which defaults to an ExtendedResolver initialized with the
14  * resolvers located by the ResolverConfig class, performs the queries.  A
15  * search path of domain suffixes is used to resolve relative names, and is
16  * also determined by the ResolverConfig class.
17  *
18  * A Lookup object may be reused, but should not be used by multiple threads.
19  *
20  * @see Cache
21  * @see Resolver
22  * @see ResolverConfig
23  *
24  * @author Brian Wellington
25  */
26 
27 public final class Lookup {
28 
29 private static Resolver defaultResolver;
30 private static Name [] defaultSearchPath;
31 private static Map defaultCaches;
32 private static int defaultNdots;
33 
34 private Resolver resolver;
35 private Name [] searchPath;
36 private Cache cache;
37 private boolean temporary_cache;
38 private int credibility;
39 private Name name;
40 private int type;
41 private int dclass;
42 private boolean verbose;
43 private int iterations;
44 private boolean foundAlias;
45 private boolean done;
46 private boolean doneCurrent;
47 private List aliases;
48 private Record [] answers;
49 private int result;
50 private String error;
51 private boolean nxdomain;
52 private boolean badresponse;
53 private String badresponse_error;
54 private boolean networkerror;
55 private boolean timedout;
56 private boolean nametoolong;
57 private boolean referral;
58 
59 private static final Name [] noAliases = new Name[0];
60 
61 /** The lookup was successful. */
62 public static final int SUCCESSFUL = 0;
63 
64 /**
65  * The lookup failed due to a data or server error. Repeating the lookup
66  * would not be helpful.
67  */
68 public static final int UNRECOVERABLE = 1;
69 
70 /**
71  * The lookup failed due to a network error. Repeating the lookup may be
72  * helpful.
73  */
74 public static final int TRY_AGAIN = 2;
75 
76 /** The host does not exist. */
77 public static final int HOST_NOT_FOUND = 3;
78 
79 /** The host exists, but has no records associated with the queried type. */
80 public static final int TYPE_NOT_FOUND = 4;
81 
82 public static synchronized void
refreshDefault()83 refreshDefault() {
84 
85 	try {
86 		defaultResolver = new ExtendedResolver();
87 	}
88 	catch (UnknownHostException e) {
89 		throw new RuntimeException("Failed to initialize resolver");
90 	}
91 	defaultSearchPath = ResolverConfig.getCurrentConfig().searchPath();
92 	defaultCaches = new HashMap();
93 	defaultNdots = ResolverConfig.getCurrentConfig().ndots();
94 }
95 
96 static {
refreshDefault()97 	refreshDefault();
98 }
99 
100 /**
101  * Gets the Resolver that will be used as the default by future Lookups.
102  * @return The default resolver.
103  */
104 public static synchronized Resolver
getDefaultResolver()105 getDefaultResolver() {
106 	return defaultResolver;
107 }
108 
109 /**
110  * Sets the default Resolver to be used as the default by future Lookups.
111  * @param resolver The default resolver.
112  */
113 public static synchronized void
setDefaultResolver(Resolver resolver)114 setDefaultResolver(Resolver resolver) {
115 	defaultResolver = resolver;
116 }
117 
118 /**
119  * Gets the Cache that will be used as the default for the specified
120  * class by future Lookups.
121  * @param dclass The class whose cache is being retrieved.
122  * @return The default cache for the specified class.
123  */
124 public static synchronized Cache
getDefaultCache(int dclass)125 getDefaultCache(int dclass) {
126 	DClass.check(dclass);
127 	Cache c = (Cache) defaultCaches.get(Mnemonic.toInteger(dclass));
128 	if (c == null) {
129 		c = new Cache(dclass);
130 		defaultCaches.put(Mnemonic.toInteger(dclass), c);
131 	}
132 	return c;
133 }
134 
135 /**
136  * Sets the Cache to be used as the default for the specified class by future
137  * Lookups.
138  * @param cache The default cache for the specified class.
139  * @param dclass The class whose cache is being set.
140  */
141 public static synchronized void
setDefaultCache(Cache cache, int dclass)142 setDefaultCache(Cache cache, int dclass) {
143 	DClass.check(dclass);
144 	defaultCaches.put(Mnemonic.toInteger(dclass), cache);
145 }
146 
147 /**
148  * Gets the search path that will be used as the default by future Lookups.
149  * @return The default search path.
150  */
151 public static synchronized Name []
getDefaultSearchPath()152 getDefaultSearchPath() {
153 	return defaultSearchPath;
154 }
155 
156 /**
157  * Sets the search path to be used as the default by future Lookups.
158  * @param domains The default search path.
159  */
160 public static synchronized void
setDefaultSearchPath(Name [] domains)161 setDefaultSearchPath(Name [] domains) {
162 	defaultSearchPath = domains;
163 }
164 
165 /**
166  * Sets the search path that will be used as the default by future Lookups.
167  * @param domains The default search path.
168  * @throws TextParseException A name in the array is not a valid DNS name.
169  */
170 public static synchronized void
setDefaultSearchPath(String [] domains)171 setDefaultSearchPath(String [] domains) throws TextParseException {
172 	if (domains == null) {
173 		defaultSearchPath = null;
174 		return;
175 	}
176 	Name [] newdomains = new Name[domains.length];
177 	for (int i = 0; i < domains.length; i++)
178 		newdomains[i] = Name.fromString(domains[i], Name.root);
179 	defaultSearchPath = newdomains;
180 }
181 
182 private final void
reset()183 reset() {
184 	iterations = 0;
185 	foundAlias = false;
186 	done = false;
187 	doneCurrent = false;
188 	aliases = null;
189 	answers = null;
190 	result = -1;
191 	error = null;
192 	nxdomain = false;
193 	badresponse = false;
194 	badresponse_error = null;
195 	networkerror = false;
196 	timedout = false;
197 	nametoolong = false;
198 	referral = false;
199 	if (temporary_cache)
200 		cache.clearCache();
201 }
202 
203 /**
204  * Create a Lookup object that will find records of the given name, type,
205  * and class.  The lookup will use the default cache, resolver, and search
206  * path, and look for records that are reasonably credible.
207  * @param name The name of the desired records
208  * @param type The type of the desired records
209  * @param dclass The class of the desired records
210  * @throws IllegalArgumentException The type is a meta type other than ANY.
211  * @see Cache
212  * @see Resolver
213  * @see Credibility
214  * @see Name
215  * @see Type
216  * @see DClass
217  */
218 public
Lookup(Name name, int type, int dclass)219 Lookup(Name name, int type, int dclass) {
220 	Type.check(type);
221 	DClass.check(dclass);
222 	if (!Type.isRR(type) && type != Type.ANY)
223 		throw new IllegalArgumentException("Cannot query for " +
224 						   "meta-types other than ANY");
225 	this.name = name;
226 	this.type = type;
227 	this.dclass = dclass;
228 	synchronized (Lookup.class) {
229 		this.resolver = getDefaultResolver();
230 		this.searchPath = getDefaultSearchPath();
231 		this.cache = getDefaultCache(dclass);
232 	}
233 	this.credibility = Credibility.NORMAL;
234 	this.verbose = Options.check("verbose");
235 	this.result = -1;
236 }
237 
238 /**
239  * Create a Lookup object that will find records of the given name and type
240  * in the IN class.
241  * @param name The name of the desired records
242  * @param type The type of the desired records
243  * @throws IllegalArgumentException The type is a meta type other than ANY.
244  * @see #Lookup(Name,int,int)
245  */
246 public
Lookup(Name name, int type)247 Lookup(Name name, int type) {
248 	this(name, type, DClass.IN);
249 }
250 
251 /**
252  * Create a Lookup object that will find records of type A at the given name
253  * in the IN class.
254  * @param name The name of the desired records
255  * @see #Lookup(Name,int,int)
256  */
257 public
Lookup(Name name)258 Lookup(Name name) {
259 	this(name, Type.A, DClass.IN);
260 }
261 
262 /**
263  * Create a Lookup object that will find records of the given name, type,
264  * and class.
265  * @param name The name of the desired records
266  * @param type The type of the desired records
267  * @param dclass The class of the desired records
268  * @throws TextParseException The name is not a valid DNS name
269  * @throws IllegalArgumentException The type is a meta type other than ANY.
270  * @see #Lookup(Name,int,int)
271  */
272 public
Lookup(String name, int type, int dclass)273 Lookup(String name, int type, int dclass) throws TextParseException {
274 	this(Name.fromString(name), type, dclass);
275 }
276 
277 /**
278  * Create a Lookup object that will find records of the given name and type
279  * in the IN class.
280  * @param name The name of the desired records
281  * @param type The type of the desired records
282  * @throws TextParseException The name is not a valid DNS name
283  * @throws IllegalArgumentException The type is a meta type other than ANY.
284  * @see #Lookup(Name,int,int)
285  */
286 public
Lookup(String name, int type)287 Lookup(String name, int type) throws TextParseException {
288 	this(Name.fromString(name), type, DClass.IN);
289 }
290 
291 /**
292  * Create a Lookup object that will find records of type A at the given name
293  * in the IN class.
294  * @param name The name of the desired records
295  * @throws TextParseException The name is not a valid DNS name
296  * @see #Lookup(Name,int,int)
297  */
298 public
Lookup(String name)299 Lookup(String name) throws TextParseException {
300 	this(Name.fromString(name), Type.A, DClass.IN);
301 }
302 
303 /**
304  * Sets the resolver to use when performing this lookup.  This overrides the
305  * default value.
306  * @param resolver The resolver to use.
307  */
308 public void
setResolver(Resolver resolver)309 setResolver(Resolver resolver) {
310 	this.resolver = resolver;
311 }
312 
313 /**
314  * Sets the search path to use when performing this lookup.  This overrides the
315  * default value.
316  * @param domains An array of names containing the search path.
317  */
318 public void
setSearchPath(Name [] domains)319 setSearchPath(Name [] domains) {
320 	this.searchPath = domains;
321 }
322 
323 /**
324  * Sets the search path to use when performing this lookup. This overrides the
325  * default value.
326  * @param domains An array of names containing the search path.
327  * @throws TextParseException A name in the array is not a valid DNS name.
328  */
329 public void
setSearchPath(String [] domains)330 setSearchPath(String [] domains) throws TextParseException {
331 	if (domains == null) {
332 		this.searchPath = null;
333 		return;
334 	}
335 	Name [] newdomains = new Name[domains.length];
336 	for (int i = 0; i < domains.length; i++)
337 		newdomains[i] = Name.fromString(domains[i], Name.root);
338 	this.searchPath = newdomains;
339 }
340 
341 /**
342  * Sets the cache to use when performing this lookup.  This overrides the
343  * default value.  If the results of this lookup should not be permanently
344  * cached, null can be provided here.
345  * @param cache The cache to use.
346  */
347 public void
setCache(Cache cache)348 setCache(Cache cache) {
349 	if (cache == null) {
350 		this.cache = new Cache(dclass);
351 		this.temporary_cache = true;
352 	} else {
353 		this.cache = cache;
354 		this.temporary_cache = false;
355 	}
356 }
357 
358 /**
359  * Sets ndots to use when performing this lookup, overriding the default value.
360  * Specifically, this refers to the number of "dots" which, if present in a
361  * name, indicate that a lookup for the absolute name should be attempted
362  * before appending any search path elements.
363  * @param ndots The ndots value to use, which must be greater than or equal to
364  * 0.
365  */
366 public void
setNdots(int ndots)367 setNdots(int ndots) {
368 	if (ndots < 0)
369 		throw new IllegalArgumentException("Illegal ndots value: " +
370 						   ndots);
371 	defaultNdots = ndots;
372 }
373 
374 /**
375  * Sets the minimum credibility level that will be accepted when performing
376  * the lookup.  This defaults to Credibility.NORMAL.
377  * @param credibility The minimum credibility level.
378  */
379 public void
setCredibility(int credibility)380 setCredibility(int credibility) {
381 	this.credibility = credibility;
382 }
383 
384 private void
follow(Name name, Name oldname)385 follow(Name name, Name oldname) {
386 	foundAlias = true;
387 	badresponse = false;
388 	networkerror = false;
389 	timedout = false;
390 	nxdomain = false;
391 	referral = false;
392 	iterations++;
393 	if (iterations >= 6 || name.equals(oldname)) {
394 		result = UNRECOVERABLE;
395 		error = "CNAME loop";
396 		done = true;
397 		return;
398 	}
399 	if (aliases == null)
400 		aliases = new ArrayList();
401 	aliases.add(oldname);
402 	lookup(name);
403 }
404 
405 private void
processResponse(Name name, SetResponse response)406 processResponse(Name name, SetResponse response) {
407 	if (response.isSuccessful()) {
408 		RRset [] rrsets = response.answers();
409 		List l = new ArrayList();
410 		Iterator it;
411 		int i;
412 
413 		for (i = 0; i < rrsets.length; i++) {
414 			it = rrsets[i].rrs();
415 			while (it.hasNext())
416 				l.add(it.next());
417 		}
418 
419 		result = SUCCESSFUL;
420 		answers = (Record []) l.toArray(new Record[l.size()]);
421 		done = true;
422 	} else if (response.isNXDOMAIN()) {
423 		nxdomain = true;
424 		doneCurrent = true;
425 		if (iterations > 0) {
426 			result = HOST_NOT_FOUND;
427 			done = true;
428 		}
429 	} else if (response.isNXRRSET()) {
430 		result = TYPE_NOT_FOUND;
431 		answers = null;
432 		done = true;
433 	} else if (response.isCNAME()) {
434 		CNAMERecord cname = response.getCNAME();
435 		follow(cname.getTarget(), name);
436 	} else if (response.isDNAME()) {
437 		DNAMERecord dname = response.getDNAME();
438 		try {
439 			follow(name.fromDNAME(dname), name);
440 		} catch (NameTooLongException e) {
441 			result = UNRECOVERABLE;
442 			error = "Invalid DNAME target";
443 			done = true;
444 		}
445 	} else if (response.isDelegation()) {
446 		// We shouldn't get a referral.  Ignore it.
447 		referral = true;
448 	}
449 }
450 
451 private void
lookup(Name current)452 lookup(Name current) {
453 	SetResponse sr = cache.lookupRecords(current, type, credibility);
454 	if (verbose) {
455 		System.err.println("lookup " + current + " " +
456 				   Type.string(type));
457 		System.err.println(sr);
458 	}
459 	processResponse(current, sr);
460 	if (done || doneCurrent)
461 		return;
462 
463 	Record question = Record.newRecord(current, type, dclass);
464 	Message query = Message.newQuery(question);
465 	Message response = null;
466 	try {
467 		response = resolver.send(query);
468 	}
469 	catch (IOException e) {
470 		// A network error occurred.  Press on.
471 		if (e instanceof InterruptedIOException)
472 			timedout = true;
473 		else
474 			networkerror = true;
475 		return;
476 	}
477 	int rcode = response.getHeader().getRcode();
478 	if (rcode != Rcode.NOERROR && rcode != Rcode.NXDOMAIN) {
479 		// The server we contacted is broken or otherwise unhelpful.
480 		// Press on.
481 		badresponse = true;
482 		badresponse_error = Rcode.string(rcode);
483 		return;
484 	}
485 
486 	if (!query.getQuestion().equals(response.getQuestion())) {
487 		// The answer doesn't match the question.  That's not good.
488 		badresponse = true;
489 		badresponse_error = "response does not match query";
490 		return;
491 	}
492 
493 	sr = cache.addMessage(response);
494 	if (sr == null)
495 		sr = cache.lookupRecords(current, type, credibility);
496 	if (verbose) {
497 		System.err.println("queried " + current + " " +
498 				   Type.string(type));
499 		System.err.println(sr);
500 	}
501 	processResponse(current, sr);
502 }
503 
504 private void
resolve(Name current, Name suffix)505 resolve(Name current, Name suffix) {
506 	doneCurrent = false;
507 	Name tname = null;
508 	if (suffix == null)
509 		tname = current;
510 	else {
511 		try {
512 			tname = Name.concatenate(current, suffix);
513 		}
514 		catch (NameTooLongException e) {
515 			nametoolong = true;
516 			return;
517 		}
518 	}
519 	lookup(tname);
520 }
521 
522 /**
523  * Performs the lookup, using the specified Cache, Resolver, and search path.
524  * @return The answers, or null if none are found.
525  */
526 public Record []
run()527 run() {
528 	if (done)
529 		reset();
530 	if (name.isAbsolute())
531 		resolve(name, null);
532 	else if (searchPath == null)
533 		resolve(name, Name.root);
534 	else {
535 		if (name.labels() > defaultNdots)
536 			resolve(name, Name.root);
537 		if (done)
538 			return answers;
539 
540 		for (int i = 0; i < searchPath.length; i++) {
541 			resolve(name, searchPath[i]);
542 			if (done)
543 				return answers;
544 			else if (foundAlias)
545 				break;
546 		}
547 	}
548 	if (!done) {
549 		if (badresponse) {
550 			result = TRY_AGAIN;
551 			error = badresponse_error;
552 			done = true;
553 		} else if (timedout) {
554 			result = TRY_AGAIN;
555 			error = "timed out";
556 			done = true;
557 		} else if (networkerror) {
558 			result = TRY_AGAIN;
559 			error = "network error";
560 			done = true;
561 		} else if (nxdomain) {
562 			result = HOST_NOT_FOUND;
563 			done = true;
564 		} else if (referral) {
565 			result = UNRECOVERABLE;
566 			error = "referral";
567 			done = true;
568 		} else if (nametoolong) {
569 			result = UNRECOVERABLE;
570 			error = "name too long";
571 			done = true;
572 		}
573 	}
574 	return answers;
575 }
576 
577 private void
checkDone()578 checkDone() {
579 	if (done && result != -1)
580 		return;
581 	StringBuffer sb = new StringBuffer("Lookup of " + name + " ");
582 	if (dclass != DClass.IN)
583 		sb.append(DClass.string(dclass) + " ");
584 	sb.append(Type.string(type) + " isn't done");
585 	throw new IllegalStateException(sb.toString());
586 }
587 
588 /**
589  * Returns the answers from the lookup.
590  * @return The answers, or null if none are found.
591  * @throws IllegalStateException The lookup has not completed.
592  */
593 public Record []
getAnswers()594 getAnswers() {
595 	checkDone();
596 	return answers;
597 }
598 
599 /**
600  * Returns all known aliases for this name.  Whenever a CNAME/DNAME is
601  * followed, an alias is added to this array.  The last element in this
602  * array will be the owner name for records in the answer, if there are any.
603  * @return The aliases.
604  * @throws IllegalStateException The lookup has not completed.
605  */
606 public Name []
getAliases()607 getAliases() {
608 	checkDone();
609 	if (aliases == null)
610 		return noAliases;
611 	return (Name []) aliases.toArray(new Name[aliases.size()]);
612 }
613 
614 /**
615  * Returns the result code of the lookup.
616  * @return The result code, which can be SUCCESSFUL, UNRECOVERABLE, TRY_AGAIN,
617  * HOST_NOT_FOUND, or TYPE_NOT_FOUND.
618  * @throws IllegalStateException The lookup has not completed.
619  */
620 public int
getResult()621 getResult() {
622 	checkDone();
623 	return result;
624 }
625 
626 /**
627  * Returns an error string describing the result code of this lookup.
628  * @return A string, which may either directly correspond the result code
629  * or be more specific.
630  * @throws IllegalStateException The lookup has not completed.
631  */
632 public String
getErrorString()633 getErrorString() {
634 	checkDone();
635 	if (error != null)
636 		return error;
637 	switch (result) {
638 		case SUCCESSFUL:	return "successful";
639 		case UNRECOVERABLE:	return "unrecoverable error";
640 		case TRY_AGAIN:		return "try again";
641 		case HOST_NOT_FOUND:	return "host not found";
642 		case TYPE_NOT_FOUND:	return "type not found";
643 	}
644 	throw new IllegalStateException("unknown result");
645 }
646 
647 }
648