001: // Copyright (c) 2002-2004 Brian Wellington (bwelling@xbill.org)
002:
003: package org.xbill.DNS;
004:
005: import java.util.*;
006: import java.io.*;
007: import java.net.*;
008:
009: /**
010: * The Lookup object issues queries to caching DNS servers. The input consists
011: * of a name, an optional type, and an optional class. Caching is enabled
012: * by default and used when possible to reduce the number of DNS requests.
013: * A Resolver, which defaults to an ExtendedResolver initialized with the
014: * resolvers located by the ResolverConfig class, performs the queries. A
015: * search path of domain suffixes is used to resolve relative names, and is
016: * also determined by the ResolverConfig class.
017: *
018: * A Lookup object may be reused, but should not be used by multiple threads.
019: *
020: * @see Cache
021: * @see Resolver
022: * @see ResolverConfig
023: *
024: * @author Brian Wellington
025: */
026:
027: public final class Lookup {
028:
029: private static Resolver defaultResolver;
030: private static Name[] defaultSearchPath;
031: private static Map defaultCaches;
032:
033: private Resolver resolver;
034: private Name[] searchPath;
035: private Cache cache;
036: private boolean temporary_cache;
037: private int credibility;
038: private Name name;
039: private int type;
040: private int dclass;
041: private boolean verbose;
042: private int iterations;
043: private boolean foundAlias;
044: private boolean done;
045: private boolean doneCurrent;
046: private List aliases;
047: private Record[] answers;
048: private int result;
049: private String error;
050: private boolean nxdomain;
051: private boolean badresponse;
052: private String badresponse_error;
053: private boolean networkerror;
054: private boolean timedout;
055: private boolean nametoolong;
056: private boolean referral;
057:
058: private static final Name[] noAliases = new Name[0];
059:
060: /** The lookup was successful. */
061: public static final int SUCCESSFUL = 0;
062:
063: /**
064: * The lookup failed due to a data or server error. Repeating the lookup
065: * would not be helpful.
066: */
067: public static final int UNRECOVERABLE = 1;
068:
069: /**
070: * The lookup failed due to a network error. Repeating the lookup may be
071: * helpful.
072: */
073: public static final int TRY_AGAIN = 2;
074:
075: /** The host does not exist. */
076: public static final int HOST_NOT_FOUND = 3;
077:
078: /** The host exists, but has no records associated with the queried type. */
079: public static final int TYPE_NOT_FOUND = 4;
080:
081: public static synchronized void refreshDefault() {
082:
083: try {
084: defaultResolver = new ExtendedResolver();
085: } catch (UnknownHostException e) {
086: throw new RuntimeException("Failed to initialize resolver");
087: }
088: defaultSearchPath = ResolverConfig.getCurrentConfig()
089: .searchPath();
090: defaultCaches = new HashMap();
091: }
092:
093: static {
094: refreshDefault();
095: }
096:
097: /**
098: * Gets the Resolver that will be used as the default by future Lookups.
099: * @return The default resolver.
100: */
101: public static synchronized Resolver getDefaultResolver() {
102: return defaultResolver;
103: }
104:
105: /**
106: * Sets the default Resolver to be used as the default by future Lookups.
107: * @param resolver The default resolver.
108: */
109: public static synchronized void setDefaultResolver(Resolver resolver) {
110: defaultResolver = resolver;
111: }
112:
113: /**
114: * Gets the Cache that will be used as the default for the specified
115: * class by future Lookups.
116: * @param dclass The class whose cache is being retrieved.
117: * @return The default cache for the specified class.
118: */
119: public static synchronized Cache getDefaultCache(int dclass) {
120: DClass.check(dclass);
121: Cache c = (Cache) defaultCaches.get(Mnemonic.toInteger(dclass));
122: if (c == null) {
123: c = new Cache(dclass);
124: defaultCaches.put(Mnemonic.toInteger(dclass), c);
125: }
126: return c;
127: }
128:
129: /**
130: * Sets the Cache to be used as the default for the specified class by future
131: * Lookups.
132: * @param cache The default cache for the specified class.
133: * @param dclass The class whose cache is being set.
134: */
135: public static synchronized void setDefaultCache(Cache cache,
136: int dclass) {
137: DClass.check(dclass);
138: defaultCaches.put(Mnemonic.toInteger(dclass), cache);
139: }
140:
141: /**
142: * Gets the search path that will be used as the default by future Lookups.
143: * @return The default search path.
144: */
145: public static synchronized Name[] getDefaultSearchPath() {
146: return defaultSearchPath;
147: }
148:
149: /**
150: * Sets the search path to be used as the default by future Lookups.
151: * @param domains The default search path.
152: */
153: public static synchronized void setDefaultSearchPath(Name[] domains) {
154: defaultSearchPath = domains;
155: }
156:
157: /**
158: * Sets the search path that will be used as the default by future Lookups.
159: * @param domains The default search path.
160: * @throws TextParseException A name in the array is not a valid DNS name.
161: */
162: public static synchronized void setDefaultSearchPath(
163: String[] domains) throws TextParseException {
164: if (domains == null) {
165: defaultSearchPath = null;
166: return;
167: }
168: Name[] newdomains = new Name[domains.length];
169: for (int i = 0; i < domains.length; i++)
170: newdomains[i] = Name.fromString(domains[i], Name.root);
171: defaultSearchPath = newdomains;
172: }
173:
174: private final void reset() {
175: iterations = 0;
176: foundAlias = false;
177: done = false;
178: doneCurrent = false;
179: aliases = null;
180: answers = null;
181: result = -1;
182: error = null;
183: nxdomain = false;
184: badresponse = false;
185: badresponse_error = null;
186: networkerror = false;
187: timedout = false;
188: nametoolong = false;
189: referral = false;
190: if (temporary_cache)
191: cache.clearCache();
192: }
193:
194: /**
195: * Create a Lookup object that will find records of the given name, type,
196: * and class. The lookup will use the default cache, resolver, and search
197: * path, and look for records that are reasonably credible.
198: * @param name The name of the desired records
199: * @param type The type of the desired records
200: * @param dclass The class of the desired records
201: * @throws IllegalArgumentException The type is a meta type other than ANY.
202: * @see Cache
203: * @see Resolver
204: * @see Credibility
205: * @see Name
206: * @see Type
207: * @see DClass
208: */
209: public Lookup(Name name, int type, int dclass) {
210: Type.check(type);
211: DClass.check(dclass);
212: if (!Type.isRR(type) && type != Type.ANY)
213: throw new IllegalArgumentException("Cannot query for "
214: + "meta-types other than ANY");
215: this .name = name;
216: this .type = type;
217: this .dclass = dclass;
218: synchronized (Lookup.class) {
219: this .resolver = getDefaultResolver();
220: this .searchPath = getDefaultSearchPath();
221: this .cache = getDefaultCache(dclass);
222: }
223: this .credibility = Credibility.NORMAL;
224: this .verbose = Options.check("verbose");
225: this .result = -1;
226: }
227:
228: /**
229: * Create a Lookup object that will find records of the given name and type
230: * in the IN class.
231: * @param name The name of the desired records
232: * @param type The type of the desired records
233: * @throws IllegalArgumentException The type is a meta type other than ANY.
234: * @see #Lookup(Name,int,int)
235: */
236: public Lookup(Name name, int type) {
237: this (name, type, DClass.IN);
238: }
239:
240: /**
241: * Create a Lookup object that will find records of type A at the given name
242: * in the IN class.
243: * @param name The name of the desired records
244: * @see #Lookup(Name,int,int)
245: */
246: public Lookup(Name name) {
247: this (name, Type.A, DClass.IN);
248: }
249:
250: /**
251: * Create a Lookup object that will find records of the given name, type,
252: * and class.
253: * @param name The name of the desired records
254: * @param type The type of the desired records
255: * @param dclass The class of the desired records
256: * @throws TextParseException The name is not a valid DNS name
257: * @throws IllegalArgumentException The type is a meta type other than ANY.
258: * @see #Lookup(Name,int,int)
259: */
260: public Lookup(String name, int type, int dclass)
261: throws TextParseException {
262: this (Name.fromString(name), type, dclass);
263: }
264:
265: /**
266: * Create a Lookup object that will find records of the given name and type
267: * in the IN class.
268: * @param name The name of the desired records
269: * @param type The type of the desired records
270: * @throws TextParseException The name is not a valid DNS name
271: * @throws IllegalArgumentException The type is a meta type other than ANY.
272: * @see #Lookup(Name,int,int)
273: */
274: public Lookup(String name, int type) throws TextParseException {
275: this (Name.fromString(name), type, DClass.IN);
276: }
277:
278: /**
279: * Create a Lookup object that will find records of type A at the given name
280: * in the IN class.
281: * @param name The name of the desired records
282: * @throws TextParseException The name is not a valid DNS name
283: * @see #Lookup(Name,int,int)
284: */
285: public Lookup(String name) throws TextParseException {
286: this (Name.fromString(name), Type.A, DClass.IN);
287: }
288:
289: /**
290: * Sets the resolver to use when performing this lookup. This overrides the
291: * default value.
292: * @param resolver The resolver to use.
293: */
294: public void setResolver(Resolver resolver) {
295: this .resolver = resolver;
296: }
297:
298: /**
299: * Sets the search path to use when performing this lookup. This overrides the
300: * default value.
301: * @param domains An array of names containing the search path.
302: */
303: public void setSearchPath(Name[] domains) {
304: this .searchPath = domains;
305: }
306:
307: /**
308: * Sets the search path to use when performing this lookup. This overrides the
309: * default value.
310: * @param domains An array of names containing the search path.
311: * @throws TextParseException A name in the array is not a valid DNS name.
312: */
313: public void setSearchPath(String[] domains)
314: throws TextParseException {
315: if (domains == null) {
316: this .searchPath = null;
317: return;
318: }
319: Name[] newdomains = new Name[domains.length];
320: for (int i = 0; i < domains.length; i++)
321: newdomains[i] = Name.fromString(domains[i], Name.root);
322: this .searchPath = newdomains;
323: }
324:
325: /**
326: * Sets the cache to use when performing this lookup. This overrides the
327: * default value. If the results of this lookup should not be permanently
328: * cached, null can be provided here.
329: * @param cache The cache to use.
330: */
331: public void setCache(Cache cache) {
332: if (cache == null) {
333: this .cache = new Cache(dclass);
334: this .temporary_cache = true;
335: } else {
336: this .cache = cache;
337: this .temporary_cache = false;
338: }
339: }
340:
341: /**
342: * Sets the minimum credibility level that will be accepted when performing
343: * the lookup. This defaults to Credibility.NORMAL.
344: * @param credibility The minimum credibility level.
345: */
346: public void setCredibility(int credibility) {
347: this .credibility = credibility;
348: }
349:
350: private void follow(Name name, Name oldname) {
351: foundAlias = true;
352: badresponse = false;
353: networkerror = false;
354: timedout = false;
355: nxdomain = false;
356: referral = false;
357: iterations++;
358: if (iterations >= 6 || name.equals(oldname)) {
359: result = UNRECOVERABLE;
360: error = "CNAME loop";
361: done = true;
362: return;
363: }
364: if (aliases == null)
365: aliases = new ArrayList();
366: aliases.add(oldname);
367: lookup(name);
368: }
369:
370: private void processResponse(Name name, SetResponse response) {
371: if (response.isSuccessful()) {
372: RRset[] rrsets = response.answers();
373: List l = new ArrayList();
374: Iterator it;
375: int i;
376:
377: for (i = 0; i < rrsets.length; i++) {
378: it = rrsets[i].rrs();
379: while (it.hasNext())
380: l.add(it.next());
381: }
382:
383: result = SUCCESSFUL;
384: answers = (Record[]) l.toArray(new Record[l.size()]);
385: done = true;
386: } else if (response.isNXDOMAIN()) {
387: nxdomain = true;
388: doneCurrent = true;
389: if (iterations > 0) {
390: result = HOST_NOT_FOUND;
391: done = true;
392: }
393: } else if (response.isNXRRSET()) {
394: result = TYPE_NOT_FOUND;
395: answers = null;
396: done = true;
397: } else if (response.isCNAME()) {
398: CNAMERecord cname = response.getCNAME();
399: follow(cname.getTarget(), name);
400: } else if (response.isDNAME()) {
401: DNAMERecord dname = response.getDNAME();
402: Name newname = null;
403: try {
404: follow(name.fromDNAME(dname), name);
405: } catch (NameTooLongException e) {
406: result = UNRECOVERABLE;
407: error = "Invalid DNAME target";
408: done = true;
409: }
410: } else if (response.isDelegation()) {
411: // We shouldn't get a referral. Ignore it.
412: referral = true;
413: }
414: }
415:
416: private void lookup(Name current) {
417: SetResponse sr = cache
418: .lookupRecords(current, type, credibility);
419: if (verbose) {
420: System.err.println("lookup " + current + " "
421: + Type.string(type));
422: System.err.println(sr);
423: }
424: processResponse(current, sr);
425: if (done || doneCurrent)
426: return;
427:
428: Record question = Record.newRecord(current, type, dclass);
429: Message query = Message.newQuery(question);
430: Message response = null;
431: try {
432: response = resolver.send(query);
433: } catch (IOException e) {
434: // A network error occurred. Press on.
435: if (e instanceof InterruptedIOException)
436: timedout = true;
437: else
438: networkerror = true;
439: return;
440: }
441: int rcode = response.getHeader().getRcode();
442: if (rcode != Rcode.NOERROR && rcode != Rcode.NXDOMAIN) {
443: // The server we contacted is broken or otherwise unhelpful.
444: // Press on.
445: badresponse = true;
446: badresponse_error = Rcode.string(rcode);
447: return;
448: }
449:
450: if (!query.getQuestion().equals(response.getQuestion())) {
451: // The answer doesn't match the question. That's not good.
452: badresponse = true;
453: badresponse_error = "response does not match query";
454: return;
455: }
456:
457: sr = cache.addMessage(response);
458: if (sr == null)
459: sr = cache.lookupRecords(current, type, credibility);
460: if (verbose) {
461: System.err.println("queried " + current + " "
462: + Type.string(type));
463: System.err.println(sr);
464: }
465: processResponse(current, sr);
466: }
467:
468: private void resolve(Name current, Name suffix) {
469: doneCurrent = false;
470: Name tname = null;
471: if (suffix == null)
472: tname = current;
473: else {
474: try {
475: tname = Name.concatenate(current, suffix);
476: } catch (NameTooLongException e) {
477: nametoolong = true;
478: return;
479: }
480: }
481: lookup(tname);
482: }
483:
484: /**
485: * Performs the lookup, using the specified Cache, Resolver, and search path.
486: * @return The answers, or null if none are found.
487: */
488: public Record[] run() {
489: if (done)
490: reset();
491: if (name.isAbsolute())
492: resolve(name, null);
493: else if (searchPath == null)
494: resolve(name, Name.root);
495: else {
496: if (name.labels() > 1)
497: resolve(name, Name.root);
498: if (done)
499: return answers;
500:
501: for (int i = 0; i < searchPath.length; i++) {
502: resolve(name, searchPath[i]);
503: if (done)
504: return answers;
505: else if (foundAlias)
506: break;
507: }
508: }
509: if (!done) {
510: if (badresponse) {
511: result = TRY_AGAIN;
512: error = badresponse_error;
513: done = true;
514: } else if (timedout) {
515: result = TRY_AGAIN;
516: error = "timed out";
517: done = true;
518: } else if (networkerror) {
519: result = TRY_AGAIN;
520: error = "network error";
521: done = true;
522: } else if (nxdomain) {
523: result = HOST_NOT_FOUND;
524: done = true;
525: } else if (referral) {
526: result = UNRECOVERABLE;
527: error = "referral";
528: done = true;
529: } else if (nametoolong) {
530: result = UNRECOVERABLE;
531: error = "name too long";
532: done = true;
533: }
534: }
535: return answers;
536: }
537:
538: private void checkDone() {
539: if (done && result != -1)
540: return;
541: StringBuffer sb = new StringBuffer("Lookup of " + name + " ");
542: if (dclass != DClass.IN)
543: sb.append(DClass.string(dclass) + " ");
544: sb.append(Type.string(type) + " isn't done");
545: throw new IllegalStateException(sb.toString());
546: }
547:
548: /**
549: * Returns the answers from the lookup.
550: * @return The answers, or null if none are found.
551: * @throws IllegalStateException The lookup has not completed.
552: */
553: public Record[] getAnswers() {
554: checkDone();
555: return answers;
556: }
557:
558: /**
559: * Returns all known aliases for this name. Whenever a CNAME/DNAME is
560: * followed, an alias is added to this array. The last element in this
561: * array will be the owner name for records in the answer, if there are any.
562: * @return The aliases.
563: * @throws IllegalStateException The lookup has not completed.
564: */
565: public Name[] getAliases() {
566: checkDone();
567: if (aliases == null)
568: return noAliases;
569: return (Name[]) aliases.toArray(new Name[aliases.size()]);
570: }
571:
572: /**
573: * Returns the result code of the lookup.
574: * @return The result code, which can be SUCCESSFUL, UNRECOVERABLE, TRY_AGAIN,
575: * HOST_NOT_FOUND, or TYPE_NOT_FOUND.
576: * @throws IllegalStateException The lookup has not completed.
577: */
578: public int getResult() {
579: checkDone();
580: return result;
581: }
582:
583: /**
584: * Returns an error string describing the result code of this lookup.
585: * @return A string, which may either directly correspond the result code
586: * or be more specific.
587: * @throws IllegalStateException The lookup has not completed.
588: */
589: public String getErrorString() {
590: checkDone();
591: if (error != null)
592: return error;
593: switch (result) {
594: case SUCCESSFUL:
595: return "successful";
596: case UNRECOVERABLE:
597: return "unrecoverable error";
598: case TRY_AGAIN:
599: return "try again";
600: case HOST_NOT_FOUND:
601: return "host not found";
602: case TYPE_NOT_FOUND:
603: return "type not found";
604: }
605: throw new IllegalStateException("unknown result");
606: }
607:
608: }
|