diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..49ca4650a --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,38 @@ +name: dnsjava CI + +on: [push] + +jobs: + test: + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ ubuntu-16.04, ubuntu-latest, windows-latest ] + java: [ '1.8', '11' ] + arch: [ 'x86', 'x64' ] + exclude: + - os: ubuntu-16.04 + arch: x86 + - os: ubuntu-latest + arch: x86 + + name: Java ${{ matrix.java }}/${{ matrix.arch }}/${{ matrix.os }} + + steps: + - uses: actions/checkout@v1 + + - name: Cache Maven dependencies + uses: actions/cache@v1 + with: + path: ~/.m2 + key: m2 + + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@v1 + with: + java-version: ${{ matrix.java }} + architecture: ${{ matrix.arch }} + + - name: Build with Maven + run: mvn test jacoco:report -B -"Dgpg.skip" diff --git a/pom.xml b/pom.xml index dc0ddec1d..b474963d3 100644 --- a/pom.xml +++ b/pom.xml @@ -93,7 +93,11 @@ org.xbill.DNS.* - !org.xbill.DNS*,!sun.*,android.os;resolution:=optional,* + !org.xbill.DNS*, + !sun.*, + android.*;resolution:=optional, + javax.naming.*;resolution:=optional, + com.sun.jna.*;resolution:=optional,* JavaSE-1.8 @@ -201,7 +205,7 @@ org.slf4j slf4j-api - 1.7.28 + 1.7.29 org.projectlombok @@ -209,6 +213,18 @@ 1.18.8 provided + + net.java.dev.jna + jna + 5.5.0 + true + + + net.java.dev.jna + jna-platform + 5.5.0 + true + org.bouncycastle bcprov-jdk15on @@ -233,6 +249,12 @@ 3.1.0 test + + org.slf4j + slf4j-simple + 1.7.29 + test + diff --git a/src/main/java/org/xbill/DNS/Cache.java b/src/main/java/org/xbill/DNS/Cache.java index 706059dfa..1f833abab 100644 --- a/src/main/java/org/xbill/DNS/Cache.java +++ b/src/main/java/org/xbill/DNS/Cache.java @@ -721,8 +721,9 @@ public SetResponse addMessage(Message in) { cred = getCred(Section.ADDITIONAL, isAuth); addRRset(rRset, cred); } - log.debug("addMessage: {}", response); - return (response); + + log.debug("caching {} for {}", response, in.getQuestion().getName()); + return response; } /** diff --git a/src/main/java/org/xbill/DNS/ExtendedResolver.java b/src/main/java/org/xbill/DNS/ExtendedResolver.java index a3cf8739e..99e851e0f 100644 --- a/src/main/java/org/xbill/DNS/ExtendedResolver.java +++ b/src/main/java/org/xbill/DNS/ExtendedResolver.java @@ -4,9 +4,11 @@ import java.io.IOException; import java.io.InterruptedIOException; +import java.net.InetSocketAddress; import java.net.SocketException; import java.net.UnknownHostException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import lombok.extern.slf4j.Slf4j; @@ -264,19 +266,14 @@ private void init() { * * @see SimpleResolver * @see ResolverConfig - * @exception UnknownHostException Failure occured initializing SimpleResolvers */ - public ExtendedResolver() throws UnknownHostException { + public ExtendedResolver() { init(); - String[] servers = ResolverConfig.getCurrentConfig().servers(); - if (servers != null) { - for (String server : servers) { - Resolver r = new SimpleResolver(server); - r.setTimeout(quantum); - resolvers.add(r); - } - } else { - resolvers.add(new SimpleResolver()); + List servers = ResolverConfig.getCurrentConfig().servers(); + for (InetSocketAddress server : servers) { + Resolver r = new SimpleResolver(server); + r.setTimeout(quantum); + resolvers.add(r); } } @@ -286,7 +283,7 @@ public ExtendedResolver() throws UnknownHostException { * @param servers An array of server names for which SimpleResolver contexts should be * initialized. * @see SimpleResolver - * @exception UnknownHostException Failure occured initializing SimpleResolvers + * @exception UnknownHostException Failure occurred initializing SimpleResolvers */ public ExtendedResolver(String[] servers) throws UnknownHostException { init(); @@ -305,9 +302,7 @@ public ExtendedResolver(String[] servers) throws UnknownHostException { */ public ExtendedResolver(Resolver[] res) { init(); - for (Resolver re : res) { - resolvers.add(re); - } + resolvers.addAll(Arrays.asList(res)); } @Override @@ -433,4 +428,9 @@ public void setLoadBalance(boolean flag) { public void setRetries(int retries) { this.retries = retries; } + + @Override + public String toString() { + return "ExtendedResolver of " + resolvers; + } } diff --git a/src/main/java/org/xbill/DNS/Lookup.java b/src/main/java/org/xbill/DNS/Lookup.java index defd64c9b..e1bf3444e 100644 --- a/src/main/java/org/xbill/DNS/Lookup.java +++ b/src/main/java/org/xbill/DNS/Lookup.java @@ -4,7 +4,6 @@ import java.io.IOException; import java.io.InterruptedIOException; -import java.net.UnknownHostException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -30,12 +29,12 @@ public final class Lookup { private static Resolver defaultResolver; - private static Name[] defaultSearchPath; + private static List defaultSearchPath; private static Map defaultCaches; private static int defaultNdots; private Resolver resolver; - private Name[] searchPath; + private List searchPath; private Cache cache; private boolean temporary_cache; private int credibility; @@ -76,12 +75,7 @@ public final class Lookup { public static final int TYPE_NOT_FOUND = 4; public static synchronized void refreshDefault() { - - try { - defaultResolver = new ExtendedResolver(); - } catch (UnknownHostException e) { - throw new RuntimeException("Failed to initialize resolver"); - } + defaultResolver = new ExtendedResolver(); defaultSearchPath = ResolverConfig.getCurrentConfig().searchPath(); defaultCaches = new HashMap<>(); defaultNdots = ResolverConfig.getCurrentConfig().ndots(); @@ -141,7 +135,7 @@ public static synchronized void setDefaultCache(Cache cache, int dclass) { * * @return The default search path. */ - public static synchronized Name[] getDefaultSearchPath() { + public static synchronized List getDefaultSearchPath() { return defaultSearchPath; } @@ -150,7 +144,7 @@ public static synchronized Name[] getDefaultSearchPath() { * * @param domains The default search path. */ - public static synchronized void setDefaultSearchPath(Name[] domains) { + public static synchronized void setDefaultSearchPath(List domains) { defaultSearchPath = domains; } @@ -165,10 +159,12 @@ public static synchronized void setDefaultSearchPath(String[] domains) throws Te defaultSearchPath = null; return; } - Name[] newdomains = new Name[domains.length]; - for (int i = 0; i < domains.length; i++) { - newdomains[i] = Name.fromString(domains[i], Name.root); + + List newdomains = new ArrayList<>(domains.length); + for (String domain : domains) { + newdomains.add(Name.fromString(domain, Name.root)); } + defaultSearchPath = newdomains; } @@ -310,7 +306,7 @@ public void setResolver(Resolver resolver) { * * @param domains An array of names containing the search path. */ - public void setSearchPath(Name[] domains) { + public void setSearchPath(List domains) { this.searchPath = domains; } @@ -325,9 +321,10 @@ public void setSearchPath(String[] domains) throws TextParseException { this.searchPath = null; return; } - Name[] newdomains = new Name[domains.length]; - for (int i = 0; i < domains.length; i++) { - newdomains[i] = Name.fromString(domains[i], Name.root); + + List newdomains = new ArrayList<>(domains.length); + for (String domain : domains) { + newdomains.add(Name.fromString(domain, Name.root)); } this.searchPath = newdomains; } @@ -436,10 +433,8 @@ private void processResponse(Name name, SetResponse response) { private void lookup(Name current) { SetResponse sr = cache.lookupRecords(current, type, credibility); - if (log.isDebugEnabled()) { - log.debug("lookup {} {}", current, Type.string(type)); - log.debug(sr.toString()); - } + log.debug("lookup {} {}, cache answer: {}", current, Type.string(type), sr); + processResponse(current, sr); if (done || doneCurrent) { return; @@ -451,7 +446,7 @@ private void lookup(Name current) { try { response = resolver.send(query); } catch (IOException e) { - log.debug("Lookup failed", e); + log.debug("Lookup failed using resolver {}", resolver, e); // A network error occurred. Press on. if (e instanceof InterruptedIOException) { @@ -481,10 +476,8 @@ private void lookup(Name current) { if (sr == null) { sr = cache.lookupRecords(current, type, credibility); } - if (log.isDebugEnabled()) { - log.debug("queried {} {}", current, Type.string(type)); - log.debug(sr.toString()); - } + + log.debug("Queried {} {}: {}", current, Type.string(type), sr); processResponse(current, sr); } diff --git a/src/main/java/org/xbill/DNS/Name.java b/src/main/java/org/xbill/DNS/Name.java index 490362226..1481ba2db 100644 --- a/src/main/java/org/xbill/DNS/Name.java +++ b/src/main/java/org/xbill/DNS/Name.java @@ -307,7 +307,7 @@ public static Name fromString(String s, Name origin) throws TextParseException { if (s.equals("@") && origin != null) { return origin; } else if (s.equals(".")) { - return (root); + return root; } return new Name(s, origin); diff --git a/src/main/java/org/xbill/DNS/ResolverConfig.java b/src/main/java/org/xbill/DNS/ResolverConfig.java index 3d21c0fbd..4e398c204 100644 --- a/src/main/java/org/xbill/DNS/ResolverConfig.java +++ b/src/main/java/org/xbill/DNS/ResolverConfig.java @@ -2,483 +2,138 @@ package org.xbill.DNS; -import java.io.BufferedInputStream; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.lang.reflect.Method; -import java.nio.file.Files; -import java.nio.file.Paths; +import java.net.InetAddress; +import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.List; -import java.util.Locale; -import java.util.ResourceBundle; -import java.util.StringTokenizer; import lombok.extern.slf4j.Slf4j; +import org.xbill.DNS.config.AndroidResolverConfigProvider; +import org.xbill.DNS.config.InitializationException; +import org.xbill.DNS.config.JndiContextResolverConfigProvider; +import org.xbill.DNS.config.PropertyResolverConfigProvider; +import org.xbill.DNS.config.ResolvConfResolverConfigProvider; +import org.xbill.DNS.config.ResolverConfigProvider; +import org.xbill.DNS.config.SunJvmResolverConfigProvider; +import org.xbill.DNS.config.WindowsResolverConfigProvider; /** - * A class that tries to locate name servers and the search path to be appended to unqualified - * names. + * Locates name servers and the search path to be appended to unqualified names. * *

The following are attempted, in order, until one succeeds. * *

    - *
  • The properties 'dns.server' and 'dns.search' (comma delimited lists) are checked. The - * servers can either be IP addresses or hostnames (which are resolved using Java's built in - * DNS support). - *
  • The sun.net.dns.ResolverConfiguration class is queried. - *
  • On Unix, /etc/resolv.conf is parsed. - *
  • On Windows, ipconfig is called and its output parsed. This may fail for non-English - * versions on Windows. + *
  • dnsjava properties, see {@link org.xbill.DNS.config.PropertyResolverConfigProvider} + *
  • On Unix, /etc/resolv.conf is parsed, see {@link + * org.xbill.DNS.config.ResolvConfResolverConfigProvider} + *
  • On Windows, GetAdaptersAddresses is called, see {@link + * org.xbill.DNS.config.WindowsResolverConfigProvider} + *
  • On Android, system properties or the ConnectivityManager are read, see {@link + * org.xbill.DNS.config.AndroidResolverConfigProvider} + *
  • The JNDI DNS Service Provider is queried, see {@link + * org.xbill.DNS.config.JndiContextResolverConfigProvider} + *
  • The sun.net.dns.ResolverConfiguration class is queried, see {@link + * org.xbill.DNS.config.SunJvmResolverConfigProvider} *
  • "localhost" is used as the nameserver, and the search path is empty. *
* * These routines will be called internally when creating Resolvers/Lookups without explicitly * specifying server names, and can also be called directly if desired. - * - * @author Brian Wellington - * @author Yannick Meudal - * @author Arnt Gulbrandsen */ @Slf4j -public class ResolverConfig { - - public static final String DNS_SERVER_PROP = "dns.server"; - public static final String DNS_SEARCH_PROP = "dns.search"; - - private String[] servers = null; - private Name[] searchlist = null; - private int ndots = -1; +public final class ResolverConfig { + private List servers = new ArrayList<>(2); + private List searchlist = new ArrayList<>(0); + private int ndots = 1; private static ResolverConfig currentConfig; + private static List configProviders; static { + configProviders = new ArrayList<>(8); + configProviders.add(new PropertyResolverConfigProvider()); + configProviders.add(new ResolvConfResolverConfigProvider()); + configProviders.add(new WindowsResolverConfigProvider()); + configProviders.add(new AndroidResolverConfigProvider()); + configProviders.add(new JndiContextResolverConfigProvider()); + configProviders.add(new SunJvmResolverConfigProvider()); refresh(); } - public ResolverConfig() { - if (findProperty()) { - return; - } - if (findSunJVM()) { - return; - } - if (servers == null || searchlist == null) { - String OS = System.getProperty("os.name"); - String vendor = System.getProperty("java.vendor"); - if (OS.contains("Windows")) { - findWin(); - } else if (OS.contains("NetWare")) { - findNetware(); - } else if (vendor.contains("Android")) { - findAndroid(); - } else { - findUnix(); - } - } - } - - private void addServer(String server, List list) { - if (list.contains(server)) { - return; - } - log.debug("adding server {}", server); - list.add(server); - } - - private void addSearch(String search, List list) { - Name name; - log.debug("adding search {}", search); - try { - name = Name.fromString(search, Name.root); - } catch (TextParseException e) { - return; - } - if (list.contains(name)) { - return; - } - list.add(name); - } - - private int parseNdots(String token) { - token = token.substring(6); - try { - int ndots = Integer.parseInt(token); - if (ndots >= 0) { - log.debug("setting ndots {}", token); - return ndots; - } - } catch (NumberFormatException e) { - } - return -1; - } - - private void configureFromLists(List lserver, List lsearch) { - if (servers == null && lserver.size() > 0) { - servers = lserver.toArray(new String[0]); - } - if (searchlist == null && lsearch.size() > 0) { - searchlist = lsearch.toArray(new Name[0]); - } + /** Gets the current configuration */ + public static synchronized ResolverConfig getCurrentConfig() { + return currentConfig; } - private void configureNdots(int lndots) { - if (ndots < 0 && lndots > 0) { - ndots = lndots; - } + /** Set a new ordered list of resolver config providers. */ + public static synchronized void setConfigProviders(List providers) { + configProviders = new ArrayList<>(providers); } - /** - * Looks in the system properties to find servers and a search path. Servers are defined by - * dns.server=server1,server2... The search path is defined by dns.search=domain1,domain2... - */ - boolean findProperty() { - String prop; - List lserver = new ArrayList<>(0); - List lsearch = new ArrayList<>(0); - StringTokenizer st; - - prop = System.getProperty(DNS_SERVER_PROP); - if (prop != null) { - st = new StringTokenizer(prop, ","); - while (st.hasMoreTokens()) { - addServer(st.nextToken(), lserver); - } - } - - prop = System.getProperty(DNS_SEARCH_PROP); - if (prop != null) { - st = new StringTokenizer(prop, ","); - while (st.hasMoreTokens()) { - addSearch(st.nextToken(), lsearch); - } - } - configureFromLists(lserver, lsearch); - return (servers != null && searchlist != null); - } - - /** - * Uses the undocumented Sun DNS implementation to determine the configuration. This doesn't work - * or even compile with all JVMs (gcj, for example). - */ - @SuppressWarnings("unchecked") - private boolean findSunJVM() { - List lserver = new ArrayList<>(0); - List lserver_tmp; - List lsearch = new ArrayList<>(0); - List lsearch_tmp; - - try { - Class[] noClasses = new Class[0]; - Object[] noObjects = new Object[0]; - String resConfName = "sun.net.dns.ResolverConfiguration"; - Class resConfClass = Class.forName(resConfName); - Object resConf; - - // ResolverConfiguration resConf = ResolverConfiguration.open(); - Method open = resConfClass.getDeclaredMethod("open", noClasses); - resConf = open.invoke(null, noObjects); - - // lserver_tmp = resConf.nameservers(); - Method nameservers = resConfClass.getMethod("nameservers", noClasses); - lserver_tmp = (List) nameservers.invoke(resConf, noObjects); - - // lsearch_tmp = resConf.searchlist(); - Method searchlist = resConfClass.getMethod("searchlist", noClasses); - lsearch_tmp = (List) searchlist.invoke(resConf, noObjects); - } catch (Exception e) { - return false; - } - - if (lserver_tmp.size() == 0) { - return false; - } else { - for (String s : lserver_tmp) { - addServer(s, lserver); - } - } - - if (lsearch_tmp.size() > 0) { - for (String s : lsearch_tmp) { - addSearch(s, lsearch); - } + /** Gets the current configuration */ + public static void refresh() { + ResolverConfig newConfig = new ResolverConfig(); + synchronized (ResolverConfig.class) { + currentConfig = newConfig; } - configureFromLists(lserver, lsearch); - return true; } - /** - * Looks in /etc/resolv.conf to find servers and a search path. "nameserver" lines specify - * servers. "domain" and "search" lines define the search path. - */ - boolean findResolvConf(InputStream in) { - List lserver = new ArrayList<>(0); - List lsearch = new ArrayList<>(0); - int lndots = -1; - try (InputStreamReader isr = new InputStreamReader(in); - BufferedReader br = new BufferedReader(isr)) { - String line; - while ((line = br.readLine()) != null) { - if (line.startsWith("nameserver")) { - StringTokenizer st = new StringTokenizer(line); - st.nextToken(); /* skip nameserver */ - addServer(st.nextToken(), lserver); - } else if (line.startsWith("domain")) { - StringTokenizer st = new StringTokenizer(line); - st.nextToken(); /* skip domain */ - if (!st.hasMoreTokens()) { - continue; - } - if (lsearch.isEmpty()) { - addSearch(st.nextToken(), lsearch); - } - } else if (line.startsWith("search")) { - if (!lsearch.isEmpty()) { - lsearch.clear(); - } - StringTokenizer st = new StringTokenizer(line); - st.nextToken(); /* skip search */ - while (st.hasMoreTokens()) { - addSearch(st.nextToken(), lsearch); + public ResolverConfig() { + for (ResolverConfigProvider provider : configProviders) { + if (provider.isEnabled()) { + try { + provider.initialize(); + if (servers.isEmpty()) { + servers.addAll(provider.servers()); } - } else if (line.startsWith("options")) { - StringTokenizer st = new StringTokenizer(line); - st.nextToken(); /* skip options */ - while (st.hasMoreTokens()) { - String token = st.nextToken(); - if (token.startsWith("ndots:")) { - lndots = parseNdots(token); + + if (searchlist.isEmpty()) { + List lsearchPaths = provider.searchPaths(); + if (!lsearchPaths.isEmpty()) { + searchlist.addAll(lsearchPaths); + ndots = provider.ndots(); } } - } - } - } catch (IOException e) { - return false; - } - - if (lserver.isEmpty()) { - return false; - } - - configureFromLists(lserver, lsearch); - configureNdots(lndots); - return true; - } - - boolean findUnix() { - try (InputStream in = Files.newInputStream(Paths.get("/etc/resolv.conf"))) { - return findResolvConf(in); - } catch (Exception e) { - return false; - } - } - - boolean findNetware() { - try (InputStream in = Files.newInputStream(Paths.get("sys:/etc/resolv.cfg"))) { - return findResolvConf(in); - } catch (Exception e) { - return false; - } - } - /** Parses the output of ipconfig. */ - boolean findWin(InputStream in, Locale locale) { - String packageName = ResolverConfig.class.getPackage().getName(); - String resPackageName = packageName + ".windows.DNSServer"; - ResourceBundle res; - if (locale != null) { - res = ResourceBundle.getBundle(resPackageName, locale); - } else { - res = ResourceBundle.getBundle(resPackageName); - } - - String host_name = res.getString("host_name"); - String primary_dns_suffix = res.getString("primary_dns_suffix"); - String dns_suffix = res.getString("dns_suffix"); - String dns_servers = res.getString("dns_servers"); - - BufferedReader br = new BufferedReader(new InputStreamReader(in)); - try { - List lserver = new ArrayList<>(); - List lsearch = new ArrayList<>(); - String line; - boolean readingServers = false; - boolean readingSearches = false; - while ((line = br.readLine()) != null) { - StringTokenizer st = new StringTokenizer(line); - if (!st.hasMoreTokens()) { - readingServers = false; - readingSearches = false; - continue; - } - String s = st.nextToken(); - if (line.contains(":")) { - readingServers = false; - readingSearches = false; - } - - if (line.contains(host_name)) { - while (st.hasMoreTokens()) { - s = st.nextToken(); - } - Name name; - try { - name = Name.fromString(s, null); - } catch (TextParseException e) { - continue; - } - if (name.labels() == 1) { - continue; - } - addSearch(s, lsearch); - } else if (line.contains(primary_dns_suffix)) { - while (st.hasMoreTokens()) { - s = st.nextToken(); - } - if (s.equals(":")) { - continue; - } - addSearch(s, lsearch); - readingSearches = true; - } else if (readingSearches || line.contains(dns_suffix)) { - while (st.hasMoreTokens()) { - s = st.nextToken(); - } - if (s.equals(":")) { - continue; - } - addSearch(s, lsearch); - readingSearches = true; - } else if (readingServers || line.contains(dns_servers)) { - while (st.hasMoreTokens()) { - s = st.nextToken(); - } - if (s.equals(":")) { - continue; + if (!servers.isEmpty() && !searchlist.isEmpty()) { + // found both servers and search path, we're done + return; } - addServer(s, lserver); - readingServers = true; + } catch (InitializationException e) { + log.warn("Failed to initialize provider", e); } } - if (lserver.isEmpty()) { - return false; - } - configureFromLists(lserver, lsearch); - return true; - } catch (IOException e) { - return false; - } - } - - private boolean findWin(InputStream in) { - boolean found; - String property = "org.xbill.DNS.windows.parse.buffer"; - final int defaultBufSize = 8 * 1024; - int bufSize = Integer.getInteger(property, defaultBufSize); - BufferedInputStream b = new BufferedInputStream(in, bufSize); - b.mark(bufSize); - found = findWin(b, null); - if (servers == null) { - try { - b.reset(); - } catch (IOException e) { - return false; - } - found = findWin(b, new Locale("", "")); } - return found; - } - /** Calls ipconfig and parses the result to find servers and a search path. */ - boolean findWin() { - boolean found; - try { - Process p; - p = Runtime.getRuntime().exec("ipconfig /all"); - found = findWin(p.getInputStream()); - p.destroy(); - } catch (Exception e) { - return false; + if (servers.isEmpty()) { + servers.add( + new InetSocketAddress(InetAddress.getLoopbackAddress(), SimpleResolver.DEFAULT_PORT)); } - return found; - } - - /** - * Parses the output of getprop, which is the only way to get DNS info on Android. getprop might - * disappear in future releases, so this code comes with a use-by date. - */ - boolean findAndroid() { - // This originally looked for all lines containing .dns; but - // http://code.google.com/p/android/issues/detail?id=2207#c73 - // indicates that net.dns* should always be the active nameservers, so - // we use those. - final String re1 = "^\\d+(\\.\\d+){3}$"; - final String re2 = "^[0-9a-f]+(:[0-9a-f]*)+:[0-9a-f]+$"; - ArrayList lserver = new ArrayList<>(); - ArrayList lsearch = new ArrayList<>(); - try { - Class SystemProperties = Class.forName("android.os.SystemProperties"); - Method method = SystemProperties.getMethod("get", String.class); - final String[] netdns = new String[] {"net.dns1", "net.dns2", "net.dns3", "net.dns4"}; - for (String netdn : netdns) { - Object[] args = new Object[] {netdn}; - String v = (String) method.invoke(null, args); - if (v != null && (v.matches(re1) || v.matches(re2)) && !lserver.contains(v)) { - lserver.add(v); - } - } - } catch (Exception e) { - return false; - } - if (lserver.isEmpty()) { - return false; - } - configureFromLists(lserver, lsearch); - return true; } /** Returns all located servers */ - public String[] servers() { + public List servers() { return servers; } /** Returns the first located server */ - public String server() { - if (servers == null) { - return null; - } - return servers[0]; + public InetSocketAddress server() { + return servers.get(0); } /** Returns all entries in the located search path */ - public Name[] searchPath() { + public List searchPath() { return searchlist; } /** - * Returns the located ndots value, or the default (1) if not configured. Note that ndots can only - * be configured in a resolv.conf file, and will only take effect if ResolverConfig uses - * resolv.conf directly (that is, if the JVM does not include the - * sun.net.dns.ResolverConfiguration class). + * Gets the threshold for the number of dots which must appear in a name before it is considered + * absolute. The default is {@code 1}, meaning meaning that if there are any dots in a name, the + * name will be tried first as an absolute name. + * + *

Note that ndots can only be configured in a resolv.conf file or the property {@link + * #DNS_NDOTS_PROP}. */ public int ndots() { - if (ndots < 0) { - return 1; - } return ndots; } - - /** Gets the current configuration */ - public static synchronized ResolverConfig getCurrentConfig() { - return currentConfig; - } - - /** Gets the current configuration */ - public static void refresh() { - ResolverConfig newConfig = new ResolverConfig(); - synchronized (ResolverConfig.class) { - currentConfig = newConfig; - } - } } diff --git a/src/main/java/org/xbill/DNS/SimpleResolver.java b/src/main/java/org/xbill/DNS/SimpleResolver.java index 4a6dea60e..332cd3200 100644 --- a/src/main/java/org/xbill/DNS/SimpleResolver.java +++ b/src/main/java/org/xbill/DNS/SimpleResolver.java @@ -7,6 +7,7 @@ import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.List; +import java.util.Objects; import lombok.extern.slf4j.Slf4j; /** @@ -36,9 +37,21 @@ public class SimpleResolver implements Resolver { private static final short DEFAULT_UDPSIZE = 512; - private static String defaultResolver = "localhost"; + private static InetSocketAddress defaultResolver = + new InetSocketAddress(InetAddress.getLoopbackAddress(), DEFAULT_PORT); private static int uniqueID = 0; + /** + * Creates a SimpleResolver. The host to query is either found by using ResolverConfig, or the + * default host is used. + * + * @see ResolverConfig + * @exception UnknownHostException Failure occurred while finding the host + */ + public SimpleResolver() throws UnknownHostException { + this((String) null); + } + /** * Creates a SimpleResolver that will query the specified host * @@ -46,29 +59,25 @@ public class SimpleResolver implements Resolver { */ public SimpleResolver(String hostname) throws UnknownHostException { if (hostname == null) { - hostname = ResolverConfig.getCurrentConfig().server(); - if (hostname == null) { - hostname = defaultResolver; - } + address = ResolverConfig.getCurrentConfig().server(); } - InetAddress addr; - if (hostname.equals("0")) { - addr = InetAddress.getLocalHost(); + + if ("0".equals(hostname)) { + address = defaultResolver; } else { - addr = InetAddress.getByName(hostname); + address = new InetSocketAddress(InetAddress.getByName(hostname), DEFAULT_PORT); } - address = new InetSocketAddress(addr, DEFAULT_PORT); } - /** - * Creates a SimpleResolver. The host to query is either found by using ResolverConfig, or the - * default host is used. - * - * @see ResolverConfig - * @exception UnknownHostException Failure occurred while finding the host - */ - public SimpleResolver() throws UnknownHostException { - this(null); + /** Creates a SimpleResolver that will query the specified host */ + public SimpleResolver(InetSocketAddress host) { + address = Objects.requireNonNull(host, "host must not be null"); + } + + /** Creates a SimpleResolver that will query the specified host */ + public SimpleResolver(InetAddress host) { + Objects.requireNonNull(host, "host must not be null"); + address = new InetSocketAddress(host, DEFAULT_PORT); } /** @@ -82,7 +91,7 @@ public InetSocketAddress getAddress() { } /** Sets the default host (initially localhost) to query */ - public static void setDefaultResolver(String hostname) { + public static void setDefaultResolver(InetSocketAddress hostname) { defaultResolver = hostname; } @@ -335,4 +344,9 @@ private Message sendAXFR(Message query) throws IOException { } return response; } + + @Override + public String toString() { + return "SimpleResolver [" + address + "]"; + } } diff --git a/src/main/java/org/xbill/DNS/config/AndroidResolverConfigProvider.java b/src/main/java/org/xbill/DNS/config/AndroidResolverConfigProvider.java new file mode 100644 index 000000000..dd290f6c0 --- /dev/null +++ b/src/main/java/org/xbill/DNS/config/AndroidResolverConfigProvider.java @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: BSD-2-Clause +package org.xbill.DNS.config; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.xbill.DNS.ResolverConfig; +import org.xbill.DNS.SimpleResolver; + +/** + * Resolver config provider for Android. Contrary to all other providers, this provider needs a + * context to operate on which must be set by calling {@link #setContext(Object)}. + * + *

If you are developing for Android, consider implementing your own {@link + * ResolverConfigProvider} that listens to network callbacks and properly refreshes on link changes. + * Something you need to do anyway to call {@link ResolverConfig#refresh()} otherwise it is pretty + * much guaranteed to have outdated servers sooner or later. + */ +@Slf4j +public class AndroidResolverConfigProvider extends BaseResolverConfigProvider { + private static Object context = null; + + /** Gets the current configuration */ + public static void setContext(Object ctx) { + context = ctx; + } + + @Override + public void initialize() throws InitializationException { + // This originally looked for all lines containing .dns; but + // http://code.google.com/p/android/issues/detail?id=2207#c73 indicates + // that net.dns* should always be the active nameservers, so we use those. + // Starting with Android 8 (API 26), the net.dns[1234] properties are no longer available: + // https://developer.android.com/about/versions/oreo/android-8.0-changes.html#o-pri + try { + Class Version = Class.forName("android.os.Build$VERSION"); + Field SDK_INT = Version.getField("SDK_INT"); + + if (SDK_INT.getInt(null) >= 26) { + initializeApi26Nameservers(); + } else { + initializeNameservers(); + } + } catch (NoSuchMethodException + | InvocationTargetException + | NoSuchFieldException + | IllegalAccessException + | ClassNotFoundException e) { + throw new InitializationException(e); + } + } + + private void initializeNameservers() + throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, + InvocationTargetException { + Class systemPropertiesClass = Class.forName("android.os.SystemProperties"); + Method method = systemPropertiesClass.getMethod("get", String.class); + for (int i = 1; i <= 4; i++) { + String server = (String) method.invoke(null, "net.dns" + i); + if (server != null && !server.isEmpty()) { + nameservers.add(new InetSocketAddress(server, SimpleResolver.DEFAULT_PORT)); + } + } + } + + private void initializeApi26Nameservers() + throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, + InvocationTargetException, InitializationException { + if (context == null) { + throw new InitializationException("Context must be initialized by calling setContext"); + } + + Class contextClass = Class.forName("android.content.Context"); + Method getSystemService = contextClass.getDeclaredMethod("getSystemService", String.class); + Object cm = getSystemService.invoke(context, "connectivity"); + + Class connectivityManagerClass = Class.forName("android.net.ConnectivityManager"); + Method getActiveNetwork = connectivityManagerClass.getDeclaredMethod("getActiveNetwork"); + Object network = getActiveNetwork.invoke(cm); + if (network == null) { + // if the device is offline, there's no active network + return; + } + + Class networkClass = Class.forName("android.net.Network"); + Method getLinkProperties = + connectivityManagerClass.getDeclaredMethod("getLinkProperties", networkClass); + Object lp = getLinkProperties.invoke(cm, network); + if (lp == null) { + // can be null for an unknown network, which may happen if networks change + return; + } + + Class linkPropertiesClass = Class.forName("android.net.LinkProperties"); + Method getDnsServers = linkPropertiesClass.getDeclaredMethod("getDnsServers"); + @SuppressWarnings("unchecked") + List addresses = (List) getDnsServers.invoke(lp); + + for (InetAddress address : addresses) { + addNameserver(new InetSocketAddress(address, SimpleResolver.DEFAULT_PORT)); + } + + Method getDomains = linkPropertiesClass.getDeclaredMethod("getDomains"); + parseSearchPathList((String) getDomains.invoke(lp), ","); + } + + @Override + public boolean isEnabled() { + return System.getProperty("java.vendor").contains("Android"); + } +} diff --git a/src/main/java/org/xbill/DNS/config/BaseResolverConfigProvider.java b/src/main/java/org/xbill/DNS/config/BaseResolverConfigProvider.java new file mode 100644 index 000000000..8f4f1c6d4 --- /dev/null +++ b/src/main/java/org/xbill/DNS/config/BaseResolverConfigProvider.java @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: BSD-2-Clause +package org.xbill.DNS.config; + +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.StringTokenizer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xbill.DNS.Name; +import org.xbill.DNS.TextParseException; + +/** + * Base class for resolver config providers that provides a default implementation for the lists and + * utility methods to prevent duplicates. + */ +abstract class BaseResolverConfigProvider implements ResolverConfigProvider { + final Logger log = LoggerFactory.getLogger(getClass()); + List nameservers = new ArrayList<>(3); + List searchlist = new ArrayList<>(1); + + void parseSearchPathList(String search, String delimiter) { + if (search != null) { + StringTokenizer st = new StringTokenizer(search, delimiter); + while (st.hasMoreTokens()) { + addSearchPath(st.nextToken()); + } + } + } + + void addSearchPath(String searchPath) { + if (searchPath == null || searchPath.isEmpty()) { + return; + } + + try { + Name n = Name.fromString(searchPath, Name.root); + if (!searchlist.contains(n)) { + searchlist.add(n); + log.debug("Added {} to search paths", n); + } + } catch (TextParseException e) { + log.warn("Could not parse search path {} as a dns name, ignoring", searchPath); + } + } + + void addNameserver(InetSocketAddress server) { + if (!nameservers.contains(server)) { + nameservers.add(server); + log.debug("Added {} to nameservers", server); + } + } + + int parseNdots(String token) { + if (token != null && !token.isEmpty()) { + try { + int ndots = Integer.parseInt(token); + if (ndots >= 0) { + if (ndots > 15) { + // man resolv.conf: + // The value for this option is silently capped to 15 + ndots = 15; + } + + return ndots; + } + } catch (NumberFormatException e) { + // ignore + } + } + + return 1; + } + + @Override + public final List servers() { + return Collections.unmodifiableList(nameservers); + } + + @Override + public final List searchPaths() { + return Collections.unmodifiableList(searchlist); + } +} diff --git a/src/main/java/org/xbill/DNS/config/IPHlpAPI.java b/src/main/java/org/xbill/DNS/config/IPHlpAPI.java new file mode 100644 index 000000000..d39e54635 --- /dev/null +++ b/src/main/java/org/xbill/DNS/config/IPHlpAPI.java @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: BSD-2-Clause +package org.xbill.DNS.config; + +import com.sun.jna.Library; +import com.sun.jna.Native; +import com.sun.jna.Pointer; +import com.sun.jna.Structure; +import com.sun.jna.WString; +import com.sun.jna.platform.win32.Guid; +import com.sun.jna.ptr.IntByReference; +import com.sun.jna.win32.W32APIOptions; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; + +interface IPHlpAPI extends Library { + IPHlpAPI INSTANCE = Native.load("IPHlpAPI", IPHlpAPI.class, W32APIOptions.ASCII_OPTIONS); + + int AF_UNSPEC = 0; + int AF_INET = 2; + int AF_INET6 = 23; + + int GAA_FLAG_SKIP_UNICAST = 0x0001; + int GAA_FLAG_SKIP_ANYCAST = 0x0002; + int GAA_FLAG_SKIP_MULTICAST = 0x0004; + int GAA_FLAG_SKIP_DNS_SERVER = 0x0008; + int GAA_FLAG_INCLUDE_PREFIX = 0x0010; + int GAA_FLAG_SKIP_FRIENDLY_NAME = 0x0020; + int GAA_FLAG_INCLUDE_WINS_INFO = 0x0040; + int GAA_FLAG_INCLUDE_GATEWAYS = 0x0080; + int GAA_FLAG_INCLUDE_ALL_INTERFACES = 0x0100; + int GAA_FLAG_INCLUDE_ALL_COMPARTMENTS = 0x0200; + int GAA_FLAG_INCLUDE_TUNNEL_BINDINGORDER = 0x0400; + + @Structure.FieldOrder({"sin_family", "sin_port", "sin_addr", "sin_zero"}) + class sockaddr_in extends Structure { + public sockaddr_in(Pointer p) { + super(p); + read(); + } + + public short sin_family; + public short sin_port; + public byte[] sin_addr = new byte[4]; + public byte[] sin_zero = new byte[8]; + } + + @Structure.FieldOrder({"sin6_family", "sin6_port", "sin6_flowinfo", "sin6_addr", "sin6_scope_id"}) + class sockaddr_in6 extends Structure { + public sockaddr_in6(Pointer p) { + super(p); + read(); + } + + public short sin6_family; + public short sin6_port; + public int sin6_flowinfo; + public byte[] sin6_addr = new byte[16]; + public int sin6_scope_id; + } + + @Structure.FieldOrder({"lpSockaddr", "iSockaddrLength"}) + class SOCKET_ADDRESS extends Structure { + public Pointer lpSockaddr; + public int iSockaddrLength; + + InetAddress toAddress() throws UnknownHostException { + switch (lpSockaddr.getShort(0)) { + case AF_INET: + sockaddr_in in4 = new sockaddr_in(lpSockaddr); + return InetAddress.getByAddress(in4.sin_addr); + case AF_INET6: + sockaddr_in6 in6 = new sockaddr_in6(lpSockaddr); + return Inet6Address.getByAddress("", in6.sin6_addr, in6.sin6_scope_id); + } + + return null; + } + } + + @Structure.FieldOrder({ + "Length", + "IfIndex", + "Next", + "Address", + "PrefixOrigin", + "SuffixOrigin", + "DadState", + "ValidLifetime", + "PreferredLifetime", + "LeaseLifetime", + "OnLinkPrefixLength" + }) + class IP_ADAPTER_UNICAST_ADDRESS_LH extends Structure { + public static class ByReference extends IP_ADAPTER_UNICAST_ADDRESS_LH + implements Structure.ByReference {} + + public int Length; + public int IfIndex; + + public IP_ADAPTER_UNICAST_ADDRESS_LH.ByReference Next; + public SOCKET_ADDRESS Address; + public int PrefixOrigin; + public int SuffixOrigin; + public int DadState; + public int ValidLifetime; + public int PreferredLifetime; + public int LeaseLifetime; + public byte OnLinkPrefixLength; + } + + @Structure.FieldOrder({"Length", "Reserved", "Next", "Address"}) + class IP_ADAPTER_DNS_SERVER_ADDRESS_XP extends Structure { + public static class ByReference extends IP_ADAPTER_DNS_SERVER_ADDRESS_XP + implements Structure.ByReference {} + + public int Length; + public int Reserved; + public IP_ADAPTER_DNS_SERVER_ADDRESS_XP.ByReference Next; + public SOCKET_ADDRESS Address; + } + + @Structure.FieldOrder({"Length", "Reserved", "Next", "Address"}) + class IP_ADAPTER_ANYCAST_ADDRESS_XP extends Structure { + public static class ByReference extends IP_ADAPTER_ANYCAST_ADDRESS_XP + implements Structure.ByReference {} + + public int Length; + public int Reserved; + public IP_ADAPTER_DNS_SERVER_ADDRESS_XP.ByReference Next; + public SOCKET_ADDRESS Address; + } + + @Structure.FieldOrder({"Length", "Reserved", "Next", "Address"}) + class IP_ADAPTER_MULTICAST_ADDRESS_XP extends Structure { + public static class ByReference extends IP_ADAPTER_MULTICAST_ADDRESS_XP + implements Structure.ByReference {} + + public int Length; + public int Reserved; + public IP_ADAPTER_DNS_SERVER_ADDRESS_XP.ByReference Next; + public SOCKET_ADDRESS Address; + } + + @Structure.FieldOrder({"Next", "_String"}) + class IP_ADAPTER_DNS_SUFFIX extends Structure { + public static class ByReference extends IP_ADAPTER_DNS_SUFFIX + implements Structure.ByReference {} + + public IP_ADAPTER_DNS_SUFFIX.ByReference Next; + public char[] _String = new char[256]; + } + + @Structure.FieldOrder({ + "Length", + "IfIndex", + "Next", + "AdapterName", + "FirstUnicastAddress", + "FirstAnycastAddress", + "FirstMulticastAddress", + "FirstDnsServerAddress", + "DnsSuffix", + "Description", + "FriendlyName", + "PhysicalAddress", + "PhysicalAddressLength", + "Flags", + "Mtu", + "IfType", + "OperStatus", + "Ipv6IfIndex", + "ZoneIndices", + "FirstPrefix", + "TransmitLinkSpeed", + "ReceiveLinkSpeed", + "FirstWinsServerAddress", + "FirstGatewayAddress", + "Ipv4Metric", + "Ipv6Metric", + "Luid", + "Dhcpv4Server", + "CompartmentId", + "NetworkGuid", + "ConnectionType", + "TunnelType", + "Dhcpv6Server", + "Dhcpv6ClientDuid", + "Dhcpv6ClientDuidLength", + "Dhcpv6Iaid", + "FirstDnsSuffix", + }) + class IP_ADAPTER_ADDRESSES_LH extends Structure { + public static class ByReference extends IP_ADAPTER_ADDRESSES_LH + implements Structure.ByReference {} + + public IP_ADAPTER_ADDRESSES_LH(Pointer p) { + super(p); + read(); + } + + public IP_ADAPTER_ADDRESSES_LH() {} + + public int Length; + public int IfIndex; + + public IP_ADAPTER_ADDRESSES_LH.ByReference Next; + public String AdapterName; + public IP_ADAPTER_UNICAST_ADDRESS_LH.ByReference FirstUnicastAddress; + public IP_ADAPTER_ANYCAST_ADDRESS_XP.ByReference FirstAnycastAddress; + public IP_ADAPTER_MULTICAST_ADDRESS_XP.ByReference FirstMulticastAddress; + public IP_ADAPTER_DNS_SERVER_ADDRESS_XP.ByReference FirstDnsServerAddress; + public WString DnsSuffix; + public WString Description; + public WString FriendlyName; + public byte[] PhysicalAddress = new byte[8]; + public int PhysicalAddressLength; + public int Flags; + public int Mtu; + public int IfType; + public int OperStatus; + public int Ipv6IfIndex; + public int[] ZoneIndices = new int[16]; + public Pointer FirstPrefix; + public long TransmitLinkSpeed; + public long ReceiveLinkSpeed; + public Pointer FirstWinsServerAddress; + public Pointer FirstGatewayAddress; + public int Ipv4Metric; + public int Ipv6Metric; + public Pointer Luid; + public SOCKET_ADDRESS Dhcpv4Server; + public int CompartmentId; + public Guid.GUID NetworkGuid; + public int ConnectionType; + public int TunnelType; + public SOCKET_ADDRESS Dhcpv6Server; + public byte[] Dhcpv6ClientDuid = new byte[130]; + public int Dhcpv6ClientDuidLength; + public int Dhcpv6Iaid; + public IP_ADAPTER_DNS_SUFFIX.ByReference FirstDnsSuffix; + } + + int GetAdaptersAddresses( + int family, + int flags, + Pointer reserved, + Pointer adapterAddresses, + IntByReference sizePointer); +} diff --git a/src/main/java/org/xbill/DNS/config/InitializationException.java b/src/main/java/org/xbill/DNS/config/InitializationException.java new file mode 100644 index 000000000..7648e1822 --- /dev/null +++ b/src/main/java/org/xbill/DNS/config/InitializationException.java @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: BSD-2-Clause +package org.xbill.DNS.config; + +public class InitializationException extends Exception { + InitializationException(String message) { + super(message); + } + + InitializationException(Exception e) { + super(e); + } +} diff --git a/src/main/java/org/xbill/DNS/config/JndiContextResolverConfigProvider.java b/src/main/java/org/xbill/DNS/config/JndiContextResolverConfigProvider.java new file mode 100644 index 000000000..6e15a1a68 --- /dev/null +++ b/src/main/java/org/xbill/DNS/config/JndiContextResolverConfigProvider.java @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: BSD-2-Clause +package org.xbill.DNS.config; + +import java.net.InetSocketAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Hashtable; +import java.util.List; +import java.util.StringTokenizer; +import javax.naming.Context; +import javax.naming.NamingException; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; +import lombok.extern.slf4j.Slf4j; +import org.xbill.DNS.Name; +import org.xbill.DNS.SimpleResolver; + +/** + * Resolver config provider that tries to extract the system's DNS servers from the JNDI DNS Service + * Provider. + */ +@Slf4j +public class JndiContextResolverConfigProvider implements ResolverConfigProvider { + private InnerJndiContextResolverConfigProvider inner; + + public JndiContextResolverConfigProvider() { + if (!System.getProperty("java.vendor").contains("Android")) { + try { + inner = new InnerJndiContextResolverConfigProvider(); + } catch (NoClassDefFoundError e) { + log.debug("JNDI DNS not available"); + } + } + } + + private static class InnerJndiContextResolverConfigProvider extends BaseResolverConfigProvider { + public void initialize() { + Hashtable env = new Hashtable<>(); + env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory"); + // http://mail.openjdk.java.net/pipermail/net-dev/2017-March/010695.html + env.put("java.naming.provider.url", "dns://"); + + String servers = null; + try { + DirContext ctx = new InitialDirContext(env); + servers = (String) ctx.getEnvironment().get("java.naming.provider.url"); + ctx.close(); + } catch (NamingException e) { + // ignore + } + + if (servers != null) { + StringTokenizer st = new StringTokenizer(servers, " "); + while (st.hasMoreTokens()) { + String server = st.nextToken(); + try { + URI serverUri = new URI(server); + String host = serverUri.getHost(); + if (host == null || host.isEmpty()) { + // skip the fallback server to localhost + continue; + } + + int port = serverUri.getPort(); + if (port == -1) { + port = SimpleResolver.DEFAULT_PORT; + } + + addNameserver(new InetSocketAddress(host, port)); + } catch (URISyntaxException e) { + log.debug("Could not parse {} as a dns server, ignoring", server, e); + } + } + } + } + } + + @Override + public void initialize() { + inner.initialize(); + } + + @Override + public List servers() { + return inner.servers(); + } + + @Override + public List searchPaths() { + return inner.searchPaths(); + } + + @Override + public boolean isEnabled() { + return inner != null; + } +} diff --git a/src/main/java/org/xbill/DNS/config/PropertyResolverConfigProvider.java b/src/main/java/org/xbill/DNS/config/PropertyResolverConfigProvider.java new file mode 100644 index 000000000..ea07f6383 --- /dev/null +++ b/src/main/java/org/xbill/DNS/config/PropertyResolverConfigProvider.java @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: BSD-2-Clause +package org.xbill.DNS.config; + +import java.net.InetSocketAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.StringTokenizer; +import org.xbill.DNS.SimpleResolver; + +/** + * The properties {@link #DNS_SERVER_PROP}, {@link #DNS_SEARCH_PROP} (comma delimited lists) are + * checked. The servers can either be IP addresses or hostnames (which are resolved using Java's + * built in DNS support). + */ +public class PropertyResolverConfigProvider extends BaseResolverConfigProvider { + public static final String DNS_SERVER_PROP = "dns.server"; + public static final String DNS_SEARCH_PROP = "dns.search"; + public static final String DNS_NDOTS_PROP = "dns.ndots"; + + private int ndots; + + @Override + public void initialize() { + String servers = System.getProperty(DNS_SERVER_PROP); + if (servers != null) { + StringTokenizer st = new StringTokenizer(servers, ","); + while (st.hasMoreTokens()) { + String server = st.nextToken(); + try { + URI uri = new URI("dns://" + server); + // assume this is an IPv6 address without brackets + if (uri.getHost() == null) { + addNameserver(new InetSocketAddress(server, SimpleResolver.DEFAULT_PORT)); + } else { + int port = uri.getPort(); + if (port == -1) { + port = SimpleResolver.DEFAULT_PORT; + } + + addNameserver(new InetSocketAddress(uri.getHost(), port)); + } + } catch (URISyntaxException e) { + log.warn("Ignored invalid server {}", server); + } + } + } + + String searchPathProperty = System.getProperty(DNS_SEARCH_PROP); + parseSearchPathList(searchPathProperty, ","); + + String ndotsProperty = System.getProperty(DNS_NDOTS_PROP); + ndots = parseNdots(ndotsProperty); + } + + @Override + public int ndots() { + return ndots; + } +} diff --git a/src/main/java/org/xbill/DNS/config/ResolvConfResolverConfigProvider.java b/src/main/java/org/xbill/DNS/config/ResolvConfResolverConfigProvider.java new file mode 100644 index 000000000..9f41d1619 --- /dev/null +++ b/src/main/java/org/xbill/DNS/config/ResolvConfResolverConfigProvider.java @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: BSD-2-Clause +package org.xbill.DNS.config; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.InetSocketAddress; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.StringTokenizer; +import org.xbill.DNS.SimpleResolver; + +public class ResolvConfResolverConfigProvider extends BaseResolverConfigProvider { + private int ndots = 1; + + public void initialize() { + // first try the default unix config path + if (!tryParseResolveConf("/etc/resolv.cfg")) { + // then fallback to netware + tryParseResolveConf("sys:/etc/resolv.cfg"); + } + } + + private boolean tryParseResolveConf(String path) { + Path p = Paths.get(path); + if (Files.exists(p)) { + try (InputStream in = Files.newInputStream(p)) { + parseResolvConf(in); + return true; + } catch (IOException e) { + // ignore + } + } + + return false; + } + + protected void parseResolvConf(InputStream in) throws IOException { + try (InputStreamReader isr = new InputStreamReader(in); + BufferedReader br = new BufferedReader(isr)) { + String line; + while ((line = br.readLine()) != null) { + StringTokenizer st = new StringTokenizer(line); + if (!st.hasMoreTokens()) { + continue; + } + + switch (st.nextToken()) { + case "nameserver": + addServer(st.nextToken()); + break; + + case "domain": + // man resolv.conf: + // The domain and search keywords are mutually exclusive. If more than one instance of + // these keywords is present, the last instance wins. + searchlist.clear(); + if (!st.hasMoreTokens()) { + continue; + } + + addSearchPath(st.nextToken()); + break; + + case "search": + // man resolv.conf: + // The domain and search keywords are mutually exclusive. If more than one instance of + // these keywords is present, the last instance wins. + searchlist.clear(); + while (st.hasMoreTokens()) { + addSearchPath(st.nextToken()); + } + + case "options": + while (st.hasMoreTokens()) { + String token = st.nextToken(); + if (token.startsWith("ndots:")) { + ndots = parseNdots(token.substring(6)); + } + } + break; + } + } + } + + // man resolv.conf: + // The search keyword of a system's resolv.conf file can be overridden on a per-process basis by + // setting the environment variable LOCALDOMAIN to a space-separated list of search domains. + String localdomain = System.getenv("LOCALDOMAIN"); + if (localdomain != null && !localdomain.isEmpty()) { + searchlist.clear(); + parseSearchPathList(localdomain, " "); + } + + // man resolv.conf: + // The options keyword of a system's resolv.conf file can be amended on a per-process basis by + // setting the environment variable RES_OPTIONS to a space-separated list of resolver options as + // explained above under options. + String resOptions = System.getenv("RES_OPTIONS"); + if (resOptions != null && !resOptions.isEmpty()) { + StringTokenizer st = new StringTokenizer(resOptions, " "); + while (st.hasMoreTokens()) { + String token = st.nextToken(); + if (token.startsWith("ndots:")) { + ndots = parseNdots(token.substring(6)); + } + } + } + } + + private void addServer(String server) { + if (nameservers.size() < 3) { + addNameserver(new InetSocketAddress(server, SimpleResolver.DEFAULT_PORT)); + } + } + + @Override + public int ndots() { + return ndots; + } + + @Override + public boolean isEnabled() { + return !System.getProperty("os.name").contains("Windows"); + } +} diff --git a/src/main/java/org/xbill/DNS/config/ResolverConfigProvider.java b/src/main/java/org/xbill/DNS/config/ResolverConfigProvider.java new file mode 100644 index 000000000..176bb9b63 --- /dev/null +++ b/src/main/java/org/xbill/DNS/config/ResolverConfigProvider.java @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: BSD-2-Clause +package org.xbill.DNS.config; + +import java.net.InetSocketAddress; +import java.util.List; +import org.xbill.DNS.Name; + +public interface ResolverConfigProvider { + /** Initializes the servers, search paths, etc. */ + void initialize() throws InitializationException; + + /** Returns all located servers, which may be empty. */ + List servers(); + + /** Returns all entries in the located search path, which may be empty. */ + List searchPaths(); + + /** + * Gets the threshold for the number of dots which must appear in a name before it is considered + * absolute. The default is {@code -1}, meaning this provider does not supported reading the ndots + * configuration. + */ + default int ndots() { + return -1; + } + + /** Determines if this provider is enabled. */ + default boolean isEnabled() { + return true; + } +} diff --git a/src/main/java/org/xbill/DNS/config/SunJvmResolverConfigProvider.java b/src/main/java/org/xbill/DNS/config/SunJvmResolverConfigProvider.java new file mode 100644 index 000000000..cb0a9612e --- /dev/null +++ b/src/main/java/org/xbill/DNS/config/SunJvmResolverConfigProvider.java @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: BSD-2-Clause +package org.xbill.DNS.config; + +import static java.util.stream.Collectors.toList; + +import java.lang.reflect.Method; +import java.net.InetSocketAddress; +import java.util.Collections; +import java.util.List; +import org.xbill.DNS.Name; +import org.xbill.DNS.TextParseException; + +/** + * Resolver config provider that queries the traditional class {@code + * sun.net.dns.ResolverConfiguration} via reflection. + * + *

As of Java 9, this generates an illegal reflective access exception and on Windows, this may + * return invalid nameservers of disconnected NICs. + */ +public class SunJvmResolverConfigProvider implements ResolverConfigProvider { + private List nameservers = null; + private List searchlist = null; + + public void initialize() throws InitializationException { + try { + Class resConfClass = Class.forName("sun.net.dns.ResolverConfiguration"); + Method open = resConfClass.getDeclaredMethod("open"); + Object resConf = open.invoke(null); + + Method nameserversMethod = resConfClass.getMethod("nameservers"); + @SuppressWarnings("unchecked") + List jvmNameservers = (List) nameserversMethod.invoke(resConf); + nameservers = + jvmNameservers.stream().map(ns -> new InetSocketAddress(ns, 53)).collect(toList()); + + Method searchlistMethod = resConfClass.getMethod("searchlist"); + @SuppressWarnings("unchecked") + List jvmSearchlist = (List) searchlistMethod.invoke(resConf); + searchlist = + jvmSearchlist.stream() + .map( + n -> { + try { + return Name.fromString(n, Name.root); + } catch (TextParseException e) { + throw new IllegalArgumentException(e); + } + }) + .collect(toList()); + } catch (Exception e) { + throw new InitializationException(e); + } + } + + @Override + public List servers() { + if (nameservers == null) { + throw new IllegalStateException("not initialized"); + } + + return Collections.unmodifiableList(nameservers); + } + + @Override + public List searchPaths() { + if (searchlist == null) { + throw new IllegalStateException("not initialized"); + } + + return Collections.unmodifiableList(searchlist); + } + + @Override + public boolean isEnabled() { + return !System.getProperty("java.vendor").contains("Android"); + } +} diff --git a/src/main/java/org/xbill/DNS/config/WindowsResolverConfigProvider.java b/src/main/java/org/xbill/DNS/config/WindowsResolverConfigProvider.java new file mode 100644 index 000000000..7c2c69bfe --- /dev/null +++ b/src/main/java/org/xbill/DNS/config/WindowsResolverConfigProvider.java @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: BSD-2-Clause +package org.xbill.DNS.config; + +import static org.xbill.DNS.config.IPHlpAPI.AF_UNSPEC; +import static org.xbill.DNS.config.IPHlpAPI.GAA_FLAG_SKIP_ANYCAST; +import static org.xbill.DNS.config.IPHlpAPI.GAA_FLAG_SKIP_FRIENDLY_NAME; +import static org.xbill.DNS.config.IPHlpAPI.GAA_FLAG_SKIP_MULTICAST; +import static org.xbill.DNS.config.IPHlpAPI.GAA_FLAG_SKIP_UNICAST; +import static org.xbill.DNS.config.IPHlpAPI.INSTANCE; +import static org.xbill.DNS.config.IPHlpAPI.IP_ADAPTER_ADDRESSES_LH; +import static org.xbill.DNS.config.IPHlpAPI.IP_ADAPTER_DNS_SERVER_ADDRESS_XP; +import static org.xbill.DNS.config.IPHlpAPI.IP_ADAPTER_DNS_SUFFIX; + +import com.sun.jna.Memory; +import com.sun.jna.Pointer; +import com.sun.jna.platform.win32.Win32Exception; +import com.sun.jna.platform.win32.WinError; +import com.sun.jna.ptr.IntByReference; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.xbill.DNS.Name; +import org.xbill.DNS.SimpleResolver; + +/** + * Resolver config provider for Windows. It reads the nameservers and search path by calling the API + * GetAdaptersAddresses. + * This class requires the JNA library on + * the classpath. + */ +@Slf4j +public class WindowsResolverConfigProvider implements ResolverConfigProvider { + private InnerWindowsResolverConfigProvider inner; + + public WindowsResolverConfigProvider() { + if (System.getProperty("os.name").contains("Windows")) { + try { + inner = new InnerWindowsResolverConfigProvider(); + } catch (NoClassDefFoundError e) { + log.debug("JNA not available"); + } + } + } + + @Slf4j + private static class InnerWindowsResolverConfigProvider extends BaseResolverConfigProvider { + public void initialize() throws InitializationException { + // The recommended method of calling the GetAdaptersAddresses function is to pre-allocate a + // 15KB working buffer + Memory buffer = new Memory(15 * 1024); + IntByReference size = new IntByReference(0); + int flags = + GAA_FLAG_SKIP_UNICAST + | GAA_FLAG_SKIP_ANYCAST + | GAA_FLAG_SKIP_MULTICAST + | GAA_FLAG_SKIP_FRIENDLY_NAME; + int error = INSTANCE.GetAdaptersAddresses(AF_UNSPEC, flags, Pointer.NULL, buffer, size); + if (error == WinError.ERROR_BUFFER_OVERFLOW) { + buffer = new Memory(size.getValue()); + error = INSTANCE.GetAdaptersAddresses(AF_UNSPEC, flags, Pointer.NULL, buffer, size); + if (error != WinError.ERROR_SUCCESS) { + throw new InitializationException(new Win32Exception(error)); + } + } + + IP_ADAPTER_ADDRESSES_LH result = new IP_ADAPTER_ADDRESSES_LH(buffer); + do { + // only interfaces with IfOperStatusUp + if (result.OperStatus == 1) { + IP_ADAPTER_DNS_SERVER_ADDRESS_XP dns = result.FirstDnsServerAddress; + while (dns != null) { + InetAddress address = null; + try { + address = dns.Address.toAddress(); + if (!address.isSiteLocalAddress()) { + addNameserver(new InetSocketAddress(address, SimpleResolver.DEFAULT_PORT)); + } else { + log.debug( + "Skipped site-local IPv6 server address {} on adapter index {}", + address, + result.IfIndex); + } + } catch (UnknownHostException e) { + log.warn("Invalid nameserver address on adapter index {}", result.IfIndex, e); + } + + dns = dns.Next; + } + + addSearchPath(result.DnsSuffix.toString()); + IP_ADAPTER_DNS_SUFFIX suffix = result.FirstDnsSuffix; + while (suffix != null) { + addSearchPath(String.valueOf(suffix._String)); + suffix = suffix.Next; + } + } + + result = result.Next; + } while (result != null); + } + } + + @Override + public void initialize() throws InitializationException { + inner.initialize(); + } + + @Override + public List servers() { + return inner.servers(); + } + + @Override + public List searchPaths() { + return inner.searchPaths(); + } + + @Override + public boolean isEnabled() { + return inner != null; + } +} diff --git a/src/main/resources/org/xbill/DNS/windows/DNSServer.properties b/src/main/resources/org/xbill/DNS/windows/DNSServer.properties deleted file mode 100644 index 25342f97b..000000000 --- a/src/main/resources/org/xbill/DNS/windows/DNSServer.properties +++ /dev/null @@ -1,4 +0,0 @@ -host_name=Host Name -primary_dns_suffix=Primary Dns Suffix -dns_suffix=DNS Suffix -dns_servers=DNS Servers diff --git a/src/main/resources/org/xbill/DNS/windows/DNSServer_de.properties b/src/main/resources/org/xbill/DNS/windows/DNSServer_de.properties deleted file mode 100644 index aa3f4a690..000000000 --- a/src/main/resources/org/xbill/DNS/windows/DNSServer_de.properties +++ /dev/null @@ -1,4 +0,0 @@ -host_name=Hostname -primary_dns_suffix=Prim\u00E4res DNS-Suffix -dns_suffix=DNS-Suffixsuchliste -dns_servers=DNS-Server diff --git a/src/main/resources/org/xbill/DNS/windows/DNSServer_fr.properties b/src/main/resources/org/xbill/DNS/windows/DNSServer_fr.properties deleted file mode 100644 index 7c87a25b3..000000000 --- a/src/main/resources/org/xbill/DNS/windows/DNSServer_fr.properties +++ /dev/null @@ -1,4 +0,0 @@ -host_name=Nom de l'h\u00F4te -primary_dns_suffix=Suffixe DNS principal -dns_suffix=Suffixe DNS propre \u00E0 la connexion -dns_servers=Serveurs DNS diff --git a/src/main/resources/org/xbill/DNS/windows/DNSServer_ja.properties b/src/main/resources/org/xbill/DNS/windows/DNSServer_ja.properties deleted file mode 100644 index f87316448..000000000 --- a/src/main/resources/org/xbill/DNS/windows/DNSServer_ja.properties +++ /dev/null @@ -1,4 +0,0 @@ -host_name=\u30db\u30b9\u30c8\u540d -primary_dns_suffix=\u30d7\u30e9\u30a4\u30de\u30ea DNS \u30b5\u30d5\u30a3\u30c3\u30af\u30b9 -dns_suffix=DNS \u30b5\u30d5\u30a3\u30c3\u30af\u30b9 -dns_servers=DNS \u30b5\u30fc\u30d0\u30fc diff --git a/src/main/resources/org/xbill/DNS/windows/DNSServer_pl.properties b/src/main/resources/org/xbill/DNS/windows/DNSServer_pl.properties deleted file mode 100644 index eab57743b..000000000 --- a/src/main/resources/org/xbill/DNS/windows/DNSServer_pl.properties +++ /dev/null @@ -1,4 +0,0 @@ -host_name=Nazwa hosta -primary_dns_suffix=Sufiks podstawowej domeny DNS -dns_suffix=Sufiks DNS konkretnego po\u0142\u0105czenia -dns_servers=Serwery DNS diff --git a/src/test/java/org/xbill/DNS/AddressTest.java b/src/test/java/org/xbill/DNS/AddressTest.java index f894fcf49..8e7e91222 100644 --- a/src/test/java/org/xbill/DNS/AddressTest.java +++ b/src/test/java/org/xbill/DNS/AddressTest.java @@ -51,6 +51,7 @@ import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; +import org.mockito.stubbing.Answer; class AddressTest { @Test @@ -326,7 +327,15 @@ void getByName() throws IOException { Section.ANSWER); Resolver mockResolver = Mockito.mock(Resolver.class); - when(mockResolver.send(ArgumentMatchers.any(Message.class))).thenReturn(aMessage); + when(mockResolver.send(ArgumentMatchers.any(Message.class))) + .thenAnswer( + (Answer) + invocation -> { + Message query = invocation.getArgument(0); + Message answer = aMessage.clone(); + answer.addRecord(query.getQuestion(), Section.QUESTION); + return answer; + }); Lookup.setDefaultResolver(mockResolver); out = Address.getByName("a.root-servers.net"); @@ -341,7 +350,15 @@ void getByName_invalid() throws IOException { Message m = new Message(); m.getHeader().setRcode(Rcode.NXDOMAIN); Resolver mockResolver = Mockito.mock(Resolver.class); - when(mockResolver.send(ArgumentMatchers.any(Message.class))).thenReturn(m); + when(mockResolver.send(ArgumentMatchers.any(Message.class))) + .thenAnswer( + (Answer) + invocation -> { + Message query = invocation.getArgument(0); + Message answer = m.clone(); + answer.addRecord(query.getQuestion(), Section.QUESTION); + return answer; + }); Lookup.setDefaultResolver(mockResolver); assertThrows(UnknownHostException.class, () -> Address.getByName("example.invalid")); // reset resolver @@ -401,7 +418,15 @@ void getAllByName_invalid() throws IOException { Message m = new Message(); m.getHeader().setRcode(Rcode.NXDOMAIN); Resolver mockResolver = Mockito.mock(Resolver.class); - when(mockResolver.send(ArgumentMatchers.any(Message.class))).thenReturn(m); + when(mockResolver.send(ArgumentMatchers.any(Message.class))) + .thenAnswer( + (Answer) + invocation -> { + Message query = invocation.getArgument(0); + Message answer = m.clone(); + answer.addRecord(query.getQuestion(), Section.QUESTION); + return answer; + }); Lookup.setDefaultResolver(mockResolver); assertThrows(UnknownHostException.class, () -> Address.getAllByName("example.invalid")); @@ -427,7 +452,15 @@ void getHostName() throws IOException { ptrMessage.addRecord(Record.newRecord(aRootServerPtr, Type.PTR, DClass.IN), Section.QUESTION); ptrMessage.addRecord(new PTRRecord(aRootServerPtr, DClass.IN, 60, aRootServer), Section.ANSWER); Resolver mockResolver = Mockito.mock(Resolver.class); - when(mockResolver.send(any(Message.class))).thenReturn(ptrMessage); + when(mockResolver.send(any(Message.class))) + .thenAnswer( + (Answer) + invocation -> { + Message query = invocation.getArgument(0); + Message answer = ptrMessage.clone(); + answer.addRecord(query.getQuestion(), Section.QUESTION); + return answer; + }); Lookup.setDefaultResolver(mockResolver); String out = Address.getHostName(InetAddress.getByName("198.41.0.4")); @@ -439,7 +472,15 @@ void getHostName() throws IOException { Record.newRecord(Name.fromString("1.1.168.192.in-addr.arpa."), Type.PTR, DClass.IN), Section.QUESTION); mockResolver = Mockito.mock(Resolver.class); - when(mockResolver.send(any())).thenReturn(ptrMessage2); + when(mockResolver.send(any())) + .thenAnswer( + (Answer) + invocation -> { + Message query = invocation.getArgument(0); + Message answer = ptrMessage2.clone(); + answer.addRecord(query.getQuestion(), Section.QUESTION); + return answer; + }); Lookup.setDefaultResolver(mockResolver); assertThrows( UnknownHostException.class, diff --git a/src/test/java/org/xbill/DNS/ResolverConfigTest.java b/src/test/java/org/xbill/DNS/ResolverConfigTest.java index 4a5693b08..48cc009da 100644 --- a/src/test/java/org/xbill/DNS/ResolverConfigTest.java +++ b/src/test/java/org/xbill/DNS/ResolverConfigTest.java @@ -2,82 +2,185 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assumptions.assumeFalse; +import static org.xbill.DNS.config.PropertyResolverConfigProvider.DNS_NDOTS_PROP; +import static org.xbill.DNS.config.PropertyResolverConfigProvider.DNS_SEARCH_PROP; +import static org.xbill.DNS.config.PropertyResolverConfigProvider.DNS_SERVER_PROP; +import java.io.IOException; +import java.io.InputStream; +import java.net.InetSocketAddress; import java.util.Arrays; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledOnOs; import org.junit.jupiter.api.condition.EnabledOnOs; import org.junit.jupiter.api.condition.OS; +import org.xbill.DNS.config.InitializationException; +import org.xbill.DNS.config.JndiContextResolverConfigProvider; +import org.xbill.DNS.config.PropertyResolverConfigProvider; +import org.xbill.DNS.config.ResolvConfResolverConfigProvider; +import org.xbill.DNS.config.SunJvmResolverConfigProvider; +import org.xbill.DNS.config.WindowsResolverConfigProvider; class ResolverConfigTest { - - @AfterEach - void tearDown() { - ResolverConfig.refresh(); - } - @Test - void findProperty_Success() { - String[] dnsServers = {"server1", "server2"}; + void properties() { + String[] dnsServers = {"192.168.1.1", "192.168.1.2", "192.168.1.1"}; // intentionally adding duplicate search entries for testing String[] dnsSearch = {"dnsjava.org", "example.com", "dnsjava.org"}; Name[] searchPath = Arrays.stream(dnsSearch).map(s -> Name.fromConstantString(s + ".")).toArray(Name[]::new); - System.setProperty(ResolverConfig.DNS_SERVER_PROP, String.join(",", dnsServers)); - System.setProperty(ResolverConfig.DNS_SEARCH_PROP, String.join(",", dnsSearch)); + System.setProperty(DNS_SERVER_PROP, String.join(",", dnsServers)); + System.setProperty(DNS_SEARCH_PROP, String.join(",", dnsSearch)); + System.setProperty(DNS_NDOTS_PROP, String.valueOf(5)); try { - ResolverConfig.refresh(); - ResolverConfig rc = ResolverConfig.getCurrentConfig(); - assertTrue(rc.findProperty()); - assertEquals("server1", rc.server()); - assertEquals(2, rc.servers().length); + PropertyResolverConfigProvider rc = new PropertyResolverConfigProvider(); + assertTrue(rc.isEnabled()); + rc.initialize(); + + assertEquals(2, rc.servers().size()); + assertEquals(dnsServers[0], rc.servers().get(0).getAddress().getHostAddress()); + // any duplicate suffixes should be excluded - assertEquals(2, rc.searchPath().length); - assertEquals(searchPath[0], rc.searchPath()[0]); - assertEquals(searchPath[1], rc.searchPath()[1]); + assertEquals(2, rc.searchPaths().size()); + assertEquals(searchPath[0], rc.searchPaths().get(0)); + assertEquals(searchPath[1], rc.searchPaths().get(1)); + + assertEquals(5, rc.ndots()); + } finally { + System.clearProperty(DNS_SERVER_PROP); + System.clearProperty(DNS_SEARCH_PROP); + System.clearProperty(DNS_NDOTS_PROP); + } + } + + @Test + void propertiesWithPort() { + String[] dnsServers = { + "192.168.1.1", "192.168.1.1:54", "::1", "0:0:0:0:0:0:0:1", "[::1]:54", "[::2]" + }; + System.setProperty(DNS_SERVER_PROP, String.join(",", dnsServers)); + try { + PropertyResolverConfigProvider rc = new PropertyResolverConfigProvider(); + rc.initialize(); + + assertEquals(5, rc.servers().size()); + assertEquals("192.168.1.1", rc.servers().get(0).getAddress().getHostAddress()); + assertEquals(SimpleResolver.DEFAULT_PORT, rc.servers().get(0).getPort()); + + assertEquals("192.168.1.1", rc.servers().get(1).getAddress().getHostAddress()); + assertEquals(54, rc.servers().get(1).getPort()); + + assertEquals("0:0:0:0:0:0:0:1", rc.servers().get(2).getAddress().getHostAddress()); + assertEquals(SimpleResolver.DEFAULT_PORT, rc.servers().get(2).getPort()); + + assertEquals("0:0:0:0:0:0:0:1", rc.servers().get(3).getAddress().getHostAddress()); + assertEquals(54, rc.servers().get(3).getPort()); + + assertEquals("0:0:0:0:0:0:0:2", rc.servers().get(4).getAddress().getHostAddress()); + assertEquals(53, rc.servers().get(4).getPort()); } finally { - System.clearProperty(ResolverConfig.DNS_SERVER_PROP); - System.clearProperty(ResolverConfig.DNS_SEARCH_PROP); + System.clearProperty(DNS_SERVER_PROP); } } @Test - @EnabledOnOs({OS.WINDOWS}) - void findNT_Windows() { - assertTrue(ResolverConfig.getCurrentConfig().findWin()); + @EnabledOnOs(OS.WINDOWS) + void resolvConfDisabledOnWindows() { + ResolvConfResolverConfigProvider rc = new ResolvConfResolverConfigProvider(); + assertFalse(rc.isEnabled()); + } + + @Test + @DisabledOnOs(OS.WINDOWS) + void resolvConfEnabledOnUnix() { + ResolvConfResolverConfigProvider rc = new ResolvConfResolverConfigProvider(); + assertTrue(rc.isEnabled()); + } + + @Test + @EnabledOnOs(OS.WINDOWS) + void windowsEnabledOnWindows() { + WindowsResolverConfigProvider rc = new WindowsResolverConfigProvider(); + assertTrue(rc.isEnabled()); + } + + @Test + @DisabledOnOs(OS.WINDOWS) + void windowsDisabledOnUnix() { + WindowsResolverConfigProvider rc = new WindowsResolverConfigProvider(); + assertFalse(rc.isEnabled()); + } + + @Test + void resolvConf() { + ResolvConfResolverConfigProvider rc = + new ResolvConfResolverConfigProvider() { + @Override + public void initialize() { + try { + try (InputStream in = + ResolverConfigTest.class.getResourceAsStream("/test_loaded_resolv.conf")) { + parseResolvConf(in); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + }; + + rc.initialize(); + assertEquals(1, rc.servers().size()); + assertEquals(new InetSocketAddress("192.168.1.1", 53), rc.servers().get(0)); + assertEquals(2, rc.searchPaths().size()); + assertFalse(rc.searchPaths().contains(Name.fromConstantString("domain.com."))); + assertEquals(5, rc.ndots()); } @Test - @DisabledOnOs({OS.WINDOWS}) - void findNT_NotWindows() { - assertFalse(ResolverConfig.getCurrentConfig().findWin()); + void jndi() { + JndiContextResolverConfigProvider rc = new JndiContextResolverConfigProvider(); + assertTrue(rc.isEnabled()); + rc.initialize(); } @Test - @DisabledOnOs({OS.WINDOWS}) - void findUnix() { - assertTrue(ResolverConfig.getCurrentConfig().findUnix()); + void sunJvmThrowsIfNotInitialized() { + SunJvmResolverConfigProvider rc = new SunJvmResolverConfigProvider(); + assertThrows(IllegalStateException.class, rc::servers); } @Test - void resolvConfLoaded() { - assertTrue( - ResolverConfig.getCurrentConfig() - .findResolvConf( - ResolverConfigTest.class.getResourceAsStream("/test_loaded_resolv.conf"))); - assertEquals(5, ResolverConfig.getCurrentConfig().ndots()); + void sunJvm() throws InitializationException { + SunJvmResolverConfigProvider rc = new SunJvmResolverConfigProvider(); + assertTrue(rc.isEnabled()); + rc.initialize(); } @Test - void findNetware() { - assumeFalse(ResolverConfig.getCurrentConfig().findNetware()); + void sunJvmServersEqualsJndi() throws InitializationException { + SunJvmResolverConfigProvider sun = new SunJvmResolverConfigProvider(); + sun.initialize(); + JndiContextResolverConfigProvider jndi = new JndiContextResolverConfigProvider(); + jndi.initialize(); + assertEquals(sun.servers(), jndi.servers()); } @Test - void findAndroid() { - assumeFalse(ResolverConfig.getCurrentConfig().findAndroid()); + @EnabledOnOs(OS.WINDOWS) + void windowsServersContainedInJndi() throws InitializationException { + JndiContextResolverConfigProvider jndi = new JndiContextResolverConfigProvider(); + jndi.initialize(); + WindowsResolverConfigProvider win = new WindowsResolverConfigProvider(); + win.initialize(); + + // the servers returned via Windows API must be in the JNDI list, but not necessarily the other + // way round + for (InetSocketAddress winServer : win.servers()) { + assertTrue( + jndi.servers().contains(winServer), + winServer + " not found in JNDI, " + win.servers() + "; " + jndi.servers()); + } } } diff --git a/src/test/resources/simplelogger.properties b/src/test/resources/simplelogger.properties new file mode 100644 index 000000000..beb56b2e1 --- /dev/null +++ b/src/test/resources/simplelogger.properties @@ -0,0 +1 @@ +org.slf4j.simpleLogger.defaultLogLevel=debug