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