All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xbill.DNS.Lookup Maven / Gradle / Ivy

There is a newer version: 3.6.2_1
Show newest version
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2002-2004 Brian Wellington (bwelling@xbill.org)

package org.xbill.DNS;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.xbill.DNS.hosts.HostsFileParser;

/**
 * The Lookup object issues queries to the local hosts database ({@code /etc/hosts}) and to
 * recursive DNS servers. The input consists of a name, an optional type, and an optional class.
 * Caching is enabled by default and used when possible to reduce the number of DNS requests. A
 * {@link Resolver}, which defaults to an {@link ExtendedResolver} initialized with the resolvers
 * located by the {@link ResolverConfig} class, performs the queries. A search path of domain
 * suffixes is used to resolve relative names, and is also determined by the {@link ResolverConfig}
 * class.
 *
 * 

A Lookup object may be reused, but should not be used by multiple threads. * *

Lookup is considered legacy (but not yet deprecated). Use {@link * org.xbill.DNS.lookup.LookupSession} instead, which is thread safe and fully async. * * @see org.xbill.DNS.lookup.LookupSession * @see Cache * @see Resolver * @see ResolverConfig * @see HostsFileParser * @author Brian Wellington */ @Slf4j public final class Lookup { private static Resolver defaultResolver; private static List defaultSearchPath; private static Map defaultCaches; private static int defaultNdots; private static HostsFileParser defaultHostsFileParser; private Resolver resolver; private List searchPath; private int ndots; private Cache cache; private boolean temporary_cache; private int credibility; private final Name name; private final int type; private final int dclass; private int iterations; private boolean foundAlias; private boolean done; private boolean doneCurrent; private List aliases; private Record[] answers; private int result; private String error; private boolean nxdomain; private boolean badresponse; private String badresponse_error; private boolean networkerror; private boolean timedout; private boolean nametoolong; private boolean referral; private boolean cycleResults = true; private final int maxIterations; /** * Gets or sets the local hosts database parser to use for lookup before using a {@link Resolver}. * * @since 3.4 */ @Getter @Setter private HostsFileParser hostsFileParser; private static final Name[] noAliases = new Name[0]; /** The lookup was successful. */ public static final int SUCCESSFUL = 0; /** The lookup failed due to a data or server error. Repeating the lookup would not be helpful. */ public static final int UNRECOVERABLE = 1; /** The lookup failed due to a network error. Repeating the lookup may be helpful. */ public static final int TRY_AGAIN = 2; /** The host does not exist. */ public static final int HOST_NOT_FOUND = 3; /** The host exists, but has no records associated with the queried type. */ public static final int TYPE_NOT_FOUND = 4; public static synchronized void refreshDefault() { defaultResolver = new ExtendedResolver(); defaultSearchPath = ResolverConfig.getCurrentConfig().searchPath(); defaultCaches = new HashMap<>(); defaultNdots = ResolverConfig.getCurrentConfig().ndots(); defaultHostsFileParser = new HostsFileParser(); } static { refreshDefault(); } /** * Gets the Resolver that will be used as the default by future Lookups. * * @return The default resolver. */ public static synchronized Resolver getDefaultResolver() { return defaultResolver; } /** * Sets the default Resolver to be used as the default by future Lookups. * * @param resolver The default resolver. */ public static synchronized void setDefaultResolver(Resolver resolver) { defaultResolver = resolver; } /** * Gets the Cache that will be used as the default for the specified class by future Lookups. * * @param dclass The class whose cache is being retrieved. * @return The default cache for the specified class. */ public static synchronized Cache getDefaultCache(int dclass) { DClass.check(dclass); Cache c = defaultCaches.get(dclass); if (c == null) { c = new Cache(dclass); defaultCaches.put(dclass, c); } return c; } /** * Sets the Cache to be used as the default for the specified class by future Lookups. * * @param cache The default cache for the specified class. * @param dclass The class whose cache is being set. */ public static synchronized void setDefaultCache(Cache cache, int dclass) { DClass.check(dclass); defaultCaches.put(dclass, cache); } /** * Gets the search path that will be used as the default by future Lookups. * * @return The default search path. */ public static synchronized List getDefaultSearchPath() { return defaultSearchPath; } /** * Sets the search path to be used as the default by future Lookups. * * @param domains The default search path. * @throws IllegalArgumentException if a domain in the search path is not absolute and cannot be * made absolute. */ public static synchronized void setDefaultSearchPath(List domains) { defaultSearchPath = convertSearchPathDomainList(domains); } /** * Sets the search path to be used as the default by future Lookups. * * @param domains The default search path. * @throws IllegalArgumentException if a domain in the search path is not absolute and cannot be * made absolute. */ public static synchronized void setDefaultSearchPath(Name... domains) { setDefaultSearchPath(Arrays.asList(domains)); } /** * Sets the search path that will be used as the default by future Lookups. * * @param domains The default search path. * @throws TextParseException A name in the array is not a valid DNS name. */ public static synchronized void setDefaultSearchPath(String... domains) throws TextParseException { if (domains == null) { defaultSearchPath = null; return; } List newdomains = new ArrayList<>(domains.length); for (String domain : domains) { newdomains.add(Name.fromString(domain, Name.root)); } defaultSearchPath = newdomains; } /** * Gets the default {@link HostsFileParser} to use for new Lookup instances. * * @since 3.4 */ public static synchronized HostsFileParser getDefaultHostsFileParser() { return defaultHostsFileParser; } /** * Sets the default {@link HostsFileParser} to use for new Lookup instances. * * @since 3.4 */ public static synchronized void setDefaultHostsFileParser(HostsFileParser hostsFileParser) { defaultHostsFileParser = hostsFileParser; } private static List convertSearchPathDomainList(List domains) { try { return domains.stream() .map( n -> { try { return Name.concatenate(n, Name.root); } catch (NameTooLongException e) { throw new RuntimeException(e); } }) .collect(Collectors.toList()); } catch (RuntimeException e) { if (e.getCause() instanceof NameTooLongException) { throw new IllegalArgumentException(e.getCause()); } else { throw e; } } } /** * Sets a custom logger that will be used to log the sent and received packets. * * @param logger The logger */ public static synchronized void setPacketLogger(PacketLogger logger) { NioClient.setPacketLogger(logger); } private void reset() { iterations = 0; foundAlias = false; done = false; doneCurrent = false; aliases = null; answers = null; result = -1; error = null; nxdomain = false; badresponse = false; badresponse_error = null; networkerror = false; timedout = false; nametoolong = false; referral = false; if (temporary_cache) { cache.clearCache(); } } /** * Create a Lookup object that will find records of the given name, type, and class. The lookup * will use the default cache, resolver, and search path, and look for records that are reasonably * credible. * * @param name The name of the desired records * @param type The type of the desired records * @param dclass The class of the desired records * @throws IllegalArgumentException The type is a meta type other than ANY. * @see Cache * @see Resolver * @see Credibility * @see Name * @see Type * @see DClass */ public Lookup(Name name, int type, int dclass) { Type.check(type); DClass.check(dclass); if (!Type.isRR(type) && type != Type.ANY) { throw new IllegalArgumentException("Cannot query for meta-types other than ANY"); } this.name = name; this.type = type; this.dclass = dclass; synchronized (Lookup.class) { this.resolver = getDefaultResolver(); this.searchPath = getDefaultSearchPath(); this.cache = getDefaultCache(dclass); } this.ndots = defaultNdots; this.credibility = Credibility.NORMAL; this.result = -1; this.maxIterations = Integer.parseInt(System.getProperty("dnsjava.lookup.max_iterations", "16")); if (Boolean.parseBoolean(System.getProperty("dnsjava.lookup.use_hosts_file", "true"))) { this.hostsFileParser = getDefaultHostsFileParser(); } } /** * Create a Lookup object that will find records of the given name and type in the IN class. * * @param name The name of the desired records * @param type The type of the desired records * @throws IllegalArgumentException The type is a meta type other than ANY. * @see #Lookup(Name,int,int) */ public Lookup(Name name, int type) { this(name, type, DClass.IN); } /** * Create a Lookup object that will find records of type A at the given name in the IN class. * * @param name The name of the desired records * @see #Lookup(Name,int,int) */ public Lookup(Name name) { this(name, Type.A, DClass.IN); } /** * Create a Lookup object that will find records of the given name, type, and class. * * @param name The name of the desired records * @param type The type of the desired records * @param dclass The class of the desired records * @throws TextParseException The name is not a valid DNS name * @throws IllegalArgumentException The type is a meta type other than ANY. * @see #Lookup(Name,int,int) */ public Lookup(String name, int type, int dclass) throws TextParseException { this(Name.fromString(name), type, dclass); } /** * Create a Lookup object that will find records of the given name and type in the IN class. * * @param name The name of the desired records * @param type The type of the desired records * @throws TextParseException The name is not a valid DNS name * @throws IllegalArgumentException The type is a meta type other than ANY. * @see #Lookup(Name,int,int) */ public Lookup(String name, int type) throws TextParseException { this(Name.fromString(name), type, DClass.IN); } /** * Create a Lookup object that will find records of type A at the given name in the IN class. * * @param name The name of the desired records * @throws TextParseException The name is not a valid DNS name * @see #Lookup(Name,int,int) */ public Lookup(String name) throws TextParseException { this(Name.fromString(name), Type.A, DClass.IN); } /** * Sets the resolver to use when performing this lookup. This overrides the default value. * * @param resolver The resolver to use. */ public void setResolver(Resolver resolver) { this.resolver = resolver; } /** * Sets the search path to use when performing this lookup. This overrides the default value. * * @param domains An array of names containing the search path. * @throws IllegalArgumentException if a domain in the search path is not absolute and cannot be * made absolute. */ public void setSearchPath(List domains) { this.searchPath = convertSearchPathDomainList(domains); } /** * Sets the search path to use when performing this lookup. This overrides the default value. * * @param domains An array of names containing the search path. * @throws IllegalArgumentException if a domain in the search path is not absolute and cannot be * made absolute. */ public void setSearchPath(Name... domains) { setSearchPath(Arrays.asList(domains)); } /** * Sets the search path to use when performing this lookup. This overrides the default value. * * @param domains An array of names containing the search path. * @throws TextParseException A name in the array is not a valid DNS name. */ public void setSearchPath(String... domains) throws TextParseException { if (domains == null) { this.searchPath = null; return; } List newdomains = new ArrayList<>(domains.length); for (String domain : domains) { newdomains.add(Name.fromString(domain, Name.root)); } this.searchPath = newdomains; } /** * Sets the cache to use when performing this lookup. This overrides the default value. If the * results of this lookup should not be permanently cached, null can be provided here. * * @param cache The cache to use. */ public void setCache(Cache cache) { if (cache == null) { this.cache = new Cache(dclass); this.temporary_cache = true; } else { this.cache = cache; this.temporary_cache = false; } } /** * Sets the default ndots to use when performing a lookup, overriding the default value. * Specifically, this refers to the number of "dots" which, if present in a name, indicate that a * lookup for the absolute name should be attempted before appending any search path elements. * * @param ndots The ndots value to use, which must be greater than or equal to 0. */ public static void setDefaultNdots(int ndots) { if (ndots < 0) { throw new IllegalArgumentException("Illegal ndots value: " + ndots); } defaultNdots = ndots; } /** * Sets ndots to use when performing this lookup, overriding the default value. Specifically, this * refers to the number of "dots" which, if present in a name, indicate that a lookup for the * absolute name should be attempted before appending any search path elements. * * @param ndots The ndots value to use, which must be greater than or equal to 0. */ public void setNdots(int ndots) { if (ndots < 0) { throw new IllegalArgumentException("Illegal ndots value: " + ndots); } this.ndots = ndots; } /** * Sets the minimum credibility level that will be accepted when performing the lookup. This * defaults to Credibility.NORMAL. * * @param credibility The minimum credibility level. */ public void setCredibility(int credibility) { this.credibility = credibility; } /** * Controls the behavior if results being returned from the cache should be cycled in a * round-robin style (true) or if the raw lookup results should be returned (false). * * @param cycleResults The desired behavior of the order of the results */ public void setCycleResults(boolean cycleResults) { this.cycleResults = cycleResults; } private void follow(Name name, Name oldname) { foundAlias = true; badresponse = false; networkerror = false; timedout = false; nxdomain = false; referral = false; iterations++; if (iterations >= maxIterations || name.equals(oldname)) { result = UNRECOVERABLE; error = "CNAME loop"; done = true; return; } if (aliases == null) { aliases = new ArrayList<>(); } aliases.add(oldname); lookup(name); } private void processResponse(Name name, SetResponse response) { if (response.isSuccessful()) { List rrsets = response.answers(); List l = new ArrayList<>(); for (RRset set : rrsets) { l.addAll(set.rrs(cycleResults)); } result = SUCCESSFUL; answers = l.toArray(new Record[0]); done = true; } else if (response.isNXDOMAIN()) { nxdomain = true; doneCurrent = true; if (iterations > 0) { result = HOST_NOT_FOUND; done = true; } } else if (response.isNXRRSET()) { result = TYPE_NOT_FOUND; answers = null; done = true; } else if (response.isCNAME()) { CNAMERecord cname = response.getCNAME(); follow(cname.getTarget(), name); } else if (response.isDNAME()) { DNAMERecord dname = response.getDNAME(); try { follow(name.fromDNAME(dname), name); } catch (NameTooLongException e) { result = UNRECOVERABLE; error = "Invalid DNAME target"; done = true; } } else if (response.isDelegation()) { // We shouldn't get a referral. Ignore it. referral = true; } } private void lookup(Name current) { if (lookupFromHostsFile(current)) { return; } SetResponse sr = cache.lookupRecords(current, type, credibility); log.debug("Lookup for {}/{}, cache answer: {}", current, Type.string(type), sr); processResponse(current, sr); if (done || doneCurrent) { return; } Record question = Record.newRecord(current, type, dclass); Message query = Message.newQuery(question); Message response; try { response = resolver.send(query); } catch (IOException e) { log.debug( "Lookup for {}/{}, id={} failed using resolver {}", current, Type.string(query.getQuestion().getType()), query.getHeader().getID(), resolver, e); // A network error occurred. Press on. if (e instanceof InterruptedIOException) { timedout = true; } else { networkerror = true; } return; } int rcode = response.getHeader().getRcode(); if (rcode != Rcode.NOERROR && rcode != Rcode.NXDOMAIN) { // The server we contacted is broken or otherwise unhelpful. // Press on. badresponse = true; badresponse_error = Rcode.string(rcode); return; } if (!query.getQuestion().equals(response.getQuestion())) { // The answer doesn't match the question. That's not good. badresponse = true; badresponse_error = "response does not match query"; return; } sr = cache.addMessage(response); if (sr == null) { sr = cache.lookupRecords(current, type, credibility); } log.debug( "Queried {}/{}, id={}: {}", current, Type.string(type), response.getHeader().getID(), sr); processResponse(current, sr); } private boolean lookupFromHostsFile(Name current) { if (hostsFileParser != null && (type == Type.A || type == Type.AAAA)) { try { Optional localLookup = hostsFileParser.getAddressForHost(current, type); if (localLookup.isPresent()) { result = SUCCESSFUL; done = true; if (type == Type.A) { answers = new ARecord[] {new ARecord(current, dclass, 0L, localLookup.get())}; } else { answers = new AAAARecord[] {new AAAARecord(current, dclass, 0L, localLookup.get())}; } return true; } } catch (IOException e) { log.debug("Local hosts database parsing failed, ignoring and using resolver", e); } } return false; } private void resolve(Name current, Name suffix) { doneCurrent = false; Name tname; if (suffix == null) { tname = current; } else { try { tname = Name.concatenate(current, suffix); } catch (NameTooLongException e) { nametoolong = true; return; } } lookup(tname); } /** * Performs the lookup, using the specified Cache, Resolver, and search path. * * @return The answers, or null if none are found. */ public Record[] run() { if (done) { reset(); } if (name.isAbsolute()) { resolve(name, null); } else if (searchPath == null) { resolve(name, Name.root); } else { if (name.labels() > ndots) { resolve(name, Name.root); } if (done) { return answers; } for (Name value : searchPath) { resolve(name, value); if (done) { return answers; } else if (foundAlias) { break; } } resolve(name, Name.root); } if (!done) { if (badresponse) { result = TRY_AGAIN; error = badresponse_error; done = true; } else if (timedout) { result = TRY_AGAIN; error = "timed out"; done = true; } else if (networkerror) { result = TRY_AGAIN; error = "network error"; done = true; } else if (nxdomain) { result = HOST_NOT_FOUND; done = true; } else if (referral) { result = UNRECOVERABLE; error = "referral"; done = true; } else if (nametoolong) { result = UNRECOVERABLE; error = "name too long"; done = true; } } return answers; } private void checkDone() { if (done && result != -1) { return; } StringBuilder sb = new StringBuilder("Lookup of " + name + " "); if (dclass != DClass.IN) { sb.append(DClass.string(dclass)).append(" "); } sb.append(Type.string(type)).append(" isn't done"); throw new IllegalStateException(sb.toString()); } /** * Returns the answers from the lookup. * * @return The answers, or null if none are found. * @throws IllegalStateException The lookup has not completed. */ public Record[] getAnswers() { checkDone(); return answers; } /** * Returns all known aliases for this name. Whenever a CNAME/DNAME is followed, an alias is added * to this array. The last element in this array will be the owner name for records in the answer, * if there are any. * * @return The aliases. * @throws IllegalStateException The lookup has not completed. */ public Name[] getAliases() { checkDone(); if (aliases == null) { return noAliases; } return aliases.toArray(new Name[0]); } /** * Returns the result code of the lookup. * * @return The result code, which can be SUCCESSFUL, UNRECOVERABLE, TRY_AGAIN, HOST_NOT_FOUND, or * TYPE_NOT_FOUND. * @throws IllegalStateException The lookup has not completed. */ public int getResult() { checkDone(); return result; } /** * Returns an error string describing the result code of this lookup. * * @return A string, which may either directly correspond the result code or be more specific. * @throws IllegalStateException The lookup has not completed. */ public String getErrorString() { checkDone(); if (error != null) { return error; } switch (result) { case SUCCESSFUL: return "successful"; case UNRECOVERABLE: return "unrecoverable error"; case TRY_AGAIN: return "try again"; case HOST_NOT_FOUND: return "host not found"; case TYPE_NOT_FOUND: return "type not found"; } throw new IllegalStateException("unknown result"); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy