~ubuntu-branches/ubuntu/lucid/dnsjava/lucid

« back to all changes in this revision

Viewing changes to org/xbill/DNS/Lookup.java

  • Committer: Bazaar Package Importer
  • Author(s): Thierry Carrez
  • Date: 2009-07-21 15:17:03 UTC
  • Revision ID: james.westby@ubuntu.com-20090721151703-6v0107p1s3h7gv1c
Tags: upstream-2.0.6
ImportĀ upstreamĀ versionĀ 2.0.6

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright (c) 2002-2004 Brian Wellington (bwelling@xbill.org)
 
2
 
 
3
package org.xbill.DNS;
 
4
 
 
5
import java.util.*;
 
6
import java.io.*;
 
7
import 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
 
 
27
public final class Lookup {
 
28
 
 
29
private static Resolver defaultResolver;
 
30
private static Name [] defaultSearchPath;
 
31
private static Map defaultCaches;
 
32
 
 
33
private Resolver resolver;
 
34
private Name [] searchPath;
 
35
private Cache cache;
 
36
private boolean temporary_cache;
 
37
private int credibility;
 
38
private Name name;
 
39
private int type;
 
40
private int dclass;
 
41
private boolean verbose;
 
42
private int iterations;
 
43
private boolean foundAlias;
 
44
private boolean done;
 
45
private boolean doneCurrent;
 
46
private List aliases;
 
47
private Record [] answers;
 
48
private int result;
 
49
private String error;
 
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;
 
57
 
 
58
private static final Name [] noAliases = new Name[0];
 
59
 
 
60
/** The lookup was successful. */
 
61
public static final int SUCCESSFUL = 0;
 
62
 
 
63
/**
 
64
 * The lookup failed due to a data or server error. Repeating the lookup
 
65
 * would not be helpful.
 
66
 */
 
67
public static final int UNRECOVERABLE = 1;
 
68
 
 
69
/**
 
70
 * The lookup failed due to a network error. Repeating the lookup may be
 
71
 * helpful.
 
72
 */
 
73
public static final int TRY_AGAIN = 2;
 
74
 
 
75
/** The host does not exist. */
 
76
public static final int HOST_NOT_FOUND = 3;
 
77
 
 
78
/** The host exists, but has no records associated with the queried type. */
 
79
public static final int TYPE_NOT_FOUND = 4;
 
80
 
 
81
public static synchronized void
 
82
refreshDefault() {
 
83
 
 
84
        try {
 
85
                defaultResolver = new ExtendedResolver();
 
86
        }
 
87
        catch (UnknownHostException e) {
 
88
                throw new RuntimeException("Failed to initialize resolver");
 
89
        }
 
90
        defaultSearchPath = ResolverConfig.getCurrentConfig().searchPath();
 
91
        defaultCaches = new HashMap();
 
92
}
 
93
 
 
94
static {
 
95
        refreshDefault();
 
96
}
 
97
 
 
98
/**
 
99
 * Gets the Resolver that will be used as the default by future Lookups.
 
100
 * @return The default resolver.
 
101
 */
 
102
public static synchronized Resolver
 
103
getDefaultResolver() {
 
104
        return defaultResolver;
 
105
}
 
106
 
 
107
/**
 
108
 * Sets the default Resolver to be used as the default by future Lookups.
 
109
 * @param resolver The default resolver.
 
110
 */
 
111
public static synchronized void
 
112
setDefaultResolver(Resolver resolver) {
 
113
        defaultResolver = resolver;
 
114
}
 
115
 
 
116
/**
 
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.
 
121
 */
 
122
public static synchronized Cache
 
123
getDefaultCache(int dclass) {
 
124
        DClass.check(dclass);
 
125
        Cache c = (Cache) defaultCaches.get(Mnemonic.toInteger(dclass));
 
126
        if (c == null) {
 
127
                c = new Cache(dclass);
 
128
                defaultCaches.put(Mnemonic.toInteger(dclass), c);
 
129
        }
 
130
        return c;
 
131
}
 
132
 
 
133
/**
 
134
 * Sets the Cache to be used as the default for the specified class by future
 
135
 * Lookups.
 
136
 * @param cache The default cache for the specified class.
 
137
 * @param dclass The class whose cache is being set.
 
138
 */
 
139
public static synchronized void
 
140
setDefaultCache(Cache cache, int dclass) {
 
141
        DClass.check(dclass);
 
142
        defaultCaches.put(Mnemonic.toInteger(dclass), cache);
 
143
}
 
144
 
 
145
/**
 
146
 * Gets the search path that will be used as the default by future Lookups.
 
147
 * @return The default search path.
 
148
 */
 
149
public static synchronized Name []
 
150
getDefaultSearchPath() {
 
151
        return defaultSearchPath;
 
152
}
 
153
 
 
154
/**
 
155
 * Sets the search path to be used as the default by future Lookups.
 
156
 * @param domains The default search path.
 
157
 */
 
158
public static synchronized void
 
159
setDefaultSearchPath(Name [] domains) {
 
160
        defaultSearchPath = domains;
 
161
}
 
162
 
 
163
/**
 
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.
 
167
 */
 
168
public static synchronized void
 
169
setDefaultSearchPath(String [] domains) throws TextParseException {
 
170
        if (domains == null) {
 
171
                defaultSearchPath = null;
 
172
                return;
 
173
        }
 
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;
 
178
}
 
179
 
 
180
private final void
 
181
reset() {
 
182
        iterations = 0;
 
183
        foundAlias = false;
 
184
        done = false;
 
185
        doneCurrent = false;
 
186
        aliases = null;
 
187
        answers = null;
 
188
        result = -1;
 
189
        error = null;
 
190
        nxdomain = false;
 
191
        badresponse = false;
 
192
        badresponse_error = null;
 
193
        networkerror = false;
 
194
        timedout = false;
 
195
        nametoolong = false;
 
196
        referral = false;
 
197
        if (temporary_cache)
 
198
                cache.clearCache();
 
199
}
 
200
 
 
201
/**
 
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.
 
209
 * @see Cache
 
210
 * @see Resolver
 
211
 * @see Credibility
 
212
 * @see Name
 
213
 * @see Type
 
214
 * @see DClass
 
215
 */
 
216
public
 
217
Lookup(Name name, int type, int dclass) {
 
218
        Type.check(type);
 
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");
 
223
        this.name = name;
 
224
        this.type = type;
 
225
        this.dclass = dclass;
 
226
        synchronized (Lookup.class) {
 
227
                this.resolver = getDefaultResolver();
 
228
                this.searchPath = getDefaultSearchPath();
 
229
                this.cache = getDefaultCache(dclass);
 
230
        }
 
231
        this.credibility = Credibility.NORMAL;
 
232
        this.verbose = Options.check("verbose");
 
233
        this.result = -1;
 
234
}
 
235
 
 
236
/**
 
237
 * Create a Lookup object that will find records of the given name and type
 
238
 * in the IN class.
 
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)
 
243
 */
 
244
public
 
245
Lookup(Name name, int type) {
 
246
        this(name, type, DClass.IN);
 
247
}
 
248
 
 
249
/**
 
250
 * Create a Lookup object that will find records of type A at the given name
 
251
 * in the IN class.
 
252
 * @param name The name of the desired records
 
253
 * @see #Lookup(Name,int,int)
 
254
 */
 
255
public
 
256
Lookup(Name name) {
 
257
        this(name, Type.A, DClass.IN);
 
258
}
 
259
 
 
260
/**
 
261
 * Create a Lookup object that will find records of the given name, type,
 
262
 * and class.
 
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)
 
269
 */
 
270
public
 
271
Lookup(String name, int type, int dclass) throws TextParseException {
 
272
        this(Name.fromString(name), type, dclass);
 
273
}
 
274
 
 
275
/**
 
276
 * Create a Lookup object that will find records of the given name and type
 
277
 * in the IN class.
 
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)
 
283
 */
 
284
public
 
285
Lookup(String name, int type) throws TextParseException {
 
286
        this(Name.fromString(name), type, DClass.IN);
 
287
}
 
288
 
 
289
/**
 
290
 * Create a Lookup object that will find records of type A at the given name
 
291
 * in the IN class.
 
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)
 
295
 */
 
296
public
 
297
Lookup(String name) throws TextParseException {
 
298
        this(Name.fromString(name), Type.A, DClass.IN);
 
299
}
 
300
 
 
301
/**
 
302
 * Sets the resolver to use when performing this lookup.  This overrides the
 
303
 * default value.
 
304
 * @param resolver The resolver to use.
 
305
 */
 
306
public void
 
307
setResolver(Resolver resolver) {
 
308
        this.resolver = resolver;
 
309
}
 
310
 
 
311
/**
 
312
 * Sets the search path to use when performing this lookup.  This overrides the
 
313
 * default value.
 
314
 * @param domains An array of names containing the search path.
 
315
 */
 
316
public void
 
317
setSearchPath(Name [] domains) {
 
318
        this.searchPath = domains;
 
319
}
 
320
 
 
321
/**
 
322
 * Sets the search path to use when performing this lookup. This overrides the
 
323
 * default value.
 
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.
 
326
 */
 
327
public void
 
328
setSearchPath(String [] domains) throws TextParseException {
 
329
        if (domains == null) {
 
330
                this.searchPath = null;
 
331
                return;
 
332
        }
 
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;
 
337
}
 
338
 
 
339
/**
 
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.
 
344
 */
 
345
public void
 
346
setCache(Cache cache) {
 
347
        if (cache == null) {
 
348
                this.cache = new Cache(dclass);
 
349
                this.temporary_cache = true;
 
350
        } else {
 
351
                this.cache = cache;
 
352
                this.temporary_cache = false;
 
353
        }
 
354
}
 
355
 
 
356
/**
 
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.
 
360
 */
 
361
public void
 
362
setCredibility(int credibility) {
 
363
        this.credibility = credibility;
 
364
}
 
365
 
 
366
private void
 
367
follow(Name name, Name oldname) {
 
368
        foundAlias = true;
 
369
        badresponse = false;
 
370
        networkerror = false;
 
371
        timedout = false;
 
372
        nxdomain = false;
 
373
        referral = false;
 
374
        iterations++;
 
375
        if (iterations >= 6 || name.equals(oldname)) {
 
376
                result = UNRECOVERABLE;
 
377
                error = "CNAME loop";
 
378
                done = true;
 
379
                return;
 
380
        }
 
381
        if (aliases == null)
 
382
                aliases = new ArrayList();
 
383
        aliases.add(oldname);
 
384
        lookup(name);
 
385
}
 
386
 
 
387
private void
 
388
processResponse(Name name, SetResponse response) {
 
389
        if (response.isSuccessful()) {
 
390
                RRset [] rrsets = response.answers();
 
391
                List l = new ArrayList();
 
392
                Iterator it;
 
393
                int i;
 
394
 
 
395
                for (i = 0; i < rrsets.length; i++) {
 
396
                        it = rrsets[i].rrs();
 
397
                        while (it.hasNext())
 
398
                                l.add(it.next());
 
399
                }
 
400
 
 
401
                result = SUCCESSFUL;
 
402
                answers = (Record []) l.toArray(new Record[l.size()]);
 
403
                done = true;
 
404
        } else if (response.isNXDOMAIN()) {
 
405
                nxdomain = true;
 
406
                doneCurrent = true;
 
407
                if (iterations > 0) {
 
408
                        result = HOST_NOT_FOUND;
 
409
                        done = true;
 
410
                }
 
411
        } else if (response.isNXRRSET()) {
 
412
                result = TYPE_NOT_FOUND;
 
413
                answers = null;
 
414
                done = true;
 
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();
 
420
                Name newname = null;
 
421
                try {
 
422
                        follow(name.fromDNAME(dname), name);
 
423
                } catch (NameTooLongException e) {
 
424
                        result = UNRECOVERABLE;
 
425
                        error = "Invalid DNAME target";
 
426
                        done = true;
 
427
                }
 
428
        } else if (response.isDelegation()) {
 
429
                // We shouldn't get a referral.  Ignore it.
 
430
                referral = true;
 
431
        }
 
432
}
 
433
 
 
434
private void
 
435
lookup(Name current) {
 
436
        SetResponse sr = cache.lookupRecords(current, type, credibility);
 
437
        if (verbose) {
 
438
                System.err.println("lookup " + current + " " +
 
439
                                   Type.string(type));
 
440
                System.err.println(sr);
 
441
        }
 
442
        processResponse(current, sr);
 
443
        if (done || doneCurrent)
 
444
                return;
 
445
 
 
446
        Record question = Record.newRecord(current, type, dclass);
 
447
        Message query = Message.newQuery(question);
 
448
        Message response = null;
 
449
        try {
 
450
                response = resolver.send(query);
 
451
        }
 
452
        catch (IOException e) {
 
453
                // A network error occurred.  Press on.
 
454
                if (e instanceof InterruptedIOException)
 
455
                        timedout = true;
 
456
                else
 
457
                        networkerror = true;
 
458
                return;
 
459
        }
 
460
        int rcode = response.getHeader().getRcode();
 
461
        if (rcode != Rcode.NOERROR && rcode != Rcode.NXDOMAIN) {
 
462
                // The server we contacted is broken or otherwise unhelpful.
 
463
                // Press on.
 
464
                badresponse = true;
 
465
                badresponse_error = Rcode.string(rcode);
 
466
                return;
 
467
        }
 
468
 
 
469
        if (!query.getQuestion().equals(response.getQuestion())) {
 
470
                // The answer doesn't match the question.  That's not good.
 
471
                badresponse = true;
 
472
                badresponse_error = "response does not match query";
 
473
                return;
 
474
        }
 
475
 
 
476
        sr = cache.addMessage(response);
 
477
        if (sr == null)
 
478
                sr = cache.lookupRecords(current, type, credibility);
 
479
        if (verbose) {
 
480
                System.err.println("queried " + current + " " +
 
481
                                   Type.string(type));
 
482
                System.err.println(sr);
 
483
        }
 
484
        processResponse(current, sr);
 
485
}
 
486
 
 
487
private void
 
488
resolve(Name current, Name suffix) {
 
489
        doneCurrent = false;
 
490
        Name tname = null;
 
491
        if (suffix == null)
 
492
                tname = current;
 
493
        else {
 
494
                try {
 
495
                        tname = Name.concatenate(current, suffix);
 
496
                }
 
497
                catch (NameTooLongException e) {
 
498
                        nametoolong = true;
 
499
                        return;
 
500
                }
 
501
        }
 
502
        lookup(tname);
 
503
}
 
504
 
 
505
/**
 
506
 * Performs the lookup, using the specified Cache, Resolver, and search path.
 
507
 * @return The answers, or null if none are found.
 
508
 */
 
509
public Record []
 
510
run() {
 
511
        if (done)
 
512
                reset();
 
513
        if (name.isAbsolute())
 
514
                resolve(name, null);
 
515
        else if (searchPath == null)
 
516
                resolve(name, Name.root);
 
517
        else {
 
518
                if (name.labels() > 1)
 
519
                        resolve(name, Name.root);
 
520
                if (done)
 
521
                        return answers;
 
522
 
 
523
                for (int i = 0; i < searchPath.length; i++) {
 
524
                        resolve(name, searchPath[i]);
 
525
                        if (done)
 
526
                                return answers;
 
527
                        else if (foundAlias)
 
528
                                break;
 
529
                }
 
530
        }
 
531
        if (!done) {
 
532
                if (badresponse) {
 
533
                        result = TRY_AGAIN;
 
534
                        error = badresponse_error;
 
535
                        done = true;
 
536
                } else if (timedout) {
 
537
                        result = TRY_AGAIN;
 
538
                        error = "timed out";
 
539
                        done = true;
 
540
                } else if (networkerror) {
 
541
                        result = TRY_AGAIN;
 
542
                        error = "network error";
 
543
                        done = true;
 
544
                } else if (nxdomain) {
 
545
                        result = HOST_NOT_FOUND;
 
546
                        done = true;
 
547
                } else if (referral) {
 
548
                        result = UNRECOVERABLE;
 
549
                        error = "referral";
 
550
                        done = true;
 
551
                } else if (nametoolong) {
 
552
                        result = UNRECOVERABLE;
 
553
                        error = "name too long";
 
554
                        done = true;
 
555
                }
 
556
        }
 
557
        return answers;
 
558
}
 
559
 
 
560
private void
 
561
checkDone() {
 
562
        if (done && result != -1)
 
563
                return;
 
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());
 
569
}
 
570
 
 
571
/**
 
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.
 
575
 */
 
576
public Record []
 
577
getAnswers() {
 
578
        checkDone();
 
579
        return answers;
 
580
}
 
581
 
 
582
/**
 
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.
 
588
 */
 
589
public Name []
 
590
getAliases() {
 
591
        checkDone();
 
592
        if (aliases == null)
 
593
                return noAliases;
 
594
        return (Name []) aliases.toArray(new Name[aliases.size()]);
 
595
}
 
596
 
 
597
/**
 
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.
 
602
 */
 
603
public int
 
604
getResult() {
 
605
        checkDone();
 
606
        return result;
 
607
}
 
608
 
 
609
/**
 
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.
 
614
 */
 
615
public String
 
616
getErrorString() {
 
617
        checkDone();
 
618
        if (error != null)
 
619
                return error;
 
620
        switch (result) {
 
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";
 
626
        }
 
627
        throw new IllegalStateException("unknown result");
 
628
}
 
629
 
 
630
}