diff options
Diffstat (limited to 'src/org/xbill/DNS/Lookup.java')
-rw-r--r-- | src/org/xbill/DNS/Lookup.java | 647 |
1 files changed, 647 insertions, 0 deletions
diff --git a/src/org/xbill/DNS/Lookup.java b/src/org/xbill/DNS/Lookup.java new file mode 100644 index 0000000..0beba59 --- /dev/null +++ b/src/org/xbill/DNS/Lookup.java @@ -0,0 +1,647 @@ +// Copyright (c) 2002-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.util.*; +import java.io.*; +import java.net.*; + +/** + * The Lookup object issues queries to caching DNS servers. The input consists + * of a name, an optional type, and an optional class. Caching is enabled + * by default and used when possible to reduce the number of DNS requests. + * A Resolver, which defaults to an ExtendedResolver initialized with the + * resolvers located by the ResolverConfig class, performs the queries. A + * search path of domain suffixes is used to resolve relative names, and is + * also determined by the ResolverConfig class. + * + * A Lookup object may be reused, but should not be used by multiple threads. + * + * @see Cache + * @see Resolver + * @see ResolverConfig + * + * @author Brian Wellington + */ + +public final class Lookup { + +private static Resolver defaultResolver; +private static Name [] defaultSearchPath; +private static Map defaultCaches; +private static int defaultNdots; + +private Resolver resolver; +private Name [] searchPath; +private Cache cache; +private boolean temporary_cache; +private int credibility; +private Name name; +private int type; +private int dclass; +private boolean verbose; +private int iterations; +private boolean foundAlias; +private boolean done; +private boolean doneCurrent; +private List aliases; +private Record [] answers; +private int result; +private String error; +private boolean nxdomain; +private boolean badresponse; +private String badresponse_error; +private boolean networkerror; +private boolean timedout; +private boolean nametoolong; +private boolean referral; + +private static final Name [] noAliases = new Name[0]; + +/** The lookup was successful. */ +public static final int SUCCESSFUL = 0; + +/** + * The lookup failed due to a data or server error. Repeating the lookup + * would not be helpful. + */ +public static final int UNRECOVERABLE = 1; + +/** + * The lookup failed due to a network error. Repeating the lookup may be + * helpful. + */ +public static final int TRY_AGAIN = 2; + +/** The host does not exist. */ +public static final int HOST_NOT_FOUND = 3; + +/** The host exists, but has no records associated with the queried type. */ +public static final int TYPE_NOT_FOUND = 4; + +public static synchronized void +refreshDefault() { + + try { + defaultResolver = new ExtendedResolver(); + } + catch (UnknownHostException e) { + throw new RuntimeException("Failed to initialize resolver"); + } + defaultSearchPath = ResolverConfig.getCurrentConfig().searchPath(); + defaultCaches = new HashMap(); + defaultNdots = ResolverConfig.getCurrentConfig().ndots(); +} + +static { + refreshDefault(); +} + +/** + * Gets the Resolver that will be used as the default by future Lookups. + * @return The default resolver. + */ +public static synchronized Resolver +getDefaultResolver() { + return defaultResolver; +} + +/** + * Sets the default Resolver to be used as the default by future Lookups. + * @param resolver The default resolver. + */ +public static synchronized void +setDefaultResolver(Resolver resolver) { + defaultResolver = resolver; +} + +/** + * Gets the Cache that will be used as the default for the specified + * class by future Lookups. + * @param dclass The class whose cache is being retrieved. + * @return The default cache for the specified class. + */ +public static synchronized Cache +getDefaultCache(int dclass) { + DClass.check(dclass); + Cache c = (Cache) defaultCaches.get(Mnemonic.toInteger(dclass)); + if (c == null) { + c = new Cache(dclass); + defaultCaches.put(Mnemonic.toInteger(dclass), c); + } + return c; +} + +/** + * Sets the Cache to be used as the default for the specified class by future + * Lookups. + * @param cache The default cache for the specified class. + * @param dclass The class whose cache is being set. + */ +public static synchronized void +setDefaultCache(Cache cache, int dclass) { + DClass.check(dclass); + defaultCaches.put(Mnemonic.toInteger(dclass), cache); +} + +/** + * Gets the search path that will be used as the default by future Lookups. + * @return The default search path. + */ +public static synchronized Name [] +getDefaultSearchPath() { + return defaultSearchPath; +} + +/** + * Sets the search path to be used as the default by future Lookups. + * @param domains The default search path. + */ +public static synchronized void +setDefaultSearchPath(Name [] domains) { + defaultSearchPath = domains; +} + +/** + * Sets the search path that will be used as the default by future Lookups. + * @param domains The default search path. + * @throws TextParseException A name in the array is not a valid DNS name. + */ +public static synchronized void +setDefaultSearchPath(String [] domains) throws TextParseException { + if (domains == null) { + defaultSearchPath = null; + return; + } + Name [] newdomains = new Name[domains.length]; + for (int i = 0; i < domains.length; i++) + newdomains[i] = Name.fromString(domains[i], Name.root); + defaultSearchPath = newdomains; +} + +private final void +reset() { + iterations = 0; + foundAlias = false; + done = false; + doneCurrent = false; + aliases = null; + answers = null; + result = -1; + error = null; + nxdomain = false; + badresponse = false; + badresponse_error = null; + networkerror = false; + timedout = false; + nametoolong = false; + referral = false; + if (temporary_cache) + cache.clearCache(); +} + +/** + * Create a Lookup object that will find records of the given name, type, + * and class. The lookup will use the default cache, resolver, and search + * path, and look for records that are reasonably credible. + * @param name The name of the desired records + * @param type The type of the desired records + * @param dclass The class of the desired records + * @throws IllegalArgumentException The type is a meta type other than ANY. + * @see Cache + * @see Resolver + * @see Credibility + * @see Name + * @see Type + * @see DClass + */ +public +Lookup(Name name, int type, int dclass) { + Type.check(type); + DClass.check(dclass); + if (!Type.isRR(type) && type != Type.ANY) + throw new IllegalArgumentException("Cannot query for " + + "meta-types other than ANY"); + this.name = name; + this.type = type; + this.dclass = dclass; + synchronized (Lookup.class) { + this.resolver = getDefaultResolver(); + this.searchPath = getDefaultSearchPath(); + this.cache = getDefaultCache(dclass); + } + this.credibility = Credibility.NORMAL; + this.verbose = Options.check("verbose"); + this.result = -1; +} + +/** + * Create a Lookup object that will find records of the given name and type + * in the IN class. + * @param name The name of the desired records + * @param type The type of the desired records + * @throws IllegalArgumentException The type is a meta type other than ANY. + * @see #Lookup(Name,int,int) + */ +public +Lookup(Name name, int type) { + this(name, type, DClass.IN); +} + +/** + * Create a Lookup object that will find records of type A at the given name + * in the IN class. + * @param name The name of the desired records + * @see #Lookup(Name,int,int) + */ +public +Lookup(Name name) { + this(name, Type.A, DClass.IN); +} + +/** + * Create a Lookup object that will find records of the given name, type, + * and class. + * @param name The name of the desired records + * @param type The type of the desired records + * @param dclass The class of the desired records + * @throws TextParseException The name is not a valid DNS name + * @throws IllegalArgumentException The type is a meta type other than ANY. + * @see #Lookup(Name,int,int) + */ +public +Lookup(String name, int type, int dclass) throws TextParseException { + this(Name.fromString(name), type, dclass); +} + +/** + * Create a Lookup object that will find records of the given name and type + * in the IN class. + * @param name The name of the desired records + * @param type The type of the desired records + * @throws TextParseException The name is not a valid DNS name + * @throws IllegalArgumentException The type is a meta type other than ANY. + * @see #Lookup(Name,int,int) + */ +public +Lookup(String name, int type) throws TextParseException { + this(Name.fromString(name), type, DClass.IN); +} + +/** + * Create a Lookup object that will find records of type A at the given name + * in the IN class. + * @param name The name of the desired records + * @throws TextParseException The name is not a valid DNS name + * @see #Lookup(Name,int,int) + */ +public +Lookup(String name) throws TextParseException { + this(Name.fromString(name), Type.A, DClass.IN); +} + +/** + * Sets the resolver to use when performing this lookup. This overrides the + * default value. + * @param resolver The resolver to use. + */ +public void +setResolver(Resolver resolver) { + this.resolver = resolver; +} + +/** + * Sets the search path to use when performing this lookup. This overrides the + * default value. + * @param domains An array of names containing the search path. + */ +public void +setSearchPath(Name [] domains) { + this.searchPath = domains; +} + +/** + * Sets the search path to use when performing this lookup. This overrides the + * default value. + * @param domains An array of names containing the search path. + * @throws TextParseException A name in the array is not a valid DNS name. + */ +public void +setSearchPath(String [] domains) throws TextParseException { + if (domains == null) { + this.searchPath = null; + return; + } + Name [] newdomains = new Name[domains.length]; + for (int i = 0; i < domains.length; i++) + newdomains[i] = Name.fromString(domains[i], Name.root); + this.searchPath = newdomains; +} + +/** + * Sets the cache to use when performing this lookup. This overrides the + * default value. If the results of this lookup should not be permanently + * cached, null can be provided here. + * @param cache The cache to use. + */ +public void +setCache(Cache cache) { + if (cache == null) { + this.cache = new Cache(dclass); + this.temporary_cache = true; + } else { + this.cache = cache; + this.temporary_cache = false; + } +} + +/** + * Sets ndots to use when performing this lookup, overriding the default value. + * Specifically, this refers to the number of "dots" which, if present in a + * name, indicate that a lookup for the absolute name should be attempted + * before appending any search path elements. + * @param ndots The ndots value to use, which must be greater than or equal to + * 0. + */ +public void +setNdots(int ndots) { + if (ndots < 0) + throw new IllegalArgumentException("Illegal ndots value: " + + ndots); + defaultNdots = ndots; +} + +/** + * Sets the minimum credibility level that will be accepted when performing + * the lookup. This defaults to Credibility.NORMAL. + * @param credibility The minimum credibility level. + */ +public void +setCredibility(int credibility) { + this.credibility = credibility; +} + +private void +follow(Name name, Name oldname) { + foundAlias = true; + badresponse = false; + networkerror = false; + timedout = false; + nxdomain = false; + referral = false; + iterations++; + if (iterations >= 6 || name.equals(oldname)) { + result = UNRECOVERABLE; + error = "CNAME loop"; + done = true; + return; + } + if (aliases == null) + aliases = new ArrayList(); + aliases.add(oldname); + lookup(name); +} + +private void +processResponse(Name name, SetResponse response) { + if (response.isSuccessful()) { + RRset [] rrsets = response.answers(); + List l = new ArrayList(); + Iterator it; + int i; + + for (i = 0; i < rrsets.length; i++) { + it = rrsets[i].rrs(); + while (it.hasNext()) + l.add(it.next()); + } + + result = SUCCESSFUL; + answers = (Record []) l.toArray(new Record[l.size()]); + done = true; + } else if (response.isNXDOMAIN()) { + nxdomain = true; + doneCurrent = true; + if (iterations > 0) { + result = HOST_NOT_FOUND; + done = true; + } + } else if (response.isNXRRSET()) { + result = TYPE_NOT_FOUND; + answers = null; + done = true; + } else if (response.isCNAME()) { + CNAMERecord cname = response.getCNAME(); + follow(cname.getTarget(), name); + } else if (response.isDNAME()) { + DNAMERecord dname = response.getDNAME(); + try { + follow(name.fromDNAME(dname), name); + } catch (NameTooLongException e) { + result = UNRECOVERABLE; + error = "Invalid DNAME target"; + done = true; + } + } else if (response.isDelegation()) { + // We shouldn't get a referral. Ignore it. + referral = true; + } +} + +private void +lookup(Name current) { + SetResponse sr = cache.lookupRecords(current, type, credibility); + if (verbose) { + System.err.println("lookup " + current + " " + + Type.string(type)); + System.err.println(sr); + } + processResponse(current, sr); + if (done || doneCurrent) + return; + + Record question = Record.newRecord(current, type, dclass); + Message query = Message.newQuery(question); + Message response = null; + try { + response = resolver.send(query); + } + catch (IOException e) { + // A network error occurred. Press on. + if (e instanceof InterruptedIOException) + timedout = true; + else + networkerror = true; + return; + } + int rcode = response.getHeader().getRcode(); + if (rcode != Rcode.NOERROR && rcode != Rcode.NXDOMAIN) { + // The server we contacted is broken or otherwise unhelpful. + // Press on. + badresponse = true; + badresponse_error = Rcode.string(rcode); + return; + } + + if (!query.getQuestion().equals(response.getQuestion())) { + // The answer doesn't match the question. That's not good. + badresponse = true; + badresponse_error = "response does not match query"; + return; + } + + sr = cache.addMessage(response); + if (sr == null) + sr = cache.lookupRecords(current, type, credibility); + if (verbose) { + System.err.println("queried " + current + " " + + Type.string(type)); + System.err.println(sr); + } + processResponse(current, sr); +} + +private void +resolve(Name current, Name suffix) { + doneCurrent = false; + Name tname = null; + if (suffix == null) + tname = current; + else { + try { + tname = Name.concatenate(current, suffix); + } + catch (NameTooLongException e) { + nametoolong = true; + return; + } + } + lookup(tname); +} + +/** + * Performs the lookup, using the specified Cache, Resolver, and search path. + * @return The answers, or null if none are found. + */ +public Record [] +run() { + if (done) + reset(); + if (name.isAbsolute()) + resolve(name, null); + else if (searchPath == null) + resolve(name, Name.root); + else { + if (name.labels() > defaultNdots) + resolve(name, Name.root); + if (done) + return answers; + + for (int i = 0; i < searchPath.length; i++) { + resolve(name, searchPath[i]); + if (done) + return answers; + else if (foundAlias) + break; + } + } + if (!done) { + if (badresponse) { + result = TRY_AGAIN; + error = badresponse_error; + done = true; + } else if (timedout) { + result = TRY_AGAIN; + error = "timed out"; + done = true; + } else if (networkerror) { + result = TRY_AGAIN; + error = "network error"; + done = true; + } else if (nxdomain) { + result = HOST_NOT_FOUND; + done = true; + } else if (referral) { + result = UNRECOVERABLE; + error = "referral"; + done = true; + } else if (nametoolong) { + result = UNRECOVERABLE; + error = "name too long"; + done = true; + } + } + return answers; +} + +private void +checkDone() { + if (done && result != -1) + return; + StringBuffer sb = new StringBuffer("Lookup of " + name + " "); + if (dclass != DClass.IN) + sb.append(DClass.string(dclass) + " "); + sb.append(Type.string(type) + " isn't done"); + throw new IllegalStateException(sb.toString()); +} + +/** + * Returns the answers from the lookup. + * @return The answers, or null if none are found. + * @throws IllegalStateException The lookup has not completed. + */ +public Record [] +getAnswers() { + checkDone(); + return answers; +} + +/** + * Returns all known aliases for this name. Whenever a CNAME/DNAME is + * followed, an alias is added to this array. The last element in this + * array will be the owner name for records in the answer, if there are any. + * @return The aliases. + * @throws IllegalStateException The lookup has not completed. + */ +public Name [] +getAliases() { + checkDone(); + if (aliases == null) + return noAliases; + return (Name []) aliases.toArray(new Name[aliases.size()]); +} + +/** + * Returns the result code of the lookup. + * @return The result code, which can be SUCCESSFUL, UNRECOVERABLE, TRY_AGAIN, + * HOST_NOT_FOUND, or TYPE_NOT_FOUND. + * @throws IllegalStateException The lookup has not completed. + */ +public int +getResult() { + checkDone(); + return result; +} + +/** + * Returns an error string describing the result code of this lookup. + * @return A string, which may either directly correspond the result code + * or be more specific. + * @throws IllegalStateException The lookup has not completed. + */ +public String +getErrorString() { + checkDone(); + if (error != null) + return error; + switch (result) { + case SUCCESSFUL: return "successful"; + case UNRECOVERABLE: return "unrecoverable error"; + case TRY_AGAIN: return "try again"; + case HOST_NOT_FOUND: return "host not found"; + case TYPE_NOT_FOUND: return "type not found"; + } + throw new IllegalStateException("unknown result"); +} + +} |