--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/make/lib/Lib-jdk.dns.client.gmk Thu Oct 31 16:16:21 2019 +0000
@@ -0,0 +1,54 @@
+#
+# Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Oracle designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Oracle in the LICENSE file that accompanied this code.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+
+include LibCommon.gmk
+
+################################################################################
+# Create the resolver library
+
+$(eval $(call SetupJdkLibrary, BUILD_LIBRESOLVER, \
+ NAME := resolver, \
+ OPTIMIZATION := LOW, \
+ CFLAGS := $(CFLAGS_JDKLIB), \
+ DISABLED_WARNINGS_gcc := format-nonliteral unused-function, \
+ DISABLED_WARNINGS_clang := parentheses-equality constant-logical-operand \
+ format-nonliteral undef, \
+ DISABLED_WARNINGS_microsoft := 4244 4047 4133 4996, \
+ DISABLED_WARNINGS_solstudio := E_ARG_INCOMPATIBLE_WITH_ARG_L, \
+ LDFLAGS := $(LDFLAGS_JDKLIB) \
+ $(call SET_SHARED_LIBRARY_ORIGIN), \
+ LDFLAGS_windows := -delayload:secur32.dll -delayload:iphlpapi.dll, \
+ LIBS_unix := -ljvm -ljava, \
+ LIBS_linux := $(LIBDL) -lpthread, \
+ LIBS_solaris := -lnsl -lsocket $(LIBDL), \
+ LIBS_aix := $(LIBDL),\
+ LIBS_windows := ws2_32.lib jvm.lib secur32.lib iphlpapi.lib winhttp.lib \
+ delayimp.lib $(WIN_JAVA_LIB) advapi32.lib, \
+ LIBS_macosx := -framework CoreFoundation -framework CoreServices, \
+))
+
+$(BUILD_LIBRESOLVER): $(call FindLib, java.base, java)
+
+TARGETS += $(BUILD_LIBRESOLVER)
--- a/src/java.base/share/classes/java/net/InetAddress.java Thu Oct 31 11:32:07 2019 +0000
+++ b/src/java.base/share/classes/java/net/InetAddress.java Thu Oct 31 16:16:21 2019 +0000
@@ -41,11 +41,13 @@
import java.io.ObjectOutputStream;
import java.io.ObjectOutputStream.PutField;
import java.lang.annotation.Native;
+import java.util.ServiceLoader;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.atomic.AtomicLong;
import java.util.Arrays;
+import jdk.internal.misc.VM;
import jdk.internal.access.JavaNetInetAddressAccess;
import jdk.internal.access.SharedSecrets;
@@ -292,6 +294,8 @@
/* Used to store the name service provider */
private static transient NameService nameService;
+ private static transient NameService defaultNameService;
+
/**
* Used to store the best available hostname.
* Lazily initialized via a data race; safe because Strings are immutable.
@@ -336,6 +340,27 @@
init();
}
+ private static NameService nameService() {
+ if (nameService != null) {
+ return nameService;
+ }
+ if (VM.isBooted()) {
+ synchronized (NameService.class) {
+ if (nameService != null) {
+ return nameService;
+ }
+ var nameService = ServiceLoader.load(NameService.class)
+ .findFirst()
+ .orElse(defaultNameService);
+ InetAddress.nameService = nameService;
+ return nameService;
+ }
+ } else {
+ return defaultNameService;
+ }
+ }
+
+
/**
* Constructor for the Socket.accept() method.
* This creates an empty InetAddress, which is filled in by
@@ -652,7 +677,7 @@
String host = null;
try {
// first lookup the hostname
- host = nameService.getHostByAddr(addr.getAddress());
+ host = nameService().getHostByAddr(addr.getAddress());
/* check to see if calling code is allowed to know
* the hostname for this IP address, ie, connect to the host
@@ -885,7 +910,7 @@
*
* @since 9
*/
- private interface NameService {
+ public interface NameService {
/**
* Lookup a host mapping by name. Retrieve the IP addresses
@@ -1111,8 +1136,8 @@
impl = InetAddressImplFactory.create();
// create name service
- nameService = createNameService();
- }
+ defaultNameService = createNameService();
+ }
/**
* Create an instance of the NameService interface based on
@@ -1491,7 +1516,7 @@
UnknownHostException ex = null;
try {
- addresses = nameService.lookupAllHostAddr(host);
+ addresses = nameService().lookupAllHostAddr(host);
} catch (UnknownHostException uhe) {
if (host.equalsIgnoreCase("localhost")) {
addresses = new InetAddress[] { impl.loopbackAddress() };
--- a/src/java.base/share/classes/module-info.java Thu Oct 31 11:32:07 2019 +0000
+++ b/src/java.base/share/classes/module-info.java Thu Oct 31 16:16:21 2019 +0000
@@ -153,7 +153,8 @@
jdk.jlink;
exports jdk.internal.loader to
java.instrument,
- java.logging;
+ java.logging,
+ jdk.dns.client;
exports jdk.internal.jmod to
jdk.compiler,
jdk.jlink;
@@ -241,7 +242,8 @@
exports sun.net.util to
java.desktop,
jdk.jconsole,
- java.net.http;
+ java.net.http,
+ jdk.dns.client;
exports sun.net.www to
java.net.http,
jdk.jartool;
@@ -276,7 +278,8 @@
java.smartcardio,
jdk.crypto.ec,
jdk.crypto.cryptoki,
- jdk.naming.dns;
+ jdk.naming.dns,
+ jdk.dns.client;
exports sun.security.pkcs to
jdk.crypto.ec,
jdk.jartool;
@@ -359,6 +362,7 @@
uses java.util.spi.TimeZoneNameProvider;
uses java.util.spi.ToolProvider;
uses javax.security.auth.spi.LoginModule;
+ uses java.net.InetAddress.NameService;
// JDK-internal service types
--- a/src/java.base/share/lib/security/default.policy Thu Oct 31 11:32:07 2019 +0000
+++ b/src/java.base/share/lib/security/default.policy Thu Oct 31 16:16:21 2019 +0000
@@ -31,6 +31,10 @@
permission java.net.NetPermission "getProxySelector";
};
+grant codeBase "jrt:/jdk.dns.client" {
+ permission java.security.AllPermission;
+};
+
grant codeBase "jrt:/java.scripting" {
permission java.security.AllPermission;
};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.dns.client/share/classes/jdk/dns/client/AddressFamily.java Thu Oct 31 16:16:21 2019 +0000
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.dns.client;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.ProtocolFamily;
+import java.net.StandardProtocolFamily;
+
+public enum AddressFamily {
+ IPv4,
+ IPv6,
+ ANY;
+
+ public static AddressFamily fromProtocolFamily(ProtocolFamily protocolFamily) {
+ if (protocolFamily == StandardProtocolFamily.INET) {
+ return IPv4;
+ } else if (protocolFamily == StandardProtocolFamily.INET6) {
+ return IPv6;
+ } else {
+ return ANY;
+ }
+
+ }
+
+ public boolean sameFamily(InetAddress inetAddress) {
+ switch (this) {
+ case IPv4:
+ return inetAddress instanceof Inet4Address;
+ case IPv6:
+ return inetAddress instanceof Inet6Address;
+ case ANY:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public static AddressFamily fromInetAddress(InetAddress addr) {
+ return addr instanceof Inet4Address ? IPv4 : addr instanceof Inet6Address ? IPv6 : ANY;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.dns.client/share/classes/jdk/dns/client/NetworkNamesResolver.java Thu Oct 31 16:16:21 2019 +0000
@@ -0,0 +1,382 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.dns.client;
+
+import jdk.dns.client.ex.DnsResolverException;
+import jdk.dns.client.internal.DnsName;
+import jdk.dns.client.internal.HostsFileResolver;
+import jdk.dns.client.internal.Resolver;
+import jdk.dns.client.internal.ResourceClassType;
+import jdk.dns.client.internal.ResourceRecord;
+import jdk.dns.client.internal.ResourceRecords;
+import jdk.dns.conf.DnsResolverConfiguration;
+import sun.net.util.IPAddressUtil;
+
+import java.net.InetAddress;
+import java.net.ProtocolFamily;
+import java.net.UnknownHostException;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class NetworkNamesResolver {
+
+ private final AddressFamily addressFamily;
+
+ public static NetworkNamesResolver open() throws UnknownHostException {
+ return new NetworkNamesResolver(AddressFamily.ANY);
+ }
+
+ public static NetworkNamesResolver open(ProtocolFamily protocolFamily) throws UnknownHostException {
+ return new NetworkNamesResolver(AddressFamily.fromProtocolFamily(protocolFamily));
+ }
+
+ private NetworkNamesResolver(AddressFamily addressFamily) throws UnknownHostException {
+ this.addressFamily = addressFamily;
+ this.dnsResolver = new Resolver(dnsResolverConfiguration.nameservers(), 1000, 4);
+ }
+
+
+ /**
+ * Lookup the IP address of a host.
+ * The family of required address needed can be specified with {@code addressFamily} parameter.
+ *
+ * @param hostname the specified host name
+ * @return first IP address that matches requested {@code addressFamily}
+ * @throws UnknownHostException if no IP address found for the specified host name and the specified address family
+ */
+ public InetAddress lookupHostAddr(String hostname) throws UnknownHostException {
+
+ // First try hosts file
+ // TODO: Add nsswitch.conf to select proper order
+ try {
+ return hostsFileResolver.getHostAddress(hostname, addressFamily);
+ } catch (UnknownHostException uhe) {
+ if (DEBUG) {
+ System.err.printf("Hosts file doesn't know '%s' host with '%s' address family%n",
+ hostname, addressFamily.toString());
+ }
+ }
+
+ // If no luck - try to ask name servers
+ try {
+ var results = lookup(hostname, addressFamily, true, true, 0);
+ if (results.isEmpty()) {
+ throw new UnknownHostException(hostname);
+ }
+ return results.get(0);
+ } catch (DnsResolverException e) {
+ UnknownHostException uhe = new UnknownHostException(hostname);
+ uhe.initCause(e);
+ throw uhe;
+ }
+ }
+
+ /**
+ * <p>Lookup a host mapping by name. Retrieve the IP addresses
+ * associated with a host.
+ *
+ * @param hostname the specified hostname
+ * @return array of IP addresses for the requested host
+ * @throws UnknownHostException if no IP address for the {@code hostname} could be found
+ */
+ public List<InetAddress> lookupAllHostAddr(String hostname) throws UnknownHostException {
+ // First try hosts file
+ // TODO: Add nsswitch.conf ReloadTracker and parser to select proper order
+ try {
+ return List.of(hostsFileResolver.getHostAddress(hostname));
+ } catch (UnknownHostException uhe) {
+ if (DEBUG) {
+ System.err.printf("Resolver API: Hosts file doesn't know '%s' host%n", hostname);
+ }
+ }
+ try {
+ var results = lookup(hostname, AddressFamily.ANY, false, true, 0);
+ if (results.isEmpty()) {
+ throw new UnknownHostException(hostname + " unknown host name");
+ }
+ return results;
+ } catch (DnsResolverException e) {
+ UnknownHostException uhe = new UnknownHostException(hostname);
+ uhe.initCause(e);
+ throw uhe;
+ }
+ }
+
+ /**
+ * Lookup the host name corresponding to the IP address provided.
+ *
+ * @param address the specified IP address
+ * @return {@code String} representing the host name
+ * @throws UnknownHostException if no host found for the specified IP address
+ */
+ public String getHostByAddr(InetAddress address) throws UnknownHostException {
+ var results = getAllHostsByAddr(address);
+ return results.get(0);
+ }
+
+ /**
+ * Lookup all known host names which correspond to the IP address provided
+ *
+ * @param address the specified IP address
+ * @return array of {@code String} representing the host names
+ * @throws UnknownHostException if no host found for the specified IP address
+ */
+ public List<String> getAllHostsByAddr(InetAddress address) throws UnknownHostException {
+ // First try hosts file
+ // TODO: Add nsswitch.conf to select proper order
+ try {
+ return List.of(hostsFileResolver.getByAddress(address));
+ } catch (UnknownHostException uhe) {
+ if (DEBUG) {
+ System.err.printf("Resolver API: No host in hosts file with %s address%n", address);
+ }
+ }
+ try {
+ var literalIP = addressToLiteralIP(address);
+ var family = AddressFamily.fromInetAddress(address);
+ var results = rlookup(literalIP, family);
+ if (results.isEmpty()) {
+ throw new UnknownHostException();
+ }
+ // remove trailing dot
+ return results.stream()
+ .map(host -> host.endsWith(".") ? host.substring(0, host.length() - 1) : host)
+ .collect(Collectors.toList());
+ } catch (DnsResolverException dre) {
+ UnknownHostException uhe = new UnknownHostException();
+ uhe.initCause(dre);
+ throw uhe;
+ }
+ }
+
+ private Set<String> prepareDomainsSearchList() {
+ var domainsToSearch = new LinkedHashSet<String>();
+ var domain = dnsResolverConfiguration.domain();
+ // First will try the domain
+ if (!domain.isBlank()) {
+ domainsToSearch.add(domain);
+ }
+ // Then will iterate over search list
+ domainsToSearch.addAll(dnsResolverConfiguration.searchlist());
+ if (DEBUG) {
+ System.out.printf("Domains search list:%s%n", domainsToSearch);
+ }
+ return domainsToSearch;
+ }
+
+ private List<InetAddress> lookup(String host, AddressFamily addressFamily, boolean oneIsEnough, boolean checkDomains, int depth) throws UnknownHostException, DnsResolverException {
+
+ if (DEBUG) {
+ System.out.printf("Resolver API: internal lookup call - %s%n", host);
+ }
+
+ // Will remain the same during DNS queries execution
+ ResourceClassType ct = ResourceClassType.fromAddressFamily(addressFamily);
+ ResourceRecords rrs = null;
+
+ // First try to get the resource records with the requested DNS host name if it is FQDN
+ if (host.contains(".")) {
+ rrs = execAddrResolutionQuery(new DnsName(host), ct);
+ }
+ // Non-FQDN or not known host address and domains still can be checked
+ if ((rrs == null || rrs.answer.isEmpty()) && checkDomains) {
+ for (String domain : prepareDomainsSearchList()) {
+ String hostWithSuffix = host + "." + domain;
+ if (DEBUG) {
+ System.err.printf("Resolver API: Trying to lookup:'%s'%n", hostWithSuffix);
+ }
+ rrs = execAddrResolutionQuery(new DnsName(hostWithSuffix), ct);
+ if (rrs != null && !rrs.answer.isEmpty()) {
+ if (DEBUG) {
+ System.err.printf("Resolver API: Found host in '%s' domain%n", domain);
+ }
+ break;
+ }
+ }
+ }
+
+ // If no answers then host is not known in registered domain
+ if (rrs == null || rrs.answer.isEmpty()) {
+ throw new UnknownHostException(host);
+ }
+
+ // Parse answers
+ List<InetAddress> results = new ArrayList<>();
+ for (int i = 0; i < rrs.answer.size(); i++) {
+ ResourceRecord rr = rrs.answer.elementAt(i);
+ int answerType = rr.getType();
+ String recordData = rr.getRdata().toString();
+ if (DEBUG) {
+ System.err.printf("Resolver API: Got %s type: %s%n", ResourceRecord.getTypeName(rr.getType()), rr.getRdata());
+ }
+ if (answerType == ResourceRecord.TYPE_CNAME) {
+ // We've got CNAME entry - issue another request to resolve the canonical host name
+ if (depth > MAX_CNAME_RESOLUTION_DEPTH) {
+ throw new UnknownHostException(host);
+ }
+ List<InetAddress> cnameResults = lookup(recordData, addressFamily, oneIsEnough, false, depth + 1);
+ if (oneIsEnough) {
+ return cnameResults;
+ } else {
+ results.addAll(cnameResults);
+ }
+ } else {
+ byte[] addrBytes;
+ if (answerType == ResourceRecord.TYPE_A && (addressFamily == AddressFamily.IPv4 || addressFamily == AddressFamily.ANY)) {
+ addrBytes = IPAddressUtil.textToNumericFormatV4(recordData);
+ if (addrBytes == null) {
+ if (DEBUG) {
+ System.err.println("Incorrect A resource record answer");
+ }
+ return Collections.emptyList();
+ }
+ } else if (answerType == ResourceRecord.TYPE_AAAA && (addressFamily == AddressFamily.IPv6 || addressFamily == AddressFamily.ANY)) {
+ addrBytes = IPAddressUtil.textToNumericFormatV6(recordData);
+ if (addrBytes == null) {
+ if (DEBUG) {
+ System.err.println("Incorrect AAAA resource record answer");
+ }
+ return Collections.emptyList();
+ }
+ } else {
+ continue;
+ }
+ // IPAddressUtil.textToNumeric can return null
+ if (addrBytes == null) {
+ throw new RuntimeException("Internal error");
+ }
+ InetAddress address = InetAddress.getByAddress(host, addrBytes);
+ results.add(address);
+ if (oneIsEnough) {
+ break;
+ }
+ }
+ }
+
+ return results;
+ }
+
+ // Can return null if query failed
+ private ResourceRecords execAddrResolutionQuery(DnsName name, ResourceClassType ct) {
+ // Not supporting authoritative requests [for now?]
+ try {
+ ResourceRecords rrs = dnsResolver.query(name, ct.getRecordClass(), ct.getRecordType(),
+ true, false);
+ return rrs.hasAddressOrAlias() ? rrs : null;
+ } catch (DnsResolverException e) {
+ if (DEBUG) {
+ System.err.println("Query failed:" + e.getMessage());
+ e.printStackTrace();
+ }
+ return null;
+ }
+ }
+
+ private List<String> rlookup(String literalIP, AddressFamily family) throws DnsResolverException {
+ switch (family) {
+ case IPv4:
+ return ipv4rlookup(literalIP);
+ case IPv6:
+ return ipv6rlookup(literalIP);
+ default:
+ throw new RuntimeException("Only IPv4 and IPv6 addresses are supported");
+ }
+ }
+
+ private List<String> ipv4rlookup(String literalIP) throws DnsResolverException {
+ var request = literalIP + "IN-ADDR.ARPA.";
+ var hostNames = new ArrayList<String>();
+ var name = new DnsName(request);
+ var rrs = dnsResolver.query(name, ResourceRecord.CLASS_INTERNET,
+ ResourceRecord.TYPE_PTR, true, false);
+ for (int i = 0; i < rrs.answer.size(); i++) {
+ ResourceRecord rr = rrs.answer.elementAt(i);
+ hostNames.add(rr.getRdata().toString());
+ }
+ return hostNames;
+ }
+
+ private List<String> ipv6rlookup(String literalIP) throws DnsResolverException {
+ List<String> hostNames = new ArrayList<>();
+ DnsName name = new DnsName(literalIP + "IP6.ARPA.");
+ ResourceRecords rrs = dnsResolver.query(name, ResourceRecord.CLASS_INTERNET,
+ ResourceRecord.TYPE_PTR, true, false);
+ /**
+ * Because RFC 3152 changed the root domain name for reverse
+ * lookups from IP6.INT. to IP6.ARPA., we need to check
+ * both. I.E. first the new one, IP6.ARPA, then if it fails
+ * the older one, IP6.INT
+ */
+ if (rrs.answer.isEmpty()) {
+ name = new DnsName(literalIP + "IP6.INT.");
+ rrs = dnsResolver.query(name, ResourceRecord.CLASS_INTERNET,
+ ResourceRecord.TYPE_PTR, true, false);
+ }
+ for (int i = 0; i < rrs.answer.size(); i++) {
+ ResourceRecord rr = rrs.answer.elementAt(i);
+ hostNames.add(rr.getRdata().toString());
+ }
+ return hostNames;
+ }
+
+ private static String addressToLiteralIP(InetAddress address) {
+ byte[] bytes = address.getAddress();
+ StringBuilder addressBuff = new StringBuilder();
+ // IPv4 address
+ if (bytes.length == 4) {
+ for (int i = bytes.length - 1; i >= 0; i--) {
+ addressBuff.append(bytes[i] & 0xff);
+ addressBuff.append(".");
+ }
+ // IPv6 address
+ } else if (bytes.length == 16) {
+ for (int i = bytes.length - 1; i >= 0; i--) {
+ addressBuff.append(Integer.toHexString((bytes[i] & 0x0f)));
+ addressBuff.append(".");
+ addressBuff.append(Integer.toHexString((bytes[i] & 0xf0) >> 4));
+ addressBuff.append(".");
+ }
+ } else {
+ return null;
+ }
+ return addressBuff.toString();
+ }
+
+ private static DnsResolverConfiguration dnsResolverConfiguration = new DnsResolverConfiguration();
+ private static HostsFileResolver hostsFileResolver = new HostsFileResolver();
+
+ // TODO: Add system property with the sequence of resolution OR nsswitch.conf location to get the order:
+ // file, name
+ private final Resolver dnsResolver;
+ private static final boolean DEBUG = java.security.AccessController.doPrivileged(
+ (PrivilegedAction<Boolean>) () -> Boolean.getBoolean("jdk.dns.client.debug"));
+ private static final int MAX_CNAME_RESOLUTION_DEPTH = 4;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.dns.client/share/classes/jdk/dns/client/ex/DnsCommunicationException.java Thu Oct 31 16:16:21 2019 +0000
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.dns.client.ex;
+
+public class DnsCommunicationException extends DnsResolverException {
+ private static final long serialVersionUID = 5764739815069894318L;
+
+ public DnsCommunicationException(String message) {
+ super(message);
+ }
+
+ public DnsCommunicationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.dns.client/share/classes/jdk/dns/client/ex/DnsInvalidAttributeIdentifierException.java Thu Oct 31 16:16:21 2019 +0000
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.dns.client.ex;
+
+public class DnsInvalidAttributeIdentifierException extends DnsResolverException {
+ private static final long serialVersionUID = 745256513458941901L;
+
+ public DnsInvalidAttributeIdentifierException(String message) {
+ super(message);
+ }
+
+ public DnsInvalidAttributeIdentifierException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.dns.client/share/classes/jdk/dns/client/ex/DnsInvalidNameException.java Thu Oct 31 16:16:21 2019 +0000
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.dns.client.ex;
+
+public class DnsInvalidNameException extends DnsResolverException {
+ private static final long serialVersionUID = -6375885343819861145L;
+
+ public DnsInvalidNameException(String message) {
+ super(message);
+ }
+
+ public DnsInvalidNameException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.dns.client/share/classes/jdk/dns/client/ex/DnsNameNotFoundException.java Thu Oct 31 16:16:21 2019 +0000
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.dns.client.ex;
+
+public class DnsNameNotFoundException extends DnsResolverException {
+
+ private static final long serialVersionUID = 2646341064300322397L;
+
+ public DnsNameNotFoundException(String message) {
+ super(message);
+ }
+
+ public DnsNameNotFoundException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.dns.client/share/classes/jdk/dns/client/ex/DnsOperationNotSupportedException.java Thu Oct 31 16:16:21 2019 +0000
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.dns.client.ex;
+
+public class DnsOperationNotSupportedException extends DnsResolverException {
+ private static final long serialVersionUID = -8739097013731823223L;
+
+ public DnsOperationNotSupportedException(String message) {
+ super(message);
+ }
+
+ public DnsOperationNotSupportedException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.dns.client/share/classes/jdk/dns/client/ex/DnsResolverException.java Thu Oct 31 16:16:21 2019 +0000
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.dns.client.ex;
+
+public class DnsResolverException extends Exception {
+
+ private static final long serialVersionUID = -2403620877869567780L;
+
+ public DnsResolverException(String message) {
+ super(message);
+ }
+
+ public DnsResolverException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.dns.client/share/classes/jdk/dns/client/ex/DnsServiceUnavailableException.java Thu Oct 31 16:16:21 2019 +0000
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.dns.client.ex;
+
+public class DnsServiceUnavailableException extends DnsResolverException {
+ private static final long serialVersionUID = -4912814054659577232L;
+
+ public DnsServiceUnavailableException(String message) {
+ super(message);
+ }
+
+ public DnsServiceUnavailableException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/DnsClient.java Thu Oct 31 16:16:21 2019 +0000
@@ -0,0 +1,764 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.dns.client.internal;
+
+import jdk.dns.client.ex.DnsCommunicationException;
+import jdk.dns.client.ex.DnsNameNotFoundException;
+import jdk.dns.client.ex.DnsOperationNotSupportedException;
+import jdk.dns.client.ex.DnsResolverException;
+import jdk.dns.client.ex.DnsServiceUnavailableException;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.channels.DatagramChannel;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.locks.ReentrantLock;
+
+import sun.net.util.IPAddressUtil;
+import sun.security.jca.JCAUtil;
+
+// Some of this code began life as part of sun.javaos.net.DnsClient
+// originally by sritchie@eng 1/96. It was first hacked up for JNDI
+// use by caveh@eng 6/97.
+
+
+/**
+ * The DnsClient class performs DNS client operations in support of DnsContext.
+ */
+
+public class DnsClient {
+
+ // DNS packet header field offsets
+ private static final int IDENT_OFFSET = 0;
+ private static final int FLAGS_OFFSET = 2;
+ private static final int NUMQ_OFFSET = 4;
+ private static final int NUMANS_OFFSET = 6;
+ private static final int NUMAUTH_OFFSET = 8;
+ private static final int NUMADD_OFFSET = 10;
+ private static final int DNS_HDR_SIZE = 12;
+
+ // DNS response codes
+ private static final int NO_ERROR = 0;
+ private static final int FORMAT_ERROR = 1;
+ private static final int SERVER_FAILURE = 2;
+ private static final int NAME_ERROR = 3;
+ private static final int NOT_IMPL = 4;
+ private static final int REFUSED = 5;
+
+ private static final String[] rcodeDescription = {
+ "No error",
+ "DNS format error",
+ "DNS server failure",
+ "DNS name not found",
+ "DNS operation not supported",
+ "DNS service refused"
+ };
+
+ private static final int DEFAULT_PORT = 53;
+ private static final int TRANSACTION_ID_BOUND = 0x10000;
+ private List<InetAddress> servers;
+ private List<Integer> serverPorts;
+ private int timeout; // initial timeout on UDP and TCP queries in ms
+ private int retries; // number of UDP retries
+
+
+
+ private static final SecureRandom random;
+
+ static {
+ var pa = (PrivilegedAction<SecureRandom>) () -> JCAUtil.getSecureRandom();
+ random = System.getSecurityManager() == null ? pa.run()
+ : AccessController.doPrivileged(pa);
+ }
+
+ private static final DnsDatagramChannelFactory factory =
+ new DnsDatagramChannelFactory(random);
+
+ // Requests sent
+ private Map<Integer, ResourceRecord> reqs;
+
+ // Responses received
+ private Map<Integer, byte[]> resps;
+
+ //-------------------------------------------------------------------------
+
+ /*
+ * Each server is of the form "server[:port]". IPv6 literal host names
+ * include delimiting brackets.
+ * "timeout" is the initial timeout interval (in ms) for queries,
+ * and "retries" gives the number of retries per server.
+ */
+ public DnsClient(List<String> servers, int timeout, int retries)
+ throws UnknownHostException {
+ this.timeout = timeout;
+ this.retries = retries;
+ var serversList = new ArrayList<InetAddress>();
+ var serverPortsList = new ArrayList<Integer>();
+
+ for (String serverString:servers) {
+
+ // Is optional port given?
+ int colon = serverString.indexOf(':',
+ serverString.indexOf(']') + 1);
+
+ int serverPort = (colon < 0) ? DEFAULT_PORT
+ : Integer.parseInt(serverString.substring(colon + 1));
+ String server = (colon < 0)
+ ? serverString
+ : serverString.substring(0, colon);
+
+ var pa = (PrivilegedAction<byte[]>) () -> {
+ if (IPAddressUtil.isIPv4LiteralAddress(server)) {
+ return IPAddressUtil.textToNumericFormatV4(server);
+ } else if (IPAddressUtil.isIPv6LiteralAddress(server)) {
+ return IPAddressUtil.textToNumericFormatV6(server);
+ }
+ return null;
+ };
+ byte[] addr = System.getSecurityManager() == null ?
+ pa.run() : AccessController.doPrivileged(pa);
+ if (addr != null) {
+ serversList.add(InetAddress.getByAddress(server, addr));
+ serverPortsList.add(serverPort);
+ }
+ }
+ this.servers = Collections.unmodifiableList(serversList);
+ this.serverPorts = Collections.unmodifiableList(serverPortsList);
+ reqs = Collections.synchronizedMap(
+ new HashMap<>());
+ resps = Collections.synchronizedMap(new HashMap<>());
+ }
+
+ DatagramChannel getDatagramChannel() throws DnsResolverException {
+ try {
+ return factory.open();
+ } catch (java.net.SocketException e) {
+ throw new DnsResolverException("Can't create datagram channel", e);
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ protected void finalize() {
+ close();
+ }
+
+ // A lock to access the request and response queues in tandem.
+ private ReentrantLock queuesLock = new ReentrantLock();
+
+ public void close() {
+ queuesLock.lock();
+ try {
+ reqs.clear();
+ resps.clear();
+ } finally {
+ queuesLock.unlock();
+ }
+ }
+
+ /*
+ * If recursion is true, recursion is requested on the query.
+ * If auth is true, only authoritative responses are accepted; other
+ * responses throw NameNotFoundException.
+ */
+ ResourceRecords query(DnsName fqdn, int qclass, int qtype,
+ boolean recursion, boolean auth)
+ throws DnsResolverException {
+
+ int xid;
+ Packet pkt;
+ ResourceRecord collision;
+
+ do {
+ // Generate a random transaction ID
+ xid = random.nextInt(TRANSACTION_ID_BOUND);
+ pkt = makeQueryPacket(fqdn, xid, qclass, qtype, recursion);
+
+ // enqueue the outstanding request
+ collision = reqs.putIfAbsent(xid, new ResourceRecord(pkt.getData(),
+ pkt.length(), Header.HEADER_SIZE, true, false));
+
+ } while (collision != null);
+
+ Exception caughtException = null;
+ boolean[] doNotRetry = new boolean[servers.size()];
+
+ try {
+ //
+ // The UDP retry strategy is to try the 1st server, and then
+ // each server in order. If no answer, double the timeout
+ // and try each server again.
+ //
+ for (int retry = 0; retry < retries; retry++) {
+
+ // Try each name server.
+ for (int i = 0; i < servers.size(); i++) {
+ if (doNotRetry[i]) {
+ continue;
+ }
+
+ // send the request packet and wait for a response.
+ try {
+ if (DEBUG) {
+ dprint("SEND ID (" + (retry + 1) + "): " + xid);
+ }
+
+ byte[] msg = doUdpQuery(pkt, servers.get(i), serverPorts.get(i), retry, xid);
+ //
+ // If the matching response is not got within the
+ // given timeout, check if the response was enqueued
+ // by some other thread, if not proceed with the next
+ // server or retry.
+ //
+ if (msg == null) {
+ if (resps.size() > 0) {
+ msg = lookupResponse(xid);
+ }
+ if (msg == null) { // try next server or retry
+ continue;
+ }
+ }
+ Header hdr = new Header(msg, msg.length);
+
+ if (auth && !hdr.authoritative) {
+ caughtException = new DnsNameNotFoundException(
+ "DNS response not authoritative");
+ doNotRetry[i] = true;
+ continue;
+ }
+ if (hdr.truncated) { // message is truncated -- try TCP
+
+ // Try each server, starting with the one that just
+ // provided the truncated message.
+ int retryTimeout = (timeout * (1 << retry));
+ for (int j = 0; j < servers.size(); j++) {
+ int ij = (i + j) % servers.size();
+ if (doNotRetry[ij]) {
+ continue;
+ }
+ try {
+ Tcp tcp =
+ new Tcp(servers.get(ij), serverPorts.get(ij), retryTimeout);
+ byte[] msg2;
+ try {
+ msg2 = doTcpQuery(tcp, pkt);
+ } finally {
+ tcp.close();
+ }
+ Header hdr2 = new Header(msg2, msg2.length);
+ if (hdr2.query) {
+ throw new DnsCommunicationException(
+ "DNS error: expecting response");
+ }
+ checkResponseCode(hdr2);
+
+ if (!auth || hdr2.authoritative) {
+ // Got a valid response
+ hdr = hdr2;
+ msg = msg2;
+ break;
+ } else {
+ doNotRetry[ij] = true;
+ }
+ } catch (Exception e) {
+ // Try next server, or use UDP response
+ }
+ } // servers
+ }
+ return new ResourceRecords(msg, msg.length, hdr, false);
+
+ } catch (IOException e) {
+ if (DEBUG) {
+ dprint("Caught IOException:" + e);
+ }
+ if (caughtException == null) {
+ caughtException = e;
+ }
+ // Use reflection to allow pre-1.4 compilation.
+ // This won't be needed much longer.
+ if (e.getClass().getName().equals(
+ "java.net.PortUnreachableException")) {
+ doNotRetry[i] = true;
+ }
+ // doNotRetry set - needs to be added
+ } catch (DnsNameNotFoundException e) {
+ // This is authoritative, so return immediately
+ throw e;
+ } catch (DnsCommunicationException e) {
+ if (caughtException == null) {
+ caughtException = e;
+ }
+ } catch (DnsResolverException e) {
+ if (caughtException == null) {
+ caughtException = e;
+ }
+ doNotRetry[i] = true;
+ }
+ } // servers
+ } // retries
+
+ } finally {
+ reqs.remove(xid); // cleanup
+ }
+
+ if (caughtException instanceof DnsResolverException) {
+ throw (DnsResolverException) caughtException;
+ }
+ // A network timeout or other error occurred.
+ throw new DnsResolverException("DNS error", caughtException);
+ }
+
+ /**
+ * Tries to retrieve a UDP packet matching the given xid
+ * received within the timeout.
+ * If a packet with different xid is received, the received packet
+ * is enqueued with the corresponding xid in 'resps'.
+ */
+ private byte[] doUdpQuery(Packet pkt, InetAddress server,
+ int port, int retry, int xid)
+ throws IOException, DnsResolverException {
+
+ int minTimeout = 50; // msec after which there are no retries.
+
+
+ try (DatagramChannel dc = getDatagramChannel()) {
+ DatagramPacket opkt = new DatagramPacket(pkt.getData(), pkt.length(), server, port);
+ DatagramPacket ipkt = new DatagramPacket(new byte[8000], 8000);
+ // Packets may only be sent to or received from this server address
+ // TODO: Revisit
+ var pa = (PrivilegedAction<Void>) () -> {dc.socket().connect(server, port); return null;};
+ if (System.getSecurityManager() == null) {
+ pa.run();
+ } else {
+ AccessController.doPrivileged(pa);
+ }
+
+
+ int pktTimeout = (timeout * (1 << retry));
+ try {
+ dc.socket().send(opkt);
+
+ // timeout remaining after successive 'receive()'
+ int timeoutLeft = pktTimeout;
+ int cnt = 0;
+ do {
+ if (DEBUG) {
+ cnt++;
+ dprint("Trying RECEIVE(" +
+ cnt + ") retry(" + (retry + 1) +
+ ") for:" + xid + " sock-timeout:" +
+ timeoutLeft + " ms.");
+ }
+ dc.socket().setSoTimeout(timeoutLeft);
+ long start = System.currentTimeMillis();
+
+
+ byte[] data = ipkt.getData();
+ ByteBuffer bb = ByteBuffer.wrap(data);
+ dc.read(bb);
+ long end = System.currentTimeMillis();
+
+ if (isMatchResponse(data, xid)) {
+ return data;
+ }
+ timeoutLeft = pktTimeout - ((int) (end - start));
+ } while (timeoutLeft > minTimeout);
+
+ } finally {
+ dc.disconnect();
+ }
+ return null; // no matching packet received within the timeout
+ }
+ }
+
+ /*
+ * Sends a TCP query, and returns the first DNS message in the response.
+ */
+ private byte[] doTcpQuery(Tcp tcp, Packet pkt) throws IOException {
+
+ int len = pkt.length();
+ // Send 2-byte message length, then send message.
+ tcp.out.write(len >> 8);
+ tcp.out.write(len);
+ tcp.out.write(pkt.getData(), 0, len);
+ tcp.out.flush();
+
+ byte[] msg = continueTcpQuery(tcp);
+ if (msg == null) {
+ throw new IOException("DNS error: no response");
+ }
+ return msg;
+ }
+
+ /*
+ * Returns the next DNS message from the TCP socket, or null on EOF.
+ */
+ private byte[] continueTcpQuery(Tcp tcp) throws IOException {
+
+ int lenHi = tcp.read(); // high-order byte of response length
+ if (lenHi == -1) {
+ return null; // EOF
+ }
+ int lenLo = tcp.read(); // low-order byte of response length
+ if (lenLo == -1) {
+ throw new IOException("Corrupted DNS response: bad length");
+ }
+ int len = (lenHi << 8) | lenLo;
+ byte[] msg = new byte[len];
+ int pos = 0; // next unfilled position in msg
+ while (len > 0) {
+ int n = tcp.read(msg, pos, len);
+ if (n == -1) {
+ throw new IOException(
+ "Corrupted DNS response: too little data");
+ }
+ len -= n;
+ pos += n;
+ }
+ return msg;
+ }
+
+ private Packet makeQueryPacket(DnsName fqdn, int xid,
+ int qclass, int qtype, boolean recursion) {
+ int qnameLen = fqdn.getOctets();
+ int pktLen = DNS_HDR_SIZE + qnameLen + 4;
+ Packet pkt = new Packet(pktLen);
+
+ short flags = recursion ? Header.RD_BIT : 0;
+
+ pkt.putShort(xid, IDENT_OFFSET);
+ pkt.putShort(flags, FLAGS_OFFSET);
+ pkt.putShort(1, NUMQ_OFFSET);
+ pkt.putShort(0, NUMANS_OFFSET);
+ pkt.putInt(0, NUMAUTH_OFFSET);
+
+ makeQueryName(fqdn, pkt, DNS_HDR_SIZE);
+ pkt.putShort(qtype, DNS_HDR_SIZE + qnameLen);
+ pkt.putShort(qclass, DNS_HDR_SIZE + qnameLen + 2);
+
+ return pkt;
+ }
+
+ // Builds a query name in pkt according to the RFC spec.
+ private void makeQueryName(DnsName fqdn, Packet pkt, int off) {
+
+ // Loop through labels, least-significant first.
+ for (int i = fqdn.size() - 1; i >= 0; i--) {
+ String label = fqdn.get(i);
+ int len = label.length();
+
+ pkt.putByte(len, off++);
+ for (int j = 0; j < len; j++) {
+ pkt.putByte(label.charAt(j), off++);
+ }
+ }
+ if (!fqdn.hasRootLabel()) {
+ pkt.putByte(0, off);
+ }
+ }
+
+ //-------------------------------------------------------------------------
+
+ private byte[] lookupResponse(Integer xid) throws DnsResolverException {
+ //
+ // Check the queued responses: some other thread in between
+ // received the response for this request.
+ //
+ if (DEBUG) {
+ dprint("LOOKUP for: " + xid +
+ "\tResponse Q:" + resps);
+ }
+ byte[] pkt;
+ if ((pkt = resps.get(xid)) != null) {
+ checkResponseCode(new Header(pkt, pkt.length));
+ queuesLock.lock();
+ try {
+ resps.remove(xid);
+ reqs.remove(xid);
+ } finally {
+ queuesLock.unlock();
+ }
+
+ if (DEBUG) {
+ dprint("FOUND (" + Thread.currentThread() +
+ ") for:" + xid);
+ }
+ }
+ return pkt;
+ }
+
+ /*
+ * Checks the header of an incoming DNS response.
+ * Returns true if it matches the given xid and throws a naming
+ * exception, if appropriate, based on the response code.
+ *
+ * Also checks that the domain name, type and class in the response
+ * match those in the original query.
+ */
+ private boolean isMatchResponse(byte[] pkt, int xid)
+ throws DnsResolverException {
+
+ Header hdr = new Header(pkt, pkt.length);
+ if (hdr.query) {
+ throw new DnsCommunicationException("DNS error: expecting response");
+ }
+
+ if (!reqs.containsKey(xid)) { // already received, ignore the response
+ return false;
+ }
+
+ // common case- the request sent matches the subsequent response read
+ if (hdr.xid == xid) {
+ if (DEBUG) {
+ dprint("XID MATCH:" + xid);
+ }
+ checkResponseCode(hdr);
+ if (!hdr.query && hdr.numQuestions == 1) {
+
+ ResourceRecord rr = new ResourceRecord(pkt, pkt.length,
+ Header.HEADER_SIZE, true, false);
+
+ // Retrieve the original query
+ ResourceRecord query = reqs.get(xid);
+ int qtype = query.getType();
+ int qclass = query.getRrclass();
+ DnsName qname = query.getName();
+
+ // Check that the type/class/name in the query section of the
+ // response match those in the original query
+ if ((qtype == ResourceRecord.QTYPE_STAR ||
+ qtype == rr.getType()) &&
+ (qclass == ResourceRecord.QCLASS_STAR ||
+ qclass == rr.getRrclass()) &&
+ qname.equals(rr.getName())) {
+
+ if (DEBUG) {
+ dprint("MATCH NAME:" + qname + " QTYPE:" + qtype +
+ " QCLASS:" + qclass);
+ }
+
+ // Remove the response for the xid if received by some other
+ // thread.
+ queuesLock.lock();
+ try {
+ resps.remove(xid);
+ reqs.remove(xid);
+ } finally {
+ queuesLock.unlock();
+ }
+ return true;
+
+ } else {
+ if (DEBUG) {
+ dprint("NO-MATCH NAME:" + qname + " QTYPE:" + qtype +
+ " QCLASS:" + qclass);
+ }
+ }
+ }
+ return false;
+ }
+
+ //
+ // xid mis-match: enqueue the response, it may belong to some other
+ // thread that has not yet had a chance to read its response.
+ // enqueue only the first response, responses for retries are ignored.
+ //
+ queuesLock.lock();
+ try {
+ if (reqs.containsKey(hdr.xid)) { // enqueue only the first response
+ resps.put(hdr.xid, pkt);
+ }
+ } finally {
+ queuesLock.unlock();
+ }
+
+ if (DEBUG) {
+ dprint("NO-MATCH SEND ID:" +
+ xid + " RECVD ID:" + hdr.xid +
+ " Response Q:" + resps +
+ " Reqs size:" + reqs.size());
+ }
+ return false;
+ }
+
+ /*
+ * Throws an exception if appropriate for the response code of a
+ * given header.
+ */
+ private void checkResponseCode(Header hdr) throws DnsResolverException {
+
+ int rcode = hdr.rcode;
+ if (rcode == NO_ERROR) {
+ return;
+ }
+ String msg = (rcode < rcodeDescription.length)
+ ? rcodeDescription[rcode]
+ : "DNS error";
+
+ msg += " [response code " + rcode + "]";
+
+ switch (rcode) {
+ case SERVER_FAILURE:
+ throw new DnsServiceUnavailableException(msg);
+ case NAME_ERROR:
+ throw new DnsNameNotFoundException(msg);
+ case NOT_IMPL:
+ case REFUSED:
+ throw new DnsOperationNotSupportedException(msg);
+ case FORMAT_ERROR:
+ default:
+ throw new DnsResolverException(msg);
+ }
+ }
+
+ //-------------------------------------------------------------------------
+
+ private static final boolean DEBUG = java.security.AccessController.doPrivileged(
+ (PrivilegedAction<Boolean>) () -> Boolean.getBoolean("jdk.dns.client.debug"));
+
+ private static void dprint(String mess) {
+ if (DEBUG) {
+ System.err.println("DNS: " + mess);
+ }
+ }
+
+}
+
+class Tcp {
+
+ private final Socket sock;
+ private final java.io.InputStream in;
+ final java.io.OutputStream out;
+ private int timeoutLeft;
+
+ Tcp(InetAddress server, int port, int timeout) throws IOException {
+ sock = new Socket();
+ try {
+ long start = System.currentTimeMillis();
+ sock.connect(new InetSocketAddress(server, port), timeout);
+ timeoutLeft = (int) (timeout - (System.currentTimeMillis() - start));
+ if (timeoutLeft <= 0)
+ throw new SocketTimeoutException();
+
+ sock.setTcpNoDelay(true);
+ out = new java.io.BufferedOutputStream(sock.getOutputStream());
+ in = new java.io.BufferedInputStream(sock.getInputStream());
+ } catch (Exception e) {
+ try {
+ sock.close();
+ } catch (IOException ex) {
+ e.addSuppressed(ex);
+ }
+ throw e;
+ }
+ }
+
+ void close() throws IOException {
+ sock.close();
+ }
+
+ private interface SocketReadOp {
+ int read() throws IOException;
+ }
+
+ private int readWithTimeout(SocketReadOp reader) throws IOException {
+ if (timeoutLeft <= 0)
+ throw new SocketTimeoutException();
+
+ sock.setSoTimeout(timeoutLeft);
+ long start = System.currentTimeMillis();
+ try {
+ return reader.read();
+ } finally {
+ timeoutLeft -= System.currentTimeMillis() - start;
+ }
+ }
+
+ int read() throws IOException {
+ return readWithTimeout(in::read);
+ }
+
+ int read(byte b[], int off, int len) throws IOException {
+ return readWithTimeout(() -> in.read(b, off, len));
+ }
+}
+
+/*
+ * javaos emulation -cj
+ */
+class Packet {
+ byte[] buf;
+
+ Packet(int len) {
+ buf = new byte[len];
+ }
+
+ Packet(byte data[], int len) {
+ buf = new byte[len];
+ System.arraycopy(data, 0, buf, 0, len);
+ }
+
+ void putInt(int x, int off) {
+ buf[off] = (byte) (x >> 24);
+ buf[off + 1] = (byte) (x >> 16);
+ buf[off + 2] = (byte) (x >> 8);
+ buf[off + 3] = (byte) x;
+ }
+
+ void putShort(int x, int off) {
+ buf[off] = (byte) (x >> 8);
+ buf[off + 1] = (byte) x;
+ }
+
+ void putByte(int x, int off) {
+ buf[off] = (byte) x;
+ }
+
+ void putBytes(byte src[], int src_offset, int dst_offset, int len) {
+ System.arraycopy(src, src_offset, buf, dst_offset, len);
+ }
+
+ int length() {
+ return buf.length;
+ }
+
+ byte[] getData() {
+ return buf;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/DnsDatagramChannelFactory.java Thu Oct 31 16:16:21 2019 +0000
@@ -0,0 +1,287 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.dns.client.internal;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.ProtocolFamily;
+import java.net.SocketException;
+import java.nio.channels.DatagramChannel;
+import java.util.Objects;
+import java.util.Random;
+import java.util.concurrent.locks.ReentrantLock;
+
+class DnsDatagramChannelFactory {
+ static final int DEVIATION = 3;
+ static final int THRESHOLD = 6;
+ static final int BIT_DEVIATION = 2;
+ static final int HISTORY = 32;
+ static final int MAX_RANDOM_TRIES = 5;
+
+ /**
+ * The dynamic allocation port range (aka ephemeral ports), as configured
+ * on the system. Use nested class for lazy evaluation.
+ */
+ static final class EphemeralPortRange {
+ private EphemeralPortRange() {
+ }
+
+ static final int LOWER = PortConfig.getLower();
+ static final int UPPER = PortConfig.getUpper();
+ static final int RANGE = UPPER - LOWER + 1;
+ }
+
+ // Records a subset of max {@code capacity} previously used ports
+ static final class PortHistory {
+ final int capacity;
+ final int[] ports;
+ final Random random;
+ int index;
+
+ PortHistory(int capacity, Random random) {
+ this.random = random;
+ this.capacity = capacity;
+ this.ports = new int[capacity];
+ }
+
+ // returns true if the history contains the specified port.
+ public boolean contains(int port) {
+ int p = 0;
+ for (int i = 0; i < capacity; i++) {
+ if ((p = ports[i]) == 0 || p == port) break;
+ }
+ return p == port;
+ }
+
+ // Adds the port to the history - doesn't check whether the port
+ // is already present. Always adds the port and always return true.
+ public boolean add(int port) {
+ if (ports[index] != 0) { // at max capacity
+ // remove one port at random and store the new port there
+ ports[random.nextInt(capacity)] = port;
+ } else { // there's a free slot
+ ports[index] = port;
+ }
+ if (++index == capacity) index = 0;
+ return true;
+ }
+
+ // Adds the port to the history if not already present.
+ // Return true if the port was added, false if the port was already
+ // present.
+ public boolean offer(int port) {
+ if (contains(port)) return false;
+ else return add(port);
+ }
+ }
+
+ int lastport = 0;
+ int suitablePortCount;
+ int unsuitablePortCount;
+ final ProtocolFamily family; // null (default) means dual stack
+ final int thresholdCount; // decision point
+ final int deviation;
+ final Random random;
+ final PortHistory history;
+ final ReentrantLock factoryLock = new ReentrantLock();
+
+ DnsDatagramChannelFactory() {
+ this(new Random());
+ }
+
+ DnsDatagramChannelFactory(Random random) {
+ this(Objects.requireNonNull(random), null, DEVIATION, THRESHOLD);
+ }
+
+ DnsDatagramChannelFactory(Random random,
+ ProtocolFamily family,
+ int deviation,
+ int threshold) {
+ this.random = Objects.requireNonNull(random);
+ this.history = new PortHistory(HISTORY, random);
+ this.family = family;
+ this.deviation = Math.max(1, deviation);
+ this.thresholdCount = Math.max(2, threshold);
+ }
+
+ /**
+ * Opens a datagram socket listening to the wildcard address on a
+ * random port. If the underlying OS supports UDP port randomization
+ * out of the box (if binding a socket to port 0 binds it to a random
+ * port) then the underlying OS implementation is used. Otherwise, this
+ * method will allocate and bind a socket on a randomly selected ephemeral
+ * port in the dynamic range.
+ *
+ * @return A new DatagramChannel bound to a random port.
+ * @throws SocketException if the socket cannot be created.
+ */
+ public DatagramChannel open() throws SocketException {
+ factoryLock.lock();
+ try {
+ int lastseen = lastport;
+ DatagramChannel dc;
+
+ boolean thresholdCrossed = unsuitablePortCount > thresholdCount;
+ if (thresholdCrossed) {
+ // Underlying stack does not support random UDP port out of the box.
+ // Use our own algorithm to allocate a random UDP port
+ dc = openRandom();
+ if (dc != null) return dc;
+
+ // couldn't allocate a random port: reset all counters and fall
+ // through.
+ unsuitablePortCount = 0;
+ suitablePortCount = 0;
+ lastseen = 0;
+ }
+
+ // Allocate an ephemeral port (port 0)
+ dc = openDefault();
+ lastport = dc.socket().getLocalPort();
+ if (lastseen == 0) {
+ history.offer(lastport);
+ return dc;
+ }
+
+ thresholdCrossed = suitablePortCount > thresholdCount;
+ boolean farEnough = Integer.bitCount(lastseen ^ lastport) > BIT_DEVIATION
+ && Math.abs(lastport - lastseen) > deviation;
+ boolean recycled = history.contains(lastport);
+ boolean suitable = (thresholdCrossed || farEnough && !recycled);
+ if (suitable && !recycled) history.add(lastport);
+
+ if (suitable) {
+ if (!thresholdCrossed) {
+ suitablePortCount++;
+ } else if (!farEnough || recycled) {
+ unsuitablePortCount = 1;
+ suitablePortCount = thresholdCount / 2;
+ }
+ // Either the underlying stack supports random UDP port allocation,
+ // or the new port is sufficiently distant from last port to make
+ // it look like it is. Let's use it.
+ return dc;
+ }
+
+ // Undecided... the new port was too close. Let's allocate a random
+ // port using our own algorithm
+ assert !thresholdCrossed;
+ try {
+ dc.close();
+ } catch (IOException ioe) {
+ throw new SocketException(ioe.getMessage());
+ }
+ dc = openRandom();
+ unsuitablePortCount++;
+ return dc;
+ } finally {
+ factoryLock.unlock();
+ }
+ }
+
+ private DatagramChannel openDefault() throws SocketException {
+ if (family != null) {
+ try {
+ DatagramChannel dc = DatagramChannel.open(family);
+ try {
+ dc.bind(null);
+ return dc;
+ } catch (Throwable x) {
+ dc.close();
+ throw x;
+ }
+ } catch (SocketException x) {
+ throw x;
+ } catch (IOException x) {
+ SocketException e = new SocketException(x.getMessage());
+ e.initCause(x);
+ throw e;
+ }
+ }
+ try {
+ return DatagramChannel.open();
+ } catch (IOException ioe) {
+ throw new SocketException(ioe.getMessage());
+ }
+ }
+
+ boolean isUsingNativePortRandomization() {
+ factoryLock.lock();
+ try {
+ return unsuitablePortCount <= thresholdCount
+ && suitablePortCount > thresholdCount;
+ } finally {
+ factoryLock.unlock();
+ }
+ }
+
+ boolean isUsingJavaPortRandomization() {
+ factoryLock.lock();
+ try {
+ return unsuitablePortCount > thresholdCount;
+ } finally {
+ factoryLock.unlock();
+ }
+ }
+
+ boolean isUndecided() {
+ factoryLock.lock();
+ try {
+ return !isUsingJavaPortRandomization()
+ && !isUsingNativePortRandomization();
+ } finally {
+ factoryLock.unlock();
+ }
+ }
+
+ private DatagramChannel openRandom() {
+ int maxtries = MAX_RANDOM_TRIES;
+ while (maxtries-- > 0) {
+ int port = EphemeralPortRange.LOWER
+ + random.nextInt(EphemeralPortRange.RANGE);
+ try {
+ if (family != null) {
+ DatagramChannel dc = DatagramChannel.open(family);
+ try {
+ dc.bind(new InetSocketAddress(port));
+ return dc;
+ } catch (Throwable x) {
+ dc.close();
+ throw x;
+ }
+ } else {
+
+ }
+ DatagramChannel dc = DatagramChannel.open(family);
+ return dc.bind(new InetSocketAddress(port));
+ } catch (IOException x) {
+ // try again until maxtries == 0;
+ }
+ }
+ return null;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/DnsName.java Thu Oct 31 16:16:21 2019 +0000
@@ -0,0 +1,513 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.dns.client.internal;
+
+import jdk.dns.client.ex.DnsInvalidNameException;
+
+import java.util.ArrayList;
+
+/**
+ * {@code DnsName} implements compound names for DNS as specified by
+ * RFCs 1034 and 1035, and as updated and clarified by RFCs 1123 and 2181.
+ *
+ * <p> The labels in a domain name correspond to JNDI atomic names.
+ * Each label must be less than 64 octets in length, and only the
+ * optional root label at the end of the name may be 0 octets long.
+ * The sum of the lengths of all labels in a name, plus the number of
+ * non-root labels plus 1, must be less than 256. The textual
+ * representation of a domain name consists of the labels, escaped as
+ * needed, dot-separated, and ordered right-to-left.
+ *
+ * <p> A label consists of a sequence of octets, each of which may
+ * have any value from 0 to 255.
+ *
+ * <p> <em>Host names</em> are a subset of domain names.
+ * Their labels contain only ASCII letters, digits, and hyphens, and
+ * none may begin or end with a hyphen. While names not conforming to
+ * these rules may be valid domain names, they will not be usable by a
+ * number of DNS applications, and should in most cases be avoided.
+ *
+ * <p> DNS does not specify an encoding (such as UTF-8) to use for
+ * octets with non-ASCII values. As of this writing there is some
+ * work going on in this area, but it is not yet finalized.
+ * {@code DnsName} currently converts any non-ASCII octets into
+ * characters using ISO-LATIN-1 encoding, in effect taking the
+ * value of each octet and storing it directly into the low-order byte
+ * of a Java character and <i>vice versa</i>. As a consequence, no
+ * character in a DNS name will ever have a non-zero high-order byte.
+ * When the work on internationalizing domain names has stabilized
+ * (see for example <i>draft-ietf-idn-idna-10.txt</i>), {@code DnsName}
+ * may be updated to conform to that work.
+ *
+ * <p> Backslash ({@code \}) is used as the escape character in the
+ * textual representation of a domain name. The character sequence
+ * `{@code \DDD}', where {@code DDD} is a 3-digit decimal number
+ * (with leading zeros if needed), represents the octet whose value
+ * is {@code DDD}. The character sequence `{@code \C}', where
+ * {@code C} is a character other than {@code '0'} through
+ * {@code '9'}, represents the octet whose value is that of
+ * {@code C} (again using ISO-LATIN-1 encoding); this is particularly
+ * useful for escaping {@code '.'} or backslash itself. Backslash is
+ * otherwise not allowed in a domain name. Note that escape characters
+ * are interpreted when a name is parsed. So, for example, the character
+ * sequences `{@code S}', `{@code \S}', and `{@code \083}' each
+ * represent the same one-octet name. The {@code toString()} method
+ * does not generally insert escape sequences except where necessary.
+ * If, however, the {@code DnsName} was constructed using unneeded
+ * escapes, those escapes may appear in the {@code toString} result.
+ *
+ * <p> Atomic names passed as parameters to methods of
+ * {@code DnsName}, and those returned by them, are unescaped. So,
+ * for example, <code>(new DnsName()).add("a.b")</code> creates an
+ * object representing the one-label domain name {@code a\.b}, and
+ * calling {@code get(0)} on this object returns {@code "a.b"}.
+ *
+ * <p> While DNS names are case-preserving, comparisons between them
+ * are case-insensitive. When comparing names containing non-ASCII
+ * octets, {@code DnsName} uses case-insensitive comparison
+ * between pairs of ASCII values, and exact binary comparison
+ * otherwise.
+ *
+ * <p> A {@code DnsName} instance is not synchronized against
+ * concurrent access by multiple threads.
+ *
+ * @author Scott Seligman
+ */
+
+// Stripped copy with removed serialization code and jndi.Name interface
+
+public final class DnsName {
+
+ // If non-null, the domain name represented by this DnsName.
+ private String domain = "";
+
+ // The labels of this domain name, as a list of strings. Index 0
+ // corresponds to the leftmost (least significant) label: note that
+ // this is the reverse of the ordering used by the Name interface.
+ private ArrayList<String> labels = new ArrayList<>();
+
+ // The number of octets needed to carry this domain name in a DNS
+ // packet. Equal to the sum of the lengths of each label, plus the
+ // number of non-root labels, plus 1. Must remain less than 256.
+ private short octets = 1;
+
+
+ /**
+ * Constructs a {@code DnsName} representing the empty domain name.
+ */
+ public DnsName() {
+ }
+
+ /**
+ * Constructs a {@code DnsName} representing a given domain name.
+ *
+ * @param name the domain name to parse
+ * @throws DnsInvalidNameException if {@code name} does not conform
+ * to DNS syntax.
+ */
+ public DnsName(String name) throws DnsInvalidNameException {
+ parse(name);
+ }
+
+ /*
+ * Returns a new DnsName with its name components initialized to
+ * the components of "n" in the range [beg,end). Indexing is as
+ * for the Name interface, with 0 being the most significant.
+ */
+ private DnsName(DnsName n, int beg, int end) {
+ // Compute indexes into "labels", which has least-significant label
+ // at index 0 (opposite to the convention used for "beg" and "end").
+ int b = n.size() - end;
+ int e = n.size() - beg;
+ labels.addAll(n.labels.subList(b, e));
+
+ if (size() == n.size()) {
+ domain = n.domain;
+ octets = n.octets;
+ } else {
+ for (String label : labels) {
+ if (label.length() > 0) {
+ octets += (short) (label.length() + 1);
+ }
+ }
+ }
+ }
+
+
+ public String toString() {
+ if (domain == null) {
+ StringBuilder buf = new StringBuilder();
+ for (String label : labels) {
+ if (buf.length() > 0 || label.length() == 0) {
+ buf.append('.');
+ }
+ escape(buf, label);
+ }
+ domain = buf.toString();
+ }
+ return domain;
+ }
+
+ /**
+ * Does this domain name follow <em>host name</em> syntax?
+ */
+ public boolean isHostName() {
+ for (String label : labels) {
+ if (!isHostNameLabel(label)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public short getOctets() {
+ return octets;
+ }
+
+ public int size() {
+ return labels.size();
+ }
+
+ public boolean isEmpty() {
+ return (size() == 0);
+ }
+
+ public int hashCode() {
+ int h = 0;
+ for (int i = 0; i < size(); i++) {
+ h = 31 * h + getKey(i).hashCode();
+ }
+ return h;
+ }
+
+ public boolean equals(Object obj) {
+ if (!(obj instanceof DnsName)) {
+ return false;
+ }
+ DnsName n = (DnsName) obj;
+ return ((size() == n.size()) && // shortcut: do sizes differ?
+ (compareTo(obj) == 0));
+ }
+
+ public int compareTo(Object obj) {
+ DnsName n = (DnsName) obj;
+ return compareRange(0, size(), n); // never 0 if sizes differ
+ }
+
+ public String get(int pos) {
+ if (pos < 0 || pos >= size()) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ int i = size() - pos - 1; // index of "pos" component in "labels"
+ return labels.get(i);
+ }
+
+ public DnsName getPrefix(int pos) {
+ return new DnsName(this, 0, pos);
+ }
+
+ public DnsName getSuffix(int pos) {
+ return new DnsName(this, pos, size());
+ }
+
+ public Object clone() {
+ return new DnsName(this, 0, size());
+ }
+
+ public DnsName add(int pos, String comp) throws DnsInvalidNameException {
+ if (pos < 0 || pos > size()) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ // Check for empty labels: may have only one, and only at end.
+ int len = comp.length();
+ if ((pos > 0 && len == 0) ||
+ (pos == 0 && hasRootLabel())) {
+ throw new DnsInvalidNameException(
+ "Empty label must be the last label in a domain name");
+ }
+ // Check total name length.
+ if (len > 0) {
+ if (octets + len + 1 >= 256) {
+ throw new DnsInvalidNameException("Name too long");
+ }
+ octets += (short) (len + 1);
+ }
+
+ int i = size() - pos; // index for insertion into "labels"
+ verifyLabel(comp);
+ labels.add(i, comp);
+
+ domain = null; // invalidate "domain"
+ return this;
+ }
+
+ public DnsName addAll(int pos, DnsName n) throws DnsInvalidNameException {
+ // "n" is a DnsName so we can insert it as a whole, rather than
+ // verifying and inserting it component-by-component.
+ // More code, but less work.
+
+ if (n.isEmpty()) {
+ return this;
+ }
+ // Check for empty labels: may have only one, and only at end.
+ if ((pos > 0 && n.hasRootLabel()) ||
+ (pos == 0 && hasRootLabel())) {
+ throw new DnsInvalidNameException(
+ "Empty label must be the last label in a domain name");
+ }
+
+ short newOctets = (short) (octets + n.octets - 1);
+ if (newOctets > 255) {
+ throw new DnsInvalidNameException("Name too long");
+ }
+ octets = newOctets;
+ int i = size() - pos; // index for insertion into "labels"
+ labels.addAll(i, n.labels);
+
+ // Preserve "domain" if we're appending or prepending,
+ // otherwise invalidate it.
+ if (isEmpty()) {
+ domain = n.domain;
+ } else if (domain == null || n.domain == null) {
+ domain = null;
+ } else if (pos == 0) {
+ domain += (n.domain.equals(".") ? "" : ".") + n.domain;
+ } else if (pos == size()) {
+ domain = n.domain + (domain.equals(".") ? "" : ".") + domain;
+ } else {
+ domain = null;
+ }
+ return this;
+ }
+
+ boolean hasRootLabel() {
+ return (!isEmpty() &&
+ get(0).isEmpty());
+ }
+
+ /*
+ * Helper method for public comparison methods. Lexicographically
+ * compares components of this name in the range [beg,end) with
+ * all components of "n". Indexing is as for the Name interface,
+ * with 0 being the most significant. Returns negative, zero, or
+ * positive as these name components are less than, equal to, or
+ * greater than those of "n".
+ */
+ private int compareRange(int beg, int end, DnsName n) {
+ // aee: Removed CompositeName ClassCastException generation here
+
+ // Loop through labels, starting with most significant.
+ int minSize = Math.min(end - beg, n.size());
+ for (int i = 0; i < minSize; i++) {
+ String label1 = get(i + beg);
+ String label2 = n.get(i);
+
+ // int j = size() - (i + beg) - 1; // index of label1 in "labels"
+ // assert (label1 == labels.get(j));
+
+ int c = compareLabels(label1, label2);
+ if (c != 0) {
+ return c;
+ }
+ }
+ return ((end - beg) - n.size()); // longer range wins
+ }
+
+ /*
+ * Returns a key suitable for hashing the label at index i.
+ * Indexing is as for the Name interface, with 0 being the most
+ * significant.
+ */
+ String getKey(int i) {
+ return keyForLabel(get(i));
+ }
+
+
+ /*
+ * Parses a domain name, setting the values of instance vars accordingly.
+ */
+ private void parse(String name) throws DnsInvalidNameException {
+
+ StringBuilder label = new StringBuilder(); // label being parsed
+
+ for (int i = 0; i < name.length(); i++) {
+ char c = name.charAt(i);
+
+ if (c == '\\') { // found an escape sequence
+ c = getEscapedOctet(name, i++);
+ if (isDigit(name.charAt(i))) { // sequence is \DDD
+ i += 2; // consume remaining digits
+ }
+ label.append(c);
+
+ } else if (c != '.') { // an unescaped octet
+ label.append(c);
+
+ } else { // found '.' separator
+ add(0, label.toString()); // check syntax, then add label
+ // to end of name
+ label.delete(0, i); // clear buffer for next label
+ }
+ }
+
+ // If name is neither "." nor "", the octets (zero or more)
+ // from the rightmost dot onward are now added as the final
+ // label of the name. Those two are special cases in that for
+ // all other domain names, the number of labels is one greater
+ // than the number of dot separators.
+ if (!name.isEmpty() && !name.equals(".")) {
+ add(0, label.toString());
+ }
+
+ domain = name; // do this last, since add() sets it to null
+ }
+
+ /*
+ * Returns (as a char) the octet indicated by the escape sequence
+ * at a given position within a domain name.
+ * @throws InvalidNameException if a valid escape sequence is not found.
+ */
+ private static char getEscapedOctet(String name, int pos)
+ throws DnsInvalidNameException {
+ try {
+ // assert (name.charAt(pos) == '\\');
+ char c1 = name.charAt(++pos);
+ if (isDigit(c1)) { // sequence is `\DDD'
+ char c2 = name.charAt(++pos);
+ char c3 = name.charAt(++pos);
+ if (isDigit(c2) && isDigit(c3)) {
+ return (char)
+ ((c1 - '0') * 100 + (c2 - '0') * 10 + (c3 - '0'));
+ } else {
+ throw new DnsInvalidNameException(
+ "Invalid escape sequence in " + name);
+ }
+ } else { // sequence is `\C'
+ return c1;
+ }
+ } catch (IndexOutOfBoundsException e) {
+ throw new DnsInvalidNameException(
+ "Invalid escape sequence in " + name);
+ }
+ }
+
+ /*
+ * Checks that this label is valid.
+ * @throws InvalidNameException if label is not valid.
+ */
+ private static void verifyLabel(String label) throws DnsInvalidNameException {
+ if (label.length() > 63) {
+ throw new DnsInvalidNameException(
+ "Label exceeds 63 octets: " + label);
+ }
+ // Check for two-byte characters.
+ for (int i = 0; i < label.length(); i++) {
+ char c = label.charAt(i);
+ if ((c & 0xFF00) != 0) {
+ throw new DnsInvalidNameException(
+ "Label has two-byte char: " + label);
+ }
+ }
+ }
+
+ /*
+ * Does this label conform to host name syntax?
+ */
+ private static boolean isHostNameLabel(String label) {
+ for (int i = 0; i < label.length(); i++) {
+ char c = label.charAt(i);
+ if (!isHostNameChar(c)) {
+ return false;
+ }
+ }
+ return !(label.startsWith("-") || label.endsWith("-"));
+ }
+
+ private static boolean isHostNameChar(char c) {
+ return (c == '-' ||
+ c >= 'a' && c <= 'z' ||
+ c >= 'A' && c <= 'Z' ||
+ c >= '0' && c <= '9');
+ }
+
+ private static boolean isDigit(char c) {
+ return (c >= '0' && c <= '9');
+ }
+
+ /*
+ * Append a label to buf, escaping as needed.
+ */
+ private static void escape(StringBuilder buf, String label) {
+ for (int i = 0; i < label.length(); i++) {
+ char c = label.charAt(i);
+ if (c == '.' || c == '\\') {
+ buf.append('\\');
+ }
+ buf.append(c);
+ }
+ }
+
+ /*
+ * Compares two labels, ignoring case for ASCII values.
+ * Returns negative, zero, or positive as the first label
+ * is less than, equal to, or greater than the second.
+ * See keyForLabel().
+ */
+ private static int compareLabels(String label1, String label2) {
+ int min = Math.min(label1.length(), label2.length());
+ for (int i = 0; i < min; i++) {
+ char c1 = label1.charAt(i);
+ char c2 = label2.charAt(i);
+ if (c1 >= 'A' && c1 <= 'Z') {
+ c1 += 'a' - 'A'; // to lower case
+ }
+ if (c2 >= 'A' && c2 <= 'Z') {
+ c2 += 'a' - 'A'; // to lower case
+ }
+ if (c1 != c2) {
+ return (c1 - c2);
+ }
+ }
+ return (label1.length() - label2.length()); // the longer one wins
+ }
+
+ /*
+ * Returns a key suitable for hashing a label. Two labels map to
+ * the same key iff they are equal, taking possible case-folding
+ * into account. See compareLabels().
+ */
+ private static String keyForLabel(String label) {
+ StringBuilder sb = new StringBuilder(label.length());
+ for (int i = 0; i < label.length(); i++) {
+ char c = label.charAt(i);
+ if (c >= 'A' && c <= 'Z') {
+ c += 'a' - 'A'; // to lower case
+ }
+ sb.append(c);
+ }
+ return sb.toString();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/DnsNameService.java Thu Oct 31 16:16:21 2019 +0000
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.dns.client.internal;
+
+import jdk.dns.client.NetworkNamesResolver;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Comparator;
+
+public class DnsNameService implements InetAddress.NameService {
+ private static final InetAddress[] NONE = new InetAddress[0];
+
+ enum AddressOrder {
+ DontCare,
+ IPv4First,
+ IPv6First;
+
+ private static AddressOrder fromString(String value) {
+ if (value == null) {
+ return IPv4First;
+ }
+ if ("true".equals(value)) {
+ return IPv6First;
+ }
+ if ("false".equals(value)) {
+ return IPv4First;
+ }
+ if ("system".equals(value)) {
+ return DontCare; // TODO: Decide if it is compatible way with default InetAddress resolver
+ }
+ return IPv4First;
+ }
+ }
+
+ static final AddressOrder order;
+
+ static {
+ var action = (PrivilegedAction<String>) () -> System.getProperty("java.net.preferIPv6Addresses");
+
+ String spValue = System.getSecurityManager() == null ? action.run() : AccessController.doPrivileged(action);
+ order = AddressOrder.fromString(spValue);
+ }
+
+ public DnsNameService() {
+ }
+
+ @Override
+ public InetAddress[] lookupAllHostAddr(String host) throws UnknownHostException {
+ if (order == AddressOrder.DontCare) {
+ return NetworkNamesResolver.open().lookupAllHostAddr(host).toArray(NONE);
+ } else {
+ return NetworkNamesResolver.open()
+ .lookupAllHostAddr(host)
+ .stream()
+ .sorted(
+ Comparator.comparing(
+ ia -> (ia instanceof Inet4Address && order == AddressOrder.IPv4First)
+ || (ia instanceof Inet6Address && order == AddressOrder.IPv6First),
+ Boolean::compareTo)
+ .reversed()
+ ).toArray(InetAddress[]::new);
+ }
+ }
+
+ @Override
+ public String getHostByAddr(byte[] addr) throws UnknownHostException {
+ return NetworkNamesResolver.open().getHostByAddr(InetAddress.getByAddress(addr));
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/Header.java Thu Oct 31 16:16:21 2019 +0000
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.dns.client.internal;
+
+import jdk.dns.client.ex.DnsResolverException;
+
+class Header {
+
+ static final int HEADER_SIZE = 12; // octets in a DNS header
+
+ // Masks and shift amounts for DNS header flag fields.
+ static final short QR_BIT = (short) 0x8000;
+ static final short OPCODE_MASK = (short) 0x7800;
+ static final int OPCODE_SHIFT = 11;
+ static final short AA_BIT = (short) 0x0400;
+ static final short TC_BIT = (short) 0x0200;
+ static final short RD_BIT = (short) 0x0100;
+ static final short RA_BIT = (short) 0x0080;
+ static final short RCODE_MASK = (short) 0x000F;
+
+ int xid; // ID: 16-bit query identifier
+ boolean query; // QR: true if query, false if response
+ int opcode; // OPCODE: 4-bit opcode
+ boolean authoritative; // AA
+ boolean truncated; // TC
+ boolean recursionDesired; // RD
+ boolean recursionAvail; // RA
+ int rcode; // RCODE: 4-bit response code
+ int numQuestions;
+ int numAnswers;
+ int numAuthorities;
+ int numAdditionals;
+
+ /*
+ * Returns a representation of a decoded DNS message header.
+ * Does not modify or store a reference to the msg array.
+ */
+ Header(byte[] msg, int msgLen) throws DnsResolverException {
+ decode(msg, msgLen);
+ }
+
+ /*
+ * Decodes a DNS message header. Does not modify or store a
+ * reference to the msg array.
+ */
+ private void decode(byte[] msg, int msgLen) throws DnsResolverException {
+
+ try {
+ int pos = 0; // current offset into msg
+
+ if (msgLen < HEADER_SIZE) {
+ throw new DnsResolverException(
+ "DNS error: corrupted message header");
+ }
+
+ xid = getShort(msg, pos);
+ pos += 2;
+
+ // Flags
+ short flags = (short) getShort(msg, pos);
+ pos += 2;
+ query = (flags & QR_BIT) == 0;
+ opcode = (flags & OPCODE_MASK) >>> OPCODE_SHIFT;
+ authoritative = (flags & AA_BIT) != 0;
+ truncated = (flags & TC_BIT) != 0;
+ recursionDesired = (flags & RD_BIT) != 0;
+ recursionAvail = (flags & RA_BIT) != 0;
+ rcode = (flags & RCODE_MASK);
+
+ // RR counts
+ numQuestions = getShort(msg, pos);
+ pos += 2;
+ numAnswers = getShort(msg, pos);
+ pos += 2;
+ numAuthorities = getShort(msg, pos);
+ pos += 2;
+ numAdditionals = getShort(msg, pos);
+ pos += 2;
+
+ } catch (IndexOutOfBoundsException e) {
+ throw new DnsResolverException("DNS error: corrupted message header");
+ }
+ }
+
+ /*
+ * Returns the 2-byte unsigned value at msg[pos]. The high
+ * order byte comes first.
+ */
+ private static int getShort(byte[] msg, int pos) {
+ return (((msg[pos] & 0xFF) << 8) |
+ (msg[pos + 1] & 0xFF));
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/HostsFileResolver.java Thu Oct 31 16:16:21 2019 +0000
@@ -0,0 +1,271 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.dns.client.internal;
+
+import jdk.dns.client.AddressFamily;
+import jdk.dns.client.internal.util.ReloadTracker;
+import sun.net.util.IPAddressUtil;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+
+public class HostsFileResolver {
+ private static final String HOSTS_FILE_LOCATION_PROPERTY_VALUE =
+ AccessController.doPrivileged((PrivilegedAction<String>)
+ () -> System.getProperty("jdk.net.hosts.file", "/etc/hosts")
+ );
+ private static final ReadWriteLock LOCK = new ReentrantReadWriteLock();
+
+ // 300 seconds, similar to DnsResolverConfiguration in millis since Epoch
+ private static final long REFRESH_TIMEOUT_MILLIS = 300_000;
+ private static final ReloadTracker HOSTS_FILE_TRACKER;
+ private static volatile Map<String, HostFileEntry> HOST_ADDRESSES = Collections.emptyMap();
+
+ void loadHostsAddresses() {
+ LOCK.readLock().lock();
+ var rsf = HOSTS_FILE_TRACKER.getReloadStatus();
+ try {
+ if (!HOSTS_FILE_TRACKER.getReloadStatus().isReloadNeeded()) {
+ return;
+ }
+ } finally {
+ LOCK.readLock().unlock();
+ }
+
+ LOCK.writeLock().lock();
+ try {
+ var rs = HOSTS_FILE_TRACKER.getReloadStatus();
+ // Check if reload is still needed
+ if (rs.isReloadNeeded()) {
+ if (rs.isFileExists()) {
+ HOST_ADDRESSES = parseHostsFile();
+ HOSTS_FILE_TRACKER.updateTimestamps(rs);
+ } else {
+ HOST_ADDRESSES = Collections.emptyMap();
+ }
+ }
+ } finally {
+ LOCK.writeLock().unlock();
+ }
+ }
+
+ private static String removeComments(String hostsEntry) {
+ String filteredEntry = hostsEntry;
+ int hashIndex;
+
+ if ((hashIndex = hostsEntry.indexOf("#")) != -1) {
+ filteredEntry = hostsEntry.substring(0, hashIndex);
+ }
+ return filteredEntry;
+ }
+
+ private static class HostFileEntry {
+ final List<String> names; // Might need to split into aliases and name
+ final InetAddress address;
+ final boolean isValid;
+ final boolean isHostname;
+
+ HostFileEntry(String[] data) {
+ assert data.length > 1;
+ List<String> ln = List.of(Arrays.copyOfRange(data, 1, data.length));
+ String addressString = data[0];
+ names = ln;
+ address = parseAddress(ln.isEmpty() ? null : ln.get(0), addressString);
+ isValid = address != null;
+ isHostname = false;
+ }
+
+ @Override
+ public String toString() {
+ return names+"/"+address;
+ }
+
+ private HostFileEntry(String name, InetAddress address, boolean isHostname) {
+ this.names = List.of(name);
+ this.address = address;
+ this.isValid = address != null;
+ this.isHostname = isHostname;
+ }
+
+ boolean isValid() {
+ return isValid;
+ }
+ boolean isHostname() {return isHostname;}
+
+ String getHostName() {
+ return names.get(0);
+ }
+
+ Stream<HostFileEntry> oneNameStream() {
+ HostFileEntry hostName = new HostFileEntry(names.get(0), address, true);
+ Stream<HostFileEntry> aliases = names.stream()
+ .skip(1)
+ .map(n -> new HostFileEntry(n, address, false));
+ return Stream.concat(Stream.of(hostName), aliases);
+ }
+
+ private InetAddress parseAddress(String hostName, String addressString) {
+ // TODO: Revisit
+ Objects.requireNonNull(hostName);
+
+ // IPAddressUtil is from
+ var pa = (PrivilegedAction<byte[]>) () -> {
+ if (IPAddressUtil.isIPv4LiteralAddress(addressString)) {
+ return IPAddressUtil.textToNumericFormatV4(addressString);
+ } else if (IPAddressUtil.isIPv6LiteralAddress(addressString)) {
+ return IPAddressUtil.textToNumericFormatV6(addressString);
+ }
+ return null;
+ };
+ byte[] addr = System.getSecurityManager() == null ?
+ pa.run() : AccessController.doPrivileged(pa);
+
+ if (addr != null) {
+ try {
+ // if (hostName == null) hostName = addressString
+ return InetAddress.getByAddress(hostName, addr);
+ } catch (UnknownHostException e) {
+ }
+ }
+ return null;
+ }
+ }
+
+ private Map<String, HostFileEntry> parseHostsFile() {
+ Path hf = Paths.get(HOSTS_FILE_LOCATION_PROPERTY_VALUE);
+ try {
+ // TODO: Revisit
+ var pea = (PrivilegedExceptionAction<Boolean>) () -> Files.isRegularFile(hf);
+ boolean isRegularFile = System.getSecurityManager() == null ? pea.run()
+ : AccessController.doPrivileged(pea);
+
+ if (isRegularFile) {
+ var result = new HashMap<String, HostFileEntry>();
+ var pea2 = (PrivilegedExceptionAction<List<String>>) () -> Files.readAllLines(hf, StandardCharsets.UTF_8);
+ var lines = System.getSecurityManager() == null ? pea2.run()
+ : AccessController.doPrivileged(pea2);
+
+ lines.stream()
+ .map(HostsFileResolver::removeComments)
+ .filter(Predicate.not(String::isBlank))
+ .map(s -> s.split("\\s+"))
+ .filter(a -> a.length > 1)
+ .map(HostFileEntry::new)
+ .filter(HostFileEntry::isValid)
+ .flatMap(HostFileEntry::oneNameStream)
+ .forEachOrdered(
+ // If the same host name is listed multiple times then
+ // use the first encountered line
+ hfe -> result.putIfAbsent(hfe.names.get(0), hfe)
+ );
+ return Map.copyOf(result);
+ }
+ } catch (PrivilegedActionException pae) {
+ throw new RuntimeException("Can't read hosts file", pae.getCause());
+ } catch (Exception e) {
+ throw new RuntimeException("Can't read hosts file", e);
+ }
+ return Collections.emptyMap();
+ }
+
+ public InetAddress getHostAddress(String hostName) throws UnknownHostException {
+ return getHostAddress(hostName, AddressFamily.ANY);
+ }
+
+ public InetAddress getHostAddress(String hostName, AddressFamily family) throws UnknownHostException {
+
+ loadHostsAddresses();
+ var map = HOST_ADDRESSES;
+ var he = map.get(hostName);
+ if (he == null) {
+ throw new UnknownHostException(hostName);
+ }
+ var addr = he.address;
+ if (!family.sameFamily(addr)) {
+ throw new UnknownHostException(hostName);
+ }
+ return addr;
+ }
+
+ public String getByAddress(final InetAddress ha) throws UnknownHostException {
+ loadHostsAddresses();
+ var map = HOST_ADDRESSES;
+ var entry = map.values().stream()
+ .filter(HostFileEntry::isHostname)
+ .filter(e -> isAddressBytesTheSame(ha.getAddress(), e.address.getAddress()))
+ .findFirst();
+ if (entry.isEmpty()) {
+ throw new UnknownHostException(ha.toString());
+ }
+ return entry.get().getHostName();
+ }
+
+ private static boolean isAddressBytesTheSame(byte [] addr1, byte [] addr2) {
+ if (addr1 == null || addr2 == null) {
+ return false;
+ }
+ if (addr1.length != addr2.length) {
+ return false;
+ }
+ for (int i=0; i<addr1.length; i++) {
+ if (addr1[i] != addr2[i])
+ return false;
+ }
+ return true;
+ }
+
+ static {
+ // TODO: Revisit
+ try {
+ var pea = (PrivilegedExceptionAction<ReloadTracker>) () ->
+ ReloadTracker.newInstance(Paths.get(HOSTS_FILE_LOCATION_PROPERTY_VALUE), REFRESH_TIMEOUT_MILLIS);
+ HOSTS_FILE_TRACKER = System.getSecurityManager() == null ? pea.run() :
+ AccessController.doPrivileged(pea);
+ } catch (PrivilegedActionException pae) {
+ throw new RuntimeException("Error registering hosts file watch service", pae.getCause());
+ } catch (Exception e) {
+ throw new RuntimeException("Error registering hosts file watch service", e);
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/PortConfig.java Thu Oct 31 16:16:21 2019 +0000
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.dns.client.internal;
+
+final class PortConfig {
+ /**
+ * Determines the ephemeral port range in use on this system.
+ * If this cannot be determined, then the default settings
+ * of the OS are returned.
+ */
+ private static int defaultUpper, defaultLower;
+ private static final int upper, lower;
+
+ private PortConfig() {
+ }
+
+ static {
+ String os = System.getProperty("os.name");
+ if (os.startsWith("Linux")) {
+ defaultLower = 32768;
+ defaultUpper = 61000;
+ } else if (os.startsWith("SunOS")) {
+ defaultLower = 32768;
+ defaultUpper = 65535;
+ } else if (os.contains("OS X")) {
+ defaultLower = 49152;
+ defaultUpper = 65535;
+ } else if (os.startsWith("AIX")) {
+ // The ephemeral port is OS version dependent on AIX:
+ // http://publib.boulder.ibm.com/infocenter/aix/v7r1/topic/com.ibm.aix.rsct315.admin/bl503_ephport.htm
+ // However, on AIX 5.3 / 6.1 / 7.1 we always see the
+ // settings below by using:
+ // /usr/sbin/no -a | fgrep ephemeral
+ defaultLower = 32768;
+ defaultUpper = 65535;
+ } else {
+ throw new InternalError(
+ "sun.net.PortConfig: unknown OS");
+ }
+
+ lower = defaultLower;
+ upper = defaultUpper;
+ }
+
+ public static int getLower() {
+ return lower;
+ }
+
+ public static int getUpper() {
+ return upper;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/Resolver.java Thu Oct 31 16:16:21 2019 +0000
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.dns.client.internal;
+
+import jdk.dns.client.ex.DnsResolverException;
+
+import java.net.UnknownHostException;
+import java.util.List;
+
+// TODO: Remove this one
+// TODO: All with close() implements AutoCloseable
+// TODO: Analyse exceptions and remove all different types if not needed to maintain code-flow
+public class Resolver implements AutoCloseable {
+ private DnsClient dnsClient;
+
+ /*
+ * Constructs a new Resolver given package oracle.dns.client.standalone;its servers and timeout parameters.
+ * Each server is of the form "server[:port]".
+ * IPv6 literal host names include delimiting brackets.
+ * There must be at least one server.
+ * "timeout" is the initial timeout interval (in ms) for UDP queries,
+ * and "retries" gives the number of retries per server.
+ */
+ public Resolver(List<String> servers, int timeout, int retries)
+ throws UnknownHostException {
+ dnsClient = new DnsClient(servers, timeout, retries);
+ }
+
+ public void close() {
+ dnsClient.close();
+ dnsClient = null;
+ }
+
+ /*
+ * Queries resource records of a particular class and type for a
+ * given domain name.
+ * Useful values of rrclass are ResourceRecord.[Q]CLASS_xxx.
+ * Useful values of rrtype are ResourceRecord.[Q]TYPE_xxx.
+ * If recursion is true, recursion is requested on the query.
+ * If auth is true, only authoritative responses are accepted.
+ */
+ public ResourceRecords query(DnsName fqdn, int rrclass, int rrtype,
+ boolean recursion, boolean auth)
+ throws DnsResolverException {
+ return dnsClient.query(fqdn, rrclass, rrtype, recursion, auth);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/ResourceClassType.java Thu Oct 31 16:16:21 2019 +0000
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.dns.client.internal;
+
+import jdk.dns.client.AddressFamily;
+import jdk.dns.client.ex.DnsInvalidAttributeIdentifierException;
+
+public class ResourceClassType {
+ int rrclass;
+ int rrtype;
+
+ ResourceClassType(int rrtype) {
+ this.rrclass = ResourceRecord.CLASS_INTERNET;
+ this.rrtype = rrtype;
+ }
+
+ public int getRecordClass() {
+ return rrclass;
+ }
+
+ public int getRecordType() {
+ return rrtype;
+ }
+
+ public static ResourceClassType fromAddressFamily(AddressFamily addressFamily) {
+ ResourceClassType[] cts;
+ try {
+ cts = ResourceClassType.attrIdsToClassesAndTypes(typeFromAddressFamily(addressFamily));
+ } catch (DnsInvalidAttributeIdentifierException e) {
+ return new ResourceClassType(ResourceRecord.QTYPE_STAR);
+ }
+ return ResourceClassType.getClassAndTypeToQuery(cts);
+ }
+
+ private static String[] typeFromAddressFamily(AddressFamily addressFamily) {
+ switch (addressFamily) {
+ case IPv4:
+ return new String[]{"A", "CNAME"};
+ case IPv6:
+ return new String[]{"AAAA", "CNAME"};
+ default:
+ return new String[]{"A", "AAAA", "CNAME"};
+ }
+ }
+
+
+ public static ResourceClassType[] attrIdsToClassesAndTypes(String[] attrIds)
+ throws DnsInvalidAttributeIdentifierException {
+ if (attrIds == null) {
+ return null;
+ }
+ ResourceClassType[] cts = new ResourceClassType[attrIds.length];
+
+ for (int i = 0; i < attrIds.length; i++) {
+ cts[i] = fromAttrId(attrIds[i]);
+ }
+ return cts;
+ }
+
+ private static ResourceClassType fromAttrId(String attrId)
+ throws DnsInvalidAttributeIdentifierException {
+
+ if (attrId.isEmpty()) {
+ throw new DnsInvalidAttributeIdentifierException(
+ "Attribute ID cannot be empty");
+ }
+ int rrclass;
+ int rrtype;
+ int space = attrId.indexOf(' ');
+
+ // class
+ if (space < 0) {
+ rrclass = ResourceRecord.CLASS_INTERNET;
+ } else {
+ String className = attrId.substring(0, space);
+ rrclass = ResourceRecord.getRrclass(className);
+ if (rrclass < 0) {
+ throw new DnsInvalidAttributeIdentifierException(
+ "Unknown resource record class '" + className + '\'');
+ }
+ }
+
+ // type
+ String typeName = attrId.substring(space + 1);
+ rrtype = ResourceRecord.getType(typeName);
+ if (rrtype < 0) {
+ throw new DnsInvalidAttributeIdentifierException(
+ "Unknown resource record type '" + typeName + '\'');
+ }
+
+ return new ResourceClassType(rrtype);
+ }
+
+ /*
+ * Returns the most restrictive resource record class and type
+ * that may be used to query for records matching cts.
+ * See classAndTypeMatch() for matching rules.
+ */
+ public static ResourceClassType getClassAndTypeToQuery(ResourceClassType[] cts) {
+ int rrclass;
+ int rrtype;
+
+ if (cts == null) {
+ // Query all records.
+ throw new RuntimeException("Internal DNS resolver error");
+ } else if (cts.length == 0) {
+ // No records are requested, but we need to ask for something.
+ rrtype = ResourceRecord.QTYPE_STAR;
+ } else {
+ rrclass = ResourceRecord.CLASS_INTERNET;
+ rrtype = cts[0].rrtype;
+ for (int i = 1; i < cts.length; i++) {
+ if (rrclass != cts[i].rrclass) {
+ throw new RuntimeException("Internal error: Only CLASS_INTERNET is supported");
+ }
+ if (rrtype != cts[i].rrtype) {
+ rrtype = ResourceRecord.QTYPE_STAR;
+ }
+ }
+ }
+ return new ResourceClassType(rrtype);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/ResourceRecord.java Thu Oct 31 16:16:21 2019 +0000
@@ -0,0 +1,615 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.dns.client.internal;
+
+import jdk.dns.client.ex.DnsCommunicationException;
+import jdk.dns.client.ex.DnsInvalidNameException;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+
+/**
+ * The ResourceRecord class represents a DNS resource record.
+ * The string format is based on the master file representation in
+ * RFC 1035.
+ *
+ * @author Scott Seligman
+ */
+
+public class ResourceRecord {
+ /*
+ * Resource record type codes
+ */
+ public static final int TYPE_A = 1;
+ public static final int TYPE_CNAME = 5;
+ public static final int TYPE_AAAA = 28;
+ public static final int TYPE_PTR = 12;
+ static final int QTYPE_STAR = 255; // query type "*"
+
+ private static final int TYPE_NS = 2;
+ private static final int TYPE_SOA = 6;
+ private static final int TYPE_HINFO = 13;
+ private static final int TYPE_MX = 15;
+ private static final int TYPE_TXT = 16;
+ private static final int TYPE_SRV = 33;
+ private static final int TYPE_NAPTR = 35;
+ //static final int QTYPE_AXFR = 252; // zone transfer //Do not want to support that
+
+ /*
+ * Mapping from resource record type codes to type name strings.
+ */
+ private static final String[] rrTypeNames = {
+ null, "A", "NS", null, null,
+ "CNAME", "SOA", null, null, null,
+ null, null, "PTR", "HINFO", null,
+ "MX", "TXT", null, null, null,
+ null, null, null, null, null,
+ null, null, null, "AAAA", null,
+ null, null, null, "SRV", null,
+ "NAPTR"
+ };
+
+ /*
+ * Resource record class codes
+ */
+ public static final int CLASS_INTERNET = 1;
+ static final int QCLASS_STAR = 255; // query class "*"
+
+ /*
+ * Mapping from resource record type codes to class name strings.
+ */
+ private static final String[] rrClassNames = {
+ null, "IN"
+ };
+
+ /*
+ * Maximum number of compression references in labels.
+ * Used to detect compression loops.
+ */
+ private static final int MAXIMUM_COMPRESSION_REFERENCES = 16;
+
+ byte[] msg; // DNS message
+ int msgLen; // msg size (in octets)
+ boolean qSection; // true if this RR is part of question section
+ // and therefore has no ttl or rdata
+ int offset; // offset of RR w/in msg
+ int rrlen; // number of octets in encoded RR
+ DnsName name; // name field of RR, including root label
+ int rrtype; // type field of RR
+ String rrtypeName; // name of rrtype
+ int rrclass; // class field of RR
+ String rrclassName; // name of rrclass
+ int ttl = 0; // ttl field of RR
+ int rdlen = 0; // number of octets of rdata
+ Object rdata = null; // rdata -- most are String, unknown are byte[]
+
+
+ /*
+ * Constructs a new ResourceRecord. The encoded data of the DNS
+ * message is contained in msg; data for this RR begins at msg[offset].
+ * If qSection is true this RR is part of a question section. It's
+ * not a true resource record in that case, but is treated as if it
+ * were a shortened one (with no ttl or rdata). If decodeRdata is
+ * false, the rdata is not decoded (and getRdata() will return null)
+ * unless this is an SOA record.
+ *
+ * @throws CommunicationException if a decoded domain name isn't valid.
+ * @throws ArrayIndexOutOfBoundsException given certain other corrupt data.
+ */
+ ResourceRecord(byte[] msg, int msgLen, int offset,
+ boolean qSection, boolean decodeRdata)
+ throws DnsCommunicationException {
+
+ this.msg = msg;
+ this.msgLen = msgLen;
+ this.offset = offset;
+ this.qSection = qSection;
+ decode(decodeRdata);
+ }
+
+ public String toString() {
+ String text = name + " " + rrclassName + " " + rrtypeName;
+ if (!qSection) {
+ text += " " + ttl + " " +
+ ((rdata != null) ? rdata : "[n/a]");
+ }
+ return text;
+ }
+
+ /*
+ * Returns the name field of this RR, including the root label.
+ */
+ public DnsName getName() {
+ return name;
+ }
+
+ /*
+ * Returns the number of octets in the encoded RR.
+ */
+ public int size() {
+ return rrlen;
+ }
+
+ public int getType() {
+ return rrtype;
+ }
+
+ public int getRrclass() {
+ return rrclass;
+ }
+
+ public Object getRdata() {
+ return rdata;
+ }
+
+
+ public static String getTypeName(int rrtype) {
+ return valueToName(rrtype, rrTypeNames);
+ }
+
+ public static int getType(String typeName) {
+ return nameToValue(typeName, rrTypeNames);
+ }
+
+ public static int getRrclass(String className) {
+ return nameToValue(className, rrClassNames);
+ }
+
+ private static String valueToName(int val, String[] names) {
+ String name = null;
+ if ((val > 0) && (val < names.length)) {
+ name = names[val];
+ } else if (val == QTYPE_STAR) { // QTYPE_STAR == QCLASS_STAR
+ name = "*";
+ }
+ if (name == null) {
+ name = Integer.toString(val);
+ }
+ return name;
+ }
+
+ private static int nameToValue(String name, String[] names) {
+ if (name.isEmpty()) {
+ return -1; // invalid name
+ } else if (name.equals("*")) {
+ return QTYPE_STAR; // QTYPE_STAR == QCLASS_STAR
+ }
+ if (Character.isDigit(name.charAt(0))) {
+ try {
+ return Integer.parseInt(name);
+ } catch (NumberFormatException e) {
+ }
+ }
+ for (int i = 1; i < names.length; i++) {
+ if ((names[i] != null) &&
+ name.equalsIgnoreCase(names[i])) {
+ return i;
+ }
+ }
+ return -1; // unknown name
+ }
+
+ /*
+ * Decodes the binary format of the RR.
+ * May throw ArrayIndexOutOfBoundsException given corrupt data.
+ */
+ private void decode(boolean decodeRdata) throws DnsCommunicationException {
+ int pos = offset; // index of next unread octet
+
+ name = new DnsName(); // NAME
+ pos = decodeName(pos, name);
+
+ rrtype = getUShort(pos); // TYPE
+ rrtypeName = (rrtype < rrTypeNames.length)
+ ? rrTypeNames[rrtype]
+ : null;
+ if (rrtypeName == null) {
+ rrtypeName = Integer.toString(rrtype);
+ }
+ pos += 2;
+
+ rrclass = getUShort(pos); // CLASS
+ rrclassName = (rrclass < rrClassNames.length)
+ ? rrClassNames[rrclass]
+ : null;
+ if (rrclassName == null) {
+ rrclassName = Integer.toString(rrclass);
+ }
+ pos += 2;
+
+ if (!qSection) {
+ ttl = getInt(pos); // TTL
+ pos += 4;
+
+ rdlen = getUShort(pos); // RDLENGTH
+ pos += 2;
+
+ rdata = (decodeRdata || // RDATA
+ (rrtype == TYPE_SOA))
+ ? decodeRdata(pos)
+ : null;
+ if (rdata instanceof DnsName) {
+ rdata = rdata.toString();
+ }
+ pos += rdlen;
+ }
+
+ rrlen = pos - offset;
+
+ msg = null; // free up for GC
+ }
+
+ /*
+ * Returns the 1-byte unsigned value at msg[pos].
+ */
+ private int getUByte(int pos) {
+ return (msg[pos] & 0xFF);
+ }
+
+ /*
+ * Returns the 2-byte unsigned value at msg[pos]. The high
+ * order byte comes first.
+ */
+ private int getUShort(int pos) {
+ return (((msg[pos] & 0xFF) << 8) |
+ (msg[pos + 1] & 0xFF));
+ }
+
+ /*
+ * Returns the 4-byte signed value at msg[pos]. The high
+ * order byte comes first.
+ */
+ private int getInt(int pos) {
+ return ((getUShort(pos) << 16) | getUShort(pos + 2));
+ }
+
+ /*
+ * Returns the 4-byte unsigned value at msg[pos]. The high
+ * order byte comes first.
+ */
+ private long getUInt(int pos) {
+ return (getInt(pos) & 0xffffffffL);
+ }
+
+ /*
+ * Returns the name encoded at msg[pos], including the root label.
+ */
+ private DnsName decodeName(int pos) throws DnsCommunicationException {
+ DnsName n = new DnsName();
+ decodeName(pos, n);
+ return n;
+ }
+
+ /*
+ * Prepends to "n" the domain name encoded at msg[pos], including the root
+ * label. Returns the index into "msg" following the name.
+ */
+ private int decodeName(int pos, DnsName n) throws DnsCommunicationException {
+ int endPos = -1;
+ int level = 0;
+ try {
+ while (true) {
+ if (level > MAXIMUM_COMPRESSION_REFERENCES)
+ throw new IOException("Too many compression references");
+ int typeAndLen = msg[pos] & 0xFF;
+ if (typeAndLen == 0) { // end of name
+ ++pos;
+ n.add(0, "");
+ break;
+ } else if (typeAndLen <= 63) { // regular label
+ ++pos;
+ n.add(0, new String(msg, pos, typeAndLen,
+ StandardCharsets.ISO_8859_1));
+ pos += typeAndLen;
+ } else if ((typeAndLen & 0xC0) == 0xC0) { // name compression
+ ++level;
+ // cater for the case where the name pointed to is itself
+ // compressed: we don't want endPos to be reset by the second
+ // compression level.
+ int ppos = pos;
+ if (endPos == -1) endPos = pos + 2;
+ pos = getUShort(pos) & 0x3FFF;
+ if (debug) {
+ dprint("decode: name compression at " + ppos
+ + " -> " + pos + " endPos=" + endPos);
+ assert endPos > 0;
+ assert pos < ppos;
+ assert pos >= Header.HEADER_SIZE;
+ }
+ } else
+ throw new IOException("Invalid label type: " + typeAndLen);
+ }
+ } catch (IOException | DnsInvalidNameException e) {
+ DnsCommunicationException ce = new DnsCommunicationException(
+ "DNS error: malformed packet");
+ ce.initCause(e);
+ throw ce;
+ }
+ if (endPos == -1)
+ endPos = pos;
+ return endPos;
+ }
+
+ /*
+ * Returns the rdata encoded at msg[pos]. The format is dependent
+ * on the rrtype and rrclass values, which have already been set.
+ * The length of the encoded data is rdlen, which has already been
+ * set.
+ * The rdata of records with unknown type/class combinations is
+ * returned in a newly-allocated byte array.
+ */
+ private Object decodeRdata(int pos) throws DnsCommunicationException {
+ if (rrclass == CLASS_INTERNET) {
+ switch (rrtype) {
+ case TYPE_A:
+ return decodeA(pos);
+ case TYPE_AAAA:
+ return decodeAAAA(pos);
+ case TYPE_CNAME:
+ case TYPE_NS:
+ case TYPE_PTR:
+ return decodeName(pos);
+ case TYPE_MX:
+ return decodeMx(pos);
+ case TYPE_SOA:
+ return decodeSoa(pos);
+ case TYPE_SRV:
+ return decodeSrv(pos);
+ case TYPE_NAPTR:
+ return decodeNaptr(pos);
+ case TYPE_TXT:
+ return decodeTxt(pos);
+ case TYPE_HINFO:
+ return decodeHinfo(pos);
+ }
+ }
+ // Unknown RR type/class
+ if (debug) {
+ dprint("Unknown RR type for RR data: " + rrtype + " rdlen=" + rdlen
+ + ", pos=" + pos + ", msglen=" + msg.length + ", remaining="
+ + (msg.length - pos));
+ }
+ byte[] rd = new byte[rdlen];
+ System.arraycopy(msg, pos, rd, 0, rdlen);
+ return rd;
+ }
+
+ /*
+ * Returns the rdata of an MX record that is encoded at msg[pos].
+ */
+ private String decodeMx(int pos) throws DnsCommunicationException {
+ int preference = getUShort(pos);
+ pos += 2;
+ DnsName name = decodeName(pos);
+ return (preference + " " + name);
+ }
+
+ /*
+ * Returns the rdata of an SOA record that is encoded at msg[pos].
+ */
+ private String decodeSoa(int pos) throws DnsCommunicationException {
+ DnsName mname = new DnsName();
+ pos = decodeName(pos, mname);
+ DnsName rname = new DnsName();
+ pos = decodeName(pos, rname);
+
+ long serial = getUInt(pos);
+ pos += 4;
+ long refresh = getUInt(pos);
+ pos += 4;
+ long retry = getUInt(pos);
+ pos += 4;
+ long expire = getUInt(pos);
+ pos += 4;
+ long minimum = getUInt(pos); // now used as negative TTL
+ pos += 4;
+
+ return (mname + " " + rname + " " + serial + " " +
+ refresh + " " + retry + " " + expire + " " + minimum);
+ }
+
+ /*
+ * Returns the rdata of an SRV record that is encoded at msg[pos].
+ * See RFC 2782.
+ */
+ private String decodeSrv(int pos) throws DnsCommunicationException {
+ int priority = getUShort(pos);
+ pos += 2;
+ int weight = getUShort(pos);
+ pos += 2;
+ int port = getUShort(pos);
+ pos += 2;
+ DnsName target = decodeName(pos);
+ return (priority + " " + weight + " " + port + " " + target);
+ }
+
+ /*
+ * Returns the rdata of an NAPTR record that is encoded at msg[pos].
+ * See RFC 2915.
+ */
+ private String decodeNaptr(int pos) throws DnsCommunicationException {
+ int order = getUShort(pos);
+ pos += 2;
+ int preference = getUShort(pos);
+ pos += 2;
+ StringBuffer flags = new StringBuffer();
+ pos += decodeCharString(pos, flags);
+ StringBuffer services = new StringBuffer();
+ pos += decodeCharString(pos, services);
+ StringBuffer regexp = new StringBuffer(rdlen);
+ pos += decodeCharString(pos, regexp);
+ DnsName replacement = decodeName(pos);
+
+ return (order + " " + preference + " " + flags + " " +
+ services + " " + regexp + " " + replacement);
+ }
+
+ /*
+ * Returns the rdata of a TXT record that is encoded at msg[pos].
+ * The rdata consists of one or more <character-string>s.
+ */
+ private String decodeTxt(int pos) {
+ StringBuffer buf = new StringBuffer(rdlen);
+ int end = pos + rdlen;
+ while (pos < end) {
+ pos += decodeCharString(pos, buf);
+ if (pos < end) {
+ buf.append(' ');
+ }
+ }
+ return buf.toString();
+ }
+
+ /*
+ * Returns the rdata of an HINFO record that is encoded at msg[pos].
+ * The rdata consists of two <character-string>s.
+ */
+ private String decodeHinfo(int pos) {
+ StringBuffer buf = new StringBuffer(rdlen);
+ pos += decodeCharString(pos, buf);
+ buf.append(' ');
+ pos += decodeCharString(pos, buf);
+ return buf.toString();
+ }
+
+ /*
+ * Decodes the <character-string> at msg[pos] and adds it to buf.
+ * If the string contains one of the meta-characters ' ', '\\', or
+ * '"', then the result is quoted and any embedded '\\' or '"'
+ * chars are escaped with '\\'. Empty strings are also quoted.
+ * Returns the size of the encoded string, including the initial
+ * length octet.
+ */
+ private int decodeCharString(int pos, StringBuffer buf) {
+ int start = buf.length(); // starting index of this string
+ int len = getUByte(pos++); // encoded string length
+ boolean quoted = (len == 0); // quote string if empty
+ for (int i = 0; i < len; i++) {
+ int c = getUByte(pos++);
+ quoted |= (c == ' ');
+ if ((c == '\\') || (c == '"')) {
+ quoted = true;
+ buf.append('\\');
+ }
+ buf.append((char) c);
+ }
+ if (quoted) {
+ buf.insert(start, '"');
+ buf.append('"');
+ }
+ return (len + 1); // size includes initial octet
+ }
+
+ /*
+ * Returns the rdata of an A record, in dotted-decimal format,
+ * that is encoded at msg[pos].
+ */
+ private String decodeA(int pos) {
+ return ((msg[pos] & 0xff) + "." +
+ (msg[pos + 1] & 0xff) + "." +
+ (msg[pos + 2] & 0xff) + "." +
+ (msg[pos + 3] & 0xff));
+ }
+
+ /*
+ * Returns the rdata of an AAAA record, in colon-separated format,
+ * that is encoded at msg[pos]. For example: 4321:0:1:2:3:4:567:89ab.
+ * See RFCs 1886 and 2373.
+ */
+ private String decodeAAAA(int pos) {
+ int[] addr6 = new int[8]; // the unsigned 16-bit words of the address
+ for (int i = 0; i < 8; i++) {
+ addr6[i] = getUShort(pos);
+ pos += 2;
+ }
+
+ // Find longest sequence of two or more zeros, to compress them.
+ int curBase = -1;
+ int curLen = 0;
+ int bestBase = -1;
+ int bestLen = 0;
+ for (int i = 0; i < 8; i++) {
+ if (addr6[i] == 0) {
+ if (curBase == -1) { // new sequence
+ curBase = i;
+ curLen = 1;
+ } else { // extend sequence
+ ++curLen;
+ if ((curLen >= 2) && (curLen > bestLen)) {
+ bestBase = curBase;
+ bestLen = curLen;
+ }
+ }
+ } else { // not in sequence
+ curBase = -1;
+ }
+ }
+
+ // If addr begins with at least 6 zeros and is not :: or ::1,
+ // or with 5 zeros followed by 0xffff, use the text format for
+ // IPv4-compatible or IPv4-mapped addresses.
+ if (bestBase == 0) {
+ if ((bestLen == 6) ||
+ ((bestLen == 7) && (addr6[7] > 1))) {
+ return ("::" + decodeA(pos - 4));
+ } else if ((bestLen == 5) && (addr6[5] == 0xffff)) {
+ return ("::ffff:" + decodeA(pos - 4));
+ }
+ }
+
+ // If bestBase != -1, compress zeros in [bestBase, bestBase+bestLen)
+ boolean compress = (bestBase != -1);
+
+ StringBuilder sb = new StringBuilder(40);
+ if (bestBase == 0) {
+ sb.append(':');
+ }
+ for (int i = 0; i < 8; i++) {
+ if (!compress || (i < bestBase) || (i >= bestBase + bestLen)) {
+ sb.append(Integer.toHexString(addr6[i]));
+ if (i < 7) {
+ sb.append(':');
+ }
+ } else if (compress && (i == bestBase)) { // first compressed zero
+ sb.append(':');
+ }
+ }
+
+ return sb.toString();
+ }
+
+ //-------------------------------------------------------------------------
+
+ private static final boolean debug = false;
+
+ private static void dprint(String mess) {
+ if (debug) {
+ System.err.println("DNS: " + mess);
+ }
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/ResourceRecords.java Thu Oct 31 16:16:21 2019 +0000
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.dns.client.internal;
+
+import jdk.dns.client.ex.DnsResolverException;
+
+import java.util.Vector;
+
+
+/**
+ * The ResourceRecords class represents the resource records in the
+ * four sections of a DNS message.
+ * <p>
+ * The additional records section is currently ignored.
+ *
+ * @author Scott Seligman
+ */
+
+
+public class ResourceRecords {
+
+ // Four sections: question, answer, authority, additional.
+ // The question section is treated as being made up of (shortened)
+ // resource records, although this isn't technically how it's defined.
+ Vector<ResourceRecord> question = new Vector<>();
+ public Vector<ResourceRecord> answer = new Vector<>();
+ Vector<ResourceRecord> authority = new Vector<>();
+ // TODO: Try to reuse additional section to avoid additional CNAME lookups
+ public Vector<ResourceRecord> additional = new Vector<>();
+
+
+ public boolean hasAddressOrAlias() {
+ return answer.stream().anyMatch(
+ rr -> rr.rrtype == ResourceRecord.TYPE_A
+ || rr.rrtype == ResourceRecord.TYPE_AAAA
+ || rr.rrtype == ResourceRecord.TYPE_CNAME);
+ }
+
+ /*
+ * True if these resource records are from a zone transfer. In
+ * that case only answer records are read (as per
+ * draft-ietf-dnsext-axfr-clarify-02.txt). Also, the rdata of
+ * those answer records is not decoded (for efficiency) except
+ * for SOA records.
+ */
+ boolean zoneXfer;
+
+ /*
+ * Returns a representation of the resource records in a DNS message.
+ * Does not modify or store a reference to the msg array.
+ */
+ ResourceRecords(byte[] msg, int msgLen, Header hdr, boolean zoneXfer)
+ throws DnsResolverException {
+ if (zoneXfer) {
+ answer.ensureCapacity(8192); // an arbitrary "large" number
+ }
+ this.zoneXfer = zoneXfer;
+ add(msg, msgLen, hdr);
+ }
+
+ /*
+ * Returns the type field of the first answer record, or -1 if
+ * there are no answer records.
+ */
+ int getFirstAnsType() {
+ if (answer.size() == 0) {
+ return -1;
+ }
+ return answer.firstElement().getType();
+ }
+
+ /*
+ * Returns the type field of the last answer record, or -1 if
+ * there are no answer records.
+ */
+ int getLastAnsType() {
+ if (answer.size() == 0) {
+ return -1;
+ }
+ return answer.lastElement().getType();
+ }
+
+ /*
+ * Decodes the resource records in a DNS message and adds
+ * them to this object.
+ * Does not modify or store a reference to the msg array.
+ */
+ void add(byte[] msg, int msgLen, Header hdr) throws DnsResolverException {
+
+ ResourceRecord rr;
+ int pos = Header.HEADER_SIZE; // current offset into msg
+
+ try {
+ for (int i = 0; i < hdr.numQuestions; i++) {
+ rr = new ResourceRecord(msg, msgLen, pos, true, false);
+ if (!zoneXfer) {
+ question.addElement(rr);
+ }
+ pos += rr.size();
+ }
+
+ for (int i = 0; i < hdr.numAnswers; i++) {
+ rr = new ResourceRecord(
+ msg, msgLen, pos, false, !zoneXfer);
+ answer.addElement(rr);
+ pos += rr.size();
+ }
+
+ if (zoneXfer) {
+ return;
+ }
+
+ for (int i = 0; i < hdr.numAuthorities; i++) {
+ rr = new ResourceRecord(msg, msgLen, pos, false, true);
+ authority.addElement(rr);
+ pos += rr.size();
+ }
+
+ // TODO: Might be useful
+ // The additional records section is currently ignored.
+// for (int i = 0; i < hdr.numAdditionals; i++) {
+// rr = new ResourceRecord(msg, msgLen, pos, false, true);
+// additional.addElement(rr);
+// pos += rr.size();
+// }
+
+ } catch (IndexOutOfBoundsException e) {
+ throw new DnsResolverException(
+ "DNS error: corrupted message");
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/util/ReloadTracker.java Thu Oct 31 16:16:21 2019 +0000
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.dns.client.internal.util;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class ReloadTracker {
+
+ public static ReloadTracker newInstance(Path fileToTrack, long refreshTimeoutMillis) {
+ Path absolutePath = fileToTrack.toAbsolutePath();
+ return new ReloadTracker(absolutePath, refreshTimeoutMillis);
+ }
+
+ public static class ReloadStatus {
+ private final boolean isReloadNeeded;
+ private final long lastModificationTimestamp;
+ private final boolean fileExists;
+
+ ReloadStatus(boolean fileExists, boolean isReloadNeeded, long lastModificationTimestamp) {
+ this.isReloadNeeded = fileExists && isReloadNeeded;
+ this.lastModificationTimestamp = lastModificationTimestamp;
+ this.fileExists = fileExists;
+ }
+
+ public boolean isFileExists() {
+ return fileExists;
+ }
+
+ public boolean isReloadNeeded() {
+ return isReloadNeeded;
+ }
+
+ public long getLastModificationTimestamp() {
+ return lastModificationTimestamp;
+ }
+ }
+
+
+ private ReloadTracker(Path filePath, long refreshTimeoutMillis) {
+ this.filePath = filePath;
+ this.refreshTimeoutMillis = refreshTimeoutMillis;
+ }
+
+
+ public ReloadStatus getReloadStatus() {
+ boolean fileExists;
+ var pa = (PrivilegedAction<Boolean>) () -> filePath.toFile().isFile();
+ fileExists = System.getSecurityManager() == null ? pa.run() : AccessController.doPrivileged(pa);
+
+ long lastModificationTime = -1;
+ long lastReload = lastRefreshed.get();
+
+ // Do not update lastModification time if file doesn't exist
+ if (fileExists) {
+ try {
+ var pea = (PrivilegedExceptionAction<Long>) () -> Files.getLastModifiedTime(filePath).toMillis();
+ lastModificationTime = System.getSecurityManager() == null ? pea.run()
+ : AccessController.doPrivileged(pea);
+ } catch (Exception e) {
+ // In case of Exception the tracked file will be reloaded, ie lastModificationTime == -1
+ }
+ }
+ return new ReloadStatus(fileExists,
+ (lastModificationTime != lastSeenChanged.get()) ||
+ (System.currentTimeMillis() - lastReload > refreshTimeoutMillis),
+ lastModificationTime);
+ }
+
+ public void updateTimestamps(ReloadStatus rs) {
+ lastSeenChanged.set(rs.lastModificationTimestamp);
+ lastRefreshed.set(System.currentTimeMillis());
+ }
+
+ // Last timestamp when tracked file has been reloaded by consumer of this utility class
+ private final AtomicLong lastRefreshed = new AtomicLong(-1);
+ // Last processed FS last modified timestamp
+ private final AtomicLong lastSeenChanged = new AtomicLong(-1);
+ // Refresh timeout
+ private final long refreshTimeoutMillis;
+ // Path of the tracked file
+ private final Path filePath;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.dns.client/share/classes/module-info.java Thu Oct 31 16:16:21 2019 +0000
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import jdk.dns.client.internal.DnsNameService;
+
+import java.net.InetAddress;
+
+module jdk.dns.client {
+ exports jdk.dns.client;
+ exports jdk.dns.client.ex;
+ exports jdk.dns.client.internal; // For debugging
+ provides InetAddress.NameService with DnsNameService;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.dns.client/unix/classes/jdk/dns/conf/DnsResolverConfiguration.java Thu Oct 31 16:16:21 2019 +0000
@@ -0,0 +1,286 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.dns.conf;
+
+import jdk.dns.client.internal.util.ReloadTracker;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedExceptionAction;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.StringTokenizer;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/*
+ * An implementation of DnsResolverConfiguration for Solaris
+ * and Linux.
+ */
+
+public class DnsResolverConfiguration {
+ // Lock held whilst loading configuration or checking
+ private static final ReadWriteLock LOCK = new ReentrantReadWriteLock();
+
+ //
+ private static String RESOLV_CONF_LOCATION = java.security.AccessController.doPrivileged(
+ (PrivilegedAction<String>) () -> System.getProperty("jdk.dns.client.resolv.conf", "/etc/resolv.conf"));
+ private static final ReloadTracker RESOLVE_CONF_TRACKER;
+
+
+ // Cache timeout (300 seconds) - should be converted into property
+ // or configured as preference in the future.
+ private static final int TIMEOUT = 300_000;
+
+ // Parse /etc/resolv.conf to get the values for a particular
+ // keyword.
+ //
+ private List<String> resolvconf(String keyword,
+ int maxperkeyword,
+ int maxkeywords) {
+ LinkedList<String> ll = new LinkedList<>();
+
+ try {
+ BufferedReader in =
+ new BufferedReader(new FileReader(RESOLV_CONF_LOCATION));
+ String line;
+ while ((line = in.readLine()) != null) {
+ int maxvalues = maxperkeyword;
+ if (line.isEmpty())
+ continue;
+ if (line.charAt(0) == '#' || line.charAt(0) == ';')
+ continue;
+ if (!line.startsWith(keyword))
+ continue;
+ String value = line.substring(keyword.length());
+ if (value.isEmpty())
+ continue;
+ if (value.charAt(0) != ' ' && value.charAt(0) != '\t')
+ continue;
+ StringTokenizer st = new StringTokenizer(value, " \t");
+ while (st.hasMoreTokens()) {
+ String val = st.nextToken();
+ if (val.charAt(0) == '#' || val.charAt(0) == ';') {
+ break;
+ }
+ if ("nameserver".equals(keyword)) {
+ if (val.indexOf(':') >= 0 &&
+ val.indexOf('.') < 0 && // skip for IPv4 literals with port
+ val.indexOf('[') < 0 &&
+ val.indexOf(']') < 0) {
+ // IPv6 literal, in non-BSD-style.
+ val = "[" + val + "]";
+ }
+ }
+ ll.add(val);
+ if (--maxvalues == 0) {
+ break;
+ }
+ }
+ if (--maxkeywords == 0) {
+ break;
+ }
+ }
+ in.close();
+ } catch (IOException ioe) {
+ // problem reading value
+ }
+
+ return Collections.unmodifiableList(ll);
+ }
+
+ private volatile List<String> searchlist = Collections.emptyList();
+ private volatile List<String> nameservers = Collections.emptyList();
+ private volatile String domain = "";
+
+
+ // Load DNS configuration from OS
+ private void loadConfig() {
+ LOCK.readLock().lock();
+ try {
+ var rs = RESOLVE_CONF_TRACKER.getReloadStatus();
+ if (!rs.isReloadNeeded() || !rs.isFileExists()) {
+ return;
+ }
+ } finally {
+ LOCK.readLock().unlock();
+ }
+
+ LOCK.writeLock().lock();
+ try {
+ var rs = RESOLVE_CONF_TRACKER.getReloadStatus();
+ // Check if reload is needed again
+ if (rs.isReloadNeeded()) {
+ if (rs.isFileExists()) {
+ // get the name servers from /etc/resolv.conf
+ nameservers =
+ java.security.AccessController.doPrivileged(
+ (PrivilegedAction<List<String>>) () -> {
+ // typically MAXNS is 3 but we've picked 5 here
+ // to allow for additional servers if required.
+ return resolvconf("nameserver", 1, 5);
+ });
+
+ // get the search list (or domain)
+ searchlist = getSearchList();
+
+ // update the timestamp on the configuration
+ RESOLVE_CONF_TRACKER.updateTimestamps(rs);
+ } else {
+ nameservers = Collections.emptyList();
+ searchlist = Collections.emptyList();
+ }
+ }
+ } finally {
+ LOCK.writeLock().unlock();
+ }
+ }
+
+ // obtain search list or local domain
+ private List<String> getSearchList() {
+
+ List<String> sl;
+
+ // first try the search keyword in /etc/resolv.conf
+
+ /* run */
+ sl = java.security.AccessController.doPrivileged(
+ (PrivilegedAction<List<String>>) () -> {
+ List<String> ll;
+
+ // first try search keyword (max 6 domains)
+ ll = resolvconf("search", 6, 1);
+ if (ll.size() > 0) {
+ domain = ll.get(0);
+ return ll;
+ }
+
+ return null;
+
+ });
+ if (sl != null) {
+ return sl;
+ }
+
+ // No search keyword so use local domain
+
+
+ // LOCALDOMAIN has absolute priority on Solaris
+
+ String localDomain = localDomain0();
+ if (localDomain != null && !localDomain.isEmpty()) {
+ sl = new LinkedList<>();
+ sl.add(localDomain);
+ return Collections.unmodifiableList(sl);
+ }
+
+ // try domain keyword in /etc/resolv.conf
+
+ /* run */
+ sl = java.security.AccessController.doPrivileged(
+ (PrivilegedAction<List<String>>) () -> {
+ List<String> ll;
+
+ ll = resolvconf("domain", 1, 1);
+ if (ll.size() > 0) {
+ domain = ll.get(0);
+ return ll;
+ }
+ return null;
+
+ });
+ if (sl != null) {
+ // sl is already UnmodifiableList
+ return sl;
+ }
+
+ // no local domain so try fallback (RPC) domain or
+ // hostName
+
+ sl = new LinkedList<>();
+ String domain = fallbackDomain0();
+ if (domain != null && !domain.isEmpty()) {
+ sl.add(domain);
+ }
+
+ return Collections.unmodifiableList(sl);
+ }
+
+
+ // ----
+
+ public DnsResolverConfiguration() {
+ }
+
+ @SuppressWarnings("unchecked")
+ public List<String> searchlist() {
+ loadConfig();
+ // List is unmodifiable
+ return searchlist;
+ }
+
+ @SuppressWarnings("unchecked")
+ public List<String> nameservers() {
+ loadConfig();
+ // List is unmodifiable
+ return nameservers;
+ }
+
+ public String domain() {
+ loadConfig();
+ return domain;
+ }
+
+ // --- Native methods --
+
+ static native String localDomain0();
+
+ static native String fallbackDomain0();
+
+ static {
+ var pa = (PrivilegedAction<Void>) () -> {System.loadLibrary("resolver"); return null;};
+ if (System.getSecurityManager() == null) {
+ pa.run();
+ } else {
+ AccessController.doPrivileged(pa);
+ }
+ var pea = (PrivilegedExceptionAction<ReloadTracker>) () ->
+ ReloadTracker.newInstance(Paths.get(RESOLV_CONF_LOCATION), TIMEOUT);
+ // TODO: Revisit
+ try {
+ RESOLVE_CONF_TRACKER = System.getSecurityManager() == null ? pea.run()
+ : AccessController.doPrivileged(pea);
+ } catch (Exception e) {
+ throw new RuntimeException("Can't instantiate resolver configuration file tracker", e);
+ }
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.dns.client/unix/native/libresolver/DnsResolverConfiguration.c Thu Oct 31 16:16:21 2019 +0000
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+
+#ifdef __solaris__
+#include <sys/systeminfo.h>
+#endif
+
+#include <string.h>
+
+#include "jni.h"
+
+#ifndef MAXDNAME
+#define MAXDNAME 1025
+#endif
+
+
+/*
+ * Class: jdk_dns_conf_ResolverConfiguration
+ * Method: localDomain0
+ * Signature: ()Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL
+Java_jdk_dns_conf_DnsResolverConfiguration_localDomain0(JNIEnv *env, jclass cls)
+{
+ /*
+ * On Solaris the LOCALDOMAIN environment variable has absolute
+ * priority.
+ */
+#ifdef __solaris__
+ {
+ char *cp = getenv("LOCALDOMAIN");
+ if (cp != NULL) {
+ jstring s = (*env)->NewStringUTF(env, cp);
+ return s;
+ }
+ }
+#endif
+ return (jstring)NULL;
+}
+
+/*
+ * Class: jdk_dns_conf_DnsResolverConfiguration
+ * Method: loadConfig0
+ * Signature: ()Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL
+Java_jdk_dns_conf_DnsResolverConfiguration_fallbackDomain0(JNIEnv *env, jclass cls)
+{
+ char buf[MAXDNAME];
+
+ /*
+ * On Solaris if domain or search directives aren't specified
+ * in /etc/resolv.conf then sysinfo or gethostname is used to
+ * determine the domain name.
+ *
+ * On Linux if domain or search directives aren't specified
+ * then gethostname is used.
+ */
+
+#ifdef __solaris__
+ {
+ int ret = sysinfo(SI_SRPC_DOMAIN, buf, sizeof(buf));
+
+ if ((ret > 0) && (ret<sizeof(buf))) {
+ char *cp;
+ jstring s;
+
+ if (buf[0] == '+') {
+ buf[0] = '.';
+ }
+ cp = strchr(buf, '.');
+ if (cp == NULL) {
+ s = (*env)->NewStringUTF(env, buf);
+ } else {
+ s = (*env)->NewStringUTF(env, cp+1);
+ }
+ return s;
+ }
+ }
+#endif
+
+ if (gethostname(buf, sizeof(buf)) == 0) {
+ char *cp;
+
+ /* gethostname doesn't null terminate if insufficient space */
+ buf[sizeof(buf)-1] = '\0';
+
+ cp = strchr(buf, '.');
+ if (cp != NULL) {
+ jstring s = (*env)->NewStringUTF(env, cp+1);
+ return s;
+ }
+ }
+
+ return (jstring)NULL;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.dns.client/windows/classes/jdk/dns/conf/DnsResolverConfiguration.java Thu Oct 31 16:16:21 2019 +0000
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.dns.conf;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+public class DnsResolverConfiguration {
+ // Lock held whilst loading configuration or checking
+ private static ReentrantLock lock = new ReentrantLock();
+
+ // Addresses have changed
+ private static boolean changed = false;
+
+ // Time of last refresh.
+ private static long lastRefresh = -1;
+
+ // Cache timeout (120 seconds) - should be converted into property
+ // or configured as preference in the future.
+ private static final int TIMEOUT = 120000;
+
+ // DNS suffix list and name servers populated by native method
+ private static String os_searchlist;
+ private static String os_nameservers;
+
+ // Cached lists
+ private static LinkedList<String> searchlist;
+ private static LinkedList<String> nameservers;
+
+ // Parse string that consists of token delimited by space or commas
+ // and return LinkedHashMap
+ private LinkedList<String> stringToList(String str) {
+ LinkedList<String> ll = new LinkedList<>();
+
+ // comma and space are valid delimiters
+ StringTokenizer st = new StringTokenizer(str, ", ");
+ while (st.hasMoreTokens()) {
+ String s = st.nextToken();
+ if (!ll.contains(s)) {
+ ll.add(s);
+ }
+ }
+ return ll;
+ }
+
+ // Load DNS configuration from OS
+
+ private void loadConfig() {
+ assert lock.isHeldByCurrentThread();
+
+ // if address have changed then DNS probably changed as well;
+ // otherwise check if cached settings have expired.
+ //
+ if (changed) {
+ changed = false;
+ } else {
+ if (lastRefresh >= 0) {
+ long currTime = System.currentTimeMillis();
+ if ((currTime - lastRefresh) < TIMEOUT) {
+ return;
+ }
+ }
+ }
+
+ // load DNS configuration, update timestamp, create
+ // new HashMaps from the loaded configuration
+ //
+ loadDNSconfig0();
+
+ lastRefresh = System.currentTimeMillis();
+ searchlist = stringToList(os_searchlist);
+ nameservers = stringToList(os_nameservers);
+ os_searchlist = null; // can be GC'ed
+ os_nameservers = null;
+ }
+
+ DnsResolverConfiguration() {
+ }
+
+ @SuppressWarnings("unchecked") // clone()
+ public List<String> searchlist() {
+ lock.lock();
+ try {
+ loadConfig();
+
+ // List is mutable so return a shallow copy
+ return (List<String>) searchlist.clone();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ @SuppressWarnings("unchecked") // clone()
+ public List<String> nameservers() {
+ lock.lock()
+ try {
+ loadConfig();
+
+ // List is mutable so return a shallow copy
+ return (List<String>) nameservers.clone();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ // --- Address Change Listener
+
+ static class AddressChangeListener extends Thread {
+ public void run() {
+ for (; ; ) {
+ // wait for configuration to change
+ if (notifyAddrChange0() != 0)
+ return;
+ lock.lock();
+ try {
+ changed = true;
+ } finally {
+ lock.unlock();
+ }
+ }
+ }
+ }
+
+
+ // --- Native methods --
+
+ static native void init0();
+
+ static native void loadDNSconfig0();
+
+ static native int notifyAddrChange0();
+
+ static {
+ System.loadLibrary("resolver");
+ init0();
+
+ // start the address listener thread
+ AddressChangeListener thr = new AddressChangeListener();
+ thr.setDaemon(true);
+ thr.start();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.dns.client/windows/native/libresolve/DnsResolverConfiguration.c Thu Oct 31 16:16:21 2019 +0000
@@ -0,0 +1,304 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include <stdlib.h>
+#include <windows.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <iprtrmib.h>
+#include <time.h>
+#include <assert.h>
+#include <iphlpapi.h>
+
+#include "jni_util.h"
+
+#define MAX_STR_LEN 256
+
+#define STS_NO_CONFIG 0x0 /* no configuration found */
+#define STS_SL_FOUND 0x1 /* search list found */
+#define STS_NS_FOUND 0x2 /* name servers found */
+#define STS_ERROR -1 /* error return lodConfig failed memory allccation failure*/
+
+#define IS_SL_FOUND(sts) (sts & STS_SL_FOUND)
+#define IS_NS_FOUND(sts) (sts & STS_NS_FOUND)
+
+/* JNI ids */
+static jfieldID searchlistID;
+static jfieldID nameserversID;
+
+/*
+ * Utility routine to append s2 to s1 with a space delimiter.
+ * strappend(s1="abc", "def") => "abc def"
+ * strappend(s1="", "def") => "def
+ */
+void strappend(char *s1, char *s2) {
+ size_t len;
+
+ if (s2[0] == '\0') /* nothing to append */
+ return;
+
+ len = strlen(s1)+1;
+ if (s1[0] != 0) /* needs space character */
+ len++;
+ if (len + strlen(s2) > MAX_STR_LEN) /* insufficient space */
+ return;
+
+ if (s1[0] != 0) {
+ strcat(s1, " ");
+ }
+ strcat(s1, s2);
+}
+
+/*
+ * Windows 2000/XP
+ *
+ * Use registry approach based on settings described in Appendix C
+ * of "Microsoft Windows 2000 TCP/IP Implementation Details".
+ *
+ * DNS suffix list is obtained from SearchList registry setting. If
+ * this is not specified we compile suffix list based on the
+ * per-connection domain suffix.
+ *
+ * DNS name servers and domain settings are on a per-connection
+ * basic. We therefore enumerate the network adapters to get the
+ * names of each adapter and then query the corresponding registry
+ * settings to obtain NameServer/DhcpNameServer and Domain/DhcpDomain.
+ */
+static int loadConfig(char *sl, char *ns) {
+ IP_ADAPTER_INFO *adapterP;
+ ULONG size;
+ DWORD ret;
+ DWORD dwLen;
+ ULONG ulType;
+ char result[MAX_STR_LEN];
+ HANDLE hKey;
+ int gotSearchList = 0;
+
+ /*
+ * First see if there is a global suffix list specified.
+ */
+ ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
+ "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters",
+ 0,
+ KEY_READ,
+ (PHKEY)&hKey);
+ if (ret == ERROR_SUCCESS) {
+ dwLen = sizeof(result);
+ ret = RegQueryValueEx(hKey, "SearchList", NULL, &ulType,
+ (LPBYTE)&result, &dwLen);
+ if (ret == ERROR_SUCCESS) {
+ assert(ulType == REG_SZ);
+ if (strlen(result) > 0) {
+ strappend(sl, result);
+ gotSearchList = 1;
+ }
+ }
+ RegCloseKey(hKey);
+ }
+
+ /*
+ * Ask the IP Helper library to enumerate the adapters
+ */
+ size = sizeof(IP_ADAPTER_INFO);
+ adapterP = (IP_ADAPTER_INFO *)malloc(size);
+ if (adapterP == NULL) {
+ return STS_ERROR;
+ }
+ ret = GetAdaptersInfo(adapterP, &size);
+ if (ret == ERROR_BUFFER_OVERFLOW) {
+ IP_ADAPTER_INFO *newAdapterP = (IP_ADAPTER_INFO *)realloc(adapterP, size);
+ if (newAdapterP == NULL) {
+ free(adapterP);
+ return STS_ERROR;
+ }
+ adapterP = newAdapterP;
+
+ ret = GetAdaptersInfo(adapterP, &size);
+ }
+
+ /*
+ * Iterate through the list of adapters as registry settings are
+ * keyed on the adapter name (GUID).
+ */
+ if (ret == ERROR_SUCCESS) {
+ IP_ADAPTER_INFO *curr = adapterP;
+ while (curr != NULL) {
+ char key[MAX_STR_LEN];
+
+ sprintf(key,
+ "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces\\%s",
+ curr->AdapterName);
+
+ ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
+ key,
+ 0,
+ KEY_READ,
+ (PHKEY)&hKey);
+ if (ret == ERROR_SUCCESS) {
+ DWORD enableDhcp = 0;
+
+ /*
+ * Is DHCP enabled on this interface
+ */
+ dwLen = sizeof(enableDhcp);
+ ret = RegQueryValueEx(hKey, "EnableDhcp", NULL, &ulType,
+ (LPBYTE)&enableDhcp, &dwLen);
+
+ /*
+ * If we don't have the suffix list when get the Domain
+ * or DhcpDomain. If DHCP is enabled then Domain overides
+ * DhcpDomain
+ */
+ if (!gotSearchList) {
+ result[0] = '\0';
+ dwLen = sizeof(result);
+ ret = RegQueryValueEx(hKey, "Domain", NULL, &ulType,
+ (LPBYTE)&result, &dwLen);
+ if (((ret != ERROR_SUCCESS) || (strlen(result) == 0)) &&
+ enableDhcp) {
+ dwLen = sizeof(result);
+ ret = RegQueryValueEx(hKey, "DhcpDomain", NULL, &ulType,
+ (LPBYTE)&result, &dwLen);
+ }
+ if (ret == ERROR_SUCCESS) {
+ assert(ulType == REG_SZ);
+ strappend(sl, result);
+ }
+ }
+
+ /*
+ * Get DNS servers based on NameServer or DhcpNameServer
+ * registry setting. If NameServer is set then it overrides
+ * DhcpNameServer (even if DHCP is enabled).
+ */
+ result[0] = '\0';
+ dwLen = sizeof(result);
+ ret = RegQueryValueEx(hKey, "NameServer", NULL, &ulType,
+ (LPBYTE)&result, &dwLen);
+ if (((ret != ERROR_SUCCESS) || (strlen(result) == 0)) &&
+ enableDhcp) {
+ dwLen = sizeof(result);
+ ret = RegQueryValueEx(hKey, "DhcpNameServer", NULL, &ulType,
+ (LPBYTE)&result, &dwLen);
+ }
+ if (ret == ERROR_SUCCESS) {
+ assert(ulType == REG_SZ);
+ strappend(ns, result);
+ }
+
+ /*
+ * Finished with this registry key
+ */
+ RegCloseKey(hKey);
+ }
+
+ /*
+ * Onto the next adapeter
+ */
+ curr = curr->Next;
+ }
+ }
+
+ /*
+ * Free the adpater structure
+ */
+ if (adapterP) {
+ free(adapterP);
+ }
+
+ return STS_SL_FOUND & STS_NS_FOUND;
+}
+
+
+/*
+ * Initialize JNI field IDs.
+ */
+JNIEXPORT void JNICALL
+Java_jdk_dns_conf_DnsResolverConfiguration_init0(JNIEnv *env, jclass cls)
+{
+ searchlistID = (*env)->GetStaticFieldID(env, cls, "os_searchlist",
+ "Ljava/lang/String;");
+ CHECK_NULL(searchlistID);
+ nameserversID = (*env)->GetStaticFieldID(env, cls, "os_nameservers",
+ "Ljava/lang/String;");
+}
+
+/*
+ * Class: jdk_dns_conf_DnsResolverConfguration
+ * Method: loadConfig0
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL
+Java_jdk_dns_conf_DnsResolverConfiguration_loadDNSconfig0(JNIEnv *env, jclass cls)
+{
+ char searchlist[MAX_STR_LEN];
+ char nameservers[MAX_STR_LEN];
+ jstring obj;
+
+ searchlist[0] = '\0';
+ nameservers[0] = '\0';
+
+ if (loadConfig(searchlist, nameservers) != STS_ERROR) {
+
+ /*
+ * Populate static fields in jdk.dns.conf.ResolverConfiguration
+ */
+ obj = (*env)->NewStringUTF(env, searchlist);
+ CHECK_NULL(obj);
+ (*env)->SetStaticObjectField(env, cls, searchlistID, obj);
+
+ obj = (*env)->NewStringUTF(env, nameservers);
+ CHECK_NULL(obj);
+ (*env)->SetStaticObjectField(env, cls, nameserversID, obj);
+ } else {
+ JNU_ThrowOutOfMemoryError(env, "native memory allocation failed");
+ }
+}
+
+
+/*
+ * Class: jdk_dns_conf_DnsResolverConfguration
+ * Method: notifyAddrChange0
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL
+Java_jdk_dns_conf_DnsResolverConfiguration_notifyAddrChange0(JNIEnv *env, jclass cls)
+{
+ OVERLAPPED ol;
+ HANDLE h;
+ DWORD rc, xfer;
+
+ ol.hEvent = (HANDLE)0;
+ rc = NotifyAddrChange(&h, &ol);
+ if (rc == ERROR_IO_PENDING) {
+ rc = GetOverlappedResult(h, &ol, &xfer, TRUE);
+ if (rc != 0) {
+ return 0; /* address changed */
+ }
+ }
+
+ /* error */
+ return -1;
+}