Home | History | Annotate | Download | only in DNS
      1 // Copyright (c) 2002-2004 Brian Wellington (bwelling (at) 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
     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 {
     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
    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
    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
    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
    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 []
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    380 setCredibility(int credibility) {
    381 	this.credibility = credibility;
    382 }
    383 
    384 private void
    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
    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
    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
    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 []
    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
    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 []
    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 []
    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
    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
    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