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