Created
August 9, 2016 22:27
-
-
Save alexlehm/7659db45433605eb60de52f6e2d150ce to your computer and use it in GitHub Desktop.
DnsNameResolver.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * Copyright 2015 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.vertx.core.dns.impl.fix; | |
| 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.DatagramDnsResponseDecoder; | |
| import io.netty.handler.codec.dns.DnsQuestion; | |
| import io.netty.handler.codec.dns.DnsRecord; | |
| import io.netty.handler.codec.dns.DnsResponse; | |
| import io.netty.resolver.HostsFileEntriesResolver; | |
| import io.netty.resolver.InetNameResolver; | |
| import io.netty.resolver.dns.*; | |
| import io.netty.util.NetUtil; | |
| import io.netty.util.ReferenceCountUtil; | |
| import io.netty.util.concurrent.DefaultPromise; | |
| import io.netty.util.concurrent.FastThreadLocal; | |
| import io.netty.util.concurrent.Future; | |
| import io.netty.util.concurrent.FutureListener; | |
| import io.netty.util.concurrent.Promise; | |
| import io.netty.util.internal.UnstableApi; | |
| import io.netty.util.internal.logging.InternalLogger; | |
| import io.netty.util.internal.logging.InternalLoggerFactory; | |
| import java.net.IDN; | |
| import java.net.InetAddress; | |
| import java.net.InetSocketAddress; | |
| import java.net.UnknownHostException; | |
| import java.util.ArrayList; | |
| import java.util.Arrays; | |
| import java.util.Collections; | |
| import java.util.List; | |
| import static io.netty.util.internal.ObjectUtil.checkNonEmpty; | |
| import static io.netty.util.internal.ObjectUtil.checkNotNull; | |
| import static io.netty.util.internal.ObjectUtil.checkPositive; | |
| /** | |
| * A DNS-based {@link InetNameResolver}. | |
| */ | |
| @UnstableApi | |
| public class DnsNameResolver extends InetNameResolver { | |
| private static final InternalLogger logger = InternalLoggerFactory.getInstance(DnsNameResolver.class); | |
| static final InternetProtocolFamily[] DEFAULT_RESOLVE_ADDRESS_TYPES = new InternetProtocolFamily[2]; | |
| static { | |
| // Note that we did not use SystemPropertyUtil.getBoolean() here to emulate the behavior of JDK. | |
| if (Boolean.getBoolean("java.net.preferIPv4Stack")) { | |
| // hack to disable ipv6 resolution, proper fix will be this https://github.com/netty/netty/issues/5657 | |
| DEFAULT_RESOLVE_ADDRESS_TYPES[0] = InternetProtocolFamily.IPv4; | |
| DEFAULT_RESOLVE_ADDRESS_TYPES[1] = InternetProtocolFamily.IPv4; | |
| logger.debug("-Djava.net.preferIPv4Stack: true"); | |
| } else { | |
| if (Boolean.getBoolean("java.net.preferIPv6Addresses")) { | |
| DEFAULT_RESOLVE_ADDRESS_TYPES[0] = InternetProtocolFamily.IPv6; | |
| DEFAULT_RESOLVE_ADDRESS_TYPES[1] = InternetProtocolFamily.IPv4; | |
| logger.debug("-Djava.net.preferIPv6Addresses: true"); | |
| } else { | |
| DEFAULT_RESOLVE_ADDRESS_TYPES[0] = InternetProtocolFamily.IPv4; | |
| DEFAULT_RESOLVE_ADDRESS_TYPES[1] = InternetProtocolFamily.IPv6; | |
| logger.debug("-Djava.net.preferIPv6Addresses: false"); | |
| } | |
| } | |
| } | |
| private static final DatagramDnsResponseDecoder DECODER = new DatagramDnsResponseDecoder(); | |
| private static final DatagramDnsQueryEncoder ENCODER = new DatagramDnsQueryEncoder(); | |
| final DnsServerAddresses nameServerAddresses; | |
| final Future<Channel> 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<DnsServerAddressStream> nameServerAddrStream = | |
| new FastThreadLocal<DnsServerAddressStream>() { | |
| @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 List<String> searchDomains; | |
| private final int ndots; | |
| /** | |
| * 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 TODO | |
| * @param ndots TODO | |
| */ | |
| public DnsNameResolver( | |
| EventLoop eventLoop, | |
| ChannelFactory<? extends DatagramChannel> channelFactory, | |
| DnsServerAddresses nameServerAddresses, | |
| DnsCache resolveCache, | |
| long queryTimeoutMillis, | |
| InternetProtocolFamily[] resolvedAddressTypes, | |
| boolean recursionDesired, | |
| int maxQueriesPerResolve, | |
| boolean traceEnabled, | |
| int maxPayloadSize, | |
| boolean optResourceEnabled, | |
| HostsFileEntriesResolver hostsFileEntriesResolver, | |
| List<String> searchDomains, | |
| int ndots) { | |
| 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 = resolveCache; | |
| this.searchDomains = checkNotNull(searchDomains, "searchDomains"); | |
| this.ndots = checkPositive(ndots, "ndots"); | |
| 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().<Channel>newPromise()); | |
| b.handler(new ChannelInitializer<DatagramChannel>() { | |
| @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<InternetProtocolFamily> resolvedAddressTypes() { | |
| return Arrays.asList(resolvedAddressTypes); | |
| } | |
| InternetProtocolFamily[] resolveAddressTypesUnsafe() { | |
| return resolvedAddressTypes; | |
| } | |
| /** | |
| * 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() { | |
| ch.close(); | |
| } | |
| @Override | |
| protected EventLoop executor() { | |
| return (EventLoop) super.executor(); | |
| } | |
| private InetAddress resolveHostsFileEntry(String hostname) { | |
| return hostsFileEntriesResolver != null ? hostsFileEntriesResolver.address(hostname) : null; | |
| } | |
| @Override | |
| protected void doResolve(String inetHost, Promise<InetAddress> promise) throws Exception { | |
| doResolve(inetHost, 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 doResolve(String inetHost, | |
| Promise<InetAddress> 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, promise, resolveCache)) { | |
| doResolveUncached(hostname, promise, resolveCache); | |
| } | |
| } | |
| private boolean doResolveCached(String hostname, | |
| Promise<InetAddress> promise, | |
| DnsCache resolveCache) { | |
| final List<DnsCacheEntry> cachedEntries = resolveCache.get(hostname); | |
| if (cachedEntries == null) { | |
| 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) { | |
| setSuccess(promise, address); | |
| } else if (cause != null) { | |
| if (!promise.tryFailure(cause)) { | |
| logger.warn("Failed to notify failure to a promise: {}", promise, cause); | |
| } | |
| } else { | |
| return false; | |
| } | |
| return true; | |
| } | |
| private static void setSuccess(Promise<InetAddress> promise, InetAddress result) { | |
| if (!promise.trySuccess(result)) { | |
| logger.warn("Failed to notify success ({}) to a promise: {}", result, promise); | |
| } | |
| } | |
| private boolean searchDomains(String hostname) { | |
| if (searchDomains.isEmpty() || (hostname.length() > 0 && hostname.charAt(hostname.length() - 1) == '.')) { | |
| return false; | |
| } else { | |
| int idx = hostname.length(); | |
| int dots = 0; | |
| while (idx-- > 0) { | |
| if (hostname.charAt(idx) == '.') { | |
| dots++; | |
| } | |
| if (dots >= ndots) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| } | |
| private void doResolveUncached(String hostname, | |
| Promise<InetAddress> promise, | |
| DnsCache resolveCache) { | |
| doResolveUncached(hostname, promise, resolveCache, searchDomains.size() > 0 && !hostname.endsWith(".")); | |
| } | |
| private void doResolveUncached(String hostname, | |
| Promise<InetAddress> promise, | |
| DnsCache resolveCache, boolean trySearchDomain) { | |
| if (trySearchDomain) { | |
| Promise<InetAddress> original = promise; | |
| promise = new DefaultPromise<>(executor()); | |
| FutureListener<InetAddress> globalListener = future -> { | |
| if (future.isSuccess()) { | |
| original.setSuccess(future.getNow()); | |
| } else { | |
| FutureListener<InetAddress> sdListener = new FutureListener<InetAddress>() { | |
| int count; | |
| @Override | |
| public void operationComplete(Future<InetAddress> future) throws Exception { | |
| if (future.isSuccess()) { | |
| original.trySuccess(future.getNow()); | |
| } else { | |
| if (count < searchDomains.size()) { | |
| String searchDomain = searchDomains.get(count++); | |
| Promise<InetAddress> p = new DefaultPromise<>(executor()); | |
| doResolveUncached(hostname + "." + searchDomain, p, resolveCache, false); | |
| p.addListener(this); | |
| } else { | |
| original.tryFailure(future.cause()); | |
| } | |
| } | |
| } | |
| }; | |
| future.addListener(sdListener); | |
| } | |
| }; | |
| promise.addListener(globalListener); | |
| } | |
| if (searchDomains(hostname)) { | |
| promise.tryFailure(new UnknownHostException(hostname)); | |
| } else { | |
| final DnsNameResolverContext<InetAddress> ctx = | |
| new DnsNameResolverContext<InetAddress>(this, hostname, promise, resolveCache) { | |
| @Override | |
| protected boolean finishResolve( | |
| Class<? extends InetAddress> addressType, List<DnsCacheEntry> resolvedEntries) { | |
| final int numEntries = resolvedEntries.size(); | |
| for (int i = 0; i < numEntries; i++) { | |
| final InetAddress a = resolvedEntries.get(i).address(); | |
| if (addressType.isInstance(a)) { | |
| setSuccess(promise(), a); | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| }; | |
| ctx.resolve(); | |
| } | |
| } | |
| @Override | |
| protected void doResolveAll(String inetHost, Promise<List<InetAddress>> promise) throws Exception { | |
| doResolveAll(inetHost, 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, | |
| Promise<List<InetAddress>> 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, promise, resolveCache)) { | |
| doResolveAllUncached(hostname, promise, resolveCache); | |
| } | |
| } | |
| private boolean doResolveAllCached(String hostname, | |
| Promise<List<InetAddress>> promise, | |
| DnsCache resolveCache) { | |
| final List<DnsCacheEntry> cachedEntries = resolveCache.get(hostname); | |
| if (cachedEntries == null) { | |
| return false; | |
| } | |
| List<InetAddress> 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<InetAddress>(numEntries); | |
| } | |
| result.add(e.address()); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| if (result != null) { | |
| promise.trySuccess(result); | |
| } else if (cause != null) { | |
| promise.tryFailure(cause); | |
| } else { | |
| return false; | |
| } | |
| return true; | |
| } | |
| private void doResolveAllUncached(final String hostname, | |
| final Promise<List<InetAddress>> promise, | |
| DnsCache resolveCache) { | |
| final DnsNameResolverContext<List<InetAddress>> ctx = | |
| new DnsNameResolverContext<List<InetAddress>>(this, hostname, promise, resolveCache) { | |
| @Override | |
| protected boolean finishResolve( | |
| Class<? extends InetAddress> addressType, List<DnsCacheEntry> resolvedEntries) { | |
| List<InetAddress> 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<InetAddress>(numEntries); | |
| } | |
| result.add(a); | |
| } | |
| } | |
| if (result != null) { | |
| promise().trySuccess(result); | |
| return true; | |
| } | |
| return false; | |
| } | |
| }; | |
| ctx.resolve(); | |
| } | |
| private static String hostname(String inetHost) { | |
| return IDN.toASCII(inetHost); | |
| } | |
| /** | |
| * Sends a DNS query with the specified question. | |
| */ | |
| public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(DnsQuestion question) { | |
| return query(nextNameServerAddress(), question); | |
| } | |
| /** | |
| * Sends a DNS query with the specified question with additional records. | |
| */ | |
| public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query( | |
| DnsQuestion question, Iterable<DnsRecord> additional) { | |
| return query(nextNameServerAddress(), question, additional); | |
| } | |
| /** | |
| * Sends a DNS query with the specified question. | |
| */ | |
| public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query( | |
| DnsQuestion question, Promise<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>> promise) { | |
| return query(nextNameServerAddress(), question, Collections.<DnsRecord>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<AddressedEnvelope<DnsResponse, InetSocketAddress>> query( | |
| InetSocketAddress nameServerAddr, DnsQuestion question) { | |
| return query0(nameServerAddr, question, Collections.<DnsRecord>emptyList(), | |
| ch.eventLoop().<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>>newPromise()); | |
| } | |
| /** | |
| * Sends a DNS query with the specified question with additional records using the specified name server list. | |
| */ | |
| public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query( | |
| InetSocketAddress nameServerAddr, DnsQuestion question, Iterable<DnsRecord> additional) { | |
| return query0(nameServerAddr, question, additional, | |
| ch.eventLoop().<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>>newPromise()); | |
| } | |
| /** | |
| * Sends a DNS query with the specified question using the specified name server list. | |
| */ | |
| public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query( | |
| InetSocketAddress nameServerAddr, DnsQuestion question, | |
| Promise<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>> promise) { | |
| return query0(nameServerAddr, question, Collections.<DnsRecord>emptyList(), promise); | |
| } | |
| /** | |
| * Sends a DNS query with the specified question with additional records using the specified name server list. | |
| */ | |
| public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query( | |
| InetSocketAddress nameServerAddr, DnsQuestion question, | |
| Iterable<DnsRecord> additional, | |
| Promise<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>> promise) { | |
| return query0(nameServerAddr, question, additional, promise); | |
| } | |
| private Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query0( | |
| InetSocketAddress nameServerAddr, DnsQuestion question, | |
| Iterable<DnsRecord> additional, | |
| Promise<AddressedEnvelope<? extends DnsResponse, InetSocketAddress>> promise) { | |
| final Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>> castPromise = cast( | |
| checkNotNull(promise, "promise")); | |
| try { | |
| new DnsQueryContext(this, nameServerAddr, question, additional, castPromise).query(); | |
| return castPromise; | |
| } catch (Exception e) { | |
| return castPromise.setFailure(e); | |
| } | |
| } | |
| @SuppressWarnings("unchecked") | |
| private static Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>> cast(Promise<?> promise) { | |
| return (Promise<AddressedEnvelope<DnsResponse, InetSocketAddress>>) promise; | |
| } | |
| private final class DnsResponseHandler extends ChannelInboundHandlerAdapter { | |
| private final Promise<Channel> channelActivePromise; | |
| DnsResponseHandler(Promise<Channel> 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); | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment