From 6d7ccb322e66735e8c7138fb5a2962e2076734ea Mon Sep 17 00:00:00 2001 From: nathan Date: Thu, 11 Jan 2018 14:58:35 +0100 Subject: [PATCH] DnsClient now throws UnknownHostException if there is no DNS resolution. Added a default timeout for DNS queries/etc + method with the timeout as a parameter --- src/dorkbox/network/DnsClient.java | 134 ++++++++++++++++++++--------- 1 file changed, 92 insertions(+), 42 deletions(-) diff --git a/src/dorkbox/network/DnsClient.java b/src/dorkbox/network/DnsClient.java index b9dddb90..b1df8137 100644 --- a/src/dorkbox/network/DnsClient.java +++ b/src/dorkbox/network/DnsClient.java @@ -27,6 +27,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.concurrent.TimeUnit; import org.slf4j.Logger; @@ -72,19 +73,23 @@ import io.netty.util.internal.PlatformDependent; public class DnsClient extends EndPoint { - /* - http://bugs.java.com/view_bug.do?bug_id=8176361 - Previous JDK releases documented how to configure `java.net.InetAddress` to use the JNDI DNS service provider as the name service. This mechanism, and the system properties to configure it, have been removed in JDK 9 - -A new mechanism to configure the use of a hosts file has been introduced. - -A new system property `jdk.net.hosts.file` has been defined. When this system property is set, the name and address resolution calls of `InetAddress`, i.e `getByXXX`, retrieve the relevant mapping from the specified file. The structure of this file is equivalent to that of the `/etc/hosts` file. - -When the system property `jdk.net.hosts.file` is set, and the specified file doesn't exist, the name or address lookup will result in an UnknownHostException. Thus, a non existent hosts file is handled as if the file is empty. - - -UP UNTIL java 1.8, one can use org/xbill/DNS/spi, ie: sun.net.dns.ResolverConfiguration - */ +/* + * TODO: verify ResolverConfiguration works as expected! + * http://bugs.java.com/view_bug.do?bug_id=8176361 + * Previous JDK releases documented how to configure `java.net.InetAddress` to use the JNDI DNS service provider as the name service. + * This mechanism, and the system properties to configure it, have been removed in JDK 9 + * + * A new mechanism to configure the use of a hosts file has been introduced. + * + * A new system property `jdk.net.hosts.file` has been defined. When this system property is set, the name and address resolution calls + * of `InetAddress`, i.e `getByXXX`, retrieve the relevant mapping from the specified file. The structure of this file is equivalent to + * that of the `/etc/hosts` file. + * + * When the system property `jdk.net.hosts.file` is set, and the specified file doesn't exist, the name or address lookup will result in + * an UnknownHostException. Thus, a non existent hosts file is handled as if the file is empty. + * + * UP UNTIL java 1.8, one can use org/xbill/DNS/spi, ie: sun.net.dns.ResolverConfiguration + */ /** @@ -108,7 +113,6 @@ UP UNTIL java 1.8, one can use org/xbill/DNS/spi, ie: sun.net.dns.ResolverConfig */ public final static List DEFAULT_DNS_SERVER_LIST = DefaultDnsServerAddressStreamProvider.defaultAddressList(); - private static final String ptrSuffix = ".in-addr.arpa"; public static final InetAddress[] INET_ADDRESSES = new InetAddress[0]; /** @@ -602,41 +606,87 @@ UP UNTIL java 1.8, one can use org/xbill/DNS/spi, ie: sun.net.dns.ResolverConfig /** - * Resolves a specific hostname A/AAAA record. + * Resolves a specific hostname A/AAAA record with the default timeout of 5 seconds * * @param hostname the hostname, ie: google.com, that you want to resolve - * @return the list of resolved InetAddress or null + * + * @return the list of resolved InetAddress or throws an exception if the hostname cannot be resolved * @throws UnknownHostException if the hostname cannot be resolved */ public List resolve(String hostname) throws UnknownHostException { + return resolve(hostname, 5); + } + + /** + * Resolves a specific hostname A/AAAA record. + * + * @param hostname the hostname, ie: google.com, that you want to resolve + * @param queryTimeoutSeconds the number of seconds to wait for host resolution + * + * @return the list of resolved InetAddress or throws an exception if the hostname cannot be resolved + * @throws UnknownHostException if the hostname cannot be resolved + */ + public + List resolve(String hostname, int queryTimeoutSeconds) throws UnknownHostException { + if (hostname == null) { + throw new UnknownHostException("Cannot submit query for an unknown host"); + } + if (resolver == null) { start(); } // use "resolve", since it handles A/AAAA records + redirects correctly final Future> resolve = resolver.resolveAll(hostname); - final Future> result = resolve.awaitUninterruptibly(); + + boolean finished = resolve.awaitUninterruptibly(queryTimeoutSeconds, TimeUnit.SECONDS); // now return whatever value we had - if (result.isSuccess() && result.isDone()) { + if (finished && resolve.isSuccess() && resolve.isDone()) { try { - List now = result.getNow(); + List now = resolve.getNow(); return now; } catch (Exception e) { - logger.error("Could not ask question to DNS server", e); - return null; + String msg = "Could not ask question to DNS server"; + logger.error(msg, e); + throw new UnknownHostException(msg); } } - logger.error("Could not ask question to DNS server for A/AAAA record: {}", hostname); + String msg = "Could not ask question to DNS server for A/AAAA record: {}"; + logger.error(msg, hostname); - UnknownHostException cause = (UnknownHostException) result.cause(); + UnknownHostException cause = (UnknownHostException) resolve.cause(); if (cause != null) { throw cause; } - return null; + throw new UnknownHostException(msg); + } + + /** + * Resolves a specific hostname record, of the specified type (PTR, MX, TXT, etc) with the default timeout of 5 seconds + *

+ *

+ * Note: PTR queries absolutely MUST end in '.in-addr.arpa' in order for the DNS server to understand it. + * -- because of this, we will automatically fix this in case that clients are unaware of this requirement + *

+ *

+ * Note: A/AAAA queries absolutely MUST end in a '.' -- because of this we will automatically fix this in case that clients are + * unaware of this requirement + * + * @param hostname the hostname, ie: google.com, that you want to resolve + * @param type the DnsRecordType you want to resolve (PTR, MX, TXT, etc) + * + * @return the DnsRecords or throws an exception if the hostname cannot be resolved + * + * @throws @throws UnknownHostException if the hostname cannot be resolved + */ + @SuppressWarnings({"unchecked", "Duplicates"}) + public + DnsRecord[] query(String hostname, final int type) throws UnknownHostException { + return query(hostname, type, 5); } /** @@ -652,32 +702,32 @@ UP UNTIL java 1.8, one can use org/xbill/DNS/spi, ie: sun.net.dns.ResolverConfig * * @param hostname the hostname, ie: google.com, that you want to resolve * @param type the DnsRecordType you want to resolve (PTR, MX, TXT, etc) - * @return the DnsRecords or null if there was an error resolving the hostname + * @param queryTimeoutSeconds the number of seconds to wait for host resolution + * + * @return the DnsRecords or throws an exception if the hostname cannot be resolved * * @throws @throws UnknownHostException if the hostname cannot be resolved */ @SuppressWarnings({"unchecked", "Duplicates"}) public - DnsRecord[] query(String hostname, final int type) throws UnknownHostException { - if (resolver == null) { - start(); + DnsRecord[] query(String hostname, final int type, int queryTimeoutSeconds) throws UnknownHostException { + if (hostname == null) { + throw new UnknownHostException("Cannot submit query for an unknown host"); } - if (type == DnsRecordType.PTR && !hostname.endsWith(ptrSuffix)) { - // PTR absolutely MUST end in '.in-addr.arpa' in order for the DNS server to understand it. - // in this case, hostname is an ip address - hostname += ptrSuffix; + if (resolver == null) { + start(); } // we use our own resolvers DnsQuestion dnsMessage = DnsQuestion.newQuery(hostname, type, recursionDesired); final Future> query = resolver.query(dnsMessage); - final Future> result = query.awaitUninterruptibly(); + boolean finished = query.awaitUninterruptibly(queryTimeoutSeconds, TimeUnit.SECONDS); // now return whatever value we had - if (result.isSuccess() && result.isDone()) { - AddressedEnvelope envelope = result.getNow(); + if (finished && query.isSuccess() && query.isDone()) { + AddressedEnvelope envelope = query.getNow(); DnsResponse response = envelope.content(); try { final int code = response.getHeader().getRcode(); @@ -685,24 +735,24 @@ UP UNTIL java 1.8, one can use org/xbill/DNS/spi, ie: sun.net.dns.ResolverConfig return response.getSectionArray(DnsSection.ANSWER); } - logger.error("Could not ask question to DNS server: Error code {} for type: {} - {}", - code, type, DnsRecordType.string(type)); + String msg = "Could not ask question to DNS server: Error code " + code + " for type: " + type + " - " + DnsRecordType.string(type); + logger.error(msg); - logger.error("Could not ask question to DNS server: Error code {}", code); - return null; + throw new UnknownHostException(msg); } finally { response.release(); } } - logger.error("Could not ask question to DNS server for type: {}", DnsRecordType.string(type)); + String msg = "Could not ask question to DNS server for type: " + DnsRecordType.string(type); + logger.error(msg); - UnknownHostException cause = (UnknownHostException) result.cause(); + UnknownHostException cause = (UnknownHostException) query.cause(); if (cause != null) { throw cause; } - return null; + throw new UnknownHostException(msg); } }