# HG changeset patch # User aefimov # Date 1573152366 0 # Node ID 465a15dd6bed6ff3fe80e92ad704adbaba12e1b8 # Parent 027e4cb87353348a0b60e53c565c7108edb55940 aefimov-dns-client-branch: Cleanup and address ANY type failures diff -r 027e4cb87353 -r 465a15dd6bed src/jdk.dns.client/share/classes/jdk/dns/client/AddressFamily.java --- a/src/jdk.dns.client/share/classes/jdk/dns/client/AddressFamily.java Thu Nov 07 18:44:09 2019 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.dns.client; - -import java.net.Inet4Address; -import java.net.Inet6Address; -import java.net.InetAddress; -import java.net.ProtocolFamily; -import java.net.StandardProtocolFamily; - -public enum AddressFamily { - IPv4, - IPv6, - ANY; - - public static AddressFamily fromProtocolFamily(ProtocolFamily protocolFamily) { - if (protocolFamily == StandardProtocolFamily.INET) { - return IPv4; - } else if (protocolFamily == StandardProtocolFamily.INET6) { - return IPv6; - } else { - return ANY; - } - - } - - public boolean sameFamily(InetAddress inetAddress) { - switch (this) { - case IPv4: - return inetAddress instanceof Inet4Address; - case IPv6: - return inetAddress instanceof Inet6Address; - case ANY: - return true; - default: - return false; - } - } - - public static AddressFamily fromInetAddress(InetAddress addr) { - return addr instanceof Inet4Address ? IPv4 : addr instanceof Inet6Address ? IPv6 : ANY; - } -} diff -r 027e4cb87353 -r 465a15dd6bed src/jdk.dns.client/share/classes/jdk/dns/client/NetworkNamesResolver.java --- a/src/jdk.dns.client/share/classes/jdk/dns/client/NetworkNamesResolver.java Thu Nov 07 18:44:09 2019 +0000 +++ b/src/jdk.dns.client/share/classes/jdk/dns/client/NetworkNamesResolver.java Thu Nov 07 18:46:06 2019 +0000 @@ -26,76 +26,58 @@ package jdk.dns.client; import jdk.dns.client.ex.DnsResolverException; -import jdk.dns.client.internal.DnsName; +import jdk.dns.client.internal.AddressResolutionQueue; +import jdk.dns.client.internal.DnsResolver; import jdk.dns.client.internal.HostsFileResolver; -import jdk.dns.client.internal.Resolver; -import jdk.dns.client.internal.ResourceClassType; -import jdk.dns.client.internal.ResourceRecord; -import jdk.dns.client.internal.ResourceRecords; -import jdk.dns.conf.DnsResolverConfiguration; -import sun.net.util.IPAddressUtil; import java.net.InetAddress; import java.net.ProtocolFamily; import java.net.UnknownHostException; import java.security.PrivilegedAction; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Set; import java.util.stream.Collectors; public class NetworkNamesResolver { - private final AddressFamily addressFamily; + private final ProtocolFamily protocolFamily; public static NetworkNamesResolver open() throws UnknownHostException { - return new NetworkNamesResolver(AddressFamily.ANY); + // null for any (IPv4+IPv6) addresses family + return new NetworkNamesResolver(null); } public static NetworkNamesResolver open(ProtocolFamily protocolFamily) throws UnknownHostException { - return new NetworkNamesResolver(AddressFamily.fromProtocolFamily(protocolFamily)); + return new NetworkNamesResolver(protocolFamily); } - private NetworkNamesResolver(AddressFamily addressFamily) throws UnknownHostException { - this.addressFamily = addressFamily; - this.dnsResolver = new Resolver(dnsResolverConfiguration.nameservers(), 1000, 4); + 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 + * @throws UnknownHostException if no IP address found for the specified host name and the specified address family */ public InetAddress lookupHostAddr(String hostname) throws UnknownHostException { - // First try hosts file // TODO: Add nsswitch.conf to select proper order try { - return hostsFileResolver.getHostAddress(hostname, addressFamily); + 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, addressFamily.toString()); + hostname, protocolFamily == null ? "ANY" : protocolFamily.toString()); } } // If no luck - try to ask name servers - try { - var results = lookup(hostname, addressFamily, true, true, 0); - if (results.isEmpty()) { - throw new UnknownHostException(hostname); - } + try (DnsResolver dnsResolver = new DnsResolver()) { + var results = lookup(dnsResolver, hostname, protocolFamily, true); return results.get(0); - } catch (DnsResolverException e) { - UnknownHostException uhe = new UnknownHostException(hostname); - uhe.initCause(e); - throw uhe; } } @@ -111,22 +93,19 @@ // First try hosts file // TODO: Add nsswitch.conf ReloadTracker and parser to select proper order try { - return List.of(hostsFileResolver.getHostAddress(hostname)); + 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 results = lookup(hostname, AddressFamily.ANY, false, true, 0); + + try (var dnsResolver = new DnsResolver()) { + var results = lookup(dnsResolver, hostname, null, false); if (results.isEmpty()) { throw new UnknownHostException(hostname + " unknown host name"); } return results; - } catch (DnsResolverException e) { - UnknownHostException uhe = new UnknownHostException(hostname); - uhe.initCause(e); - throw uhe; } } @@ -159,10 +138,9 @@ System.err.printf("Resolver API: No host in hosts file with %s address%n", address); } } - try { + try (DnsResolver dnsResolver = new DnsResolver()) { var literalIP = addressToLiteralIP(address); - var family = AddressFamily.fromInetAddress(address); - var results = rlookup(literalIP, family); + var results = dnsResolver.rlookup(literalIP, address); if (results.isEmpty()) { throw new UnknownHostException(); } @@ -177,176 +155,6 @@ } } - private Set prepareDomainsSearchList() { - var domainsToSearch = new LinkedHashSet(); - var domain = dnsResolverConfiguration.domain(); - // First will try the domain - if (!domain.isBlank()) { - domainsToSearch.add(domain); - } - // Then will iterate over search list - domainsToSearch.addAll(dnsResolverConfiguration.searchlist()); - if (DEBUG) { - System.out.printf("Domains search list:%s%n", domainsToSearch); - } - return domainsToSearch; - } - - private List lookup(String host, AddressFamily addressFamily, boolean oneIsEnough, boolean checkDomains, int depth) throws UnknownHostException, DnsResolverException { - - if (DEBUG) { - System.out.printf("Resolver API: internal lookup call - %s%n", host); - } - - // Will remain the same during DNS queries execution - ResourceClassType ct = ResourceClassType.fromAddressFamily(addressFamily); - ResourceRecords rrs = null; - - // First try to get the resource records with the requested DNS host name if it is FQDN - if (host.contains(".")) { - rrs = execAddrResolutionQuery(new DnsName(host), ct); - } - // Non-FQDN or not known host address and domains still can be checked - if ((rrs == null || rrs.answer.isEmpty()) && checkDomains) { - for (String domain : prepareDomainsSearchList()) { - String hostWithSuffix = host + "." + domain; - if (DEBUG) { - System.err.printf("Resolver API: Trying to lookup:'%s'%n", hostWithSuffix); - } - rrs = execAddrResolutionQuery(new DnsName(hostWithSuffix), ct); - if (rrs != null && !rrs.answer.isEmpty()) { - if (DEBUG) { - System.err.printf("Resolver API: Found host in '%s' domain%n", domain); - } - break; - } - } - } - - // If no answers then host is not known in registered domain - if (rrs == null || rrs.answer.isEmpty()) { - throw new UnknownHostException(host); - } - - // Parse answers - List results = new ArrayList<>(); - for (int i = 0; i < rrs.answer.size(); i++) { - ResourceRecord rr = rrs.answer.elementAt(i); - int answerType = rr.getType(); - String recordData = rr.getRdata().toString(); - if (DEBUG) { - System.err.printf("Resolver API: Got %s type: %s%n", ResourceRecord.getTypeName(rr.getType()), rr.getRdata()); - } - if (answerType == ResourceRecord.TYPE_CNAME) { - // We've got CNAME entry - issue another request to resolve the canonical host name - if (depth > MAX_CNAME_RESOLUTION_DEPTH) { - throw new UnknownHostException(host); - } - List cnameResults = lookup(recordData, addressFamily, oneIsEnough, false, depth + 1); - if (oneIsEnough) { - return cnameResults; - } else { - results.addAll(cnameResults); - } - } else { - byte[] addrBytes; - if (answerType == ResourceRecord.TYPE_A && (addressFamily == AddressFamily.IPv4 || addressFamily == AddressFamily.ANY)) { - addrBytes = IPAddressUtil.textToNumericFormatV4(recordData); - if (addrBytes == null) { - if (DEBUG) { - System.err.println("Incorrect A resource record answer"); - } - return Collections.emptyList(); - } - } else if (answerType == ResourceRecord.TYPE_AAAA && (addressFamily == AddressFamily.IPv6 || addressFamily == AddressFamily.ANY)) { - addrBytes = IPAddressUtil.textToNumericFormatV6(recordData); - if (addrBytes == null) { - if (DEBUG) { - System.err.println("Incorrect AAAA resource record answer"); - } - return Collections.emptyList(); - } - } else { - continue; - } - // IPAddressUtil.textToNumeric can return null - if (addrBytes == null) { - throw new RuntimeException("Internal error"); - } - InetAddress address = InetAddress.getByAddress(host, addrBytes); - results.add(address); - if (oneIsEnough) { - break; - } - } - } - - return results; - } - - // Can return null if query failed - private ResourceRecords execAddrResolutionQuery(DnsName name, ResourceClassType ct) { - // Not supporting authoritative requests [for now?] - try { - ResourceRecords rrs = dnsResolver.query(name, ct.getRecordClass(), ct.getRecordType(), - true, false); - return rrs.hasAddressOrAlias() ? rrs : null; - } catch (DnsResolverException e) { - if (DEBUG) { - System.err.println("Query failed:" + e.getMessage()); - e.printStackTrace(); - } - return null; - } - } - - private List rlookup(String literalIP, AddressFamily family) throws DnsResolverException { - switch (family) { - case IPv4: - return ipv4rlookup(literalIP); - case IPv6: - return ipv6rlookup(literalIP); - default: - throw new RuntimeException("Only IPv4 and IPv6 addresses are supported"); - } - } - - private List ipv4rlookup(String literalIP) throws DnsResolverException { - var request = literalIP + "IN-ADDR.ARPA."; - var hostNames = new ArrayList(); - var name = new DnsName(request); - var rrs = dnsResolver.query(name, ResourceRecord.CLASS_INTERNET, - ResourceRecord.TYPE_PTR, true, false); - for (int i = 0; i < rrs.answer.size(); i++) { - ResourceRecord rr = rrs.answer.elementAt(i); - hostNames.add(rr.getRdata().toString()); - } - return hostNames; - } - - private List ipv6rlookup(String literalIP) throws DnsResolverException { - List hostNames = new ArrayList<>(); - DnsName name = new DnsName(literalIP + "IP6.ARPA."); - ResourceRecords rrs = dnsResolver.query(name, ResourceRecord.CLASS_INTERNET, - ResourceRecord.TYPE_PTR, true, false); - /** - * Because RFC 3152 changed the root domain name for reverse - * lookups from IP6.INT. to IP6.ARPA., we need to check - * both. I.E. first the new one, IP6.ARPA, then if it fails - * the older one, IP6.INT - */ - if (rrs.answer.isEmpty()) { - name = new DnsName(literalIP + "IP6.INT."); - rrs = dnsResolver.query(name, ResourceRecord.CLASS_INTERNET, - ResourceRecord.TYPE_PTR, true, false); - } - for (int i = 0; i < rrs.answer.size(); i++) { - ResourceRecord rr = rrs.answer.elementAt(i); - hostNames.add(rr.getRdata().toString()); - } - return hostNames; - } - private static String addressToLiteralIP(InetAddress address) { byte[] bytes = address.getAddress(); StringBuilder addressBuff = new StringBuilder(); @@ -356,7 +164,7 @@ addressBuff.append(bytes[i] & 0xff); addressBuff.append("."); } - // IPv6 address + // IPv6 address } else if (bytes.length == 16) { for (int i = bytes.length - 1; i >= 0; i--) { addressBuff.append(Integer.toHexString((bytes[i] & 0x0f))); @@ -370,13 +178,14 @@ return addressBuff.toString(); } - private static DnsResolverConfiguration dnsResolverConfiguration = new DnsResolverConfiguration(); - private static HostsFileResolver hostsFileResolver = new HostsFileResolver(); + private List lookup(DnsResolver dnsResolver, String host, ProtocolFamily protocolFamily, boolean oneIsEnough) throws UnknownHostException { + if (DEBUG) { + System.out.printf("Resolver API: internal lookup call - %s%n", host); + } + return AddressResolutionQueue.resolve(dnsResolver, host, protocolFamily, oneIsEnough); + } - // TODO: Add system property with the sequence of resolution OR nsswitch.conf location to get the order: - // file, name - private final Resolver dnsResolver; + private static HostsFileResolver hostsFileResolver = new HostsFileResolver(); private static final boolean DEBUG = java.security.AccessController.doPrivileged( (PrivilegedAction) () -> Boolean.getBoolean("jdk.dns.client.debug")); - private static final int MAX_CNAME_RESOLUTION_DEPTH = 4; } diff -r 027e4cb87353 -r 465a15dd6bed src/jdk.dns.client/share/classes/jdk/dns/client/ex/DnsCommunicationException.java --- a/src/jdk.dns.client/share/classes/jdk/dns/client/ex/DnsCommunicationException.java Thu Nov 07 18:44:09 2019 +0000 +++ b/src/jdk.dns.client/share/classes/jdk/dns/client/ex/DnsCommunicationException.java Thu Nov 07 18:46:06 2019 +0000 @@ -32,7 +32,4 @@ super(message); } - public DnsCommunicationException(String message, Throwable cause) { - super(message, cause); - } } diff -r 027e4cb87353 -r 465a15dd6bed src/jdk.dns.client/share/classes/jdk/dns/client/ex/DnsInvalidAttributeIdentifierException.java --- a/src/jdk.dns.client/share/classes/jdk/dns/client/ex/DnsInvalidAttributeIdentifierException.java Thu Nov 07 18:44:09 2019 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.dns.client.ex; - -public class DnsInvalidAttributeIdentifierException extends DnsResolverException { - private static final long serialVersionUID = 745256513458941901L; - - public DnsInvalidAttributeIdentifierException(String message) { - super(message); - } - - public DnsInvalidAttributeIdentifierException(String message, Throwable cause) { - super(message, cause); - } -} diff -r 027e4cb87353 -r 465a15dd6bed src/jdk.dns.client/share/classes/jdk/dns/client/ex/DnsInvalidNameException.java --- a/src/jdk.dns.client/share/classes/jdk/dns/client/ex/DnsInvalidNameException.java Thu Nov 07 18:44:09 2019 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.dns.client.ex; - -public class DnsInvalidNameException extends DnsResolverException { - private static final long serialVersionUID = -6375885343819861145L; - - public DnsInvalidNameException(String message) { - super(message); - } - - public DnsInvalidNameException(String message, Throwable cause) { - super(message, cause); - } -} diff -r 027e4cb87353 -r 465a15dd6bed src/jdk.dns.client/share/classes/jdk/dns/client/ex/DnsNameNotFoundException.java --- a/src/jdk.dns.client/share/classes/jdk/dns/client/ex/DnsNameNotFoundException.java Thu Nov 07 18:44:09 2019 +0000 +++ b/src/jdk.dns.client/share/classes/jdk/dns/client/ex/DnsNameNotFoundException.java Thu Nov 07 18:46:06 2019 +0000 @@ -26,14 +26,10 @@ package jdk.dns.client.ex; public class DnsNameNotFoundException extends DnsResolverException { - private static final long serialVersionUID = 2646341064300322397L; public DnsNameNotFoundException(String message) { super(message); } - public DnsNameNotFoundException(String message, Throwable cause) { - super(message, cause); - } } diff -r 027e4cb87353 -r 465a15dd6bed src/jdk.dns.client/share/classes/jdk/dns/client/ex/DnsOperationNotSupportedException.java --- a/src/jdk.dns.client/share/classes/jdk/dns/client/ex/DnsOperationNotSupportedException.java Thu Nov 07 18:44:09 2019 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.dns.client.ex; - -public class DnsOperationNotSupportedException extends DnsResolverException { - private static final long serialVersionUID = -8739097013731823223L; - - public DnsOperationNotSupportedException(String message) { - super(message); - } - - public DnsOperationNotSupportedException(String message, Throwable cause) { - super(message, cause); - } -} diff -r 027e4cb87353 -r 465a15dd6bed src/jdk.dns.client/share/classes/jdk/dns/client/ex/DnsServiceUnavailableException.java --- a/src/jdk.dns.client/share/classes/jdk/dns/client/ex/DnsServiceUnavailableException.java Thu Nov 07 18:44:09 2019 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.dns.client.ex; - -public class DnsServiceUnavailableException extends DnsResolverException { - private static final long serialVersionUID = -4912814054659577232L; - - public DnsServiceUnavailableException(String message) { - super(message); - } - - public DnsServiceUnavailableException(String message, Throwable cause) { - super(message, cause); - } -} diff -r 027e4cb87353 -r 465a15dd6bed 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 07 18:46:06 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 027e4cb87353 -r 465a15dd6bed 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 07 18:46:06 2019 +0000 @@ -0,0 +1,315 @@ +/* + * 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 = true; + 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); + 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) { + + try { + if (DEBUG) { + System.err.printf("Executing processing request:%s%n", request); + } + var rrs = dnsResolver.query(request.hostName, request.resourceId); + if (DEBUG) { + System.err.println("================DNS Resolution queue=================="); + System.err.println("Got answers:" + 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 withSystem.out.println("================DNS Resolution queue=================="); + // the requested addresses + 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; + // 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 027e4cb87353 -r 465a15dd6bed src/jdk.dns.client/share/classes/jdk/dns/client/internal/DnsClient.java --- a/src/jdk.dns.client/share/classes/jdk/dns/client/internal/DnsClient.java Thu Nov 07 18:44:09 2019 +0000 +++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/DnsClient.java Thu Nov 07 18:46:06 2019 +0000 @@ -27,11 +27,13 @@ import jdk.dns.client.ex.DnsCommunicationException; import jdk.dns.client.ex.DnsNameNotFoundException; -import jdk.dns.client.ex.DnsOperationNotSupportedException; import jdk.dns.client.ex.DnsResolverException; -import jdk.dns.client.ex.DnsServiceUnavailableException; +import java.io.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; @@ -44,7 +46,6 @@ import java.security.PrivilegedAction; import java.security.SecureRandom; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -99,7 +100,6 @@ private int retries; // number of UDP retries - private static final SecureRandom random; static { @@ -125,14 +125,13 @@ * "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) - throws UnknownHostException { + 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) { + for (String serverString : servers) { // Is optional port given? int colon = serverString.indexOf(':', @@ -155,8 +154,12 @@ byte[] addr = System.getSecurityManager() == null ? pa.run() : AccessController.doPrivileged(pa); if (addr != null) { - serversList.add(InetAddress.getByAddress(server, addr)); - serverPortsList.add(serverPort); + 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); @@ -257,8 +260,7 @@ Header hdr = new Header(msg, msg.length); if (auth && !hdr.authoritative) { - caughtException = new DnsNameNotFoundException( - "DNS response not authoritative"); + caughtException = new DnsResolverException("DNS response not authoritative"); doNotRetry[i] = true; continue; } @@ -283,7 +285,7 @@ } Header hdr2 = new Header(msg2, msg2.length); if (hdr2.query) { - throw new DnsCommunicationException( + throw new DnsResolverException( "DNS error: expecting response"); } checkResponseCode(hdr2); @@ -362,7 +364,10 @@ 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;}; + var pa = (PrivilegedAction) () -> { + dc.socket().connect(server, port); + return null; + }; if (System.getSecurityManager() == null) { pa.run(); } else { @@ -461,6 +466,7 @@ 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); @@ -536,7 +542,7 @@ Header hdr = new Header(pkt, pkt.length); if (hdr.query) { - throw new DnsCommunicationException("DNS error: expecting response"); + throw new DnsResolverException("DNS error: expecting response"); } if (!reqs.containsKey(xid)) { // already received, ignore the response @@ -562,7 +568,7 @@ // Check that the type/class/name in the query section of the // response match those in the original query - if ((qtype == ResourceRecord.QTYPE_STAR || + if ((qtype == ResourceRecord.TYPE_ANY || qtype == rr.getType()) && (qclass == ResourceRecord.QCLASS_STAR || qclass == rr.getRrclass()) && @@ -632,19 +638,7 @@ : "DNS error"; msg += " [response code " + rcode + "]"; - - switch (rcode) { - case SERVER_FAILURE: - throw new DnsServiceUnavailableException(msg); - case NAME_ERROR: - throw new DnsNameNotFoundException(msg); - case NOT_IMPL: - case REFUSED: - throw new DnsOperationNotSupportedException(msg); - case FORMAT_ERROR: - default: - throw new DnsResolverException(msg); - } + throw new DnsResolverException(msg); } //------------------------------------------------------------------------- @@ -663,8 +657,8 @@ class Tcp { private final Socket sock; - private final java.io.InputStream in; - final java.io.OutputStream out; + private final InputStream in; + final OutputStream out; private int timeoutLeft; Tcp(InetAddress server, int port, int timeout) throws IOException { @@ -677,8 +671,8 @@ throw new SocketTimeoutException(); sock.setTcpNoDelay(true); - out = new java.io.BufferedOutputStream(sock.getOutputStream()); - in = new java.io.BufferedInputStream(sock.getInputStream()); + out = new BufferedOutputStream(sock.getOutputStream()); + in = new BufferedInputStream(sock.getInputStream()); } catch (Exception e) { try { sock.close(); diff -r 027e4cb87353 -r 465a15dd6bed src/jdk.dns.client/share/classes/jdk/dns/client/internal/DnsName.java --- a/src/jdk.dns.client/share/classes/jdk/dns/client/internal/DnsName.java Thu Nov 07 18:44:09 2019 +0000 +++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/DnsName.java Thu Nov 07 18:46:06 2019 +0000 @@ -25,7 +25,7 @@ package jdk.dns.client.internal; -import jdk.dns.client.ex.DnsInvalidNameException; +import jdk.dns.client.ex.DnsResolverException; import java.util.ArrayList; @@ -125,10 +125,10 @@ * Constructs a {@code DnsName} representing a given domain name. * * @param name the domain name to parse - * @throws DnsInvalidNameException if {@code name} does not conform - * to DNS syntax. + * @throws DnsResolverException if {@code name} does not conform + * to DNS syntax. */ - public DnsName(String name) throws DnsInvalidNameException { + public DnsName(String name) throws DnsResolverException { parse(name); } @@ -237,7 +237,7 @@ return new DnsName(this, 0, size()); } - public DnsName add(int pos, String comp) throws DnsInvalidNameException { + public DnsName add(int pos, String comp) throws DnsResolverException { if (pos < 0 || pos > size()) { throw new ArrayIndexOutOfBoundsException(); } @@ -245,13 +245,13 @@ int len = comp.length(); if ((pos > 0 && len == 0) || (pos == 0 && hasRootLabel())) { - throw new DnsInvalidNameException( + 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 DnsInvalidNameException("Name too long"); + throw new DnsResolverException("Name too long"); } octets += (short) (len + 1); } @@ -264,7 +264,7 @@ return this; } - public DnsName addAll(int pos, DnsName n) throws DnsInvalidNameException { + 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. @@ -275,13 +275,13 @@ // Check for empty labels: may have only one, and only at end. if ((pos > 0 && n.hasRootLabel()) || (pos == 0 && hasRootLabel())) { - throw new DnsInvalidNameException( + 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 DnsInvalidNameException("Name too long"); + throw new DnsResolverException("Name too long"); } octets = newOctets; int i = size() - pos; // index for insertion into "labels" @@ -349,7 +349,7 @@ /* * Parses a domain name, setting the values of instance vars accordingly. */ - private void parse(String name) throws DnsInvalidNameException { + private void parse(String name) throws DnsResolverException { StringBuilder label = new StringBuilder(); // label being parsed @@ -391,7 +391,7 @@ * @throws InvalidNameException if a valid escape sequence is not found. */ private static char getEscapedOctet(String name, int pos) - throws DnsInvalidNameException { + throws DnsResolverException { try { // assert (name.charAt(pos) == '\\'); char c1 = name.charAt(++pos); @@ -402,14 +402,14 @@ return (char) ((c1 - '0') * 100 + (c2 - '0') * 10 + (c3 - '0')); } else { - throw new DnsInvalidNameException( + throw new DnsResolverException( "Invalid escape sequence in " + name); } } else { // sequence is `\C' return c1; } } catch (IndexOutOfBoundsException e) { - throw new DnsInvalidNameException( + throw new DnsResolverException( "Invalid escape sequence in " + name); } } @@ -418,16 +418,16 @@ * Checks that this label is valid. * @throws InvalidNameException if label is not valid. */ - private static void verifyLabel(String label) throws DnsInvalidNameException { + private static void verifyLabel(String label) throws DnsResolverException { if (label.length() > 63) { - throw new DnsInvalidNameException( + 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 DnsInvalidNameException( + throw new DnsResolverException( "Label has two-byte char: " + label); } } diff -r 027e4cb87353 -r 465a15dd6bed src/jdk.dns.client/share/classes/jdk/dns/client/internal/DnsNameService.java --- a/src/jdk.dns.client/share/classes/jdk/dns/client/internal/DnsNameService.java Thu Nov 07 18:44:09 2019 +0000 +++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/DnsNameService.java Thu Nov 07 18:46:06 2019 +0000 @@ -53,8 +53,9 @@ if ("false".equals(value)) { return IPv4First; } + // TODO: Decide if it is compatible way with default InetAddress resolver if ("system".equals(value)) { - return DontCare; // TODO: Decide if it is compatible way with default InetAddress resolver + return DontCare; } return IPv4First; } @@ -80,13 +81,12 @@ 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); + .sorted(Comparator.comparing( + ia -> (ia instanceof Inet4Address && order == AddressOrder.IPv4First) + || (ia instanceof Inet6Address && order == AddressOrder.IPv6First), + Boolean::compareTo) + .reversed()) + .toArray(InetAddress[]::new); } } diff -r 027e4cb87353 -r 465a15dd6bed 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 07 18:46:06 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 027e4cb87353 -r 465a15dd6bed src/jdk.dns.client/share/classes/jdk/dns/client/internal/Header.java --- a/src/jdk.dns.client/share/classes/jdk/dns/client/internal/Header.java Thu Nov 07 18:44:09 2019 +0000 +++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/Header.java Thu Nov 07 18:46:06 2019 +0000 @@ -36,6 +36,8 @@ 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; @@ -48,6 +50,8 @@ 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; @@ -88,6 +92,8 @@ 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 diff -r 027e4cb87353 -r 465a15dd6bed src/jdk.dns.client/share/classes/jdk/dns/client/internal/HostsFileResolver.java --- a/src/jdk.dns.client/share/classes/jdk/dns/client/internal/HostsFileResolver.java Thu Nov 07 18:44:09 2019 +0000 +++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/HostsFileResolver.java Thu Nov 07 18:46:06 2019 +0000 @@ -25,11 +25,11 @@ package jdk.dns.client.internal; -import jdk.dns.client.AddressFamily; import jdk.dns.client.internal.util.ReloadTracker; import sun.net.util.IPAddressUtil; import java.net.InetAddress; +import java.net.ProtocolFamily; import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -66,7 +66,7 @@ LOCK.readLock().lock(); var rsf = HOSTS_FILE_TRACKER.getReloadStatus(); try { - if (!HOSTS_FILE_TRACKER.getReloadStatus().isReloadNeeded()) { + if (!rsf.isReloadNeeded()) { return; } } finally { @@ -118,7 +118,7 @@ @Override public String toString() { - return names+"/"+address; + return names + "/" + address; } private HostFileEntry(String name, InetAddress address, boolean isHostname) { @@ -131,7 +131,10 @@ boolean isValid() { return isValid; } - boolean isHostname() {return isHostname;} + + boolean isHostname() { + return isHostname; + } String getHostName() { return names.get(0); @@ -210,11 +213,11 @@ } public InetAddress getHostAddress(String hostName) throws UnknownHostException { - return getHostAddress(hostName, AddressFamily.ANY); + return getHostAddress(hostName, null); } - public InetAddress getHostAddress(String hostName, AddressFamily family) throws UnknownHostException { - + public InetAddress getHostAddress(String hostName, ProtocolFamily family) throws UnknownHostException { + var af = AddressFamily.fromProtocolFamily(family); loadHostsAddresses(); var map = HOST_ADDRESSES; var he = map.get(hostName); @@ -222,7 +225,7 @@ throw new UnknownHostException(hostName); } var addr = he.address; - if (!family.sameFamily(addr)) { + if (!af.sameFamily(addr)) { throw new UnknownHostException(hostName); } return addr; @@ -241,14 +244,14 @@ return entry.get().getHostName(); } - private static boolean isAddressBytesTheSame(byte [] addr1, byte [] addr2) { + 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 servers, int timeout, int retries) - throws UnknownHostException { - dnsClient = new DnsClient(servers, timeout, retries); - } - - public void close() { - dnsClient.close(); - dnsClient = null; - } - - /* - * Queries resource records of a particular class and type for a - * given domain name. - * Useful values of rrclass are ResourceRecord.[Q]CLASS_xxx. - * Useful values of rrtype are ResourceRecord.[Q]TYPE_xxx. - * If recursion is true, recursion is requested on the query. - * If auth is true, only authoritative responses are accepted. - */ - public ResourceRecords query(DnsName fqdn, int rrclass, int rrtype, - boolean recursion, boolean auth) - throws DnsResolverException { - return dnsClient.query(fqdn, rrclass, rrtype, recursion, auth); - } -} diff -r 027e4cb87353 -r 465a15dd6bed src/jdk.dns.client/share/classes/jdk/dns/client/internal/ResourceClassType.java --- a/src/jdk.dns.client/share/classes/jdk/dns/client/internal/ResourceClassType.java Thu Nov 07 18:44:09 2019 +0000 +++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/ResourceClassType.java Thu Nov 07 18:46:06 2019 +0000 @@ -25,8 +25,7 @@ package jdk.dns.client.internal; -import jdk.dns.client.AddressFamily; -import jdk.dns.client.ex.DnsInvalidAttributeIdentifierException; +import jdk.dns.client.ex.DnsResolverException; public class ResourceClassType { int rrclass; @@ -49,8 +48,8 @@ ResourceClassType[] cts; try { cts = ResourceClassType.attrIdsToClassesAndTypes(typeFromAddressFamily(addressFamily)); - } catch (DnsInvalidAttributeIdentifierException e) { - return new ResourceClassType(ResourceRecord.QTYPE_STAR); + } catch (DnsResolverException e) { + return new ResourceClassType(ResourceRecord.TYPE_ANY); } return ResourceClassType.getClassAndTypeToQuery(cts); } @@ -68,7 +67,7 @@ public static ResourceClassType[] attrIdsToClassesAndTypes(String[] attrIds) - throws DnsInvalidAttributeIdentifierException { + throws DnsResolverException { if (attrIds == null) { return null; } @@ -81,10 +80,10 @@ } private static ResourceClassType fromAttrId(String attrId) - throws DnsInvalidAttributeIdentifierException { + throws DnsResolverException { if (attrId.isEmpty()) { - throw new DnsInvalidAttributeIdentifierException( + throw new DnsResolverException( "Attribute ID cannot be empty"); } int rrclass; @@ -98,7 +97,7 @@ String className = attrId.substring(0, space); rrclass = ResourceRecord.getRrclass(className); if (rrclass < 0) { - throw new DnsInvalidAttributeIdentifierException( + throw new DnsResolverException( "Unknown resource record class '" + className + '\''); } } @@ -107,7 +106,7 @@ String typeName = attrId.substring(space + 1); rrtype = ResourceRecord.getType(typeName); if (rrtype < 0) { - throw new DnsInvalidAttributeIdentifierException( + throw new DnsResolverException( "Unknown resource record type '" + typeName + '\''); } @@ -128,7 +127,7 @@ throw new RuntimeException("Internal DNS resolver error"); } else if (cts.length == 0) { // No records are requested, but we need to ask for something. - rrtype = ResourceRecord.QTYPE_STAR; + rrtype = ResourceRecord.TYPE_ANY; } else { rrclass = ResourceRecord.CLASS_INTERNET; rrtype = cts[0].rrtype; @@ -137,7 +136,7 @@ throw new RuntimeException("Internal error: Only CLASS_INTERNET is supported"); } if (rrtype != cts[i].rrtype) { - rrtype = ResourceRecord.QTYPE_STAR; + rrtype = ResourceRecord.TYPE_ANY; } } } diff -r 027e4cb87353 -r 465a15dd6bed src/jdk.dns.client/share/classes/jdk/dns/client/internal/ResourceRecord.java --- a/src/jdk.dns.client/share/classes/jdk/dns/client/internal/ResourceRecord.java Thu Nov 07 18:44:09 2019 +0000 +++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/ResourceRecord.java Thu Nov 07 18:46:06 2019 +0000 @@ -26,7 +26,7 @@ package jdk.dns.client.internal; import jdk.dns.client.ex.DnsCommunicationException; -import jdk.dns.client.ex.DnsInvalidNameException; +import jdk.dns.client.ex.DnsResolverException; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -48,7 +48,7 @@ public static final int TYPE_CNAME = 5; public static final int TYPE_AAAA = 28; public static final int TYPE_PTR = 12; - static final int QTYPE_STAR = 255; // query type "*" + public static final int TYPE_ANY = 255; // query type "*" private static final int TYPE_NS = 2; private static final int TYPE_SOA = 6; @@ -57,7 +57,6 @@ private static final int TYPE_TXT = 16; private static final int TYPE_SRV = 33; private static final int TYPE_NAPTR = 35; - //static final int QTYPE_AXFR = 252; // zone transfer //Do not want to support that /* * Mapping from resource record type codes to type name strings. @@ -183,7 +182,7 @@ String name = null; if ((val > 0) && (val < names.length)) { name = names[val]; - } else if (val == QTYPE_STAR) { // QTYPE_STAR == QCLASS_STAR + } else if (val == TYPE_ANY) { // QTYPE_STAR == QCLASS_STAR name = "*"; } if (name == null) { @@ -196,7 +195,7 @@ if (name.isEmpty()) { return -1; // invalid name } else if (name.equals("*")) { - return QTYPE_STAR; // QTYPE_STAR == QCLASS_STAR + return TYPE_ANY; // QTYPE_STAR == QCLASS_STAR } if (Character.isDigit(name.charAt(0))) { try { @@ -343,7 +342,7 @@ } else throw new IOException("Invalid label type: " + typeAndLen); } - } catch (IOException | DnsInvalidNameException e) { + } catch (IOException | DnsResolverException e) { DnsCommunicationException ce = new DnsCommunicationException( "DNS error: malformed packet"); ce.initCause(e); diff -r 027e4cb87353 -r 465a15dd6bed src/jdk.dns.client/share/classes/jdk/dns/client/internal/ResourceRecords.java --- a/src/jdk.dns.client/share/classes/jdk/dns/client/internal/ResourceRecords.java Thu Nov 07 18:44:09 2019 +0000 +++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/ResourceRecords.java Thu Nov 07 18:46:06 2019 +0000 @@ -51,12 +51,20 @@ // TODO: Try to reuse additional section to avoid additional CNAME lookups public Vector additional = new Vector<>(); + private boolean hasIpv4InAnswer; + private boolean hasIpv6InAnswer; + private boolean hasAliasInAnswer; - public boolean hasAddressOrAlias() { - return answer.stream().anyMatch( - rr -> rr.rrtype == ResourceRecord.TYPE_A - || rr.rrtype == ResourceRecord.TYPE_AAAA - || rr.rrtype == ResourceRecord.TYPE_CNAME); + + 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; } /* @@ -82,28 +90,6 @@ } /* - * Returns the type field of the first answer record, or -1 if - * there are no answer records. - */ - int getFirstAnsType() { - if (answer.size() == 0) { - return -1; - } - return answer.firstElement().getType(); - } - - /* - * Returns the type field of the last answer record, or -1 if - * there are no answer records. - */ - int getLastAnsType() { - if (answer.size() == 0) { - return -1; - } - return answer.lastElement().getType(); - } - - /* * Decodes the resource records in a DNS message and adds * them to this object. * Does not modify or store a reference to the msg array. @@ -125,6 +111,9 @@ 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(); } diff -r 027e4cb87353 -r 465a15dd6bed src/jdk.dns.client/share/classes/module-info.java --- a/src/jdk.dns.client/share/classes/module-info.java Thu Nov 07 18:44:09 2019 +0000 +++ b/src/jdk.dns.client/share/classes/module-info.java Thu Nov 07 18:46:06 2019 +0000 @@ -30,6 +30,5 @@ module jdk.dns.client { exports jdk.dns.client; exports jdk.dns.client.ex; - exports jdk.dns.client.internal; // For debugging provides InetAddress.NameService with DnsNameService; }