# HG changeset patch # User aefimov # Date 1573739403 0 # Node ID fcdb8e7ead8f113416ecb968b449ab596acbd18f # Parent 15e026239a6c590865a6786ddccb840c7ec79eb5# Parent 355f4f42dda5b1d0167e0f14d1a1cb6f0f070226 Merge diff -r 355f4f42dda5 -r fcdb8e7ead8f make/lib/Lib-jdk.dns.client.gmk --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/make/lib/Lib-jdk.dns.client.gmk Thu Nov 14 13:50:03 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) diff -r 355f4f42dda5 -r fcdb8e7ead8f src/java.base/share/classes/java/net/InetAddress.java --- a/src/java.base/share/classes/java/net/InetAddress.java Thu Nov 14 10:55:46 2019 +0100 +++ b/src/java.base/share/classes/java/net/InetAddress.java Thu Nov 14 13:50:03 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() }; diff -r 355f4f42dda5 -r fcdb8e7ead8f src/java.base/share/classes/module-info.java --- a/src/java.base/share/classes/module-info.java Thu Nov 14 10:55:46 2019 +0100 +++ b/src/java.base/share/classes/module-info.java Thu Nov 14 13:50:03 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 diff -r 355f4f42dda5 -r fcdb8e7ead8f src/java.base/share/lib/security/default.policy --- a/src/java.base/share/lib/security/default.policy Thu Nov 14 10:55:46 2019 +0100 +++ b/src/java.base/share/lib/security/default.policy Thu Nov 14 13:50:03 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; }; diff -r 355f4f42dda5 -r fcdb8e7ead8f src/jdk.dns.client/share/classes/jdk/dns/client/NetworkNamesResolver.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.dns.client/share/classes/jdk/dns/client/NetworkNamesResolver.java Thu Nov 14 13:50:03 2019 +0000 @@ -0,0 +1,191 @@ +/* + * 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.AddressResolutionQueue; +import jdk.dns.client.internal.DnsResolver; +import jdk.dns.client.internal.HostsFileResolver; + +import java.net.InetAddress; +import java.net.ProtocolFamily; +import java.net.UnknownHostException; +import java.security.PrivilegedAction; +import java.util.List; +import java.util.stream.Collectors; + +public class NetworkNamesResolver { + + private final ProtocolFamily protocolFamily; + + public static NetworkNamesResolver open() throws UnknownHostException { + // null for any (IPv4+IPv6) addresses family + return new NetworkNamesResolver(null); + } + + public static NetworkNamesResolver open(ProtocolFamily protocolFamily) throws UnknownHostException { + return new NetworkNamesResolver(protocolFamily); + } + + private NetworkNamesResolver(ProtocolFamily protocolFamily) throws UnknownHostException { + this.protocolFamily = protocolFamily; + } + + /** + * 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, protocolFamily); + } catch (UnknownHostException uhe) { + if (DEBUG) { + System.err.printf("Hosts file doesn't know '%s' host with '%s' address family%n", + hostname, protocolFamily == null ? "ANY" : protocolFamily.toString()); + } + } + + // If no luck - try to ask name servers + try (DnsResolver dnsResolver = new DnsResolver()) { + var results = lookup(dnsResolver, hostname, protocolFamily, false); + return results.get(0); + } + } + + /** + *

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 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, protocolFamily)); + } catch (UnknownHostException uhe) { + if (DEBUG) { + System.err.printf("Resolver API: Hosts file doesn't know '%s' host%n", hostname); + } + } + + try (var dnsResolver = new DnsResolver()) { + var results = lookup(dnsResolver, hostname, null, true); + if (results.isEmpty()) { + throw new UnknownHostException(hostname + " unknown host name"); + } + return results; + } + } + + /** + * 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 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 (DnsResolver dnsResolver = new DnsResolver()) { + var literalIP = addressToLiteralIP(address); + var results = dnsResolver.rlookup(literalIP, address); + 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 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 List lookup(DnsResolver dnsResolver, String host, ProtocolFamily protocolFamily, boolean needAllAddresses) throws UnknownHostException { + if (DEBUG) { + System.out.printf("Resolver API: internal lookup call - %s%n", host); + } + return AddressResolutionQueue.resolve(dnsResolver, host, protocolFamily, needAllAddresses); + } + + private static HostsFileResolver hostsFileResolver = new HostsFileResolver(); + private static final boolean DEBUG = java.security.AccessController.doPrivileged( + (PrivilegedAction) () -> Boolean.getBoolean("jdk.dns.client.debug")); +} diff -r 355f4f42dda5 -r fcdb8e7ead8f src/jdk.dns.client/share/classes/jdk/dns/client/ex/DnsCommunicationException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.dns.client/share/classes/jdk/dns/client/ex/DnsCommunicationException.java Thu Nov 14 13:50:03 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. + */ + +package jdk.dns.client.ex; + +public class DnsCommunicationException extends DnsResolverException { + private static final long serialVersionUID = 5764739815069894318L; + + public DnsCommunicationException(String message) { + super(message); + } + +} diff -r 355f4f42dda5 -r fcdb8e7ead8f src/jdk.dns.client/share/classes/jdk/dns/client/ex/DnsNameNotFoundException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.dns.client/share/classes/jdk/dns/client/ex/DnsNameNotFoundException.java Thu Nov 14 13:50:03 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. + */ + +package jdk.dns.client.ex; + +public class DnsNameNotFoundException extends DnsResolverException { + private static final long serialVersionUID = 2646341064300322397L; + + public DnsNameNotFoundException(String message) { + super(message); + } + +} diff -r 355f4f42dda5 -r fcdb8e7ead8f src/jdk.dns.client/share/classes/jdk/dns/client/ex/DnsResolverException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.dns.client/share/classes/jdk/dns/client/ex/DnsResolverException.java Thu Nov 14 13:50:03 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); + } +} diff -r 355f4f42dda5 -r fcdb8e7ead8f src/jdk.dns.client/share/classes/jdk/dns/client/internal/AddressFamily.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/AddressFamily.java Thu Nov 14 13:50:03 2019 +0000 @@ -0,0 +1,87 @@ +/* + * 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.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 == null) { + return ANY; + } else 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; + } + } + + boolean matchesResourceRecord(ResourceRecord rr) { + int type = rr.getType(); + switch (this) { + case IPv4: + return type == ResourceRecord.TYPE_A; + case IPv6: + return type == ResourceRecord.TYPE_AAAA; + case ANY: + return type == ResourceRecord.TYPE_A || + type == ResourceRecord.TYPE_AAAA; + default: + return false; + } + } + + static AddressFamily fromResourceRecord(ResourceRecord rr) { + return rr.getType() == ResourceRecord.TYPE_AAAA ? IPv6 : IPv4; + } + + static AddressFamily fromInetAddress(InetAddress addr) { + return addr instanceof Inet4Address ? IPv4 : addr instanceof Inet6Address ? IPv6 : ANY; + } +} diff -r 355f4f42dda5 -r fcdb8e7ead8f src/jdk.dns.client/share/classes/jdk/dns/client/internal/AddressResolutionQueue.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/AddressResolutionQueue.java Thu Nov 14 13:50:03 2019 +0000 @@ -0,0 +1,318 @@ +/* + * 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 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.LinkedList; +import java.util.List; +import java.util.Optional; +import java.util.Queue; +import java.util.stream.Collectors; + +// This class handles DNS requests to perform host name to IP addresses resolutions. +// Reverse-DNS requests are handled by DnsResolver itself +public class AddressResolutionQueue { + /** + * Resolve hostname with provided @{code DnsResolver} + * + * @param dnsResolver resolver that will be used to issue DNS requests + * @param hostName host name to resolve + * @param family address family to search + * @param needAllAddresses search for all available addresses + * @return list of found addresses + * @throws UnknownHostException host name cannot be resolved to an address + */ + public static List resolve(DnsResolver dnsResolver, + String hostName, + ProtocolFamily family, + boolean needAllAddresses) throws UnknownHostException { + AddressResolutionQueue rq = new AddressResolutionQueue(dnsResolver, + hostName, + AddressFamily.fromProtocolFamily(family), + needAllAddresses); + var rqResults = rq.resolve(); + if (rqResults.isEmpty()) { + throw new UnknownHostException(hostName); + } + + // Parse answers + var results = new ArrayList(); + for (var result : rqResults) { + byte[] addrBytes = null; + switch (result.getType()) { + case IPv4: + addrBytes = IPAddressUtil.textToNumericFormatV4(result.getAddressString()); + break; + case IPv6: + addrBytes = IPAddressUtil.textToNumericFormatV6(result.getAddressString()); + break; + default: + break; + } + + if (addrBytes == null) { + if (DEBUG) { + System.err.println("Bad address format: " + result.getAddressString()); + } + continue; + } + InetAddress address = InetAddress.getByAddress(hostName, addrBytes); + results.add(address); + if (!needAllAddresses) { + break; + } + } + return results; + } + + private AddressResolutionQueue(DnsResolver dnsResolver, String nameToResolve, + AddressFamily requestedAddressType, boolean needAllAddresses) { + this.dnsResolver = dnsResolver; + this.nameToResolve = nameToResolve; + this.requestedAddressType = requestedAddressType; + this.needAllAddresses = needAllAddresses; + } + + private void fillQueue() { + if (nameToResolve.contains(".")) + addQueriesForHostname(nameToResolve); + for (var domainName : dnsResolver.getDomainsSearchList()) { + addQueriesForHostname(nameToResolve + "." + domainName); + } + } + + private List resolve() { + // Used in non-ANY mode and stores the host name that yielded some result (A or AAAA) + String gotResultsFor = ""; + // Init queue with ANY requests first + isAnyMode = USE_ANY_SP_VALUE; + fillQueue(); + List addresses = new ArrayList<>(); + while (true) { + if (queue.isEmpty()) { + if (isAnyMode) { + // If previous request was for ANY address and no A/AAAA/CNAME replies were received + // then populate queue with separate A/AAAA/CNAME requests + isAnyMode = false; + cnameCount = 0; + fillQueue(); + } else { + // Tried everything - return what we have + return Collections.unmodifiableList(addresses); + } + } + // Output queue for debugging purposes + if (DEBUG) { + System.out.println("================DNS Resolution queue=================="); + System.out.println("Address resolution queue:" + queue); + System.out.println("======================================================"); + } + // Execute request + var resRequest = queue.poll(); + // If not in ANY mode and already found something - continue searches only for the same + // name and only for the left addresses and no need to process CNAME requests + if (gotResultsFor.isEmpty() || (resRequest.hostName.equals(gotResultsFor) + && resRequest.resourceId != ResourceRecord.TYPE_CNAME)) { + var result = executeRequest(resRequest, !gotResultsFor.isEmpty()); + addresses.addAll(result); + } + + // If found something and is in ANY mode - return the addresses + // If in separate A/AAAA requests - update host name with found address to filter out + // other requests + if (!addresses.isEmpty()) { + if (isAnyMode || !needAllAddresses) { + return Collections.unmodifiableList(addresses); + } else if (gotResultsFor.isEmpty()) { + gotResultsFor = resRequest.hostName; + } + } + } + } + + private void addQueriesForHostname(String hostname) { + if (isAnyMode) { + queue.add(ResolutionRequest.of(hostname, ResourceRecord.TYPE_ANY)); + } else { + if (requestedAddressType == AddressFamily.ANY || requestedAddressType == AddressFamily.IPv4) { + queue.add(ResolutionRequest.of(hostname, ResourceRecord.TYPE_A)); + } + if (requestedAddressType == AddressFamily.ANY || requestedAddressType == AddressFamily.IPv6) { + queue.add(ResolutionRequest.of(hostname, ResourceRecord.TYPE_AAAA)); + } + queue.add(ResolutionRequest.of(hostname, ResourceRecord.TYPE_CNAME)); + } + } + + private List executeRequest(ResolutionRequest request, boolean alreadyHaveResults) { + + try { + var rrs = dnsResolver.query(request.hostName, request.resourceId); + if (DEBUG) { + System.err.println("================DNS Resolution queue=================="); + System.err.printf("Got answers for '%s':%n", request); + System.err.println(rrs.answer); + System.err.println("======================================================"); + } + // If no addresses or aliases - return + if (!rrs.hasAddressesOrAlias()) { + return Collections.emptyList(); + } + // Has addresses + if (rrs.hasAddressOfFamily(AddressFamily.ANY)) { + // if no address of requested type but has other addresses + // clear the queue and return empty list + // Clear queue if address is found or doesn't match the + // requested address type - only for ANY requests + if (request.resourceId == ResourceRecord.TYPE_ANY) { + queue.clear(); + } + if (!rrs.hasAddressOfFamily(requestedAddressType)) { + return Collections.emptyList(); + } + // Parse addresses + return rrs.answer.stream() + .filter(requestedAddressType::matchesResourceRecord) + .map(ResolvedAddress::of) + .collect(Collectors.toList()); + } + // If has alias - clear the queue and add new requests with + // the alias name. Do it only for cases when no addresses is found + if (!alreadyHaveResults) { + Optional aliasO = rrs.answer.stream() + .filter(rr -> rr.rrtype == ResourceRecord.TYPE_CNAME) + .map(ResourceRecord::getRdata) + .map(Object::toString) + .findFirst(); + if (aliasO.isPresent()) { + queue.clear(); + String cname = aliasO.get(); + if (DEBUG) { + System.err.println("Found alias: " + cname); + } + cnameCount++; + if (cnameCount > MAX_CNAME_RESOLUTION_DEPTH) { + throw new RuntimeException("CNAME loop detected"); + } + addQueriesForHostname(cname); + } + } + // Filter the results depending on typeOfAddress + return Collections.emptyList(); + } catch (DnsResolverException e) { + return Collections.emptyList(); + } + } + + static class ResolvedAddress { + final AddressFamily type; + final String addressString; + + private ResolvedAddress(AddressFamily type, String addressString) { + this.type = type; + this.addressString = addressString; + } + + public AddressFamily getType() { + return type; + } + + public String getAddressString() { + return addressString; + } + + public static ResolvedAddress of(ResourceRecord resourceRecord) { + AddressFamily type = AddressFamily.fromResourceRecord(resourceRecord); + String addressString = resourceRecord.getRdata().toString(); + return new ResolvedAddress(type, addressString); + } + + @Override + public String toString() { + return "[" + type.name() + "]:" + addressString; + } + } + + static class ResolutionRequest { + final int resourceId; + final String hostName; + + private ResolutionRequest(String hostName, int resourceId) { + this.resourceId = resourceId; + this.hostName = hostName; + } + + public boolean isAny() { + return resourceId == ResourceRecord.TYPE_ANY; + } + + public static ResolutionRequest of(String hostName, int resourceId) { + assert hostName != null && !hostName.isBlank(); + return new ResolutionRequest(hostName, resourceId); + } + + @Override + public String toString() { + return hostName + ":" + resourceId; + } + } + + // DNS resolver instance to issue DNS queries + private final DnsResolver dnsResolver; + // Queue with address resolution requests (ANY/A/AAAA or CNAME) + private final Queue queue = new LinkedList<>(); + + // Internal state variables to control queue processing + // Type of requested address + private final AddressFamily requestedAddressType; + // Requested name to resolve + private final String nameToResolve; + // Is it getAllByName [true] or getByName [false] + private final boolean needAllAddresses; + // CNAME lookups counter needed to avoid alias loops + private int cnameCount = 0; + // Private resolution queue has two modes - ANY and single requests. + // The mode in use is tracked with this boolean flag + private boolean isAnyMode; + // Use any mode property value + private static final boolean USE_ANY_SP_VALUE = java.security.AccessController.doPrivileged( + (PrivilegedAction) () -> Boolean.getBoolean("jdk.dns.client.use.any")); + // Maximum number of sequential CNAME requests + private static final int MAX_CNAME_RESOLUTION_DEPTH = 4; + // Enable debug output + private static final boolean DEBUG = java.security.AccessController.doPrivileged( + (PrivilegedAction) () -> Boolean.getBoolean("jdk.dns.client.debug")); + + +} diff -r 355f4f42dda5 -r fcdb8e7ead8f src/jdk.dns.client/share/classes/jdk/dns/client/internal/DnsClient.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/DnsClient.java Thu Nov 14 13:50:03 2019 +0000 @@ -0,0 +1,758 @@ +/* + * 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.DnsResolverException; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +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.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 servers; + private List 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) () -> JCAUtil.getSecureRandom(); + random = System.getSecurityManager() == null ? pa.run() + : AccessController.doPrivileged(pa); + } + + private static final DnsDatagramChannelFactory factory = + new DnsDatagramChannelFactory(random); + + // Requests sent + private Map reqs; + + // Responses received + private Map 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 servers, int timeout, int retries) { + this.timeout = timeout; + this.retries = retries; + var serversList = new ArrayList(); + var serverPortsList = new ArrayList(); + + 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) () -> { + 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) { + try { + serversList.add(InetAddress.getByAddress(server, addr)); + serverPortsList.add(serverPort); + } catch (UnknownHostException e) { + // Malformed IP address is specified - will ignore it + } + } + } + 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 DnsResolverException("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 DnsResolverException( + "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) () -> { + 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; + // flags = (short) (flags | Header.CD_BIT | Header.AD_BIT); + + 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 DnsResolverException("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.TYPE_ANY || + 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 + "]"; + throw new DnsResolverException(msg); + } + + //------------------------------------------------------------------------- + + private static final boolean DEBUG = java.security.AccessController.doPrivileged( + (PrivilegedAction) () -> 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 InputStream in; + final 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 BufferedOutputStream(sock.getOutputStream()); + in = new 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; + } +} diff -r 355f4f42dda5 -r fcdb8e7ead8f src/jdk.dns.client/share/classes/jdk/dns/client/internal/DnsDatagramChannelFactory.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/DnsDatagramChannelFactory.java Thu Nov 14 13:50:03 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; + } + +} diff -r 355f4f42dda5 -r fcdb8e7ead8f src/jdk.dns.client/share/classes/jdk/dns/client/internal/DnsName.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/DnsName.java Thu Nov 14 13:50:03 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.DnsResolverException; + +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. + * + *

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. + * + *

A label consists of a sequence of octets, each of which may + * have any value from 0 to 255. + * + *

Host names 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. + * + *

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 vice versa. 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 draft-ietf-idn-idna-10.txt), {@code DnsName} + * may be updated to conform to that work. + * + *

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. + * + *

Atomic names passed as parameters to methods of + * {@code DnsName}, and those returned by them, are unescaped. So, + * for example, (new DnsName()).add("a.b") creates an + * object representing the one-label domain name {@code a\.b}, and + * calling {@code get(0)} on this object returns {@code "a.b"}. + * + *

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. + * + *

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 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 DnsResolverException if {@code name} does not conform + * to DNS syntax. + */ + public DnsName(String name) throws DnsResolverException { + 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 host name 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 DnsResolverException { + 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 DnsResolverException( + "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 DnsResolverException("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 DnsResolverException { + // "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 DnsResolverException( + "Empty label must be the last label in a domain name"); + } + + short newOctets = (short) (octets + n.octets - 1); + if (newOctets > 255) { + throw new DnsResolverException("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 DnsResolverException { + + 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 DnsResolverException { + 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 DnsResolverException( + "Invalid escape sequence in " + name); + } + } else { // sequence is `\C' + return c1; + } + } catch (IndexOutOfBoundsException e) { + throw new DnsResolverException( + "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 DnsResolverException { + if (label.length() > 63) { + throw new DnsResolverException( + "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 DnsResolverException( + "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(); + } +} diff -r 355f4f42dda5 -r fcdb8e7ead8f src/jdk.dns.client/share/classes/jdk/dns/client/internal/DnsNameService.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/DnsNameService.java Thu Nov 14 13:50:03 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; + } + // TODO: Decide if it is compatible way with default InetAddress resolver + if ("system".equals(value)) { + return DontCare; + } + return IPv4First; + } + } + + static final AddressOrder order; + + static { + var action = (PrivilegedAction) () -> 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)); + } +} diff -r 355f4f42dda5 -r fcdb8e7ead8f src/jdk.dns.client/share/classes/jdk/dns/client/internal/DnsResolver.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/DnsResolver.java Thu Nov 14 13:50:03 2019 +0000 @@ -0,0 +1,131 @@ +/* + * 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 jdk.dns.conf.DnsResolverConfiguration; + +import java.net.InetAddress; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +public class DnsResolver 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 DnsResolver() { + dnsClient = new DnsClient(dnsResolverConfiguration.nameservers(), 1000, 4); + } + + @Override + public void close() { + dnsClient.close(); + dnsClient = null; + } + + public Set getDomainsSearchList() { + var domainsToSearch = new LinkedHashSet(); + var domain = dnsResolverConfiguration.domain(); + // First, try the domain + if (!domain.isBlank()) { + domainsToSearch.add(domain); + } + // Then iterate over the search list + domainsToSearch.addAll(dnsResolverConfiguration.searchlist()); + if (DEBUG) { + System.out.printf("Domains search list:%s%n", domainsToSearch); + } + return domainsToSearch; + } + + /* + * Queries resource records of a particular class and type for a + * given domain name. + * Useful values of rrtype are ResourceRecord.[Q]TYPE_xxx. + */ + public ResourceRecords query(String fqdn, int rrtype) + throws DnsResolverException { + return dnsClient.query(new DnsName(fqdn), ResourceRecord.CLASS_INTERNET, rrtype, true, false); + } + + public List rlookup(String literalIP, InetAddress address) throws DnsResolverException { + + switch (AddressFamily.fromInetAddress(address)) { + case IPv4: + return ipv4rlookup(literalIP); + case IPv6: + return ipv6rlookup(literalIP); + default: + throw new RuntimeException("Only IPv4 and IPv6 addresses are supported"); + } + } + + private List ipv4rlookup(String literalIP) throws DnsResolverException { + var request = literalIP + "IN-ADDR.ARPA."; + var hostNames = new ArrayList(); + var rrs = query(request, ResourceRecord.TYPE_PTR); + for (int i = 0; i < rrs.answer.size(); i++) { + ResourceRecord rr = rrs.answer.elementAt(i); + hostNames.add(rr.getRdata().toString()); + } + return hostNames; + } + + private List ipv6rlookup(String literalIP) throws DnsResolverException { + List hostNames = new ArrayList<>(); + ResourceRecords rrs = query(literalIP + "IP6.ARPA.", ResourceRecord.TYPE_PTR); + /** + * 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()) { + rrs = query(literalIP + "IP6.INT.", ResourceRecord.TYPE_PTR); + } + for (int i = 0; i < rrs.answer.size(); i++) { + ResourceRecord rr = rrs.answer.elementAt(i); + hostNames.add(rr.getRdata().toString()); + } + return hostNames; + } + + + // Configuration file tracker + private static DnsResolverConfiguration dnsResolverConfiguration = new DnsResolverConfiguration(); + private static final boolean DEBUG = java.security.AccessController.doPrivileged( + (PrivilegedAction) () -> Boolean.getBoolean("jdk.dns.client.debug")); +} diff -r 355f4f42dda5 -r fcdb8e7ead8f src/jdk.dns.client/share/classes/jdk/dns/client/internal/Header.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/Header.java Thu Nov 14 13:50:03 2019 +0000 @@ -0,0 +1,122 @@ +/* + * 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 AD_BIT = (short) 0x0020; + static final short CD_BIT = (short) 0x0010; + 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 + boolean authenticData; // AD + boolean checkingDisabled; // CD + 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; + authenticData = (flags & AD_BIT) != 0; + checkingDisabled = (flags & CD_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)); + } +} diff -r 355f4f42dda5 -r fcdb8e7ead8f src/jdk.dns.client/share/classes/jdk/dns/client/internal/HostsFileResolver.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/HostsFileResolver.java Thu Nov 14 13:50:03 2019 +0000 @@ -0,0 +1,274 @@ +/* + * 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.internal.util.ReloadTracker; +import sun.net.util.IPAddressUtil; + +import java.net.InetAddress; +import java.net.ProtocolFamily; +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) + () -> 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 HOST_ADDRESSES = Collections.emptyMap(); + + void loadHostsAddresses() { + LOCK.readLock().lock(); + var rsf = HOSTS_FILE_TRACKER.getReloadStatus(); + try { + if (!rsf.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 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 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 oneNameStream() { + HostFileEntry hostName = new HostFileEntry(names.get(0), address, true); + Stream 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) () -> { + 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 parseHostsFile() { + Path hf = Paths.get(HOSTS_FILE_LOCATION_PROPERTY_VALUE); + try { + // TODO: Revisit + var pea = (PrivilegedExceptionAction) () -> Files.isRegularFile(hf); + boolean isRegularFile = System.getSecurityManager() == null ? pea.run() + : AccessController.doPrivileged(pea); + + if (isRegularFile) { + var result = new HashMap(); + var pea2 = (PrivilegedExceptionAction>) () -> 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, null); + } + + public InetAddress getHostAddress(String hostName, ProtocolFamily family) throws UnknownHostException { + var af = AddressFamily.fromProtocolFamily(family); + loadHostsAddresses(); + var map = HOST_ADDRESSES; + var he = map.get(hostName); + if (he == null) { + throw new UnknownHostException(hostName); + } + var addr = he.address; + if (!af.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.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); + } + } +} diff -r 355f4f42dda5 -r fcdb8e7ead8f src/jdk.dns.client/share/classes/jdk/dns/client/internal/PortConfig.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/PortConfig.java Thu Nov 14 13:50:03 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; + } +} diff -r 355f4f42dda5 -r fcdb8e7ead8f src/jdk.dns.client/share/classes/jdk/dns/client/internal/ResourceClassType.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/ResourceClassType.java Thu Nov 14 13:50:03 2019 +0000 @@ -0,0 +1,145 @@ +/* + * 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; + +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 (DnsResolverException e) { + return new ResourceClassType(ResourceRecord.TYPE_ANY); + } + 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 DnsResolverException { + 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 DnsResolverException { + + if (attrId.isEmpty()) { + throw new DnsResolverException( + "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 DnsResolverException( + "Unknown resource record class '" + className + '\''); + } + } + + // type + String typeName = attrId.substring(space + 1); + rrtype = ResourceRecord.getType(typeName); + if (rrtype < 0) { + throw new DnsResolverException( + "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.TYPE_ANY; + } 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.TYPE_ANY; + } + } + } + return new ResourceClassType(rrtype); + } +} diff -r 355f4f42dda5 -r fcdb8e7ead8f src/jdk.dns.client/share/classes/jdk/dns/client/internal/ResourceRecord.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/ResourceRecord.java Thu Nov 14 13:50:03 2019 +0000 @@ -0,0 +1,614 @@ +/* + * 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.DnsResolverException; + +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; + public static final int TYPE_ANY = 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; + + /* + * 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 == TYPE_ANY) { // 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 TYPE_ANY; // 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 | DnsResolverException 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 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 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 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); + } + } + +} diff -r 355f4f42dda5 -r fcdb8e7ead8f src/jdk.dns.client/share/classes/jdk/dns/client/internal/ResourceRecords.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/ResourceRecords.java Thu Nov 14 13:50:03 2019 +0000 @@ -0,0 +1,143 @@ +/* + * 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. + *

+ * 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 question = new Vector<>(); + public Vector answer = new Vector<>(); + Vector authority = new Vector<>(); + public Vector additional = new Vector<>(); + + private boolean hasIpv4InAnswer; + private boolean hasIpv6InAnswer; + private boolean hasAliasInAnswer; + + + public boolean hasAddressesOrAlias() { + return hasIpv4InAnswer || hasIpv6InAnswer || hasAliasInAnswer; + } + + public boolean hasAddressOfFamily(AddressFamily af) { + if (af == AddressFamily.ANY) { + return hasIpv6InAnswer || hasIpv4InAnswer; + } + return af == AddressFamily.IPv4 ? hasIpv4InAnswer : hasIpv6InAnswer; + } + + /* + * 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); + } + + /* + * 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); + hasIpv4InAnswer |= rr.rrtype == ResourceRecord.TYPE_A; + hasIpv6InAnswer |= rr.rrtype == ResourceRecord.TYPE_AAAA; + hasAliasInAnswer |= rr.rrtype == ResourceRecord.TYPE_CNAME; + 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"); + } + } +} diff -r 355f4f42dda5 -r fcdb8e7ead8f src/jdk.dns.client/share/classes/jdk/dns/client/internal/util/ReloadTracker.java --- /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 Nov 14 13:50:03 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) () -> 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) () -> 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; +} diff -r 355f4f42dda5 -r fcdb8e7ead8f src/jdk.dns.client/share/classes/module-info.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.dns.client/share/classes/module-info.java Thu Nov 14 13:50:03 2019 +0000 @@ -0,0 +1,34 @@ +/* + * 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; + provides InetAddress.NameService with DnsNameService; +} diff -r 355f4f42dda5 -r fcdb8e7ead8f src/jdk.dns.client/unix/classes/jdk/dns/conf/DnsResolverConfiguration.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.dns.client/unix/classes/jdk/dns/conf/DnsResolverConfiguration.java Thu Nov 14 13:50:03 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) () -> 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 resolvconf(String keyword, + int maxperkeyword, + int maxkeywords) { + LinkedList 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 searchlist = Collections.emptyList(); + private volatile List 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>) () -> { + // 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 getSearchList() { + + List sl; + + // first try the search keyword in /etc/resolv.conf + + /* run */ + sl = java.security.AccessController.doPrivileged( + (PrivilegedAction>) () -> { + List 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 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 searchlist() { + loadConfig(); + // List is unmodifiable + return searchlist; + } + + @SuppressWarnings("unchecked") + public List 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) () -> {System.loadLibrary("resolver"); return null;}; + if (System.getSecurityManager() == null) { + pa.run(); + } else { + AccessController.doPrivileged(pa); + } + var pea = (PrivilegedExceptionAction) () -> + 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); + } + } + +} diff -r 355f4f42dda5 -r fcdb8e7ead8f src/jdk.dns.client/unix/native/libresolver/DnsResolverConfiguration.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.dns.client/unix/native/libresolver/DnsResolverConfiguration.c Thu Nov 14 13:50:03 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 +#include +#include +#include + +#ifdef __solaris__ +#include +#endif + +#include + +#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) && (retNewStringUTF(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; +} diff -r 355f4f42dda5 -r fcdb8e7ead8f src/jdk.dns.client/windows/classes/jdk/dns/conf/DnsResolverConfiguration.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.dns.client/windows/classes/jdk/dns/conf/DnsResolverConfiguration.java Thu Nov 14 13:50:03 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 searchlist; + private static LinkedList nameservers; + + // Parse string that consists of token delimited by space or commas + // and return LinkedHashMap + private LinkedList stringToList(String str) { + LinkedList 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 searchlist() { + lock.lock(); + try { + loadConfig(); + + // List is mutable so return a shallow copy + return (List) searchlist.clone(); + } finally { + lock.unlock(); + } + } + + @SuppressWarnings("unchecked") // clone() + public List nameservers() { + lock.lock() + try { + loadConfig(); + + // List is mutable so return a shallow copy + return (List) 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(); + } +} diff -r 355f4f42dda5 -r fcdb8e7ead8f src/jdk.dns.client/windows/native/libresolve/DnsResolverConfiguration.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.dns.client/windows/native/libresolve/DnsResolverConfiguration.c Thu Nov 14 13:50:03 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 +#include +#include +#include +#include +#include +#include +#include + +#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; +}