blob: 0beba59903e4803beb43b84df8a280295e67f14f [file] [log] [blame]
Shuyi Chend7955ce2013-05-22 14:51:55 -07001// Copyright (c) 2002-2004 Brian Wellington (bwelling@xbill.org)
2
3package org.xbill.DNS;
4
5import java.util.*;
6import java.io.*;
7import 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
27public final class Lookup {
28
29private static Resolver defaultResolver;
30private static Name [] defaultSearchPath;
31private static Map defaultCaches;
32private static int defaultNdots;
33
34private Resolver resolver;
35private Name [] searchPath;
36private Cache cache;
37private boolean temporary_cache;
38private int credibility;
39private Name name;
40private int type;
41private int dclass;
42private boolean verbose;
43private int iterations;
44private boolean foundAlias;
45private boolean done;
46private boolean doneCurrent;
47private List aliases;
48private Record [] answers;
49private int result;
50private String error;
51private boolean nxdomain;
52private boolean badresponse;
53private String badresponse_error;
54private boolean networkerror;
55private boolean timedout;
56private boolean nametoolong;
57private boolean referral;
58
59private static final Name [] noAliases = new Name[0];
60
61/** The lookup was successful. */
62public 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 */
68public static final int UNRECOVERABLE = 1;
69
70/**
71 * The lookup failed due to a network error. Repeating the lookup may be
72 * helpful.
73 */
74public static final int TRY_AGAIN = 2;
75
76/** The host does not exist. */
77public static final int HOST_NOT_FOUND = 3;
78
79/** The host exists, but has no records associated with the queried type. */
80public static final int TYPE_NOT_FOUND = 4;
81
82public static synchronized void
83refreshDefault() {
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
96static {
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 */
104public static synchronized Resolver
105getDefaultResolver() {
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 */
113public static synchronized void
114setDefaultResolver(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 */
124public static synchronized Cache
125getDefaultCache(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 */
141public static synchronized void
142setDefaultCache(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 */
151public static synchronized Name []
152getDefaultSearchPath() {
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 */
160public static synchronized void
161setDefaultSearchPath(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 */
170public static synchronized void
171setDefaultSearchPath(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
182private final void
183reset() {
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 */
218public
219Lookup(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 */
246public
247Lookup(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 */
257public
258Lookup(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 */
272public
273Lookup(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 */
286public
287Lookup(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 */
298public
299Lookup(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 */
308public void
309setResolver(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 */
318public void
319setSearchPath(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 */
329public void
330setSearchPath(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 */
347public void
348setCache(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 */
366public void
367setNdots(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 */
379public void
380setCredibility(int credibility) {
381 this.credibility = credibility;
382}
383
384private void
385follow(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
405private void
406processResponse(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
451private void
452lookup(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
504private void
505resolve(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 */
526public Record []
527run() {
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
577private void
578checkDone() {
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 */
593public Record []
594getAnswers() {
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 */
606public Name []
607getAliases() {
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 */
620public int
621getResult() {
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 */
632public String
633getErrorString() {
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}