aefimov-dns-client-branch: Add jdk.dns.client source aefimov-dns-client-branch
authoraefimov
Thu, 31 Oct 2019 16:16:21 +0000
branchaefimov-dns-client-branch
changeset 58870 35c438a6d45c
parent 58869 cc66ac8c7646
child 58970 027e4cb87353
aefimov-dns-client-branch: Add jdk.dns.client source
make/lib/Lib-jdk.dns.client.gmk
src/java.base/share/classes/java/net/InetAddress.java
src/java.base/share/classes/module-info.java
src/java.base/share/lib/security/default.policy
src/jdk.dns.client/share/classes/jdk/dns/client/AddressFamily.java
src/jdk.dns.client/share/classes/jdk/dns/client/NetworkNamesResolver.java
src/jdk.dns.client/share/classes/jdk/dns/client/ex/DnsCommunicationException.java
src/jdk.dns.client/share/classes/jdk/dns/client/ex/DnsInvalidAttributeIdentifierException.java
src/jdk.dns.client/share/classes/jdk/dns/client/ex/DnsInvalidNameException.java
src/jdk.dns.client/share/classes/jdk/dns/client/ex/DnsNameNotFoundException.java
src/jdk.dns.client/share/classes/jdk/dns/client/ex/DnsOperationNotSupportedException.java
src/jdk.dns.client/share/classes/jdk/dns/client/ex/DnsResolverException.java
src/jdk.dns.client/share/classes/jdk/dns/client/ex/DnsServiceUnavailableException.java
src/jdk.dns.client/share/classes/jdk/dns/client/internal/DnsClient.java
src/jdk.dns.client/share/classes/jdk/dns/client/internal/DnsDatagramChannelFactory.java
src/jdk.dns.client/share/classes/jdk/dns/client/internal/DnsName.java
src/jdk.dns.client/share/classes/jdk/dns/client/internal/DnsNameService.java
src/jdk.dns.client/share/classes/jdk/dns/client/internal/Header.java
src/jdk.dns.client/share/classes/jdk/dns/client/internal/HostsFileResolver.java
src/jdk.dns.client/share/classes/jdk/dns/client/internal/PortConfig.java
src/jdk.dns.client/share/classes/jdk/dns/client/internal/Resolver.java
src/jdk.dns.client/share/classes/jdk/dns/client/internal/ResourceClassType.java
src/jdk.dns.client/share/classes/jdk/dns/client/internal/ResourceRecord.java
src/jdk.dns.client/share/classes/jdk/dns/client/internal/ResourceRecords.java
src/jdk.dns.client/share/classes/jdk/dns/client/internal/util/ReloadTracker.java
src/jdk.dns.client/share/classes/module-info.java
src/jdk.dns.client/unix/classes/jdk/dns/conf/DnsResolverConfiguration.java
src/jdk.dns.client/unix/native/libresolver/DnsResolverConfiguration.c
src/jdk.dns.client/windows/classes/jdk/dns/conf/DnsResolverConfiguration.java
src/jdk.dns.client/windows/native/libresolve/DnsResolverConfiguration.c
--- /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&nbsp;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;
+}