aboutsummaryrefslogtreecommitdiff
path: root/src/org/xbill/DNS/Lookup.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/org/xbill/DNS/Lookup.java')
-rw-r--r--src/org/xbill/DNS/Lookup.java647
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");
+}
+
+}