/* * Copyright 2014 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package io.netty.resolver.dns; import io.netty.bootstrap.Bootstrap; import io.netty.channel.AddressedEnvelope; import io.netty.channel.Channel; import io.netty.channel.ChannelFactory; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoop; import io.netty.channel.FixedRecvByteBufAllocator; import io.netty.channel.socket.DatagramChannel; import io.netty.channel.socket.InternetProtocolFamily; import io.netty.handler.codec.dns.DatagramDnsQueryEncoder; import io.netty.handler.codec.dns.DatagramDnsResponse; import io.netty.handler.codec.dns.DnsRawRecord; import io.netty.handler.codec.dns.DnsRecord; import io.netty.handler.codec.dns.DatagramDnsResponseDecoder; import io.netty.handler.codec.dns.DnsQuestion; import io.netty.handler.codec.dns.DnsRecordType; import io.netty.handler.codec.dns.DnsResponse; import io.netty.resolver.HostsFileEntriesResolver; import io.netty.resolver.InetNameResolver; import io.netty.util.NetUtil; import io.netty.util.ReferenceCountUtil; import io.netty.util.concurrent.FastThreadLocal; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Promise; import io.netty.util.internal.EmptyArrays; import io.netty.util.internal.PlatformDependent; import io.netty.util.internal.StringUtil; import io.netty.util.internal.UnstableApi; import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLoggerFactory; import java.lang.reflect.Method; import java.net.IDN; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import static io.netty.util.internal.ObjectUtil.*; /** * A DNS-based {@link InetNameResolver}. */ @UnstableApi public class DnsNameResolver extends InetNameResolver { private static final InternalLogger logger = InternalLoggerFactory.getInstance(DnsNameResolver.class); private static final String LOCALHOST = "localhost"; private static final InetAddress LOCALHOST_ADDRESS; private static final DnsRecord[] EMTPY_ADDITIONALS = new DnsRecord[0]; static final InternetProtocolFamily[] DEFAULT_RESOLVE_ADDRESS_TYPES; static final String[] DEFAULT_SEACH_DOMAINS; static { if (NetUtil.isIpV4StackPreferred()) { DEFAULT_RESOLVE_ADDRESS_TYPES = new InternetProtocolFamily[] { InternetProtocolFamily.IPv4 }; LOCALHOST_ADDRESS = NetUtil.LOCALHOST4; } else { DEFAULT_RESOLVE_ADDRESS_TYPES = new InternetProtocolFamily[2]; if (NetUtil.isIpV6AddressesPreferred()) { DEFAULT_RESOLVE_ADDRESS_TYPES[0] = InternetProtocolFamily.IPv6; DEFAULT_RESOLVE_ADDRESS_TYPES[1] = InternetProtocolFamily.IPv4; LOCALHOST_ADDRESS = NetUtil.LOCALHOST6; } else { DEFAULT_RESOLVE_ADDRESS_TYPES[0] = InternetProtocolFamily.IPv4; DEFAULT_RESOLVE_ADDRESS_TYPES[1] = InternetProtocolFamily.IPv6; LOCALHOST_ADDRESS = NetUtil.LOCALHOST4; } } } static { String[] searchDomains; try { Class configClass = Class.forName("sun.net.dns.ResolverConfiguration"); Method open = configClass.getMethod("open"); Method nameservers = configClass.getMethod("searchlist"); Object instance = open.invoke(null); @SuppressWarnings("unchecked") List list = (List) nameservers.invoke(instance); searchDomains = list.toArray(new String[list.size()]); } catch (Exception ignore) { // Failed to get the system name search domain list. searchDomains = EmptyArrays.EMPTY_STRINGS; } DEFAULT_SEACH_DOMAINS = searchDomains; } private static final DatagramDnsResponseDecoder DECODER = new DatagramDnsResponseDecoder(); private static final DatagramDnsQueryEncoder ENCODER = new DatagramDnsQueryEncoder(); final DnsServerAddresses nameServerAddresses; final Future channelFuture; final DatagramChannel ch; /** * Manages the {@link DnsQueryContext}s in progress and their query IDs. */ final DnsQueryContextManager queryContextManager = new DnsQueryContextManager(); /** * Cache for {@link #doResolve(String, Promise)} and {@link #doResolveAll(String, Promise)}. */ private final DnsCache resolveCache; private final FastThreadLocal nameServerAddrStream = new FastThreadLocal() { @Override protected DnsServerAddressStream initialValue() throws Exception { return nameServerAddresses.stream(); } }; private final long queryTimeoutMillis; private final int maxQueriesPerResolve; private final boolean traceEnabled; private final InternetProtocolFamily[] resolvedAddressTypes; private final boolean recursionDesired; private final int maxPayloadSize; private final boolean optResourceEnabled; private final HostsFileEntriesResolver hostsFileEntriesResolver; private final String[] searchDomains; private final int ndots; private final boolean cnameFollowARecords; private final boolean cnameFollowAAAARecords; private final InternetProtocolFamily preferredAddressType; private final DnsRecordType[] resolveRecordTypes; private final boolean decodeIdn; /** * Creates a new DNS-based name resolver that communicates with the specified list of DNS servers. * * @param eventLoop the {@link EventLoop} which will perform the communication with the DNS servers * @param channelFactory the {@link ChannelFactory} that will create a {@link DatagramChannel} * @param nameServerAddresses the addresses of the DNS server. For each DNS query, a new stream is created from * this to determine which DNS server should be contacted for the next retry in case * of failure. * @param resolveCache the DNS resolved entries cache * @param queryTimeoutMillis timeout of each DNS query in millis * @param resolvedAddressTypes list of the protocol families * @param recursionDesired if recursion desired flag must be set * @param maxQueriesPerResolve the maximum allowed number of DNS queries for a given name resolution * @param traceEnabled if trace is enabled * @param maxPayloadSize the capacity of the datagram packet buffer * @param optResourceEnabled if automatic inclusion of a optional records is enabled * @param hostsFileEntriesResolver the {@link HostsFileEntriesResolver} used to check for local aliases * @param searchDomains the list of search domain * @param ndots the ndots value * @deprecated use {@link #DnsNameResolver(EventLoop, ChannelFactory, DnsServerAddresses, DnsCache, long, * InternetProtocolFamily[], boolean, int, boolean, int, * boolean, HostsFileEntriesResolver, String[], int, boolean)} */ @Deprecated public DnsNameResolver( EventLoop eventLoop, ChannelFactory channelFactory, DnsServerAddresses nameServerAddresses, final DnsCache resolveCache, long queryTimeoutMillis, InternetProtocolFamily[] resolvedAddressTypes, boolean recursionDesired, int maxQueriesPerResolve, boolean traceEnabled, int maxPayloadSize, boolean optResourceEnabled, HostsFileEntriesResolver hostsFileEntriesResolver, String[] searchDomains, int ndots) { this(eventLoop, channelFactory, nameServerAddresses, resolveCache, queryTimeoutMillis, resolvedAddressTypes, recursionDesired, maxQueriesPerResolve, traceEnabled, maxPayloadSize, optResourceEnabled, hostsFileEntriesResolver, searchDomains, ndots, true); } /** * Creates a new DNS-based name resolver that communicates with the specified list of DNS servers. * * @param eventLoop the {@link EventLoop} which will perform the communication with the DNS servers * @param channelFactory the {@link ChannelFactory} that will create a {@link DatagramChannel} * @param nameServerAddresses the addresses of the DNS server. For each DNS query, a new stream is created from * this to determine which DNS server should be contacted for the next retry in case * of failure. * @param resolveCache the DNS resolved entries cache * @param queryTimeoutMillis timeout of each DNS query in millis * @param resolvedAddressTypes list of the protocol families * @param recursionDesired if recursion desired flag must be set * @param maxQueriesPerResolve the maximum allowed number of DNS queries for a given name resolution * @param traceEnabled if trace is enabled * @param maxPayloadSize the capacity of the datagram packet buffer * @param optResourceEnabled if automatic inclusion of a optional records is enabled * @param hostsFileEntriesResolver the {@link HostsFileEntriesResolver} used to check for local aliases * @param searchDomains the list of search domain * @param ndots the ndots value * @param decodeIdn {@code true} if domain / host names should be decoded to unicode when received. * See rfc3492. */ public DnsNameResolver( EventLoop eventLoop, ChannelFactory channelFactory, DnsServerAddresses nameServerAddresses, final DnsCache resolveCache, long queryTimeoutMillis, InternetProtocolFamily[] resolvedAddressTypes, boolean recursionDesired, int maxQueriesPerResolve, boolean traceEnabled, int maxPayloadSize, boolean optResourceEnabled, HostsFileEntriesResolver hostsFileEntriesResolver, String[] searchDomains, int ndots, boolean decodeIdn) { super(eventLoop); checkNotNull(channelFactory, "channelFactory"); this.nameServerAddresses = checkNotNull(nameServerAddresses, "nameServerAddresses"); this.queryTimeoutMillis = checkPositive(queryTimeoutMillis, "queryTimeoutMillis"); this.resolvedAddressTypes = checkNonEmpty(resolvedAddressTypes, "resolvedAddressTypes"); this.recursionDesired = recursionDesired; this.maxQueriesPerResolve = checkPositive(maxQueriesPerResolve, "maxQueriesPerResolve"); this.traceEnabled = traceEnabled; this.maxPayloadSize = checkPositive(maxPayloadSize, "maxPayloadSize"); this.optResourceEnabled = optResourceEnabled; this.hostsFileEntriesResolver = checkNotNull(hostsFileEntriesResolver, "hostsFileEntriesResolver"); this.resolveCache = checkNotNull(resolveCache, "resolveCache"); this.searchDomains = checkNotNull(searchDomains, "searchDomains").clone(); this.ndots = checkPositiveOrZero(ndots, "ndots"); this.decodeIdn = decodeIdn; boolean cnameFollowARecords = false; boolean cnameFollowAAAARecords = false; // Use LinkedHashSet to maintain correct ordering. Set recordTypes = new LinkedHashSet(resolvedAddressTypes.length); for (InternetProtocolFamily family: resolvedAddressTypes) { switch (family) { case IPv4: cnameFollowARecords = true; recordTypes.add(DnsRecordType.A); break; case IPv6: cnameFollowAAAARecords = true; recordTypes.add(DnsRecordType.AAAA); break; default: throw new Error(); } } // One of both must be always true. assert cnameFollowARecords || cnameFollowAAAARecords; this.cnameFollowAAAARecords = cnameFollowAAAARecords; this.cnameFollowARecords = cnameFollowARecords; resolveRecordTypes = recordTypes.toArray(new DnsRecordType[recordTypes.size()]); preferredAddressType = resolvedAddressTypes[0]; Bootstrap b = new Bootstrap(); b.group(executor()); b.channelFactory(channelFactory); b.option(ChannelOption.DATAGRAM_CHANNEL_ACTIVE_ON_REGISTRATION, true); final DnsResponseHandler responseHandler = new DnsResponseHandler(executor().newPromise()); b.handler(new ChannelInitializer() { @Override protected void initChannel(DatagramChannel ch) throws Exception { ch.pipeline().addLast(DECODER, ENCODER, responseHandler); } }); channelFuture = responseHandler.channelActivePromise; ch = (DatagramChannel) b.register().channel(); ch.config().setRecvByteBufAllocator(new FixedRecvByteBufAllocator(maxPayloadSize)); ch.closeFuture().addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { resolveCache.clear(); } }); } /** * Returns the resolution cache. */ public DnsCache resolveCache() { return resolveCache; } /** * Returns the timeout of each DNS query performed by this resolver (in milliseconds). * The default value is 5 seconds. */ public long queryTimeoutMillis() { return queryTimeoutMillis; } /** * Returns the list of the protocol families of the address resolved by {@link #resolve(String)} * in the order of preference. * The default value depends on the value of the system property {@code "java.net.preferIPv6Addresses"}. */ public List resolvedAddressTypes() { return Arrays.asList(resolvedAddressTypes); } InternetProtocolFamily[] resolveAddressTypesUnsafe() { return resolvedAddressTypes; } final String[] searchDomains() { return searchDomains; } final int ndots() { return ndots; } final boolean isCnameFollowAAAARecords() { return cnameFollowAAAARecords; } final boolean isCnameFollowARecords() { return cnameFollowARecords; } final InternetProtocolFamily preferredAddressType() { return preferredAddressType; } final DnsRecordType[] resolveRecordTypes() { return resolveRecordTypes; } final boolean isDecodeIdn() { return decodeIdn; } /** * Returns {@code true} if and only if this resolver sends a DNS query with the RD (recursion desired) flag set. * The default value is {@code true}. */ public boolean isRecursionDesired() { return recursionDesired; } /** * Returns the maximum allowed number of DNS queries to send when resolving a host name. * The default value is {@code 8}. */ public int maxQueriesPerResolve() { return maxQueriesPerResolve; } /** * Returns if this resolver should generate the detailed trace information in an exception message so that * it is easier to understand the cause of resolution failure. The default value if {@code true}. */ public boolean isTraceEnabled() { return traceEnabled; } /** * Returns the capacity of the datagram packet buffer (in bytes). The default value is {@code 4096} bytes. */ public int maxPayloadSize() { return maxPayloadSize; } /** * Returns the automatic inclusion of a optional records that tries to give the remote DNS server a hint about how * much data the resolver can read per response is enabled. */ public boolean isOptResourceEnabled() { return optResourceEnabled; } /** * Returns the component that tries to resolve hostnames against the hosts file prior to asking to * remotes DNS servers. */ public HostsFileEntriesResolver hostsFileEntriesResolver() { return hostsFileEntriesResolver; } /** * Closes the internal datagram channel used for sending and receiving DNS messages, and clears all DNS resource * records from the cache. Attempting to send a DNS query or to resolve a domain name will fail once this method * has been called. */ @Override public void close() { if (ch.isOpen()) { ch.close(); } } @Override protected EventLoop executor() { return (EventLoop) super.executor(); } private InetAddress resolveHostsFileEntry(String hostname) { if (hostsFileEntriesResolver == null) { return null; } else { InetAddress address = hostsFileEntriesResolver.address(hostname); if (address == null && PlatformDependent.isWindows() && LOCALHOST.equalsIgnoreCase(hostname)) { // If we tried to resolve localhost we need workaround that windows removed localhost from its // hostfile in later versions. // See https://github.com/netty/netty/issues/5386 return LOCALHOST_ADDRESS; } return address; } } /** * Resolves the specified name into an address. * * @param inetHost the name to resolve * @param additionals additional records ({@code OPT}) * * @return the address as the result of the resolution */ public final Future resolve(String inetHost, Iterable additionals) { return resolve(inetHost, additionals, executor().newPromise()); } /** * Resolves the specified name into an address. * * @param inetHost the name to resolve * @param additionals additional records ({@code OPT}) * @param promise the {@link Promise} which will be fulfilled when the name resolution is finished * * @return the address as the result of the resolution */ public final Future resolve(String inetHost, Iterable additionals, Promise promise) { checkNotNull(promise, "promise"); if (inetHost == null || inetHost.isEmpty()) { // If an empty hostname is used we should use "localhost", just like InetAddress.getByName(...) does. return promise.setSuccess(loopbackAddress()); } DnsRecord[] additionalsArray = toArray(additionals, true); try { doResolve(inetHost, additionalsArray, promise, resolveCache); return promise; } catch (Exception e) { return promise.setFailure(e); } } /** * Resolves the specified host name and port into a list of address. * * @param inetHost the name to resolve * @param additionals additional records ({@code OPT}) * * @return the list of the address as the result of the resolution */ public final Future> resolveAll(String inetHost, Iterable additionals) { return resolveAll(inetHost, additionals, executor().>newPromise()); } /** * Resolves the specified host name and port into a list of address. * * @param inetHost the name to resolve * @param additionals additional records ({@code OPT}) * @param promise the {@link Promise} which will be fulfilled when the name resolution is finished * * @return the list of the address as the result of the resolution */ public final Future> resolveAll(String inetHost, Iterable additionals, Promise> promise) { checkNotNull(promise, "promise"); if (inetHost == null || inetHost.isEmpty()) { // If an empty hostname is used we should use "localhost", just like InetAddress.getAllByName(...) does. return promise.setSuccess(Collections.singletonList(loopbackAddress())); } DnsRecord[] additionalsArray = toArray(additionals, true); try { doResolveAll(inetHost, additionalsArray, promise, resolveCache); return promise; } catch (Exception e) { return promise.setFailure(e); } } @Override protected void doResolve(String inetHost, Promise promise) throws Exception { doResolve(inetHost, EMTPY_ADDITIONALS, promise, resolveCache); } private static DnsRecord[] toArray(Iterable additionals, boolean validateType) { checkNotNull(additionals, "additionals"); if (additionals instanceof Collection) { Collection records = (Collection) additionals; for (DnsRecord r: additionals) { validateAdditional(r, validateType); } return records.toArray(new DnsRecord[records.size()]); } Iterator additionalsIt = additionals.iterator(); if (!additionalsIt.hasNext()) { return EMTPY_ADDITIONALS; } List records = new ArrayList(); do { DnsRecord r = additionalsIt.next(); validateAdditional(r, validateType); records.add(r); } while (additionalsIt.hasNext()); return records.toArray(new DnsRecord[records.size()]); } private static void validateAdditional(DnsRecord record, boolean validateType) { checkNotNull(record, "record"); if (validateType && record instanceof DnsRawRecord) { throw new IllegalArgumentException("DnsRawRecord implementations not allowed: " + record); } } @Override protected final InetAddress loopbackAddress() { return preferredAddressType() == InternetProtocolFamily.IPv4 ? NetUtil.LOCALHOST4 : NetUtil.LOCALHOST6; } /** * Hook designed for extensibility so one can pass a different cache on each resolution attempt * instead of using the global one. */ protected void doResolve(String inetHost, DnsRecord[] additionals, Promise promise, DnsCache resolveCache) throws Exception { final byte[] bytes = NetUtil.createByteArrayFromIpAddressString(inetHost); if (bytes != null) { // The inetHost is actually an ipaddress. promise.setSuccess(InetAddress.getByAddress(bytes)); return; } final String hostname = hostname(inetHost); InetAddress hostsFileEntry = resolveHostsFileEntry(hostname); if (hostsFileEntry != null) { promise.setSuccess(hostsFileEntry); return; } if (!doResolveCached(hostname, additionals, promise, resolveCache)) { doResolveUncached(hostname, additionals, promise, resolveCache); } } private boolean doResolveCached(String hostname, DnsRecord[] additionals, Promise promise, DnsCache resolveCache) { final List cachedEntries = resolveCache.get(hostname, additionals); if (cachedEntries == null || cachedEntries.isEmpty()) { return false; } InetAddress address = null; Throwable cause = null; synchronized (cachedEntries) { final int numEntries = cachedEntries.size(); assert numEntries > 0; if (cachedEntries.get(0).cause() != null) { cause = cachedEntries.get(0).cause(); } else { // Find the first entry with the preferred address type. for (InternetProtocolFamily f : resolvedAddressTypes) { for (int i = 0; i < numEntries; i++) { final DnsCacheEntry e = cachedEntries.get(i); if (f.addressType().isInstance(e.address())) { address = e.address(); break; } } } } } if (address != null) { trySuccess(promise, address); return true; } if (cause != null) { tryFailure(promise, cause); return true; } return false; } private static void trySuccess(Promise promise, T result) { if (!promise.trySuccess(result)) { logger.warn("Failed to notify success ({}) to a promise: {}", result, promise); } } private static void tryFailure(Promise promise, Throwable cause) { if (!promise.tryFailure(cause)) { logger.warn("Failed to notify failure to a promise: {}", promise, cause); } } private void doResolveUncached(String hostname, DnsRecord[] additionals, Promise promise, DnsCache resolveCache) { SingleResolverContext ctx = new SingleResolverContext(this, hostname, additionals, resolveCache); ctx.resolve(promise); } static final class SingleResolverContext extends DnsNameResolverContext { SingleResolverContext(DnsNameResolver parent, String hostname, DnsRecord[] additionals, DnsCache resolveCache) { super(parent, hostname, additionals, resolveCache); } @Override DnsNameResolverContext newResolverContext(DnsNameResolver parent, String hostname, DnsRecord[] additionals, DnsCache resolveCache) { return new SingleResolverContext(parent, hostname, additionals, resolveCache); } @Override boolean finishResolve( Class addressType, List resolvedEntries, Promise promise) { final int numEntries = resolvedEntries.size(); for (int i = 0; i < numEntries; i++) { final InetAddress a = resolvedEntries.get(i).address(); if (addressType.isInstance(a)) { trySuccess(promise, a); return true; } } return false; } } @Override protected void doResolveAll(String inetHost, Promise> promise) throws Exception { doResolveAll(inetHost, EMTPY_ADDITIONALS, promise, resolveCache); } /** * Hook designed for extensibility so one can pass a different cache on each resolution attempt * instead of using the global one. */ protected void doResolveAll(String inetHost, DnsRecord[] additionals, Promise> promise, DnsCache resolveCache) throws Exception { final byte[] bytes = NetUtil.createByteArrayFromIpAddressString(inetHost); if (bytes != null) { // The unresolvedAddress was created via a String that contains an ipaddress. promise.setSuccess(Collections.singletonList(InetAddress.getByAddress(bytes))); return; } final String hostname = hostname(inetHost); InetAddress hostsFileEntry = resolveHostsFileEntry(hostname); if (hostsFileEntry != null) { promise.setSuccess(Collections.singletonList(hostsFileEntry)); return; } if (!doResolveAllCached(hostname, additionals, promise, resolveCache)) { doResolveAllUncached(hostname, additionals, promise, resolveCache); } } private boolean doResolveAllCached(String hostname, DnsRecord[] additionals, Promise> promise, DnsCache resolveCache) { final List cachedEntries = resolveCache.get(hostname, additionals); if (cachedEntries == null || cachedEntries.isEmpty()) { return false; } List result = null; Throwable cause = null; synchronized (cachedEntries) { final int numEntries = cachedEntries.size(); assert numEntries > 0; if (cachedEntries.get(0).cause() != null) { cause = cachedEntries.get(0).cause(); } else { for (InternetProtocolFamily f : resolvedAddressTypes) { for (int i = 0; i < numEntries; i++) { final DnsCacheEntry e = cachedEntries.get(i); if (f.addressType().isInstance(e.address())) { if (result == null) { result = new ArrayList(numEntries); } result.add(e.address()); } } } } } if (result != null) { trySuccess(promise, result); return true; } if (cause != null) { tryFailure(promise, cause); return true; } return false; } static final class ListResolverContext extends DnsNameResolverContext> { ListResolverContext(DnsNameResolver parent, String hostname, DnsRecord[] additionals, DnsCache resolveCache) { super(parent, hostname, additionals, resolveCache); } @Override DnsNameResolverContext> newResolverContext( DnsNameResolver parent, String hostname, DnsRecord[] additionals, DnsCache resolveCache) { return new ListResolverContext(parent, hostname, additionals, resolveCache); } @Override boolean finishResolve( Class addressType, List resolvedEntries, Promise> promise) { List result = null; final int numEntries = resolvedEntries.size(); for (int i = 0; i < numEntries; i++) { final InetAddress a = resolvedEntries.get(i).address(); if (addressType.isInstance(a)) { if (result == null) { result = new ArrayList(numEntries); } result.add(a); } } if (result != null) { promise.trySuccess(result); return true; } return false; } } private void doResolveAllUncached(String hostname, DnsRecord[] additionals, Promise> promise, DnsCache resolveCache) { DnsNameResolverContext> ctx = new ListResolverContext( this, hostname, additionals, resolveCache); ctx.resolve(promise); } private static String hostname(String inetHost) { String hostname = IDN.toASCII(inetHost); // Check for http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6894622 if (StringUtil.endsWith(inetHost, '.') && !StringUtil.endsWith(hostname, '.')) { hostname += "."; } return hostname; } /** * Sends a DNS query with the specified question. */ public Future> query(DnsQuestion question) { return query(nextNameServerAddress(), question); } /** * Sends a DNS query with the specified question with additional records. */ public Future> query( DnsQuestion question, Iterable additionals) { return query(nextNameServerAddress(), question, additionals); } /** * Sends a DNS query with the specified question. */ public Future> query( DnsQuestion question, Promise> promise) { return query(nextNameServerAddress(), question, Collections.emptyList(), promise); } private InetSocketAddress nextNameServerAddress() { return nameServerAddrStream.get().next(); } /** * Sends a DNS query with the specified question using the specified name server list. */ public Future> query( InetSocketAddress nameServerAddr, DnsQuestion question) { return query0(nameServerAddr, question, EMTPY_ADDITIONALS, ch.eventLoop().>newPromise()); } /** * Sends a DNS query with the specified question with additional records using the specified name server list. */ public Future> query( InetSocketAddress nameServerAddr, DnsQuestion question, Iterable additionals) { return query0(nameServerAddr, question, toArray(additionals, false), ch.eventLoop().>newPromise()); } /** * Sends a DNS query with the specified question using the specified name server list. */ public Future> query( InetSocketAddress nameServerAddr, DnsQuestion question, Promise> promise) { return query0(nameServerAddr, question, EMTPY_ADDITIONALS, promise); } /** * Sends a DNS query with the specified question with additional records using the specified name server list. */ public Future> query( InetSocketAddress nameServerAddr, DnsQuestion question, Iterable additionals, Promise> promise) { return query0(nameServerAddr, question, toArray(additionals, false), promise); } Future> query0( InetSocketAddress nameServerAddr, DnsQuestion question, DnsRecord[] additionals, Promise> promise) { final Promise> castPromise = cast( checkNotNull(promise, "promise")); try { new DnsQueryContext(this, nameServerAddr, question, additionals, castPromise).query(); return castPromise; } catch (Exception e) { return castPromise.setFailure(e); } } @SuppressWarnings("unchecked") private static Promise> cast(Promise promise) { return (Promise>) promise; } private final class DnsResponseHandler extends ChannelInboundHandlerAdapter { private final Promise channelActivePromise; DnsResponseHandler(Promise channelActivePromise) { this.channelActivePromise = channelActivePromise; } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { try { final DatagramDnsResponse res = (DatagramDnsResponse) msg; final int queryId = res.id(); if (logger.isDebugEnabled()) { logger.debug("{} RECEIVED: [{}: {}], {}", ch, queryId, res.sender(), res); } final DnsQueryContext qCtx = queryContextManager.get(res.sender(), queryId); if (qCtx == null) { logger.warn("{} Received a DNS response with an unknown ID: {}", ch, queryId); return; } qCtx.finish(res); } finally { ReferenceCountUtil.safeRelease(msg); } } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { super.channelActive(ctx); channelActivePromise.setSuccess(ctx.channel()); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { logger.warn("{} Unexpected exception: ", ch, cause); } } }