1
// Copyright (c) 2002-2004 Brian Wellington (bwelling@xbill.org)
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.
18
* A Lookup object may be reused, but should not be used by multiple threads.
24
* @author Brian Wellington
27
public final class Lookup {
29
private static Resolver defaultResolver;
30
private static Name [] defaultSearchPath;
31
private static Map defaultCaches;
33
private Resolver resolver;
34
private Name [] searchPath;
36
private boolean temporary_cache;
37
private int credibility;
41
private boolean verbose;
42
private int iterations;
43
private boolean foundAlias;
45
private boolean doneCurrent;
47
private Record [] answers;
50
private boolean nxdomain;
51
private boolean badresponse;
52
private String badresponse_error;
53
private boolean networkerror;
54
private boolean timedout;
55
private boolean nametoolong;
56
private boolean referral;
58
private static final Name [] noAliases = new Name[0];
60
/** The lookup was successful. */
61
public static final int SUCCESSFUL = 0;
64
* The lookup failed due to a data or server error. Repeating the lookup
65
* would not be helpful.
67
public static final int UNRECOVERABLE = 1;
70
* The lookup failed due to a network error. Repeating the lookup may be
73
public static final int TRY_AGAIN = 2;
75
/** The host does not exist. */
76
public static final int HOST_NOT_FOUND = 3;
78
/** The host exists, but has no records associated with the queried type. */
79
public static final int TYPE_NOT_FOUND = 4;
81
public static synchronized void
85
defaultResolver = new ExtendedResolver();
87
catch (UnknownHostException e) {
88
throw new RuntimeException("Failed to initialize resolver");
90
defaultSearchPath = ResolverConfig.getCurrentConfig().searchPath();
91
defaultCaches = new HashMap();
99
* Gets the Resolver that will be used as the default by future Lookups.
100
* @return The default resolver.
102
public static synchronized Resolver
103
getDefaultResolver() {
104
return defaultResolver;
108
* Sets the default Resolver to be used as the default by future Lookups.
109
* @param resolver The default resolver.
111
public static synchronized void
112
setDefaultResolver(Resolver resolver) {
113
defaultResolver = resolver;
117
* Gets the Cache that will be used as the default for the specified
118
* class by future Lookups.
119
* @param dclass The class whose cache is being retrieved.
120
* @return The default cache for the specified class.
122
public static synchronized Cache
123
getDefaultCache(int dclass) {
124
DClass.check(dclass);
125
Cache c = (Cache) defaultCaches.get(Mnemonic.toInteger(dclass));
127
c = new Cache(dclass);
128
defaultCaches.put(Mnemonic.toInteger(dclass), c);
134
* Sets the Cache to be used as the default for the specified class by future
136
* @param cache The default cache for the specified class.
137
* @param dclass The class whose cache is being set.
139
public static synchronized void
140
setDefaultCache(Cache cache, int dclass) {
141
DClass.check(dclass);
142
defaultCaches.put(Mnemonic.toInteger(dclass), cache);
146
* Gets the search path that will be used as the default by future Lookups.
147
* @return The default search path.
149
public static synchronized Name []
150
getDefaultSearchPath() {
151
return defaultSearchPath;
155
* Sets the search path to be used as the default by future Lookups.
156
* @param domains The default search path.
158
public static synchronized void
159
setDefaultSearchPath(Name [] domains) {
160
defaultSearchPath = domains;
164
* Sets the search path that will be used as the default by future Lookups.
165
* @param domains The default search path.
166
* @throws TextParseException A name in the array is not a valid DNS name.
168
public static synchronized void
169
setDefaultSearchPath(String [] domains) throws TextParseException {
170
if (domains == null) {
171
defaultSearchPath = null;
174
Name [] newdomains = new Name[domains.length];
175
for (int i = 0; i < domains.length; i++)
176
newdomains[i] = Name.fromString(domains[i], Name.root);
177
defaultSearchPath = newdomains;
192
badresponse_error = null;
193
networkerror = false;
202
* Create a Lookup object that will find records of the given name, type,
203
* and class. The lookup will use the default cache, resolver, and search
204
* path, and look for records that are reasonably credible.
205
* @param name The name of the desired records
206
* @param type The type of the desired records
207
* @param dclass The class of the desired records
208
* @throws IllegalArgumentException The type is a meta type other than ANY.
217
Lookup(Name name, int type, int dclass) {
219
DClass.check(dclass);
220
if (!Type.isRR(type) && type != Type.ANY)
221
throw new IllegalArgumentException("Cannot query for " +
222
"meta-types other than ANY");
225
this.dclass = dclass;
226
synchronized (Lookup.class) {
227
this.resolver = getDefaultResolver();
228
this.searchPath = getDefaultSearchPath();
229
this.cache = getDefaultCache(dclass);
231
this.credibility = Credibility.NORMAL;
232
this.verbose = Options.check("verbose");
237
* Create a Lookup object that will find records of the given name and type
239
* @param name The name of the desired records
240
* @param type The type of the desired records
241
* @throws IllegalArgumentException The type is a meta type other than ANY.
242
* @see #Lookup(Name,int,int)
245
Lookup(Name name, int type) {
246
this(name, type, DClass.IN);
250
* Create a Lookup object that will find records of type A at the given name
252
* @param name The name of the desired records
253
* @see #Lookup(Name,int,int)
257
this(name, Type.A, DClass.IN);
261
* Create a Lookup object that will find records of the given name, type,
263
* @param name The name of the desired records
264
* @param type The type of the desired records
265
* @param dclass The class of the desired records
266
* @throws TextParseException The name is not a valid DNS name
267
* @throws IllegalArgumentException The type is a meta type other than ANY.
268
* @see #Lookup(Name,int,int)
271
Lookup(String name, int type, int dclass) throws TextParseException {
272
this(Name.fromString(name), type, dclass);
276
* Create a Lookup object that will find records of the given name and type
278
* @param name The name of the desired records
279
* @param type The type of the desired records
280
* @throws TextParseException The name is not a valid DNS name
281
* @throws IllegalArgumentException The type is a meta type other than ANY.
282
* @see #Lookup(Name,int,int)
285
Lookup(String name, int type) throws TextParseException {
286
this(Name.fromString(name), type, DClass.IN);
290
* Create a Lookup object that will find records of type A at the given name
292
* @param name The name of the desired records
293
* @throws TextParseException The name is not a valid DNS name
294
* @see #Lookup(Name,int,int)
297
Lookup(String name) throws TextParseException {
298
this(Name.fromString(name), Type.A, DClass.IN);
302
* Sets the resolver to use when performing this lookup. This overrides the
304
* @param resolver The resolver to use.
307
setResolver(Resolver resolver) {
308
this.resolver = resolver;
312
* Sets the search path to use when performing this lookup. This overrides the
314
* @param domains An array of names containing the search path.
317
setSearchPath(Name [] domains) {
318
this.searchPath = domains;
322
* Sets the search path to use when performing this lookup. This overrides the
324
* @param domains An array of names containing the search path.
325
* @throws TextParseException A name in the array is not a valid DNS name.
328
setSearchPath(String [] domains) throws TextParseException {
329
if (domains == null) {
330
this.searchPath = null;
333
Name [] newdomains = new Name[domains.length];
334
for (int i = 0; i < domains.length; i++)
335
newdomains[i] = Name.fromString(domains[i], Name.root);
336
this.searchPath = newdomains;
340
* Sets the cache to use when performing this lookup. This overrides the
341
* default value. If the results of this lookup should not be permanently
342
* cached, null can be provided here.
343
* @param cache The cache to use.
346
setCache(Cache cache) {
348
this.cache = new Cache(dclass);
349
this.temporary_cache = true;
352
this.temporary_cache = false;
357
* Sets the minimum credibility level that will be accepted when performing
358
* the lookup. This defaults to Credibility.NORMAL.
359
* @param credibility The minimum credibility level.
362
setCredibility(int credibility) {
363
this.credibility = credibility;
367
follow(Name name, Name oldname) {
370
networkerror = false;
375
if (iterations >= 6 || name.equals(oldname)) {
376
result = UNRECOVERABLE;
377
error = "CNAME loop";
382
aliases = new ArrayList();
383
aliases.add(oldname);
388
processResponse(Name name, SetResponse response) {
389
if (response.isSuccessful()) {
390
RRset [] rrsets = response.answers();
391
List l = new ArrayList();
395
for (i = 0; i < rrsets.length; i++) {
396
it = rrsets[i].rrs();
402
answers = (Record []) l.toArray(new Record[l.size()]);
404
} else if (response.isNXDOMAIN()) {
407
if (iterations > 0) {
408
result = HOST_NOT_FOUND;
411
} else if (response.isNXRRSET()) {
412
result = TYPE_NOT_FOUND;
415
} else if (response.isCNAME()) {
416
CNAMERecord cname = response.getCNAME();
417
follow(cname.getTarget(), name);
418
} else if (response.isDNAME()) {
419
DNAMERecord dname = response.getDNAME();
422
follow(name.fromDNAME(dname), name);
423
} catch (NameTooLongException e) {
424
result = UNRECOVERABLE;
425
error = "Invalid DNAME target";
428
} else if (response.isDelegation()) {
429
// We shouldn't get a referral. Ignore it.
435
lookup(Name current) {
436
SetResponse sr = cache.lookupRecords(current, type, credibility);
438
System.err.println("lookup " + current + " " +
440
System.err.println(sr);
442
processResponse(current, sr);
443
if (done || doneCurrent)
446
Record question = Record.newRecord(current, type, dclass);
447
Message query = Message.newQuery(question);
448
Message response = null;
450
response = resolver.send(query);
452
catch (IOException e) {
453
// A network error occurred. Press on.
454
if (e instanceof InterruptedIOException)
460
int rcode = response.getHeader().getRcode();
461
if (rcode != Rcode.NOERROR && rcode != Rcode.NXDOMAIN) {
462
// The server we contacted is broken or otherwise unhelpful.
465
badresponse_error = Rcode.string(rcode);
469
if (!query.getQuestion().equals(response.getQuestion())) {
470
// The answer doesn't match the question. That's not good.
472
badresponse_error = "response does not match query";
476
sr = cache.addMessage(response);
478
sr = cache.lookupRecords(current, type, credibility);
480
System.err.println("queried " + current + " " +
482
System.err.println(sr);
484
processResponse(current, sr);
488
resolve(Name current, Name suffix) {
495
tname = Name.concatenate(current, suffix);
497
catch (NameTooLongException e) {
506
* Performs the lookup, using the specified Cache, Resolver, and search path.
507
* @return The answers, or null if none are found.
513
if (name.isAbsolute())
515
else if (searchPath == null)
516
resolve(name, Name.root);
518
if (name.labels() > 1)
519
resolve(name, Name.root);
523
for (int i = 0; i < searchPath.length; i++) {
524
resolve(name, searchPath[i]);
534
error = badresponse_error;
536
} else if (timedout) {
540
} else if (networkerror) {
542
error = "network error";
544
} else if (nxdomain) {
545
result = HOST_NOT_FOUND;
547
} else if (referral) {
548
result = UNRECOVERABLE;
551
} else if (nametoolong) {
552
result = UNRECOVERABLE;
553
error = "name too long";
562
if (done && result != -1)
564
StringBuffer sb = new StringBuffer("Lookup of " + name + " ");
565
if (dclass != DClass.IN)
566
sb.append(DClass.string(dclass) + " ");
567
sb.append(Type.string(type) + " isn't done");
568
throw new IllegalStateException(sb.toString());
572
* Returns the answers from the lookup.
573
* @return The answers, or null if none are found.
574
* @throws IllegalStateException The lookup has not completed.
583
* Returns all known aliases for this name. Whenever a CNAME/DNAME is
584
* followed, an alias is added to this array. The last element in this
585
* array will be the owner name for records in the answer, if there are any.
586
* @return The aliases.
587
* @throws IllegalStateException The lookup has not completed.
594
return (Name []) aliases.toArray(new Name[aliases.size()]);
598
* Returns the result code of the lookup.
599
* @return The result code, which can be SUCCESSFUL, UNRECOVERABLE, TRY_AGAIN,
600
* HOST_NOT_FOUND, or TYPE_NOT_FOUND.
601
* @throws IllegalStateException The lookup has not completed.
610
* Returns an error string describing the result code of this lookup.
611
* @return A string, which may either directly correspond the result code
612
* or be more specific.
613
* @throws IllegalStateException The lookup has not completed.
621
case SUCCESSFUL: return "successful";
622
case UNRECOVERABLE: return "unrecoverable error";
623
case TRY_AGAIN: return "try again";
624
case HOST_NOT_FOUND: return "host not found";
625
case TYPE_NOT_FOUND: return "type not found";
627
throw new IllegalStateException("unknown result");